From e7af0375131ec1beabe049ddf4149d8aad03e36c Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 30 Mar 2022 19:07:49 +0800 Subject: [PATCH 001/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4command=20input=20=E9=95=BF=E5=BA=A6=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#7996)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 修改命令command input max_length 1024 * perf: 修改命令command input 长度问题 * perf: 修改命令command input 长度问题 * perf: 修改命令command input 长度问题 * perf: 修改命令command input 长度问题 Co-authored-by: Jiangjie.Bai --- apps/common/utils/common.py | 21 +++++++++ apps/terminal/api/command.py | 2 +- apps/terminal/backends/command/db.py | 5 +- apps/terminal/backends/command/serializers.py | 46 ++++++++++--------- apps/terminal/models/command.py | 29 ++++++++++++ apps/terminal/serializers/__init__.py | 1 - apps/terminal/serializers/command.py | 11 ----- apps/terminal/serializers/terminal.py | 13 +++--- apps/users/models/user.py | 6 ++- apps/users/serializers/user.py | 8 +++- apps/users/utils.py | 16 ++++--- utils/generate_fake_data/generate.py | 2 + .../generate_fake_data/resources/terminal.py | 10 ++++ 13 files changed, 117 insertions(+), 53 deletions(-) delete mode 100644 apps/terminal/serializers/command.py create mode 100644 utils/generate_fake_data/resources/terminal.py diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index e5b9de4fd..d579eacfb 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -338,3 +338,24 @@ def get_file_by_arch(dir, filename): settings.BASE_DIR, dir, platform_name, arch, filename ) return file_path + + +def pretty_string(data: str, max_length=128, ellipsis_str='...'): + """ + params: + data: abcdefgh + max_length: 7 + ellipsis_str: ... + return: + ab...gh + """ + if len(data) < max_length: + return data + remain_length = max_length - len(ellipsis_str) + half = remain_length // 2 + if half <= 1: + return data[:max_length] + start = data[:half] + end = data[-half:] + data = f'{start}{ellipsis_str}{end}' + return data diff --git a/apps/terminal/api/command.py b/apps/terminal/api/command.py index 09ed27e30..22a2bb5c3 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/command.py @@ -14,7 +14,7 @@ from terminal.filters import CommandFilter from orgs.utils import current_org from common.drf.api import JMSBulkModelViewSet from common.utils import get_logger -from terminal.serializers import InsecureCommandAlertSerializer +from terminal.backends.command.serializers import InsecureCommandAlertSerializer from terminal.exceptions import StorageInvalid from ..backends import ( get_command_storage, get_multi_command_storage, diff --git a/apps/terminal/backends/command/db.py b/apps/terminal/backends/command/db.py index bb4fa957a..8b11569e9 100644 --- a/apps/terminal/backends/command/db.py +++ b/apps/terminal/backends/command/db.py @@ -4,6 +4,7 @@ import datetime from django.db import transaction from django.utils import timezone from django.db.utils import OperationalError +from common.utils.common import pretty_string from .base import CommandBase @@ -32,9 +33,11 @@ class CommandStore(CommandBase): """ _commands = [] for c in commands: + cmd_input = pretty_string(c['input']) + cmd_output = pretty_string(c['output'], max_length=1024) _commands.append(self.model( user=c["user"], asset=c["asset"], system_user=c["system_user"], - input=c["input"], output=c["output"], session=c["session"], + input=cmd_input, output=cmd_output, session=c["session"], risk_level=c.get("risk_level", 0), org_id=c["org_id"], timestamp=c["timestamp"] )) diff --git a/apps/terminal/backends/command/serializers.py b/apps/terminal/backends/command/serializers.py index 8114ddbb1..625c4282e 100644 --- a/apps/terminal/backends/command/serializers.py +++ b/apps/terminal/backends/command/serializers.py @@ -4,27 +4,19 @@ from rest_framework import serializers from .models import AbstractSessionCommand +__all__ = ['SessionCommandSerializer', 'InsecureCommandAlertSerializer'] -class SessionCommandSerializer(serializers.Serializer): - """使用这个类作为基础Command Log Serializer类, 用来序列化""" - id = serializers.UUIDField(read_only=True) +class SimpleSessionCommandSerializer(serializers.Serializer): + """ 简单Session命令序列类, 用来提取公共字段 """ user = serializers.CharField(label=_("User")) # 限制 64 字符,见 validate_user asset = serializers.CharField(max_length=128, label=_("Asset")) - system_user = serializers.CharField(max_length=64, label=_("System user")) - input = serializers.CharField(max_length=128, label=_("Command")) - output = serializers.CharField(max_length=1024, allow_blank=True, label=_("Output")) + input = serializers.CharField(max_length=2048, label=_("Command")) session = serializers.CharField(max_length=36, label=_("Session ID")) - risk_level = serializers.ChoiceField(required=False, label=_("Risk level"), choices=AbstractSessionCommand.RISK_LEVEL_CHOICES) - risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) + risk_level = serializers.ChoiceField( + required=False, label=_("Risk level"), choices=AbstractSessionCommand.RISK_LEVEL_CHOICES + ) org_id = serializers.CharField(max_length=36, required=False, default='', allow_null=True, allow_blank=True) - timestamp = serializers.IntegerField(label=_('Timestamp')) - remote_addr = serializers.CharField(read_only=True, label=_('Remote Address')) - - @staticmethod - def get_risk_level_display(obj): - risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES) - return risk_mapper.get(obj.risk_level) def validate_user(self, value): if len(value) > 64: @@ -32,9 +24,21 @@ class SessionCommandSerializer(serializers.Serializer): return value -class InsecureCommandAlertSerializer(serializers.Serializer): - input = serializers.CharField() - asset = serializers.CharField() - user = serializers.CharField() - risk_level = serializers.IntegerField() - session = serializers.UUIDField() +class InsecureCommandAlertSerializer(SimpleSessionCommandSerializer): + pass + + +class SessionCommandSerializer(SimpleSessionCommandSerializer): + """使用这个类作为基础Command Log Serializer类, 用来序列化""" + + id = serializers.UUIDField(read_only=True) + system_user = serializers.CharField(max_length=64, label=_("System user")) + output = serializers.CharField(max_length=2048, allow_blank=True, label=_("Output")) + risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) + timestamp = serializers.IntegerField(label=_('Timestamp')) + remote_addr = serializers.CharField(read_only=True, label=_('Remote Address')) + + @staticmethod + def get_risk_level_display(obj): + risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES) + return risk_mapper.get(obj.risk_level) diff --git a/apps/terminal/models/command.py b/apps/terminal/models/command.py index 7609902f2..3e94740ff 100644 --- a/apps/terminal/models/command.py +++ b/apps/terminal/models/command.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +import time + from django.db import models from django.db.models.signals import post_save from django.utils.translation import ugettext_lazy as _ @@ -18,6 +20,33 @@ class CommandManager(models.Manager): class Command(AbstractSessionCommand): objects = CommandManager() + @classmethod + def generate_fake(cls, count=100, org=None): + import uuid + import datetime + from orgs.models import Organization + from common.utils import random_string + + if not org: + org = Organization.default() + + d = datetime.datetime.now() - datetime.timedelta(days=1) + commands = [ + cls(**{ + 'user': random_string(6), + 'asset': random_string(10), + 'system_user': random_string(6), + 'session': str(uuid.uuid4()), + 'input': random_string(16), + 'output': random_string(64), + 'timestamp': int(d.timestamp()), + 'org_id': str(org.id) + }) + for i in range(count) + ] + cls.objects.bulk_create(commands) + print(f'Create {len(commands)} commands of org ({org})') + class Meta: db_table = "terminal_command" ordering = ('-timestamp',) diff --git a/apps/terminal/serializers/__init__.py b/apps/terminal/serializers/__init__.py index a2a5bbf30..4e868cabc 100644 --- a/apps/terminal/serializers/__init__.py +++ b/apps/terminal/serializers/__init__.py @@ -3,5 +3,4 @@ from .terminal import * from .session import * from .storage import * -from .command import * from .sharing import * diff --git a/apps/terminal/serializers/command.py b/apps/terminal/serializers/command.py deleted file mode 100644 index 01343e825..000000000 --- a/apps/terminal/serializers/command.py +++ /dev/null @@ -1,11 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -from rest_framework import serializers - - -class InsecureCommandAlertSerializer(serializers.Serializer): - input = serializers.CharField() - asset = serializers.CharField() - user = serializers.CharField() - risk_level = serializers.IntegerField() - session = serializers.UUIDField() - org_id = serializers.CharField() diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index be991d8a8..dd6565348 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import BulkModelSerializer, AdaptedBulkListSerializer from common.utils import is_uuid from users.serializers import ServiceAccountSerializer -from common.utils import get_request_ip +from common.utils import get_request_ip, pretty_string from .. import const from ..models import ( @@ -111,12 +111,11 @@ class TerminalRegistrationSerializer(serializers.ModelSerializer): valid = super().is_valid(raise_exception=raise_exception) if not valid: return valid - name = self.validated_data.get('name') - if len(name) > 128: - self.validated_data['comment'] = name - name = '{}...{}'.format(name[:32], name[-32:]) - self.validated_data['name'] = name - + raw_name = self.validated_data.get('name') + name = pretty_string(raw_name) + self.validated_data['name'] = name + if len(raw_name) > 128: + self.validated_data['comment'] = raw_name data = {'name': name} kwargs = {'data': data} if self.instance and self.instance.user: diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 3aae3223b..1a5ce634c 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -401,10 +401,12 @@ class RoleMixin: def is_staff(self, value): pass + service_account_email_suffix = '@local.domain' + @classmethod - def create_service_account(cls, name, comment): + def create_service_account(cls, name, email, comment): app = cls.objects.create( - username=name, name=name, email='{}@local.domain'.format(name), + username=name, name=name, email=email, comment=comment, is_first_login=False, created_by='System', is_service_account=True, ) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index b998e8f6d..56409addf 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -6,6 +6,7 @@ from rest_framework import serializers from common.mixins import CommonBulkSerializerMixin from common.validators import PhoneValidator +from common.utils import pretty_string from rbac.builtin import BuiltinRole from rbac.permissions import RBACPermission from rbac.models import OrgRoleBinding, SystemRoleBinding, Role @@ -268,7 +269,9 @@ class ServiceAccountSerializer(serializers.ModelSerializer): def get_email(self): name = self.initial_data.get('name') - return '{}@serviceaccount.local'.format(name) + name_max_length = 128 - len(User.service_account_email_suffix) + name = pretty_string(name, max_length=name_max_length, ellipsis_str='-') + return '{}{}'.format(name, User.service_account_email_suffix) def validate_name(self, name): email = self.get_email() @@ -283,6 +286,7 @@ class ServiceAccountSerializer(serializers.ModelSerializer): def create(self, validated_data): name = validated_data['name'] + email = self.get_email() comment = validated_data.get('comment', '') - user, ak = User.create_service_account(name, comment) + user, ak = User.create_service_account(name, email, comment) return user diff --git a/apps/users/utils.py b/apps/users/utils.py index 3602d67ee..b32b0c0e0 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -11,7 +11,7 @@ from django.conf import settings from django.core.cache import cache from common.tasks import send_mail_async -from common.utils import reverse, get_object_or_none, ip +from common.utils import reverse, get_object_or_none, ip, pretty_string from .models import User logger = logging.getLogger('jumpserver') @@ -229,12 +229,14 @@ class LoginIpBlockUtil(BlockGlobalIpUtilBase): BLOCK_KEY_TMPL = "_LOGIN_BLOCK_{}" -def construct_user_email(username, email): - if '@' not in email: - if '@' in username: - email = username - else: - email = '{}@{}'.format(username, settings.EMAIL_SUFFIX) +def construct_user_email(username, email, email_suffix=''): + if '@' in email: + return email + if '@' in username: + return username + if not email_suffix: + email_suffix = settings.EMAIL_SUFFIX + email = f'{username}@{email_suffix}' return email diff --git a/utils/generate_fake_data/generate.py b/utils/generate_fake_data/generate.py index 839295049..fb41d7e1d 100644 --- a/utils/generate_fake_data/generate.py +++ b/utils/generate_fake_data/generate.py @@ -15,6 +15,7 @@ django.setup() from resources.assets import AssetsGenerator, NodesGenerator, SystemUsersGenerator, AdminUsersGenerator from resources.users import UserGroupGenerator, UserGenerator from resources.perms import AssetPermissionGenerator +from resources.terminal import CommandGenerator # from resources.system import StatGenerator @@ -26,6 +27,7 @@ resource_generator_mapper = { 'user': UserGenerator, 'user_group': UserGroupGenerator, 'asset_permission': AssetPermissionGenerator, + 'command': CommandGenerator, # 'stat': StatGenerator } diff --git a/utils/generate_fake_data/resources/terminal.py b/utils/generate_fake_data/resources/terminal.py new file mode 100644 index 000000000..48d825b47 --- /dev/null +++ b/utils/generate_fake_data/resources/terminal.py @@ -0,0 +1,10 @@ +from .base import FakeDataGenerator +from terminal.models import Command + + +class CommandGenerator(FakeDataGenerator): + resource = 'command' + + def do_generate(self, batch, batch_size): + Command.generate_fake(len(batch), self.org) + From c58d2456363371d6fd05852a0e3de7bf522a6512 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 30 Mar 2022 19:51:56 +0800 Subject: [PATCH 002/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dkoko=20setting?= =?UTF-8?q?=20(#8005)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/settings/serializers/terminal.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index 1ad5d9e75..c6073aa28 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -39,8 +39,12 @@ class TerminalSettingSerializer(serializers.Serializer): ) XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP")) - TERMINAL_KOKO_HOST = serializers.BooleanField(label=_("Koko host")) - TERMINAL_KOKO_SSH_PORT = serializers.BooleanField(label=_("Koko ssh port")) + TERMINAL_KOKO_HOST = serializers.CharField( + required=False, label=_("Koko host"), max_length=1024 + ) + TERMINAL_KOKO_SSH_PORT = serializers.CharField( + required=False, label=_("Koko ssh port"), max_length=1024 + ) TERMINAL_MAGNUS_ENABLED = serializers.BooleanField(label=_("Enable database proxy")) TERMINAL_MAGNUS_HOST = serializers.CharField( @@ -59,4 +63,3 @@ class TerminalSettingSerializer(serializers.Serializer): required=False, label=_("PostgreSQL port"), default=54320, help_text=_('PostgreSQL protocol listen port') ) - From 73cb5e10b414403b91b1acb3b34e7999153ccc65 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 30 Mar 2022 19:30:27 +0800 Subject: [PATCH 003/258] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=B8=8D=E8=83=BD=E8=87=AA=E6=9B=B4=E6=96=B0=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E9=80=BB=E8=BE=91=20&=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7is=5Factive=E5=88=9B=E5=BB=BA=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 添加用户不能自更新字段逻辑 & 修复用户is_active创建失败的问题 fix: 添加用户不能自更新字段逻辑 & 修复用户is_active创建失败的问题 --- apps/users/serializers/user.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 56409addf..3014aa2d9 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -133,6 +133,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer 'date_joined', 'last_login', 'created_by', 'is_first_login', 'wecom_id', 'dingtalk_id', 'feishu_id' ] + disallow_self_update_fields = ['is_active'] extra_kwargs = { 'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True}, 'public_key': {'write_only': True}, @@ -181,7 +182,23 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer attrs.pop(field, None) return attrs + def check_disallow_self_update_fields(self, attrs): + request = self.context.get('request') + if not request or not request.user.is_authenticated: + return attrs + if not self.instance: + return attrs + if request.user.id != self.instance.id: + return attrs + disallow_fields = set(list(attrs.keys())) & set(self.Meta.disallow_self_update_fields) + if not disallow_fields: + return attrs + # 用户自己不能更新自己的一些字段 + error = 'User Cannot self-update fields: {}'.format(disallow_fields) + raise serializers.ValidationError(error) + def validate(self, attrs): + attrs = self.check_disallow_self_update_fields(attrs) attrs = self.change_password_to_raw(attrs) attrs = self.clean_auth_fields(attrs) attrs.pop('password_strategy', None) @@ -206,17 +223,6 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer field.set(value) return instance - def validate_is_active(self, is_active): - request = self.context.get('request') - if not request or not request.user.is_authenticated: - return is_active - - user = request.user - if user.id == self.instance.id and not is_active: - # 用户自己不能禁用启用自己 - raise serializers.ValidationError("Cannot inactive self") - return is_active - def update(self, instance, validated_data): save_handler = partial(super().update, instance) instance = self.save_and_set_custom_m2m_fields(validated_data, save_handler, created=False) From eff562505e2480f4a8d7b8252e866a1ff705c477 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 31 Mar 2022 11:22:48 +0800 Subject: [PATCH 004/258] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.po | 170 ++++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.po | 170 ++++++++++++++------------- apps/users/serializers/user.py | 2 +- 3 files changed, 175 insertions(+), 167 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a6c716d25..bc5707e14 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-29 18:26+0800\n" +"POT-Creation-Date: 2022-03-31 11:21+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -31,7 +31,7 @@ msgstr "Acls" #: 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:659 +#: users/models/group.py:15 users/models/user.py:661 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -67,7 +67,7 @@ msgstr "アクティブ" #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 -#: users/models/group.py:16 users/models/user.py:696 +#: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 @@ -95,8 +95,8 @@ msgstr "ログイン確認" #: terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:42 #: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:884 -#: users/models/user.py:915 users/serializers/group.py:19 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 +#: users/models/user.py:917 users/serializers/group.py:19 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 #: users/templates/users/user_database_app_permission.html:37 @@ -170,7 +170,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:657 +#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 #: users/templates/users/_msg_user_created.html:12 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:34 @@ -285,7 +285,7 @@ msgstr "アプリケーション" #: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 #: perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 +#: terminal/backends/command/serializers.py:35 terminal/models/session.py:46 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -380,7 +380,7 @@ msgstr "タイプ表示" #: assets/serializers/cmd_filter.py:49 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:916 +#: users/models/group.py:18 users/models/user.py:918 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "作成された日付" @@ -632,7 +632,7 @@ msgstr "ラベル" #: assets/models/cluster.py:28 assets/models/cmd_filter.py:52 #: assets/models/cmd_filter.py:99 assets/models/group.py:21 #: common/db/models.py:111 common/mixins/models.py:49 orgs/models.py:66 -#: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:704 +#: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 #: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 @@ -813,7 +813,7 @@ msgstr "帯域幅" msgid "Contact" msgstr "連絡先" -#: assets/models/cluster.py:22 users/models/user.py:679 +#: assets/models/cluster.py:22 users/models/user.py:681 msgid "Phone" msgstr "電話" @@ -839,7 +839,7 @@ msgid "Default" msgstr "デフォルト" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:901 +#: users/models/user.py:903 msgid "System" msgstr "システム" @@ -848,7 +848,7 @@ msgid "Default Cluster" msgstr "デフォルトクラスター" #: assets/models/cmd_filter.py:34 perms/models/base.py:86 -#: users/models/group.py:31 users/models/user.py:665 +#: users/models/group.py:31 users/models/user.py:667 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 @@ -866,7 +866,7 @@ msgid "Regex" msgstr "正規情報" #: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:53 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:53 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -1510,7 +1510,7 @@ msgstr "ユーザーエージェント" #: audits/models.py:124 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:64 users/models/user.py:682 +#: users/forms/profile.py:64 users/models/user.py:684 #: users/serializers/profile.py:121 msgid "MFA" msgstr "MFA" @@ -1588,13 +1588,13 @@ msgstr "認証トークン" #: audits/signal_handlers.py:71 authentication/notifications.py:73 #: authentication/views/login.py:164 authentication/views/wecom.py:181 -#: notifications/backends/__init__.py:11 users/models/user.py:718 +#: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企業微信" #: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 -#: users/models/user.py:719 +#: users/models/user.py:721 msgid "DingTalk" msgstr "DingTalk" @@ -2134,14 +2134,14 @@ msgid "Show" msgstr "表示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: settings/serializers/security.py:39 users/models/user.py:554 +#: settings/serializers/security.py:39 users/models/user.py:556 #: users/serializers/profile.py:111 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "無効化" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:555 users/serializers/profile.py:112 +#: users/models/user.py:557 users/serializers/profile.py:112 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2399,7 +2399,7 @@ msgid "The FeiShu is already bound to another user" msgstr "FeiShuはすでに別のユーザーにバインドされています" #: authentication/views/feishu.py:148 authentication/views/login.py:176 -#: notifications/backends/__init__.py:14 users/models/user.py:720 +#: notifications/backends/__init__.py:14 users/models/user.py:722 msgid "FeiShu" msgstr "本を飛ばす" @@ -2699,7 +2699,7 @@ msgid "Notifications" msgstr "通知" #: notifications/backends/__init__.py:10 users/forms/profile.py:101 -#: users/models/user.py:661 +#: users/models/user.py:663 msgid "Email" msgstr "メール" @@ -2925,7 +2925,7 @@ msgid "Can view root org" msgstr "グローバル組織を表示できます" #: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:43 -#: users/models/user.py:669 users/templates/users/_select_user_modal.html:15 +#: users/models/user.py:671 users/templates/users/_select_user_modal.html:15 msgid "Role" msgstr "ロール" @@ -3012,7 +3012,7 @@ msgstr "クリップボードコピーペースト" #: perms/models/base.py:90 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 -#: users/models/user.py:701 +#: users/models/user.py:703 msgid "Date expired" msgstr "期限切れの日付" @@ -3055,15 +3055,15 @@ msgstr "Organization {} のアプリケーション権限" #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:139 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 msgid "Is valid" msgstr "有効です" #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 -#: perms/serializers/asset/permission.py:44 users/serializers/user.py:85 -#: users/serializers/user.py:141 +#: perms/serializers/asset/permission.py:44 users/serializers/user.py:86 +#: users/serializers/user.py:143 msgid "Is expired" msgstr "期限切れです" @@ -4268,47 +4268,47 @@ msgstr "RDP訪問先住所、例: dev.jumpserver.org:3389" msgid "Enable XRDP" msgstr "XRDPの有効化" -#: settings/serializers/terminal.py:42 +#: settings/serializers/terminal.py:43 msgid "Koko host" msgstr "KOKO ホストアドレス" -#: settings/serializers/terminal.py:43 +#: settings/serializers/terminal.py:46 msgid "Koko ssh port" msgstr "Koko ssh ポート" -#: settings/serializers/terminal.py:45 +#: settings/serializers/terminal.py:49 msgid "Enable database proxy" msgstr "属性マップの有効化" -#: settings/serializers/terminal.py:47 +#: settings/serializers/terminal.py:51 msgid "Database proxy host" msgstr "データベースエージェントホスト" -#: settings/serializers/terminal.py:48 +#: settings/serializers/terminal.py:52 msgid "Database proxy host, eg: dev.jumpserver.org" msgstr "RDP訪問先住所、例: dev.jumpserver.org:3389" -#: settings/serializers/terminal.py:51 +#: settings/serializers/terminal.py:55 msgid "MySQL port" msgstr "MySQLポート" -#: settings/serializers/terminal.py:52 +#: settings/serializers/terminal.py:56 msgid "MySQL protocol listen port" msgstr "MySQLプロトコルリッスンポート" -#: settings/serializers/terminal.py:55 +#: settings/serializers/terminal.py:59 msgid "MariaDB port" msgstr "MariaDBポート" -#: settings/serializers/terminal.py:56 +#: settings/serializers/terminal.py:60 msgid "MariaDB protocol listen port" msgstr "MariaDBプロトコルリッスンポート" -#: settings/serializers/terminal.py:59 +#: settings/serializers/terminal.py:63 msgid "PostgreSQL port" msgstr "PostgreSQLポート" -#: settings/serializers/terminal.py:60 +#: settings/serializers/terminal.py:64 msgid "PostgreSQL protocol listen port" msgstr "PostgreSQLプロトコルリッスンポート" @@ -4859,7 +4859,7 @@ msgid "Input" msgstr "入力" #: terminal/backends/command/models.py:23 -#: terminal/backends/command/serializers.py:16 +#: terminal/backends/command/serializers.py:36 msgid "Output" msgstr "出力" @@ -4870,23 +4870,23 @@ msgid "Session" msgstr "セッション" #: terminal/backends/command/models.py:25 -#: terminal/backends/command/serializers.py:18 +#: terminal/backends/command/serializers.py:17 msgid "Risk level" msgstr "リスクレベル" -#: terminal/backends/command/serializers.py:17 +#: terminal/backends/command/serializers.py:15 msgid "Session ID" msgstr "セッションID" -#: terminal/backends/command/serializers.py:19 +#: terminal/backends/command/serializers.py:37 msgid "Risk level display" msgstr "リスクレベル表示" -#: terminal/backends/command/serializers.py:21 +#: terminal/backends/command/serializers.py:38 msgid "Timestamp" msgstr "タイムスタンプ" -#: terminal/backends/command/serializers.py:22 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:39 terminal/models/terminal.py:105 msgid "Remote Address" msgstr "リモートアドレス" @@ -4915,7 +4915,7 @@ msgstr "一括作成非サポート" msgid "Storage is invalid" msgstr "ストレージが無効です" -#: terminal/models/command.py:24 +#: terminal/models/command.py:53 msgid "Command record" msgstr "コマンドレコード" @@ -5140,7 +5140,7 @@ msgstr "エンドポイントが無効: パス '{}' を削除" msgid "Bucket" msgstr "バケット" -#: terminal/serializers/storage.py:34 users/models/user.py:693 +#: terminal/serializers/storage.py:34 users/models/user.py:695 msgid "Secret key" msgstr "秘密キー" @@ -5747,68 +5747,68 @@ msgstr "公開鍵は古いものと同じであってはなりません。" msgid "Not a valid ssh public key" msgstr "有効なssh公開鍵ではありません" -#: users/forms/profile.py:160 users/models/user.py:690 +#: users/forms/profile.py:160 users/models/user.py:692 #: users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "公開キー" -#: users/models/user.py:556 +#: users/models/user.py:558 msgid "Force enable" msgstr "強制有効" -#: users/models/user.py:623 +#: users/models/user.py:625 msgid "Local" msgstr "ローカル" -#: users/models/user.py:671 users/serializers/user.py:140 +#: users/models/user.py:673 users/serializers/user.py:142 msgid "Is service account" msgstr "サービスアカウントです" -#: users/models/user.py:673 +#: users/models/user.py:675 msgid "Avatar" msgstr "アバター" -#: users/models/user.py:676 +#: users/models/user.py:678 msgid "Wechat" msgstr "微信" -#: users/models/user.py:687 +#: users/models/user.py:689 msgid "Private key" msgstr "ssh秘密鍵" -#: users/models/user.py:709 +#: users/models/user.py:711 msgid "Source" msgstr "ソース" -#: users/models/user.py:713 +#: users/models/user.py:715 msgid "Date password last updated" msgstr "最終更新日パスワード" -#: users/models/user.py:716 +#: users/models/user.py:718 msgid "Need update password" msgstr "更新パスワードが必要" -#: users/models/user.py:886 +#: users/models/user.py:888 msgid "Can invite user" msgstr "ユーザーを招待できます" -#: users/models/user.py:887 +#: users/models/user.py:889 msgid "Can remove user" msgstr "ユーザーを削除できます" -#: users/models/user.py:888 +#: users/models/user.py:890 msgid "Can match user" msgstr "ユーザーに一致できます" -#: users/models/user.py:897 +#: users/models/user.py:899 msgid "Administrator" msgstr "管理者" -#: users/models/user.py:900 +#: users/models/user.py:902 msgid "Administrator is the super user of system" msgstr "管理者はシステムのスーパーユーザーです" -#: users/models/user.py:925 +#: users/models/user.py:927 msgid "User password history" msgstr "ユーザーパスワード履歴" @@ -5859,97 +5859,101 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:142 users/serializers/user.py:138 +#: users/serializers/profile.py:142 users/serializers/user.py:140 msgid "Is first login" msgstr "最初のログインです" -#: users/serializers/user.py:24 users/serializers/user.py:31 +#: users/serializers/user.py:25 users/serializers/user.py:32 msgid "System roles" msgstr "システムの役割" -#: users/serializers/user.py:29 users/serializers/user.py:32 +#: users/serializers/user.py:30 users/serializers/user.py:33 msgid "Org roles" msgstr "組織ロール" -#: users/serializers/user.py:77 +#: users/serializers/user.py:78 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:22 msgid "Password strategy" msgstr "パスワード戦略" -#: users/serializers/user.py:79 +#: users/serializers/user.py:80 msgid "MFA enabled" msgstr "MFA有効化" -#: users/serializers/user.py:80 +#: users/serializers/user.py:81 msgid "MFA force enabled" msgstr "MFAフォース有効化" -#: users/serializers/user.py:82 +#: users/serializers/user.py:83 msgid "MFA level display" msgstr "MFAレベル表示" -#: users/serializers/user.py:84 +#: users/serializers/user.py:85 msgid "Login blocked" msgstr "ログインブロック" -#: users/serializers/user.py:87 +#: users/serializers/user.py:88 msgid "Can public key authentication" msgstr "公開鍵認証が可能" -#: users/serializers/user.py:142 +#: users/serializers/user.py:144 msgid "Avatar url" msgstr "アバターURL" -#: users/serializers/user.py:144 +#: users/serializers/user.py:146 msgid "Groups name" msgstr "グループ名" -#: users/serializers/user.py:145 +#: users/serializers/user.py:147 msgid "Source name" msgstr "ソース名" -#: users/serializers/user.py:146 +#: users/serializers/user.py:148 msgid "Organization role name" msgstr "組織の役割名" -#: users/serializers/user.py:147 +#: users/serializers/user.py:149 msgid "Super role name" msgstr "スーパーロール名" -#: users/serializers/user.py:148 +#: users/serializers/user.py:150 msgid "Total role name" msgstr "合計ロール名" -#: users/serializers/user.py:150 +#: users/serializers/user.py:152 msgid "Is wecom bound" msgstr "企業の微信をバインドしているかどうか" -#: users/serializers/user.py:151 +#: users/serializers/user.py:153 msgid "Is dingtalk bound" msgstr "ピンをバインドしているかどうか" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Is feishu bound" msgstr "飛本を縛ったかどうか" -#: users/serializers/user.py:153 +#: users/serializers/user.py:155 msgid "Is OTP bound" msgstr "仮想MFAがバインドされているか" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "System role name" msgstr "システムロール名" -#: users/serializers/user.py:247 +#: users/serializers/user.py:197 +msgid "User cannot self-update fields: {}" +msgstr "ユーザーは自分のフィールドを更新できません: {}" + +#: users/serializers/user.py:254 msgid "Select users" msgstr "ユーザーの選択" -#: users/serializers/user.py:248 +#: users/serializers/user.py:255 msgid "For security, only list several users" msgstr "セキュリティのために、複数のユーザーのみをリストします" -#: users/serializers/user.py:281 +#: users/serializers/user.py:290 msgid "name not unique" msgstr "名前が一意ではない" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ee4727335..ffd8d4ba3 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-29 18:26+0800\n" +"POT-Creation-Date: 2022-03-31 11:19+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -30,7 +30,7 @@ msgstr "访问控制" #: 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:659 +#: users/models/group.py:15 users/models/user.py:661 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -66,7 +66,7 @@ msgstr "激活中" #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 -#: users/models/group.py:16 users/models/user.py:696 +#: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 @@ -94,8 +94,8 @@ msgstr "登录复核" #: terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:42 #: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:884 -#: users/models/user.py:915 users/serializers/group.py:19 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 +#: users/models/user.py:917 users/serializers/group.py:19 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 #: users/templates/users/user_database_app_permission.html:37 @@ -169,7 +169,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:657 +#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 #: users/templates/users/_msg_user_created.html:12 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:34 @@ -280,7 +280,7 @@ msgstr "应用程序" #: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 #: perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 +#: terminal/backends/command/serializers.py:35 terminal/models/session.py:46 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -375,7 +375,7 @@ msgstr "类型名称" #: assets/serializers/cmd_filter.py:49 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:916 +#: users/models/group.py:18 users/models/user.py:918 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "创建日期" @@ -627,7 +627,7 @@ msgstr "标签管理" #: assets/models/cluster.py:28 assets/models/cmd_filter.py:52 #: assets/models/cmd_filter.py:99 assets/models/group.py:21 #: common/db/models.py:111 common/mixins/models.py:49 orgs/models.py:66 -#: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:704 +#: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 #: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 @@ -808,7 +808,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:679 +#: assets/models/cluster.py:22 users/models/user.py:681 msgid "Phone" msgstr "手机" @@ -834,7 +834,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:901 +#: users/models/user.py:903 msgid "System" msgstr "系统" @@ -843,7 +843,7 @@ msgid "Default Cluster" msgstr "默认Cluster" #: assets/models/cmd_filter.py:34 perms/models/base.py:86 -#: users/models/group.py:31 users/models/user.py:665 +#: users/models/group.py:31 users/models/user.py:667 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 @@ -861,7 +861,7 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:53 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:53 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -1498,7 +1498,7 @@ msgstr "用户代理" #: audits/models.py:124 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:64 users/models/user.py:682 +#: users/forms/profile.py:64 users/models/user.py:684 #: users/serializers/profile.py:121 msgid "MFA" msgstr "MFA" @@ -1576,13 +1576,13 @@ msgstr "认证令牌" #: audits/signal_handlers.py:71 authentication/notifications.py:73 #: authentication/views/login.py:164 authentication/views/wecom.py:181 -#: notifications/backends/__init__.py:11 users/models/user.py:718 +#: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企业微信" #: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 -#: users/models/user.py:719 +#: users/models/user.py:721 msgid "DingTalk" msgstr "钉钉" @@ -2113,14 +2113,14 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: settings/serializers/security.py:39 users/models/user.py:554 +#: settings/serializers/security.py:39 users/models/user.py:556 #: users/serializers/profile.py:111 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:555 users/serializers/profile.py:112 +#: users/models/user.py:557 users/serializers/profile.py:112 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2369,7 +2369,7 @@ msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" #: authentication/views/feishu.py:148 authentication/views/login.py:176 -#: notifications/backends/__init__.py:14 users/models/user.py:720 +#: notifications/backends/__init__.py:14 users/models/user.py:722 msgid "FeiShu" msgstr "飞书" @@ -2664,7 +2664,7 @@ msgid "Notifications" msgstr "通知" #: notifications/backends/__init__.py:10 users/forms/profile.py:101 -#: users/models/user.py:661 +#: users/models/user.py:663 msgid "Email" msgstr "邮件" @@ -2890,7 +2890,7 @@ msgid "Can view root org" msgstr "可以查看全局组织" #: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:43 -#: users/models/user.py:669 users/templates/users/_select_user_modal.html:15 +#: users/models/user.py:671 users/templates/users/_select_user_modal.html:15 msgid "Role" msgstr "角色" @@ -2977,7 +2977,7 @@ msgstr "剪贴板复制粘贴" #: perms/models/base.py:90 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 -#: users/models/user.py:701 +#: users/models/user.py:703 msgid "Date expired" msgstr "失效日期" @@ -3020,15 +3020,15 @@ msgstr "组织 ({}) 的应用授权" #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:139 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 msgid "Is valid" msgstr "账号是否有效" #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 -#: perms/serializers/asset/permission.py:44 users/serializers/user.py:85 -#: users/serializers/user.py:141 +#: perms/serializers/asset/permission.py:44 users/serializers/user.py:86 +#: users/serializers/user.py:143 msgid "Is expired" msgstr "已过期" @@ -4206,47 +4206,47 @@ msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389" msgid "Enable XRDP" msgstr "启用 XRDP 服务" -#: settings/serializers/terminal.py:42 +#: settings/serializers/terminal.py:43 msgid "Koko host" msgstr "KOKO 主机地址" -#: settings/serializers/terminal.py:43 +#: settings/serializers/terminal.py:46 msgid "Koko ssh port" msgstr "KOKO ssh 端口" -#: settings/serializers/terminal.py:45 +#: settings/serializers/terminal.py:49 msgid "Enable database proxy" msgstr "启用数据库组件" -#: settings/serializers/terminal.py:47 +#: settings/serializers/terminal.py:51 msgid "Database proxy host" msgstr "数据库主机地址" -#: settings/serializers/terminal.py:48 +#: settings/serializers/terminal.py:52 msgid "Database proxy host, eg: dev.jumpserver.org" msgstr "数据库组件地址, 如: dev.jumpserver.org (没有端口, 不同协议端口不同)" -#: settings/serializers/terminal.py:51 +#: settings/serializers/terminal.py:55 msgid "MySQL port" msgstr "MySQL 协议端口" -#: settings/serializers/terminal.py:52 +#: settings/serializers/terminal.py:56 msgid "MySQL protocol listen port" msgstr "MySQL 协议监听端口" -#: settings/serializers/terminal.py:55 +#: settings/serializers/terminal.py:59 msgid "MariaDB port" msgstr "MariaDB 端口" -#: settings/serializers/terminal.py:56 +#: settings/serializers/terminal.py:60 msgid "MariaDB protocol listen port" msgstr "MariaDB 协议监听的端口" -#: settings/serializers/terminal.py:59 +#: settings/serializers/terminal.py:63 msgid "PostgreSQL port" msgstr "PostgreSQL 端口" -#: settings/serializers/terminal.py:60 +#: settings/serializers/terminal.py:64 msgid "PostgreSQL protocol listen port" msgstr "PostgreSQL 协议监听端口" @@ -4786,7 +4786,7 @@ msgid "Input" msgstr "输入" #: terminal/backends/command/models.py:23 -#: terminal/backends/command/serializers.py:16 +#: terminal/backends/command/serializers.py:36 msgid "Output" msgstr "输出" @@ -4797,23 +4797,23 @@ msgid "Session" msgstr "会话" #: terminal/backends/command/models.py:25 -#: terminal/backends/command/serializers.py:18 +#: terminal/backends/command/serializers.py:17 msgid "Risk level" msgstr "风险等级" -#: terminal/backends/command/serializers.py:17 +#: terminal/backends/command/serializers.py:15 msgid "Session ID" msgstr "会话ID" -#: terminal/backends/command/serializers.py:19 +#: terminal/backends/command/serializers.py:37 msgid "Risk level display" msgstr "风险等级名称" -#: terminal/backends/command/serializers.py:21 +#: terminal/backends/command/serializers.py:38 msgid "Timestamp" msgstr "时间戳" -#: terminal/backends/command/serializers.py:22 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:39 terminal/models/terminal.py:105 msgid "Remote Address" msgstr "远端地址" @@ -4842,7 +4842,7 @@ msgstr "不支持批量创建" msgid "Storage is invalid" msgstr "存储无效" -#: terminal/models/command.py:24 +#: terminal/models/command.py:53 msgid "Command record" msgstr "命令记录" @@ -5067,7 +5067,7 @@ msgstr "端点无效: 移除路径 `{}`" msgid "Bucket" msgstr "桶名称" -#: terminal/serializers/storage.py:34 users/models/user.py:693 +#: terminal/serializers/storage.py:34 users/models/user.py:695 msgid "Secret key" msgstr "密钥" @@ -5670,68 +5670,68 @@ msgstr "不能和原来的密钥相同" msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/profile.py:160 users/models/user.py:690 +#: users/forms/profile.py:160 users/models/user.py:692 #: users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "SSH公钥" -#: users/models/user.py:556 +#: users/models/user.py:558 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:623 +#: users/models/user.py:625 msgid "Local" msgstr "数据库" -#: users/models/user.py:671 users/serializers/user.py:140 +#: users/models/user.py:673 users/serializers/user.py:142 msgid "Is service account" msgstr "服务账号" -#: users/models/user.py:673 +#: users/models/user.py:675 msgid "Avatar" msgstr "头像" -#: users/models/user.py:676 +#: users/models/user.py:678 msgid "Wechat" msgstr "微信" -#: users/models/user.py:687 +#: users/models/user.py:689 msgid "Private key" msgstr "ssh私钥" -#: users/models/user.py:709 +#: users/models/user.py:711 msgid "Source" msgstr "来源" -#: users/models/user.py:713 +#: users/models/user.py:715 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:716 +#: users/models/user.py:718 msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:886 +#: users/models/user.py:888 msgid "Can invite user" msgstr "可以邀请用户" -#: users/models/user.py:887 +#: users/models/user.py:889 msgid "Can remove user" msgstr "可以移除用户" -#: users/models/user.py:888 +#: users/models/user.py:890 msgid "Can match user" msgstr "可以匹配用户" -#: users/models/user.py:897 +#: users/models/user.py:899 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:900 +#: users/models/user.py:902 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models/user.py:925 +#: users/models/user.py:927 msgid "User password history" msgstr "用户密码历史" @@ -5782,97 +5782,101 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:142 users/serializers/user.py:138 +#: users/serializers/profile.py:142 users/serializers/user.py:140 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:24 users/serializers/user.py:31 +#: users/serializers/user.py:25 users/serializers/user.py:32 msgid "System roles" msgstr "系统角色" -#: users/serializers/user.py:29 users/serializers/user.py:32 +#: users/serializers/user.py:30 users/serializers/user.py:33 msgid "Org roles" msgstr "组织角色" -#: users/serializers/user.py:77 +#: users/serializers/user.py:78 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:22 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:79 +#: users/serializers/user.py:80 msgid "MFA enabled" msgstr "MFA" -#: users/serializers/user.py:80 +#: users/serializers/user.py:81 msgid "MFA force enabled" msgstr "强制 MFA" -#: users/serializers/user.py:82 +#: users/serializers/user.py:83 msgid "MFA level display" msgstr "MFA 等级名称" -#: users/serializers/user.py:84 +#: users/serializers/user.py:85 msgid "Login blocked" msgstr "登录被阻塞" -#: users/serializers/user.py:87 +#: users/serializers/user.py:88 msgid "Can public key authentication" msgstr "能否公钥认证" -#: users/serializers/user.py:142 +#: users/serializers/user.py:144 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:144 +#: users/serializers/user.py:146 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:145 +#: users/serializers/user.py:147 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:146 +#: users/serializers/user.py:148 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:147 +#: users/serializers/user.py:149 msgid "Super role name" msgstr "超级角色名称" -#: users/serializers/user.py:148 +#: users/serializers/user.py:150 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:150 +#: users/serializers/user.py:152 msgid "Is wecom bound" msgstr "是否绑定了企业微信" -#: users/serializers/user.py:151 +#: users/serializers/user.py:153 msgid "Is dingtalk bound" msgstr "是否绑定了钉钉" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Is feishu bound" msgstr "是否绑定了飞书" -#: users/serializers/user.py:153 +#: users/serializers/user.py:155 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "System role name" msgstr "系统角色名称" -#: users/serializers/user.py:247 +#: users/serializers/user.py:197 +msgid "User cannot self-update fields: {}" +msgstr "用户不能更新自己的字段: {}" + +#: users/serializers/user.py:254 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:248 +#: users/serializers/user.py:255 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:281 +#: users/serializers/user.py:290 msgid "name not unique" msgstr "名称重复" diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 3014aa2d9..5a1043267 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -194,7 +194,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer if not disallow_fields: return attrs # 用户自己不能更新自己的一些字段 - error = 'User Cannot self-update fields: {}'.format(disallow_fields) + error = _('User cannot self-update fields: {}').format(disallow_fields) raise serializers.ValidationError(error) def validate(self, attrs): From 3121b4e3ff36f097a1aa9f3f96ba4a12670a9094 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 31 Mar 2022 11:38:49 +0800 Subject: [PATCH 005/258] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 22c57dcbe..69226e3a4 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:050a3fd63c1cf9b3dc60c8f138d58f029f2e8a32a71abd99fff6899b68c0f6d9 -size 129742 +oid sha256:bf72c66551e2e7b951960d96054ede0e3e6f3419c4d693f844dc2a61f583390f +size 129861 diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 860a45eaa..511437fb2 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1baa8c35aa2493c03c1fe7383a13ca4cfd9b18b44150770fb51f39433c18c74c -size 107492 +oid sha256:6e803750e498ee3d0f3f66f7b74ee9c4b9fd3a6b2bc9a0ecf4bbd5a9c1582e18 +size 107581 From e602bc03419cbc9747d984cd8632260ea01deab4 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 1 Apr 2022 16:52:50 +0800 Subject: [PATCH 006/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BD=91?= =?UTF-8?q?=E5=85=B3=E7=BF=BB=E8=AF=91=20(#8016)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/assets/serializers/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index b86938c43..6932572f8 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -43,7 +43,7 @@ class DomainSerializer(BulkOrgResourceModelSerializer): class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): - is_connective = serializers.BooleanField(required=False) + is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) class Meta: model = Gateway From a936092020f42e1bda57aa8f5d905c8b570f53a0 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Sat, 2 Apr 2022 13:26:18 +0800 Subject: [PATCH 007/258] =?UTF-8?q?perf:=20es=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=A0=BC=E5=BC=8F=E4=BC=98=E5=8C=96=20(#8020)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/terminal/backends/command/es.py | 37 ++++++++++++++-------------- apps/terminal/serializers/storage.py | 18 +++----------- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index d74ad3fa8..2e8a82c88 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -18,7 +18,6 @@ from common.utils import get_logger from common.exceptions import JMSException from .models import AbstractSessionCommand - logger = get_logger(__file__) @@ -27,7 +26,7 @@ class InvalidElasticsearch(JMSException): default_detail = _('Invalid elasticsearch config') -class CommandStore(): +class CommandStore(object): def __init__(self, config): hosts = config.get("HOSTS") kwargs = config.get("OTHER", {}) @@ -208,7 +207,7 @@ class CommandStore(): elif org_id in (real_default_org_id, ''): match.pop('org_id') should.append({ - 'bool':{ + 'bool': { 'must_not': [ { 'wildcard': {'org_id': '*'} @@ -226,20 +225,20 @@ class CommandStore(): ], 'should': should, 'filter': [ - { - 'term': {k: v} - } for k, v in exact.items() - ] + [ - { - 'range': { - 'timestamp': timestamp_range - } - } - ] + [ - { - 'ids': {k: v} - } for k, v in index.items() - ] + { + 'term': {k: v} + } for k, v in exact.items() + ] + [ + { + 'range': { + 'timestamp': timestamp_range + } + } + ] + [ + { + 'ids': {k: v} + } for k, v in index.items() + ] } }, } @@ -326,8 +325,8 @@ class QuerySet(DJQuerySet): def __getattribute__(self, item): if any(( - item.startswith('__'), - item in QuerySet.__dict__, + item.startswith('__'), + item in QuerySet.__dict__, )): return object.__getattribute__(self, item) diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index 2b21625bd..81c885862 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -13,8 +13,6 @@ from rest_framework.validators import UniqueValidator # Replay storage serializers # -------------------------- - - def replay_storage_endpoint_format_validator(endpoint): h = urlparse(endpoint) if h.path: @@ -116,9 +114,8 @@ class ReplayStorageTypeAzureSerializer(serializers.Serializer): label=_('Endpoint suffix'), allow_null=True, ) + # mapping - - replay_storage_type_serializer_classes_mapping = { const.ReplayStorageTypeChoices.s3.value: ReplayStorageTypeS3Serializer, const.ReplayStorageTypeChoices.ceph.value: ReplayStorageTypeCephSerializer, @@ -129,10 +126,9 @@ replay_storage_type_serializer_classes_mapping = { const.ReplayStorageTypeChoices.cos.value: ReplayStorageTypeCOSSerializer } + # Command storage serializers # --------------------------- - - def command_storage_es_host_format_validator(host): h = urlparse(host) default_error_msg = _('The address format is incorrect') @@ -151,7 +147,6 @@ def command_storage_es_host_format_validator(host): class CommandStorageTypeESSerializer(serializers.Serializer): - hosts_help_text = ''' Tip: If there are multiple hosts, use a comma (,) to separate them.
(eg: http://www.jumpserver.a.com:9100, http://www.jumpserver.b.com:9100) @@ -169,17 +164,14 @@ class CommandStorageTypeESSerializer(serializers.Serializer): source='OTHER.IGNORE_VERIFY_CERTS', allow_null=True, ) + # mapping - - command_storage_type_serializer_classes_mapping = { const.CommandStorageTypeChoices.es.value: CommandStorageTypeESSerializer } # BaseStorageSerializer - - class BaseStorageSerializer(serializers.ModelSerializer): storage_type_serializer_classes_mapping = {} meta = MethodSerializer() @@ -223,8 +215,6 @@ class BaseStorageSerializer(serializers.ModelSerializer): # CommandStorageSerializer - - class CommandStorageSerializer(BaseStorageSerializer): storage_type_serializer_classes_mapping = command_storage_type_serializer_classes_mapping @@ -236,8 +226,6 @@ class CommandStorageSerializer(BaseStorageSerializer): # ReplayStorageSerializer - - class ReplayStorageSerializer(BaseStorageSerializer): storage_type_serializer_classes_mapping = replay_storage_type_serializer_classes_mapping From 2cb08b4785a1c249290b56905003727cf6461e77 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Sat, 2 Apr 2022 15:51:23 +0800 Subject: [PATCH 008/258] fix: user is common user --- apps/ops/api/command.py | 2 +- apps/rbac/builtin.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py index 2b7e5fda9..0d513cd9e 100644 --- a/apps/ops/api/command.py +++ b/apps/ops/api/command.py @@ -59,7 +59,7 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet): raise ValidationError({"hosts": msg}) def check_permissions(self, request): - if not settings.SECURITY_COMMAND_EXECUTION and request.user.is_common_user: + if not settings.SECURITY_COMMAND_EXECUTION: return self.permission_denied(request, "Command execution disabled") return super().check_permissions(request) diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index a47139132..ce735ce8f 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -29,7 +29,6 @@ auditor_perms = user_perms + ( ('ops', 'commandexecution', 'view', 'commandexecution') ) - app_exclude_perms = [ ('users', 'user', 'add,delete', 'user'), ('orgs', 'org', 'add,delete,change', 'org'), @@ -59,7 +58,8 @@ class PredefineRole: from rbac.models import Role return Role.objects.get(id=self.id) - def _get_defaults(self): + @property + def default_perms(self): from rbac.models import Permission q = Permission.get_define_permissions_q(self.perms) permissions = Permission.get_permissions(self.scope) @@ -72,6 +72,10 @@ class PredefineRole: permissions = permissions.exclude(q) perms = permissions.values_list('id', flat=True) + return perms + + def _get_defaults(self): + perms = self.default_perms defaults = { 'id': self.id, 'name': self.name, 'scope': self.scope, 'builtin': True, 'permissions': perms From fe8527fd074de1c4300db60c21d2d9107d9c6875 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Sat, 2 Apr 2022 10:04:18 +0800 Subject: [PATCH 009/258] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +-- apps/locale/ja/LC_MESSAGES/django.po | 49 +++++++++++++++++----------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +-- apps/locale/zh/LC_MESSAGES/django.po | 49 +++++++++++++++++----------- 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 69226e3a4..663dad9e1 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf72c66551e2e7b951960d96054ede0e3e6f3419c4d693f844dc2a61f583390f -size 129861 +oid sha256:17e378f009274c169039e815158ea9072ee89811bb27a5b17a628f2066fcfb86 +size 129989 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index bc5707e14..cc8a3b317 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-31 11:21+0800\n" +"POT-Creation-Date: 2022-04-02 10:01+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -343,7 +343,7 @@ msgid "Domain" msgstr "ドメイン" #: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:58 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "ツールバーの" @@ -767,11 +767,11 @@ msgstr "OK" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失敗しました" -#: assets/models/base.py:38 +#: assets/models/base.py:38 assets/serializers/domain.py:46 msgid "Connectivity" msgstr "接続性" @@ -6520,58 +6520,62 @@ msgid "Baidu Cloud" msgstr "百度雲" #: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "京東雲" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "テンセント雲" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "華為私有雲" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青雲私有雲" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌雲" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "インスタンス名" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "インスタンス名と部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "同期していません" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新しい同期" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "同期済み" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "リリース済み" @@ -6744,11 +6748,13 @@ msgid "South America (São Paulo)" msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "華南-広州" @@ -6770,6 +6776,7 @@ msgid "CN North-Baoding" msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "華東-上海" @@ -6834,11 +6841,15 @@ msgstr "華北-ウランチャブ一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "華南-広州-友好ユーザー環境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "華東-宿遷" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有効表示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "プロバイダ表示" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 511437fb2..944990cc4 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e803750e498ee3d0f3f66f7b74ee9c4b9fd3a6b2bc9a0ecf4bbd5a9c1582e18 -size 107581 +oid sha256:51c19db490e2e3a7cc3c3fce33b2e4422239d8c64d591208cadaf062c5ccb0c9 +size 107709 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ffd8d4ba3..68b8a566c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-31 11:19+0800\n" +"POT-Creation-Date: 2022-04-02 10:01+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -338,7 +338,7 @@ msgid "Domain" msgstr "网域" #: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:58 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "属性" @@ -762,11 +762,11 @@ msgstr "成功" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失败" -#: assets/models/base.py:38 +#: assets/models/base.py:38 assets/serializers/domain.py:46 msgid "Connectivity" msgstr "可连接性" @@ -6430,58 +6430,62 @@ msgid "Baidu Cloud" msgstr "百度云" #: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "京东云" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "腾讯云" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "华为私有云" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青云私有云" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌云" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "实例名称" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "未同步" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新同步" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "已同步" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "已释放" @@ -6654,11 +6658,13 @@ msgid "South America (São Paulo)" msgstr "南美洲(圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "华南-广州" @@ -6680,6 +6686,7 @@ msgid "CN North-Baoding" msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "华东-上海" @@ -6744,11 +6751,15 @@ msgstr "华北-乌兰察布一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "华南-广州-友好用户环境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "华东-宿迁" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有效性显示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "服务商显示" From ef36b2e66265de4f196d2a90ac8444c9a7b74063 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 6 Apr 2022 14:58:13 +0800 Subject: [PATCH 010/258] =?UTF-8?q?perf:=20=E5=AE=8C=E5=96=84=20setting=20?= =?UTF-8?q?=E7=9A=84=E5=8A=A8=E6=80=81=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/signal_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/signal_handlers.py b/apps/settings/signal_handlers.py index 818b7ba03..18449a771 100644 --- a/apps/settings/signal_handlers.py +++ b/apps/settings/signal_handlers.py @@ -94,7 +94,7 @@ def monkey_patch_settings(sender, **kwargs): def monkey_patch_getattr(self, name): val = getattr(self._wrapped, name) # 只解析 defaults 中的 callable - if callable(val) and val.__module__ == 'jumpserver.conf': + if callable(val) and val.__module__.endswith('jumpserver.conf'): val = val() return val From c8758f417d5208d35d34c84adf7dcbc3570a01b6 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Fri, 1 Apr 2022 15:33:14 +0800 Subject: [PATCH 011/258] =?UTF-8?q?feat:=20ldap=E4=B8=80=E9=94=AE=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=8F=8A=E8=AE=BE=E7=BD=AE=E7=94=A8=E6=88=B7=E7=BB=84?= =?UTF-8?q?=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 5 +++-- apps/jumpserver/settings/auth.py | 1 + apps/settings/api/ldap.py | 5 +++-- apps/settings/serializers/auth/ldap.py | 9 ++++++--- apps/users/tasks.py | 6 ++++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 7aa8dc436..8e34d6f91 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -41,7 +41,7 @@ def import_string(dotted_path): except AttributeError as err: raise ImportError('Module "%s" does not define a "%s" attribute/class' % ( module_path, class_name) - ) from err + ) from err def is_absolute_uri(uri): @@ -176,6 +176,7 @@ class Config(dict): 'AUTH_LDAP_SYNC_IS_PERIODIC': False, 'AUTH_LDAP_SYNC_INTERVAL': None, 'AUTH_LDAP_SYNC_CRONTAB': None, + 'AUTH_LDAP_SYNC_ORG_ID': '00000000-0000-0000-0000-000000000002', 'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False, 'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1, @@ -272,7 +273,7 @@ class Config(dict): 'FEISHU_APP_ID': '', 'FEISHU_APP_SECRET': '', - 'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2 + 'LOGIN_REDIRECT_TO_BACKEND': '', # 'OPENID / CAS / SAML2 'LOGIN_REDIRECT_MSG_ENABLED': True, 'SMS_ENABLED': False, diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index c545772e1..f71afec9b 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -43,6 +43,7 @@ AUTH_LDAP_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_SEARCH_PAGED_SIZE AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB +AUTH_LDAP_SYNC_ORG_ID = CONFIG.AUTH_LDAP_SYNC_ORG_ID AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS diff --git a/apps/settings/api/ldap.py b/apps/settings/api/ldap.py index a66f0977e..e45414b81 100644 --- a/apps/settings/api/ldap.py +++ b/apps/settings/api/ldap.py @@ -195,7 +195,9 @@ class LDAPUserImportAPI(APIView): def get_ldap_users(self): username_list = self.request.data.get('username_list', []) cache_police = self.request.query_params.get('cache_police', True) - if cache_police in LDAP_USE_CACHE_FLAGS: + if '*' in username_list: + users = LDAPServerUtil().search() + elif cache_police in LDAP_USE_CACHE_FLAGS: users = LDAPCacheUtil().search(search_users=username_list) else: users = LDAPServerUtil().search(search_users=username_list) @@ -234,4 +236,3 @@ class LDAPCacheRefreshAPI(generics.RetrieveAPIView): logger.error(str(e)) return Response(data={'msg': str(e)}, status=400) return Response(data={'msg': 'success'}) - diff --git a/apps/settings/serializers/auth/ldap.py b/apps/settings/serializers/auth/ldap.py index 8508d2ee8..8e65d67b7 100644 --- a/apps/settings/serializers/auth/ldap.py +++ b/apps/settings/serializers/auth/ldap.py @@ -1,4 +1,3 @@ - from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers @@ -40,8 +39,9 @@ class LDAPSettingSerializer(serializers.Serializer): help_text=_('eg: ldap://localhost:389') ) AUTH_LDAP_BIND_DN = serializers.CharField(required=False, max_length=1024, label=_('Bind DN')) - AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False, - label=_('Password')) + AUTH_LDAP_BIND_PASSWORD = serializers.CharField( + max_length=1024, write_only=True, required=False, label=_('Password') + ) AUTH_LDAP_SEARCH_OU = serializers.CharField( max_length=1024, allow_blank=True, required=False, label=_('User OU'), help_text=_('Use | split multi OUs') @@ -55,6 +55,9 @@ class LDAPSettingSerializer(serializers.Serializer): help_text=_('User attr map present how to map LDAP user attr to ' 'jumpserver, username,name,email is jumpserver attr') ) + AUTH_LDAP_SYNC_ORG_ID = serializers.CharField( + required=False, label=_('Organization'), max_length=36 + ) AUTH_LDAP_SYNC_IS_PERIODIC = serializers.BooleanField( required=False, label=_('Periodic perform') ) diff --git a/apps/users/tasks.py b/apps/users/tasks.py index a92fc2fb0..0f93fa6c3 100644 --- a/apps/users/tasks.py +++ b/apps/users/tasks.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # -import sys from celery import shared_task from django.conf import settings @@ -11,6 +10,7 @@ from ops.celery.utils import ( ) from ops.celery.decorator import after_app_ready_start from common.utils import get_logger +from orgs.models import Organization from .models import User from users.notifications import UserExpirationReminderMsg from settings.utils import LDAPServerUtil, LDAPImportUtil @@ -81,7 +81,9 @@ def import_ldap_user(): util_server = LDAPServerUtil() util_import = LDAPImportUtil() users = util_server.search() - errors = util_import.perform_import(users) + org_id = settings.AUTH_LDAP_SYNC_ORG_ID + org = Organization.get_instance(org_id) + errors = util_import.perform_import(users, org) if errors: logger.error("Imported LDAP users errors: {}".format(errors)) else: From f769d5a9bbc5b2efecf71e349f7f12c4e4d6e24c Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 7 Apr 2022 10:11:16 +0800 Subject: [PATCH 012/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=95=B0=E6=8D=AE=E4=B8=8D=E5=90=8C=E6=AD=A5=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/signal_handlers/cache.py | 48 +++++++++++++++++++++++------- apps/users/models/user.py | 12 ++++---- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 1d9ca3891..546d11311 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -1,33 +1,61 @@ +from functools import wraps from django.db.models.signals import post_save, pre_delete, pre_save, post_delete from django.dispatch import receiver from orgs.models import Organization from assets.models import Node -from perms.models import (AssetPermission, ApplicationPermission) +from perms.models import AssetPermission, ApplicationPermission from users.models import UserGroup, User +from users.signals import pre_user_leave_org from applications.models import Application from terminal.models import Session +from rbac.models import OrgRoleBinding, SystemRoleBinding from assets.models import Asset, SystemUser, Domain, Gateway from orgs.caches import OrgResourceStatisticsCache +from orgs.utils import current_org +from common.utils import get_logger + +logger = get_logger(__name__) -def refresh_user_amount_on_user_create_or_delete(user_id): - orgs = Organization.objects.filter(m2m_org_members__user_id=user_id).distinct() +def refresh_cache(name, org): + names = None + if isinstance(name, (str,)): + names = [name, ] + if isinstance(names, (list, tuple)): + for name in names: + OrgResourceStatisticsCache(org).expire(name) + OrgResourceStatisticsCache(Organization.root()).expire(name) + else: + logger.warning('refresh cache fail: {}'.format(name)) + + +def refresh_user_amount_cache(user): + orgs = user.orgs.distinct() for org in orgs: - org_cache = OrgResourceStatisticsCache(org) - org_cache.expire('users_amount') - OrgResourceStatisticsCache(Organization.root()).expire('users_amount') + refresh_cache('users_amount', org) -@receiver(post_save, sender=User) -def on_user_create_refresh_cache(sender, instance, created, **kwargs): +@receiver(post_save, sender=OrgRoleBinding) +def on_user_create_or_invite_refresh_cache(sender, instance, created, **kwargs): if created: - refresh_user_amount_on_user_create_or_delete(instance.id) + refresh_cache('users_amount', instance.org) + + +@receiver(post_save, sender=SystemRoleBinding) +def on_user_global_create_refresh_cache(sender, instance, created, **kwargs): + if created and current_org.is_root(): + refresh_cache('users_amount', current_org) + + +@receiver(pre_user_leave_org) +def on_user_remove_refresh_cache(sender, org=None, **kwargs): + refresh_cache('users_amount', org) @receiver(pre_delete, sender=User) def on_user_delete_refresh_cache(sender, instance, **kwargs): - refresh_user_amount_on_user_create_or_delete(instance.id) + refresh_user_amount_cache(instance) # @receiver(m2m_changed, sender=OrganizationMember) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 1a5ce634c..ede795d13 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -78,7 +78,7 @@ class AuthMixin: def is_history_password(self, password): allow_history_password_count = settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT history_passwords = self.history_passwords.all() \ - .order_by('-date_created')[:int(allow_history_password_count)] + .order_by('-date_created')[:int(allow_history_password_count)] for history_password in history_passwords: if check_password(password, history_password.password): @@ -726,7 +726,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): @classmethod def get_group_ids_by_user_id(cls, user_id): - group_ids = cls.groups.through.objects.filter(user_id=user_id)\ + group_ids = cls.groups.through.objects.filter(user_id=user_id) \ .distinct().values_list('usergroup_id', flat=True) group_ids = list(group_ids) return group_ids @@ -866,20 +866,20 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): @property def all_orgs(self): from rbac.builtin import BuiltinRole - has_system_role = self.system_roles.all()\ - .exclude(name=BuiltinRole.system_user.name)\ + has_system_role = self.system_roles.all() \ + .exclude(name=BuiltinRole.system_user.name) \ .exists() if has_system_role: orgs = list(Organization.objects.all()) else: - orgs = list(self.orgs.all().distinct()) + orgs = list(self.orgs.distinct()) if self.has_perm('orgs.view_rootorg'): orgs = [Organization.root()] + orgs return orgs @property def my_orgs(self): - return list(self.orgs.all().distinct()) + return list(self.orgs.distinct()) class Meta: ordering = ['username'] From 7c7d7d52b2f4fbad35cdbd5b71ab0f1e4be3286c Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 12 Apr 2022 14:01:57 +0800 Subject: [PATCH 013/258] =?UTF-8?q?perf:=20asset=20number=20=E6=89=A9?= =?UTF-8?q?=E5=AE=B9=20(#8045)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- .../migrations/0090_auto_20220412_1145.py | 18 ++++++++++++++++++ apps/assets/models/asset.py | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 apps/assets/migrations/0090_auto_20220412_1145.py diff --git a/apps/assets/migrations/0090_auto_20220412_1145.py b/apps/assets/migrations/0090_auto_20220412_1145.py new file mode 100644 index 000000000..c2cb879aa --- /dev/null +++ b/apps/assets/migrations/0090_auto_20220412_1145.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2022-04-12 03:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0089_auto_20220310_0616'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='number', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Asset number'), + ), + ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index c4ecf9cfe..84ddee404 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# +# import uuid import logging @@ -223,7 +223,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin # Some information public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP')) - number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number')) + number = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Asset number')) labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) created_by = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Created by')) From 1f8ded49fa13846cb75ca303e5206a6647a5d3ae Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 12 Apr 2022 14:25:49 +0800 Subject: [PATCH 014/258] =?UTF-8?q?feat:=20=E5=B7=A5=E4=BD=9C=E5=8F=B0?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E7=BB=84=E7=BB=87=20(#8040)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 工作台受组织角色控制 * perf: workspace => workbench * perf: 修改 workspace codename Co-authored-by: ibuler --- apps/authentication/urls/view_urls.py | 1 - apps/jumpserver/views/index.py | 5 +- apps/locale/ja/LC_MESSAGES/django.mo | 2 +- apps/locale/ja/LC_MESSAGES/django.po | 4 +- apps/locale/zh/LC_MESSAGES/django.mo | 2 +- apps/locale/zh/LC_MESSAGES/django.po | 4 +- .../api/application/user_permission/mixin.py | 4 - apps/perms/api/asset/user_permission/mixin.py | 4 - apps/perms/utils/asset/user_permission.py | 8 +- apps/rbac/builtin.py | 3 +- apps/rbac/migrations/0001_initial.py | 2 +- .../migrations/0005_auto_20220307_1524.py | 2 +- .../migrations/0006_auto_20220310_0616.py | 2 +- .../migrations/0008_auto_20220411_1709.py | 22 + .../migrations/0009_auto_20220411_1724.py | 19 + apps/rbac/models/menu.py | 2 +- apps/rbac/tree.py | 16 +- apps/templates/_base_asset_tree_list.html | 54 -- apps/templates/_base_create_update.html | 43 -- apps/templates/_base_list.html | 70 -- apps/templates/_nav.html | 0 apps/templates/_nav_user.html | 0 apps/templates/_pagination.html | 66 -- apps/templates/delete_confirm.html | 15 - apps/templates/index.html | 618 ------------------ apps/terminal/api/session.py | 7 +- apps/tickets/api/relation.py | 7 +- .../templates/users/_base_user_detail.html | 26 - .../templates/users/_granted_assets.html | 222 ------- .../templates/users/_select_user_modal.html | 23 - .../users/_user_detail_nav_header.html | 97 --- .../users/_user_update_pk_modal.html | 8 - apps/users/templates/users/first_login.html | 10 - .../templates/users/first_login_done.html | 54 -- .../users/user_asset_permission.html | 201 ------ .../users/user_database_app_permission.html | 168 ----- .../templates/users/user_password_update.html | 134 ---- apps/users/utils.py | 2 - apps/users/views/profile/reset.py | 7 +- 39 files changed, 77 insertions(+), 1857 deletions(-) create mode 100644 apps/rbac/migrations/0008_auto_20220411_1709.py create mode 100644 apps/rbac/migrations/0009_auto_20220411_1724.py delete mode 100644 apps/templates/_base_asset_tree_list.html delete mode 100644 apps/templates/_base_create_update.html delete mode 100644 apps/templates/_base_list.html delete mode 100644 apps/templates/_nav.html delete mode 100644 apps/templates/_nav_user.html delete mode 100644 apps/templates/_pagination.html delete mode 100644 apps/templates/delete_confirm.html delete mode 100644 apps/templates/index.html delete mode 100644 apps/users/templates/users/_base_user_detail.html delete mode 100644 apps/users/templates/users/_granted_assets.html delete mode 100644 apps/users/templates/users/_select_user_modal.html delete mode 100644 apps/users/templates/users/_user_detail_nav_header.html delete mode 100644 apps/users/templates/users/_user_update_pk_modal.html delete mode 100644 apps/users/templates/users/first_login.html delete mode 100644 apps/users/templates/users/first_login_done.html delete mode 100644 apps/users/templates/users/user_asset_permission.html delete mode 100644 apps/users/templates/users/user_database_app_permission.html delete mode 100644 apps/users/templates/users/user_password_update.html diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 2d0749470..9abd61e3b 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -55,7 +55,6 @@ urlpatterns = [ path('profile/otp/enable/bind/', users_view.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'), path('profile/otp/disable/', users_view.UserOtpDisableView.as_view(), name='user-otp-disable'), - path('first-login/', users_view.UserFirstLoginView.as_view(), name='user-first-login'), # openid path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')), diff --git a/apps/jumpserver/views/index.py b/apps/jumpserver/views/index.py index 8f974a483..639f6d683 100644 --- a/apps/jumpserver/views/index.py +++ b/apps/jumpserver/views/index.py @@ -1,4 +1,4 @@ -from django.views.generic import TemplateView +from django.views.generic import View from django.shortcuts import redirect from common.permissions import IsValidUser from common.mixins.views import PermissionsMixin @@ -6,8 +6,7 @@ from common.mixins.views import PermissionsMixin __all__ = ['IndexView'] -class IndexView(PermissionsMixin, TemplateView): - template_name = 'index.html' +class IndexView(PermissionsMixin, View): permission_classes = [IsValidUser] def get(self, request, *args, **kwargs): diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 663dad9e1..080e2ab5e 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17e378f009274c169039e815158ea9072ee89811bb27a5b17a628f2066fcfb86 +oid sha256:097c6d06ed8dcf2e1807560b6eb52d98cba31f25fe8d67ce4315668c150ca6b8 size 129989 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index cc8a3b317..757d1e463 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -3188,7 +3188,7 @@ msgid "Can view audit view" msgstr "監査ビューを表示できます" #: rbac/models/menu.py:17 -msgid "Can view workspace view" +msgid "Can view workbench view" msgstr "ワークスペースビューを表示できます" #: rbac/models/menu.py:18 @@ -3271,7 +3271,7 @@ msgid "Console view" msgstr "コンソールビュー" #: rbac/tree.py:27 -msgid "Workspace view" +msgid "Workbench view" msgstr "ワークスペースビュー" #: rbac/tree.py:28 diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 944990cc4..88f701b6b 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51c19db490e2e3a7cc3c3fce33b2e4422239d8c64d591208cadaf062c5ccb0c9 +oid sha256:3d6a0d40534209f3ffab0b0ecfab7ec82f137156fc8e7b19ce1711036d14aeca size 107709 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 68b8a566c..fb29c7d9c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -3151,7 +3151,7 @@ msgid "Can view audit view" msgstr "可以显示审计台" #: rbac/models/menu.py:17 -msgid "Can view workspace view" +msgid "Can view workbench view" msgstr "可以显示工作台" #: rbac/models/menu.py:18 @@ -3233,7 +3233,7 @@ msgid "Console view" msgstr "控制台" #: rbac/tree.py:27 -msgid "Workspace view" +msgid "Workbench view" msgstr "工作台" #: rbac/tree.py:28 diff --git a/apps/perms/api/application/user_permission/mixin.py b/apps/perms/api/application/user_permission/mixin.py index b66788c31..6e8f91090 100644 --- a/apps/perms/api/application/user_permission/mixin.py +++ b/apps/perms/api/application/user_permission/mixin.py @@ -22,7 +22,3 @@ class AppRoleUserMixin(_RoleUserMixin): ('get_tree', 'perms.view_myapps'), ('GET', 'perms.view_myapps'), ) - - def dispatch(self, *args, **kwargs): - with tmp_to_root_org(): - return super().dispatch(*args, **kwargs) \ No newline at end of file diff --git a/apps/perms/api/asset/user_permission/mixin.py b/apps/perms/api/asset/user_permission/mixin.py index e06ec9787..c28da6c2d 100644 --- a/apps/perms/api/asset/user_permission/mixin.py +++ b/apps/perms/api/asset/user_permission/mixin.py @@ -36,7 +36,3 @@ class AssetRoleUserMixin(PermBaseMixin, _RoleUserMixin): ('get_tree', 'perms.view_myassets'), ('GET', 'perms.view_myassets'), ) - - def dispatch(self, *args, **kwargs): - with tmp_to_root_org(): - return super().dispatch(*args, **kwargs) diff --git a/apps/perms/utils/asset/user_permission.py b/apps/perms/utils/asset/user_permission.py index 1ea189230..5baa93d01 100644 --- a/apps/perms/utils/asset/user_permission.py +++ b/apps/perms/utils/asset/user_permission.py @@ -202,7 +202,9 @@ class UserGrantedTreeRefreshController: user = self.user with tmp_to_root_org(): - UserAssetGrantedTreeNodeRelation.objects.filter(user=user).exclude(org_id__in=self.org_ids).delete() + UserAssetGrantedTreeNodeRelation.objects.filter(user=user)\ + .exclude(org_id__in=self.org_ids)\ + .delete() if force or self.have_need_refresh_orgs(): with UserGrantedTreeRebuildLock(user_id=user.id): @@ -219,7 +221,9 @@ class UserGrantedTreeRefreshController: utils = UserGrantedTreeBuildUtils(user) utils.rebuild_user_granted_tree() logger.info( - f'Rebuild user tree ok: cost={time.time() - t_start} user={self.user} org={current_org}') + f'Rebuild user tree ok: cost={time.time() - t_start} ' + f'user={self.user} org={current_org}' + ) class UserGrantedUtilsBase: diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index ce735ce8f..4ce706a46 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -5,7 +5,7 @@ from .const import Scope, system_exclude_permissions, org_exclude_permissions # Todo: 获取应该区分 系统用户,和组织用户的权限 # 工作台也区分组织后再考虑 user_perms = ( - ('rbac', 'menupermission', 'view', 'workspace'), + ('rbac', 'menupermission', 'view', 'workbench'), ('rbac', 'menupermission', 'view', 'webterminal'), ('rbac', 'menupermission', 'view', 'filemanager'), ('perms', 'permedasset', 'view,connect', 'myassets'), @@ -17,6 +17,7 @@ user_perms = ( ('ops', 'commandexecution', 'add', 'commandexecution'), ('authentication', 'connectiontoken', 'add', 'connectiontoken'), ('tickets', 'ticket', 'view', 'ticket'), + ('orgs', 'organization', 'view', 'rootorg'), ) auditor_perms = user_perms + ( diff --git a/apps/rbac/migrations/0001_initial.py b/apps/rbac/migrations/0001_initial.py index 5687bf574..d3f94f6f2 100644 --- a/apps/rbac/migrations/0001_initial.py +++ b/apps/rbac/migrations/0001_initial.py @@ -27,7 +27,7 @@ class Migration(migrations.Migration): ], options={ 'verbose_name': 'Menu permission', - 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workspace view')], + 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workbench view')], 'default_permissions': [], }, ), diff --git a/apps/rbac/migrations/0005_auto_20220307_1524.py b/apps/rbac/migrations/0005_auto_20220307_1524.py index afc8ea8ba..ba4427709 100644 --- a/apps/rbac/migrations/0005_auto_20220307_1524.py +++ b/apps/rbac/migrations/0005_auto_20220307_1524.py @@ -12,6 +12,6 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='menupermission', - options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workspace view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager')], 'verbose_name': 'Menu permission'}, + options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workbench view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager')], 'verbose_name': 'Menu permission'}, ), ] diff --git a/apps/rbac/migrations/0006_auto_20220310_0616.py b/apps/rbac/migrations/0006_auto_20220310_0616.py index 395b73f03..7e3ba72de 100644 --- a/apps/rbac/migrations/0006_auto_20220310_0616.py +++ b/apps/rbac/migrations/0006_auto_20220310_0616.py @@ -12,6 +12,6 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='menupermission', - options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workspace view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager') ], 'verbose_name': 'Menu permission'}, + options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workspace', 'Can view workbench view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager') ], 'verbose_name': 'Menu permission'}, ), ] diff --git a/apps/rbac/migrations/0008_auto_20220411_1709.py b/apps/rbac/migrations/0008_auto_20220411_1709.py new file mode 100644 index 000000000..319fa5a37 --- /dev/null +++ b/apps/rbac/migrations/0008_auto_20220411_1709.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1.14 on 2022-04-11 09:09 + +from django.db import migrations + + +def migrate_workspace_to_workbench(apps, *args): + model = apps.get_model('auth', 'Permission') + model.objects.filter(codename='view_workspace').delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('rbac', '0007_auto_20220314_1525'), + ] + + operations = [ + migrations.AlterModelOptions( + name='menupermission', + options={'default_permissions': [], 'permissions': [('view_console', 'Can view console view'), ('view_audit', 'Can view audit view'), ('view_workbench', 'Can view workbench view'), ('view_webterminal', 'Can view web terminal'), ('view_filemanager', 'Can view file manager')], 'verbose_name': 'Menu permission'}, + ), + ] diff --git a/apps/rbac/migrations/0009_auto_20220411_1724.py b/apps/rbac/migrations/0009_auto_20220411_1724.py new file mode 100644 index 000000000..4ffb51068 --- /dev/null +++ b/apps/rbac/migrations/0009_auto_20220411_1724.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.14 on 2022-04-11 09:24 + +from django.db import migrations + + +def migrate_workspace_to_workbench(apps, *args): + model = apps.get_model('auth', 'Permission') + model.objects.filter(codename='view_workspace').delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('rbac', '0008_auto_20220411_1709'), + ] + + operations = [ + migrations.RunPython(migrate_workspace_to_workbench) + ] diff --git a/apps/rbac/models/menu.py b/apps/rbac/models/menu.py index 524894664..48538199b 100644 --- a/apps/rbac/models/menu.py +++ b/apps/rbac/models/menu.py @@ -14,7 +14,7 @@ class MenuPermission(models.Model): permissions = [ ('view_console', _('Can view console view')), ('view_audit', _('Can view audit view')), - ('view_workspace', _('Can view workspace view')), + ('view_workbench', _('Can view workbench view')), ('view_webterminal', _('Can view web terminal')), ('view_filemanager', _('Can view file manager')), ] diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py index 5ea43e5ee..dfccafa8f 100644 --- a/apps/rbac/tree.py +++ b/apps/rbac/tree.py @@ -1,7 +1,7 @@ #!/usr/bin/python +import os from collections import defaultdict from typing import Callable -import os from django.utils.translation import gettext_lazy as _, gettext, get_language from django.conf import settings @@ -24,7 +24,7 @@ root_node_data = { # 第二层 view 节点,手动创建的 view_nodes_data = [ {'id': 'view_console', 'name': _('Console view')}, - {'id': 'view_workspace', 'name': _('Workspace view')}, + {'id': 'view_workbench', 'name': _('Workbench view')}, {'id': 'view_audit', 'name': _('Audit view')}, {'id': 'view_setting', 'name': _('System setting')}, {'id': 'view_other', 'name': _('Other')}, @@ -55,8 +55,8 @@ extra_nodes_data = [ {"id": "app_change_plan_node", "name": _("App change auth"), "pId": "accounts"}, {"id": "asset_change_plan_node", "name": _("Asset change auth"), "pId": "accounts"}, {"id": "terminal_node", "name": _("Terminal setting"), "pId": "view_setting"}, - {'id': "my_assets", "name": _("My assets"), "pId": "view_workspace"}, - {'id': "my_apps", "name": _("My apps"), "pId": "view_workspace"}, + {'id': "my_assets", "name": _("My assets"), "pId": "view_workbench"}, + {'id': "my_apps", "name": _("My apps"), "pId": "view_workbench"}, ] # 将 model 放到其它节点下,而不是本来的 app 中 @@ -89,7 +89,7 @@ special_pid_mapper = { 'audits.ftplog': 'terminal', 'perms.view_myassets': 'my_assets', 'perms.view_myapps': 'my_apps', - 'ops.add_commandexecution': 'view_workspace', + 'ops.add_commandexecution': 'view_workbench', 'ops.view_commandexecution': 'audits', "perms.view_mykubernetsapp": "my_apps", "perms.connect_mykubernetsapp": "my_apps", @@ -102,9 +102,9 @@ special_pid_mapper = { "settings.view_setting": "view_setting", "rbac.view_console": "view_console", "rbac.view_audit": "view_audit", - "rbac.view_workspace": "view_workspace", - "rbac.view_webterminal": "view_workspace", - "rbac.view_filemanager": "view_workspace", + "rbac.view_workbench": "view_workbench", + "rbac.view_webterminal": "view_workbench", + "rbac.view_filemanager": "view_workbench", 'tickets.view_ticket': 'tickets' } diff --git a/apps/templates/_base_asset_tree_list.html b/apps/templates/_base_asset_tree_list.html deleted file mode 100644 index a989a4da1..000000000 --- a/apps/templates/_base_asset_tree_list.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block help_message %} -{% endblock %} - -{% block content %} -
-
-
- {% include 'assets/_node_tree.html' %} -
-
-
-
- -
-
-
- {% block table_container %} - - - - {% block table_head %} {% endblock %} - - - - {% block table_body %} {% endblock %} - -
- {% endblock %} -
-
-
-
- -{% endblock %} diff --git a/apps/templates/_base_create_update.html b/apps/templates/_base_create_update.html deleted file mode 100644 index d206d40b2..000000000 --- a/apps/templates/_base_create_update.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} -{% block custom_head_css_js %} - - {% block custom_head_css_js_create %} {% endblock %} -{% endblock %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
- {% if form.errors.all %} -
- {{ form.errors.all }} -
- {% endif %} - {% block form %} - {% endblock %} -
-
-
-
-
-{% endblock %} - diff --git a/apps/templates/_base_list.html b/apps/templates/_base_list.html deleted file mode 100644 index 9759081bd..000000000 --- a/apps/templates/_base_list.html +++ /dev/null @@ -1,70 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% block content %} -
-
-
-
-
-
- {{ action }} -
- -
-
-
- {% block content_left_head %} {% endblock %} - {% block table_search %} - - {% endblock %} -
- {% block table_container %} - - - - {% block table_head %} {% endblock %} - - - - {% block table_body %} {% endblock %} - -
- {% endblock %} -
-
- {% block content_bottom_left %} {% endblock %} -
- {% block table_pagination %} - {% include '_pagination.html' %} - {% endblock %} -
-
-
-
-
-
-{% endblock %} diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/templates/_pagination.html b/apps/templates/_pagination.html deleted file mode 100644 index cf23b04fa..000000000 --- a/apps/templates/_pagination.html +++ /dev/null @@ -1,66 +0,0 @@ -{% load i18n %} -{% load common_tags %} - {% if is_paginated %} -
-
-{# 显示第 {{ page_obj.start_index }} 至 {{ page_obj.end_index }} 项结果,共 {{ paginator.count }} 项#} -
-
-
-
-
    - {% if page_obj.has_previous %} - - {% endif %} - - {% for page in paginator.num_pages|pagination_range:page_obj.number %} - {% if page == page_obj.number %} -
  • - {% else %} -
  • - {% endif %} - {{ page }} -
  • - {% endfor %} - - {% if page_obj.has_next %} - - {% endif %} -
-
-
- {% endif %} - diff --git a/apps/templates/delete_confirm.html b/apps/templates/delete_confirm.html deleted file mode 100644 index 94f017ca2..000000000 --- a/apps/templates/delete_confirm.html +++ /dev/null @@ -1,15 +0,0 @@ -{% load i18n %} - - - - - {% trans 'Confirm delete' %} - - -
- {% csrf_token %} -

{% trans 'Are you sure delete' %} {{ object.name }} ?

- -
- - \ No newline at end of file diff --git a/apps/templates/index.html b/apps/templates/index.html deleted file mode 100644 index a216ddac8..000000000 --- a/apps/templates/index.html +++ /dev/null @@ -1,618 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% block content %} -
-
-
-
-
- Users -
{% trans 'Total users' %}
-
-
-

- All users -
-
-
-
-
-
- Assets -
{% trans 'Total assets' %}
-
-
-

- All assets -
-
-
- -
-
-
- Online -
{% trans 'Online users' %}
-
-
-

- Online users -
-
-
- -
-
-
- Connected -
{% trans 'Online sessions' %}
- -
-
-

- Online sessions -
-
-
-
-
-
- {% trans 'In the past week, a total of ' %}{% trans ' users have logged in ' %}{% trans ' times asset.' %} -
    -
-
-
-
-
-

- {% trans 'Active user asset ratio' %} -

-

- {% trans 'The following graphs describe the percentage of active users per month and assets per user host per month, respectively.' %} -

-
-
-
-
-
{% trans 'User' %}
-
-
-
-
{% trans 'Asset' %}
-
-
-
- -
-
-
-
-
- -
-
-
-
-
{% trans 'Top 10 assets in a week' %}
- -
-
-

{% trans 'Top 10 assets in a week'%}

- {% trans 'Login frequency and last login record.' %} -
-
-
-
-
-
-
-
-
{% trans 'Last 10 login' %}
-
- 10 Messages -
-
-
-

{% trans 'Login record' %}

- {% trans 'Last 10 login records.' %} -
-
-
-
-
-
-
-
-
- -
-
-
-
{% trans 'Top 10 users in a week' %}
- -
-
-

{% trans 'Top 10 users in a week' %}

- {% trans 'User login frequency and last login record.' %} -
-
-
-
-
-
-
- -{% endblock %} - -{% block custom_foot_js %} - - - -{% endblock %} diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index c78d48383..3193956e5 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -42,10 +42,9 @@ class MySessionAPIView(generics.ListAPIView): serializer_class = serializers.SessionSerializer def get_queryset(self): - with tmp_to_root_org(): - user = self.request.user - qs = Session.objects.filter(user_id=user.id) - return qs + user = self.request.user + qs = Session.objects.filter(user_id=user.id) + return qs class SessionViewSet(OrgBulkModelViewSet): diff --git a/apps/tickets/api/relation.py b/apps/tickets/api/relation.py index 2dfa33146..5061e6c00 100644 --- a/apps/tickets/api/relation.py +++ b/apps/tickets/api/relation.py @@ -24,10 +24,11 @@ class TicketSessionApi(views.APIView): def get(self, request, *args, **kwargs): with tmp_to_root_org(): - ticketsession = TicketSession.objects.filter(ticket=self.kwargs['ticket_id']).first() - if not ticketsession: + tid = self.kwargs['ticket_id'] + ticket_session = TicketSession.objects.filter(ticket=tid).first() + if not ticket_session: return Response(status=status.HTTP_404_NOT_FOUND) - session = ticketsession.session + session = ticket_session.session serializer = SessionSerializer(session) return Response(serializer.data) diff --git a/apps/users/templates/users/_base_user_detail.html b/apps/users/templates/users/_base_user_detail.html deleted file mode 100644 index 936037ab8..000000000 --- a/apps/users/templates/users/_base_user_detail.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
-
- -
-
- {% block content_table %} - {% endblock %} -
-
-
-
-
-{% endblock %} diff --git a/apps/users/templates/users/_granted_assets.html b/apps/users/templates/users/_granted_assets.html deleted file mode 100644 index 9d5006910..000000000 --- a/apps/users/templates/users/_granted_assets.html +++ /dev/null @@ -1,222 +0,0 @@ -{% load i18n %} -
-
-
-
-
- {% trans 'Loading' %} ... -
-
-
-
-
-
-
-
-
- -
-
-
- - - - - - - - {% if show_actions %} - - {% endif %} - - - - -
{% trans 'Hostname' %}{% trans 'IP' %}{% trans 'System user' %}{% trans 'Action' %}
-
-
- diff --git a/apps/users/templates/users/_select_user_modal.html b/apps/users/templates/users/_select_user_modal.html deleted file mode 100644 index 5395b0a05..000000000 --- a/apps/users/templates/users/_select_user_modal.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_class %}modal-lg{% endblock %} -{% block modal_id %}select_user_modal{% endblock %} -{% block modal_title%}{% trans "Please Select User" %}{% endblock %} -{% block modal_body %} - - - - - - - - - - - - -
-
-
{% trans 'Name' %}{% trans 'Username' %}{% trans 'Role' %}{% trans 'User group' %}{% trans 'Asset num' %}{% trans 'Active' %}
-{% endblock %} -{% block modal_confirm_id %}btn_select_user{% endblock %} diff --git a/apps/users/templates/users/_user_detail_nav_header.html b/apps/users/templates/users/_user_detail_nav_header.html deleted file mode 100644 index 28079e149..000000000 --- a/apps/users/templates/users/_user_detail_nav_header.html +++ /dev/null @@ -1,97 +0,0 @@ -{% load static %} -{% load i18n %} - - - -
  • - {% trans 'User detail' %} -
  • -
  • - - {% trans "User permissions" %} - - - - -
  • - - \ No newline at end of file diff --git a/apps/users/templates/users/_user_update_pk_modal.html b/apps/users/templates/users/_user_update_pk_modal.html deleted file mode 100644 index a7ed525c4..000000000 --- a/apps/users/templates/users/_user_update_pk_modal.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}user_update_pk_modal{% endblock %} -{% block modal_title%}{% trans "Update User SSH Public Key" %}{% endblock %} -{% block modal_body %} - -{% endblock %} -{% block modal_confirm_id %}btn_user_update_pk{% endblock %} diff --git a/apps/users/templates/users/first_login.html b/apps/users/templates/users/first_login.html deleted file mode 100644 index bffa2fc0e..000000000 --- a/apps/users/templates/users/first_login.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends '_base_only_content.html' %} -{% load static %} -{% load i18n %} -{% load bootstrap3 %} - -{% block title %} {% trans 'First Login' %} {% endblock %} - -{% block content %} - 使用UI重构这个页面 -{% endblock %} diff --git a/apps/users/templates/users/first_login_done.html b/apps/users/templates/users/first_login_done.html deleted file mode 100644 index ae43d032b..000000000 --- a/apps/users/templates/users/first_login_done.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load bootstrap3 %} - - -{% block custom_head_css_js %} -{{ wizard.form.media }} - -{% endblock %} -{% block first_login_message %}{% endblock %} - -{% block content %} -
    -
    -
    -
    -
    -
    {% trans 'First Login' %}
    - -
    -
    -
    - {% trans 'Welcome to use jumpserver, visit ' %} - {% trans 'Use guide' %} {% trans ' for more information' %} -
    -
    -
    -
    -
    -
    -{% endblock %} - - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/users/templates/users/user_asset_permission.html b/apps/users/templates/users/user_asset_permission.html deleted file mode 100644 index d0975f4b8..000000000 --- a/apps/users/templates/users/user_asset_permission.html +++ /dev/null @@ -1,201 +0,0 @@ -{% extends 'users/_base_user_detail.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} - - -{% block content_table %} -
    -
    -
    - {{ object.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - -
    {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'Asset' %}{% trans 'Node' %}{% trans 'System user' %}{% trans 'Validity' %}{% trans 'Action' %}
    -
    -
    -
    - -{% include '_filter_dropdown.html' %} - -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/users/templates/users/user_database_app_permission.html b/apps/users/templates/users/user_database_app_permission.html deleted file mode 100644 index 73b3a7972..000000000 --- a/apps/users/templates/users/user_database_app_permission.html +++ /dev/null @@ -1,168 +0,0 @@ -{% extends 'users/_base_user_detail.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content_table %} -
    -
    -
    - {{ object.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - -
    {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'DatabaseApp' %}{% trans 'System user' %}{% trans 'Validity' %}{% trans 'Action' %}
    -
    -
    -
    -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/users/templates/users/user_password_update.html b/apps/users/templates/users/user_password_update.html deleted file mode 100644 index 97bc044d6..000000000 --- a/apps/users/templates/users/user_password_update.html +++ /dev/null @@ -1,134 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load bootstrap3 %} - -{% block custom_head_css_js %} - - - - - - - -{% endblock %} -{% block content %} -
    -
    -
    -
    -
    - -
    -
    -
    -
    - {% csrf_token %} - {% bootstrap_field form.old_password layout="horizontal" %} - {% bootstrap_field form.new_password layout="horizontal" %} - {# 密码popover #} -
    - -
    - {% bootstrap_field form.confirm_password layout="horizontal" %} - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/users/utils.py b/apps/users/utils.py index b32b0c0e0..f11d233e4 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -46,8 +46,6 @@ def get_user_or_pre_auth_user(request): def redirect_user_first_login_or_index(request, redirect_field_name): - # if request.user.is_first_login: - # return reverse('authentication:user-first-login') url_in_post = request.POST.get(redirect_field_name) if url_in_post: return url_in_post diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index c3529c73c..cc14f6d53 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -21,7 +21,7 @@ from ... import forms __all__ = [ - 'UserLoginView', 'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView', + 'UserLoginView', 'UserResetPasswordView', 'UserForgotPasswordView', ] @@ -130,8 +130,3 @@ class UserResetPasswordView(FormView): 'auto_redirect': True, } return FlashMessageUtil.gen_message_url(message_data) - - -class UserFirstLoginView(PermissionsMixin, TemplateView): - template_name = 'users/first_login.html' - permission_classes = [IsValidUser] From ffd98c6e3f409fd48ac7efc563356c8fc9481426 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 Apr 2022 15:11:51 +0800 Subject: [PATCH 015/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/utils.py | 3 +-- apps/terminal/api/terminal.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/authentication/utils.py b/apps/authentication/utils.py index 6dc3866fe..cf4ce8185 100644 --- a/apps/authentication/utils.py +++ b/apps/authentication/utils.py @@ -9,8 +9,7 @@ from django.conf import settings from .notifications import DifferentCityLoginMessage from audits.models import UserLoginLog from audits.const import DEFAULT_CITY -from common.utils import get_request_ip -from common.utils import validate_ip, get_ip_city +from common.utils import validate_ip, get_ip_city, get_request_ip from common.utils import get_logger logger = get_logger(__file__) diff --git a/apps/terminal/api/terminal.py b/apps/terminal/api/terminal.py index f3fa8f5d0..209492baa 100644 --- a/apps/terminal/api/terminal.py +++ b/apps/terminal/api/terminal.py @@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _ from common.exceptions import JMSException from common.drf.api import JMSBulkModelViewSet -from common.utils import get_object_or_none +from common.utils import get_object_or_none, get_request_ip from common.permissions import WithBootstrapToken from ..models import Terminal from .. import serializers From 4cf90df17ca174ef1a107bf9a114cdef0ca0763e Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 Apr 2022 15:19:59 +0800 Subject: [PATCH 016/258] =?UTF-8?q?perf:=20=E9=BB=98=E8=AE=A4=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E6=B7=BB=E5=8A=A0=20created=20by?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/builtin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index 4ce706a46..513ec210a 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -79,7 +79,7 @@ class PredefineRole: perms = self.default_perms defaults = { 'id': self.id, 'name': self.name, 'scope': self.scope, - 'builtin': True, 'permissions': perms + 'builtin': True, 'permissions': perms, 'created_by': 'System', } return defaults From f481463c64553490e0f1d43e55f35ef94b7a30f6 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 12 Apr 2022 17:45:10 +0800 Subject: [PATCH 017/258] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0Endpoint=20(#?= =?UTF-8?q?8041)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add Endpoint EndpointRule EndpointProtocol model * feat: add Endpoint EndpointRule EndpointProtocol API * feat: modify protocols field * feat: 修改序列类 * feat: 获取connect-url连接地址 * feat: 获取connect-url连接地址 * feat: 优化后台获取smart-endpoint逻辑 * feat: 优化后台获取smart-endpoint逻辑 * feat: 删除配置KOKO、XRDP、MAGNUS * feat: 删除配置KOKO、XRDP、MAGNUS * feat: 修改翻译 * feat: 修改smart endpoint * feat: 修改翻译 * feat: smart API 添加token解析 * feat: 删除 smart serializer * feat: 修改迁移逻辑 * feat: 解决冲突 * feat: 修改匹配 endpoint Co-authored-by: Jiangjie.Bai --- apps/applications/models/application.py | 20 + apps/assets/models/asset.py | 3 + apps/authentication/api/connection_token.py | 30 +- apps/common/fields/model.py | 14 +- apps/jumpserver/conf.py | 14 +- apps/jumpserver/settings/custom.py | 11 - apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 628 +++++------------ apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 634 +++++------------- apps/settings/api/public.py | 7 - apps/settings/serializers/terminal.py | 30 +- apps/terminal/api/__init__.py | 1 + apps/terminal/api/endpoint.py | 78 +++ .../migrations/0048_endpoint_endpointrule.py | 86 +++ apps/terminal/models/__init__.py | 1 + apps/terminal/models/endpoint.py | 94 +++ apps/terminal/models/session.py | 9 + apps/terminal/serializers/__init__.py | 1 + apps/terminal/serializers/endpoint.py | 51 ++ apps/terminal/urls/api_urls.py | 2 + 21 files changed, 695 insertions(+), 1027 deletions(-) create mode 100644 apps/terminal/api/endpoint.py create mode 100644 apps/terminal/migrations/0048_endpoint_endpointrule.py create mode 100644 apps/terminal/models/endpoint.py create mode 100644 apps/terminal/serializers/endpoint.py diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index aa2d56f70..15e7dae14 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -247,6 +247,14 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): def category_remote_app(self): return self.category == const.AppCategory.remote_app.value + @property + def category_cloud(self): + return self.category == const.AppCategory.cloud.value + + @property + def category_db(self): + return self.category == const.AppCategory.db.value + def get_rdp_remote_app_setting(self): from applications.serializers.attrs import get_serializer_class_by_application_type if not self.category_remote_app: @@ -279,6 +287,18 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): if raise_exception: raise ValueError("Remote App not has asset attr") + def get_target_ip(self): + if self.category_remote_app: + asset = self.get_remote_app_asset() + target_ip = asset.ip + elif self.category_cloud: + target_ip = self.attrs.get('cluster') + elif self.category_db: + target_ip = self.attrs.get('host') + else: + target_ip = '' + return target_ip + class ApplicationUser(SystemUser): class Meta: diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 84ddee404..c80c65ce6 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -235,6 +235,9 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin def __str__(self): return '{0.hostname}({0.ip})'.format(self) + def get_target_ip(self): + return self.ip + def set_admin_user_relation(self): from .authbook import AuthBook if not self.admin_user: diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index f8c64e417..3e905076c 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -31,12 +31,13 @@ from perms.models.base import Action from perms.utils.application.permission import get_application_actions from perms.utils.asset.permission import get_asset_actions from common.const.http import PATCH +from terminal.models import EndpointRule from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, ) logger = get_logger(__name__) -__all__ = ['UserConnectionTokenViewSet'] +__all__ = ['UserConnectionTokenViewSet', 'TokenCacheMixin'] class ClientProtocolMixin: @@ -51,6 +52,17 @@ class ClientProtocolMixin: request: Request get_serializer: Callable create_token: Callable + get_serializer_context: Callable + + def get_smart_endpoint(self, protocol, asset=None, application=None): + if asset: + target_ip = asset.get_target_ip() + elif application: + target_ip = application.get_target_ip() + else: + target_ip = '' + endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request) + return endpoint def get_request_resource(self, serializer): asset = serializer.validated_data.get('asset') @@ -122,10 +134,10 @@ class ClientProtocolMixin: options['screen mode id:i'] = '2' if full_screen else '1' # RDP Server 地址 - address = settings.TERMINAL_RDP_ADDR - if not address or address == 'localhost:3389': - address = self.request.get_host().split(':')[0] + ':3389' - options['full address:s'] = address + endpoint = self.get_smart_endpoint( + protocol='rdp', asset=asset, application=application + ) + options['full address:s'] = f'{endpoint.host}:{endpoint.rdp_port}' # 用户名 options['username:s'] = '{}|{}'.format(user.username, token) if system_user.ad_domain: @@ -169,9 +181,12 @@ class ClientProtocolMixin: else: name = '*' + endpoint = self.get_smart_endpoint( + protocol='ssh', asset=asset, application=application + ) content = { - 'ip': settings.TERMINAL_KOKO_HOST, - 'port': str(settings.TERMINAL_KOKO_SSH_PORT), + 'ip': endpoint.host, + 'port': endpoint.ssh_port, 'username': f'JMS-{token}', 'password': secret } @@ -345,6 +360,7 @@ class SecretDetailMixin: class TokenCacheMixin: + """ endpoint smart view 用到此类来解析token中的资产、应用 """ CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}' def get_token_cache_key(self, token): diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index 4a4f3525d..a3ac13e82 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -4,7 +4,7 @@ import json from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text - +from django.core.validators import MinValueValidator, MaxValueValidator from ..utils import signer, crypto @@ -13,7 +13,7 @@ __all__ = [ 'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', 'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField', 'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField', - 'EncryptJsonDictCharField', + 'EncryptJsonDictCharField', 'PortField' ] @@ -180,3 +180,13 @@ class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField): class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField): pass + +class PortField(models.IntegerField): + def __init__(self, *args, **kwargs): + kwargs.update({ + 'blank': False, + 'null': False, + 'validators': [MinValueValidator(0), MaxValueValidator(65535)] + }) + super().__init__(*args, **kwargs) + diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 8e34d6f91..f72ed899f 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -310,16 +310,12 @@ class Config(dict): 'TERMINAL_HOST_KEY': '', 'TERMINAL_TELNET_REGEX': '', 'TERMINAL_COMMAND_STORAGE': {}, - 'TERMINAL_RDP_ADDR': lambda: urlparse(settings.SITE_URL).hostname + ':3389', - 'XRDP_ENABLED': True, - 'TERMINAL_KOKO_HOST': lambda: urlparse(settings.SITE_URL).hostname, - 'TERMINAL_KOKO_SSH_PORT': 2222, - + # 未来废弃(当下迁移会用) + 'TERMINAL_RDP_ADDR': '', + # 保留(Luna还在用) 'TERMINAL_MAGNUS_ENABLED': True, - 'TERMINAL_MAGNUS_HOST': lambda: urlparse(settings.SITE_URL).hostname, - 'TERMINAL_MAGNUS_MYSQL_PORT': 33060, - 'TERMINAL_MAGNUS_MARIADB_PORT': 33061, - 'TERMINAL_MAGNUS_POSTGRE_PORT': 54320, + # 保留(Luna还在用) + 'XRDP_ENABLED': True, # 安全配置 'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启 diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 2aa05afe5..794180fd2 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -140,9 +140,6 @@ CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = CONFIG.CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS XRDP_ENABLED = CONFIG.XRDP_ENABLED -TERMINAL_KOKO_HOST = CONFIG.TERMINAL_KOKO_HOST -TERMINAL_KOKO_SSH_PORT = CONFIG.TERMINAL_KOKO_SSH_PORT - # SMS enabled SMS_ENABLED = CONFIG.SMS_ENABLED SMS_BACKEND = CONFIG.SMS_BACKEND @@ -170,11 +167,3 @@ ANNOUNCEMENT = CONFIG.ANNOUNCEMENT # help HELP_DOCUMENT_URL = CONFIG.HELP_DOCUMENT_URL HELP_SUPPORT_URL = CONFIG.HELP_SUPPORT_URL - -# Magnus -MAGNUS_ENABLED = CONFIG.MAGNUS_ENABLED -TERMINAL_MAGNUS_HOST = CONFIG.TERMINAL_MAGNUS_HOST -TERMINAL_MAGNUS_ENABLED = CONFIG.TERMINAL_MAGNUS_ENABLED -TERMINAL_MAGNUS_MYSQL_PORT = CONFIG.TERMINAL_MAGNUS_MYSQL_PORT -TERMINAL_MAGNUS_MARIADB_PORT = CONFIG.TERMINAL_MAGNUS_MARIADB_PORT -TERMINAL_MAGNUS_POSTGRE_PORT = CONFIG.TERMINAL_MAGNUS_POSTGRE_PORT diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 080e2ab5e..73226f058 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:097c6d06ed8dcf2e1807560b6eb52d98cba31f25fe8d67ce4315668c150ca6b8 -size 129989 +oid sha256:70685e92cbf84f4178224a44fd84eb884a0acfb9749200541ea6655a9a397a72 +size 125019 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 757d1e463..c80ef4904 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-02 10:01+0800\n" +"POT-Creation-Date: 2022-04-12 17:03+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,31 +29,27 @@ msgstr "Acls" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:53 #: 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:661 -#: users/templates/users/_select_user_modal.html:13 -#: users/templates/users/user_asset_permission.html:37 -#: users/templates/users/user_asset_permission.html:154 -#: users/templates/users/user_database_app_permission.html:36 #: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:247 terminal/models/endpoint.py:56 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:247 terminal/models/endpoint.py:57 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" #: acls/models/base.py:31 authentication/models.py:17 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:26 -#: users/templates/users/_select_user_modal.html:18 msgid "Active" msgstr "アクティブ" @@ -65,6 +61,7 @@ msgstr "アクティブ" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 +#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:63 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 @@ -91,16 +88,12 @@ msgstr "ログイン確認" #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 #: authentication/models.py:50 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:101 rbac/models/rolebinding.py:40 templates/index.html:78 +#: rbac/builtin.py:106 rbac/models/rolebinding.py:40 #: terminal/backends/command/models.py:19 -#: terminal/backends/command/serializers.py:12 terminal/models/session.py:42 +#: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 #: users/models/user.py:917 users/serializers/group.py:19 -#: users/templates/users/user_asset_permission.html:38 -#: users/templates/users/user_asset_permission.html:64 -#: users/templates/users/user_database_app_permission.html:37 -#: users/templates/users/user_database_app_permission.html:58 msgid "User" msgstr "ユーザー" @@ -112,10 +105,6 @@ msgstr "ルール" #: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 #: assets/models/cmd_filter.py:89 audits/models.py:61 audits/serializers.py:51 #: authentication/templates/authentication/_access_key_modal.html:34 -#: users/templates/users/_granted_assets.html:29 -#: users/templates/users/user_asset_permission.html:44 -#: users/templates/users/user_asset_permission.html:79 -#: users/templates/users/user_database_app_permission.html:42 msgid "Action" msgstr "アクション" @@ -136,16 +125,13 @@ msgstr "システムユーザー" #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:36 -#: assets/models/asset.py:383 assets/models/authbook.py:19 +#: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/serializers/system_user.py:264 audits/models.py:39 -#: perms/models/asset_permission.py:23 templates/index.html:82 -#: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 +#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:20 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 -#: users/templates/users/user_asset_permission.html:40 -#: users/templates/users/user_asset_permission.html:70 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 #: xpack/plugins/cloud/models.py:223 @@ -172,7 +158,6 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 #: users/templates/users/_msg_user_created.html:12 -#: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:195 #: xpack/plugins/cloud/serializers/account_attrs.py:22 @@ -196,17 +181,13 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 -#: users/templates/users/_granted_assets.html:26 -#: users/templates/users/user_asset_permission.html:156 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:37 msgid "IP" msgstr "IP" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 #: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 -#: users/templates/users/_granted_assets.html:25 -#: users/templates/users/user_asset_permission.html:157 msgid "Hostname" msgstr "ホスト名" @@ -220,7 +201,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 #: assets/models/domain.py:62 assets/models/user.py:248 -#: terminal/serializers/session.py:30 terminal/serializers/storage.py:69 +#: terminal/serializers/session.py:30 terminal/serializers/storage.py:67 msgid "Protocol" msgstr "プロトコル" @@ -285,13 +266,7 @@ msgstr "アプリケーション" #: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 #: perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:35 terminal/models/session.py:46 -#: users/templates/users/_granted_assets.html:27 -#: users/templates/users/user_asset_permission.html:42 -#: users/templates/users/user_asset_permission.html:76 -#: users/templates/users/user_asset_permission.html:159 -#: users/templates/users/user_database_app_permission.html:40 -#: users/templates/users/user_database_app_permission.html:67 +#: terminal/backends/command/serializers.py:35 terminal/models/session.py:48 #: xpack/plugins/change_auth_plan/models/app.py:36 #: xpack/plugins/change_auth_plan/models/app.py:147 #: xpack/plugins/change_auth_plan/serializers/app.py:65 @@ -343,7 +318,7 @@ msgid "Domain" msgstr "ドメイン" #: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/serializers/account.py:58 msgid "Attrs" msgstr "ツールバーの" @@ -351,7 +326,7 @@ msgstr "ツールバーの" msgid "Can match application" msgstr "アプリケーションを一致させることができます" -#: applications/models/application.py:286 +#: applications/models/application.py:306 msgid "Application user" msgstr "アプリケーションユーザー" @@ -409,6 +384,7 @@ msgstr "クラスター" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 +#: terminal/models/endpoint.py:11 #: xpack/plugins/cloud/serializers/account_attrs.py:68 msgid "Host" msgstr "ホスト" @@ -639,27 +615,27 @@ msgstr "ラベル" msgid "Created by" msgstr "によって作成された" -#: assets/models/asset.py:386 +#: assets/models/asset.py:389 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" -#: assets/models/asset.py:387 +#: assets/models/asset.py:390 msgid "Can test asset connectivity" msgstr "資産接続をテストできます" -#: assets/models/asset.py:388 +#: assets/models/asset.py:391 msgid "Can push system user to asset" msgstr "システムユーザーを資産にプッシュできます" -#: assets/models/asset.py:389 +#: assets/models/asset.py:392 msgid "Can match asset" msgstr "アセットを一致させることができます" -#: assets/models/asset.py:390 +#: assets/models/asset.py:393 msgid "Add asset to node" msgstr "ノードにアセットを追加する" -#: assets/models/asset.py:391 +#: assets/models/asset.py:394 msgid "Move asset to node" msgstr "アセットをノードに移動する" @@ -706,7 +682,7 @@ msgid "Timing trigger" msgstr "タイミングトリガー" #: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31 -#: perms/models/base.py:89 terminal/models/session.py:56 +#: perms/models/base.py:89 terminal/models/session.py:58 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 #: xpack/plugins/change_auth_plan/models/base.py:112 @@ -767,7 +743,7 @@ msgstr "OK" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:30 msgid "Failed" msgstr "失敗しました" @@ -782,9 +758,8 @@ msgstr "確認済みの日付" #: assets/models/base.py:177 audits/signal_handlers.py:68 #: authentication/forms.py:22 #: authentication/templates/authentication/login.html:181 -#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 +#: settings/serializers/auth/ldap.py:43 users/forms/profile.py:21 #: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 @@ -849,11 +824,6 @@ msgstr "デフォルトクラスター" #: assets/models/cmd_filter.py:34 perms/models/base.py:86 #: users/models/group.py:31 users/models/user.py:667 -#: 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 -#: users/templates/users/user_database_app_permission.html:61 msgid "User group" msgstr "ユーザーグループ" @@ -866,7 +836,7 @@ msgid "Regex" msgstr "正規情報" #: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:53 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:55 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -966,7 +936,7 @@ msgstr "ラベル" msgid "New node" msgstr "新しいノード" -#: assets/models/node.py:474 users/templates/users/_granted_assets.html:130 +#: assets/models/node.py:474 msgid "empty" msgstr "空" @@ -983,9 +953,6 @@ msgid "Parent key" msgstr "親キー" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: users/templates/users/user_asset_permission.html:41 -#: users/templates/users/user_asset_permission.html:73 -#: users/templates/users/user_asset_permission.html:158 #: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -1140,7 +1107,7 @@ msgid "Actions" msgstr "アクション" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:59 +#: settings/serializers/auth/ldap.py:62 #: xpack/plugins/change_auth_plan/serializers/base.py:42 msgid "Periodic perform" msgstr "定期的なパフォーマンス" @@ -1388,8 +1355,7 @@ msgstr "監査" #: audits/models.py:27 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:166 users/templates/users/user_asset_permission.html:128 -#: users/templates/users/user_database_app_permission.html:111 +#: rbac/tree.py:166 msgid "Delete" msgstr "削除" @@ -1418,7 +1384,7 @@ msgid "Symlink" msgstr "Symlink" #: audits/models.py:38 audits/models.py:64 audits/models.py:87 -#: terminal/models/session.py:49 terminal/models/sharing.py:82 +#: terminal/models/session.py:51 terminal/models/sharing.py:82 msgid "Remote addr" msgstr "リモートaddr" @@ -1448,8 +1414,6 @@ msgstr "作成" #: audits/models.py:56 rbac/tree.py:165 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 -#: users/templates/users/user_asset_permission.html:127 -#: users/templates/users/user_database_app_permission.html:110 msgid "Update" msgstr "更新" @@ -1558,7 +1522,7 @@ msgstr "ホスト表示" msgid "Result" msgstr "結果" -#: audits/serializers.py:98 terminal/serializers/storage.py:161 +#: audits/serializers.py:98 terminal/serializers/storage.py:156 msgid "Hosts" msgstr "ホスト" @@ -1669,7 +1633,6 @@ msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" #: audits/signal_handlers.py:131 perms/models/asset_permission.py:29 -#: users/templates/users/_user_detail_nav_header.html:31 msgid "Asset permission" msgstr "資産権限" @@ -1767,7 +1730,7 @@ msgstr "{ApplicationPermission} 追加 {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}" -#: authentication/api/connection_token.py:313 +#: authentication/api/connection_token.py:328 msgid "Invalid token" msgstr "無効なトークン" @@ -2065,7 +2028,7 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:33 terminal/serializers/storage.py:30 +#: authentication/models.py:33 terminal/serializers/storage.py:28 msgid "Access key" msgstr "アクセスキー" @@ -2129,7 +2092,6 @@ msgid "Date" msgstr "日付" #: authentication/templates/authentication/_access_key_modal.html:48 -#: users/templates/users/_granted_assets.html:75 msgid "Show" msgstr "表示" @@ -2189,7 +2151,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:295 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:296 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2650,11 +2612,11 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:294 +#: jumpserver/conf.py:295 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:296 +#: jumpserver/conf.py:297 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -2720,12 +2682,12 @@ msgid "App ops" msgstr "アプリ操作" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:66 +#: settings/serializers/auth/ldap.py:69 msgid "Cycle perform" msgstr "サイクル実行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 -#: settings/serializers/auth/ldap.py:63 +#: settings/serializers/auth/ldap.py:66 msgid "Regularly perform" msgstr "定期的に実行する" @@ -2912,7 +2874,8 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:47 -#: rbac/serializers/rolebinding.py:40 tickets/serializers/ticket/ticket.py:77 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:59 +#: tickets/serializers/ticket/ticket.py:77 msgid "Organization" msgstr "組織" @@ -2925,7 +2888,7 @@ msgid "Can view root org" msgstr "グローバル組織を表示できます" #: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:43 -#: users/models/user.py:671 users/templates/users/_select_user_modal.html:15 +#: users/models/user.py:671 msgid "Role" msgstr "ロール" @@ -3151,27 +3114,27 @@ msgstr "{} 少なくとも1つのシステムロール" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:92 +#: rbac/builtin.py:97 msgid "SystemAdmin" msgstr "システム管理者" -#: rbac/builtin.py:95 +#: rbac/builtin.py:100 msgid "SystemAuditor" msgstr "システム監査人" -#: rbac/builtin.py:98 +#: rbac/builtin.py:103 msgid "SystemComponent" msgstr "システムコンポーネント" -#: rbac/builtin.py:104 +#: rbac/builtin.py:109 msgid "OrgAdmin" msgstr "組織管理者" -#: rbac/builtin.py:107 +#: rbac/builtin.py:112 msgid "OrgAuditor" msgstr "監査員を組織する" -#: rbac/builtin.py:110 +#: rbac/builtin.py:115 msgid "OrgUser" msgstr "組織ユーザー" @@ -3376,11 +3339,11 @@ msgstr "同期が実行中です。しばらくお待ちください。" msgid "Synchronization error: {}" msgstr "同期エラー: {}" -#: settings/api/ldap.py:211 +#: settings/api/ldap.py:213 msgid "Get ldap users is None" msgstr "Ldapユーザーを取得するにはNone" -#: settings/api/ldap.py:220 +#: settings/api/ldap.py:222 msgid "Imported {} users successfully (Organization: {})" msgstr "{} 人のユーザーを正常にインポートしました (組織: {})" @@ -3508,15 +3471,15 @@ msgstr "ピン認証の有効化" msgid "Enable FeiShu Auth" msgstr "飛本認証の有効化" -#: settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/ldap.py:38 msgid "LDAP server" msgstr "LDAPサーバー" -#: settings/serializers/auth/ldap.py:40 +#: settings/serializers/auth/ldap.py:39 msgid "eg: ldap://localhost:389" msgstr "例: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:42 +#: settings/serializers/auth/ldap.py:41 msgid "Bind DN" msgstr "DN のバインド" @@ -3549,15 +3512,15 @@ msgstr "" "ユーザー属性マッピングは、LDAPのユーザー属性をjumpserverユーザーにマッピング" "する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" -#: settings/serializers/auth/ldap.py:70 +#: settings/serializers/auth/ldap.py:73 msgid "Connect timeout" msgstr "接続タイムアウト" -#: settings/serializers/auth/ldap.py:72 +#: settings/serializers/auth/ldap.py:75 msgid "Search paged size" msgstr "ページサイズを検索" -#: settings/serializers/auth/ldap.py:74 +#: settings/serializers/auth/ldap.py:77 msgid "Enable LDAP auth" msgstr "LDAP認証の有効化" @@ -4256,61 +4219,13 @@ msgstr "" "ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバイスにロ" "グインできない場合は、このパラメーターを設定します。" -#: settings/serializers/terminal.py:37 -msgid "RDP address" -msgstr "RDPアドレス" - -#: settings/serializers/terminal.py:38 -msgid "RDP visit address, eg: dev.jumpserver.org:3389" -msgstr "RDP訪問先住所、例: dev.jumpserver.org:3389" - -#: settings/serializers/terminal.py:40 -msgid "Enable XRDP" -msgstr "XRDPの有効化" - -#: settings/serializers/terminal.py:43 -msgid "Koko host" -msgstr "KOKO ホストアドレス" - -#: settings/serializers/terminal.py:46 -msgid "Koko ssh port" -msgstr "Koko ssh ポート" - -#: settings/serializers/terminal.py:49 +#: settings/serializers/terminal.py:36 msgid "Enable database proxy" msgstr "属性マップの有効化" -#: settings/serializers/terminal.py:51 -msgid "Database proxy host" -msgstr "データベースエージェントホスト" - -#: settings/serializers/terminal.py:52 -msgid "Database proxy host, eg: dev.jumpserver.org" -msgstr "RDP訪問先住所、例: dev.jumpserver.org:3389" - -#: settings/serializers/terminal.py:55 -msgid "MySQL port" -msgstr "MySQLポート" - -#: settings/serializers/terminal.py:56 -msgid "MySQL protocol listen port" -msgstr "MySQLプロトコルリッスンポート" - -#: settings/serializers/terminal.py:59 -msgid "MariaDB port" -msgstr "MariaDBポート" - -#: settings/serializers/terminal.py:60 -msgid "MariaDB protocol listen port" -msgstr "MariaDBプロトコルリッスンポート" - -#: settings/serializers/terminal.py:63 -msgid "PostgreSQL port" -msgstr "PostgreSQLポート" - -#: settings/serializers/terminal.py:64 -msgid "PostgreSQL protocol listen port" -msgstr "PostgreSQLプロトコルリッスンポート" +#: settings/serializers/terminal.py:37 +msgid "Enable XRDP" +msgstr "XRDPの有効化" #: settings/utils/ldap.py:417 msgid "ldap:// or ldaps:// protocol is used." @@ -4413,10 +4328,6 @@ msgstr "認証に失敗しました (不明): {}" msgid "Authentication success: {}" msgstr "認証成功: {}" -#: templates/_base_list.html:37 -msgid "Search" -msgstr "検索" - #: templates/_csv_import_export.html:8 msgid "Export" msgstr "エクスポート" @@ -4466,7 +4377,6 @@ msgid "Commercial support" msgstr "商用サポート" #: templates/_header_bar.html:76 users/forms/profile.py:43 -#: users/templates/users/user_password_update.html:39 msgid "Profile" msgstr "プロフィール" @@ -4572,175 +4482,14 @@ msgstr "待つ:" msgid "The verification code has been sent" msgstr "確認コードが送信されました" -#: templates/_pagination.html:59 -msgid "" -"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" -msgstr "アイテムの結果を表示します _START_ to _END_; 合計 _TOTAL_ エントリ" - #: templates/_without_nav_base.html:26 msgid "Home page" msgstr "ホームページ" -#: templates/delete_confirm.html:6 -msgid "Confirm delete" -msgstr "削除の確認" - -#: templates/delete_confirm.html:11 -msgid "Are you sure delete" -msgstr "削除してもよろしいですか" - #: templates/flash_message_standalone.html:25 msgid "Cancel" msgstr "キャンセル" -#: templates/index.html:11 -msgid "Total users" -msgstr "合計ユーザー数" - -#: templates/index.html:23 -msgid "Total assets" -msgstr "総資産" - -#: templates/index.html:36 -msgid "Online users" -msgstr "オンラインユーザー" - -#: templates/index.html:49 -msgid "Online sessions" -msgstr "オンラインセッション" - -#: templates/index.html:61 -msgid "In the past week, a total of " -msgstr "過去1週間、共有 " - -#: templates/index.html:61 -msgid " users have logged in " -msgstr " ビットユーザーログイン." - -#: templates/index.html:61 -msgid " times asset." -msgstr " 次资产." - -#: templates/index.html:69 -msgid "Active user asset ratio" -msgstr "アクティブなユーザー資産比率" - -#: templates/index.html:72 -msgid "" -"The following graphs describe the percentage of active users per month and " -"assets per user host per month, respectively." -msgstr "" -"次のグラフは、1か月あたりのアクティブユーザーの割合と、1か月あたりのユーザー" -"ホストあたりの資産の割合をそれぞれ示しています。" - -#: templates/index.html:97 templates/index.html:112 -msgid "Top 10 assets in a week" -msgstr "1週間でトップ10の資産" - -#: templates/index.html:113 -msgid "Login frequency and last login record." -msgstr "ログイン頻度と最後のログイン記録。" - -#: templates/index.html:122 -msgid "Last 10 login" -msgstr "最後の10ログイン" - -#: templates/index.html:128 -msgid "Login record" -msgstr "ログイン記録" - -#: templates/index.html:129 -msgid "Last 10 login records." -msgstr "最後の10件のログイン記録。" - -#: templates/index.html:143 templates/index.html:158 -msgid "Top 10 users in a week" -msgstr "1週間でトップ10のユーザー" - -#: templates/index.html:159 -msgid "User login frequency and last login record." -msgstr "ユーザーログイン頻度と最後のログインレコード。" - -#: templates/index.html:184 -msgid "Monthly data overview" -msgstr "毎月のデータ概要" - -#: templates/index.html:185 -msgid "History summary in one month" -msgstr "1ヶ月で履歴概要" - -#: templates/index.html:193 templates/index.html:217 -msgid "Login count" -msgstr "ログイン数" - -#: templates/index.html:193 templates/index.html:224 -msgid "Active users" -msgstr "アクティブユーザー" - -#: templates/index.html:193 templates/index.html:231 -msgid "Active assets" -msgstr "アクティブな資産" - -#: templates/index.html:262 templates/index.html:313 -msgid "Monthly active users" -msgstr "毎月のアクティブユーザー数" - -#: templates/index.html:262 templates/index.html:314 -msgid "Disable user" -msgstr "ユーザーを無効にする" - -#: templates/index.html:262 templates/index.html:315 -msgid "Month not logged in user" -msgstr "ユーザーにログインしていない月" - -#: templates/index.html:288 templates/index.html:368 -msgid "Access to the source" -msgstr "ソースへのアクセス" - -#: templates/index.html:342 -msgid "Month is logged into the asset" -msgstr "月が資産にログインされます" - -#: templates/index.html:342 templates/index.html:393 -msgid "Disable host" -msgstr "ホストの無効化" - -#: templates/index.html:342 templates/index.html:394 -msgid "Month not logged on host" -msgstr "月がホストにログオンしていない" - -#: templates/index.html:392 -msgid "Month is logged into the host" -msgstr "月がホストにログインされます" - -#: templates/index.html:466 -msgid " times/week" -msgstr " 時間/週" - -#: templates/index.html:491 templates/index.html:527 -msgid " times" -msgstr " 回数" - -#: templates/index.html:494 templates/index.html:530 -msgid "The time last logged in" -msgstr "最後にログインした時刻" - -#: templates/index.html:495 templates/index.html:531 -msgid "At" -msgstr "于" - -#: templates/index.html:510 templates/index.html:545 templates/index.html:580 -msgid "(No)" -msgstr "(しばらく)" - -#: templates/index.html:561 -msgid "Before" -msgstr "前" - -#: templates/index.html:562 -msgid "Login in " -msgstr "ログイン" - #: templates/resource_download.html:18 templates/resource_download.html:24 #: templates/resource_download.html:25 templates/resource_download.html:30 msgid "Client" @@ -4786,19 +4535,23 @@ msgstr "" msgid "Filters" msgstr "フィルター" -#: terminal/api/session.py:211 +#: terminal/api/endpoint.py:65 +msgid "Not found protocol query params" +msgstr "" + +#: terminal/api/session.py:210 msgid "Session does not exist: {}" msgstr "セッションが存在しません: {}" -#: terminal/api/session.py:214 +#: terminal/api/session.py:213 msgid "Session is finished or the protocol not supported" msgstr "セッションが終了したか、プロトコルがサポートされていません" -#: terminal/api/session.py:219 +#: terminal/api/session.py:218 msgid "User does not exist: {}" msgstr "ユーザーが存在しない: {}" -#: terminal/api/session.py:227 +#: terminal/api/session.py:226 msgid "User does not have permission" msgstr "ユーザーに権限がありません" @@ -4842,7 +4595,7 @@ msgstr "オンラインセッションを持つ" msgid "Terminals" msgstr "ターミナル管理" -#: terminal/backends/command/es.py:27 +#: terminal/backends/command/es.py:26 msgid "Invalid elasticsearch config" msgstr "無効なElasticsearch構成" @@ -4899,7 +4652,6 @@ msgid "High" msgstr "高い" #: terminal/const.py:35 users/templates/users/reset_password.html:50 -#: users/templates/users/user_password_update.html:104 msgid "Normal" msgstr "正常" @@ -4919,6 +4671,49 @@ msgstr "ストレージが無効です" msgid "Command record" msgstr "コマンドレコード" +#: terminal/models/endpoint.py:13 +msgid "HTTPS Port" +msgstr "HTTPS ポート" + +#: terminal/models/endpoint.py:14 terminal/models/terminal.py:107 +msgid "HTTP Port" +msgstr "HTTP ポート" + +#: terminal/models/endpoint.py:15 terminal/models/terminal.py:106 +msgid "SSH Port" +msgstr "SSH ポート" + +#: terminal/models/endpoint.py:16 +msgid "RDP Port" +msgstr "RDP ポート" + +#: terminal/models/endpoint.py:17 +msgid "MySQL Port" +msgstr "MySQL ポート" + +#: terminal/models/endpoint.py:18 +msgid "MariaDB Port" +msgstr "MariaDB ポート" + +#: terminal/models/endpoint.py:19 +msgid "PostgreSQL Port" +msgstr "PostgreSQL ポート" + +#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:61 +#: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 +#: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 +#: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 +msgid "Endpoint" +msgstr "エンドポイント" + +#: terminal/models/endpoint.py:54 +msgid "IP group" +msgstr "IP グループ" + +#: terminal/models/endpoint.py:66 +msgid "Endpoint rule" +msgstr "エンドポイントルール" + #: terminal/models/replay.py:12 msgid "Session replay" msgstr "セッション再生" @@ -4931,35 +4726,35 @@ msgstr "セッションのリプレイをアップロードできます" msgid "Can download session replay" msgstr "セッション再生をダウンロードできます" -#: terminal/models/session.py:48 terminal/models/sharing.py:87 +#: terminal/models/session.py:50 terminal/models/sharing.py:87 msgid "Login from" msgstr "ログイン元" -#: terminal/models/session.py:52 +#: terminal/models/session.py:54 msgid "Replay" msgstr "リプレイ" -#: terminal/models/session.py:57 +#: terminal/models/session.py:59 msgid "Date end" msgstr "終了日" -#: terminal/models/session.py:242 +#: terminal/models/session.py:251 msgid "Session record" msgstr "セッション記録" -#: terminal/models/session.py:244 +#: terminal/models/session.py:253 msgid "Can monitor session" msgstr "セッションを監視できます" -#: terminal/models/session.py:245 +#: terminal/models/session.py:254 msgid "Can share session" msgstr "セッションを共有できます" -#: terminal/models/session.py:246 +#: terminal/models/session.py:255 msgid "Can terminate session" msgstr "セッションを終了できます" -#: terminal/models/session.py:247 +#: terminal/models/session.py:256 msgid "Can validate session action perm" msgstr "セッションアクションのパーマを検証できます" @@ -5068,14 +4863,6 @@ msgstr "クワーグ" msgid "type" msgstr "タイプ" -#: terminal/models/terminal.py:106 -msgid "SSH Port" -msgstr "SSHポート" - -#: terminal/models/terminal.py:107 -msgid "HTTP Port" -msgstr "HTTPポート" - #: terminal/models/terminal.py:183 terminal/serializers/session.py:38 msgid "Terminal" msgstr "ターミナル" @@ -5132,65 +4919,59 @@ msgstr "終了できます" msgid "Command amount" msgstr "コマンド量" -#: terminal/serializers/storage.py:21 +#: terminal/serializers/storage.py:19 msgid "Endpoint invalid: remove path `{}`" msgstr "エンドポイントが無効: パス '{}' を削除" -#: terminal/serializers/storage.py:27 +#: terminal/serializers/storage.py:25 msgid "Bucket" msgstr "バケット" -#: terminal/serializers/storage.py:34 users/models/user.py:695 +#: terminal/serializers/storage.py:32 users/models/user.py:695 msgid "Secret key" msgstr "秘密キー" -#: terminal/serializers/storage.py:39 terminal/serializers/storage.py:51 -#: terminal/serializers/storage.py:81 terminal/serializers/storage.py:91 -#: terminal/serializers/storage.py:99 -msgid "Endpoint" -msgstr "エンドポイント" - -#: terminal/serializers/storage.py:66 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "リージョン" -#: terminal/serializers/storage.py:110 +#: terminal/serializers/storage.py:108 msgid "Container name" msgstr "コンテナー名" -#: terminal/serializers/storage.py:112 +#: terminal/serializers/storage.py:110 msgid "Account name" msgstr "アカウント名" -#: terminal/serializers/storage.py:113 +#: terminal/serializers/storage.py:111 msgid "Account key" msgstr "アカウントキー" -#: terminal/serializers/storage.py:116 +#: terminal/serializers/storage.py:114 msgid "Endpoint suffix" msgstr "エンドポイントサフィックス" -#: terminal/serializers/storage.py:138 +#: terminal/serializers/storage.py:134 msgid "The address format is incorrect" msgstr "アドレス形式が正しくありません" -#: terminal/serializers/storage.py:145 +#: terminal/serializers/storage.py:141 msgid "Host invalid" msgstr "ホスト無効" -#: terminal/serializers/storage.py:148 +#: terminal/serializers/storage.py:144 msgid "Port invalid" msgstr "ポートが無効" -#: terminal/serializers/storage.py:164 +#: terminal/serializers/storage.py:159 msgid "Index" msgstr "インデックス" -#: terminal/serializers/storage.py:166 +#: terminal/serializers/storage.py:161 msgid "Doc type" msgstr "Docタイプ" -#: terminal/serializers/storage.py:168 +#: terminal/serializers/storage.py:163 msgid "Ignore Certificate Verification" msgstr "証明書の検証を無視する" @@ -5748,7 +5529,6 @@ msgid "Not a valid ssh public key" msgstr "有効なssh公開鍵ではありません" #: users/forms/profile.py:160 users/models/user.py:692 -#: users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "公開キー" @@ -5957,10 +5737,6 @@ msgstr "セキュリティのために、複数のユーザーのみをリスト msgid "name not unique" msgstr "名前が一意ではない" -#: users/templates/users/_granted_assets.html:7 -msgid "Loading" -msgstr "読み込み中" - #: users/templates/users/_msg_account_expire_reminder.html:7 msgid "Your account will expire in" msgstr "アカウントの有効期限は" @@ -6015,69 +5791,11 @@ msgstr "あなたのssh公開鍵はサイト管理者によってリセットさ msgid "click here to set your password" msgstr "ここをクリックしてパスワードを設定してください" -#: users/templates/users/_select_user_modal.html:5 -msgid "Please Select User" -msgstr "ユーザーを選択してください" - -#: users/templates/users/_select_user_modal.html:17 -msgid "Asset num" -msgstr "資産num" - -#: users/templates/users/_user_detail_nav_header.html:11 -msgid "User detail" -msgstr "ユーザーの詳細" - -#: users/templates/users/_user_detail_nav_header.html:15 -msgid "User permissions" -msgstr "ユーザー権限" - -#: users/templates/users/_user_detail_nav_header.html:23 -msgid "Asset granted" -msgstr "付与された資産" - -#: users/templates/users/_user_detail_nav_header.html:40 -msgid "RemoteApp granted" -msgstr "承認されたリモートアプリケーション" - -#: users/templates/users/_user_detail_nav_header.html:47 -msgid "RemoteApp permission" -msgstr "リモートアプリケーションのライセンス" - -#: users/templates/users/_user_detail_nav_header.html:54 -msgid "DatabaseApp granted" -msgstr "認可されたデータベース・アプリケーション" - -#: users/templates/users/_user_detail_nav_header.html:61 -msgid "DatabaseApp permission" -msgstr "数据库应用授权" - -#: users/templates/users/_user_update_pk_modal.html:4 -msgid "Update User SSH Public Key" -msgstr "SSHキーの更新" - -#: users/templates/users/first_login.html:6 -#: users/templates/users/first_login_done.html:19 -msgid "First Login" -msgstr "最初のログイン" - -#: users/templates/users/first_login_done.html:31 -msgid "Welcome to use jumpserver, visit " -msgstr "JumpServer砦機へようこそ" - -#: users/templates/users/first_login_done.html:32 -msgid "Use guide" -msgstr "ガイド人" - -#: users/templates/users/first_login_done.html:32 -msgid " for more information" -msgstr "より多くの情報のため" - #: users/templates/users/forgot_password.html:23 msgid "Input your email, that will send a mail to your" msgstr "あなたのメールを入力し、それはあなたにメールを送信します" #: users/templates/users/forgot_password.html:32 -#: users/templates/users/user_password_update.html:75 msgid "Submit" msgstr "送信" @@ -6094,12 +5812,10 @@ msgid "MFA setting" msgstr "MFAの設定" #: users/templates/users/reset_password.html:23 -#: users/templates/users/user_password_update.html:64 msgid "Your password must satisfy" msgstr "パスワードを満たす必要があります" #: users/templates/users/reset_password.html:24 -#: users/templates/users/user_password_update.html:65 msgid "Password strength" msgstr "パスワードの強さ" @@ -6108,54 +5824,25 @@ msgid "Setting" msgstr "設定" #: users/templates/users/reset_password.html:48 -#: users/templates/users/user_password_update.html:102 msgid "Very weak" msgstr "非常に弱い" #: users/templates/users/reset_password.html:49 -#: users/templates/users/user_password_update.html:103 msgid "Weak" msgstr "弱い" #: users/templates/users/reset_password.html:51 -#: users/templates/users/user_password_update.html:105 msgid "Medium" msgstr "中" #: users/templates/users/reset_password.html:52 -#: users/templates/users/user_password_update.html:106 msgid "Strong" msgstr "強い" #: users/templates/users/reset_password.html:53 -#: users/templates/users/user_password_update.html:107 msgid "Very strong" msgstr "非常に強い" -#: users/templates/users/user_asset_permission.html:43 -#: users/templates/users/user_asset_permission.html:155 -#: users/templates/users/user_database_app_permission.html:41 -#: xpack/plugins/cloud/models.py:34 -msgid "Validity" -msgstr "有効性" - -#: users/templates/users/user_asset_permission.html:160 -msgid "Inherit" -msgstr "継承" - -#: users/templates/users/user_asset_permission.html:161 -msgid "Include" -msgstr "含める" - -#: users/templates/users/user_asset_permission.html:162 -msgid "Exclude" -msgstr "除外" - -#: users/templates/users/user_database_app_permission.html:39 -#: users/templates/users/user_database_app_permission.html:64 -msgid "DatabaseApp" -msgstr "データベースの適用" - #: users/templates/users/user_otp_check_password.html:6 msgid "Enable OTP" msgstr "OTPの有効化" @@ -6205,10 +5892,6 @@ msgstr "" "インストール後、次のステップをクリックしてバインディングページに入ります (イ" "ンストールされている場合は、次のステップに直接進みます)。" -#: users/templates/users/user_password_update.html:74 -msgid "Reset" -msgstr "リセット" - #: users/templates/users/user_password_verify.html:8 #: users/templates/users/user_password_verify.html:9 msgid "Verify password" @@ -6520,62 +6203,58 @@ msgid "Baidu Cloud" msgstr "百度雲" #: xpack/plugins/cloud/const.py:15 -msgid "JD Cloud" -msgstr "京東雲" - -#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "テンセント雲" -#: xpack/plugins/cloud/const.py:17 +#: xpack/plugins/cloud/const.py:16 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:18 msgid "Huawei Private Cloud" msgstr "華為私有雲" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:19 msgid "Qingyun Private Cloud" msgstr "青雲私有雲" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:20 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:22 +#: xpack/plugins/cloud/const.py:21 msgid "Google Cloud Platform" msgstr "谷歌雲" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:25 msgid "Instance name" msgstr "インスタンス名" -#: xpack/plugins/cloud/const.py:27 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name and Partial IP" msgstr "インスタンス名と部分IP" -#: xpack/plugins/cloud/const.py:32 +#: xpack/plugins/cloud/const.py:31 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:35 msgid "Unsync" msgstr "同期していません" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:36 msgid "New Sync" msgstr "新しい同期" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:37 msgid "Synced" msgstr "同期済み" -#: xpack/plugins/cloud/const.py:39 +#: xpack/plugins/cloud/const.py:38 msgid "Released" msgstr "リリース済み" @@ -6587,6 +6266,10 @@ msgstr "クラウドセンター" msgid "Provider" msgstr "プロバイダー" +#: xpack/plugins/cloud/models.py:34 +msgid "Validity" +msgstr "有効性" + #: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "クラウドアカウント" @@ -6748,13 +6431,11 @@ msgid "South America (São Paulo)" msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "華南-広州" @@ -6776,7 +6457,6 @@ msgid "CN North-Baoding" msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "華東-上海" @@ -6841,15 +6521,11 @@ msgstr "華北-ウランチャブ一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "華南-広州-友好ユーザー環境" -#: xpack/plugins/cloud/providers/jdcloud.py:128 -msgid "CN East-Suqian" -msgstr "華東-宿遷" - -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Validity display" msgstr "有効表示" -#: xpack/plugins/cloud/serializers/account.py:61 +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Provider display" msgstr "プロバイダ表示" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 88f701b6b..5b9f31b64 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d6a0d40534209f3ffab0b0ecfab7ec82f137156fc8e7b19ce1711036d14aeca -size 107709 +oid sha256:9c13775875a335e3c8dbc7f666af622c5aa12050100b15e616c210e8e3043e38 +size 103490 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index fb29c7d9c..b15b2c545 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-02 10:01+0800\n" +"POT-Creation-Date: 2022-04-12 17:03+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -28,31 +28,27 @@ msgstr "访问控制" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:53 #: 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:661 -#: users/templates/users/_select_user_modal.html:13 -#: users/templates/users/user_asset_permission.html:37 -#: users/templates/users/user_asset_permission.html:154 -#: users/templates/users/user_database_app_permission.html:36 #: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:247 terminal/models/endpoint.py:56 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:247 terminal/models/endpoint.py:57 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" #: acls/models/base.py:31 authentication/models.py:17 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:26 -#: users/templates/users/_select_user_modal.html:18 msgid "Active" msgstr "激活中" @@ -64,6 +60,7 @@ msgstr "激活中" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 +#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:63 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 @@ -90,16 +87,12 @@ msgstr "登录复核" #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 #: authentication/models.py:50 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:101 rbac/models/rolebinding.py:40 templates/index.html:78 +#: rbac/builtin.py:106 rbac/models/rolebinding.py:40 #: terminal/backends/command/models.py:19 -#: terminal/backends/command/serializers.py:12 terminal/models/session.py:42 +#: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 #: users/models/user.py:917 users/serializers/group.py:19 -#: users/templates/users/user_asset_permission.html:38 -#: users/templates/users/user_asset_permission.html:64 -#: users/templates/users/user_database_app_permission.html:37 -#: users/templates/users/user_database_app_permission.html:58 msgid "User" msgstr "用户" @@ -111,10 +104,6 @@ msgstr "规则" #: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 #: assets/models/cmd_filter.py:89 audits/models.py:61 audits/serializers.py:51 #: authentication/templates/authentication/_access_key_modal.html:34 -#: users/templates/users/_granted_assets.html:29 -#: users/templates/users/user_asset_permission.html:44 -#: users/templates/users/user_asset_permission.html:79 -#: users/templates/users/user_database_app_permission.html:42 msgid "Action" msgstr "动作" @@ -135,16 +124,13 @@ msgstr "系统用户" #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:36 -#: assets/models/asset.py:383 assets/models/authbook.py:19 +#: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/serializers/system_user.py:264 audits/models.py:39 -#: perms/models/asset_permission.py:23 templates/index.html:82 -#: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 +#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:20 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 -#: users/templates/users/user_asset_permission.html:40 -#: users/templates/users/user_asset_permission.html:70 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 #: xpack/plugins/cloud/models.py:223 @@ -171,7 +157,6 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 #: users/templates/users/_msg_user_created.html:12 -#: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:195 #: xpack/plugins/cloud/serializers/account_attrs.py:22 @@ -194,17 +179,13 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 -#: users/templates/users/_granted_assets.html:26 -#: users/templates/users/user_asset_permission.html:156 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:37 msgid "IP" msgstr "IP" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 #: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 -#: users/templates/users/_granted_assets.html:25 -#: users/templates/users/user_asset_permission.html:157 msgid "Hostname" msgstr "主机名" @@ -216,7 +197,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议 #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 #: assets/models/domain.py:62 assets/models/user.py:248 -#: terminal/serializers/session.py:30 terminal/serializers/storage.py:69 +#: terminal/serializers/session.py:30 terminal/serializers/storage.py:67 msgid "Protocol" msgstr "协议" @@ -280,13 +261,7 @@ msgstr "应用程序" #: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 #: perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:35 terminal/models/session.py:46 -#: users/templates/users/_granted_assets.html:27 -#: users/templates/users/user_asset_permission.html:42 -#: users/templates/users/user_asset_permission.html:76 -#: users/templates/users/user_asset_permission.html:159 -#: users/templates/users/user_database_app_permission.html:40 -#: users/templates/users/user_database_app_permission.html:67 +#: terminal/backends/command/serializers.py:35 terminal/models/session.py:48 #: xpack/plugins/change_auth_plan/models/app.py:36 #: xpack/plugins/change_auth_plan/models/app.py:147 #: xpack/plugins/change_auth_plan/serializers/app.py:65 @@ -338,7 +313,7 @@ msgid "Domain" msgstr "网域" #: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/serializers/account.py:58 msgid "Attrs" msgstr "属性" @@ -346,7 +321,7 @@ msgstr "属性" msgid "Can match application" msgstr "匹配应用" -#: applications/models/application.py:286 +#: applications/models/application.py:306 msgid "Application user" msgstr "应用用户" @@ -404,6 +379,7 @@ msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 +#: terminal/models/endpoint.py:11 #: xpack/plugins/cloud/serializers/account_attrs.py:68 msgid "Host" msgstr "主机" @@ -634,27 +610,27 @@ msgstr "标签管理" msgid "Created by" msgstr "创建者" -#: assets/models/asset.py:386 +#: assets/models/asset.py:389 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" -#: assets/models/asset.py:387 +#: assets/models/asset.py:390 msgid "Can test asset connectivity" msgstr "可以测试资产连接性" -#: assets/models/asset.py:388 +#: assets/models/asset.py:391 msgid "Can push system user to asset" msgstr "可以推送系统用户到资产" -#: assets/models/asset.py:389 +#: assets/models/asset.py:392 msgid "Can match asset" msgstr "可以匹配资产" -#: assets/models/asset.py:390 +#: assets/models/asset.py:393 msgid "Add asset to node" msgstr "添加资产到节点" -#: assets/models/asset.py:391 +#: assets/models/asset.py:394 msgid "Move asset to node" msgstr "移动资产到节点" @@ -701,7 +677,7 @@ msgid "Timing trigger" msgstr "定时触发" #: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31 -#: perms/models/base.py:89 terminal/models/session.py:56 +#: perms/models/base.py:89 terminal/models/session.py:58 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 #: xpack/plugins/change_auth_plan/models/base.py:112 @@ -762,7 +738,7 @@ msgstr "成功" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:30 msgid "Failed" msgstr "失败" @@ -777,9 +753,8 @@ msgstr "校验日期" #: assets/models/base.py:177 audits/signal_handlers.py:68 #: authentication/forms.py:22 #: authentication/templates/authentication/login.html:181 -#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 +#: settings/serializers/auth/ldap.py:43 users/forms/profile.py:21 #: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 @@ -844,11 +819,6 @@ msgstr "默认Cluster" #: assets/models/cmd_filter.py:34 perms/models/base.py:86 #: users/models/group.py:31 users/models/user.py:667 -#: 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 -#: users/templates/users/user_database_app_permission.html:61 msgid "User group" msgstr "用户组" @@ -861,7 +831,7 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:53 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:55 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -961,7 +931,7 @@ msgstr "标签" msgid "New node" msgstr "新节点" -#: assets/models/node.py:474 users/templates/users/_granted_assets.html:130 +#: assets/models/node.py:474 msgid "empty" msgstr "空" @@ -978,9 +948,6 @@ msgid "Parent key" msgstr "ssh私钥" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: users/templates/users/user_asset_permission.html:41 -#: users/templates/users/user_asset_permission.html:73 -#: users/templates/users/user_asset_permission.html:158 #: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -1132,7 +1099,7 @@ msgid "Actions" msgstr "动作" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:59 +#: settings/serializers/auth/ldap.py:62 #: xpack/plugins/change_auth_plan/serializers/base.py:42 msgid "Periodic perform" msgstr "定时执行" @@ -1376,8 +1343,7 @@ msgstr "日志审计" #: audits/models.py:27 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:166 users/templates/users/user_asset_permission.html:128 -#: users/templates/users/user_database_app_permission.html:111 +#: rbac/tree.py:166 msgid "Delete" msgstr "删除" @@ -1406,7 +1372,7 @@ msgid "Symlink" msgstr "建立软链接" #: audits/models.py:38 audits/models.py:64 audits/models.py:87 -#: terminal/models/session.py:49 terminal/models/sharing.py:82 +#: terminal/models/session.py:51 terminal/models/sharing.py:82 msgid "Remote addr" msgstr "远端地址" @@ -1436,8 +1402,6 @@ msgstr "创建" #: audits/models.py:56 rbac/tree.py:165 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 -#: users/templates/users/user_asset_permission.html:127 -#: users/templates/users/user_database_app_permission.html:110 msgid "Update" msgstr "更新" @@ -1546,7 +1510,7 @@ msgstr "主机名称" msgid "Result" msgstr "结果" -#: audits/serializers.py:98 terminal/serializers/storage.py:161 +#: audits/serializers.py:98 terminal/serializers/storage.py:156 msgid "Hosts" msgstr "主机" @@ -1657,7 +1621,6 @@ msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" #: audits/signal_handlers.py:131 perms/models/asset_permission.py:29 -#: users/templates/users/_user_detail_nav_header.html:31 msgid "Asset permission" msgstr "资产授权" @@ -1755,7 +1718,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" -#: authentication/api/connection_token.py:313 +#: authentication/api/connection_token.py:328 msgid "Invalid token" msgstr "无效的令牌" @@ -2044,7 +2007,7 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:33 terminal/serializers/storage.py:30 +#: authentication/models.py:33 terminal/serializers/storage.py:28 msgid "Access key" msgstr "API key" @@ -2108,7 +2071,6 @@ msgid "Date" msgstr "日期" #: authentication/templates/authentication/_access_key_modal.html:48 -#: users/templates/users/_granted_assets.html:75 msgid "Show" msgstr "显示" @@ -2168,7 +2130,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:295 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:296 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2620,11 +2582,11 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:294 +#: jumpserver/conf.py:295 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:296 +#: jumpserver/conf.py:297 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -2685,12 +2647,12 @@ msgid "App ops" msgstr "作业中心" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:66 +#: settings/serializers/auth/ldap.py:69 msgid "Cycle perform" msgstr "周期执行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 -#: settings/serializers/auth/ldap.py:63 +#: settings/serializers/auth/ldap.py:66 msgid "Regularly perform" msgstr "定期执行" @@ -2877,7 +2839,8 @@ msgstr "组织管理" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:47 -#: rbac/serializers/rolebinding.py:40 tickets/serializers/ticket/ticket.py:77 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:59 +#: tickets/serializers/ticket/ticket.py:77 msgid "Organization" msgstr "组织" @@ -2890,7 +2853,7 @@ msgid "Can view root org" msgstr "可以查看全局组织" #: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:43 -#: users/models/user.py:671 users/templates/users/_select_user_modal.html:15 +#: users/models/user.py:671 msgid "Role" msgstr "角色" @@ -3114,27 +3077,27 @@ msgstr "{} 至少有一个系统角色" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:92 +#: rbac/builtin.py:97 msgid "SystemAdmin" msgstr "系统管理员" -#: rbac/builtin.py:95 +#: rbac/builtin.py:100 msgid "SystemAuditor" msgstr "系统审计员" -#: rbac/builtin.py:98 +#: rbac/builtin.py:103 msgid "SystemComponent" msgstr "系统组件" -#: rbac/builtin.py:104 +#: rbac/builtin.py:109 msgid "OrgAdmin" msgstr "组织管理员" -#: rbac/builtin.py:107 +#: rbac/builtin.py:112 msgid "OrgAuditor" msgstr "组织审计员" -#: rbac/builtin.py:110 +#: rbac/builtin.py:115 msgid "OrgUser" msgstr "组织用户" @@ -3338,11 +3301,11 @@ msgstr "同步正在运行,请稍等" msgid "Synchronization error: {}" msgstr "同步错误: {}" -#: settings/api/ldap.py:211 +#: settings/api/ldap.py:213 msgid "Get ldap users is None" msgstr "获取 LDAP 用户为 None" -#: settings/api/ldap.py:220 +#: settings/api/ldap.py:222 msgid "Imported {} users successfully (Organization: {})" msgstr "成功导入 {} 个用户 ( 组织: {} )" @@ -3470,15 +3433,15 @@ msgstr "启用钉钉认证" msgid "Enable FeiShu Auth" msgstr "启用飞书认证" -#: settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/ldap.py:38 msgid "LDAP server" msgstr "LDAP 地址" -#: settings/serializers/auth/ldap.py:40 +#: settings/serializers/auth/ldap.py:39 msgid "eg: ldap://localhost:389" msgstr "如: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:42 +#: settings/serializers/auth/ldap.py:41 msgid "Bind DN" msgstr "绑定 DN" @@ -3511,15 +3474,15 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的用户需要属性" -#: settings/serializers/auth/ldap.py:70 +#: settings/serializers/auth/ldap.py:73 msgid "Connect timeout" msgstr "连接超时时间" -#: settings/serializers/auth/ldap.py:72 +#: settings/serializers/auth/ldap.py:75 msgid "Search paged size" msgstr "搜索分页数量" -#: settings/serializers/auth/ldap.py:74 +#: settings/serializers/auth/ldap.py:77 msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" @@ -4194,61 +4157,13 @@ msgid "" "device through Telnet, set this parameter" msgstr "不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" -#: settings/serializers/terminal.py:37 -msgid "RDP address" -msgstr "RDP 地址" - -#: settings/serializers/terminal.py:38 -msgid "RDP visit address, eg: dev.jumpserver.org:3389" -msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389" - -#: settings/serializers/terminal.py:40 -msgid "Enable XRDP" -msgstr "启用 XRDP 服务" - -#: settings/serializers/terminal.py:43 -msgid "Koko host" -msgstr "KOKO 主机地址" - -#: settings/serializers/terminal.py:46 -msgid "Koko ssh port" -msgstr "KOKO ssh 端口" - -#: settings/serializers/terminal.py:49 +#: settings/serializers/terminal.py:36 msgid "Enable database proxy" msgstr "启用数据库组件" -#: settings/serializers/terminal.py:51 -msgid "Database proxy host" -msgstr "数据库主机地址" - -#: settings/serializers/terminal.py:52 -msgid "Database proxy host, eg: dev.jumpserver.org" -msgstr "数据库组件地址, 如: dev.jumpserver.org (没有端口, 不同协议端口不同)" - -#: settings/serializers/terminal.py:55 -msgid "MySQL port" -msgstr "MySQL 协议端口" - -#: settings/serializers/terminal.py:56 -msgid "MySQL protocol listen port" -msgstr "MySQL 协议监听端口" - -#: settings/serializers/terminal.py:59 -msgid "MariaDB port" -msgstr "MariaDB 端口" - -#: settings/serializers/terminal.py:60 -msgid "MariaDB protocol listen port" -msgstr "MariaDB 协议监听的端口" - -#: settings/serializers/terminal.py:63 -msgid "PostgreSQL port" -msgstr "PostgreSQL 端口" - -#: settings/serializers/terminal.py:64 -msgid "PostgreSQL protocol listen port" -msgstr "PostgreSQL 协议监听端口" +#: settings/serializers/terminal.py:37 +msgid "Enable XRDP" +msgstr "启用 XRDP 服务" #: settings/utils/ldap.py:417 msgid "ldap:// or ldaps:// protocol is used." @@ -4351,10 +4266,6 @@ msgstr "认证失败: (未知): {}" msgid "Authentication success: {}" msgstr "认证成功: {}" -#: templates/_base_list.html:37 -msgid "Search" -msgstr "搜索" - #: templates/_csv_import_export.html:8 msgid "Export" msgstr "导出" @@ -4400,7 +4311,6 @@ msgid "Commercial support" msgstr "商业支持" #: templates/_header_bar.html:76 users/forms/profile.py:43 -#: users/templates/users/user_password_update.html:39 msgid "Profile" msgstr "个人信息" @@ -4505,173 +4415,14 @@ msgstr "等待:" msgid "The verification code has been sent" msgstr "验证码已发送" -#: templates/_pagination.html:59 -msgid "" -"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" -msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" - #: templates/_without_nav_base.html:26 msgid "Home page" msgstr "首页" -#: templates/delete_confirm.html:6 -msgid "Confirm delete" -msgstr "确认删除" - -#: templates/delete_confirm.html:11 -msgid "Are you sure delete" -msgstr "您确定删除吗?" - #: templates/flash_message_standalone.html:25 msgid "Cancel" msgstr "取消" -#: templates/index.html:11 -msgid "Total users" -msgstr "用户总数" - -#: templates/index.html:23 -msgid "Total assets" -msgstr "资产总数" - -#: templates/index.html:36 -msgid "Online users" -msgstr "在线用户" - -#: templates/index.html:49 -msgid "Online sessions" -msgstr "在线会话" - -#: templates/index.html:61 -msgid "In the past week, a total of " -msgstr "过去一周, 共有 " - -#: templates/index.html:61 -msgid " users have logged in " -msgstr " 位用户登录 " - -#: templates/index.html:61 -msgid " times asset." -msgstr " 次资产." - -#: templates/index.html:69 -msgid "Active user asset ratio" -msgstr "活跃用户资产占比" - -#: templates/index.html:72 -msgid "" -"The following graphs describe the percentage of active users per month and " -"assets per user host per month, respectively." -msgstr "以下图形分别描述一个月活跃用户和资产占所有用户主机的百分比" - -#: templates/index.html:97 templates/index.html:112 -msgid "Top 10 assets in a week" -msgstr "一周Top10资产" - -#: templates/index.html:113 -msgid "Login frequency and last login record." -msgstr "登录次数及最近一次登录记录." - -#: templates/index.html:122 -msgid "Last 10 login" -msgstr "最近十次登录" - -#: templates/index.html:128 -msgid "Login record" -msgstr "登录记录" - -#: templates/index.html:129 -msgid "Last 10 login records." -msgstr "最近十次登录记录." - -#: templates/index.html:143 templates/index.html:158 -msgid "Top 10 users in a week" -msgstr "一周Top10用户" - -#: templates/index.html:159 -msgid "User login frequency and last login record." -msgstr "用户登录次数及最近一次登录记录" - -#: templates/index.html:184 -msgid "Monthly data overview" -msgstr "月数据总览" - -#: templates/index.html:185 -msgid "History summary in one month" -msgstr "一个月内历史汇总" - -#: templates/index.html:193 templates/index.html:217 -msgid "Login count" -msgstr "登录次数" - -#: templates/index.html:193 templates/index.html:224 -msgid "Active users" -msgstr "活跃用户" - -#: templates/index.html:193 templates/index.html:231 -msgid "Active assets" -msgstr "活跃资产" - -#: templates/index.html:262 templates/index.html:313 -msgid "Monthly active users" -msgstr "月活跃用户" - -#: templates/index.html:262 templates/index.html:314 -msgid "Disable user" -msgstr "禁用用户" - -#: templates/index.html:262 templates/index.html:315 -msgid "Month not logged in user" -msgstr "月未登录用户" - -#: templates/index.html:288 templates/index.html:368 -msgid "Access to the source" -msgstr "访问来源" - -#: templates/index.html:342 -msgid "Month is logged into the asset" -msgstr "月被登录资产" - -#: templates/index.html:342 templates/index.html:393 -msgid "Disable host" -msgstr "禁用主机" - -#: templates/index.html:342 templates/index.html:394 -msgid "Month not logged on host" -msgstr "月未登录主机" - -#: templates/index.html:392 -msgid "Month is logged into the host" -msgstr "月被登录主机" - -#: templates/index.html:466 -msgid " times/week" -msgstr " 次/周" - -#: templates/index.html:491 templates/index.html:527 -msgid " times" -msgstr " 次" - -#: templates/index.html:494 templates/index.html:530 -msgid "The time last logged in" -msgstr "最近一次登录日期" - -#: templates/index.html:495 templates/index.html:531 -msgid "At" -msgstr "于" - -#: templates/index.html:510 templates/index.html:545 templates/index.html:580 -msgid "(No)" -msgstr "(暂无)" - -#: templates/index.html:561 -msgid "Before" -msgstr "前" - -#: templates/index.html:562 -msgid "Login in " -msgstr "登录了" - #: templates/resource_download.html:18 templates/resource_download.html:24 #: templates/resource_download.html:25 templates/resource_download.html:30 msgid "Client" @@ -4713,19 +4464,23 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远 msgid "Filters" msgstr "过滤" -#: terminal/api/session.py:211 +#: terminal/api/endpoint.py:65 +msgid "Not found protocol query params" +msgstr "" + +#: terminal/api/session.py:210 msgid "Session does not exist: {}" msgstr "会话不存在: {}" -#: terminal/api/session.py:214 +#: terminal/api/session.py:213 msgid "Session is finished or the protocol not supported" msgstr "会话已经完成或协议不支持" -#: terminal/api/session.py:219 +#: terminal/api/session.py:218 msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: terminal/api/session.py:227 +#: terminal/api/session.py:226 msgid "User does not have permission" msgstr "用户没有权限" @@ -4769,7 +4524,7 @@ msgstr "有在线会话" msgid "Terminals" msgstr "终端管理" -#: terminal/backends/command/es.py:27 +#: terminal/backends/command/es.py:26 msgid "Invalid elasticsearch config" msgstr "无效的 Elasticsearch 配置" @@ -4826,7 +4581,6 @@ msgid "High" msgstr "较高" #: terminal/const.py:35 users/templates/users/reset_password.html:50 -#: users/templates/users/user_password_update.html:104 msgid "Normal" msgstr "正常" @@ -4846,6 +4600,49 @@ msgstr "存储无效" msgid "Command record" msgstr "命令记录" +#: terminal/models/endpoint.py:13 +msgid "HTTPS Port" +msgstr "HTTPS 端口" + +#: terminal/models/endpoint.py:14 terminal/models/terminal.py:107 +msgid "HTTP Port" +msgstr "HTTP 端口" + +#: terminal/models/endpoint.py:15 terminal/models/terminal.py:106 +msgid "SSH Port" +msgstr "SSH 端口" + +#: terminal/models/endpoint.py:16 +msgid "RDP Port" +msgstr "RDP 端口" + +#: terminal/models/endpoint.py:17 +msgid "MySQL Port" +msgstr "MySQL 端口" + +#: terminal/models/endpoint.py:18 +msgid "MariaDB Port" +msgstr "MariaDB 端口" + +#: terminal/models/endpoint.py:19 +msgid "PostgreSQL Port" +msgstr "PostgreSQL 端口" + +#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:61 +#: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 +#: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 +#: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 +msgid "Endpoint" +msgstr "端点" + +#: terminal/models/endpoint.py:54 +msgid "IP group" +msgstr "IP 组" + +#: terminal/models/endpoint.py:66 +msgid "Endpoint rule" +msgstr "端点规则" + #: terminal/models/replay.py:12 msgid "Session replay" msgstr "会话录像" @@ -4858,35 +4655,35 @@ msgstr "可以上传会话录像" msgid "Can download session replay" msgstr "可以下载会话录像" -#: terminal/models/session.py:48 terminal/models/sharing.py:87 +#: terminal/models/session.py:50 terminal/models/sharing.py:87 msgid "Login from" msgstr "登录来源" -#: terminal/models/session.py:52 +#: terminal/models/session.py:54 msgid "Replay" msgstr "回放" -#: terminal/models/session.py:57 +#: terminal/models/session.py:59 msgid "Date end" msgstr "结束日期" -#: terminal/models/session.py:242 +#: terminal/models/session.py:251 msgid "Session record" msgstr "会话记录" -#: terminal/models/session.py:244 +#: terminal/models/session.py:253 msgid "Can monitor session" msgstr "可以监控会话" -#: terminal/models/session.py:245 +#: terminal/models/session.py:254 msgid "Can share session" msgstr "可以分享会话" -#: terminal/models/session.py:246 +#: terminal/models/session.py:255 msgid "Can terminate session" msgstr "可以终断会话" -#: terminal/models/session.py:247 +#: terminal/models/session.py:256 msgid "Can validate session action perm" msgstr "可以验证会话动作权限" @@ -4995,14 +4792,6 @@ msgstr "其它参数" msgid "type" msgstr "类型" -#: terminal/models/terminal.py:106 -msgid "SSH Port" -msgstr "SSH端口" - -#: terminal/models/terminal.py:107 -msgid "HTTP Port" -msgstr "HTTP端口" - #: terminal/models/terminal.py:183 terminal/serializers/session.py:38 msgid "Terminal" msgstr "终端" @@ -5059,65 +4848,59 @@ msgstr "是否可中断" msgid "Command amount" msgstr "命令数量" -#: terminal/serializers/storage.py:21 +#: terminal/serializers/storage.py:19 msgid "Endpoint invalid: remove path `{}`" msgstr "端点无效: 移除路径 `{}`" -#: terminal/serializers/storage.py:27 +#: terminal/serializers/storage.py:25 msgid "Bucket" msgstr "桶名称" -#: terminal/serializers/storage.py:34 users/models/user.py:695 +#: terminal/serializers/storage.py:32 users/models/user.py:695 msgid "Secret key" msgstr "密钥" -#: terminal/serializers/storage.py:39 terminal/serializers/storage.py:51 -#: terminal/serializers/storage.py:81 terminal/serializers/storage.py:91 -#: terminal/serializers/storage.py:99 -msgid "Endpoint" -msgstr "端点" - -#: terminal/serializers/storage.py:66 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "地域" -#: terminal/serializers/storage.py:110 +#: terminal/serializers/storage.py:108 msgid "Container name" msgstr "容器名称" -#: terminal/serializers/storage.py:112 +#: terminal/serializers/storage.py:110 msgid "Account name" msgstr "账号名称" -#: terminal/serializers/storage.py:113 +#: terminal/serializers/storage.py:111 msgid "Account key" msgstr "账号密钥" -#: terminal/serializers/storage.py:116 +#: terminal/serializers/storage.py:114 msgid "Endpoint suffix" msgstr "端点后缀" -#: terminal/serializers/storage.py:138 +#: terminal/serializers/storage.py:134 msgid "The address format is incorrect" msgstr "地址格式不正确" -#: terminal/serializers/storage.py:145 +#: terminal/serializers/storage.py:141 msgid "Host invalid" msgstr "主机无效" -#: terminal/serializers/storage.py:148 +#: terminal/serializers/storage.py:144 msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:164 +#: terminal/serializers/storage.py:159 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:166 +#: terminal/serializers/storage.py:161 msgid "Doc type" msgstr "文档类型" -#: terminal/serializers/storage.py:168 +#: terminal/serializers/storage.py:163 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" @@ -5671,7 +5454,6 @@ msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" #: users/forms/profile.py:160 users/models/user.py:692 -#: users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "SSH公钥" @@ -5880,10 +5662,6 @@ msgstr "为了安全,仅列出几个用户" msgid "name not unique" msgstr "名称重复" -#: users/templates/users/_granted_assets.html:7 -msgid "Loading" -msgstr "加载中" - #: users/templates/users/_msg_account_expire_reminder.html:7 msgid "Your account will expire in" msgstr "您的账号即将过期" @@ -5936,69 +5714,11 @@ msgstr "你的 SSH 密钥已经被管理员重置" msgid "click here to set your password" msgstr "点击这里设置密码" -#: users/templates/users/_select_user_modal.html:5 -msgid "Please Select User" -msgstr "选择用户" - -#: users/templates/users/_select_user_modal.html:17 -msgid "Asset num" -msgstr "资产数量" - -#: users/templates/users/_user_detail_nav_header.html:11 -msgid "User detail" -msgstr "用户详情" - -#: users/templates/users/_user_detail_nav_header.html:15 -msgid "User permissions" -msgstr "用户授权" - -#: users/templates/users/_user_detail_nav_header.html:23 -msgid "Asset granted" -msgstr "授权的资产" - -#: users/templates/users/_user_detail_nav_header.html:40 -msgid "RemoteApp granted" -msgstr "授权的远程应用" - -#: users/templates/users/_user_detail_nav_header.html:47 -msgid "RemoteApp permission" -msgstr "远程应用授权" - -#: users/templates/users/_user_detail_nav_header.html:54 -msgid "DatabaseApp granted" -msgstr "授权的数据库应用" - -#: users/templates/users/_user_detail_nav_header.html:61 -msgid "DatabaseApp permission" -msgstr "数据库应用授权" - -#: users/templates/users/_user_update_pk_modal.html:4 -msgid "Update User SSH Public Key" -msgstr "更新SSH密钥" - -#: users/templates/users/first_login.html:6 -#: users/templates/users/first_login_done.html:19 -msgid "First Login" -msgstr "首次登录" - -#: users/templates/users/first_login_done.html:31 -msgid "Welcome to use jumpserver, visit " -msgstr "欢迎使用 JumpServer 堡垒机" - -#: users/templates/users/first_login_done.html:32 -msgid "Use guide" -msgstr "向导" - -#: users/templates/users/first_login_done.html:32 -msgid " for more information" -msgstr "获取更多信息" - #: users/templates/users/forgot_password.html:23 msgid "Input your email, that will send a mail to your" msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" #: users/templates/users/forgot_password.html:32 -#: users/templates/users/user_password_update.html:75 msgid "Submit" msgstr "提交" @@ -6015,12 +5735,10 @@ msgid "MFA setting" msgstr "设置 MFA 多因子认证" #: users/templates/users/reset_password.html:23 -#: users/templates/users/user_password_update.html:64 msgid "Your password must satisfy" msgstr "您的密码必须满足:" #: users/templates/users/reset_password.html:24 -#: users/templates/users/user_password_update.html:65 msgid "Password strength" msgstr "密码强度:" @@ -6029,54 +5747,25 @@ msgid "Setting" msgstr "设置" #: users/templates/users/reset_password.html:48 -#: users/templates/users/user_password_update.html:102 msgid "Very weak" msgstr "很弱" #: users/templates/users/reset_password.html:49 -#: users/templates/users/user_password_update.html:103 msgid "Weak" msgstr "弱" #: users/templates/users/reset_password.html:51 -#: users/templates/users/user_password_update.html:105 msgid "Medium" msgstr "一般" #: users/templates/users/reset_password.html:52 -#: users/templates/users/user_password_update.html:106 msgid "Strong" msgstr "强" #: users/templates/users/reset_password.html:53 -#: users/templates/users/user_password_update.html:107 msgid "Very strong" msgstr "很强" -#: users/templates/users/user_asset_permission.html:43 -#: users/templates/users/user_asset_permission.html:155 -#: users/templates/users/user_database_app_permission.html:41 -#: xpack/plugins/cloud/models.py:34 -msgid "Validity" -msgstr "有效" - -#: users/templates/users/user_asset_permission.html:160 -msgid "Inherit" -msgstr "继承" - -#: users/templates/users/user_asset_permission.html:161 -msgid "Include" -msgstr "包含" - -#: users/templates/users/user_asset_permission.html:162 -msgid "Exclude" -msgstr "不包含" - -#: users/templates/users/user_database_app_permission.html:39 -#: users/templates/users/user_database_app_permission.html:64 -msgid "DatabaseApp" -msgstr "数据库应用" - #: users/templates/users/user_otp_check_password.html:6 msgid "Enable OTP" msgstr "启用 MFA(OTP)" @@ -6120,10 +5809,6 @@ msgid "" "installed, go to the next step directly)." msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)" -#: users/templates/users/user_password_update.html:74 -msgid "Reset" -msgstr "重置" - #: users/templates/users/user_password_verify.html:8 #: users/templates/users/user_password_verify.html:9 msgid "Verify password" @@ -6430,62 +6115,58 @@ msgid "Baidu Cloud" msgstr "百度云" #: xpack/plugins/cloud/const.py:15 -msgid "JD Cloud" -msgstr "京东云" - -#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "腾讯云" -#: xpack/plugins/cloud/const.py:17 +#: xpack/plugins/cloud/const.py:16 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:18 msgid "Huawei Private Cloud" msgstr "华为私有云" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:19 msgid "Qingyun Private Cloud" msgstr "青云私有云" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:20 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:22 +#: xpack/plugins/cloud/const.py:21 msgid "Google Cloud Platform" msgstr "谷歌云" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:25 msgid "Instance name" msgstr "实例名称" -#: xpack/plugins/cloud/const.py:27 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/const.py:32 +#: xpack/plugins/cloud/const.py:31 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:35 msgid "Unsync" msgstr "未同步" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:36 msgid "New Sync" msgstr "新同步" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:37 msgid "Synced" msgstr "已同步" -#: xpack/plugins/cloud/const.py:39 +#: xpack/plugins/cloud/const.py:38 msgid "Released" msgstr "已释放" @@ -6497,6 +6178,10 @@ msgstr "云管中心" msgid "Provider" msgstr "云服务商" +#: xpack/plugins/cloud/models.py:34 +msgid "Validity" +msgstr "有效" + #: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "云账号" @@ -6658,13 +6343,11 @@ msgid "South America (São Paulo)" msgstr "南美洲(圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "华南-广州" @@ -6686,7 +6369,6 @@ msgid "CN North-Baoding" msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "华东-上海" @@ -6751,15 +6433,11 @@ msgstr "华北-乌兰察布一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "华南-广州-友好用户环境" -#: xpack/plugins/cloud/providers/jdcloud.py:128 -msgid "CN East-Suqian" -msgstr "华东-宿迁" - -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Validity display" msgstr "有效性显示" -#: xpack/plugins/cloud/serializers/account.py:61 +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Provider display" msgstr "服务商显示" @@ -6921,11 +6599,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" - -#~ msgid "Database proxy MySQL protocol listen port" -#~ msgstr "MySQL 协议监听的端口" - -#, fuzzy -#~| msgid "Database proxy PostgreSQL port" -#~ msgid "Database proxy PostgreSQL listen port" -#~ msgstr "数据库组件 PostgreSQL 协议监听的端口" diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index 43acffaf0..a6dc3b43c 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -64,13 +64,6 @@ class PublicSettingApi(generics.RetrieveAPIView): "AUTH_FEISHU": settings.AUTH_FEISHU, # Terminal "XRDP_ENABLED": settings.XRDP_ENABLED, - "TERMINAL_KOKO_HOST": settings.TERMINAL_KOKO_HOST, - "TERMINAL_KOKO_SSH_PORT": settings.TERMINAL_KOKO_SSH_PORT, - "TERMINAL_MAGNUS_ENABLED": settings.TERMINAL_MAGNUS_ENABLED, - "TERMINAL_MAGNUS_HOST": settings.TERMINAL_MAGNUS_HOST, - "TERMINAL_MAGNUS_MYSQL_PORT": settings.TERMINAL_MAGNUS_MYSQL_PORT, - "TERMINAL_MAGNUS_MARIADB_PORT": settings.TERMINAL_MAGNUS_MARIADB_PORT, - "TERMINAL_MAGNUS_POSTGRE_PORT": settings.TERMINAL_MAGNUS_POSTGRE_PORT, # Announcement "ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED, "ANNOUNCEMENT": settings.ANNOUNCEMENT, diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index c6073aa28..bf4b8d7a0 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -33,33 +33,5 @@ class TerminalSettingSerializer(serializers.Serializer): help_text=_("The login success message varies with devices. " "if you cannot log in to the device through Telnet, set this parameter") ) - TERMINAL_RDP_ADDR = serializers.CharField( - required=False, label=_("RDP address"), max_length=1024, allow_blank=True, - help_text=_('RDP visit address, eg: dev.jumpserver.org:3389') - ) - XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP")) - - TERMINAL_KOKO_HOST = serializers.CharField( - required=False, label=_("Koko host"), max_length=1024 - ) - TERMINAL_KOKO_SSH_PORT = serializers.CharField( - required=False, label=_("Koko ssh port"), max_length=1024 - ) - TERMINAL_MAGNUS_ENABLED = serializers.BooleanField(label=_("Enable database proxy")) - TERMINAL_MAGNUS_HOST = serializers.CharField( - required=False, label=_("Database proxy host"), max_length=1024, allow_blank=True, - help_text=_('Database proxy host, eg: dev.jumpserver.org') - ) - TERMINAL_MAGNUS_MYSQL_PORT = serializers.IntegerField( - required=False, label=_("MySQL port"), default=33060, - help_text=_('MySQL protocol listen port') - ) - TERMINAL_MAGNUS_MARIADB_PORT = serializers.IntegerField( - required=False, label=_("MariaDB port"), default=33061, - help_text=_('MariaDB protocol listen port') - ) - TERMINAL_MAGNUS_POSTGRE_PORT = serializers.IntegerField( - required=False, label=_("PostgreSQL port"), default=54320, - help_text=_('PostgreSQL protocol listen port') - ) + XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP")) diff --git a/apps/terminal/api/__init__.py b/apps/terminal/api/__init__.py index fec6da11e..16021a5ed 100644 --- a/apps/terminal/api/__init__.py +++ b/apps/terminal/api/__init__.py @@ -7,3 +7,4 @@ from .task import * from .storage import * from .status import * from .sharing import * +from .endpoint import * diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/endpoint.py new file mode 100644 index 000000000..14efb738f --- /dev/null +++ b/apps/terminal/api/endpoint.py @@ -0,0 +1,78 @@ +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework import status +from common.drf.api import JMSBulkModelViewSet +from common.utils import get_object_or_none +from django.shortcuts import get_object_or_404 +from assets.models import Asset +from orgs.utils import tmp_to_root_org +from applications.models import Application +from terminal.models import Session +from ..models import Endpoint, EndpointRule +from .. import serializers + + +__all__ = ['EndpointViewSet', 'EndpointRuleViewSet'] + + +class EndpointViewSet(JMSBulkModelViewSet): + filterset_fields = ('name', 'host') + search_fields = filterset_fields + serializer_class = serializers.EndpointSerializer + queryset = Endpoint.objects.all() + rbac_perms = { + 'smart': 'terminal.view_endpoint' + } + + @staticmethod + def get_target_ip(request): + # 用来方便测试 + target_ip = request.GET.get('target_ip') + if target_ip: + return target_ip + + asset_id = request.GET.get('asset_id') + app_id = request.GET.get('app_id') + session_id = request.GET.get('session_id') + token = request.GET.get('token') + if token: + from authentication.api.connection_token import TokenCacheMixin as TokenUtil + value = TokenUtil().get_token_from_cache(token) + if value: + if value.get('type') == 'asset': + asset_id = value.get('asset') + else: + app_id = value.get('application') + if asset_id: + pk, model = asset_id, Asset + elif app_id: + pk, model = app_id, Application + elif session_id: + pk, model = session_id, Session + else: + return '' + + with tmp_to_root_org(): + instance = get_object_or_404(model, pk=pk) + target_ip = instance.get_target_ip() + return target_ip + + @action(methods=['get'], detail=False, url_path='smart') + def smart(self, request, *args, **kwargs): + protocol = request.GET.get('protocol') + if not protocol: + return Response( + data={'error': _('Not found protocol query params')}, + status=status.HTTP_404_NOT_FOUND + ) + target_ip = self.get_target_ip(request) + endpoint = EndpointRule.match_endpoint(target_ip, protocol, request) + serializer = self.get_serializer(endpoint) + return Response(serializer.data) + + +class EndpointRuleViewSet(JMSBulkModelViewSet): + filterset_fields = ('name',) + search_fields = filterset_fields + serializer_class = serializers.EndpointRuleSerializer + queryset = EndpointRule.objects.all() diff --git a/apps/terminal/migrations/0048_endpoint_endpointrule.py b/apps/terminal/migrations/0048_endpoint_endpointrule.py new file mode 100644 index 000000000..9e75823e2 --- /dev/null +++ b/apps/terminal/migrations/0048_endpoint_endpointrule.py @@ -0,0 +1,86 @@ +# Generated by Django 3.1.14 on 2022-04-12 07:39 + +import common.fields.model +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import uuid +from django.conf import settings + + +def migrate_endpoints(apps, schema_editor): + endpoint_data = { + 'id': '00000000-0000-0000-0000-000000000001', + 'name': 'Default', + 'host': '', + 'https_port': 0, + 'http_port': 0, + 'created_by': 'System' + } + + if settings.XRDP_ENABLED: + xrdp_addr = settings.TERMINAL_RDP_ADDR + if ':' in xrdp_addr: + hostname, port = xrdp_addr.strip().split(':') + else: + hostname, port = xrdp_addr, 3389 + endpoint_data.update({ + 'host': '' if hostname.strip() in ['localhost', '127.0.0.1'] else hostname.strip(), + 'rdp_port': int(port) + }) + Endpoint = apps.get_model("terminal", "Endpoint") + Endpoint.objects.create(**endpoint_data) + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0047_auto_20220302_1951'), + ] + + operations = [ + migrations.CreateModel( + name='Endpoint', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, unique=True, blank=True, verbose_name='Name')), + ('host', models.CharField(max_length=256, verbose_name='Host')), + ('https_port', common.fields.model.PortField(default=443, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTPS Port')), + ('http_port', common.fields.model.PortField(default=80, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTP Port')), + ('ssh_port', common.fields.model.PortField(default=2222, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='SSH Port')), + ('rdp_port', common.fields.model.PortField(default=3389, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='RDP Port')), + ('mysql_port', common.fields.model.PortField(default=33060, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MySQL Port')), + ('mariadb_port', common.fields.model.PortField(default=33061, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='MariaDB Port')), + ('postgresql_port', common.fields.model.PortField(default=54320, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='PostgreSQL Port')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ], + options={ + 'verbose_name': 'Endpoint', + 'ordering': ('name',), + }, + ), + migrations.CreateModel( + name='EndpointRule', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('ip_group', models.JSONField(default=list, verbose_name='IP group')), + ('priority', models.IntegerField(help_text='1-100, the lower the value will be match first', unique=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('endpoint', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rules', to='terminal.endpoint', verbose_name='Endpoint')), + ], + options={ + 'verbose_name': 'Endpoint rule', + 'ordering': ('priority', 'name'), + }, + ), + migrations.RunPython(migrate_endpoints), + ] diff --git a/apps/terminal/models/__init__.py b/apps/terminal/models/__init__.py index e69928b3a..be079721d 100644 --- a/apps/terminal/models/__init__.py +++ b/apps/terminal/models/__init__.py @@ -6,3 +6,4 @@ from .task import * from .terminal import * from .sharing import * from .replay import * +from .endpoint import * diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/endpoint.py new file mode 100644 index 000000000..39f275f9a --- /dev/null +++ b/apps/terminal/models/endpoint.py @@ -0,0 +1,94 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.core.validators import MinValueValidator, MaxValueValidator +from common.db.models import JMSModel +from common.fields.model import PortField +from common.utils.ip import contains_ip + + +class Endpoint(JMSModel): + name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) + host = models.CharField(max_length=256, blank=True, verbose_name=_('Host')) + # disabled value=0 + https_port = PortField(default=443, verbose_name=_('HTTPS Port')) + http_port = PortField(default=80, verbose_name=_('HTTP Port')) + ssh_port = PortField(default=2222, verbose_name=_('SSH Port')) + rdp_port = PortField(default=3389, verbose_name=_('RDP Port')) + mysql_port = PortField(default=33060, verbose_name=_('MySQL Port')) + mariadb_port = PortField(default=33061, verbose_name=_('MariaDB Port')) + postgresql_port = PortField(default=54320, verbose_name=_('PostgreSQL Port')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + + default_id = '00000000-0000-0000-0000-000000000001' + + class Meta: + verbose_name = _('Endpoint') + ordering = ('name',) + + def __str__(self): + return self.name + + def get_port(self, protocol): + return getattr(self, f'{protocol}_port', 0) + + def delete(self, using=None, keep_parents=False): + if self.id == self.default_id: + return + return super().delete(using, keep_parents) + + @classmethod + def get_or_create_default(cls, request=None): + data = { + 'id': cls.default_id, + 'name': 'Default', + 'host': '', + 'https_port': 0, + 'http_port': 0, + } + endpoint, created = cls.objects.get_or_create(id=cls.default_id, defaults=data) + if not endpoint.host and request: + endpoint.host = request.get_host().split(':')[0] + return endpoint + + +class EndpointRule(JMSModel): + name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) + ip_group = models.JSONField(default=list, verbose_name=_('IP group')) + priority = models.IntegerField( + verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)], + unique=True, help_text=_("1-100, the lower the value will be match first"), + ) + endpoint = models.ForeignKey( + 'terminal.Endpoint', null=True, blank=True, related_name='rules', + on_delete=models.SET_NULL, verbose_name=_("Endpoint"), + ) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + + class Meta: + verbose_name = _('Endpoint rule') + ordering = ('priority', 'name') + + def __str__(self): + return f'{self.name}({self.priority})' + + @classmethod + def match(cls, target_ip, protocol): + for endpoint_rule in cls.objects.all().prefetch_related('endpoint'): + if not contains_ip(target_ip, endpoint_rule.ip_group): + continue + if not endpoint_rule.endpoint: + continue + if not endpoint_rule.endpoint.host: + continue + if endpoint_rule.endpoint.get_port(protocol) == 0: + continue + return endpoint_rule + + @classmethod + def match_endpoint(cls, target_ip, protocol, request=None): + endpoint_rule = cls.match(target_ip, protocol) + if endpoint_rule: + endpoint = endpoint_rule.endpoint + else: + endpoint = Endpoint.get_or_create_default(request) + return endpoint diff --git a/apps/terminal/models/session.py b/apps/terminal/models/session.py index 09f1c9e91..565c54335 100644 --- a/apps/terminal/models/session.py +++ b/apps/terminal/models/session.py @@ -11,9 +11,11 @@ from django.core.files.storage import default_storage from django.core.cache import cache from assets.models import Asset +from applications.models import Application from users.models import User from orgs.mixins.models import OrgModelMixin from django.db.models import TextChoices +from common.utils import get_object_or_none from ..backends import get_multi_command_storage @@ -194,6 +196,13 @@ class Session(OrgModelMixin): def login_from_display(self): return self.get_login_from_display() + def get_target_ip(self): + instance = get_object_or_none(Asset, pk=self.asset_id) + if not instance: + instance = get_object_or_none(Application, pk=self.asset_id) + target_ip = instance.get_target_ip() if instance else '' + return target_ip + @classmethod def generate_fake(cls, count=100, is_finished=True): import random diff --git a/apps/terminal/serializers/__init__.py b/apps/terminal/serializers/__init__.py index 4e868cabc..e1312ebae 100644 --- a/apps/terminal/serializers/__init__.py +++ b/apps/terminal/serializers/__init__.py @@ -4,3 +4,4 @@ from .terminal import * from .session import * from .storage import * from .sharing import * +from .endpoint import * diff --git a/apps/terminal/serializers/endpoint.py b/apps/terminal/serializers/endpoint.py new file mode 100644 index 000000000..822c6b5c2 --- /dev/null +++ b/apps/terminal/serializers/endpoint.py @@ -0,0 +1,51 @@ +from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ +from common.drf.serializers import BulkModelSerializer +from acls.serializers.rules import ip_group_help_text, ip_group_child_validator +from ..models import Endpoint, EndpointRule + +__all__ = ['EndpointSerializer', 'EndpointRuleSerializer'] + + +class EndpointSerializer(BulkModelSerializer): + + class Meta: + model = Endpoint + fields_mini = ['id', 'name'] + fields_small = [ + 'host', + 'https_port', 'http_port', 'ssh_port', + 'rdp_port', 'mysql_port', 'mariadb_port', + 'postgresql_port', + ] + fields = fields_mini + fields_small + [ + 'comment', 'date_created', 'date_updated', 'created_by' + ] + extra_kwargs = { + 'https_port': {'default': 443}, + 'http_port': {'default': 80}, + 'ssh_port': {'default': 2222}, + 'rdp_port': {'default': 3389}, + 'mysql_port': {'default': 33060}, + 'mariadb_port': {'default': 33061}, + 'postgresql_port': {'default': 54320}, + } + + +class EndpointRuleSerializer(BulkModelSerializer): + ip_group = serializers.ListField( + default=['*'], label=_('IP'), help_text=ip_group_help_text, + child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]) + ) + endpoint_display = serializers.ReadOnlyField(source='endpoint.name', label=_('Endpoint')) + + class Meta: + model = EndpointRule + fields_mini = ['id', 'name'] + fields_small = fields_mini + ['ip_group', 'priority'] + fields_fk = ['endpoint', 'endpoint_display'] + fields = fields_mini + fields_small + fields_fk + [ + 'comment', 'date_created', 'date_updated', 'created_by' + ] + extra_kwargs = { + } diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 9366332e2..c392f9c07 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -22,6 +22,8 @@ router.register(r'replay-storages', api.ReplayStorageViewSet, 'replay-storage') router.register(r'command-storages', api.CommandStorageViewSet, 'command-storage') router.register(r'session-sharings', api.SessionSharingViewSet, 'session-sharing') router.register(r'session-join-records', api.SessionJoinRecordsViewSet, 'session-sharing-record') +router.register(r'endpoints', api.EndpointViewSet, 'endpoint') +router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') urlpatterns = [ path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'), From 3213fe098408e6908e176944ae055cb6ee9608c0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 Apr 2022 17:59:28 +0800 Subject: [PATCH 018/258] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0action=20lgt?= =?UTF-8?q?m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lgtm.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/lgtm.yml diff --git a/.github/workflows/lgtm.yml b/.github/workflows/lgtm.yml new file mode 100644 index 000000000..022d89a71 --- /dev/null +++ b/.github/workflows/lgtm.yml @@ -0,0 +1,15 @@ +name: Send LGTM reaction +on: + issue_comment: + types: [created] + pull_request_review: + types: [submitted] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@1.0.0 + - uses: micnncim/action-lgtm-reaction@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 72608146cc76f9ecd9e6c2e07e64d5c2cc21d002 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 12 Apr 2022 18:25:28 +0800 Subject: [PATCH 019/258] chore: lgtm (#8048) * chore: lgtm * perf: add lgtm Co-authored-by: ibuler --- .github/workflows/lgtm.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lgtm.yml b/.github/workflows/lgtm.yml index 022d89a71..379ca5135 100644 --- a/.github/workflows/lgtm.yml +++ b/.github/workflows/lgtm.yml @@ -1,4 +1,5 @@ name: Send LGTM reaction + on: issue_comment: types: [created] @@ -13,3 +14,5 @@ jobs: - uses: micnncim/action-lgtm-reaction@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + trigger: '["^.?lgtm$"]' From b0f7c114fc0ff0c394d2b8b70199709ee0ec883e Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 13 Apr 2022 14:41:33 +0800 Subject: [PATCH 020/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20csrf=20tok?= =?UTF-8?q?en=20domain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index e5b382517..a4441711a 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -127,7 +127,7 @@ LOGIN_REDIRECT_URL = reverse_lazy('index') LOGIN_URL = reverse_lazy('authentication:login') SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN -CSRF_COOKIE_DOMAIN = CONFIG.CSRF_COOKIE_DOMAIN +CSRF_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 自定义的配置,SESSION_EXPIRE_AT_BROWSER_CLOSE 始终为 True, 下面这个来控制是否强制关闭后过期 cookie From c630b11bd57e763d5c8ee86ae17382a654e596aa Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 13 Apr 2022 19:48:31 +0800 Subject: [PATCH 021/258] fix: port str (#8055) Co-authored-by: feng626 <1304903146@qq.com> --- apps/authentication/api/connection_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 3e905076c..4afa7a306 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -186,7 +186,7 @@ class ClientProtocolMixin: ) content = { 'ip': endpoint.host, - 'port': endpoint.ssh_port, + 'port': str(endpoint.ssh_port), 'username': f'JMS-{token}', 'password': secret } From 10b033010e8b238a7d8af1dc5910ce192ffe1c0d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 13 Apr 2022 18:20:29 +0800 Subject: [PATCH 022/258] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=AF=BC=E5=87=BA=E6=97=B6=E9=97=B4=E6=88=B3=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/command.py | 28 +------------------ apps/terminal/backends/command/models.py | 5 ++++ apps/terminal/backends/command/serializers.py | 1 + apps/terminal/urls/api_urls.py | 1 - 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/apps/terminal/api/command.py b/apps/terminal/api/command.py index 22a2bb5c3..5b60a114a 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/command.py @@ -1,13 +1,10 @@ # -*- coding: utf-8 -*- # -import time from django.conf import settings from django.utils import timezone -from django.shortcuts import HttpResponse from rest_framework import generics from rest_framework.fields import DateTimeField from rest_framework.response import Response -from django.template import loader from terminal.models import CommandStorage, Session, Command from terminal.filters import CommandFilter @@ -23,7 +20,7 @@ from ..backends import ( from ..notifications import CommandAlertMessage logger = get_logger(__name__) -__all__ = ['CommandViewSet', 'CommandExportApi', 'InsecureCommandAlertAPI'] +__all__ = ['CommandViewSet', 'InsecureCommandAlertAPI'] class CommandQueryMixin: @@ -191,29 +188,6 @@ class CommandViewSet(JMSBulkModelViewSet): return Response({"msg": msg}, status=401) -class CommandExportApi(CommandQueryMixin, generics.ListAPIView): - serializer_class = SessionCommandSerializer - rbac_perms = { - 'list': 'terminal.view_command' - } - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - - template = 'terminal/command_report.html' - context = { - 'queryset': queryset, - 'total_count': len(queryset), - 'now': time.time(), - } - content = loader.render_to_string(template, context, request) - content_type = 'application/octet-stream' - response = HttpResponse(content, content_type) - filename = 'command-report-{}.html'.format(int(time.time())) - response['Content-Disposition'] = 'attachment; filename="%s"' % filename - return response - - class InsecureCommandAlertAPI(generics.CreateAPIView): serializer_class = InsecureCommandAlertSerializer rbac_perms = { diff --git a/apps/terminal/backends/command/models.py b/apps/terminal/backends/command/models.py index 02a16d353..0a795b905 100644 --- a/apps/terminal/backends/command/models.py +++ b/apps/terminal/backends/command/models.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from datetime import datetime import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -28,6 +29,10 @@ class AbstractSessionCommand(OrgModelMixin): class Meta: abstract = True + @lazyproperty + def timestamp_display(self): + return datetime.fromtimestamp(self.timestamp) + @lazyproperty def remote_addr(self): from terminal.models import Session diff --git a/apps/terminal/backends/command/serializers.py b/apps/terminal/backends/command/serializers.py index 625c4282e..2eb9e434c 100644 --- a/apps/terminal/backends/command/serializers.py +++ b/apps/terminal/backends/command/serializers.py @@ -36,6 +36,7 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer): output = serializers.CharField(max_length=2048, allow_blank=True, label=_("Output")) risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) timestamp = serializers.IntegerField(label=_('Timestamp')) + timestamp_display = serializers.DateTimeField(label=_('Datetime')) remote_addr = serializers.CharField(read_only=True, label=_('Remote Address')) @staticmethod diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index c392f9c07..3f0445350 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -36,7 +36,6 @@ urlpatterns = [ path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'), path('tasks/kill-session-for-ticket/', api.KillSessionForTicketAPI.as_view(), name='kill-session-for-ticket'), path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'), - path('commands/export/', api.CommandExportApi.as_view(), name="command-export"), path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"), path('replay-storages//test-connective/', api.ReplayStorageTestConnectiveApi.as_view(), name='replay-storage-test-connective'), path('command-storages//test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'), From b610d71e115a0deb2b042801df18d60eb722f94e Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 13 Apr 2022 20:24:56 +0800 Subject: [PATCH 023/258] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=20password=20(#8035)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 添加 template password * perf: 修改id * perf: 修改 翻译 * perf: 修改 tmp token * perf: 修改 token Co-authored-by: ibuler --- apps/audits/signal_handlers.py | 1 + apps/authentication/api/__init__.py | 1 + apps/authentication/api/temp_token.py | 27 ++ apps/authentication/api/token.py | 1 - apps/authentication/backends/base.py | 3 +- apps/authentication/backends/ldap.py | 5 +- apps/authentication/backends/radius.py | 27 +- .../authentication/backends/saml2/backends.py | 2 +- apps/authentication/backends/token.py | 26 ++ .../migrations/0010_temptoken.py | 32 ++ apps/authentication/models.py | 24 +- apps/authentication/serializers/__init__.py | 3 + .../connect_token.py} | 97 +----- .../serializers/password_mfa.py | 33 ++ apps/authentication/serializers/token.py | 103 +++++++ apps/authentication/urls/api_urls.py | 1 + apps/common/utils/common.py | 2 + apps/common/utils/random.py | 24 -- apps/jumpserver/conf.py | 2 + apps/jumpserver/settings/auth.py | 10 +- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 285 ++++++++++-------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 282 +++++++++-------- apps/rbac/builtin.py | 1 + apps/settings/api/public.py | 1 + 26 files changed, 611 insertions(+), 390 deletions(-) create mode 100644 apps/authentication/api/temp_token.py create mode 100644 apps/authentication/backends/token.py create mode 100644 apps/authentication/migrations/0010_temptoken.py create mode 100644 apps/authentication/serializers/__init__.py rename apps/authentication/{serializers.py => serializers/connect_token.py} (59%) create mode 100644 apps/authentication/serializers/password_mfa.py create mode 100644 apps/authentication/serializers/token.py diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index daa3fa4bf..031b1e25c 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -70,6 +70,7 @@ class AuthBackendLabelMapping(LazyObject): backend_label_mapping[settings.AUTH_BACKEND_AUTH_TOKEN] = _('Auth Token') backend_label_mapping[settings.AUTH_BACKEND_WECOM] = _('WeCom') backend_label_mapping[settings.AUTH_BACKEND_DINGTALK] = _('DingTalk') + backend_label_mapping[settings.AUTH_BACKEND_TEMP_TOKEN] = _('Temporary token') return backend_label_mapping def _setup(self): diff --git a/apps/authentication/api/__init__.py b/apps/authentication/api/__init__.py index c0064f9bd..01a4c52e9 100644 --- a/apps/authentication/api/__init__.py +++ b/apps/authentication/api/__init__.py @@ -11,3 +11,4 @@ from .wecom import * from .dingtalk import * from .feishu import * from .password import * +from .temp_token import * diff --git a/apps/authentication/api/temp_token.py b/apps/authentication/api/temp_token.py new file mode 100644 index 000000000..98c1d74d6 --- /dev/null +++ b/apps/authentication/api/temp_token.py @@ -0,0 +1,27 @@ +from django.utils import timezone +from rest_framework.response import Response +from rest_framework.decorators import action + +from common.drf.api import JMSModelViewSet +from common.permissions import IsValidUser +from ..models import TempToken +from ..serializers import TempTokenSerializer + + +class TempTokenViewSet(JMSModelViewSet): + serializer_class = TempTokenSerializer + permission_classes = [IsValidUser] + http_method_names = ['post', 'get', 'options', 'patch'] + + def get_queryset(self): + username = self.request.user.username + return TempToken.objects.filter(username=username) + + @action(methods=['PATCH'], detail=True, url_path='expire') + def expire(self, *args, **kwargs): + instance = self.get_object() + instance.date_expired = timezone.now() + instance.save() + serializer = self.get_serializer(instance) + return Response(serializer.data) + diff --git a/apps/authentication/api/token.py b/apps/authentication/api/token.py index df8c6eb3f..e5fe8bf2c 100644 --- a/apps/authentication/api/token.py +++ b/apps/authentication/api/token.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -from django.shortcuts import redirect from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.generics import CreateAPIView diff --git a/apps/authentication/backends/base.py b/apps/authentication/backends/base.py index 64faf3334..84cdeab27 100644 --- a/apps/authentication/backends/base.py +++ b/apps/authentication/backends/base.py @@ -1,10 +1,11 @@ -from django.contrib.auth.backends import BaseBackend from django.contrib.auth.backends import ModelBackend +from django.contrib.auth import get_user_model from users.models import User from common.utils import get_logger +UserModel = get_user_model() logger = get_logger(__file__) diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py index 1c8a80cb1..895226e58 100644 --- a/apps/authentication/backends/ldap.py +++ b/apps/authentication/backends/ldap.py @@ -53,7 +53,7 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend): else: built = False - return (user, built) + return user, built def pre_check(self, username, password): if not settings.AUTH_LDAP: @@ -75,6 +75,9 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend): def authenticate(self, request=None, username=None, password=None, **kwargs): logger.info('Authentication LDAP backend') + if username is None or password is None: + logger.info('No username or password') + return None match, msg = self.pre_check(username, password) if not match: logger.info('Authenticate failed: {}'.format(msg)) diff --git a/apps/authentication/backends/radius.py b/apps/authentication/backends/radius.py index 170534370..84f88165a 100644 --- a/apps/authentication/backends/radius.py +++ b/apps/authentication/backends/radius.py @@ -13,20 +13,23 @@ User = get_user_model() class CreateUserMixin: - def get_django_user(self, username, password=None, *args, **kwargs): + @staticmethod + def get_django_user(username, password=None, *args, **kwargs): if isinstance(username, bytes): username = username.decode() - try: - user = User.objects.get(username=username) - except User.DoesNotExist: - if '@' in username: - email = username - else: - email_suffix = settings.EMAIL_SUFFIX - email = '{}@{}'.format(username, email_suffix) - user = User(username=username, name=username, email=email) - user.source = user.Source.radius.value - user.save() + user = User.objects.filter(username=username).first() + if user: + return user + + if '@' in username: + email = username + else: + email_suffix = settings.EMAIL_SUFFIX + email = '{}@{}'.format(username, email_suffix) + + user = User(username=username, name=username, email=email) + user.source = user.Source.radius.value + user.save() return user def _perform_radius_auth(self, client, packet): diff --git a/apps/authentication/backends/saml2/backends.py b/apps/authentication/backends/saml2/backends.py index e1b1fb1eb..0ac0efe1c 100644 --- a/apps/authentication/backends/saml2/backends.py +++ b/apps/authentication/backends/saml2/backends.py @@ -14,7 +14,7 @@ from ..base import JMSModelBackend __all__ = ['SAML2Backend'] -logger = get_logger(__file__) +logger = get_logger(__name__) class SAML2Backend(JMSModelBackend): diff --git a/apps/authentication/backends/token.py b/apps/authentication/backends/token.py new file mode 100644 index 000000000..be9cb9032 --- /dev/null +++ b/apps/authentication/backends/token.py @@ -0,0 +1,26 @@ +from django.utils import timezone +from django.conf import settings +from django.core.exceptions import PermissionDenied + +from authentication.models import TempToken +from .base import JMSModelBackend + + +class TempTokenAuthBackend(JMSModelBackend): + model = TempToken + + def authenticate(self, request, username='', password='', *args, **kwargs): + token = self.model.objects.filter(username=username, secret=password).first() + if not token: + return None + if not token.is_valid: + raise PermissionDenied('Token is invalid, expired at {}'.format(token.date_expired)) + + token.verified = True + token.date_verified = timezone.now() + token.save() + return token.user + + @staticmethod + def is_enabled(): + return settings.AUTH_TEMP_TOKEN diff --git a/apps/authentication/migrations/0010_temptoken.py b/apps/authentication/migrations/0010_temptoken.py new file mode 100644 index 000000000..914188d3f --- /dev/null +++ b/apps/authentication/migrations/0010_temptoken.py @@ -0,0 +1,32 @@ +# Generated by Django 3.1.14 on 2022-04-08 07:04 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0009_auto_20220310_0616'), + ] + + operations = [ + migrations.CreateModel( + name='TempToken', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('username', models.CharField(max_length=128, verbose_name='Username')), + ('secret', models.CharField(max_length=64, verbose_name='Secret')), + ('verified', models.BooleanField(default=False, verbose_name='Verified')), + ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), + ('date_expired', models.DateTimeField(verbose_name='Date verified')), + ], + options={ + 'verbose_name': 'Temporary token', + }, + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 1b353f737..54cab4fbd 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -1,8 +1,9 @@ import uuid +from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from rest_framework.authtoken.models import Token from django.conf import settings +from rest_framework.authtoken.models import Token from common.db import models @@ -64,6 +65,27 @@ class ConnectionToken(models.JMSBaseModel): ] +class TempToken(models.JMSModel): + username = models.CharField(max_length=128, verbose_name=_("Username")) + secret = models.CharField(max_length=64, verbose_name=_("Secret")) + verified = models.BooleanField(default=False, verbose_name=_("Verified")) + date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified")) + date_expired = models.DateTimeField(verbose_name=_("Date expired")) + + class Meta: + verbose_name = _("Temporary token") + + @property + def user(self): + from users.models import User + return User.objects.filter(username=self.username).first() + + @property + def is_valid(self): + not_expired = self.date_expired and self.date_expired > timezone.now() + return not self.verified and not_expired + + class SuperConnectionToken(ConnectionToken): class Meta: proxy = True diff --git a/apps/authentication/serializers/__init__.py b/apps/authentication/serializers/__init__.py new file mode 100644 index 000000000..7697c46db --- /dev/null +++ b/apps/authentication/serializers/__init__.py @@ -0,0 +1,3 @@ +from .token import * +from .connect_token import * +from .password_mfa import * diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers/connect_token.py similarity index 59% rename from apps/authentication/serializers.py rename to apps/authentication/serializers/connect_token.py index f6661ba38..c8f94909d 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers/connect_token.py @@ -1,109 +1,22 @@ # -*- coding: utf-8 -*- # -from django.utils import timezone from rest_framework import serializers -from common.utils import get_object_or_none from users.models import User from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule from applications.models import Application -from users.serializers import UserProfileSerializer from assets.serializers import ProtocolsField from perms.serializers.base import ActionsField -from .models import AccessKey __all__ = [ - 'AccessKeySerializer', 'OtpVerifySerializer', 'BearerTokenSerializer', - 'MFAChallengeSerializer', 'SSOTokenSerializer', - 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer', - 'PasswordVerifySerializer', 'MFASelectTypeSerializer', + 'ConnectionTokenSerializer', 'ConnectionTokenApplicationSerializer', + 'ConnectionTokenUserSerializer', 'ConnectionTokenFilterRuleSerializer', + 'ConnectionTokenAssetSerializer', 'ConnectionTokenSystemUserSerializer', + 'ConnectionTokenDomainSerializer', 'ConnectionTokenRemoteAppSerializer', + 'ConnectionTokenGatewaySerializer', 'ConnectionTokenSecretSerializer' ] -class AccessKeySerializer(serializers.ModelSerializer): - class Meta: - model = AccessKey - fields = ['id', 'secret', 'is_active', 'date_created'] - read_only_fields = ['id', 'secret', 'date_created'] - - -class OtpVerifySerializer(serializers.Serializer): - code = serializers.CharField(max_length=6, min_length=6) - - -class PasswordVerifySerializer(serializers.Serializer): - password = serializers.CharField() - - -class BearerTokenSerializer(serializers.Serializer): - username = serializers.CharField(allow_null=True, required=False, write_only=True) - password = serializers.CharField(write_only=True, allow_null=True, - required=False, allow_blank=True) - public_key = serializers.CharField(write_only=True, allow_null=True, - allow_blank=True, required=False) - token = serializers.CharField(read_only=True) - keyword = serializers.SerializerMethodField() - date_expired = serializers.DateTimeField(read_only=True) - user = UserProfileSerializer(read_only=True) - - @staticmethod - def get_keyword(obj): - return 'Bearer' - - def update_last_login(self, user): - user.last_login = timezone.now() - user.save(update_fields=['last_login']) - - def get_request_user(self): - request = self.context.get('request') - if request.user and request.user.is_authenticated: - user = request.user - else: - user_id = request.session.get('user_id') - user = get_object_or_none(User, pk=user_id) - if not user: - raise serializers.ValidationError( - "user id {} not exist".format(user_id) - ) - return user - - def create(self, validated_data): - request = self.context.get('request') - user = self.get_request_user() - - token, date_expired = user.create_bearer_token(request) - self.update_last_login(user) - - instance = { - "token": token, - "date_expired": date_expired, - "user": user - } - return instance - - -class MFASelectTypeSerializer(serializers.Serializer): - type = serializers.CharField() - username = serializers.CharField(required=False, allow_blank=True, allow_null=True) - - -class MFAChallengeSerializer(serializers.Serializer): - type = serializers.CharField(write_only=True, required=False, allow_blank=True) - code = serializers.CharField(write_only=True) - - def create(self, validated_data): - pass - - def update(self, instance, validated_data): - pass - - -class SSOTokenSerializer(serializers.Serializer): - username = serializers.CharField(write_only=True) - login_url = serializers.CharField(read_only=True) - next = serializers.CharField(write_only=True, allow_blank=True, required=False, allow_null=True) - - class ConnectionTokenSerializer(serializers.Serializer): user = serializers.CharField(max_length=128, required=False, allow_blank=True) system_user = serializers.CharField(max_length=128, required=True) diff --git a/apps/authentication/serializers/password_mfa.py b/apps/authentication/serializers/password_mfa.py new file mode 100644 index 000000000..c4c0679c6 --- /dev/null +++ b/apps/authentication/serializers/password_mfa.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import serializers + + +__all__ = [ + 'OtpVerifySerializer', 'MFAChallengeSerializer', 'MFASelectTypeSerializer', + 'PasswordVerifySerializer', +] + + +class PasswordVerifySerializer(serializers.Serializer): + password = serializers.CharField() + + +class MFASelectTypeSerializer(serializers.Serializer): + type = serializers.CharField() + username = serializers.CharField(required=False, allow_blank=True, allow_null=True) + + +class MFAChallengeSerializer(serializers.Serializer): + type = serializers.CharField(write_only=True, required=False, allow_blank=True) + code = serializers.CharField(write_only=True) + + def create(self, validated_data): + pass + + def update(self, instance, validated_data): + pass + + +class OtpVerifySerializer(serializers.Serializer): + code = serializers.CharField(max_length=6, min_length=6) diff --git a/apps/authentication/serializers/token.py b/apps/authentication/serializers/token.py new file mode 100644 index 000000000..d1e87c0c0 --- /dev/null +++ b/apps/authentication/serializers/token.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from common.utils import get_object_or_none, random_string +from users.models import User +from users.serializers import UserProfileSerializer +from ..models import AccessKey, TempToken + +__all__ = [ + 'AccessKeySerializer', 'BearerTokenSerializer', + 'SSOTokenSerializer', 'TempTokenSerializer', +] + + +class AccessKeySerializer(serializers.ModelSerializer): + class Meta: + model = AccessKey + fields = ['id', 'secret', 'is_active', 'date_created'] + read_only_fields = ['id', 'secret', 'date_created'] + + +class BearerTokenSerializer(serializers.Serializer): + username = serializers.CharField(allow_null=True, required=False, write_only=True) + password = serializers.CharField(write_only=True, allow_null=True, + required=False, allow_blank=True) + public_key = serializers.CharField(write_only=True, allow_null=True, + allow_blank=True, required=False) + token = serializers.CharField(read_only=True) + keyword = serializers.SerializerMethodField() + date_expired = serializers.DateTimeField(read_only=True) + user = UserProfileSerializer(read_only=True) + + @staticmethod + def get_keyword(obj): + return 'Bearer' + + def update_last_login(self, user): + user.last_login = timezone.now() + user.save(update_fields=['last_login']) + + def get_request_user(self): + request = self.context.get('request') + if request.user and request.user.is_authenticated: + user = request.user + else: + user_id = request.session.get('user_id') + user = get_object_or_none(User, pk=user_id) + if not user: + raise serializers.ValidationError( + "user id {} not exist".format(user_id) + ) + return user + + def create(self, validated_data): + request = self.context.get('request') + user = self.get_request_user() + + token, date_expired = user.create_bearer_token(request) + self.update_last_login(user) + + instance = { + "token": token, + "date_expired": date_expired, + "user": user + } + return instance + + +class SSOTokenSerializer(serializers.Serializer): + username = serializers.CharField(write_only=True) + login_url = serializers.CharField(read_only=True) + next = serializers.CharField(write_only=True, allow_blank=True, required=False, allow_null=True) + + +class TempTokenSerializer(serializers.ModelSerializer): + is_valid = serializers.BooleanField(label=_("Is valid"), read_only=True) + + class Meta: + model = TempToken + fields = [ + 'id', 'username', 'secret', 'verified', 'is_valid', + 'date_created', 'date_updated', 'date_verified', + 'date_expired', + ] + read_only_fields = fields + + def create(self, validated_data): + request = self.context.get('request') + if not request or not request.user: + raise PermissionError() + + secret = random_string(36) + username = request.user.username + kwargs = { + 'username': username, 'secret': secret, + 'date_expired': timezone.now() + timezone.timedelta(seconds=5*60), + } + token = TempToken(**kwargs) + token.save() + return token diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index 1a6c43dd7..920988ee4 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -9,6 +9,7 @@ app_name = 'authentication' router = DefaultRouter() router.register('access-keys', api.AccessKeyViewSet, 'access-key') router.register('sso', api.SSOViewSet, 'sso') +router.register('temp-tokens', api.TempTokenViewSet, 'temp-token') router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-token') diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index d579eacfb..3982b1349 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -31,6 +31,8 @@ def combine_seq(s1, s2, callback=None): def get_logger(name=''): + if '/' in name: + name = os.path.basename(name).replace('.py', '') return logging.getLogger('jumpserver.%s' % name) diff --git a/apps/common/utils/random.py b/apps/common/utils/random.py index 1a7449ef0..db3b39c05 100644 --- a/apps/common/utils/random.py +++ b/apps/common/utils/random.py @@ -40,27 +40,3 @@ def random_string(length, lower=True, upper=True, digit=True, special_char=False password = ''.join(password) return password - - -# def strTimeProp(start, end, prop, fmt): -# time_start = time.mktime(time.strptime(start, fmt)) -# time_end = time.mktime(time.strptime(end, fmt)) -# ptime = time_start + prop * (time_end - time_start) -# return int(ptime) -# -# -# def randomTimestamp(start, end, fmt='%Y-%m-%d %H:%M:%S'): -# return strTimeProp(start, end, random.random(), fmt) -# -# -# def randomDate(start, end, frmt='%Y-%m-%d %H:%M:%S'): -# return time.strftime(frmt, time.localtime(strTimeProp(start, end, random.random(), frmt))) -# -# -# def randomTimestampList(start, end, n, frmt='%Y-%m-%d %H:%M:%S'): -# return [randomTimestamp(start, end, frmt) for _ in range(n)] -# -# -# def randomDateList(start, end, n, frmt='%Y-%m-%d %H:%M:%S'): -# return [randomDate(start, end, frmt) for _ in range(n)] - diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index f72ed899f..3cc8a067d 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -256,6 +256,8 @@ class Config(dict): 'AUTH_SAML2_PROVIDER_AUTHORIZATION_ENDPOINT': '/', 'AUTH_SAML2_AUTHENTICATION_FAILURE_REDIRECT_URI': '/', + 'AUTH_TEMP_TOKEN': False, + # 企业微信 'AUTH_WECOM': False, 'WECOM_CORPID': '', diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index f71afec9b..e07883d55 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -109,11 +109,11 @@ CAS_APPLY_ATTRIBUTES_TO_USER = CONFIG.CAS_APPLY_ATTRIBUTES_TO_USER CAS_RENAME_ATTRIBUTES = CONFIG.CAS_RENAME_ATTRIBUTES CAS_CREATE_USER = CONFIG.CAS_CREATE_USER -# SSO Auth +# SSO auth AUTH_SSO = CONFIG.AUTH_SSO AUTH_SSO_AUTHKEY_TTL = CONFIG.AUTH_SSO_AUTHKEY_TTL -# WECOM Auth +# WECOM auth AUTH_WECOM = CONFIG.AUTH_WECOM WECOM_CORPID = CONFIG.WECOM_CORPID WECOM_AGENTID = CONFIG.WECOM_AGENTID @@ -141,6 +141,9 @@ SAML2_SP_ADVANCED_SETTINGS = CONFIG.SAML2_SP_ADVANCED_SETTINGS SAML2_LOGIN_URL_NAME = "authentication:saml2:saml2-login" SAML2_LOGOUT_URL_NAME = "authentication:saml2:saml2-logout" +# 临时 token +AUTH_TEMP_TOKEN = CONFIG.AUTH_TEMP_TOKEN + # Other setting TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS @@ -160,6 +163,7 @@ AUTH_BACKEND_DINGTALK = 'authentication.backends.sso.DingTalkAuthentication' AUTH_BACKEND_FEISHU = 'authentication.backends.sso.FeiShuAuthentication' AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.sso.AuthorizationTokenAuthentication' AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend' +AUTH_BACKEND_TEMP_TOKEN = 'authentication.backends.token.TempTokenAuthBackend' AUTHENTICATION_BACKENDS = [ @@ -172,7 +176,7 @@ AUTHENTICATION_BACKENDS = [ # 扫码模式 AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU, # Token模式 - AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, + AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN ] ONLY_ALLOW_EXIST_USER_AUTH = CONFIG.ONLY_ALLOW_EXIST_USER_AUTH diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 73226f058..7044727ed 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70685e92cbf84f4178224a44fd84eb884a0acfb9749200541ea6655a9a397a72 -size 125019 +oid sha256:89878c511a62211520b347ccf37676cb11e9a0b3257ff968fb6d5dd81726a1e5 +size 125117 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index c80ef4904..643d66c0c 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-12 17:03+0800\n" +"POT-Creation-Date: 2022-04-13 20:21+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,25 +29,25 @@ msgstr "Acls" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:53 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:55 #: 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:661 -#: xpack/plugins/cloud/models.py:28 +#: xpack/plugins/cloud/models.py:27 msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:56 +#: assets/models/user.py:247 terminal/models/endpoint.py:58 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:57 +#: assets/models/user.py:247 terminal/models/endpoint.py:59 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" -#: acls/models/base.py:31 authentication/models.py:17 +#: acls/models/base.py:31 authentication/models.py:18 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:26 msgid "Active" @@ -61,12 +61,12 @@ msgstr "アクティブ" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 -#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:63 +#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:65 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 +#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "コメント" @@ -87,9 +87,9 @@ msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 -#: authentication/models.py:50 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:106 rbac/models/rolebinding.py:40 -#: terminal/backends/command/models.py:19 +#: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 +#: rbac/builtin.py:107 rbac/models/rolebinding.py:40 +#: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 @@ -129,12 +129,12 @@ msgstr "システムユーザー" #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/serializers/system_user.py:264 audits/models.py:39 -#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:20 +#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/cloud/models.py:222 msgid "Asset" msgstr "資産" @@ -154,6 +154,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:119 #: authentication/forms.py:15 authentication/forms.py:17 +#: authentication/models.py:69 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 @@ -265,7 +266,7 @@ msgstr "アプリケーション" #: applications/models/account.py:15 assets/models/authbook.py:20 #: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 #: perms/models/application_permission.py:33 -#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 +#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:35 terminal/models/session.py:48 #: xpack/plugins/change_auth_plan/models/app.py:36 #: xpack/plugins/change_auth_plan/models/app.py:147 @@ -317,7 +318,7 @@ msgstr "タイプ" msgid "Domain" msgstr "ドメイン" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 #: xpack/plugins/cloud/serializers/account.py:58 msgid "Attrs" msgstr "ツールバーの" @@ -356,7 +357,7 @@ msgstr "タイプ表示" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:918 -#: xpack/plugins/cloud/models.py:125 +#: xpack/plugins/cloud/models.py:124 msgid "Date created" msgstr "作成された日付" @@ -571,7 +572,7 @@ msgstr "ホスト名生" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" @@ -611,7 +612,7 @@ msgstr "ラベル" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "によって作成された" @@ -715,7 +716,7 @@ msgstr "トリガーモード" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:179 +#: xpack/plugins/cloud/models.py:178 msgid "Reason" msgstr "理由" @@ -751,7 +752,7 @@ msgstr "失敗しました" msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:40 +#: assets/models/base.py:40 authentication/models.py:72 msgid "Date verified" msgstr "確認済みの日付" @@ -953,7 +954,7 @@ msgid "Parent key" msgstr "親キー" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -1011,7 +1012,7 @@ msgstr "ログインモード" msgid "SFTP Root" msgstr "SFTPルート" -#: assets/models/user.py:254 authentication/models.py:48 +#: assets/models/user.py:254 authentication/models.py:49 msgid "Token" msgstr "トークン" @@ -1426,6 +1427,7 @@ msgid "Resource" msgstr "リソース" #: audits/models.py:65 audits/models.py:88 +#: terminal/backends/command/serializers.py:39 msgid "Datetime" msgstr "時間" @@ -1480,8 +1482,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 -#: xpack/plugins/cloud/models.py:227 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 +#: xpack/plugins/cloud/models.py:226 msgid "Status" msgstr "ステータス" @@ -1518,7 +1520,7 @@ msgid "Hosts display" msgstr "ホスト表示" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:173 +#: xpack/plugins/cloud/models.py:172 msgid "Result" msgstr "結果" @@ -1562,170 +1564,174 @@ msgstr "企業微信" msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:106 +#: audits/signal_handlers.py:73 authentication/models.py:76 +msgid "Temporary token" +msgstr "一時的なトークン" + +#: audits/signal_handlers.py:107 msgid "User and Group" msgstr "ユーザーとグループ" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:108 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} に参加 {UserGroup}" -#: audits/signal_handlers.py:108 +#: audits/signal_handlers.py:109 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} のそばを通る {UserGroup}" -#: audits/signal_handlers.py:111 +#: audits/signal_handlers.py:112 msgid "Asset and SystemUser" msgstr "資産およびシステム・ユーザー" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:113 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 追加 {SystemUser}" -#: audits/signal_handlers.py:113 +#: audits/signal_handlers.py:114 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 削除 {SystemUser}" -#: audits/signal_handlers.py:116 +#: audits/signal_handlers.py:117 msgid "Node and Asset" msgstr "ノードと資産" -#: audits/signal_handlers.py:117 +#: audits/signal_handlers.py:118 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 追加 {Asset}" -#: audits/signal_handlers.py:118 +#: audits/signal_handlers.py:119 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 削除 {Asset}" -#: audits/signal_handlers.py:121 +#: audits/signal_handlers.py:122 msgid "User asset permissions" msgstr "ユーザー資産の権限" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:123 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 追加 {User}" -#: audits/signal_handlers.py:123 +#: audits/signal_handlers.py:124 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 削除 {User}" -#: audits/signal_handlers.py:126 +#: audits/signal_handlers.py:127 msgid "User group asset permissions" msgstr "ユーザーグループの資産権限" -#: audits/signal_handlers.py:127 +#: audits/signal_handlers.py:128 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:128 +#: audits/signal_handlers.py:129 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:131 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:132 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "資産権限" -#: audits/signal_handlers.py:132 +#: audits/signal_handlers.py:133 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 追加 {Asset}" -#: audits/signal_handlers.py:133 +#: audits/signal_handlers.py:134 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 削除 {Asset}" -#: audits/signal_handlers.py:136 +#: audits/signal_handlers.py:137 msgid "Node permission" msgstr "ノード権限" -#: audits/signal_handlers.py:137 +#: audits/signal_handlers.py:138 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 追加 {Node}" -#: audits/signal_handlers.py:138 +#: audits/signal_handlers.py:139 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 削除 {Node}" -#: audits/signal_handlers.py:141 +#: audits/signal_handlers.py:142 msgid "Asset permission and SystemUser" msgstr "資産権限とSystemUser" -#: audits/signal_handlers.py:142 +#: audits/signal_handlers.py:143 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:143 +#: audits/signal_handlers.py:144 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 削除 {SystemUser}" -#: audits/signal_handlers.py:146 +#: audits/signal_handlers.py:147 msgid "User application permissions" msgstr "ユーザーアプリケーションの権限" -#: audits/signal_handlers.py:147 +#: audits/signal_handlers.py:148 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 追加 {User}" -#: audits/signal_handlers.py:148 +#: audits/signal_handlers.py:149 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 削除 {User}" -#: audits/signal_handlers.py:151 +#: audits/signal_handlers.py:152 msgid "User group application permissions" msgstr "ユーザーグループアプリケーションの権限" -#: audits/signal_handlers.py:152 +#: audits/signal_handlers.py:153 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:153 +#: audits/signal_handlers.py:154 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:156 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:157 perms/models/application_permission.py:38 msgid "Application permission" msgstr "申請許可" -#: audits/signal_handlers.py:157 +#: audits/signal_handlers.py:158 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 追加 {Application}" -#: audits/signal_handlers.py:158 +#: audits/signal_handlers.py:159 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 削除 {Application}" -#: audits/signal_handlers.py:161 +#: audits/signal_handlers.py:162 msgid "Application permission and SystemUser" msgstr "アプリケーション権限とSystemUser" -#: audits/signal_handlers.py:162 +#: audits/signal_handlers.py:163 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:163 +#: audits/signal_handlers.py:164 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}" @@ -2028,31 +2034,48 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:33 terminal/serializers/storage.py:28 +#: authentication/models.py:34 terminal/serializers/storage.py:28 msgid "Access key" msgstr "アクセスキー" -#: authentication/models.py:40 +#: authentication/models.py:41 msgid "Private Token" msgstr "プライベートトークン" -#: authentication/models.py:49 +#: authentication/models.py:50 msgid "Expired" msgstr "期限切れ" -#: authentication/models.py:53 +#: authentication/models.py:54 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:61 +#: authentication/models.py:62 msgid "Connection token" msgstr "接続トークン" -#: authentication/models.py:63 +#: authentication/models.py:64 msgid "Can view connection token secret" msgstr "接続トークンの秘密を表示できます" #: authentication/models.py:70 +#: authentication/templates/authentication/_access_key_modal.html:31 +#: settings/serializers/auth/radius.py:17 +msgid "Secret" +msgstr "ひみつ" + +#: authentication/models.py:71 +msgid "Verified" +msgstr "確認済み" + +#: authentication/models.py:73 perms/models/base.py:90 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 +#: users/models/user.py:703 +msgid "Date expired" +msgstr "期限切れの日付" + +#: authentication/models.py:92 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2064,6 +2087,14 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" +#: authentication/serializers/token.py:79 +#: perms/serializers/application/permission.py:20 +#: perms/serializers/application/permission.py:41 +#: perms/serializers/asset/permission.py:19 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 +msgid "Is valid" +msgstr "有効です" + #: authentication/templates/authentication/_access_key_modal.html:6 msgid "API key list" msgstr "APIキーリスト" @@ -2081,11 +2112,6 @@ msgstr "ドキュメント" msgid "ID" msgstr "ID" -#: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 -msgid "Secret" -msgstr "秘密" - #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:93 terminal/notifications.py:141 msgid "Date" @@ -2151,7 +2177,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:296 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:298 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2612,11 +2638,11 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:295 +#: jumpserver/conf.py:297 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:297 +#: jumpserver/conf.py:299 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -2972,13 +2998,6 @@ msgstr "クリップボードペースト" msgid "Clipboard copy paste" msgstr "クリップボードコピーペースト" -#: perms/models/base.py:90 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 -#: users/models/user.py:703 -msgid "Date expired" -msgstr "期限切れの日付" - #: perms/models/base.py:94 msgid "From ticket" msgstr "チケットから" @@ -3015,13 +3034,6 @@ msgstr "アプリケーション権限の有効期限が近づいています" msgid "application permissions of organization {}" msgstr "Organization {} のアプリケーション権限" -#: perms/serializers/application/permission.py:20 -#: perms/serializers/application/permission.py:41 -#: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 -msgid "Is valid" -msgstr "有効です" - #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 @@ -3114,27 +3126,27 @@ msgstr "{} 少なくとも1つのシステムロール" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:97 +#: rbac/builtin.py:98 msgid "SystemAdmin" msgstr "システム管理者" -#: rbac/builtin.py:100 +#: rbac/builtin.py:101 msgid "SystemAuditor" msgstr "システム監査人" -#: rbac/builtin.py:103 +#: rbac/builtin.py:104 msgid "SystemComponent" msgstr "システムコンポーネント" -#: rbac/builtin.py:109 +#: rbac/builtin.py:110 msgid "OrgAdmin" msgstr "組織管理者" -#: rbac/builtin.py:112 +#: rbac/builtin.py:113 msgid "OrgAuditor" msgstr "監査員を組織する" -#: rbac/builtin.py:115 +#: rbac/builtin.py:116 msgid "OrgUser" msgstr "組織ユーザー" @@ -4599,30 +4611,30 @@ msgstr "ターミナル管理" msgid "Invalid elasticsearch config" msgstr "無効なElasticsearch構成" -#: terminal/backends/command/models.py:15 +#: terminal/backends/command/models.py:16 msgid "Ordinary" msgstr "普通" -#: terminal/backends/command/models.py:16 +#: terminal/backends/command/models.py:17 msgid "Dangerous" msgstr "危険" -#: terminal/backends/command/models.py:22 +#: terminal/backends/command/models.py:23 msgid "Input" msgstr "入力" -#: terminal/backends/command/models.py:23 +#: terminal/backends/command/models.py:24 #: terminal/backends/command/serializers.py:36 msgid "Output" msgstr "出力" -#: terminal/backends/command/models.py:24 terminal/models/replay.py:9 +#: terminal/backends/command/models.py:25 terminal/models/replay.py:9 #: terminal/models/sharing.py:17 terminal/models/sharing.py:64 #: terminal/templates/terminal/_msg_command_alert.html:10 msgid "Session" msgstr "セッション" -#: terminal/backends/command/models.py:25 +#: terminal/backends/command/models.py:26 #: terminal/backends/command/serializers.py:17 msgid "Risk level" msgstr "リスクレベル" @@ -4639,7 +4651,7 @@ msgstr "リスクレベル表示" msgid "Timestamp" msgstr "タイムスタンプ" -#: terminal/backends/command/serializers.py:39 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:40 terminal/models/terminal.py:105 msgid "Remote Address" msgstr "リモートアドレス" @@ -4699,18 +4711,18 @@ msgstr "MariaDB ポート" msgid "PostgreSQL Port" msgstr "PostgreSQL ポート" -#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:61 +#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:63 #: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 #: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 #: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/endpoint.py:54 +#: terminal/models/endpoint.py:56 msgid "IP group" msgstr "IP グループ" -#: terminal/models/endpoint.py:66 +#: terminal/models/endpoint.py:68 msgid "Endpoint rule" msgstr "エンドポイントルール" @@ -4931,7 +4943,7 @@ msgstr "バケット" msgid "Secret key" msgstr "秘密キー" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 msgid "Region" msgstr "リージョン" @@ -6262,79 +6274,79 @@ msgstr "リリース済み" msgid "Cloud center" msgstr "クラウドセンター" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:29 msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:34 +#: xpack/plugins/cloud/models.py:33 msgid "Validity" msgstr "有効性" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:38 msgid "Cloud account" msgstr "クラウドアカウント" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:40 msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "アカウント" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "リージョン" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:90 msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix adminユーザー" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows管理者" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "常に更新" -#: xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/models.py:118 msgid "Date last sync" msgstr "最終同期日" -#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 msgid "Sync instance task" msgstr "インスタンスの同期タスク" -#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 +#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 msgid "Date sync" msgstr "日付の同期" -#: xpack/plugins/cloud/models.py:186 +#: xpack/plugins/cloud/models.py:185 msgid "Sync instance task execution" msgstr "インスタンスタスクの同期実行" -#: xpack/plugins/cloud/models.py:210 +#: xpack/plugins/cloud/models.py:209 msgid "Sync task" msgstr "同期タスク" -#: xpack/plugins/cloud/models.py:214 +#: xpack/plugins/cloud/models.py:213 msgid "Sync instance task history" msgstr "インスタンスタスク履歴の同期" -#: xpack/plugins/cloud/models.py:217 +#: xpack/plugins/cloud/models.py:216 msgid "Instance" msgstr "インスタンス" -#: xpack/plugins/cloud/models.py:234 +#: xpack/plugins/cloud/models.py:233 msgid "Sync instance detail" msgstr "同期インスタンスの詳細" @@ -6688,3 +6700,26 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" + +#~ msgid "Inherit" +#~ msgstr "継承" + +#~ msgid "Include" +#~ msgstr "含める" + +#~ msgid "Exclude" +#~ msgstr "除外" + +#~ msgid "DatabaseApp" +#~ msgstr "データベースの適用" + +#, fuzzy +#~| msgid "Connection token" +#~ msgid "One time token" +#~ msgstr "接続トークン" + +#~ msgid "JD Cloud" +#~ msgstr "京東雲" + +#~ msgid "CN East-Suqian" +#~ msgstr "華東-宿遷" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 5b9f31b64..ad6a09539 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c13775875a335e3c8dbc7f666af622c5aa12050100b15e616c210e8e3043e38 -size 103490 +oid sha256:c5e41035cf1525f01fb773511041f0f8a3a25cdfb1fa4f1e681c6d7eec85f6b9 +size 103570 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b15b2c545..365f797eb 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-12 17:03+0800\n" +"POT-Creation-Date: 2022-04-13 20:21+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -28,25 +28,25 @@ msgstr "访问控制" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:53 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:55 #: 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:661 -#: xpack/plugins/cloud/models.py:28 +#: xpack/plugins/cloud/models.py:27 msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:56 +#: assets/models/user.py:247 terminal/models/endpoint.py:58 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:57 +#: assets/models/user.py:247 terminal/models/endpoint.py:59 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" -#: acls/models/base.py:31 authentication/models.py:17 +#: acls/models/base.py:31 authentication/models.py:18 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:26 msgid "Active" @@ -60,12 +60,12 @@ msgstr "激活中" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 -#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:63 +#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:65 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 +#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" @@ -86,9 +86,9 @@ msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 -#: authentication/models.py:50 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:106 rbac/models/rolebinding.py:40 -#: terminal/backends/command/models.py:19 +#: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 +#: rbac/builtin.py:107 rbac/models/rolebinding.py:40 +#: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 @@ -128,12 +128,12 @@ msgstr "系统用户" #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 #: assets/serializers/system_user.py:264 audits/models.py:39 -#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:20 +#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/cloud/models.py:222 msgid "Asset" msgstr "资产" @@ -153,6 +153,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:119 #: authentication/forms.py:15 authentication/forms.py:17 +#: authentication/models.py:69 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 @@ -260,7 +261,7 @@ msgstr "应用程序" #: applications/models/account.py:15 assets/models/authbook.py:20 #: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 #: perms/models/application_permission.py:33 -#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 +#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:35 terminal/models/session.py:48 #: xpack/plugins/change_auth_plan/models/app.py:36 #: xpack/plugins/change_auth_plan/models/app.py:147 @@ -312,7 +313,7 @@ msgstr "类型" msgid "Domain" msgstr "网域" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 #: xpack/plugins/cloud/serializers/account.py:58 msgid "Attrs" msgstr "属性" @@ -351,7 +352,7 @@ msgstr "类型名称" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:918 -#: xpack/plugins/cloud/models.py:125 +#: xpack/plugins/cloud/models.py:124 msgid "Date created" msgstr "创建日期" @@ -566,7 +567,7 @@ msgstr "主机名原始" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" @@ -606,7 +607,7 @@ msgstr "标签管理" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -710,7 +711,7 @@ msgstr "触发模式" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:179 +#: xpack/plugins/cloud/models.py:178 msgid "Reason" msgstr "原因" @@ -746,7 +747,7 @@ msgstr "失败" msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:40 +#: assets/models/base.py:40 authentication/models.py:72 msgid "Date verified" msgstr "校验日期" @@ -948,7 +949,7 @@ msgid "Parent key" msgstr "ssh私钥" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -1006,7 +1007,7 @@ msgstr "认证方式" msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:254 authentication/models.py:48 +#: assets/models/user.py:254 authentication/models.py:49 msgid "Token" msgstr "Token" @@ -1414,6 +1415,7 @@ msgid "Resource" msgstr "资源" #: audits/models.py:65 audits/models.py:88 +#: terminal/backends/command/serializers.py:39 msgid "Datetime" msgstr "日期" @@ -1468,8 +1470,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 -#: xpack/plugins/cloud/models.py:227 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 +#: xpack/plugins/cloud/models.py:226 msgid "Status" msgstr "状态" @@ -1506,7 +1508,7 @@ msgid "Hosts display" msgstr "主机名称" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:173 +#: xpack/plugins/cloud/models.py:172 msgid "Result" msgstr "结果" @@ -1550,170 +1552,174 @@ msgstr "企业微信" msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:106 +#: audits/signal_handlers.py:73 authentication/models.py:76 +msgid "Temporary token" +msgstr "临时 Token" + +#: audits/signal_handlers.py:107 msgid "User and Group" msgstr "用户与用户组" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:108 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} 加入 {UserGroup}" -#: audits/signal_handlers.py:108 +#: audits/signal_handlers.py:109 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} 离开 {UserGroup}" -#: audits/signal_handlers.py:111 +#: audits/signal_handlers.py:112 msgid "Asset and SystemUser" msgstr "资产与系统用户" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:113 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 添加 {SystemUser}" -#: audits/signal_handlers.py:113 +#: audits/signal_handlers.py:114 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 移除 {SystemUser}" -#: audits/signal_handlers.py:116 +#: audits/signal_handlers.py:117 msgid "Node and Asset" msgstr "节点与资产" -#: audits/signal_handlers.py:117 +#: audits/signal_handlers.py:118 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 添加 {Asset}" -#: audits/signal_handlers.py:118 +#: audits/signal_handlers.py:119 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 移除 {Asset}" -#: audits/signal_handlers.py:121 +#: audits/signal_handlers.py:122 msgid "User asset permissions" msgstr "用户资产授权" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:123 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 添加 {User}" -#: audits/signal_handlers.py:123 +#: audits/signal_handlers.py:124 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 移除 {User}" -#: audits/signal_handlers.py:126 +#: audits/signal_handlers.py:127 msgid "User group asset permissions" msgstr "用户组资产授权" -#: audits/signal_handlers.py:127 +#: audits/signal_handlers.py:128 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:128 +#: audits/signal_handlers.py:129 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:131 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:132 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "资产授权" -#: audits/signal_handlers.py:132 +#: audits/signal_handlers.py:133 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 添加 {Asset}" -#: audits/signal_handlers.py:133 +#: audits/signal_handlers.py:134 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 移除 {Asset}" -#: audits/signal_handlers.py:136 +#: audits/signal_handlers.py:137 msgid "Node permission" msgstr "节点授权" -#: audits/signal_handlers.py:137 +#: audits/signal_handlers.py:138 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 添加 {Node}" -#: audits/signal_handlers.py:138 +#: audits/signal_handlers.py:139 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 移除 {Node}" -#: audits/signal_handlers.py:141 +#: audits/signal_handlers.py:142 msgid "Asset permission and SystemUser" msgstr "资产授权与系统用户" -#: audits/signal_handlers.py:142 +#: audits/signal_handlers.py:143 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:143 +#: audits/signal_handlers.py:144 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 移除 {SystemUser}" -#: audits/signal_handlers.py:146 +#: audits/signal_handlers.py:147 msgid "User application permissions" msgstr "用户应用授权" -#: audits/signal_handlers.py:147 +#: audits/signal_handlers.py:148 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 添加 {User}" -#: audits/signal_handlers.py:148 +#: audits/signal_handlers.py:149 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 移除 {User}" -#: audits/signal_handlers.py:151 +#: audits/signal_handlers.py:152 msgid "User group application permissions" msgstr "用户组应用授权" -#: audits/signal_handlers.py:152 +#: audits/signal_handlers.py:153 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:153 +#: audits/signal_handlers.py:154 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:156 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:157 perms/models/application_permission.py:38 msgid "Application permission" msgstr "应用授权" -#: audits/signal_handlers.py:157 +#: audits/signal_handlers.py:158 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 添加 {Application}" -#: audits/signal_handlers.py:158 +#: audits/signal_handlers.py:159 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 移除 {Application}" -#: audits/signal_handlers.py:161 +#: audits/signal_handlers.py:162 msgid "Application permission and SystemUser" msgstr "应用授权与系统用户" -#: audits/signal_handlers.py:162 +#: audits/signal_handlers.py:163 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:163 +#: audits/signal_handlers.py:164 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" @@ -2007,31 +2013,48 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:33 terminal/serializers/storage.py:28 +#: authentication/models.py:34 terminal/serializers/storage.py:28 msgid "Access key" msgstr "API key" -#: authentication/models.py:40 +#: authentication/models.py:41 msgid "Private Token" msgstr "SSH密钥" -#: authentication/models.py:49 +#: authentication/models.py:50 msgid "Expired" msgstr "过期时间" -#: authentication/models.py:53 +#: authentication/models.py:54 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:61 +#: authentication/models.py:62 msgid "Connection token" msgstr "连接令牌" -#: authentication/models.py:63 +#: authentication/models.py:64 msgid "Can view connection token secret" msgstr "可以查看连接令牌密文" #: authentication/models.py:70 +#: authentication/templates/authentication/_access_key_modal.html:31 +#: settings/serializers/auth/radius.py:17 +msgid "Secret" +msgstr "密钥" + +#: authentication/models.py:71 +msgid "Verified" +msgstr "已校验" + +#: authentication/models.py:73 perms/models/base.py:90 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 +#: users/models/user.py:703 +msgid "Date expired" +msgstr "失效日期" + +#: authentication/models.py:92 msgid "Super connection token" msgstr "超级连接令牌" @@ -2043,6 +2066,14 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" +#: authentication/serializers/token.py:79 +#: perms/serializers/application/permission.py:20 +#: perms/serializers/application/permission.py:41 +#: perms/serializers/asset/permission.py:19 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 +msgid "Is valid" +msgstr "账号是否有效" + #: authentication/templates/authentication/_access_key_modal.html:6 msgid "API key list" msgstr "API Key列表" @@ -2060,11 +2091,6 @@ msgstr "文档" msgid "ID" msgstr "ID" -#: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 -msgid "Secret" -msgstr "密钥" - #: authentication/templates/authentication/_access_key_modal.html:33 #: terminal/notifications.py:93 terminal/notifications.py:141 msgid "Date" @@ -2130,7 +2156,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:296 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:298 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2582,11 +2608,11 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:295 +#: jumpserver/conf.py:297 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:297 +#: jumpserver/conf.py:299 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -2937,13 +2963,6 @@ msgstr "剪贴板粘贴" msgid "Clipboard copy paste" msgstr "剪贴板复制粘贴" -#: perms/models/base.py:90 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 -#: users/models/user.py:703 -msgid "Date expired" -msgstr "失效日期" - #: perms/models/base.py:94 msgid "From ticket" msgstr "来自工单" @@ -2980,13 +2999,6 @@ msgstr "应用授权规则即将过期" msgid "application permissions of organization {}" msgstr "组织 ({}) 的应用授权" -#: perms/serializers/application/permission.py:20 -#: perms/serializers/application/permission.py:41 -#: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 -msgid "Is valid" -msgstr "账号是否有效" - #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 @@ -3077,27 +3089,27 @@ msgstr "{} 至少有一个系统角色" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:97 +#: rbac/builtin.py:98 msgid "SystemAdmin" msgstr "系统管理员" -#: rbac/builtin.py:100 +#: rbac/builtin.py:101 msgid "SystemAuditor" msgstr "系统审计员" -#: rbac/builtin.py:103 +#: rbac/builtin.py:104 msgid "SystemComponent" msgstr "系统组件" -#: rbac/builtin.py:109 +#: rbac/builtin.py:110 msgid "OrgAdmin" msgstr "组织管理员" -#: rbac/builtin.py:112 +#: rbac/builtin.py:113 msgid "OrgAuditor" msgstr "组织审计员" -#: rbac/builtin.py:115 +#: rbac/builtin.py:116 msgid "OrgUser" msgstr "组织用户" @@ -4528,30 +4540,30 @@ msgstr "终端管理" msgid "Invalid elasticsearch config" msgstr "无效的 Elasticsearch 配置" -#: terminal/backends/command/models.py:15 +#: terminal/backends/command/models.py:16 msgid "Ordinary" msgstr "普通" -#: terminal/backends/command/models.py:16 +#: terminal/backends/command/models.py:17 msgid "Dangerous" msgstr "危险" -#: terminal/backends/command/models.py:22 +#: terminal/backends/command/models.py:23 msgid "Input" msgstr "输入" -#: terminal/backends/command/models.py:23 +#: terminal/backends/command/models.py:24 #: terminal/backends/command/serializers.py:36 msgid "Output" msgstr "输出" -#: terminal/backends/command/models.py:24 terminal/models/replay.py:9 +#: terminal/backends/command/models.py:25 terminal/models/replay.py:9 #: terminal/models/sharing.py:17 terminal/models/sharing.py:64 #: terminal/templates/terminal/_msg_command_alert.html:10 msgid "Session" msgstr "会话" -#: terminal/backends/command/models.py:25 +#: terminal/backends/command/models.py:26 #: terminal/backends/command/serializers.py:17 msgid "Risk level" msgstr "风险等级" @@ -4568,7 +4580,7 @@ msgstr "风险等级名称" msgid "Timestamp" msgstr "时间戳" -#: terminal/backends/command/serializers.py:39 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:40 terminal/models/terminal.py:105 msgid "Remote Address" msgstr "远端地址" @@ -4628,18 +4640,18 @@ msgstr "MariaDB 端口" msgid "PostgreSQL Port" msgstr "PostgreSQL 端口" -#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:61 +#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:63 #: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 #: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 #: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 msgid "Endpoint" msgstr "端点" -#: terminal/models/endpoint.py:54 +#: terminal/models/endpoint.py:56 msgid "IP group" msgstr "IP 组" -#: terminal/models/endpoint.py:66 +#: terminal/models/endpoint.py:68 msgid "Endpoint rule" msgstr "端点规则" @@ -4860,7 +4872,7 @@ msgstr "桶名称" msgid "Secret key" msgstr "密钥" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 msgid "Region" msgstr "地域" @@ -6174,79 +6186,79 @@ msgstr "已释放" msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:29 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:34 +#: xpack/plugins/cloud/models.py:33 msgid "Validity" msgstr "有效" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:38 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:40 msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "账号" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:90 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix 管理员" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows 管理员" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/models.py:118 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 +#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:186 +#: xpack/plugins/cloud/models.py:185 msgid "Sync instance task execution" msgstr "同步实例任务执行" -#: xpack/plugins/cloud/models.py:210 +#: xpack/plugins/cloud/models.py:209 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:214 +#: xpack/plugins/cloud/models.py:213 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:217 +#: xpack/plugins/cloud/models.py:216 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/models.py:234 +#: xpack/plugins/cloud/models.py:233 msgid "Sync instance detail" msgstr "同步实例详情" @@ -6599,3 +6611,23 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" + +#~ msgid "Inherit" +#~ msgstr "继承" + +#~ msgid "Include" +#~ msgstr "包含" + +#~ msgid "Exclude" +#~ msgstr "不包含" + +#~ msgid "DatabaseApp" +#~ msgstr "数据库应用" + +#~ msgid "Database proxy MySQL protocol listen port" +#~ msgstr "MySQL 协议监听的端口" + +#, fuzzy +#~| msgid "Database proxy PostgreSQL port" +#~ msgid "Database proxy PostgreSQL listen port" +#~ msgstr "数据库组件 PostgreSQL 协议监听的端口" diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index 513ec210a..d8e84a554 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -16,6 +16,7 @@ user_perms = ( ('applications', 'application', 'match', 'application'), ('ops', 'commandexecution', 'add', 'commandexecution'), ('authentication', 'connectiontoken', 'add', 'connectiontoken'), + ('authentication', 'temptoken', 'add', 'temptoken'), ('tickets', 'ticket', 'view', 'ticket'), ('orgs', 'organization', 'view', 'rootorg'), ) diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index a6dc3b43c..ca221f2de 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -67,6 +67,7 @@ class PublicSettingApi(generics.RetrieveAPIView): # Announcement "ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED, "ANNOUNCEMENT": settings.ANNOUNCEMENT, + "AUTH_TEMP_TOKEN": settings.AUTH_TEMP_TOKEN, } } return instance From 5127214375cb4ad6458276a7a0c38e3cd6951c69 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 14 Apr 2022 12:18:11 +0800 Subject: [PATCH 024/258] =?UTF-8?q?feat:=20=E7=AB=99=E5=86=85=E4=BF=A1?= =?UTF-8?q?=E4=B8=80=E9=94=AE=E5=B7=B2=E8=AF=BB=20(#8057)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/notifications/api/site_msgs.py | 6 ++++++ apps/notifications/site_msg.py | 12 ++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/notifications/api/site_msgs.py b/apps/notifications/api/site_msgs.py index 632101384..29bd785d5 100644 --- a/apps/notifications/api/site_msgs.py +++ b/apps/notifications/api/site_msgs.py @@ -50,6 +50,12 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JMSGenericViewSet): SiteMessageUtil.mark_msgs_as_read(user.id, ids) return Response({'detail': 'ok'}) + @action(methods=[PATCH], detail=False, url_path='mark-as-read-all') + def mark_as_read_all(self, request, **kwargs): + user = request.user + SiteMessageUtil.mark_msgs_as_read(user.id) + return Response({'detail': 'ok'}) + @action(methods=[POST], detail=False) def send(self, request, **kwargs): seri = self.get_serializer(data=request.data) diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py index 7a3c9457f..faa36b5f4 100644 --- a/apps/notifications/site_msg.py +++ b/apps/notifications/site_msg.py @@ -1,4 +1,4 @@ -from django.db.models import F +from django.db.models import F, Q from django.db import transaction from common.utils.timezone import local_now @@ -80,11 +80,11 @@ class SiteMessageUtil: return site_msgs_count @classmethod - def mark_msgs_as_read(cls, user_id, msg_ids): - site_msg_users = SiteMessageUsers.objects.filter( - user_id=user_id, sitemessage_id__in=msg_ids, - has_read=False - ) + def mark_msgs_as_read(cls, user_id, msg_ids=None): + q = Q(user_id=user_id) & Q(has_read=False) + if msg_ids is not None: + q &= Q(sitemessage_id__in=msg_ids) + site_msg_users = SiteMessageUsers.objects.filter(q) for site_msg_user in site_msg_users: site_msg_user.has_read = True From ea478fc8012d7f4cc00ff294bad4a3d4e4c39a0a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 14 Apr 2022 12:53:33 +0800 Subject: [PATCH 025/258] =?UTF-8?q?fix:=20Public=20Setting=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20Magnus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/api/public.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index ca221f2de..5efa54319 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -64,6 +64,7 @@ class PublicSettingApi(generics.RetrieveAPIView): "AUTH_FEISHU": settings.AUTH_FEISHU, # Terminal "XRDP_ENABLED": settings.XRDP_ENABLED, + "TERMINAL_MAGNUS_ENABLED": settings.TERMINAL_MAGNUS_ENABLED, # Announcement "ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED, "ANNOUNCEMENT": settings.ANNOUNCEMENT, From c240a471dc743fe4cf9478edf92942c2bde9e69b Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 14 Apr 2022 14:10:05 +0800 Subject: [PATCH 026/258] =?UTF-8?q?fix:=20Public=20Setting=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20Magnus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/custom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 794180fd2..cafdc1a59 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -139,6 +139,7 @@ LOGIN_REDIRECT_MSG_ENABLED = CONFIG.LOGIN_REDIRECT_MSG_ENABLED CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = CONFIG.CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS XRDP_ENABLED = CONFIG.XRDP_ENABLED +TERMINAL_MAGNUS_ENABLED = CONFIG.TERMINAL_MAGNUS_ENABLED # SMS enabled SMS_ENABLED = CONFIG.SMS_ENABLED From 6d8e8856aca3c779bc8b70002a0edbd067f6aefa Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 14 Apr 2022 14:27:43 +0800 Subject: [PATCH 027/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4timestamp=5Fdisplay=E5=8F=AA=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/backends/command/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/backends/command/serializers.py b/apps/terminal/backends/command/serializers.py index 2eb9e434c..9c8484efb 100644 --- a/apps/terminal/backends/command/serializers.py +++ b/apps/terminal/backends/command/serializers.py @@ -36,7 +36,7 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer): output = serializers.CharField(max_length=2048, allow_blank=True, label=_("Output")) risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) timestamp = serializers.IntegerField(label=_('Timestamp')) - timestamp_display = serializers.DateTimeField(label=_('Datetime')) + timestamp_display = serializers.DateTimeField(read_only=True, label=_('Datetime')) remote_addr = serializers.CharField(read_only=True, label=_('Remote Address')) @staticmethod From 7aa0c9bf196db83a6da0dd4dd4f8c7198298b492 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 13 Apr 2022 20:36:06 +0800 Subject: [PATCH 028/258] feat: download --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 137 ++++++++++++++------------ apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 128 +++++++++++++----------- apps/templates/resource_download.html | 12 ++- 5 files changed, 164 insertions(+), 121 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 7044727ed..c5ec670bb 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89878c511a62211520b347ccf37676cb11e9a0b3257ff968fb6d5dd81726a1e5 -size 125117 +oid sha256:54be66877253eed7bec1db706604af83a48f1c5fbc95eef1132c7f880fef154a +size 125598 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 643d66c0c..b7026603b 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-13 20:21+0800\n" +"POT-Creation-Date: 2022-04-13 20:35+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -33,7 +33,7 @@ msgstr "Acls" #: 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:661 -#: xpack/plugins/cloud/models.py:27 +#: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名前" @@ -66,7 +66,7 @@ msgstr "アクティブ" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "コメント" @@ -134,7 +134,7 @@ msgstr "システムユーザー" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:222 +#: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "資産" @@ -318,8 +318,8 @@ msgstr "タイプ" msgid "Domain" msgstr "ドメイン" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 -#: xpack/plugins/cloud/serializers/account.py:58 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "ツールバーの" @@ -357,7 +357,7 @@ msgstr "タイプ表示" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:918 -#: xpack/plugins/cloud/models.py:124 +#: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "作成された日付" @@ -572,7 +572,7 @@ msgstr "ホスト名生" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" @@ -612,7 +612,7 @@ msgstr "ラベル" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "によって作成された" @@ -716,7 +716,7 @@ msgstr "トリガーモード" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:178 +#: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "理由" @@ -744,7 +744,7 @@ msgstr "OK" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失敗しました" @@ -954,7 +954,7 @@ msgid "Parent key" msgstr "親キー" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -1482,8 +1482,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 -#: xpack/plugins/cloud/models.py:226 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "ステータス" @@ -1520,7 +1520,7 @@ msgid "Hosts display" msgstr "ホスト表示" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:172 +#: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "結果" @@ -4504,16 +4504,18 @@ msgstr "キャンセル" #: templates/resource_download.html:18 templates/resource_download.html:24 #: templates/resource_download.html:25 templates/resource_download.html:30 +#: templates/resource_download.html:40 msgid "Client" msgstr "クライアント" #: templates/resource_download.html:20 msgid "" "JumpServer Client, currently used to launch the client, now only support " -"launch RDP client, The SSH client will next" +"launch RDP SSH client, The Telnet client will next" msgstr "" -"現在クライアントの起動に使用されているJumpServerクライアントは、RDPクライアン" -"トの起動のみをサポートしています。" +"JumpServerクライアントは、現在特定のクライアントプログラムの接続資産を喚起す" +"るために使用されており、現在はRDP SSHクライアントのみをサポートしています。" +"「Telnetは将来的にサポートする" #: templates/resource_download.html:30 msgid "Microsoft" @@ -4531,11 +4533,19 @@ msgstr "" "MacOSは、Windowsに付属のRDPアセットを接続するためにクライアントをダウンロード" "する必要があります" -#: templates/resource_download.html:41 +#: templates/resource_download.html:42 +msgid "" +"Windows needs to download the client to connect SSH assets, and the MacOS " +"system uses its own terminal" +msgstr "" +"WindowsはクライアントをダウンロードしてSSH資産に接続する必要があり、macOSシス" +"テムは独自のTerminalを採用している。" + +#: templates/resource_download.html:51 msgid "Windows Remote application publisher tools" msgstr "Windowsリモートアプリケーション発行者ツール" -#: templates/resource_download.html:42 +#: templates/resource_download.html:52 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -4943,7 +4953,7 @@ msgstr "バケット" msgid "Secret key" msgstr "秘密キー" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "リージョン" @@ -6215,58 +6225,62 @@ msgid "Baidu Cloud" msgstr "百度雲" #: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "京東雲" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "テンセント雲" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "華為私有雲" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青雲私有雲" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌雲" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "インスタンス名" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "インスタンス名と部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "同期していません" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新しい同期" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "同期済み" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "リリース済み" @@ -6274,79 +6288,79 @@ msgstr "リリース済み" msgid "Cloud center" msgstr "クラウドセンター" -#: xpack/plugins/cloud/models.py:29 +#: xpack/plugins/cloud/models.py:30 msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/models.py:34 msgid "Validity" msgstr "有効性" -#: xpack/plugins/cloud/models.py:38 +#: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "クラウドアカウント" -#: xpack/plugins/cloud/models.py:40 +#: xpack/plugins/cloud/models.py:41 msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "アカウント" -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "リージョン" -#: xpack/plugins/cloud/models.py:90 +#: xpack/plugins/cloud/models.py:91 msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix adminユーザー" -#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows管理者" -#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "常に更新" -#: xpack/plugins/cloud/models.py:118 +#: xpack/plugins/cloud/models.py:119 msgid "Date last sync" msgstr "最終同期日" -#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 msgid "Sync instance task" msgstr "インスタンスの同期タスク" -#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 msgid "Date sync" msgstr "日付の同期" -#: xpack/plugins/cloud/models.py:185 +#: xpack/plugins/cloud/models.py:186 msgid "Sync instance task execution" msgstr "インスタンスタスクの同期実行" -#: xpack/plugins/cloud/models.py:209 +#: xpack/plugins/cloud/models.py:210 msgid "Sync task" msgstr "同期タスク" -#: xpack/plugins/cloud/models.py:213 +#: xpack/plugins/cloud/models.py:214 msgid "Sync instance task history" msgstr "インスタンスタスク履歴の同期" -#: xpack/plugins/cloud/models.py:216 +#: xpack/plugins/cloud/models.py:217 msgid "Instance" msgstr "インスタンス" -#: xpack/plugins/cloud/models.py:233 +#: xpack/plugins/cloud/models.py:234 msgid "Sync instance detail" msgstr "同期インスタンスの詳細" @@ -6443,11 +6457,13 @@ msgid "South America (São Paulo)" msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "華南-広州" @@ -6469,6 +6485,7 @@ msgid "CN North-Baoding" msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "華東-上海" @@ -6533,11 +6550,15 @@ msgstr "華北-ウランチャブ一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "華南-広州-友好ユーザー環境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "華東-宿遷" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有効表示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "プロバイダ表示" @@ -6717,9 +6738,3 @@ msgstr "コミュニティ版" #~| msgid "Connection token" #~ msgid "One time token" #~ msgstr "接続トークン" - -#~ msgid "JD Cloud" -#~ msgstr "京東雲" - -#~ msgid "CN East-Suqian" -#~ msgstr "華東-宿遷" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index ad6a09539..c77cc1659 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5e41035cf1525f01fb773511041f0f8a3a25cdfb1fa4f1e681c6d7eec85f6b9 -size 103570 +oid sha256:fa084dd92472110d4bea1674d1e9a96599f42f094aab92f8d34152fdf5726321 +size 103771 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 365f797eb..5682b29ff 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-13 20:21+0800\n" +"POT-Creation-Date: 2022-04-13 20:35+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -32,7 +32,7 @@ msgstr "访问控制" #: 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:661 -#: xpack/plugins/cloud/models.py:27 +#: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名称" @@ -65,7 +65,7 @@ msgstr "激活中" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" @@ -133,7 +133,7 @@ msgstr "系统用户" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:222 +#: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "资产" @@ -313,8 +313,8 @@ msgstr "类型" msgid "Domain" msgstr "网域" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 -#: xpack/plugins/cloud/serializers/account.py:58 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "属性" @@ -352,7 +352,7 @@ msgstr "类型名称" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:918 -#: xpack/plugins/cloud/models.py:124 +#: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "创建日期" @@ -567,7 +567,7 @@ msgstr "主机名原始" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" @@ -607,7 +607,7 @@ msgstr "标签管理" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -711,7 +711,7 @@ msgstr "触发模式" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:178 +#: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "原因" @@ -739,7 +739,7 @@ msgstr "成功" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失败" @@ -949,7 +949,7 @@ msgid "Parent key" msgstr "ssh私钥" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -1470,8 +1470,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 -#: xpack/plugins/cloud/models.py:226 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "状态" @@ -1508,7 +1508,7 @@ msgid "Hosts display" msgstr "主机名称" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:172 +#: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "结果" @@ -4437,16 +4437,17 @@ msgstr "取消" #: templates/resource_download.html:18 templates/resource_download.html:24 #: templates/resource_download.html:25 templates/resource_download.html:30 +#: templates/resource_download.html:40 msgid "Client" msgstr "客户端" #: templates/resource_download.html:20 msgid "" "JumpServer Client, currently used to launch the client, now only support " -"launch RDP client, The SSH client will next" +"launch RDP SSH client, The Telnet client will next" msgstr "" -"JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP 客户" -"端,SSH、Telnet 会在未来支持" +"JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP SSH 客户" +"端,Telnet 会在未来支持" #: templates/resource_download.html:30 msgid "Microsoft" @@ -4462,11 +4463,17 @@ msgid "" "Windows" msgstr "macOS 需要下载客户端来连接 RDP 资产,Windows 系统默认安装了该程序" -#: templates/resource_download.html:41 +#: templates/resource_download.html:42 +msgid "" +"Windows needs to download the client to connect SSH assets, and the MacOS " +"system uses its own terminal" +msgstr "Windows 需要下载客户端来连接SSH资产,macOS系统采用自带的Terminal" + +#: templates/resource_download.html:51 msgid "Windows Remote application publisher tools" msgstr "Windows 远程应用发布服务器工具" -#: templates/resource_download.html:42 +#: templates/resource_download.html:52 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -4872,7 +4879,7 @@ msgstr "桶名称" msgid "Secret key" msgstr "密钥" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "地域" @@ -6127,58 +6134,62 @@ msgid "Baidu Cloud" msgstr "百度云" #: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "京东云" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "腾讯云" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "华为私有云" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青云私有云" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌云" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "实例名称" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "未同步" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新同步" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "已同步" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "已释放" @@ -6186,79 +6197,79 @@ msgstr "已释放" msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:29 +#: xpack/plugins/cloud/models.py:30 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/models.py:34 msgid "Validity" msgstr "有效" -#: xpack/plugins/cloud/models.py:38 +#: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:40 +#: xpack/plugins/cloud/models.py:41 msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "账号" -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:90 +#: xpack/plugins/cloud/models.py:91 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix 管理员" -#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows 管理员" -#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:118 +#: xpack/plugins/cloud/models.py:119 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:185 +#: xpack/plugins/cloud/models.py:186 msgid "Sync instance task execution" msgstr "同步实例任务执行" -#: xpack/plugins/cloud/models.py:209 +#: xpack/plugins/cloud/models.py:210 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:213 +#: xpack/plugins/cloud/models.py:214 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:216 +#: xpack/plugins/cloud/models.py:217 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/models.py:233 +#: xpack/plugins/cloud/models.py:234 msgid "Sync instance detail" msgstr "同步实例详情" @@ -6355,11 +6366,13 @@ msgid "South America (São Paulo)" msgstr "南美洲(圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "华南-广州" @@ -6381,6 +6394,7 @@ msgid "CN North-Baoding" msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "华东-上海" @@ -6445,11 +6459,15 @@ msgstr "华北-乌兰察布一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "华南-广州-友好用户环境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "华东-宿迁" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有效性显示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "服务商显示" diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index 479408161..5dba5457c 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -17,7 +17,7 @@ p {

    JumpServer {% trans 'Client' %}

    - {% trans 'JumpServer Client, currently used to launch the client, now only support launch RDP client, The SSH client will next' %} + {% trans 'JumpServer Client, currently used to launch the client, now only support launch RDP SSH client, The Telnet client will next' %} {# //JumpServer 客户端,支持 RDP 的本地拉起,后续会支持拉起 ssh。#}

      @@ -36,6 +36,16 @@ p {
    +
    +

    SSH {% trans 'Client' %}

    +

    + {% trans 'Windows needs to download the client to connect SSH assets, and the MacOS system uses its own terminal' %} +

    + +
    + {% if XPACK_ENABLED %}

    {% trans 'Windows Remote application publisher tools' %}

    From 0b94d7414a8d46ef829bcbc6a4266c5bd09d0c0a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:51:10 +0800 Subject: [PATCH 029/258] feat: download (#8062) Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com> --- apps/templates/resource_download.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index 5dba5457c..510b2159e 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -42,7 +42,9 @@ p { {% trans 'Windows needs to download the client to connect SSH assets, and the MacOS system uses its own terminal' %}

    From 70a07539afb2e032e60539664dc8db96d96550ce Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 14 Apr 2022 21:55:34 +0800 Subject: [PATCH 030/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BA=91=E5=8E=82=E5=95=86=E7=9A=84redis=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/cache.py | 28 ++++++++++++++++++++++++++++ apps/jumpserver/settings/libs.py | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/common/cache.py b/apps/common/cache.py index b988a8673..67aba9191 100644 --- a/apps/common/cache.py +++ b/apps/common/cache.py @@ -1,5 +1,7 @@ import time +from channels_redis.core import RedisChannelLayer as _RedisChannelLayer + from common.utils.lock import DistributedLock from common.utils.connection import get_redis_client from common.utils import lazyproperty @@ -216,3 +218,29 @@ class CacheValueDesc: def to_internal_value(self, value): return self.field_type.field_type(value) + + +class RedisChannelLayer(_RedisChannelLayer): + async def _brpop_with_clean(self, index, channel, timeout): + cleanup_script = """ + local backed_up = redis.call('ZRANGE', ARGV[2], 0, -1, 'WITHSCORES') + for i = #backed_up, 1, -2 do + redis.call('ZADD', ARGV[1], backed_up[i], backed_up[i - 1]) + end + redis.call('DEL', ARGV[2]) + """ + backup_queue = self._backup_channel_name(channel) + async with self.connection(index) as connection: + # 部分云厂商的 Redis 此操作会报错(不支持,比如阿里云有限制) + try: + await connection.eval(cleanup_script, keys=[], args=[channel, backup_queue]) + except: + pass + result = await connection.bzpopmin(channel, timeout=timeout) + + if result is not None: + _, member, timestamp = result + await connection.zadd(backup_queue, float(timestamp), member) + else: + member = None + return member diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 11aa0ba16..03c10bf1f 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -96,7 +96,7 @@ else: CHANNEL_LAYERS = { 'default': { - 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'BACKEND': 'common.cache.RedisChannelLayer', 'CONFIG': { "hosts": [{ 'address': (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT), From 97e59384e0d1be24ab4b76d1c376485d069deab5 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 15 Apr 2022 16:24:17 +0800 Subject: [PATCH 031/258] =?UTF-8?q?fix:=20connection=20token=20API=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=9C=89=E6=95=88=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 4afa7a306..9e16d31ba 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -484,7 +484,8 @@ class UserConnectionTokenViewSet( tp = 'app' if application else 'asset' data = { "id": token, 'secret': secret, - 'type': tp, 'protocol': system_user.protocol + 'type': tp, 'protocol': system_user.protocol, + 'expire_time': self.get_token_ttl(token), } return Response(data, status=201) From 7b02777f1e87345aa6a34472d1499fda8bf86a2d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 15 Apr 2022 15:31:02 +0800 Subject: [PATCH 032/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9endpoint=20sma?= =?UTF-8?q?rt=20API=E5=85=81=E8=AE=B8=E6=9C=89=E6=95=88=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/base.py | 1 - apps/terminal/api/endpoint.py | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index a4441711a..b7f48a814 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -98,7 +98,6 @@ MIDDLEWARE = [ ] ROOT_URLCONF = 'jumpserver.urls' - TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/endpoint.py index 14efb738f..d612db4a8 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/endpoint.py @@ -8,6 +8,7 @@ from assets.models import Asset from orgs.utils import tmp_to_root_org from applications.models import Application from terminal.models import Session +from common.permissions import IsValidUser from ..models import Endpoint, EndpointRule from .. import serializers @@ -20,9 +21,6 @@ class EndpointViewSet(JMSBulkModelViewSet): search_fields = filterset_fields serializer_class = serializers.EndpointSerializer queryset = Endpoint.objects.all() - rbac_perms = { - 'smart': 'terminal.view_endpoint' - } @staticmethod def get_target_ip(request): @@ -57,7 +55,7 @@ class EndpointViewSet(JMSBulkModelViewSet): target_ip = instance.get_target_ip() return target_ip - @action(methods=['get'], detail=False, url_path='smart') + @action(methods=['get'], detail=False, permission_classes=[IsValidUser], url_path='smart') def smart(self, request, *args, **kwargs): protocol = request.GET.get('protocol') if not protocol: From a647e73c02e9e36fbbe7da2dd3c1edb98dcf5306 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 15 Apr 2022 21:33:15 +0800 Subject: [PATCH 033/258] =?UTF-8?q?feat:=20=E8=AE=BE=E7=BD=AESessionCookie?= =?UTF-8?q?NamePrefix=20(#8071)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 设置SessionCookieNamePrefix * feat: 设置SessionCookieNamePrefix Co-authored-by: Jiangjie.Bai --- apps/authentication/middleware.py | 14 ++++++++++++++ apps/jumpserver/conf.py | 1 + apps/jumpserver/settings/base.py | 16 ++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index 9481c3ff6..42bbb27cb 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -1,5 +1,7 @@ from django.shortcuts import redirect, reverse +from django.utils.deprecation import MiddlewareMixin from django.http import HttpResponse +from django.conf import settings class MFAMiddleware: @@ -34,3 +36,15 @@ class MFAMiddleware: url = reverse('authentication:login-mfa') + '?_=middleware' return redirect(url) + + +class SessionCookieMiddleware(MiddlewareMixin): + + @staticmethod + def process_response(request, response: HttpResponse): + key = settings.SESSION_COOKIE_NAME_PREFIX_KEY + value = settings.SESSION_COOKIE_NAME_PREFIX + if request.COOKIES.get(key) == value: + return response + response.set_cookie(key, value) + return response diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 3cc8a067d..28ee9d8e9 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -157,6 +157,7 @@ class Config(dict): 'DEFAULT_EXPIRED_YEARS': 70, 'SESSION_COOKIE_DOMAIN': None, 'CSRF_COOKIE_DOMAIN': None, + 'SESSION_COOKIE_NAME_PREFIX': None, 'SESSION_COOKIE_AGE': 3600 * 24, 'SESSION_EXPIRE_AT_BROWSER_CLOSE': False, 'LOGIN_URL': reverse_lazy('authentication:login'), diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index b7f48a814..b654a0365 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -94,10 +94,12 @@ MIDDLEWARE = [ 'authentication.backends.oidc.middleware.OIDCRefreshIDTokenMiddleware', 'authentication.backends.cas.middleware.CASMiddleware', 'authentication.middleware.MFAMiddleware', + 'authentication.middleware.SessionCookieMiddleware', 'simple_history.middleware.HistoryRequestMiddleware', ] ROOT_URLCONF = 'jumpserver.urls' + TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -127,6 +129,20 @@ LOGIN_URL = reverse_lazy('authentication:login') SESSION_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN CSRF_COOKIE_DOMAIN = CONFIG.SESSION_COOKIE_DOMAIN + +# 设置 SESSION_COOKIE_NAME_PREFIX_KEY +# 解决 不同域 session csrf cookie 获取混乱问题 +SESSION_COOKIE_NAME_PREFIX_KEY = 'SESSION_COOKIE_NAME_PREFIX' +SESSION_COOKIE_NAME_PREFIX = CONFIG.SESSION_COOKIE_NAME_PREFIX +if SESSION_COOKIE_NAME_PREFIX is not None: + pass +elif SESSION_COOKIE_DOMAIN is not None: + SESSION_COOKIE_NAME_PREFIX = SESSION_COOKIE_DOMAIN.split('.')[0] +else: + SESSION_COOKIE_NAME_PREFIX = 'jms_' +CSRF_COOKIE_NAME = '{}csrftoken'.format(SESSION_COOKIE_NAME_PREFIX) +SESSION_COOKIE_NAME = '{}sessionid'.format(SESSION_COOKIE_NAME_PREFIX) + SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 自定义的配置,SESSION_EXPIRE_AT_BROWSER_CLOSE 始终为 True, 下面这个来控制是否强制关闭后过期 cookie From 10c146b07d2e15a3593636a0a94531eff78552d6 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 18 Apr 2022 11:31:53 +0800 Subject: [PATCH 034/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=BF=9C?= =?UTF-8?q?=E7=A8=8B=E5=BA=94=E7=94=A8=E6=97=A0=E8=B5=84=E4=BA=A7=E4=B8=8B?= =?UTF-8?q?=E8=BD=BDxrdp=20file=20500=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/models/application.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index 15e7dae14..af1e27c2d 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -288,15 +288,14 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): raise ValueError("Remote App not has asset attr") def get_target_ip(self): + target_ip = '' if self.category_remote_app: asset = self.get_remote_app_asset() - target_ip = asset.ip + target_ip = asset.ip if asset else target_ip elif self.category_cloud: target_ip = self.attrs.get('cluster') elif self.category_db: target_ip = self.attrs.get('host') - else: - target_ip = '' return target_ip From 548a374c6dfe66b20351e49e9e8031f1d57f638c Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Fri, 15 Apr 2022 10:03:26 +0800 Subject: [PATCH 035/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E5=9C=A8=E6=B2=A1=E6=9C=89=E5=AF=86=E7=A0=81=E7=9A=84?= =?UTF-8?q?redis=E4=B8=8A=E6=97=B6=EF=BC=8C=E7=AB=99=E5=86=85=E4=BF=A1?= =?UTF-8?q?=E6=95=B0=E9=87=8F=E4=B8=8D=E6=9B=B4=E6=96=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/libs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 03c10bf1f..59446b9de 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -101,7 +101,7 @@ CHANNEL_LAYERS = { "hosts": [{ 'address': (CONFIG.REDIS_HOST, CONFIG.REDIS_PORT), 'db': CONFIG.REDIS_DB_WS, - 'password': CONFIG.REDIS_PASSWORD, + 'password': CONFIG.REDIS_PASSWORD or None, 'ssl': context }], }, From 3eab621b28b8eabae474022d2681f49b1c589f7c Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 18 Apr 2022 15:17:28 +0800 Subject: [PATCH 036/258] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96Endpoint?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E9=80=BB=E8=BE=91=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?XRDP=E8=A7=84=E5=88=99=E5=92=8CEndpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 修改Endpoint迁移文件 --- apps/jumpserver/conf.py | 2 +- .../migrations/0048_endpoint_endpointrule.py | 60 +++++++++++++++---- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 28ee9d8e9..ec35ae6e5 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -313,7 +313,7 @@ class Config(dict): 'TERMINAL_HOST_KEY': '', 'TERMINAL_TELNET_REGEX': '', 'TERMINAL_COMMAND_STORAGE': {}, - # 未来废弃(当下迁移会用) + # 未来废弃(目前迁移会用) 'TERMINAL_RDP_ADDR': '', # 保留(Luna还在用) 'TERMINAL_MAGNUS_ENABLED': True, diff --git a/apps/terminal/migrations/0048_endpoint_endpointrule.py b/apps/terminal/migrations/0048_endpoint_endpointrule.py index 9e75823e2..6400f710a 100644 --- a/apps/terminal/migrations/0048_endpoint_endpointrule.py +++ b/apps/terminal/migrations/0048_endpoint_endpointrule.py @@ -1,5 +1,6 @@ # Generated by Django 3.1.14 on 2022-04-12 07:39 +import copy import common.fields.model import django.core.validators from django.db import migrations, models @@ -9,7 +10,9 @@ from django.conf import settings def migrate_endpoints(apps, schema_editor): - endpoint_data = { + Endpoint = apps.get_model("terminal", "Endpoint") + # migrate default + default_data = { 'id': '00000000-0000-0000-0000-000000000001', 'name': 'Default', 'host': '', @@ -17,19 +20,50 @@ def migrate_endpoints(apps, schema_editor): 'http_port': 0, 'created_by': 'System' } + default_endpoint = Endpoint.objects.create(**default_data) - if settings.XRDP_ENABLED: - xrdp_addr = settings.TERMINAL_RDP_ADDR - if ':' in xrdp_addr: - hostname, port = xrdp_addr.strip().split(':') - else: - hostname, port = xrdp_addr, 3389 - endpoint_data.update({ - 'host': '' if hostname.strip() in ['localhost', '127.0.0.1'] else hostname.strip(), - 'rdp_port': int(port) - }) - Endpoint = apps.get_model("terminal", "Endpoint") - Endpoint.objects.create(**endpoint_data) + if not settings.XRDP_ENABLED: + return + # migrate xrdp + xrdp_addr = settings.TERMINAL_RDP_ADDR + if ':' in xrdp_addr: + host, rdp_port = xrdp_addr.strip().split(':') + else: + host, rdp_port = xrdp_addr, 3389 + host = host.strip() + if host in ['localhost', '127.0.0.1']: + host = '' + if not host: + return + if isinstance(rdp_port, str) and rdp_port.isdigit(): + rdp_port = int(rdp_port) + elif isinstance(rdp_port, int) and (0 <= rdp_port <= 65535): + rdp_port = rdp_port + else: + rdp_port = 3389 + xrdp_data = { + 'name': 'XRDP', + 'host': host, + 'https_port': 0, + 'http_port': 0, + 'ssh_port': 0, + 'rdp_port': rdp_port, + 'mysql_port': 0, + 'mariadb_port': 0, + 'postgresql_port': 0, + 'created_by': 'System' + } + xrdp_endpoint = Endpoint.objects.create(**xrdp_data) + + EndpointRule = apps.get_model("terminal", "EndpointRule") + xrdp_rule_data = { + 'name': 'XRDP', + 'ip_group': ['*'], + 'priority': 20, + 'endpoint': xrdp_endpoint, + 'created_by': 'System' + } + EndpointRule.objects.create(**xrdp_rule_data) class Migration(migrations.Migration): From 4362f8d5afe2cd59c96ff580753b0e9554e9b90b Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:17:23 +0800 Subject: [PATCH 037/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=BB=84?= =?UTF-8?q?=E7=BB=87=20(#8080)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化用户的orgs * perf: 优化组织 Co-authored-by: ibuler --- apps/rbac/models/permission.py | 1 - apps/rbac/models/role.py | 14 ++++++++++++++ apps/rbac/models/rolebinding.py | 22 ++++++++++++++++++++++ apps/users/models/user.py | 29 +++++++++++++---------------- apps/users/serializers/profile.py | 8 +++++--- 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/apps/rbac/models/permission.py b/apps/rbac/models/permission.py index a4ea91b15..bc8fa6231 100644 --- a/apps/rbac/models/permission.py +++ b/apps/rbac/models/permission.py @@ -90,4 +90,3 @@ class Permission(DjangoPermission): permissions = cls.objects.all() permissions = cls.clean_permissions(permissions, scope=scope) return permissions - diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py index b47bccb5b..98a226e6f 100644 --- a/apps/rbac/models/role.py +++ b/apps/rbac/models/role.py @@ -121,6 +121,20 @@ class Role(JMSModel): def is_org(self): return self.scope == const.Scope.org + @classmethod + def get_roles_by_perm(cls, perm): + app_label, codename = perm.split('.') + p = Permission.objects.filter( + codename=codename, + content_type__app_label=app_label + ).first() + if not p: + return p.roles.none() + role_ids = list(p.roles.all().values_list('id', flat=True)) + admin_ids = [BuiltinRole.system_admin.id, BuiltinRole.org_admin.id] + role_ids += admin_ids + return cls.objects.filter(id__in=role_ids) + class SystemRole(Role): objects = SystemRoleManager() diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index a2ee06022..643e38207 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -100,6 +100,28 @@ class RoleBinding(JMSModel): def is_scope_org(self): return self.scope == Scope.org + @classmethod + def get_user_has_the_perm_orgs(cls, perm, user): + from orgs.models import Organization + + roles = Role.get_roles_by_perm(perm) + bindings = list(cls.objects.root_all().filter(role__in=roles, user=user)) + system_bindings = [b for b in bindings if b.scope == Role.Scope.system.value] + + if perm == 'rbac.view_workbench': + all_orgs = user.orgs.all() + else: + all_orgs = Organization.objects.all() + + if system_bindings: + orgs = all_orgs + else: + org_ids = [b.org.id for b in bindings if b.org] + orgs = all_orgs.filter(id__in=org_ids) + if orgs and user.has_perm('orgs.view_rootorg'): + orgs = [Organization.root(), *list(orgs)] + return orgs + class OrgRoleBindingManager(RoleBindingManager): def get_queryset(self): diff --git a/apps/users/models/user.py b/apps/users/models/user.py index ede795d13..80b938468 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -863,23 +863,20 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): return None return self.SOURCE_BACKEND_MAPPING.get(self.source, []) - @property - def all_orgs(self): - from rbac.builtin import BuiltinRole - has_system_role = self.system_roles.all() \ - .exclude(name=BuiltinRole.system_user.name) \ - .exists() - if has_system_role: - orgs = list(Organization.objects.all()) - else: - orgs = list(self.orgs.distinct()) - if self.has_perm('orgs.view_rootorg'): - orgs = [Organization.root()] + orgs - return orgs + @lazyproperty + def console_orgs(self): + from rbac.models import RoleBinding + return RoleBinding.get_user_has_the_perm_orgs('rbac.view_console', self) - @property - def my_orgs(self): - return list(self.orgs.distinct()) + @lazyproperty + def audit_orgs(self): + from rbac.models import RoleBinding + return RoleBinding.get_user_has_the_perm_orgs('rbac.view_audit', self) + + @lazyproperty + def workbench_orgs(self): + from rbac.models import RoleBinding + return RoleBinding.get_user_has_the_perm_orgs('rbac.view_workbench', self) class Meta: ordering = ['username'] diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py index 55083c23d..ac9671d23 100644 --- a/apps/users/serializers/profile.py +++ b/apps/users/serializers/profile.py @@ -121,14 +121,16 @@ class UserProfileSerializer(UserSerializer): mfa_level = serializers.ChoiceField(choices=MFA_LEVEL_CHOICES, label=_('MFA'), required=False) guide_url = serializers.SerializerMethodField() receive_backends = serializers.ListField(child=serializers.CharField(), read_only=True) - orgs = UserOrgSerializer(many=True, read_only=True, source='all_orgs') - myorgs = UserOrgSerializer(many=True, read_only=True, source='my_orgs') + console_orgs = UserOrgSerializer(many=True, read_only=True) + audit_orgs = UserOrgSerializer(many=True, read_only=True) + workbench_orgs = UserOrgSerializer(many=True, read_only=True) perms = serializers.ListField(label=_("Perms"), read_only=True) class Meta(UserSerializer.Meta): read_only_fields = [ 'date_joined', 'last_login', 'created_by', 'source', - 'receive_backends', 'orgs', 'myorgs', 'perms', + 'console_orgs', 'audit_orgs', 'workbench_orgs', + 'receive_backends', 'perms', ] fields = UserSerializer.Meta.fields + [ 'public_key_comment', 'public_key_hash_md5', 'guide_url', From fe47e4058865f9468d9da87e8ec23c41fb627471 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 18 Apr 2022 18:45:38 +0800 Subject: [PATCH 038/258] fix: es6 create index fail --- apps/terminal/backends/command/es.py | 46 ++++++++++++++++------------ 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 2e8a82c88..c7ae7060e 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -59,10 +59,15 @@ class CommandStore(object): data = self.es.indices.get_mapping(self.index) except NotFoundError: return False - + info = self.es.info() + version = info['version']['number'].split('.')[0] try: - # 检测索引是不是新的类型 - properties = data[self.index]['mappings']['properties'] + if version == '6': + # 检测索引是不是新的类型 es6 + properties = data[self.index]['mappings']['data']['properties'] + else: + # 检测索引是不是新的类型 es7 default index type: _doc + properties = data[self.index]['mappings']['properties'] if properties['session']['type'] == 'keyword' \ and properties['org_id']['type'] == 'keyword': return True @@ -75,27 +80,30 @@ class CommandStore(object): self._ensure_index_exists() def _ensure_index_exists(self): - mappings = { - "mappings": { - "properties": { - "session": { - "type": "keyword" - }, - "org_id": { - "type": "keyword" - }, - "@timestamp": { - "type": "date" - }, - "timestamp": { - "type": "long" - } - } + properties = { + "session": { + "type": "keyword" + }, + "org_id": { + "type": "keyword" + }, + "@timestamp": { + "type": "date" + }, + "timestamp": { + "type": "long" } } + info = self.es.info() + version = info['version']['number'].split('.')[0] + if version == '6': + mappings = {'mappings': {'data': {'properties': properties}}} + else: + mappings = {'mappings': {'properties': properties}} try: self.es.indices.create(self.index, body=mappings) + return except RequestError as e: logger.exception(e) From 7b2d51f343c803499c1eb96d408fc114424836c8 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 18 Apr 2022 14:52:41 +0800 Subject: [PATCH 039/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E8=BF=87=E6=BB=A4=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/api/role.py | 5 +++-- apps/rbac/filters.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 apps/rbac/filters.py diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py index a11edc409..ceb2e9c19 100644 --- a/apps/rbac/api/role.py +++ b/apps/rbac/api/role.py @@ -4,6 +4,7 @@ from rest_framework.exceptions import PermissionDenied from rest_framework.decorators import action from common.drf.api import JMSModelViewSet +from ..filters import RoleFilter from ..serializers import RoleSerializer, RoleUserSerializer from ..models import Role, SystemRole, OrgRole from .permission import PermissionViewSet @@ -20,8 +21,8 @@ class RoleViewSet(JMSModelViewSet): 'default': RoleSerializer, 'users': RoleUserSerializer, } - filterset_fields = ['name', 'scope', 'builtin'] - search_fields = filterset_fields + filterset_class = RoleFilter + search_fields = ('name', 'scope', 'builtin') rbac_perms = { 'users': 'rbac.view_rolebinding' } diff --git a/apps/rbac/filters.py b/apps/rbac/filters.py new file mode 100644 index 000000000..f9a936b66 --- /dev/null +++ b/apps/rbac/filters.py @@ -0,0 +1,25 @@ +from django_filters import rest_framework as filters + +from common.drf.filters import BaseFilterSet +from rbac.models import Role + + +class RoleFilter(BaseFilterSet): + name = filters.CharFilter(method='filter_name') + + class Meta: + model = Role + fields = ('name', 'scope', 'builtin') + + @staticmethod + def filter_name(queryset, name, value): + builtin_ids = [] + for role in queryset.filter(builtin=True): + if value in role.display_name: + builtin_ids.append(role.id) + if builtin_ids: + builtin_qs = queryset.model.objects.filter(id__in=builtin_ids) + else: + builtin_qs = queryset.model.objects.none() + queryset = queryset.filter(name__icontains=value) + return queryset | builtin_qs From f4ed4e1176d0168762084938f2d3250b3bd7a815 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 18 Apr 2022 19:50:51 +0800 Subject: [PATCH 040/258] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20temp=20tok?= =?UTF-8?q?en=20=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/temp_token.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/authentication/api/temp_token.py b/apps/authentication/api/temp_token.py index 98c1d74d6..a8fcc02af 100644 --- a/apps/authentication/api/temp_token.py +++ b/apps/authentication/api/temp_token.py @@ -15,7 +15,7 @@ class TempTokenViewSet(JMSModelViewSet): def get_queryset(self): username = self.request.user.username - return TempToken.objects.filter(username=username) + return TempToken.objects.filter(username=username).order_by('-date_created') @action(methods=['PATCH'], detail=True, url_path='expire') def expire(self, *args, **kwargs): @@ -24,4 +24,3 @@ class TempTokenViewSet(JMSModelViewSet): instance.save() serializer = self.get_serializer(instance) return Response(serializer.data) - From 3e3835dc28cce7d94ce270bbf6fdc17fee6e3526 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 19 Apr 2022 10:34:17 +0800 Subject: [PATCH 041/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/migrations/0010_temptoken.py | 2 +- apps/rbac/builtin.py | 13 ++++++++----- .../migrations/0048_endpoint_endpointrule.py | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/authentication/migrations/0010_temptoken.py b/apps/authentication/migrations/0010_temptoken.py index 914188d3f..b76ae0f97 100644 --- a/apps/authentication/migrations/0010_temptoken.py +++ b/apps/authentication/migrations/0010_temptoken.py @@ -23,7 +23,7 @@ class Migration(migrations.Migration): ('secret', models.CharField(max_length=64, verbose_name='Secret')), ('verified', models.BooleanField(default=False, verbose_name='Verified')), ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), - ('date_expired', models.DateTimeField(verbose_name='Date verified')), + ('date_expired', models.DateTimeField(verbose_name='Date expired')), ], options={ 'verbose_name': 'Temporary token', diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index d8e84a554..a199c149c 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -2,6 +2,13 @@ from django.utils.translation import ugettext_noop from .const import Scope, system_exclude_permissions, org_exclude_permissions +system_user_perms = ( + ('authentication', 'connectiontoken', 'add', 'connectiontoken'), + ('authentication', 'temptoken', 'add', 'temptoken'), + ('tickets', 'ticket', 'view', 'ticket'), + ('orgs', 'organization', 'view', 'rootorg'), +) + # Todo: 获取应该区分 系统用户,和组织用户的权限 # 工作台也区分组织后再考虑 user_perms = ( @@ -15,10 +22,6 @@ user_perms = ( ('assets', 'node', 'match', 'node'), ('applications', 'application', 'match', 'application'), ('ops', 'commandexecution', 'add', 'commandexecution'), - ('authentication', 'connectiontoken', 'add', 'connectiontoken'), - ('authentication', 'temptoken', 'add', 'temptoken'), - ('tickets', 'ticket', 'view', 'ticket'), - ('orgs', 'organization', 'view', 'rootorg'), ) auditor_perms = user_perms + ( @@ -104,7 +107,7 @@ class BuiltinRole: '4', ugettext_noop('SystemComponent'), Scope.system, app_exclude_perms, 'exclude' ) system_user = PredefineRole( - '3', ugettext_noop('User'), Scope.system, user_perms + '3', ugettext_noop('User'), Scope.system, system_user_perms ) org_admin = PredefineRole( '5', ugettext_noop('OrgAdmin'), Scope.org, [] diff --git a/apps/terminal/migrations/0048_endpoint_endpointrule.py b/apps/terminal/migrations/0048_endpoint_endpointrule.py index 6400f710a..a03d19e98 100644 --- a/apps/terminal/migrations/0048_endpoint_endpointrule.py +++ b/apps/terminal/migrations/0048_endpoint_endpointrule.py @@ -20,7 +20,7 @@ def migrate_endpoints(apps, schema_editor): 'http_port': 0, 'created_by': 'System' } - default_endpoint = Endpoint.objects.create(**default_data) + Endpoint.objects.create(**default_data) if not settings.XRDP_ENABLED: return @@ -81,8 +81,8 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, unique=True, blank=True, verbose_name='Name')), - ('host', models.CharField(max_length=256, verbose_name='Host')), + ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('host', models.CharField(max_length=256, verbose_name='Host', blank=True)), ('https_port', common.fields.model.PortField(default=443, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTPS Port')), ('http_port', common.fields.model.PortField(default=80, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='HTTP Port')), ('ssh_port', common.fields.model.PortField(default=2222, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='SSH Port')), From 516cb05d69a9e59a4de72909987f6dfb251b38d0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 19 Apr 2022 11:30:07 +0800 Subject: [PATCH 042/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/cmd_filter.py | 14 +- apps/locale/ja/LC_MESSAGES/django.po | 193 ++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.po | 199 +++++++++++++------------- 3 files changed, 198 insertions(+), 208 deletions(-) diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index a57fab72a..9a33dd6fa 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -31,24 +31,24 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer): class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): - type_display = serializers.ReadOnlyField(source='get_type_display') - action_display = serializers.ReadOnlyField(source='get_action_display') + type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) + action_display = serializers.ReadOnlyField(source='get_action_display', label=_("Action display")) class Meta: model = CommandFilterRule fields_mini = ['id'] fields_small = fields_mini + [ - 'type', 'type_display', 'content', 'ignore_case', 'pattern', 'priority', - 'action', 'action_display', 'reviewers', - 'date_created', 'date_updated', - 'comment', 'created_by', + 'type', 'type_display', 'content', 'ignore_case', 'pattern', + 'priority', 'action', 'action_display', 'reviewers', + 'date_created', 'date_updated', 'comment', 'created_by', ] fields_fk = ['filter'] fields = fields_small + fields_fk extra_kwargs = { 'date_created': {'label': _("Date created")}, 'date_updated': {'label': _("Date updated")}, - 'action_display': {'label': _("Action display")} + 'action_display': {'label': _("Action display")}, + 'pattern': {'label': _("Pattern")} } def __init__(self, *args, **kwargs): diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index b7026603b..40ee2d43c 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-13 20:35+0800\n" +"POT-Creation-Date: 2022-04-19 11:24+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -33,7 +33,7 @@ msgstr "Acls" #: 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:661 -#: xpack/plugins/cloud/models.py:28 +#: xpack/plugins/cloud/models.py:27 msgid "Name" msgstr "名前" @@ -66,7 +66,7 @@ msgstr "アクティブ" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 +#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "コメント" @@ -92,8 +92,8 @@ msgstr "ログイン確認" #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 -#: users/models/user.py:917 users/serializers/group.py:19 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:883 +#: users/models/user.py:914 users/serializers/group.py:19 msgid "User" msgstr "ユーザー" @@ -134,7 +134,7 @@ msgstr "システムユーザー" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/cloud/models.py:222 msgid "Asset" msgstr "資産" @@ -318,8 +318,8 @@ msgstr "タイプ" msgid "Domain" msgstr "ドメイン" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:59 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 +#: xpack/plugins/cloud/serializers/account.py:58 msgid "Attrs" msgstr "ツールバーの" @@ -327,7 +327,7 @@ msgstr "ツールバーの" msgid "Can match application" msgstr "アプリケーションを一致させることができます" -#: applications/models/application.py:306 +#: applications/models/application.py:305 msgid "Application user" msgstr "アプリケーションユーザー" @@ -340,8 +340,8 @@ msgstr "カテゴリ表示" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/system_user.py:27 audits/serializers.py:29 -#: perms/serializers/application/permission.py:19 +#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:27 +#: audits/serializers.py:29 perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 #: tickets/serializers/ticket/ticket.py:173 @@ -353,17 +353,17 @@ msgstr "タイプ表示" #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 assets/models/label.py:25 #: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 -#: assets/serializers/cmd_filter.py:49 common/db/models.py:113 +#: assets/serializers/cmd_filter.py:48 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:918 -#: xpack/plugins/cloud/models.py:125 +#: users/models/group.py:18 users/models/user.py:915 +#: xpack/plugins/cloud/models.py:124 msgid "Date created" msgstr "作成された日付" #: applications/serializers/application.py:104 assets/models/base.py:182 #: assets/models/gathered_user.py:20 assets/serializers/account.py:21 -#: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:50 +#: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 #: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 msgid "Date updated" @@ -572,7 +572,7 @@ msgstr "ホスト名生" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" @@ -612,7 +612,7 @@ msgstr "ラベル" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "によって作成された" @@ -716,7 +716,7 @@ msgstr "トリガーモード" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:179 +#: xpack/plugins/cloud/models.py:178 msgid "Reason" msgstr "理由" @@ -744,7 +744,7 @@ msgstr "OK" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:30 msgid "Failed" msgstr "失敗しました" @@ -815,7 +815,7 @@ msgid "Default" msgstr "デフォルト" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:903 +#: users/models/user.py:900 msgid "System" msgstr "システム" @@ -954,7 +954,7 @@ msgid "Parent key" msgstr "親キー" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -1126,10 +1126,14 @@ msgstr "キーパスワード" msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" -#: assets/serializers/cmd_filter.py:51 +#: assets/serializers/cmd_filter.py:35 assets/serializers/cmd_filter.py:50 msgid "Action display" msgstr "アクション表示" +#: assets/serializers/cmd_filter.py:51 ops/models/adhoc.py:155 +msgid "Pattern" +msgstr "パターン" + #: assets/serializers/domain.py:13 assets/serializers/label.py:12 #: assets/serializers/system_user.py:59 #: perms/serializers/asset/permission.py:49 @@ -1482,8 +1486,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 -#: xpack/plugins/cloud/models.py:227 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 +#: xpack/plugins/cloud/models.py:226 msgid "Status" msgstr "ステータス" @@ -1520,7 +1524,7 @@ msgid "Hosts display" msgstr "ホスト表示" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:173 +#: xpack/plugins/cloud/models.py:172 msgid "Result" msgstr "結果" @@ -2177,7 +2181,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:298 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:299 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2638,11 +2642,11 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:297 +#: jumpserver/conf.py:298 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:299 +#: jumpserver/conf.py:300 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -2765,10 +2769,6 @@ msgstr "タスクモニターを表示できます" msgid "Tasks" msgstr "タスク" -#: ops/models/adhoc.py:155 -msgid "Pattern" -msgstr "パターン" - #: ops/models/adhoc.py:156 msgid "Options" msgstr "オプション" @@ -3106,15 +3106,15 @@ msgstr "質問があったら、管理者に連絡して下さい" msgid "My applications" msgstr "私のアプリケーション" -#: rbac/api/role.py:32 +#: rbac/api/role.py:33 msgid "Internal role, can't be destroy" msgstr "内部の役割は、破壊することはできません" -#: rbac/api/role.py:36 +#: rbac/api/role.py:37 msgid "The role has been bound to users, can't be destroy" msgstr "ロールはユーザーにバインドされており、破壊することはできません" -#: rbac/api/role.py:43 +#: rbac/api/role.py:44 msgid "Internal role, can't be update" msgstr "内部ロール、更新できません" @@ -3190,11 +3190,11 @@ msgstr "権限" msgid "Built-in" msgstr "内蔵" -#: rbac/models/role.py:130 +#: rbac/models/role.py:144 msgid "System role" msgstr "システムの役割" -#: rbac/models/role.py:138 +#: rbac/models/role.py:152 msgid "Organization role" msgstr "組織の役割" @@ -3202,22 +3202,22 @@ msgstr "組織の役割" msgid "Role binding" msgstr "ロールバインディング" -#: rbac/models/rolebinding.py:128 +#: rbac/models/rolebinding.py:150 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "" "ユーザーの最後のロールは削除できません。ユーザーを組織から削除できます。" -#: rbac/models/rolebinding.py:135 +#: rbac/models/rolebinding.py:157 msgid "Organization role binding" msgstr "組織の役割バインディング" -#: rbac/models/rolebinding.py:150 +#: rbac/models/rolebinding.py:172 msgid "System role binding" msgstr "システムロールバインディング" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:126 +#: rbac/serializers/permission.py:26 users/serializers/profile.py:127 msgid "Perms" msgstr "パーマ" @@ -4541,11 +4541,11 @@ msgstr "" "WindowsはクライアントをダウンロードしてSSH資産に接続する必要があり、macOSシス" "テムは独自のTerminalを採用している。" -#: templates/resource_download.html:51 +#: templates/resource_download.html:53 msgid "Windows Remote application publisher tools" msgstr "Windowsリモートアプリケーション発行者ツール" -#: templates/resource_download.html:52 +#: templates/resource_download.html:54 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -4557,7 +4557,7 @@ msgstr "" msgid "Filters" msgstr "フィルター" -#: terminal/api/endpoint.py:65 +#: terminal/api/endpoint.py:63 msgid "Not found protocol query params" msgstr "" @@ -4953,7 +4953,7 @@ msgstr "バケット" msgid "Secret key" msgstr "秘密キー" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 msgid "Region" msgstr "リージョン" @@ -5546,7 +5546,7 @@ msgid "Public key should not be the same as your old one." msgstr "公開鍵は古いものと同じであってはなりません。" #: users/forms/profile.py:149 users/serializers/profile.py:95 -#: users/serializers/profile.py:176 users/serializers/profile.py:203 +#: users/serializers/profile.py:178 users/serializers/profile.py:205 msgid "Not a valid ssh public key" msgstr "有効なssh公開鍵ではありません" @@ -5590,27 +5590,27 @@ msgstr "最終更新日パスワード" msgid "Need update password" msgstr "更新パスワードが必要" -#: users/models/user.py:888 +#: users/models/user.py:885 msgid "Can invite user" msgstr "ユーザーを招待できます" -#: users/models/user.py:889 +#: users/models/user.py:886 msgid "Can remove user" msgstr "ユーザーを削除できます" -#: users/models/user.py:890 +#: users/models/user.py:887 msgid "Can match user" msgstr "ユーザーに一致できます" -#: users/models/user.py:899 +#: users/models/user.py:896 msgid "Administrator" msgstr "管理者" -#: users/models/user.py:902 +#: users/models/user.py:899 msgid "Administrator is the super user of system" msgstr "管理者はシステムのスーパーユーザーです" -#: users/models/user.py:927 +#: users/models/user.py:924 msgid "User password history" msgstr "ユーザーパスワード履歴" @@ -5649,7 +5649,7 @@ msgstr "MFAのリセット" msgid "The old password is incorrect" msgstr "古いパスワードが正しくありません" -#: users/serializers/profile.py:36 users/serializers/profile.py:190 +#: users/serializers/profile.py:36 users/serializers/profile.py:192 msgid "Password does not match security rules" msgstr "パスワードがセキュリティルールと一致しない" @@ -5661,7 +5661,7 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:142 users/serializers/user.py:140 +#: users/serializers/profile.py:144 users/serializers/user.py:140 msgid "Is first login" msgstr "最初のログインです" @@ -6225,62 +6225,58 @@ msgid "Baidu Cloud" msgstr "百度雲" #: xpack/plugins/cloud/const.py:15 -msgid "JD Cloud" -msgstr "京東雲" - -#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "テンセント雲" -#: xpack/plugins/cloud/const.py:17 +#: xpack/plugins/cloud/const.py:16 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:18 msgid "Huawei Private Cloud" msgstr "華為私有雲" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:19 msgid "Qingyun Private Cloud" msgstr "青雲私有雲" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:20 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:22 +#: xpack/plugins/cloud/const.py:21 msgid "Google Cloud Platform" msgstr "谷歌雲" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:25 msgid "Instance name" msgstr "インスタンス名" -#: xpack/plugins/cloud/const.py:27 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name and Partial IP" msgstr "インスタンス名と部分IP" -#: xpack/plugins/cloud/const.py:32 +#: xpack/plugins/cloud/const.py:31 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:35 msgid "Unsync" msgstr "同期していません" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:36 msgid "New Sync" msgstr "新しい同期" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:37 msgid "Synced" msgstr "同期済み" -#: xpack/plugins/cloud/const.py:39 +#: xpack/plugins/cloud/const.py:38 msgid "Released" msgstr "リリース済み" @@ -6288,79 +6284,79 @@ msgstr "リリース済み" msgid "Cloud center" msgstr "クラウドセンター" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:29 msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:34 +#: xpack/plugins/cloud/models.py:33 msgid "Validity" msgstr "有効性" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:38 msgid "Cloud account" msgstr "クラウドアカウント" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:40 msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "アカウント" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "リージョン" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:90 msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix adminユーザー" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows管理者" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "常に更新" -#: xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/models.py:118 msgid "Date last sync" msgstr "最終同期日" -#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 msgid "Sync instance task" msgstr "インスタンスの同期タスク" -#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 +#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 msgid "Date sync" msgstr "日付の同期" -#: xpack/plugins/cloud/models.py:186 +#: xpack/plugins/cloud/models.py:185 msgid "Sync instance task execution" msgstr "インスタンスタスクの同期実行" -#: xpack/plugins/cloud/models.py:210 +#: xpack/plugins/cloud/models.py:209 msgid "Sync task" msgstr "同期タスク" -#: xpack/plugins/cloud/models.py:214 +#: xpack/plugins/cloud/models.py:213 msgid "Sync instance task history" msgstr "インスタンスタスク履歴の同期" -#: xpack/plugins/cloud/models.py:217 +#: xpack/plugins/cloud/models.py:216 msgid "Instance" msgstr "インスタンス" -#: xpack/plugins/cloud/models.py:234 +#: xpack/plugins/cloud/models.py:233 msgid "Sync instance detail" msgstr "同期インスタンスの詳細" @@ -6457,13 +6453,11 @@ msgid "South America (São Paulo)" msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "華南-広州" @@ -6485,7 +6479,6 @@ msgid "CN North-Baoding" msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "華東-上海" @@ -6550,15 +6543,11 @@ msgstr "華北-ウランチャブ一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "華南-広州-友好ユーザー環境" -#: xpack/plugins/cloud/providers/jdcloud.py:128 -msgid "CN East-Suqian" -msgstr "華東-宿遷" - -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Validity display" msgstr "有効表示" -#: xpack/plugins/cloud/serializers/account.py:61 +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Provider display" msgstr "プロバイダ表示" @@ -6722,6 +6711,12 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#~ msgid "JD Cloud" +#~ msgstr "京東雲" + +#~ msgid "CN East-Suqian" +#~ msgstr "華東-宿遷" + #~ msgid "Inherit" #~ msgstr "継承" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 5682b29ff..a7387c76f 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-13 20:35+0800\n" +"POT-Creation-Date: 2022-04-19 11:24+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -32,7 +32,7 @@ msgstr "访问控制" #: 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:661 -#: xpack/plugins/cloud/models.py:28 +#: xpack/plugins/cloud/models.py:27 msgid "Name" msgstr "名称" @@ -65,7 +65,7 @@ msgstr "激活中" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 +#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" @@ -91,8 +91,8 @@ msgstr "登录复核" #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:886 -#: users/models/user.py:917 users/serializers/group.py:19 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:883 +#: users/models/user.py:914 users/serializers/group.py:19 msgid "User" msgstr "用户" @@ -133,7 +133,7 @@ msgstr "系统用户" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/cloud/models.py:222 msgid "Asset" msgstr "资产" @@ -313,8 +313,8 @@ msgstr "类型" msgid "Domain" msgstr "网域" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:59 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 +#: xpack/plugins/cloud/serializers/account.py:58 msgid "Attrs" msgstr "属性" @@ -322,7 +322,7 @@ msgstr "属性" msgid "Can match application" msgstr "匹配应用" -#: applications/models/application.py:306 +#: applications/models/application.py:305 msgid "Application user" msgstr "应用用户" @@ -335,8 +335,8 @@ msgstr "类别名称" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/system_user.py:27 audits/serializers.py:29 -#: perms/serializers/application/permission.py:19 +#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:27 +#: audits/serializers.py:29 perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 #: tickets/serializers/ticket/ticket.py:173 @@ -348,17 +348,17 @@ msgstr "类型名称" #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 assets/models/label.py:25 #: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 -#: assets/serializers/cmd_filter.py:49 common/db/models.py:113 +#: assets/serializers/cmd_filter.py:48 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:918 -#: xpack/plugins/cloud/models.py:125 +#: users/models/group.py:18 users/models/user.py:915 +#: xpack/plugins/cloud/models.py:124 msgid "Date created" msgstr "创建日期" #: applications/serializers/application.py:104 assets/models/base.py:182 #: assets/models/gathered_user.py:20 assets/serializers/account.py:21 -#: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:50 +#: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 #: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 msgid "Date updated" @@ -567,7 +567,7 @@ msgstr "主机名原始" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" @@ -607,7 +607,7 @@ msgstr "标签管理" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -711,7 +711,7 @@ msgstr "触发模式" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:179 +#: xpack/plugins/cloud/models.py:178 msgid "Reason" msgstr "原因" @@ -739,7 +739,7 @@ msgstr "成功" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:30 msgid "Failed" msgstr "失败" @@ -810,7 +810,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:903 +#: users/models/user.py:900 msgid "System" msgstr "系统" @@ -949,7 +949,7 @@ msgid "Parent key" msgstr "ssh私钥" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -1118,9 +1118,13 @@ msgstr "密钥密码" msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" -#: assets/serializers/cmd_filter.py:51 +#: assets/serializers/cmd_filter.py:35 assets/serializers/cmd_filter.py:50 msgid "Action display" -msgstr "动作" +msgstr "动作名称" + +#: assets/serializers/cmd_filter.py:51 ops/models/adhoc.py:155 +msgid "Pattern" +msgstr "模式" #: assets/serializers/domain.py:13 assets/serializers/label.py:12 #: assets/serializers/system_user.py:59 @@ -1470,8 +1474,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 -#: xpack/plugins/cloud/models.py:227 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 +#: xpack/plugins/cloud/models.py:226 msgid "Status" msgstr "状态" @@ -1508,7 +1512,7 @@ msgid "Hosts display" msgstr "主机名称" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:173 +#: xpack/plugins/cloud/models.py:172 msgid "Result" msgstr "结果" @@ -2156,7 +2160,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:298 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:299 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2608,11 +2612,11 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:297 +#: jumpserver/conf.py:298 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:299 +#: jumpserver/conf.py:300 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -2730,10 +2734,6 @@ msgstr "可以查看任务监控" msgid "Tasks" msgstr "任务" -#: ops/models/adhoc.py:155 -msgid "Pattern" -msgstr "模式" - #: ops/models/adhoc.py:156 msgid "Options" msgstr "选项" @@ -3069,15 +3069,15 @@ msgstr "如果有疑问或需求,请联系系统管理员" msgid "My applications" msgstr "我的应用" -#: rbac/api/role.py:32 +#: rbac/api/role.py:33 msgid "Internal role, can't be destroy" msgstr "内部角色,不能删除" -#: rbac/api/role.py:36 +#: rbac/api/role.py:37 msgid "The role has been bound to users, can't be destroy" msgstr "角色已绑定用户,不能删除" -#: rbac/api/role.py:43 +#: rbac/api/role.py:44 msgid "Internal role, can't be update" msgstr "内部角色,不能更新" @@ -3153,11 +3153,11 @@ msgstr "授权" msgid "Built-in" msgstr "内置" -#: rbac/models/role.py:130 +#: rbac/models/role.py:144 msgid "System role" msgstr "系统角色" -#: rbac/models/role.py:138 +#: rbac/models/role.py:152 msgid "Organization role" msgstr "组织角色" @@ -3165,21 +3165,21 @@ msgstr "组织角色" msgid "Role binding" msgstr "角色绑定" -#: rbac/models/rolebinding.py:128 +#: rbac/models/rolebinding.py:150 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "用户最后一个角色,不能删除,你可以将用户从组织移除" -#: rbac/models/rolebinding.py:135 +#: rbac/models/rolebinding.py:157 msgid "Organization role binding" msgstr "组织角色绑定" -#: rbac/models/rolebinding.py:150 +#: rbac/models/rolebinding.py:172 msgid "System role binding" msgstr "系统角色绑定" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:126 +#: rbac/serializers/permission.py:26 users/serializers/profile.py:127 msgid "Perms" msgstr "权限" @@ -4446,8 +4446,8 @@ msgid "" "JumpServer Client, currently used to launch the client, now only support " "launch RDP SSH client, The Telnet client will next" msgstr "" -"JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP SSH 客户" -"端,Telnet 会在未来支持" +"JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP SSH " +"客户端,Telnet 会在未来支持" #: templates/resource_download.html:30 msgid "Microsoft" @@ -4469,11 +4469,11 @@ msgid "" "system uses its own terminal" msgstr "Windows 需要下载客户端来连接SSH资产,macOS系统采用自带的Terminal" -#: templates/resource_download.html:51 +#: templates/resource_download.html:53 msgid "Windows Remote application publisher tools" msgstr "Windows 远程应用发布服务器工具" -#: templates/resource_download.html:52 +#: templates/resource_download.html:54 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -4483,7 +4483,7 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远 msgid "Filters" msgstr "过滤" -#: terminal/api/endpoint.py:65 +#: terminal/api/endpoint.py:63 msgid "Not found protocol query params" msgstr "" @@ -4879,7 +4879,7 @@ msgstr "桶名称" msgid "Secret key" msgstr "密钥" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 msgid "Region" msgstr "地域" @@ -5468,7 +5468,7 @@ msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" #: users/forms/profile.py:149 users/serializers/profile.py:95 -#: users/serializers/profile.py:176 users/serializers/profile.py:203 +#: users/serializers/profile.py:178 users/serializers/profile.py:205 msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" @@ -5512,27 +5512,27 @@ msgstr "最后更新密码日期" msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:888 +#: users/models/user.py:885 msgid "Can invite user" msgstr "可以邀请用户" -#: users/models/user.py:889 +#: users/models/user.py:886 msgid "Can remove user" msgstr "可以移除用户" -#: users/models/user.py:890 +#: users/models/user.py:887 msgid "Can match user" msgstr "可以匹配用户" -#: users/models/user.py:899 +#: users/models/user.py:896 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:902 +#: users/models/user.py:899 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models/user.py:927 +#: users/models/user.py:924 msgid "User password history" msgstr "用户密码历史" @@ -5571,7 +5571,7 @@ msgstr "重置 MFA" msgid "The old password is incorrect" msgstr "旧密码错误" -#: users/serializers/profile.py:36 users/serializers/profile.py:190 +#: users/serializers/profile.py:36 users/serializers/profile.py:192 msgid "Password does not match security rules" msgstr "密码不满足安全规则" @@ -5583,7 +5583,7 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:142 users/serializers/user.py:140 +#: users/serializers/profile.py:144 users/serializers/user.py:140 msgid "Is first login" msgstr "首次登录" @@ -6134,62 +6134,58 @@ msgid "Baidu Cloud" msgstr "百度云" #: xpack/plugins/cloud/const.py:15 -msgid "JD Cloud" -msgstr "京东云" - -#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "腾讯云" -#: xpack/plugins/cloud/const.py:17 +#: xpack/plugins/cloud/const.py:16 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:18 msgid "Huawei Private Cloud" msgstr "华为私有云" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:19 msgid "Qingyun Private Cloud" msgstr "青云私有云" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:20 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:22 +#: xpack/plugins/cloud/const.py:21 msgid "Google Cloud Platform" msgstr "谷歌云" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:25 msgid "Instance name" msgstr "实例名称" -#: xpack/plugins/cloud/const.py:27 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/const.py:32 +#: xpack/plugins/cloud/const.py:31 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:35 msgid "Unsync" msgstr "未同步" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:36 msgid "New Sync" msgstr "新同步" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:37 msgid "Synced" msgstr "已同步" -#: xpack/plugins/cloud/const.py:39 +#: xpack/plugins/cloud/const.py:38 msgid "Released" msgstr "已释放" @@ -6197,79 +6193,79 @@ msgstr "已释放" msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:29 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:34 +#: xpack/plugins/cloud/models.py:33 msgid "Validity" msgstr "有效" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:38 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:40 msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "账号" -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:90 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix 管理员" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows 管理员" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/models.py:118 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 +#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:186 +#: xpack/plugins/cloud/models.py:185 msgid "Sync instance task execution" msgstr "同步实例任务执行" -#: xpack/plugins/cloud/models.py:210 +#: xpack/plugins/cloud/models.py:209 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:214 +#: xpack/plugins/cloud/models.py:213 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:217 +#: xpack/plugins/cloud/models.py:216 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/models.py:234 +#: xpack/plugins/cloud/models.py:233 msgid "Sync instance detail" msgstr "同步实例详情" @@ -6366,13 +6362,11 @@ msgid "South America (São Paulo)" msgstr "南美洲(圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "华南-广州" @@ -6394,7 +6388,6 @@ msgid "CN North-Baoding" msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "华东-上海" @@ -6459,15 +6452,11 @@ msgstr "华北-乌兰察布一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "华南-广州-友好用户环境" -#: xpack/plugins/cloud/providers/jdcloud.py:128 -msgid "CN East-Suqian" -msgstr "华东-宿迁" - -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Validity display" msgstr "有效性显示" -#: xpack/plugins/cloud/serializers/account.py:61 +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Provider display" msgstr "服务商显示" @@ -6630,6 +6619,12 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "JD Cloud" +#~ msgstr "京东云" + +#~ msgid "CN East-Suqian" +#~ msgstr "华东-宿迁" + #~ msgid "Inherit" #~ msgstr "继承" From be2708f83de684e8079625849b56bc9b81318b18 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 19 Apr 2022 14:26:17 +0800 Subject: [PATCH 043/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dajax=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=90=BA=E5=B8=A6csrftoken=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/static/js/jumpserver.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index b930154ff..070c811f7 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -125,8 +125,9 @@ function csrfSafeMethod(method) { } function setAjaxCSRFToken() { - var csrftoken = getCookie('csrftoken'); - var sessionid = getCookie('sessionid'); + const prefix = getCookie('SESSION_COOKIE_NAME_PREFIX', '') + var csrftoken = getCookie(`${prefix}csrftoken`); + var sessionid = getCookie(`${prefix}sessionid`); $.ajaxSetup({ beforeSend: function (xhr, settings) { From f8fade4cf2d1e1f5b773dce7e790b012c41e6f06 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 19 Apr 2022 16:01:05 +0800 Subject: [PATCH 044/258] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=20KoKo=20SSH=20Client=20=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings/custom.py | 1 + apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 158 ++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 161 ++++++++++++-------------- apps/settings/api/public.py | 1 + apps/settings/serializers/terminal.py | 3 +- 8 files changed, 159 insertions(+), 174 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index ec35ae6e5..fa4a211d4 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -317,6 +317,7 @@ class Config(dict): 'TERMINAL_RDP_ADDR': '', # 保留(Luna还在用) 'TERMINAL_MAGNUS_ENABLED': True, + 'TERMINAL_KOKO_SSH_ENABLED': True, # 保留(Luna还在用) 'XRDP_ENABLED': True, diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index cafdc1a59..ecd710a2e 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -140,6 +140,7 @@ CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = CONFIG.CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS XRDP_ENABLED = CONFIG.XRDP_ENABLED TERMINAL_MAGNUS_ENABLED = CONFIG.TERMINAL_MAGNUS_ENABLED +TERMINAL_KOKO_SSH_ENABLED = CONFIG.TERMINAL_KOKO_SSH_ENABLED # SMS enabled SMS_ENABLED = CONFIG.SMS_ENABLED diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index c5ec670bb..e13ed4cfb 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54be66877253eed7bec1db706604af83a48f1c5fbc95eef1132c7f880fef154a -size 125598 +oid sha256:4e6962699271d0f5402223321e65211f1c7ad0b7a9b43524f3a0fac7ea2541d9 +size 125623 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 40ee2d43c..5377b3dda 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-19 11:24+0800\n" +"POT-Creation-Date: 2022-04-19 15:57+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -33,7 +33,7 @@ msgstr "Acls" #: 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:661 -#: xpack/plugins/cloud/models.py:27 +#: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名前" @@ -66,7 +66,7 @@ msgstr "アクティブ" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "コメント" @@ -88,7 +88,7 @@ msgstr "ログイン確認" #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 #: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:107 rbac/models/rolebinding.py:40 +#: rbac/builtin.py:110 rbac/models/rolebinding.py:40 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -134,7 +134,7 @@ msgstr "システムユーザー" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:222 +#: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "資産" @@ -318,8 +318,8 @@ msgstr "タイプ" msgid "Domain" msgstr "ドメイン" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 -#: xpack/plugins/cloud/serializers/account.py:58 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "ツールバーの" @@ -357,7 +357,7 @@ msgstr "タイプ表示" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:915 -#: xpack/plugins/cloud/models.py:124 +#: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "作成された日付" @@ -572,7 +572,7 @@ msgstr "ホスト名生" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" @@ -612,7 +612,7 @@ msgstr "ラベル" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "によって作成された" @@ -716,7 +716,7 @@ msgstr "トリガーモード" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:178 +#: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "理由" @@ -744,7 +744,7 @@ msgstr "OK" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失敗しました" @@ -954,7 +954,7 @@ msgid "Parent key" msgstr "親キー" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -1486,8 +1486,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 -#: xpack/plugins/cloud/models.py:226 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "ステータス" @@ -1524,7 +1524,7 @@ msgid "Hosts display" msgstr "ホスト表示" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:172 +#: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "結果" @@ -3126,27 +3126,27 @@ msgstr "{} 少なくとも1つのシステムロール" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:98 +#: rbac/builtin.py:101 msgid "SystemAdmin" msgstr "システム管理者" -#: rbac/builtin.py:101 +#: rbac/builtin.py:104 msgid "SystemAuditor" msgstr "システム監査人" -#: rbac/builtin.py:104 +#: rbac/builtin.py:107 msgid "SystemComponent" msgstr "システムコンポーネント" -#: rbac/builtin.py:110 +#: rbac/builtin.py:113 msgid "OrgAdmin" msgstr "組織管理者" -#: rbac/builtin.py:113 +#: rbac/builtin.py:116 msgid "OrgAuditor" msgstr "監査員を組織する" -#: rbac/builtin.py:116 +#: rbac/builtin.py:119 msgid "OrgUser" msgstr "組織ユーザー" @@ -4225,10 +4225,10 @@ msgstr "Telnetログインregex" #: settings/serializers/terminal.py:33 msgid "" -"The login success message varies with devices. if you cannot log in to the " -"device through Telnet, set this parameter" +"Tips: The login success message varies with devices. if you cannot log in to " +"the device through Telnet, set this parameter" msgstr "" -"ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバイスにロ" +"ヒント: ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバイスにロ" "グインできない場合は、このパラメーターを設定します。" #: settings/serializers/terminal.py:36 @@ -4239,6 +4239,10 @@ msgstr "属性マップの有効化" msgid "Enable XRDP" msgstr "XRDPの有効化" +#: settings/serializers/terminal.py:38 +msgid "Enable KoKo SSH" +msgstr "KoKo SSHの有効化" + #: settings/utils/ldap.py:417 msgid "ldap:// or ldaps:// protocol is used." msgstr "ldap:// または ldaps:// プロトコルが使用されます。" @@ -4953,7 +4957,7 @@ msgstr "バケット" msgid "Secret key" msgstr "秘密キー" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "リージョン" @@ -6225,58 +6229,62 @@ msgid "Baidu Cloud" msgstr "百度雲" #: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "京東雲" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "テンセント雲" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "華為私有雲" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青雲私有雲" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌雲" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "インスタンス名" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "インスタンス名と部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "同期していません" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新しい同期" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "同期済み" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "リリース済み" @@ -6284,79 +6292,79 @@ msgstr "リリース済み" msgid "Cloud center" msgstr "クラウドセンター" -#: xpack/plugins/cloud/models.py:29 +#: xpack/plugins/cloud/models.py:30 msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/models.py:34 msgid "Validity" msgstr "有効性" -#: xpack/plugins/cloud/models.py:38 +#: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "クラウドアカウント" -#: xpack/plugins/cloud/models.py:40 +#: xpack/plugins/cloud/models.py:41 msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "アカウント" -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "リージョン" -#: xpack/plugins/cloud/models.py:90 +#: xpack/plugins/cloud/models.py:91 msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix adminユーザー" -#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows管理者" -#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "常に更新" -#: xpack/plugins/cloud/models.py:118 +#: xpack/plugins/cloud/models.py:119 msgid "Date last sync" msgstr "最終同期日" -#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 msgid "Sync instance task" msgstr "インスタンスの同期タスク" -#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 msgid "Date sync" msgstr "日付の同期" -#: xpack/plugins/cloud/models.py:185 +#: xpack/plugins/cloud/models.py:186 msgid "Sync instance task execution" msgstr "インスタンスタスクの同期実行" -#: xpack/plugins/cloud/models.py:209 +#: xpack/plugins/cloud/models.py:210 msgid "Sync task" msgstr "同期タスク" -#: xpack/plugins/cloud/models.py:213 +#: xpack/plugins/cloud/models.py:214 msgid "Sync instance task history" msgstr "インスタンスタスク履歴の同期" -#: xpack/plugins/cloud/models.py:216 +#: xpack/plugins/cloud/models.py:217 msgid "Instance" msgstr "インスタンス" -#: xpack/plugins/cloud/models.py:233 +#: xpack/plugins/cloud/models.py:234 msgid "Sync instance detail" msgstr "同期インスタンスの詳細" @@ -6453,11 +6461,13 @@ msgid "South America (São Paulo)" msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "華南-広州" @@ -6479,6 +6489,7 @@ msgid "CN North-Baoding" msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "華東-上海" @@ -6543,11 +6554,15 @@ msgstr "華北-ウランチャブ一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "華南-広州-友好ユーザー環境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "華東-宿遷" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有効表示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "プロバイダ表示" @@ -6710,26 +6725,3 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" - -#~ msgid "JD Cloud" -#~ msgstr "京東雲" - -#~ msgid "CN East-Suqian" -#~ msgstr "華東-宿遷" - -#~ msgid "Inherit" -#~ msgstr "継承" - -#~ msgid "Include" -#~ msgstr "含める" - -#~ msgid "Exclude" -#~ msgstr "除外" - -#~ msgid "DatabaseApp" -#~ msgstr "データベースの適用" - -#, fuzzy -#~| msgid "Connection token" -#~ msgid "One time token" -#~ msgstr "接続トークン" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c77cc1659..c73d42f67 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa084dd92472110d4bea1674d1e9a96599f42f094aab92f8d34152fdf5726321 -size 103771 +oid sha256:3462a9a3eef8f372bf341f2066a33d85e1f01aca5a8fe506528a1cd0a37e98b4 +size 103951 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a7387c76f..77b7111be 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-19 11:24+0800\n" +"POT-Creation-Date: 2022-04-19 15:57+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -32,7 +32,7 @@ msgstr "访问控制" #: 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:661 -#: xpack/plugins/cloud/models.py:27 +#: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名称" @@ -65,7 +65,7 @@ msgstr "激活中" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" @@ -87,7 +87,7 @@ msgstr "登录复核" #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 #: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:107 rbac/models/rolebinding.py:40 +#: rbac/builtin.py:110 rbac/models/rolebinding.py:40 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -133,7 +133,7 @@ msgstr "系统用户" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:222 +#: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "资产" @@ -313,8 +313,8 @@ msgstr "类型" msgid "Domain" msgstr "网域" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 -#: xpack/plugins/cloud/serializers/account.py:58 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "属性" @@ -352,7 +352,7 @@ msgstr "类型名称" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:915 -#: xpack/plugins/cloud/models.py:124 +#: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "创建日期" @@ -567,7 +567,7 @@ msgstr "主机名原始" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" @@ -607,7 +607,7 @@ msgstr "标签管理" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -711,7 +711,7 @@ msgstr "触发模式" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:178 +#: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "原因" @@ -739,7 +739,7 @@ msgstr "成功" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失败" @@ -949,7 +949,7 @@ msgid "Parent key" msgstr "ssh私钥" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -1474,8 +1474,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 -#: xpack/plugins/cloud/models.py:226 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "状态" @@ -1512,7 +1512,7 @@ msgid "Hosts display" msgstr "主机名称" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:172 +#: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "结果" @@ -3089,27 +3089,27 @@ msgstr "{} 至少有一个系统角色" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:98 +#: rbac/builtin.py:101 msgid "SystemAdmin" msgstr "系统管理员" -#: rbac/builtin.py:101 +#: rbac/builtin.py:104 msgid "SystemAuditor" msgstr "系统审计员" -#: rbac/builtin.py:104 +#: rbac/builtin.py:107 msgid "SystemComponent" msgstr "系统组件" -#: rbac/builtin.py:110 +#: rbac/builtin.py:113 msgid "OrgAdmin" msgstr "组织管理员" -#: rbac/builtin.py:113 +#: rbac/builtin.py:116 msgid "OrgAuditor" msgstr "组织审计员" -#: rbac/builtin.py:116 +#: rbac/builtin.py:119 msgid "OrgUser" msgstr "组织用户" @@ -4165,9 +4165,9 @@ msgstr "Telnet 成功正则表达式" #: settings/serializers/terminal.py:33 msgid "" -"The login success message varies with devices. if you cannot log in to the " -"device through Telnet, set this parameter" -msgstr "不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" +"Tips: The login success message varies with devices. if you cannot log in to " +"the device through Telnet, set this parameter" +msgstr "提示: 不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" #: settings/serializers/terminal.py:36 msgid "Enable database proxy" @@ -4177,6 +4177,10 @@ msgstr "启用数据库组件" msgid "Enable XRDP" msgstr "启用 XRDP 服务" +#: settings/serializers/terminal.py:38 +msgid "Enable KoKo SSH" +msgstr "启用 KoKo SSH" + #: settings/utils/ldap.py:417 msgid "ldap:// or ldaps:// protocol is used." msgstr "使用 ldap:// 或 ldaps:// 协议" @@ -4879,7 +4883,7 @@ msgstr "桶名称" msgid "Secret key" msgstr "密钥" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "地域" @@ -6134,58 +6138,62 @@ msgid "Baidu Cloud" msgstr "百度云" #: xpack/plugins/cloud/const.py:15 +msgid "JD Cloud" +msgstr "京东云" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "腾讯云" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "华为私有云" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青云私有云" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌云" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "实例名称" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "未同步" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新同步" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "已同步" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "已释放" @@ -6193,79 +6201,79 @@ msgstr "已释放" msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:29 +#: xpack/plugins/cloud/models.py:30 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/models.py:34 msgid "Validity" msgstr "有效" -#: xpack/plugins/cloud/models.py:38 +#: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:40 +#: xpack/plugins/cloud/models.py:41 msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "账号" -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:90 +#: xpack/plugins/cloud/models.py:91 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix 管理员" -#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows 管理员" -#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:118 +#: xpack/plugins/cloud/models.py:119 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:185 +#: xpack/plugins/cloud/models.py:186 msgid "Sync instance task execution" msgstr "同步实例任务执行" -#: xpack/plugins/cloud/models.py:209 +#: xpack/plugins/cloud/models.py:210 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:213 +#: xpack/plugins/cloud/models.py:214 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:216 +#: xpack/plugins/cloud/models.py:217 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/models.py:233 +#: xpack/plugins/cloud/models.py:234 msgid "Sync instance detail" msgstr "同步实例详情" @@ -6362,11 +6370,13 @@ msgid "South America (São Paulo)" msgstr "南美洲(圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "华南-广州" @@ -6388,6 +6398,7 @@ msgid "CN North-Baoding" msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "华东-上海" @@ -6452,11 +6463,15 @@ msgstr "华北-乌兰察布一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "华南-广州-友好用户环境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +msgid "CN East-Suqian" +msgstr "华东-宿迁" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有效性显示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "服务商显示" @@ -6618,29 +6633,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" - -#~ msgid "JD Cloud" -#~ msgstr "京东云" - -#~ msgid "CN East-Suqian" -#~ msgstr "华东-宿迁" - -#~ msgid "Inherit" -#~ msgstr "继承" - -#~ msgid "Include" -#~ msgstr "包含" - -#~ msgid "Exclude" -#~ msgstr "不包含" - -#~ msgid "DatabaseApp" -#~ msgstr "数据库应用" - -#~ msgid "Database proxy MySQL protocol listen port" -#~ msgstr "MySQL 协议监听的端口" - -#, fuzzy -#~| msgid "Database proxy PostgreSQL port" -#~ msgid "Database proxy PostgreSQL listen port" -#~ msgstr "数据库组件 PostgreSQL 协议监听的端口" diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index 5efa54319..b9076618d 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -65,6 +65,7 @@ class PublicSettingApi(generics.RetrieveAPIView): # Terminal "XRDP_ENABLED": settings.XRDP_ENABLED, "TERMINAL_MAGNUS_ENABLED": settings.TERMINAL_MAGNUS_ENABLED, + "TERMINAL_KOKO_SSH_ENABLED": settings.TERMINAL_KOKO_SSH_ENABLED, # Announcement "ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED, "ANNOUNCEMENT": settings.ANNOUNCEMENT, diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index bf4b8d7a0..8cdb9e065 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -30,8 +30,9 @@ class TerminalSettingSerializer(serializers.Serializer): ) TERMINAL_TELNET_REGEX = serializers.CharField( allow_blank=True, max_length=1024, required=False, label=_('Telnet login regex'), - help_text=_("The login success message varies with devices. " + help_text=_("Tips: The login success message varies with devices. " "if you cannot log in to the device through Telnet, set this parameter") ) TERMINAL_MAGNUS_ENABLED = serializers.BooleanField(label=_("Enable database proxy")) XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP")) + TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField(label=_("Enable KoKo SSH")) From 3b9cb2a99c74d3e4abb71b0591f513220e4a9fa5 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 19 Apr 2022 16:25:29 +0800 Subject: [PATCH 045/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E4=B8=B4=E6=97=B6=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.po | 2 +- apps/locale/zh/LC_MESSAGES/django.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 5377b3dda..8c5147c50 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -1570,7 +1570,7 @@ msgstr "DingTalk" #: audits/signal_handlers.py:73 authentication/models.py:76 msgid "Temporary token" -msgstr "一時的なトークン" +msgstr "仮パスワード" #: audits/signal_handlers.py:107 msgid "User and Group" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 77b7111be..5160cbfcf 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1558,7 +1558,7 @@ msgstr "钉钉" #: audits/signal_handlers.py:73 authentication/models.py:76 msgid "Temporary token" -msgstr "临时 Token" +msgstr "临时密码" #: audits/signal_handlers.py:107 msgid "User and Group" From 500477fad17d4a02331f8407ed38be1d3338a465 Mon Sep 17 00:00:00 2001 From: halo Date: Tue, 19 Apr 2022 17:06:05 +0800 Subject: [PATCH 046/258] =?UTF-8?q?fix:=20ftp=E6=97=A5=E5=BF=97=E6=B8=85?= =?UTF-8?q?=E7=90=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/audits/tasks.py b/apps/audits/tasks.py index 171fbe633..10fb67f44 100644 --- a/apps/audits/tasks.py +++ b/apps/audits/tasks.py @@ -7,7 +7,7 @@ from celery import shared_task from ops.celery.decorator import ( register_as_period_task ) -from .models import UserLoginLog, OperateLog +from .models import UserLoginLog, OperateLog, FTPLog from common.utils import get_log_keep_day @@ -29,7 +29,7 @@ def clean_ftp_log_period(): now = timezone.now() days = get_log_keep_day('FTP_LOG_KEEP_DAYS') expired_day = now - datetime.timedelta(days=days) - OperateLog.objects.filter(datetime__lt=expired_day).delete() + FTPLog.objects.filter(datetime__lt=expired_day).delete() @register_as_period_task(interval=3600*24) From b4ac24ad6df81cf051e96a109e9c3083edfeab65 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 19 Apr 2022 16:44:11 +0800 Subject: [PATCH 047/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9endpoint/rule?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A0=91=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/tree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py index dfccafa8f..a585bdf5c 100644 --- a/apps/rbac/tree.py +++ b/apps/rbac/tree.py @@ -86,6 +86,8 @@ special_pid_mapper = { 'terminal.replaystorage': 'terminal_node', 'terminal.status': 'terminal_node', 'terminal.task': 'terminal_node', + 'terminal.endpoint': 'terminal_node', + 'terminal.endpointrule': 'terminal_node', 'audits.ftplog': 'terminal', 'perms.view_myassets': 'my_assets', 'perms.view_myapps': 'my_apps', From e4b0ab6a4585b1eef8db89518def30fb24d8a1d4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 19 Apr 2022 16:17:16 +0800 Subject: [PATCH 048/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E6=89=A7=E8=A1=8C=E5=8C=BA=E5=88=86=E7=BB=84=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset/user_permission/user_permission_nodes_with_assets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py b/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py index d65f08df6..5d3940c9f 100644 --- a/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py +++ b/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py @@ -65,7 +65,6 @@ class MyGrantedNodesWithAssetsAsTreeApi(SerializeToTreeNodeMixin, ListAPIView): all_assets = all_assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform') data.extend(self.serialize_assets(all_assets)) - @tmp_to_root_org() def list(self, request: Request, *args, **kwargs): """ 此算法依赖 UserGrantedMappingNode From 0addba7c149248167a0fc5fc447530257e2f7506 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 19 Apr 2022 16:20:09 +0800 Subject: [PATCH 049/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20command=20?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/asset/user_permission/common.py | 1 - apps/perms/api/system_user_permission.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/apps/perms/api/asset/user_permission/common.py b/apps/perms/api/asset/user_permission/common.py index 1594cedb9..86bff2123 100644 --- a/apps/perms/api/asset/user_permission/common.py +++ b/apps/perms/api/asset/user_permission/common.py @@ -141,7 +141,6 @@ class UserGrantedAssetSystemUsersForAdminApi(ListAPIView): return queryset_list -@method_decorator(tmp_to_root_org(), name='list') class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi): permission_classes = (IsValidUser,) diff --git a/apps/perms/api/system_user_permission.py b/apps/perms/api/system_user_permission.py index 48d440baa..6d7569192 100644 --- a/apps/perms/api/system_user_permission.py +++ b/apps/perms/api/system_user_permission.py @@ -1,14 +1,11 @@ from rest_framework import generics -from django.utils.decorators import method_decorator from assets.models import SystemUser from common.permissions import IsValidUser -from orgs.utils import tmp_to_root_org from perms.utils.asset.user_permission import get_user_all_asset_perm_ids from .. import serializers -@method_decorator(tmp_to_root_org(), name='list') class SystemUserPermission(generics.ListAPIView): permission_classes = (IsValidUser,) serializer_class = serializers.SystemUserSerializer From f026b86a204c7193e18f44695b876e15d9c09c09 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 19 Apr 2022 18:06:52 +0800 Subject: [PATCH 050/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=BB=84=E7=BB=87=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 80b938468..b69cd5726 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -455,7 +455,7 @@ class RoleMixin: if org is None: org = current_org if not org.is_root(): - queryset = current_org.get_members() + queryset = org.get_members() queryset = cls.filter_not_service_account(queryset) return queryset From 5f370c1c049aee1bf9b75f486aa50426a8964653 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 19 Apr 2022 19:10:36 +0800 Subject: [PATCH 051/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/access_key.py | 4 ++-- apps/authentication/api/temp_token.py | 7 +++++-- apps/common/validators.py | 2 +- apps/rbac/builtin.py | 3 ++- apps/rbac/const.py | 1 + 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/authentication/api/access_key.py b/apps/authentication/api/access_key.py index 0762d0de9..bbda04c02 100644 --- a/apps/authentication/api/access_key.py +++ b/apps/authentication/api/access_key.py @@ -2,14 +2,14 @@ # from rest_framework.viewsets import ModelViewSet - -from common.permissions import IsValidUser from .. import serializers +from rbac.permissions import RBACPermission class AccessKeyViewSet(ModelViewSet): serializer_class = serializers.AccessKeySerializer search_fields = ['^id', '^secret'] + permission_classes = [RBACPermission] def get_queryset(self): return self.request.user.access_keys.all() diff --git a/apps/authentication/api/temp_token.py b/apps/authentication/api/temp_token.py index a8fcc02af..6e640edd6 100644 --- a/apps/authentication/api/temp_token.py +++ b/apps/authentication/api/temp_token.py @@ -3,15 +3,18 @@ from rest_framework.response import Response from rest_framework.decorators import action from common.drf.api import JMSModelViewSet -from common.permissions import IsValidUser from ..models import TempToken from ..serializers import TempTokenSerializer +from rbac.permissions import RBACPermission class TempTokenViewSet(JMSModelViewSet): serializer_class = TempTokenSerializer - permission_classes = [IsValidUser] + permission_classes = [RBACPermission] http_method_names = ['post', 'get', 'options', 'patch'] + rbac_perms = { + 'expire': 'authentication.change_temptoken', + } def get_queryset(self): username = self.request.user.username diff --git a/apps/common/validators.py b/apps/common/validators.py index 352482a1b..4be90d855 100644 --- a/apps/common/validators.py +++ b/apps/common/validators.py @@ -42,7 +42,7 @@ class NoSpecialChars: class PhoneValidator: - pattern = re.compile(r"^1[356789]\d{9}$") + pattern = re.compile(r"^1[3456789]\d{9}$") message = _('The mobile phone number format is incorrect') def __call__(self, value): diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index a199c149c..c99181d4e 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -4,7 +4,8 @@ from .const import Scope, system_exclude_permissions, org_exclude_permissions system_user_perms = ( ('authentication', 'connectiontoken', 'add', 'connectiontoken'), - ('authentication', 'temptoken', 'add', 'temptoken'), + ('authentication', 'temptoken', 'add,change,view', 'temptoken'), + ('authentication', 'accesskey', '*', '*'), ('tickets', 'ticket', 'view', 'ticket'), ('orgs', 'organization', 'view', 'rootorg'), ) diff --git a/apps/rbac/const.py b/apps/rbac/const.py index 5d6ae08ec..d9b80b78a 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -25,6 +25,7 @@ exclude_permissions = ( ('authentication', 'connectiontoken', 'change,delete', 'connectiontoken'), ('authentication', 'ssotoken', '*', '*'), ('authentication', 'superconnectiontoken', 'change,delete', 'superconnectiontoken'), + ('authentication', 'temptoken', 'delete', 'temptoken'), ('users', 'userpasswordhistory', '*', '*'), ('applications', 'applicationuser', '*', '*'), ('applications', 'historicalaccount', '*', '*'), From 57969a4e23ed4fe27377deb3254a40fc1d799c45 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 19 Apr 2022 19:44:07 +0800 Subject: [PATCH 052/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=8E=B7?= =?UTF-8?q?=E5=8F=96smart=20endpoint=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/endpoint.py | 2 +- apps/terminal/models/endpoint.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/endpoint.py index d612db4a8..494dacbe8 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/endpoint.py @@ -2,7 +2,7 @@ from rest_framework.decorators import action from rest_framework.response import Response from rest_framework import status from common.drf.api import JMSBulkModelViewSet -from common.utils import get_object_or_none +from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from assets.models import Asset from orgs.utils import tmp_to_root_org diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/endpoint.py index 39f275f9a..71b79c0e3 100644 --- a/apps/terminal/models/endpoint.py +++ b/apps/terminal/models/endpoint.py @@ -31,8 +31,11 @@ class Endpoint(JMSModel): def get_port(self, protocol): return getattr(self, f'{protocol}_port', 0) + def is_default(self): + return self.id == self.default_id + def delete(self, using=None, keep_parents=False): - if self.id == self.default_id: + if self.is_default(): return return super().delete(using, keep_parents) @@ -78,6 +81,8 @@ class EndpointRule(JMSModel): continue if not endpoint_rule.endpoint: continue + if endpoint_rule.endpoint.is_default(): + return endpoint_rule if not endpoint_rule.endpoint.host: continue if endpoint_rule.endpoint.get_port(protocol) == 0: From 611a00a5fa3a84d0339175702f6f3e8c5c942bec Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 19 Apr 2022 19:50:46 +0800 Subject: [PATCH 053/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dsuper=20user?= =?UTF-8?q?=20perm=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 9e16d31ba..479cac98c 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -70,8 +70,6 @@ class ClientProtocolMixin: system_user = serializer.validated_data['system_user'] user = serializer.validated_data.get('user') - if not user or not self.request.user.is_superuser: - user = self.request.user return asset, application, system_user, user @staticmethod From af9248ef7cb1c41238a203f7520a04c40988bce8 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 20 Apr 2022 10:24:08 +0800 Subject: [PATCH 054/258] =?UTF-8?q?fix:=20=E8=BF=98=E5=8E=9Fconnection=20t?= =?UTF-8?q?oken=20=E9=80=BB=E8=BE=91=20(#8101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/authentication/api/connection_token.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 479cac98c..9e16d31ba 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -70,6 +70,8 @@ class ClientProtocolMixin: system_user = serializer.validated_data['system_user'] user = serializer.validated_data.get('user') + if not user or not self.request.user.is_superuser: + user = self.request.user return asset, application, system_user, user @staticmethod From 76474387921c30e7f6606a545f0846fd0b771b12 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 20 Apr 2022 11:18:50 +0800 Subject: [PATCH 055/258] =?UTF-8?q?perf:=20=E8=B4=A6=E5=8F=B7=E5=A4=87?= =?UTF-8?q?=E4=BB=BDlog=20(#8106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/assets/task_handlers/backup/handlers.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index a73bced59..600f8a2da 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -156,10 +156,7 @@ class AccountBackupHandler: logger.info('步骤完成: 用时 {}s'.format(timedelta)) return files - def send_backup_mail(self, files): - recipients = self.execution.plan_snapshot.get('recipients') - if not recipients: - return + def send_backup_mail(self, files, recipients): if not files: return recipients = User.objects.filter(id__in=list(recipients)) @@ -198,8 +195,16 @@ class AccountBackupHandler: is_success = False error = '-' try: - files = self.create_excel() - self.send_backup_mail(files) + recipients = self.execution.plan_snapshot.get('recipients') + if not recipients: + logger.info( + '\n' + '\033[32m>>> 该备份任务未分配收件人\033[0m' + '' + ) + else: + files = self.create_excel() + self.send_backup_mail(files, recipients) except Exception as e: self.is_frozen = True logger.error('任务执行被异常中断') From f1bd4ea91fbfdedf5e795202b76bb081cf25cbf2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 20 Apr 2022 11:19:37 +0800 Subject: [PATCH 056/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=BA=A7=E5=88=AB=E7=94=A8=E6=88=B7=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E7=9A=84=20perms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/builtin.py | 17 ++++++++--------- apps/rbac/models/rolebinding.py | 5 +++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index c99181d4e..179889111 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -2,15 +2,6 @@ from django.utils.translation import ugettext_noop from .const import Scope, system_exclude_permissions, org_exclude_permissions -system_user_perms = ( - ('authentication', 'connectiontoken', 'add', 'connectiontoken'), - ('authentication', 'temptoken', 'add,change,view', 'temptoken'), - ('authentication', 'accesskey', '*', '*'), - ('tickets', 'ticket', 'view', 'ticket'), - ('orgs', 'organization', 'view', 'rootorg'), -) - -# Todo: 获取应该区分 系统用户,和组织用户的权限 # 工作台也区分组织后再考虑 user_perms = ( ('rbac', 'menupermission', 'view', 'workbench'), @@ -25,6 +16,14 @@ user_perms = ( ('ops', 'commandexecution', 'add', 'commandexecution'), ) +system_user_perms = ( + ('authentication', 'connectiontoken', 'add', 'connectiontoken'), + ('authentication', 'temptoken', 'add,change,view', 'temptoken'), + ('authentication', 'accesskey', '*', '*'), + ('tickets', 'ticket', 'view', 'ticket'), + ('orgs', 'organization', 'view', 'rootorg'), +) + user_perms + auditor_perms = user_perms + ( ('rbac', 'menupermission', 'view', 'audit'), ('audits', '*', '*', '*'), diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index 643e38207..dc09f75d2 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -6,7 +6,7 @@ from rest_framework.serializers import ValidationError from common.db.models import JMSModel from common.utils import lazyproperty -from orgs.utils import current_org +from orgs.utils import current_org, tmp_to_root_org from .role import Role from ..const import Scope @@ -105,7 +105,8 @@ class RoleBinding(JMSModel): from orgs.models import Organization roles = Role.get_roles_by_perm(perm) - bindings = list(cls.objects.root_all().filter(role__in=roles, user=user)) + with tmp_to_root_org(): + bindings = list(cls.objects.root_all().filter(role__in=roles, user=user)) system_bindings = [b for b in bindings if b.scope == Role.Scope.system.value] if perm == 'rbac.view_workbench': From d2dd487e2c6d8f0c8a7f3b042c373132ac0378eb Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:05:33 +0800 Subject: [PATCH 057/258] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9LDAP=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E7=BB=84=E7=BB=87=E9=97=AE=E9=A2=98=20(#8111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jiangjie.Bai Co-authored-by: BaiJiangJie --- apps/settings/utils/ldap.py | 4 +++- apps/users/tasks.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py index 22f4c2b19..5175d7a31 100644 --- a/apps/settings/utils/ldap.py +++ b/apps/settings/utils/ldap.py @@ -376,7 +376,9 @@ class LDAPImportUtil(object): except Exception as e: errors.append({user['username']: str(e)}) logger.error(e) - if org and org.is_root(): + if not org: + return + if org.is_root(): return for obj in objs: org.add_member(obj) diff --git a/apps/users/tasks.py b/apps/users/tasks.py index 0f93fa6c3..eb3ec8b9f 100644 --- a/apps/users/tasks.py +++ b/apps/users/tasks.py @@ -81,8 +81,14 @@ def import_ldap_user(): util_server = LDAPServerUtil() util_import = LDAPImportUtil() users = util_server.search() - org_id = settings.AUTH_LDAP_SYNC_ORG_ID - org = Organization.get_instance(org_id) + if settings.XPACK_ENABLED: + org_id = settings.AUTH_LDAP_SYNC_ORG_ID + default_org = None + else: + # 社区版默认导入Default组织 + org_id = Organization.DEFAULT_ID + default_org = Organization.default() + org = Organization.get_instance(org_id, default=default_org) errors = util_import.perform_import(users, org) if errors: logger.error("Imported LDAP users errors: {}".format(errors)) From c29d1337769b40fe55cb0dae7ab59001d69a6959 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 20 Apr 2022 16:30:41 +0800 Subject: [PATCH 058/258] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9LDAP=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1interval/crontab?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jiangjie.Bai --- apps/users/tasks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/users/tasks.py b/apps/users/tasks.py index eb3ec8b9f..6d9bac660 100644 --- a/apps/users/tasks.py +++ b/apps/users/tasks.py @@ -112,6 +112,9 @@ def import_ldap_user_periodic(): else: interval = None crontab = settings.AUTH_LDAP_SYNC_CRONTAB + if crontab: + # 优先使用 crontab + interval = None tasks = { task_name: { 'task': import_ldap_user.name, From 415521a0034ca12a7aee983c2e27a2bc02e6926b Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:32:33 +0800 Subject: [PATCH 059/258] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E6=97=B6=E6=A3=80=E6=B5=8Bldap=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=BB=84=E7=BB=87=20(#8112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 57 ++++++++++++++++------------ apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 55 +++++++++++++++------------ apps/orgs/api.py | 6 +++ 5 files changed, 73 insertions(+), 53 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index e13ed4cfb..d94d92000 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e6962699271d0f5402223321e65211f1c7ad0b7a9b43524f3a0fac7ea2541d9 -size 125623 +oid sha256:c756a62144f20cbfa767a8afa63cfe3e01f65041e0ebd121533ad1411a034623 +size 125910 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 8c5147c50..690aa7d3f 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-19 15:57+0800\n" +"POT-Creation-Date: 2022-04-20 16:23+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,7 +29,7 @@ msgstr "Acls" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:55 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 #: 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:661 @@ -38,12 +38,12 @@ msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:58 +#: assets/models/user.py:247 terminal/models/endpoint.py:61 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:59 +#: assets/models/user.py:247 terminal/models/endpoint.py:62 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" @@ -61,7 +61,7 @@ msgstr "アクティブ" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 -#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:65 +#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 @@ -1360,7 +1360,7 @@ msgstr "監査" #: audits/models.py:27 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:166 +#: rbac/tree.py:168 msgid "Delete" msgstr "削除" @@ -1413,11 +1413,11 @@ msgstr "ファイル転送ログ" #: audits/models.py:55 #: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:163 +#: rbac/tree.py:165 msgid "Create" msgstr "作成" -#: audits/models.py:56 rbac/tree.py:165 templates/_csv_import_export.html:18 +#: audits/models.py:56 rbac/tree.py:167 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" @@ -2886,15 +2886,22 @@ msgstr "タスクログ" msgid "Update task content: {}" msgstr "タスク内容の更新: {}" -#: orgs/api.py:68 +#: orgs/api.py:69 msgid "The current organization ({}) cannot be deleted" msgstr "現在の組織 ({}) は削除できません" -#: orgs/api.py:76 +#: orgs/api.py:77 msgid "The organization have resource ({}) cannot be deleted" msgstr "組織のリソース ({}) は削除できません" -#: orgs/apps.py:7 rbac/tree.py:112 +#: orgs/api.py:83 +msgid "" +"LDAP synchronization is set to the current organization. Please switch to " +"another organization before deleting" +msgstr "" +"LDAP同期は現在の組織に設定されます。削除する前に別の組織に切り替えてください" + +#: orgs/apps.py:7 rbac/tree.py:114 msgid "App organizations" msgstr "アプリ組織" @@ -3202,18 +3209,18 @@ msgstr "組織の役割" msgid "Role binding" msgstr "ロールバインディング" -#: rbac/models/rolebinding.py:150 +#: rbac/models/rolebinding.py:151 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "" "ユーザーの最後のロールは削除できません。ユーザーを組織から削除できます。" -#: rbac/models/rolebinding.py:157 +#: rbac/models/rolebinding.py:158 msgid "Organization role binding" msgstr "組織の役割バインディング" -#: rbac/models/rolebinding.py:172 +#: rbac/models/rolebinding.py:173 msgid "System role binding" msgstr "システムロールバインディング" @@ -3301,27 +3308,27 @@ msgstr "私の資産" msgid "My apps" msgstr "マイアプリ" -#: rbac/tree.py:113 +#: rbac/tree.py:115 msgid "Ticket comment" msgstr "チケットコメント" -#: rbac/tree.py:114 tickets/models/ticket.py:163 +#: rbac/tree.py:116 tickets/models/ticket.py:163 msgid "Ticket" msgstr "チケット" -#: rbac/tree.py:115 +#: rbac/tree.py:117 msgid "Common setting" msgstr "共通設定" -#: rbac/tree.py:116 +#: rbac/tree.py:118 msgid "View permission tree" msgstr "権限ツリーの表示" -#: rbac/tree.py:117 +#: rbac/tree.py:119 msgid "Execute batch command" msgstr "バッチ実行コマンド" -#: rbac/tree.py:164 +#: rbac/tree.py:166 msgid "View" msgstr "表示" @@ -4228,8 +4235,8 @@ msgid "" "Tips: The login success message varies with devices. if you cannot log in to " "the device through Telnet, set this parameter" msgstr "" -"ヒント: ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバイスにロ" -"グインできない場合は、このパラメーターを設定します。" +"ヒント: ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバ" +"イスにログインできない場合は、このパラメーターを設定します。" #: settings/serializers/terminal.py:36 msgid "Enable database proxy" @@ -4725,18 +4732,18 @@ msgstr "MariaDB ポート" msgid "PostgreSQL Port" msgstr "PostgreSQL ポート" -#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:63 +#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:66 #: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 #: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 #: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/endpoint.py:56 +#: terminal/models/endpoint.py:59 msgid "IP group" msgstr "IP グループ" -#: terminal/models/endpoint.py:68 +#: terminal/models/endpoint.py:71 msgid "Endpoint rule" msgstr "エンドポイントルール" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c73d42f67..4d43bfac0 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3462a9a3eef8f372bf341f2066a33d85e1f01aca5a8fe506528a1cd0a37e98b4 -size 103951 +oid sha256:529a9646db39920766ffbe95b0de79bf0539df9f5807b5e36294031d8c4c7842 +size 104164 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 5160cbfcf..37d3e85e0 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-19 15:57+0800\n" +"POT-Creation-Date: 2022-04-20 16:23+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -28,7 +28,7 @@ msgstr "访问控制" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:55 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 #: 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:661 @@ -37,12 +37,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:58 +#: assets/models/user.py:247 terminal/models/endpoint.py:61 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:59 +#: assets/models/user.py:247 terminal/models/endpoint.py:62 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -60,7 +60,7 @@ msgstr "激活中" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 -#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:65 +#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 @@ -1348,7 +1348,7 @@ msgstr "日志审计" #: audits/models.py:27 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:166 +#: rbac/tree.py:168 msgid "Delete" msgstr "删除" @@ -1401,11 +1401,11 @@ msgstr "文件管理" #: audits/models.py:55 #: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:163 +#: rbac/tree.py:165 msgid "Create" msgstr "创建" -#: audits/models.py:56 rbac/tree.py:165 templates/_csv_import_export.html:18 +#: audits/models.py:56 rbac/tree.py:167 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" @@ -2851,15 +2851,21 @@ msgstr "任务列表" msgid "Update task content: {}" msgstr "更新任务内容: {}" -#: orgs/api.py:68 +#: orgs/api.py:69 msgid "The current organization ({}) cannot be deleted" msgstr "当前组织 ({}) 不能被删除" -#: orgs/api.py:76 +#: orgs/api.py:77 msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" -#: orgs/apps.py:7 rbac/tree.py:112 +#: orgs/api.py:83 +msgid "" +"LDAP synchronization is set to the current organization. Please switch to " +"another organization before deleting" +msgstr "LDAP同步设置组织为当前组织,请切换其他组织后再进行删除操作" + +#: orgs/apps.py:7 rbac/tree.py:114 msgid "App organizations" msgstr "组织管理" @@ -3165,17 +3171,17 @@ msgstr "组织角色" msgid "Role binding" msgstr "角色绑定" -#: rbac/models/rolebinding.py:150 +#: rbac/models/rolebinding.py:151 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "用户最后一个角色,不能删除,你可以将用户从组织移除" -#: rbac/models/rolebinding.py:157 +#: rbac/models/rolebinding.py:158 msgid "Organization role binding" msgstr "组织角色绑定" -#: rbac/models/rolebinding.py:172 +#: rbac/models/rolebinding.py:173 msgid "System role binding" msgstr "系统角色绑定" @@ -3263,27 +3269,27 @@ msgstr "我的资产" msgid "My apps" msgstr "我的应用" -#: rbac/tree.py:113 +#: rbac/tree.py:115 msgid "Ticket comment" msgstr "工单评论" -#: rbac/tree.py:114 tickets/models/ticket.py:163 +#: rbac/tree.py:116 tickets/models/ticket.py:163 msgid "Ticket" msgstr "工单管理" -#: rbac/tree.py:115 +#: rbac/tree.py:117 msgid "Common setting" msgstr "一般设置" -#: rbac/tree.py:116 +#: rbac/tree.py:118 msgid "View permission tree" msgstr "查看授权树" -#: rbac/tree.py:117 +#: rbac/tree.py:119 msgid "Execute batch command" msgstr "执行批量命令" -#: rbac/tree.py:164 +#: rbac/tree.py:166 msgid "View" msgstr "查看" @@ -4167,7 +4173,8 @@ msgstr "Telnet 成功正则表达式" msgid "" "Tips: The login success message varies with devices. if you cannot log in to " "the device through Telnet, set this parameter" -msgstr "提示: 不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" +msgstr "" +"提示: 不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" #: settings/serializers/terminal.py:36 msgid "Enable database proxy" @@ -4651,18 +4658,18 @@ msgstr "MariaDB 端口" msgid "PostgreSQL Port" msgstr "PostgreSQL 端口" -#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:63 +#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:66 #: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 #: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 #: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 msgid "Endpoint" msgstr "端点" -#: terminal/models/endpoint.py:56 +#: terminal/models/endpoint.py:59 msgid "IP group" msgstr "IP 组" -#: terminal/models/endpoint.py:68 +#: terminal/models/endpoint.py:71 msgid "Endpoint rule" msgstr "端点规则" diff --git a/apps/orgs/api.py b/apps/orgs/api.py index e1a41a29f..0723790cd 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -2,6 +2,7 @@ # from django.utils.translation import ugettext as _ +from django.conf import settings from rest_framework_bulk import BulkModelViewSet from rest_framework.generics import RetrieveAPIView from rest_framework.exceptions import PermissionDenied @@ -76,6 +77,11 @@ class OrgViewSet(BulkModelViewSet): 'The organization have resource ({}) cannot be deleted' ).format(model._meta.verbose_name) raise PermissionDenied(detail=msg) + if str(instance.id) == settings.AUTH_LDAP_SYNC_ORG_ID: + msg = _( + 'LDAP synchronization is set to the current organization. Please switch to another organization before deleting' + ) + raise PermissionDenied(detail=msg) super().perform_destroy(instance) From b0b379e5a95bbf4c97af7040f555815bddcbf983 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:38:42 +0800 Subject: [PATCH 060/258] fix: del org check ldap (#8114) Co-authored-by: feng626 <1304903146@qq.com> --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 66 ++++++++++++++-------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 66 ++++++++++++++-------------- apps/orgs/api.py | 11 ++--- 5 files changed, 76 insertions(+), 75 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index d94d92000..52b16b3bd 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c756a62144f20cbfa767a8afa63cfe3e01f65041e0ebd121533ad1411a034623 -size 125910 +oid sha256:f2c88ade4bfae213bdcdafad656af73f764e3b1b3f2b0c59aa39626e967730ca +size 125911 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 690aa7d3f..8aa00bb46 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-20 16:23+0800\n" +"POT-Creation-Date: 2022-04-20 16:35+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -2890,16 +2890,16 @@ msgstr "タスク内容の更新: {}" msgid "The current organization ({}) cannot be deleted" msgstr "現在の組織 ({}) は削除できません" -#: orgs/api.py:77 -msgid "The organization have resource ({}) cannot be deleted" -msgstr "組織のリソース ({}) は削除できません" - -#: orgs/api.py:83 +#: orgs/api.py:74 msgid "" "LDAP synchronization is set to the current organization. Please switch to " "another organization before deleting" msgstr "" -"LDAP同期は現在の組織に設定されます。削除する前に別の組織に切り替えてください" +"LDAP 同期は現在の組織に設定されます。削除する前に別の組織に切り替えてください" + +#: orgs/api.py:83 +msgid "The organization have resource ({}) cannot be deleted" +msgstr "組織のリソース ({}) は削除できません" #: orgs/apps.py:7 rbac/tree.py:114 msgid "App organizations" @@ -4250,104 +4250,104 @@ msgstr "XRDPの有効化" msgid "Enable KoKo SSH" msgstr "KoKo SSHの有効化" -#: settings/utils/ldap.py:417 +#: settings/utils/ldap.py:419 msgid "ldap:// or ldaps:// protocol is used." msgstr "ldap:// または ldaps:// プロトコルが使用されます。" -#: settings/utils/ldap.py:428 +#: settings/utils/ldap.py:430 msgid "Host or port is disconnected: {}" msgstr "ホストまたはポートが切断されました: {}" -#: settings/utils/ldap.py:430 +#: settings/utils/ldap.py:432 msgid "The port is not the port of the LDAP service: {}" msgstr "ポートはLDAPサービスのポートではありません: {}" -#: settings/utils/ldap.py:432 +#: settings/utils/ldap.py:434 msgid "Please add certificate: {}" msgstr "証明書を追加してください: {}" -#: settings/utils/ldap.py:436 settings/utils/ldap.py:463 -#: settings/utils/ldap.py:493 settings/utils/ldap.py:521 +#: settings/utils/ldap.py:438 settings/utils/ldap.py:465 +#: settings/utils/ldap.py:495 settings/utils/ldap.py:523 msgid "Unknown error: {}" msgstr "不明なエラー: {}" -#: settings/utils/ldap.py:450 +#: settings/utils/ldap.py:452 msgid "Bind DN or Password incorrect" msgstr "DNまたはパスワードのバインドが正しくありません" -#: settings/utils/ldap.py:457 +#: settings/utils/ldap.py:459 msgid "Please enter Bind DN: {}" msgstr "バインドDN: {} を入力してください" -#: settings/utils/ldap.py:459 +#: settings/utils/ldap.py:461 msgid "Please enter Password: {}" msgstr "パスワードを入力してください: {}" -#: settings/utils/ldap.py:461 +#: settings/utils/ldap.py:463 msgid "Please enter correct Bind DN and Password: {}" msgstr "正しいバインドDNとパスワードを入力してください: {}" -#: settings/utils/ldap.py:479 +#: settings/utils/ldap.py:481 msgid "Invalid User OU or User search filter: {}" msgstr "無効なユーザー OU またはユーザー検索フィルター: {}" -#: settings/utils/ldap.py:510 +#: settings/utils/ldap.py:512 msgid "LDAP User attr map not include: {}" msgstr "LDAP ユーザーattrマップは含まれません: {}" -#: settings/utils/ldap.py:517 +#: settings/utils/ldap.py:519 msgid "LDAP User attr map is not dict" msgstr "LDAPユーザーattrマップはdictではありません" -#: settings/utils/ldap.py:536 +#: settings/utils/ldap.py:538 msgid "LDAP authentication is not enabled" msgstr "LDAP 認証が有効になっていない" -#: settings/utils/ldap.py:554 +#: settings/utils/ldap.py:556 msgid "Error (Invalid LDAP server): {}" msgstr "エラー (LDAPサーバーが無効): {}" -#: settings/utils/ldap.py:556 +#: settings/utils/ldap.py:558 msgid "Error (Invalid Bind DN): {}" msgstr "エラー (DNのバインドが無効): {}" -#: settings/utils/ldap.py:558 +#: settings/utils/ldap.py:560 msgid "Error (Invalid LDAP User attr map): {}" msgstr "エラー (LDAPユーザーattrマップが無効): {}" -#: settings/utils/ldap.py:560 +#: settings/utils/ldap.py:562 msgid "Error (Invalid User OU or User search filter): {}" msgstr "エラー (ユーザーOUまたはユーザー検索フィルターが無効): {}" -#: settings/utils/ldap.py:562 +#: settings/utils/ldap.py:564 msgid "Error (Not enabled LDAP authentication): {}" msgstr "エラー (LDAP認証が有効化されていません): {}" -#: settings/utils/ldap.py:564 +#: settings/utils/ldap.py:566 msgid "Error (Unknown): {}" msgstr "エラー (不明): {}" -#: settings/utils/ldap.py:567 +#: settings/utils/ldap.py:569 msgid "Succeed: Match {} s user" msgstr "成功: {} 人のユーザーに一致" -#: settings/utils/ldap.py:600 +#: settings/utils/ldap.py:602 msgid "Authentication failed (configuration incorrect): {}" msgstr "認証に失敗しました (設定が正しくありません): {}" -#: settings/utils/ldap.py:602 +#: settings/utils/ldap.py:604 msgid "Authentication failed (before login check failed): {}" msgstr "認証に失敗しました (ログインチェックが失敗する前): {}" -#: settings/utils/ldap.py:604 +#: settings/utils/ldap.py:606 msgid "Authentication failed (username or password incorrect): {}" msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" -#: settings/utils/ldap.py:606 +#: settings/utils/ldap.py:608 msgid "Authentication failed (Unknown): {}" msgstr "認証に失敗しました (不明): {}" -#: settings/utils/ldap.py:609 +#: settings/utils/ldap.py:611 msgid "Authentication success: {}" msgstr "認証成功: {}" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4d43bfac0..915796d03 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:529a9646db39920766ffbe95b0de79bf0539df9f5807b5e36294031d8c4c7842 -size 104164 +oid sha256:c75e0a1f2a047dac1374916c630bc0e8ef5ad5eea7518ffc21e93f747fc1235e +size 104165 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 37d3e85e0..79cb6c940 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-20 16:23+0800\n" +"POT-Creation-Date: 2022-04-20 16:35+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -2855,15 +2855,15 @@ msgstr "更新任务内容: {}" msgid "The current organization ({}) cannot be deleted" msgstr "当前组织 ({}) 不能被删除" -#: orgs/api.py:77 -msgid "The organization have resource ({}) cannot be deleted" -msgstr "组织存在资源 ({}) 不能被删除" - -#: orgs/api.py:83 +#: orgs/api.py:74 msgid "" "LDAP synchronization is set to the current organization. Please switch to " "another organization before deleting" -msgstr "LDAP同步设置组织为当前组织,请切换其他组织后再进行删除操作" +msgstr "LDAP 同步设置组织为当前组织,请切换其他组织后再进行删除操作" + +#: orgs/api.py:83 +msgid "The organization have resource ({}) cannot be deleted" +msgstr "组织存在资源 ({}) 不能被删除" #: orgs/apps.py:7 rbac/tree.py:114 msgid "App organizations" @@ -4188,104 +4188,104 @@ msgstr "启用 XRDP 服务" msgid "Enable KoKo SSH" msgstr "启用 KoKo SSH" -#: settings/utils/ldap.py:417 +#: settings/utils/ldap.py:419 msgid "ldap:// or ldaps:// protocol is used." msgstr "使用 ldap:// 或 ldaps:// 协议" -#: settings/utils/ldap.py:428 +#: settings/utils/ldap.py:430 msgid "Host or port is disconnected: {}" msgstr "主机或端口不可连接: {}" -#: settings/utils/ldap.py:430 +#: settings/utils/ldap.py:432 msgid "The port is not the port of the LDAP service: {}" msgstr "端口不是LDAP服务端口: {}" -#: settings/utils/ldap.py:432 +#: settings/utils/ldap.py:434 msgid "Please add certificate: {}" msgstr "请添加证书" -#: settings/utils/ldap.py:436 settings/utils/ldap.py:463 -#: settings/utils/ldap.py:493 settings/utils/ldap.py:521 +#: settings/utils/ldap.py:438 settings/utils/ldap.py:465 +#: settings/utils/ldap.py:495 settings/utils/ldap.py:523 msgid "Unknown error: {}" msgstr "未知错误: {}" -#: settings/utils/ldap.py:450 +#: settings/utils/ldap.py:452 msgid "Bind DN or Password incorrect" msgstr "绑定DN或密码错误" -#: settings/utils/ldap.py:457 +#: settings/utils/ldap.py:459 msgid "Please enter Bind DN: {}" msgstr "请输入绑定DN: {}" -#: settings/utils/ldap.py:459 +#: settings/utils/ldap.py:461 msgid "Please enter Password: {}" msgstr "请输入密码: {}" -#: settings/utils/ldap.py:461 +#: settings/utils/ldap.py:463 msgid "Please enter correct Bind DN and Password: {}" msgstr "请输入正确的绑定DN和密码: {}" -#: settings/utils/ldap.py:479 +#: settings/utils/ldap.py:481 msgid "Invalid User OU or User search filter: {}" msgstr "不合法的用户OU或用户过滤器: {}" -#: settings/utils/ldap.py:510 +#: settings/utils/ldap.py:512 msgid "LDAP User attr map not include: {}" msgstr "LDAP属性映射没有包含: {}" -#: settings/utils/ldap.py:517 +#: settings/utils/ldap.py:519 msgid "LDAP User attr map is not dict" msgstr "LDAP属性映射不合法" -#: settings/utils/ldap.py:536 +#: settings/utils/ldap.py:538 msgid "LDAP authentication is not enabled" msgstr "LDAP认证没有启用" -#: settings/utils/ldap.py:554 +#: settings/utils/ldap.py:556 msgid "Error (Invalid LDAP server): {}" msgstr "错误 (不合法的LDAP服务器地址): {}" -#: settings/utils/ldap.py:556 +#: settings/utils/ldap.py:558 msgid "Error (Invalid Bind DN): {}" msgstr "错误(不合法的绑定DN): {}" -#: settings/utils/ldap.py:558 +#: settings/utils/ldap.py:560 msgid "Error (Invalid LDAP User attr map): {}" msgstr "错误(不合法的LDAP属性映射): {}" -#: settings/utils/ldap.py:560 +#: settings/utils/ldap.py:562 msgid "Error (Invalid User OU or User search filter): {}" msgstr "错误(不合法的用户OU或用户过滤器): {}" -#: settings/utils/ldap.py:562 +#: settings/utils/ldap.py:564 msgid "Error (Not enabled LDAP authentication): {}" msgstr "错误(没有启用LDAP认证): {}" -#: settings/utils/ldap.py:564 +#: settings/utils/ldap.py:566 msgid "Error (Unknown): {}" msgstr "错误(未知): {}" -#: settings/utils/ldap.py:567 +#: settings/utils/ldap.py:569 msgid "Succeed: Match {} s user" msgstr "成功匹配 {} 个用户" -#: settings/utils/ldap.py:600 +#: settings/utils/ldap.py:602 msgid "Authentication failed (configuration incorrect): {}" msgstr "认证失败(配置错误): {}" -#: settings/utils/ldap.py:602 +#: settings/utils/ldap.py:604 msgid "Authentication failed (before login check failed): {}" msgstr "认证失败(登录前检查失败): {}" -#: settings/utils/ldap.py:604 +#: settings/utils/ldap.py:606 msgid "Authentication failed (username or password incorrect): {}" msgstr "认证失败 (用户名或密码不正确): {}" -#: settings/utils/ldap.py:606 +#: settings/utils/ldap.py:608 msgid "Authentication failed (Unknown): {}" msgstr "认证失败: (未知): {}" -#: settings/utils/ldap.py:609 +#: settings/utils/ldap.py:611 msgid "Authentication success: {}" msgstr "认证成功: {}" diff --git a/apps/orgs/api.py b/apps/orgs/api.py index 0723790cd..7a3271c50 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -69,6 +69,12 @@ class OrgViewSet(BulkModelViewSet): msg = _('The current organization ({}) cannot be deleted').format(current_org) raise PermissionDenied(detail=msg) + if str(instance.id) == settings.AUTH_LDAP_SYNC_ORG_ID: + msg = _( + 'LDAP synchronization is set to the current organization. Please switch to another organization before deleting' + ) + raise PermissionDenied(detail=msg) + for model in org_related_models: data = self.get_data_from_model(instance, model) if not data: @@ -77,11 +83,6 @@ class OrgViewSet(BulkModelViewSet): 'The organization have resource ({}) cannot be deleted' ).format(model._meta.verbose_name) raise PermissionDenied(detail=msg) - if str(instance.id) == settings.AUTH_LDAP_SYNC_ORG_ID: - msg = _( - 'LDAP synchronization is set to the current organization. Please switch to another organization before deleting' - ) - raise PermissionDenied(detail=msg) super().perform_destroy(instance) From e61bae5ee4140d4fe5bbd4f3ca688bcd0d6e8a87 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 20 Apr 2022 18:50:53 +0800 Subject: [PATCH 061/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=9D=83?= =?UTF-8?q?=E9=99=90=E4=BD=8D=20(#8110)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化权限位 * perf: 优化返回的组织 * perf: 保证结果是 ok * perf: 去掉 distinct * perf: tree count Co-authored-by: ibuler --- apps/rbac/builtin.py | 16 ++- apps/rbac/const.py | 3 + apps/rbac/models/rolebinding.py | 8 +- apps/rbac/tree.py | 167 +++++++++++++++++++------------- 4 files changed, 120 insertions(+), 74 deletions(-) diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index 179889111..b736ba226 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -2,6 +2,10 @@ from django.utils.translation import ugettext_noop from .const import Scope, system_exclude_permissions, org_exclude_permissions +_view_root_perms = ( + ('orgs', 'organization', 'view', 'rootorg'), +) + # 工作台也区分组织后再考虑 user_perms = ( ('rbac', 'menupermission', 'view', 'workbench'), @@ -21,19 +25,23 @@ system_user_perms = ( ('authentication', 'temptoken', 'add,change,view', 'temptoken'), ('authentication', 'accesskey', '*', '*'), ('tickets', 'ticket', 'view', 'ticket'), - ('orgs', 'organization', 'view', 'rootorg'), ) + user_perms -auditor_perms = user_perms + ( +_auditor_perms = ( ('rbac', 'menupermission', 'view', 'audit'), ('audits', '*', '*', '*'), ('terminal', 'commandstorage', 'view', 'commandstorage'), ('terminal', 'sessionreplay', 'view,download', 'sessionreplay'), ('terminal', 'session', '*', '*'), ('terminal', 'command', '*', '*'), - ('ops', 'commandexecution', 'view', 'commandexecution') + ('ops', 'commandexecution', 'view', 'commandexecution'), ) +auditor_perms = user_perms + _auditor_perms + +system_auditor_perms = system_user_perms + _auditor_perms + _view_root_perms + + app_exclude_perms = [ ('users', 'user', 'add,delete', 'user'), ('orgs', 'org', 'add,delete,change', 'org'), @@ -101,7 +109,7 @@ class BuiltinRole: '1', ugettext_noop('SystemAdmin'), Scope.system, [] ) system_auditor = PredefineRole( - '2', ugettext_noop('SystemAuditor'), Scope.system, auditor_perms + '2', ugettext_noop('SystemAuditor'), Scope.system, system_auditor_perms ) system_component = PredefineRole( '4', ugettext_noop('SystemComponent'), Scope.system, app_exclude_perms, 'exclude' diff --git a/apps/rbac/const.py b/apps/rbac/const.py index d9b80b78a..b84b7c69e 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -108,8 +108,11 @@ only_system_permissions = ( ('terminal', 'replaystorage', '*', '*'), ('terminal', 'status', '*', '*'), ('terminal', 'task', '*', '*'), + ('terminal', 'endpoint', '*', '*'), + ('terminal', 'endpointrule', '*', '*'), ('authentication', '*', '*', '*'), ('tickets', '*', '*', '*'), + ('orgs', 'organization', 'view', 'rootorg'), ) only_org_permissions = ( diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index dc09f75d2..c0ac806ef 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -107,19 +107,23 @@ class RoleBinding(JMSModel): roles = Role.get_roles_by_perm(perm) with tmp_to_root_org(): bindings = list(cls.objects.root_all().filter(role__in=roles, user=user)) - system_bindings = [b for b in bindings if b.scope == Role.Scope.system.value] + system_bindings = [b for b in bindings if b.scope == Role.Scope.system.value] + # 工作台仅限于自己加入的组织 if perm == 'rbac.view_workbench': all_orgs = user.orgs.all() else: all_orgs = Organization.objects.all() + # 有系统级别的绑定,就代表在所有组织有这个权限 if system_bindings: orgs = all_orgs else: org_ids = [b.org.id for b in bindings if b.org] orgs = all_orgs.filter(id__in=org_ids) - if orgs and user.has_perm('orgs.view_rootorg'): + + # 全局组织 + if orgs and perm != 'rbac.view_workbench' and user.has_perm('orgs.view_rootorg'): orgs = [Organization.root(), *list(orgs)] return orgs diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py index a585bdf5c..bae0b930e 100644 --- a/apps/rbac/tree.py +++ b/apps/rbac/tree.py @@ -1,7 +1,8 @@ #!/usr/bin/python import os -from collections import defaultdict from typing import Callable +from treelib import Tree +from treelib.exceptions import NodeIDAbsentError from django.utils.translation import gettext_lazy as _, gettext, get_language from django.conf import settings @@ -159,6 +160,65 @@ def sort_nodes(node): return value +class CounterTree(Tree): + def get_total_count(self, node): + count = getattr(node, '_total_count', None) + if count is not None: + return count + + if not node.data.isParent: + return 1 + + count = 0 + children = self.children(node.identifier) + for child in children: + if child.data.isParent: + count += self.get_total_count(child) + else: + count += 1 + node._total_count = count + return count + + def get_checked_count(self, node): + count = getattr(node, '_checked_count', None) + if count is not None: + return count + + if not node.data.isParent: + if node.data.checked: + return 1 + else: + return 0 + + count = 0 + children = self.children(node.identifier) + for child in children: + if child.data.isParent: + count += self.get_checked_count(child) + else: + if child.data.checked: + count += 1 + node._checked_count = count + return count + + def add_nodes_to_tree(self, ztree_nodes, retry=0): + failed = [] + for node in ztree_nodes: + pid = node.pId + if retry == 2: + pid = '$ROOT$' + + try: + self.create_node(node.name, node.id, pid, data=node) + except NodeIDAbsentError: + failed.append(node) + if retry > 2: + return + if failed: + retry += 1 + return self.add_nodes_to_tree(failed, retry) + + class PermissionTreeUtil: get_permissions: Callable action_mapper = { @@ -183,8 +243,6 @@ class PermissionTreeUtil: Permission.get_permissions(scope) ) self.check_disabled = check_disabled - self.total_counts = defaultdict(int) - self.checked_counts = defaultdict(int) self.lang = get_language() @staticmethod @@ -211,38 +269,10 @@ class PermissionTreeUtil: 'name': name, 'pId': view, } - total_count = self.total_counts[app] - checked_count = self.checked_counts[app] - if total_count == 0: - continue - self.total_counts[view] += total_count - self.checked_counts[view] += checked_count - node = self._create_node( - app_data, total_count, checked_count, - 'app', is_open=False - ) + node = self._create_node(app_data, 'app', is_open=False) nodes.append(node) return nodes - def _get_model_counts_mapper(self): - model_counts = self.all_permissions \ - .values('model', 'app', 'content_type') \ - .order_by('content_type') \ - .annotate(count=Count('content_type')) - model_check_counts = self.permissions \ - .values('content_type', 'model') \ - .order_by('content_type') \ - .annotate(count=Count('content_type')) - model_counts_mapper = { - i['content_type']: i['count'] - for i in model_counts - } - model_check_counts_mapper = { - i['content_type']: i['count'] - for i in model_check_counts - } - return model_counts_mapper, model_check_counts_mapper - @staticmethod def _check_model_xpack(model_id): app, model = model_id.split('.', 2) @@ -263,17 +293,10 @@ class PermissionTreeUtil: if not self._check_model_xpack(model_id): continue - total_count = self.total_counts[model_id] - checked_count = self.checked_counts[model_id] - if total_count == 0: - continue - # 获取 pid app = ct.app_label if model_id in special_pid_mapper: app = special_pid_mapper[model_id] - self.total_counts[app] += total_count - self.checked_counts[app] += checked_count # 获取 name name = f'{ct.name}' @@ -284,7 +307,7 @@ class PermissionTreeUtil: 'id': model_id, 'name': name, 'pId': app, - }, total_count, checked_count, 'model', is_open=False) + }, 'model', is_open=False) nodes.append(node) return nodes @@ -334,10 +357,7 @@ class PermissionTreeUtil: if title in special_pid_mapper: pid = special_pid_mapper[title] - self.total_counts[pid] += 1 checked = p.id in permissions_id - if checked: - self.checked_counts[pid] += 1 node = TreeNode(**{ 'id': p.id, @@ -347,7 +367,7 @@ class PermissionTreeUtil: 'isParent': False, 'chkDisabled': self.check_disabled, 'iconSkin': icon, - 'checked': p.id in permissions_id, + 'checked': checked, 'open': False, 'meta': { 'type': 'perm', @@ -356,13 +376,11 @@ class PermissionTreeUtil: nodes.append(node) return nodes - def _create_node(self, data, total_count, checked_count, tp, - is_parent=True, is_open=True, icon='', checked=None): + def _create_node(self, data, tp, is_parent=True, is_open=True, icon='', checked=None): assert data.get('id') assert data.get('name') assert data.get('pId') is not None - if checked is None: - checked = total_count == checked_count + node_data = { 'isParent': is_parent, 'iconSkin': icon, @@ -380,46 +398,58 @@ class PermissionTreeUtil: node.name += ('[' + node.id + ']') if DEBUG_DB: node.name += ('-' + node.id) - node.name += f'({checked_count}/{total_count})' return node def _create_root_tree_node(self): - total_count = self.all_permissions.count() - checked_count = self.permissions.count() - node = self._create_node(root_node_data, total_count, checked_count, 'root') + node = self._create_node(root_node_data, 'root') return node def _create_views_node(self): nodes = [] for view_data in view_nodes_data: - view = view_data['id'] data = { **view_data, 'pId': '$ROOT$', } - total_count = self.total_counts[view] - checked_count = self.checked_counts[view] - if total_count == 0: - continue - node = self._create_node(data, total_count, checked_count, 'view', is_open=True) + node = self._create_node(data, 'view', is_open=True) nodes.append(node) return nodes def _create_extra_nodes(self): nodes = [] for data in extra_nodes_data: - i = data['id'] - pid = data['pId'] - checked_count = self.checked_counts[i] - total_count = self.total_counts[i] + node = self._create_node(data, 'extra', is_open=False) + nodes.append(node) + return nodes + + @staticmethod + def compute_nodes_count(ztree_nodes): + tree = CounterTree() + reverse_nodes = ztree_nodes[::-1] + root = reverse_nodes[0] + tree.create_node(root.name, root.id, data=root) + tree.add_nodes_to_tree(reverse_nodes[1:]) + counter_nodes = tree.all_nodes() + + node_counts = {} + for n in counter_nodes: + if not n: + continue + total_count = tree.get_total_count(n) + checked_count = tree.get_checked_count(n) + node_counts[n.identifier] = [checked_count, total_count] + + nodes = [] + for node in ztree_nodes: + counter = node_counts[node.id] + if not counter: + counter = [0, 0] + checked_count, total_count = counter if total_count == 0: continue - self.total_counts[pid] += total_count - self.checked_counts[pid] += checked_count - node = self._create_node( - data, total_count, checked_count, - 'extra', is_open=False - ) + node.name += '({}/{})'.format(checked_count, total_count) + if checked_count != 0: + node.checked = True nodes.append(node) return nodes @@ -431,5 +461,6 @@ class PermissionTreeUtil: nodes += self._create_views_node() nodes += [self._create_root_tree_node()] + nodes = self.compute_nodes_count(nodes) nodes.sort(key=sort_nodes) return nodes From 74f88d842d11211f9f4a7122b3572189dc53e2a7 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 20 Apr 2022 19:12:55 +0800 Subject: [PATCH 062/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9replay=20downl?= =?UTF-8?q?oad=20perm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 3193956e5..6ba0477b9 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -63,7 +63,7 @@ class SessionViewSet(OrgBulkModelViewSet): ] extra_filter_backends = [DatetimeRangeFilter] rbac_perms = { - 'download': ['terminal.download_sessionreplay|terminal.view_sessionreplay'] + 'download': ['terminal.download_sessionreplay'] } @staticmethod From 63ee2dd8fb1a1a893ea8da6883e9e93eb2d0e75e Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 20 Apr 2022 19:58:39 +0800 Subject: [PATCH 063/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=9D=83=E9=99=90=E6=A0=91=E6=9D=83=E9=99=90=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/api/role.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py index ceb2e9c19..f077964a6 100644 --- a/apps/rbac/api/role.py +++ b/apps/rbac/api/role.py @@ -90,7 +90,7 @@ class SystemRolePermissionsViewSet(BaseRolePermissionsViewSet): role_pk = 'system_role_pk' model = SystemRole rbac_perms = ( - ('get_tree', 'rbac.view_systemrole'), + ('get_tree', 'rbac.view_permission'), ) @@ -99,6 +99,6 @@ class OrgRolePermissionsViewSet(BaseRolePermissionsViewSet): role_pk = 'org_role_pk' model = OrgRole rbac_perms = ( - ('get_tree', 'rbac.view_orgrole'), + ('get_tree', 'rbac.view_permission'), ) From c3de7b78c2d80df73860b15fc42df2505c273585 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 21 Apr 2022 15:36:40 +0800 Subject: [PATCH 064/258] =?UTF-8?q?fix:=20=E8=BF=9C=E7=A8=8B=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E6=8E=88=E6=9D=83=E6=97=B6=20=E6=9C=89=E4=BA=9B?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E5=B7=B2=E7=BB=8F=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E4=BA=86=20=E5=AF=BC=E8=87=B4=E6=8E=88=E6=9D=83=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=20(#8127)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/perms/signal_handlers/app_permission.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/perms/signal_handlers/app_permission.py b/apps/perms/signal_handlers/app_permission.py index 779c99dca..104f56e9a 100644 --- a/apps/perms/signal_handlers/app_permission.py +++ b/apps/perms/signal_handlers/app_permission.py @@ -4,7 +4,7 @@ from django.db.models.signals import m2m_changed from django.dispatch import receiver from users.models import User, UserGroup -from assets.models import SystemUser +from assets.models import Asset, SystemUser from applications.models import Application from common.utils import get_logger from common.exceptions import M2MReverseNotAllowed @@ -48,6 +48,8 @@ def set_remote_app_asset_system_users_if_need(instance: ApplicationPermission, s attrs = instance.applications.all().values_list('attrs', flat=True) asset_ids = [attr['asset'] for attr in attrs if attr.get('asset')] + # 远程应用中资产可能在资产表里不存在 + asset_ids = Asset.objects.filter(id__in=asset_ids).values_list('id', flat=True) if not asset_ids: return From a6d61721ddd38fdff3df9de9fdd2ffb34ddb3bab Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 21 Apr 2022 16:13:03 +0800 Subject: [PATCH 065/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9csrftoken?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/static/js/jumpserver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 070c811f7..680410763 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -125,7 +125,8 @@ function csrfSafeMethod(method) { } function setAjaxCSRFToken() { - const prefix = getCookie('SESSION_COOKIE_NAME_PREFIX', '') + let prefix = getCookie('SESSION_COOKIE_NAME_PREFIX'); + if (!prefix || [`""`, `''`].indexOf(prefix) > -1) { prefix = ''; } var csrftoken = getCookie(`${prefix}csrftoken`); var sessionid = getCookie(`${prefix}sessionid`); From d25cde1bd5442a038454e4b54e86622051fd3c9b Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 Apr 2022 22:37:29 +0800 Subject: [PATCH 066/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=A4=BE?= =?UTF-8?q?=E5=8C=BA=E7=89=88=E8=B7=B3=E8=BD=AC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/models/rolebinding.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index c0ac806ef..835fbf6a7 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -1,6 +1,7 @@ from django.utils.translation import gettext_lazy as _ from django.db import models from django.db.models import Q +from django.conf import settings from django.core.exceptions import ValidationError from rest_framework.serializers import ValidationError @@ -115,6 +116,9 @@ class RoleBinding(JMSModel): else: all_orgs = Organization.objects.all() + if not settings.XPACK_ENABLED: + all_orgs = all_orgs.filter(id=Organization.DEFAULT_ID) + # 有系统级别的绑定,就代表在所有组织有这个权限 if system_bindings: orgs = all_orgs From 978c1f6363d1d49f8c02cb2f9a602e075b9a6647 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 22 Apr 2022 13:44:39 +0800 Subject: [PATCH 067/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20Dockerfile?= =?UTF-8?q?,=20=E4=BC=98=E5=8C=96=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index 421f95b20..3ef2daad9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,5 @@ -# 编译代码 -FROM python:3.8-slim as stage-build -MAINTAINER JumpServer Team -ARG VERSION -ENV VERSION=$VERSION - -WORKDIR /opt/jumpserver -ADD . . -RUN cd utils && bash -ixeu build.sh - FROM python:3.8-slim -ARG PIP_MIRROR=https://pypi.douban.com/simple -ENV PIP_MIRROR=$PIP_MIRROR -ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple -ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR - -WORKDIR /opt/jumpserver +MAINTAINER JumpServer Team ARG BUILD_DEPENDENCIES=" \ g++ \ @@ -62,21 +47,36 @@ RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \ && mv /bin/sh /bin/sh.bak \ && ln -s /bin/bash /bin/sh -RUN mkdir -p /opt/jumpserver/oracle/ \ +RUN mkdir -p /opt/oracle/ \ && wget https://download.jumpserver.org/public/instantclient-basiclite-linux.x64-21.1.0.0.0.tar \ - && tar xf instantclient-basiclite-linux.x64-21.1.0.0.0.tar -C /opt/jumpserver/oracle/ \ - && echo "/opt/jumpserver/oracle/instantclient_21_1" > /etc/ld.so.conf.d/oracle-instantclient.conf \ + && tar xf instantclient-basiclite-linux.x64-21.1.0.0.0.tar -C /opt/oracle/ \ + && echo "/opt/oracle/instantclient_21_1" > /etc/ld.so.conf.d/oracle-instantclient.conf \ && ldconfig \ && rm -f instantclient-basiclite-linux.x64-21.1.0.0.0.tar -COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver +WORKDIR /tmp/build +COPY ./requirements ./requirements -RUN echo > config.yml \ - && pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ +ARG PIP_MIRROR=https://mirrors.aliyun.com/pypi/simple/ +ENV PIP_MIRROR=$PIP_MIRROR +ARG PIP_JMS_MIRROR=https://mirrors.aliyun.com/pypi/simple/ +ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR +# 因为以 jms 或者 jumpserver 开头的 mirror 上可能没有 +RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ && pip install --no-cache-dir $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ && pip install --no-cache-dir -r requirements/requirements.txt -i ${PIP_MIRROR} \ && rm -rf ~/.cache/pip +ARG VERSION +ENV VERSION=$VERSION + +ADD . . +RUN cd utils \ + && bash -ixeu build.sh \ + && mv ../release/jumpserver /opt/jumpserver \ + && echo > /opt/jumpserver/config.yml + +WORKDIR /opt/jumpserver VOLUME /opt/jumpserver/data VOLUME /opt/jumpserver/logs From 529e3d12e097c2268ad7f680b889d8c50342780e Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 22 Apr 2022 13:50:05 +0800 Subject: [PATCH 068/258] =?UTF-8?q?perf:=20=E5=88=A0=E9=99=A4=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 3ef2daad9..dfbfbe0eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,6 +74,7 @@ ADD . . RUN cd utils \ && bash -ixeu build.sh \ && mv ../release/jumpserver /opt/jumpserver \ + && rm -rf /tmp/build \ && echo > /opt/jumpserver/config.yml WORKDIR /opt/jumpserver From 104d67263457302d98a2005d7f759b0ef4d0033f Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Sun, 24 Apr 2022 14:18:14 +0800 Subject: [PATCH 069/258] perf: client download --- apps/templates/resource_download.html | 28 ++++++++------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index 510b2159e..c9cb4ea6f 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -15,45 +15,33 @@ p {
    -

    JumpServer {% trans 'Client' %}

    +

    JumpServer {% trans 'Client' %} v1.1.4

    {% trans 'JumpServer Client, currently used to launch the client, now only support launch RDP SSH client, The Telnet client will next' %} -{# //JumpServer 客户端,支持 RDP 的本地拉起,后续会支持拉起 ssh。#}

    -

    {% trans 'Microsoft' %} RDP {% trans 'Official' %}{% trans 'Client' %}

    +

    {% trans 'Microsoft' %} RDP {% trans 'Official' %}{% trans 'Client' %} v10.6.7

    {% trans 'macOS needs to download the client to connect RDP asset, which comes with Windows' %}

    -
    - -
    -

    SSH {% trans 'Client' %}

    -

    - {% trans 'Windows needs to download the client to connect SSH assets, and the MacOS system uses its own terminal' %} -

    -
    {% if XPACK_ENABLED %}
    -

    {% trans 'Windows Remote application publisher tools' %}

    +

    {% trans 'Windows Remote application publisher tools' %} v2.0

    {% trans 'Jmservisor is the program used to pull up remote applications in Windows Remote Application publisher' %}

    {% endif %} From 034d0e285c1e322e74f561442cf52601cc8879ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Sun, 24 Apr 2022 17:37:08 +0800 Subject: [PATCH 070/258] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3fdcb7fde..813327bfc 100644 --- a/README.md +++ b/README.md @@ -131,4 +131,3 @@ Licensed under The GNU General Public License version 3 (GPLv3) (the "License") https://www.gnu.org/licenses/gpl-3.0.html Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - From 9804ca5dd0a697a72163a7a043aea337b4df9584 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:38:15 +0800 Subject: [PATCH 071/258] =?UTF-8?q?fix:=20workbench=5Forgs=20=E5=8E=BB?= =?UTF-8?q?=E9=87=8D=20(#8150)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/rbac/models/rolebinding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index 835fbf6a7..ded35a278 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -112,7 +112,7 @@ class RoleBinding(JMSModel): system_bindings = [b for b in bindings if b.scope == Role.Scope.system.value] # 工作台仅限于自己加入的组织 if perm == 'rbac.view_workbench': - all_orgs = user.orgs.all() + all_orgs = user.orgs.all().distinct() else: all_orgs = Organization.objects.all() From 3a3f7eaf715ded06a1d28a07ba08235ef1d4c09d Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 21 Apr 2022 15:18:17 +0800 Subject: [PATCH 072/258] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96SAML2?= =?UTF-8?q?=E7=94=9F=E6=88=90=E7=9A=84metadata=E6=96=87=E4=BB=B6=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=8F=8A=E5=B1=9E=E6=80=A7=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/saml2/views.py | 53 +++++++++++++-------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/authentication/backends/saml2/views.py b/apps/authentication/backends/saml2/views.py index b0b8fef8d..e91fd0660 100644 --- a/apps/authentication/backends/saml2/views.py +++ b/apps/authentication/backends/saml2/views.py @@ -74,27 +74,37 @@ class PrepareRequestMixin: return idp_settings @staticmethod - def get_attribute_consuming_service(): - attr_mapping = settings.SAML2_RENAME_ATTRIBUTES - if attr_mapping and isinstance(attr_mapping, dict): - attr_list = [ - { - "name": sp_key, - "friendlyName": idp_key, "isRequired": True - } - for idp_key, sp_key in attr_mapping.items() - ] - request_attribute_template = { - "attributeConsumingService": { - "isDefault": False, - "serviceName": "JumpServer", - "serviceDescription": "JumpServer", - "requestedAttributes": attr_list - } + def get_request_attributes(): + attr_mapping = settings.SAML2_RENAME_ATTRIBUTES or {} + attr_map_reverse = {v: k for k, v in attr_mapping.items()} + need_attrs = ( + ('username', 'username', True), + ('email', 'email', True), + ('name', 'name', False), + ('phone', 'phone', False), + ('comment', 'comment', False), + ) + attr_list = [] + for name, friend_name, is_required in need_attrs: + rename_name = attr_map_reverse.get(friend_name) + name = rename_name if rename_name else name + attr_list.append({ + "name": name, "isRequired": is_required, + "friendlyName": friend_name, + }) + return attr_list + + def get_attribute_consuming_service(self): + attr_list = self.get_request_attributes() + request_attribute_template = { + "attributeConsumingService": { + "isDefault": False, + "serviceName": "JumpServer", + "serviceDescription": "JumpServer", + "requestedAttributes": attr_list } - return request_attribute_template - else: - return {} + } + return request_attribute_template @staticmethod def get_advanced_settings(): @@ -167,11 +177,14 @@ class PrepareRequestMixin: def get_attributes(self, saml_instance): user_attrs = {} + attr_mapping = settings.SAML2_RENAME_ATTRIBUTES attrs = saml_instance.get_attributes() valid_attrs = ['username', 'name', 'email', 'comment', 'phone'] for attr, value in attrs.items(): attr = attr.rsplit('/', 1)[-1] + if attr_mapping and attr_mapping.get(attr): + attr = attr_mapping.get(attr) if attr not in valid_attrs: continue user_attrs[attr] = self.value_to_str(value) From cb5d8fa13fcdab71794d555268bdd1d5f2b41468 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 26 Apr 2022 11:26:41 +0800 Subject: [PATCH 073/258] =?UTF-8?q?fix:=20=E5=8E=BB=E6=8E=89=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E7=94=9F=E6=88=90=E7=9A=84map=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/static/js/plugins/datatables/pdfmake.min.js.map | 0 apps/static/js/plugins/xterm/addons/attach/attach.js.map | 1 - apps/static/js/plugins/xterm/addons/fit/fit.js.map | 1 - apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js.map | 1 - apps/static/js/plugins/xterm/addons/search/search.js.map | 1 - apps/static/js/plugins/xterm/addons/terminado/terminado.js.map | 1 - apps/static/js/plugins/xterm/addons/webLinks/webLinks.js.map | 1 - .../js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map | 1 - apps/static/js/plugins/xterm/addons/zmodem/zmodem.js.map | 1 - apps/static/js/plugins/xterm/xterm.js.map | 1 - 10 files changed, 9 deletions(-) delete mode 100644 apps/static/js/plugins/datatables/pdfmake.min.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/attach/attach.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/fit/fit.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/search/search.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/terminado/terminado.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/webLinks/webLinks.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map delete mode 100644 apps/static/js/plugins/xterm/addons/zmodem/zmodem.js.map delete mode 100644 apps/static/js/plugins/xterm/xterm.js.map diff --git a/apps/static/js/plugins/datatables/pdfmake.min.js.map b/apps/static/js/plugins/datatables/pdfmake.min.js.map deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/static/js/plugins/xterm/addons/attach/attach.js.map b/apps/static/js/plugins/xterm/addons/attach/attach.js.map deleted file mode 100644 index 8db239a59..000000000 --- a/apps/static/js/plugins/xterm/addons/attach/attach.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"attach.js","sources":["../../../src/addons/attach/attach.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Implements the attach method, that attaches the terminal to a WebSocket stream.\n */\n\nimport { Terminal, IDisposable } from 'xterm';\nimport { IAttachAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\nexport function attach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = term;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n // TODO: This should be typed but there seem to be issues importing the type\n let myTextDecoder: any;\n\n addonTerminal.__getMessage = function(ev: MessageEvent): void {\n let str: string;\n\n if (typeof ev.data === 'object') {\n if (!myTextDecoder) {\n myTextDecoder = new TextDecoder();\n }\n if (ev.data instanceof ArrayBuffer) {\n str = myTextDecoder.decode(ev.data);\n displayData(str);\n } else {\n const fileReader = new FileReader();\n\n fileReader.addEventListener('load', () => {\n str = myTextDecoder.decode(this.result);\n displayData(str);\n });\n fileReader.readAsArrayBuffer(ev.data);\n }\n } else if (typeof ev.data === 'string') {\n displayData(ev.data);\n } else {\n throw Error(`Cannot handle \"${typeof ev.data}\" websocket message.`);\n }\n };\n\n /**\n * Push data to buffer or write it in the terminal.\n * This is used as a callback for FileReader.onload.\n *\n * @param str String decoded by FileReader.\n * @param data The data of the EventMessage.\n */\n function displayData(str?: string, data?: string): void {\n if (buffered) {\n addonTerminal.__pushToBuffer(str || data);\n } else {\n addonTerminal.write(str || data);\n }\n }\n\n addonTerminal.__sendData = (data: string) => {\n if (socket.readyState !== 1) {\n return;\n }\n socket.send(data);\n };\n\n addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));\n\n if (bidirectional) {\n addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData));\n }\n\n addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket)));\n addonTerminal._core.register(addSocketListener(socket, 'error', () => detach(addonTerminal, socket)));\n}\n\nfunction addSocketListener(socket: WebSocket, type: string, handler: (this: WebSocket, ev: Event) => any): IDisposable {\n socket.addEventListener(type, handler);\n return {\n dispose: () => {\n if (!handler) {\n // Already disposed\n return;\n }\n socket.removeEventListener(type, handler);\n handler = null;\n }\n };\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function detach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).attach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n attach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).detach = function (socket: WebSocket): void {\n detach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADmBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AA9EA;AAgFA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAcA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/fit/fit.js.map b/apps/static/js/plugins/xterm/addons/fit/fit.js.map deleted file mode 100644 index 5f1042e97..000000000 --- a/apps/static/js/plugins/xterm/addons/fit/fit.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fit.js","sources":["../../../src/addons/fit/fit.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Fit terminal columns and rows to the dimensions of its DOM element.\n *\n * ## Approach\n *\n * Rows: Truncate the division of the terminal parent element height by the\n * terminal row height.\n * Columns: Truncate the division of the terminal parent element width by the\n * terminal character width (apply display: inline at the terminal\n * row and truncate its width with the current number of columns).\n */\n\nimport { Terminal } from 'xterm';\n\nexport interface IGeometry {\n rows: number;\n cols: number;\n}\n\nexport function proposeGeometry(term: Terminal): IGeometry {\n if (!term.element.parentElement) {\n return null;\n }\n const parentElementStyle = window.getComputedStyle(term.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(term.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - (term)._core.viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (term)._core.renderer.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (term)._core.renderer.dimensions.actualCellHeight)\n };\n return geometry;\n}\n\nexport function fit(term: Terminal): void {\n const geometry = proposeGeometry(term);\n if (geometry) {\n // Force a full render\n if (term.rows !== geometry.rows || term.cols !== geometry.cols) {\n (term)._core.renderer.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (terminalConstructor.prototype).fit = function (): void {\n fit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADsBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js.map b/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js.map deleted file mode 100644 index dafbbe62e..000000000 --- a/apps/static/js/plugins/xterm/addons/fullscreen/fullscreen.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fullscreen.js","sources":["../../../src/addons/fullscreen/fullscreen.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n * Toggle the given terminal's fullscreen mode.\n * @param term The terminal to toggle full screen mode\n * @param fullscreen Toggle fullscreen on (true) or off (false)\n */\nexport function toggleFullScreen(term: Terminal, fullscreen: boolean): void {\n let fn: string;\n\n if (typeof fullscreen === 'undefined') {\n fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';\n } else if (!fullscreen) {\n fn = 'remove';\n } else {\n fn = 'add';\n }\n\n term.element.classList[fn]('fullscreen');\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {\n toggleFullScreen(this, fullscreen);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADYA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/search/search.js.map b/apps/static/js/plugins/xterm/addons/search/search.js.map deleted file mode 100644 index eca9836aa..000000000 --- a/apps/static/js/plugins/xterm/addons/search/search.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"search.js","sources":["../../../src/addons/search/search.ts","../../../src/addons/search/SearchHelper.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { SearchHelper } from './SearchHelper';\nimport { Terminal } from 'xterm';\nimport { ISearchAddonTerminal } from './Interfaces';\n\n/**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\nexport function findNext(terminal: Terminal, term: string): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findNext(term);\n}\n\n/**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\nexport function findPrevious(terminal: Terminal, term: string): boolean {\n const addonTerminal = terminal;\n if (!addonTerminal.__searchHelper) {\n addonTerminal.__searchHelper = new SearchHelper(addonTerminal);\n }\n return addonTerminal.__searchHelper.findPrevious(term);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).findNext = function(term: string): boolean {\n return findNext(this, term);\n };\n\n (terminalConstructor.prototype).findPrevious = function(term: string): boolean {\n return findPrevious(this, term);\n };\n}\n","/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { ISearchHelper, ISearchAddonTerminal } from './Interfaces';\n\ninterface ISearchResult {\n term: string;\n col: number;\n row: number;\n}\n\n/**\n * A class that knows how to search the terminal and how to display the results.\n */\nexport class SearchHelper implements ISearchHelper {\n constructor(private _terminal: ISearchAddonTerminal) {\n // TODO: Search for multiple instances on 1 line\n // TODO: Don't use the actual selection, instead use a \"find selection\" so multiple instances can be highlighted\n // TODO: Highlight other instances in the viewport\n // TODO: Support regex, case sensitivity, etc.\n }\n\n /**\n * Find the next instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findNext(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal._core.buffer.ydisp;\n if (this._terminal._core.selectionManager.selectionEnd) {\n // Start from the selection end if there is a selection\n startRow = this._terminal._core.selectionManager.selectionEnd[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = 0; y < startRow; y++) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Find the previous instance of the term, then scroll to and select it. If it\n * doesn't exist, do nothing.\n * @param term Tne search term.\n * @return Whether a result was found.\n */\n public findPrevious(term: string): boolean {\n if (!term || term.length === 0) {\n return false;\n }\n\n let result: ISearchResult;\n\n let startRow = this._terminal._core.buffer.ydisp;\n if (this._terminal._core.selectionManager.selectionStart) {\n // Start from the selection end if there is a selection\n startRow = this._terminal._core.selectionManager.selectionStart[1];\n }\n\n // Search from ydisp + 1 to end\n for (let y = startRow - 1; y >= 0; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n\n // Search from the top to the current ydisp\n if (!result) {\n for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {\n result = this._findInLine(term, y);\n if (result) {\n break;\n }\n }\n }\n\n // Set selection and scroll if a result was found\n return this._selectResult(result);\n }\n\n /**\n * Searches a line for a search term.\n * @param term Tne search term.\n * @param y The line to search.\n * @return The search result if it was found.\n */\n private _findInLine(term: string, y: number): ISearchResult {\n const lowerStringLine = this._terminal._core.buffer.translateBufferLineToString(y, true).toLowerCase();\n const lowerTerm = term.toLowerCase();\n let searchIndex = lowerStringLine.indexOf(lowerTerm);\n if (searchIndex >= 0) {\n const line = this._terminal._core.buffer.lines.get(y);\n for (let i = 0; i < searchIndex; i++) {\n const charData = line[i];\n // Adjust the searchIndex to normalize emoji into single chars\n const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];\n if (char.length > 1) {\n searchIndex -= char.length - 1;\n }\n // Adjust the searchIndex for empty characters following wide unicode\n // chars (eg. CJK)\n const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];\n if (charWidth === 0) {\n searchIndex++;\n }\n }\n return {\n term,\n col: searchIndex,\n row: y\n };\n }\n }\n\n /**\n * Selects and scrolls to a result.\n * @param result The result to select.\n * @return Whethera result was selected.\n */\n private _selectResult(result: ISearchResult): boolean {\n if (!result) {\n return false;\n }\n this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);\n this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);\n return true;\n }\n}\n",null],"names":[],"mappings":"AEAA;;;ADgBA;AACA;AAAA;AAKA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAzIa;;;;;ADXb;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AANA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/terminado/terminado.js.map b/apps/static/js/plugins/xterm/addons/terminado/terminado.js.map deleted file mode 100644 index 2cbf18311..000000000 --- a/apps/static/js/plugins/xterm/addons/terminado/terminado.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminado.js","sources":["../../../src/addons/terminado/terminado.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This module provides methods for attaching a terminal to a terminado\n * WebSocket stream.\n */\n\nimport { Terminal } from 'xterm';\nimport { ITerminadoAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\nexport function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = term;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n addonTerminal.__getMessage = (ev: MessageEvent) => {\n const data = JSON.parse(ev.data);\n if (data[0] === 'stdout') {\n if (buffered) {\n addonTerminal.__pushToBuffer(data[1]);\n } else {\n addonTerminal.write(data[1]);\n }\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n socket.send(JSON.stringify(['stdin', data]));\n };\n\n addonTerminal.__setSize = (size: {rows: number, cols: number}) => {\n socket.send(JSON.stringify(['set_size', size.rows, size.cols]));\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal.on('data', addonTerminal.__sendData);\n }\n addonTerminal.on('resize', addonTerminal.__setSize);\n\n socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));\n socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function terminadoDetach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket - The socket to attach the current terminal.\n * @param bidirectional - Whether the terminal should send data to the socket as well.\n * @param buffered - Whether the rendering of incoming data should happen instantly or at a\n * maximum frequency of 1 rendering per 10ms.\n */\n (terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n return terminadoAttach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {\n return terminadoDetach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADoBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/CA;AAuDA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAaA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/webLinks/webLinks.js.map b/apps/static/js/plugins/xterm/addons/webLinks/webLinks.js.map deleted file mode 100644 index fd39bfb8b..000000000 --- a/apps/static/js/plugins/xterm/addons/webLinks/webLinks.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"webLinks.js","sources":["../../../src/addons/webLinks/webLinks.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ILinkMatcherOptions } from 'xterm';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\nfunction handleLink(event: MouseEvent, uri: string): void {\n window.open(uri, '_blank');\n}\n\n/**\n * Initialize the web links addon, registering the link matcher.\n * @param term The terminal to use web links within.\n * @param handler A custom handler to use.\n * @param options Custom options to use, matchIndex will always be ignored.\n */\nexport function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void {\n options.matchIndex = 1;\n term.registerLinkMatcher(strictUrlRegex, handler, options);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {\n webLinksInit(this, handler, options);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAAA;AAAA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map b/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map deleted file mode 100644 index da8f4acb1..000000000 --- a/apps/static/js/plugins/xterm/addons/winptyCompat/winptyCompat.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"winptyCompat.js","sources":["../../../src/addons/winptyCompat/winptyCompat.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\nimport { IWinptyCompatAddonTerminal } from './Interfaces';\n\nexport function winptyCompatInit(terminal: Terminal): void {\n const addonTerminal = terminal;\n\n // Don't do anything when the platform is not Windows\n const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;\n if (!isWindows) {\n return;\n }\n\n // Winpty does not support wraparound mode which means that lines will never\n // be marked as wrapped. This causes issues for things like copying a line\n // retaining the wrapped new line characters or if consumers are listening\n // in on the data stream.\n //\n // The workaround for this is to listen to every incoming line feed and mark\n // the line as wrapped if the last character in the previous line is not a\n // space. This is certainly not without its problems, but generally on\n // Windows when text reaches the end of the terminal it's likely going to be\n // wrapped.\n addonTerminal.on('linefeed', () => {\n const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);\n const lastChar = line[addonTerminal.cols - 1];\n\n if (lastChar[3] !== 32 /* ' ' */) {\n const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);\n (nextLine).isWrapped = true;\n }\n });\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (terminalConstructor.prototype).winptyCompatInit = function (): void {\n winptyCompatInit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADQA;AACA;AAGA;AACA;AACA;AACA;AAYA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA5BA;AA8BA;AACA;AACA;AACA;AACA;AAJA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/addons/zmodem/zmodem.js.map b/apps/static/js/plugins/xterm/addons/zmodem/zmodem.js.map deleted file mode 100644 index 74927e7b5..000000000 --- a/apps/static/js/plugins/xterm/addons/zmodem/zmodem.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"zmodem.js","sources":["../../../src/addons/zmodem/zmodem.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n *\n * Allow xterm.js to handle ZMODEM uploads and downloads.\n *\n * This addon is a wrapper around zmodem.js. It adds the following to the\n * Terminal class:\n *\n * - function `zmodemAttach(, )` - creates a Zmodem.Sentry\n * on the passed WebSocket object. The Object passed is optional and\n * can contain:\n * - noTerminalWriteOutsideSession: Suppress writes from the Sentry\n * object to the Terminal while there is no active Session. This\n * is necessary for compatibility with, for example, the\n * `attach.js` addon.\n *\n * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.\n * Passes the zmodem.js Detection object.\n *\n * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.\n *\n * You’ll need to provide logic to handle uploads and downloads.\n * See zmodem.js’s documentation for more details.\n *\n * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have\n * used the `attach` or `terminado` addons, you’ll need to suspend their\n * operation for the duration of the ZMODEM session. (The demo does this\n * via `detach()` and a re-`attach()`.)\n */\n\nlet zmodem;\n\nexport interface IZmodemOptions {\n noTerminalWriteOutsideSession?: boolean;\n}\n\nfunction zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {\n const term = this;\n const senderFunc = (octets: ArrayLike) => ws.send(new Uint8Array(octets));\n\n let zsentry;\n\n function shouldWrite(): boolean {\n return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;\n }\n\n zsentry = new zmodem.Sentry({\n to_terminal: (octets: ArrayLike) => {\n if (shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (term).emit('zmodemRetract'),\n on_detect: (detection: any) => (term).emit('zmodemDetect', detection)\n });\n\n function handleWSMessage(evt: MessageEvent): void {\n\n // In testing with xterm.js’s demo the first message was\n // always text even if the rest were binary. While that\n // may be specific to xterm.js’s demo, ultimately we\n // should reject anything that isn’t binary.\n if (typeof evt.data === 'string') {\n if (shouldWrite()) {\n term.write(evt.data);\n }\n }\n else {\n zsentry.consume(evt.data);\n }\n }\n\n ws.binaryType = 'arraybuffer';\n ws.addEventListener('message', handleWSMessage);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n zmodem = (typeof window === 'object') ? (window).Zmodem : {Browser: null}; // Nullify browser for tests\n\n (terminalConstructor.prototype).zmodemAttach = zmodemAttach;\n (terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;\n}\n",null],"names":[],"mappings":"ACAA;;;ADoCA;AAMA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AALA;"} \ No newline at end of file diff --git a/apps/static/js/plugins/xterm/xterm.js.map b/apps/static/js/plugins/xterm/xterm.js.map deleted file mode 100644 index 76134bb59..000000000 --- a/apps/static/js/plugins/xterm/xterm.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"xterm.js","sources":["../src/xterm.js","../src/utils/Mouse.ts","../src/utils/Generic.ts","../src/utils/DomElementObjectPool.ts","../src/utils/CircularList.ts","../src/utils/CharMeasure.ts","../src/utils/BufferLine.ts","../src/utils/Browser.ts","../src/handlers/Clipboard.ts","../src/Viewport.ts","../src/SelectionModel.ts","../src/SelectionManager.ts","../src/Renderer.ts","../src/Parser.ts","../src/Linkifier.ts","../src/InputHandler.ts","../src/EventEmitter.ts","../src/EscapeSequences.ts","../src/CompositionHelper.ts","../src/Charsets.ts","../src/BufferSet.ts","../src/Buffer.ts","../node_modules/browserify/node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * xterm.js: xterm, in the browser\n * Originally forked from (with the author's permission):\n * Fabrice Bellard's javascript vt100 for jslinux:\n * http://bellard.org/jslinux/\n * Copyright (c) 2011 Fabrice Bellard\n * The original design remains. The terminal itself\n * has been extended to include xterm CSI codes, among\n * other features.\n * @license MIT\n */\n\nimport { BufferSet } from './BufferSet';\nimport { CompositionHelper } from './CompositionHelper';\nimport { EventEmitter } from './EventEmitter';\nimport { Viewport } from './Viewport';\nimport { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './handlers/Clipboard';\nimport { CircularList } from './utils/CircularList';\nimport { C0 } from './EscapeSequences';\nimport { InputHandler } from './InputHandler';\nimport { Parser } from './Parser';\nimport { Renderer } from './Renderer';\nimport { Linkifier } from './Linkifier';\nimport { SelectionManager } from './SelectionManager';\nimport { CharMeasure } from './utils/CharMeasure';\nimport * as Browser from './utils/Browser';\nimport * as Mouse from './utils/Mouse';\nimport { CHARSETS } from './Charsets';\nimport { getRawByteCoords } from './utils/Mouse';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * Terminal Emulation References:\n * http://vt100.net/\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * http://invisible-island.net/vttest/\n * http://www.inwap.com/pdp10/ansicode.txt\n * http://linux.die.net/man/4/console_codes\n * http://linux.die.net/man/7/urxvt\n */\n\n// Let it work inside Node.js for automated testing purposes.\nvar document = (typeof window != 'undefined') ? window.document : null;\n\n/**\n * The amount of write requests to queue before sending an XOFF signal to the\n * pty process. This number must be small in order for ^C and similar sequences\n * to be responsive.\n */\nvar WRITE_BUFFER_PAUSE_THRESHOLD = 5;\n\n/**\n * The number of writes to perform in a single batch before allowing the\n * renderer to catch up with a 0ms setTimeout.\n */\nvar WRITE_BATCH_SIZE = 300;\n\n/**\n * The time between cursor blinks. This is driven by JS rather than a CSS\n * animation due to a bug in Chromium that causes it to use excessive CPU time.\n * See https://github.com/Microsoft/vscode/issues/22900\n */\nvar CURSOR_BLINK_INTERVAL = 600;\n\n/**\n * Terminal\n */\n\n/**\n * Creates a new `Terminal` object.\n *\n * @param {object} options An object containing a set of options, the available options are:\n * - `cursorBlink` (boolean): Whether the terminal cursor blinks\n * - `cols` (number): The number of columns of the terminal (horizontal size)\n * - `rows` (number): The number of rows of the terminal (vertical size)\n *\n * @public\n * @class Xterm Xterm\n * @alias module:xterm/src/xterm\n */\nfunction Terminal(options) {\n var self = this;\n\n if (!(this instanceof Terminal)) {\n return new Terminal(arguments[0], arguments[1], arguments[2]);\n }\n\n self.browser = Browser;\n self.cancel = Terminal.cancel;\n\n EventEmitter.call(this);\n\n if (typeof options === 'number') {\n options = {\n cols: arguments[0],\n rows: arguments[1],\n handler: arguments[2]\n };\n }\n\n options = options || {};\n\n\n Object.keys(Terminal.defaults).forEach(function(key) {\n if (options[key] == null) {\n options[key] = Terminal.options[key];\n\n if (Terminal[key] !== Terminal.defaults[key]) {\n options[key] = Terminal[key];\n }\n }\n self[key] = options[key];\n });\n\n if (options.colors.length === 8) {\n options.colors = options.colors.concat(Terminal._colors.slice(8));\n } else if (options.colors.length === 16) {\n options.colors = options.colors.concat(Terminal._colors.slice(16));\n } else if (options.colors.length === 10) {\n options.colors = options.colors.slice(0, -2).concat(\n Terminal._colors.slice(8, -2), options.colors.slice(-2));\n } else if (options.colors.length === 18) {\n options.colors = options.colors.concat(\n Terminal._colors.slice(16, -2), options.colors.slice(-2));\n }\n this.colors = options.colors;\n\n this.options = options;\n\n // this.context = options.context || window;\n // this.document = options.document || document;\n this.parent = options.body || options.parent || (\n document ? document.getElementsByTagName('body')[0] : null\n );\n\n this.cols = options.cols || options.geometry[0];\n this.rows = options.rows || options.geometry[1];\n this.geometry = [this.cols, this.rows];\n\n if (options.handler) {\n this.on('data', options.handler);\n }\n\n this.cursorState = 0;\n this.cursorHidden = false;\n this.convertEol;\n this.queue = '';\n this.customKeyEventHandler = null;\n this.cursorBlinkInterval = null;\n\n // modes\n this.applicationKeypad = false;\n this.applicationCursor = false;\n this.originMode = false;\n this.insertMode = false;\n this.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n\n // charset\n this.charset = null;\n this.gcharset = null;\n this.glevel = 0;\n this.charsets = [null];\n\n // mouse properties\n this.decLocator;\n this.x10Mouse;\n this.vt200Mouse;\n this.vt300Mouse;\n this.normalMouse;\n this.mouseEvents;\n this.sendFocus;\n this.utfMouse;\n this.sgrMouse;\n this.urxvtMouse;\n\n // misc\n this.element;\n this.children;\n this.refreshStart;\n this.refreshEnd;\n this.savedX;\n this.savedY;\n this.savedCols;\n\n // stream\n this.readable = true;\n this.writable = true;\n\n this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);\n this.curAttr = this.defAttr;\n\n this.params = [];\n this.currentParam = 0;\n this.prefix = '';\n this.postfix = '';\n\n this.inputHandler = new InputHandler(this);\n this.parser = new Parser(this.inputHandler, this);\n // Reuse renderer if the Terminal is being recreated via a Terminal.reset call.\n this.renderer = this.renderer || null;\n this.selectionManager = this.selectionManager || null;\n this.linkifier = this.linkifier || new Linkifier();\n\n // user input states\n this.writeBuffer = [];\n this.writeInProgress = false;\n\n /**\n * Whether _xterm.js_ sent XOFF in order to catch up with the pty process.\n * This is a distinct state from writeStopped so that if the user requested\n * XOFF via ^S that it will not automatically resume when the writeBuffer goes\n * below threshold.\n */\n this.xoffSentToCatchUp = false;\n\n /** Whether writing has been stopped as a result of XOFF */\n this.writeStopped = false;\n\n // leftover surrogate high from previous write invocation\n this.surrogate_high = '';\n\n // Create the terminal's buffers and set the current buffer\n this.buffers = new BufferSet(this);\n this.buffer = this.buffers.active; // Convenience shortcut;\n this.buffers.on('activate', function (buffer) {\n this._terminal.buffer = buffer;\n });\n\n // Ensure the selection manager has the correct buffer\n if (this.selectionManager) {\n this.selectionManager.setBuffer(this.buffer.lines);\n }\n\n this.setupStops();\n\n // Store if user went browsing history in scrollback\n this.userScrolling = false;\n}\n\ninherits(Terminal, EventEmitter);\n\n/**\n * back_color_erase feature for xterm.\n */\nTerminal.prototype.eraseAttr = function() {\n // if (this.is('screen')) return this.defAttr;\n return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);\n};\n\n/**\n * Colors\n */\n\n// Colors 0-15\nTerminal.tangoColors = [\n // dark:\n '#2e3436',\n '#cc0000',\n '#4e9a06',\n '#c4a000',\n '#3465a4',\n '#75507b',\n '#06989a',\n '#d3d7cf',\n // bright:\n '#555753',\n '#ef2929',\n '#8ae234',\n '#fce94f',\n '#729fcf',\n '#ad7fa8',\n '#34e2e2',\n '#eeeeec'\n];\n\n// Colors 0-15 + 16-255\n// Much thanks to TooTallNate for writing this.\nTerminal.colors = (function() {\n var colors = Terminal.tangoColors.slice()\n , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]\n , i;\n\n // 16-231\n i = 0;\n for (; i < 216; i++) {\n out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]);\n }\n\n // 232-255 (grey)\n i = 0;\n for (; i < 24; i++) {\n r = 8 + i * 10;\n out(r, r, r);\n }\n\n function out(r, g, b) {\n colors.push('#' + hex(r) + hex(g) + hex(b));\n }\n\n function hex(c) {\n c = c.toString(16);\n return c.length < 2 ? '0' + c : c;\n }\n\n return colors;\n})();\n\nTerminal._colors = Terminal.colors.slice();\n\nTerminal.vcolors = (function() {\n var out = []\n , colors = Terminal.colors\n , i = 0\n , color;\n\n for (; i < 256; i++) {\n color = parseInt(colors[i].substring(1), 16);\n out.push([\n (color >> 16) & 0xff,\n (color >> 8) & 0xff,\n color & 0xff\n ]);\n }\n\n return out;\n})();\n\n/**\n * Options\n */\n\nTerminal.defaults = {\n colors: Terminal.colors,\n theme: 'default',\n convertEol: false,\n termName: 'xterm',\n geometry: [80, 24],\n cursorBlink: false,\n cursorStyle: 'block',\n visualBell: false,\n popOnBell: false,\n scrollback: 1000,\n screenKeys: false,\n debug: false,\n cancelEvents: false,\n disableStdin: false,\n useFlowControl: false,\n tabStopWidth: 8\n // programFeatures: false,\n // focusKeys: false,\n};\n\nTerminal.options = {};\n\nTerminal.focus = null;\n\neach(keys(Terminal.defaults), function(key) {\n Terminal[key] = Terminal.defaults[key];\n Terminal.options[key] = Terminal.defaults[key];\n});\n\n/**\n * Focus the terminal. Delegates focus handling to the terminal's DOM element.\n */\nTerminal.prototype.focus = function() {\n return this.textarea.focus();\n};\n\n/**\n * Retrieves an option's value from the terminal.\n * @param {string} key The option key.\n */\nTerminal.prototype.getOption = function(key) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n\n if (typeof this.options[key] !== 'undefined') {\n return this.options[key];\n }\n\n return this[key];\n};\n\n/**\n * Sets an option on the terminal.\n * @param {string} key The option key.\n * @param {string} value The option value.\n */\nTerminal.prototype.setOption = function(key, value) {\n if (!(key in Terminal.defaults)) {\n throw new Error('No option with key \"' + key + '\"');\n }\n switch (key) {\n case 'scrollback':\n if (value < this.rows) {\n let msg = 'Setting the scrollback value less than the number of rows ';\n\n msg += `(${this.rows}) is not allowed.`;\n\n console.warn(msg);\n return false;\n }\n\n if (this.options[key] !== value) {\n if (this.buffer.lines.length > value) {\n const amountToTrim = this.buffer.lines.length - value;\n const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);\n this.buffer.lines.trimStart(amountToTrim);\n this.buffer.ybase = Math.max(this.buffer.ybase - amountToTrim, 0);\n this.buffer.ydisp = Math.max(this.buffer.ydisp - amountToTrim, 0);\n if (needsRefresh) {\n this.refresh(0, this.rows - 1);\n }\n }\n this.buffer.lines.maxLength = value;\n this.viewport.syncScrollArea();\n }\n break;\n }\n this[key] = value;\n this.options[key] = value;\n switch (key) {\n case 'cursorBlink': this.setCursorBlinking(value); break;\n case 'cursorStyle':\n this.element.classList.toggle(`xterm-cursor-style-block`, value === 'block');\n this.element.classList.toggle(`xterm-cursor-style-underline`, value === 'underline');\n this.element.classList.toggle(`xterm-cursor-style-bar`, value === 'bar');\n break;\n case 'tabStopWidth': this.setupStops(); break;\n }\n};\n\nTerminal.prototype.restartCursorBlinking = function () {\n this.setCursorBlinking(this.options.cursorBlink);\n};\n\nTerminal.prototype.setCursorBlinking = function (enabled) {\n this.element.classList.toggle('xterm-cursor-blink', enabled);\n this.clearCursorBlinkingInterval();\n if (enabled) {\n var self = this;\n this.cursorBlinkInterval = setInterval(function () {\n self.element.classList.toggle('xterm-cursor-blink-on');\n }, CURSOR_BLINK_INTERVAL);\n }\n};\n\nTerminal.prototype.clearCursorBlinkingInterval = function () {\n this.element.classList.remove('xterm-cursor-blink-on');\n if (this.cursorBlinkInterval) {\n clearInterval(this.cursorBlinkInterval);\n this.cursorBlinkInterval = null;\n }\n};\n\n/**\n * Binds the desired focus behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindFocus = function (term) {\n on(term.textarea, 'focus', function (ev) {\n if (term.sendFocus) {\n term.send(C0.ESC + '[I');\n }\n term.element.classList.add('focus');\n term.showCursor();\n term.restartCursorBlinking.apply(term);\n Terminal.focus = term;\n term.emit('focus', {terminal: term});\n });\n};\n\n/**\n * Blur the terminal. Delegates blur handling to the terminal's DOM element.\n */\nTerminal.prototype.blur = function() {\n return this.textarea.blur();\n};\n\n/**\n * Binds the desired blur behavior on a given terminal object.\n *\n * @static\n */\nTerminal.bindBlur = function (term) {\n on(term.textarea, 'blur', function (ev) {\n term.refresh(term.buffer.y, term.buffer.y);\n if (term.sendFocus) {\n term.send(C0.ESC + '[O');\n }\n term.element.classList.remove('focus');\n term.clearCursorBlinkingInterval.apply(term);\n Terminal.focus = null;\n term.emit('blur', {terminal: term});\n });\n};\n\n/**\n * Initialize default behavior\n */\nTerminal.prototype.initGlobal = function() {\n var term = this;\n\n Terminal.bindKeys(this);\n Terminal.bindFocus(this);\n Terminal.bindBlur(this);\n\n // Bind clipboard functionality\n on(this.element, 'copy', event => {\n // If mouse events are active it means the selection manager is disabled and\n // copy should be handled by the host program.\n if (!term.hasSelection()) {\n return;\n }\n copyHandler(event, term, this.selectionManager);\n });\n const pasteHandlerWrapper = event => pasteHandler(event, term);\n on(this.textarea, 'paste', pasteHandlerWrapper);\n on(this.element, 'paste', pasteHandlerWrapper);\n\n // Handle right click context menus\n if (term.browser.isFirefox) {\n // Firefox doesn't appear to fire the contextmenu event on right click\n on(this.element, 'mousedown', event => {\n if (event.button == 2) {\n rightClickHandler(event, this.textarea, this.selectionManager);\n }\n });\n } else {\n on(this.element, 'contextmenu', event => {\n rightClickHandler(event, this.textarea, this.selectionManager);\n });\n }\n\n // Move the textarea under the cursor when middle clicking on Linux to ensure\n // middle click to paste selection works. This only appears to work in Chrome\n // at the time is writing.\n if (term.browser.isLinux) {\n // Use auxclick event over mousedown the latter doesn't seem to work. Note\n // that the regular click event doesn't fire for the middle mouse button.\n on(this.element, 'auxclick', event => {\n if (event.button === 1) {\n moveTextAreaUnderMouseCursor(event, this.textarea, this.selectionManager);\n }\n });\n }\n};\n\n/**\n * Apply key handling to the terminal\n */\nTerminal.bindKeys = function(term) {\n on(term.element, 'keydown', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyDown(ev);\n }, true);\n\n on(term.element, 'keypress', function(ev) {\n if (document.activeElement != this) {\n return;\n }\n term.keyPress(ev);\n }, true);\n\n on(term.element, 'keyup', function(ev) {\n if (!wasMondifierKeyOnlyEvent(ev)) {\n term.focus(term);\n }\n }, true);\n\n on(term.textarea, 'keydown', function(ev) {\n term.keyDown(ev);\n }, true);\n\n on(term.textarea, 'keypress', function(ev) {\n term.keyPress(ev);\n // Truncate the textarea's value, since it is not needed\n this.value = '';\n }, true);\n\n on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper));\n on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper));\n on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper));\n term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));\n term.on('refresh', function (data) {\n term.queueLinkification(data.start, data.end)\n });\n};\n\n\n/**\n * Insert the given row to the terminal or produce a new one\n * if no row argument is passed. Return the inserted row.\n * @param {HTMLElement} row (optional) The row to append to the terminal.\n */\nTerminal.prototype.insertRow = function (row) {\n if (typeof row != 'object') {\n row = document.createElement('div');\n }\n\n this.rowContainer.appendChild(row);\n this.children.push(row);\n\n return row;\n};\n\n/**\n * Opens the terminal within an element.\n *\n * @param {HTMLElement} parent The element to create the terminal within.\n * @param {boolean} focus Focus the terminal, after it gets instantiated in the DOM\n */\nTerminal.prototype.open = function(parent, focus) {\n var self=this, i=0, div;\n\n this.parent = parent || this.parent;\n\n if (!this.parent) {\n throw new Error('Terminal requires a parent element.');\n }\n\n // Grab global elements\n this.context = this.parent.ownerDocument.defaultView;\n this.document = this.parent.ownerDocument;\n this.body = this.document.getElementsByTagName('body')[0];\n\n //Create main element container\n this.element = this.document.createElement('div');\n this.element.classList.add('terminal');\n this.element.classList.add('xterm');\n this.element.classList.add('xterm-theme-' + this.theme);\n this.element.classList.add(`xterm-cursor-style-${this.options.cursorStyle}`);\n this.setCursorBlinking(this.options.cursorBlink);\n\n this.element.setAttribute('tabindex', 0);\n\n this.viewportElement = document.createElement('div');\n this.viewportElement.classList.add('xterm-viewport');\n this.element.appendChild(this.viewportElement);\n this.viewportScrollArea = document.createElement('div');\n this.viewportScrollArea.classList.add('xterm-scroll-area');\n this.viewportElement.appendChild(this.viewportScrollArea);\n\n // Create the selection container.\n this.selectionContainer = document.createElement('div');\n this.selectionContainer.classList.add('xterm-selection');\n this.element.appendChild(this.selectionContainer);\n\n // Create the container that will hold the lines of the terminal and then\n // produce the lines the lines.\n this.rowContainer = document.createElement('div');\n this.rowContainer.classList.add('xterm-rows');\n this.element.appendChild(this.rowContainer);\n this.children = [];\n this.linkifier.attachToDom(document, this.children);\n\n // Create the container that will hold helpers like the textarea for\n // capturing DOM Events. Then produce the helpers.\n this.helperContainer = document.createElement('div');\n this.helperContainer.classList.add('xterm-helpers');\n // TODO: This should probably be inserted once it's filled to prevent an additional layout\n this.element.appendChild(this.helperContainer);\n this.textarea = document.createElement('textarea');\n this.textarea.classList.add('xterm-helper-textarea');\n this.textarea.setAttribute('autocorrect', 'off');\n this.textarea.setAttribute('autocapitalize', 'off');\n this.textarea.setAttribute('spellcheck', 'false');\n this.textarea.tabIndex = 0;\n this.textarea.addEventListener('focus', function() {\n self.emit('focus', {terminal: self});\n });\n this.textarea.addEventListener('blur', function() {\n self.emit('blur', {terminal: self});\n });\n this.helperContainer.appendChild(this.textarea);\n\n this.compositionView = document.createElement('div');\n this.compositionView.classList.add('composition-view');\n this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this);\n this.helperContainer.appendChild(this.compositionView);\n\n this.charSizeStyleElement = document.createElement('style');\n this.helperContainer.appendChild(this.charSizeStyleElement);\n\n for (; i < this.rows; i++) {\n this.insertRow();\n }\n this.parent.appendChild(this.element);\n\n this.charMeasure = new CharMeasure(document, this.helperContainer);\n this.charMeasure.on('charsizechanged', function () {\n self.updateCharSizeStyles();\n });\n this.charMeasure.measure();\n\n this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);\n this.renderer = new Renderer(this);\n this.selectionManager = new SelectionManager(\n this, this.buffer.lines, this.rowContainer, this.charMeasure\n );\n this.selectionManager.on('refresh', data => {\n this.renderer.refreshSelection(data.start, data.end);\n });\n this.selectionManager.on('newselection', text => {\n // If there's a new selection, put it into the textarea, focus and select it\n // in order to register it as a selection on the OS. This event is fired\n // only on Linux to enable middle click to paste selection.\n this.textarea.value = text;\n this.textarea.focus();\n this.textarea.select();\n });\n this.on('scroll', () => this.selectionManager.refresh());\n this.viewportElement.addEventListener('scroll', () => this.selectionManager.refresh());\n\n // Setup loop that draws to screen\n this.refresh(0, this.rows - 1);\n\n // Initialize global actions that\n // need to be taken on the document.\n this.initGlobal();\n\n /**\n * Automatic focus functionality.\n * TODO: Default to `false` starting with xterm.js 3.0.\n */\n if (typeof focus == 'undefined') {\n let message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\\n';\n\n message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';\n message += 'it will default to `false`.';\n\n console.warn(message);\n focus = true;\n }\n\n if (focus) {\n this.focus();\n }\n\n // Listen for mouse events and translate\n // them into terminal mouse protocols.\n this.bindMouse();\n\n /**\n * This event is emitted when terminal has completed opening.\n *\n * @event open\n */\n this.emit('open');\n};\n\n\n/**\n * Attempts to load an add-on using CommonJS or RequireJS (whichever is available).\n * @param {string} addon The name of the addon to load\n * @static\n */\nTerminal.loadAddon = function(addon, callback) {\n if (typeof exports === 'object' && typeof module === 'object') {\n // CommonJS\n return require('./addons/' + addon + '/' + addon);\n } else if (typeof define == 'function') {\n // RequireJS\n return require(['./addons/' + addon + '/' + addon], callback);\n } else {\n console.error('Cannot load a module without a CommonJS or RequireJS environment.');\n return false;\n }\n};\n\n/**\n * Updates the helper CSS class with any changes necessary after the terminal's\n * character width has been changed.\n */\nTerminal.prototype.updateCharSizeStyles = function() {\n this.charSizeStyleElement.textContent =\n `.xterm-wide-char{width:${this.charMeasure.width * 2}px;}` +\n `.xterm-normal-char{width:${this.charMeasure.width}px;}` +\n `.xterm-rows > div{height:${this.charMeasure.height}px;}`;\n}\n\n/**\n * XTerm mouse events\n * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking\n * To better understand these\n * the xterm code is very helpful:\n * Relevant files:\n * button.c, charproc.c, misc.c\n * Relevant functions in xterm/button.c:\n * BtnCode, EmitButtonCode, EditorButton, SendMousePosition\n */\nTerminal.prototype.bindMouse = function() {\n var el = this.element, self = this, pressed = 32;\n\n // mouseup, mousedown, wheel\n // left click: ^[[M 3<^[[M#3<\n // wheel up: ^[[M`3>\n function sendButton(ev) {\n var button\n , pos;\n\n // get the xterm-style button\n button = getButton(ev);\n\n // get mouse coordinates\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n sendEvent(button, pos);\n\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n pressed = button;\n break;\n case 'mouseup':\n // keep it at the left\n // button, just in case.\n pressed = 32;\n break;\n case 'wheel':\n // nothing. don't\n // interfere with\n // `pressed`.\n break;\n }\n }\n\n // motion example of a left click:\n // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<\n function sendMove(ev) {\n var button = pressed\n , pos;\n\n pos = getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);\n if (!pos) return;\n\n // buttons marked as motions\n // are incremented by 32\n button += 32;\n\n sendEvent(button, pos);\n }\n\n // encode button and\n // position to characters\n function encode(data, ch) {\n if (!self.utfMouse) {\n if (ch === 255) return data.push(0);\n if (ch > 127) ch = 127;\n data.push(ch);\n } else {\n if (ch === 2047) return data.push(0);\n if (ch < 127) {\n data.push(ch);\n } else {\n if (ch > 2047) ch = 2047;\n data.push(0xC0 | (ch >> 6));\n data.push(0x80 | (ch & 0x3F));\n }\n }\n }\n\n // send a mouse event:\n // regular/utf8: ^[[M Cb Cx Cy\n // urxvt: ^[[ Cb ; Cx ; Cy M\n // sgr: ^[[ Cb ; Cx ; Cy M/m\n // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \\r\n // locator: CSI P e ; P b ; P r ; P c ; P p & w\n function sendEvent(button, pos) {\n // self.emit('mouse', {\n // x: pos.x - 32,\n // y: pos.x - 32,\n // button: button\n // });\n\n if (self.vt300Mouse) {\n // NOTE: Unstable.\n // http://www.vt100.net/docs/vt3xx-gp/chapter15.html\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n var data = C0.ESC + '[24';\n if (button === 0) data += '1';\n else if (button === 1) data += '3';\n else if (button === 2) data += '5';\n else if (button === 3) return;\n else data += '0';\n data += '~[' + pos.x + ',' + pos.y + ']\\r';\n self.send(data);\n return;\n }\n\n if (self.decLocator) {\n // NOTE: Unstable.\n button &= 3;\n pos.x -= 32;\n pos.y -= 32;\n if (button === 0) button = 2;\n else if (button === 1) button = 4;\n else if (button === 2) button = 6;\n else if (button === 3) button = 3;\n self.send(C0.ESC + '['\n + button\n + ';'\n + (button === 3 ? 4 : 0)\n + ';'\n + pos.y\n + ';'\n + pos.x\n + ';'\n + (pos.page || 0)\n + '&w');\n return;\n }\n\n if (self.urxvtMouse) {\n pos.x -= 32;\n pos.y -= 32;\n pos.x++;\n pos.y++;\n self.send(C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');\n return;\n }\n\n if (self.sgrMouse) {\n pos.x -= 32;\n pos.y -= 32;\n self.send(C0.ESC + '[<'\n + (((button & 3) === 3 ? button & ~3 : button) - 32)\n + ';'\n + pos.x\n + ';'\n + pos.y\n + ((button & 3) === 3 ? 'm' : 'M'));\n return;\n }\n\n var data = [];\n\n encode(data, button);\n encode(data, pos.x);\n encode(data, pos.y);\n\n self.send(C0.ESC + '[M' + String.fromCharCode.apply(String, data));\n }\n\n function getButton(ev) {\n var button\n , shift\n , meta\n , ctrl\n , mod;\n\n // two low bits:\n // 0 = left\n // 1 = middle\n // 2 = right\n // 3 = release\n // wheel up/down:\n // 1, and 2 - with 64 added\n switch (ev.overrideType || ev.type) {\n case 'mousedown':\n button = ev.button != null\n ? +ev.button\n : ev.which != null\n ? ev.which - 1\n : null;\n\n if (self.browser.isMSIE) {\n button = button === 1 ? 0 : button === 4 ? 1 : button;\n }\n break;\n case 'mouseup':\n button = 3;\n break;\n case 'DOMMouseScroll':\n button = ev.detail < 0\n ? 64\n : 65;\n break;\n case 'wheel':\n button = ev.wheelDeltaY > 0\n ? 64\n : 65;\n break;\n }\n\n // next three bits are the modifiers:\n // 4 = shift, 8 = meta, 16 = control\n shift = ev.shiftKey ? 4 : 0;\n meta = ev.metaKey ? 8 : 0;\n ctrl = ev.ctrlKey ? 16 : 0;\n mod = shift | meta | ctrl;\n\n // no mods\n if (self.vt200Mouse) {\n // ctrl only\n mod &= ctrl;\n } else if (!self.normalMouse) {\n mod = 0;\n }\n\n // increment to SP\n button = (32 + (mod << 2)) + button;\n\n return button;\n }\n\n on(el, 'mousedown', function(ev) {\n\n // Prevent the focus on the textarea from getting lost\n // and make sure we get focused on mousedown\n ev.preventDefault();\n self.focus();\n\n if (!self.mouseEvents) return;\n\n // send the button\n sendButton(ev);\n\n // fix for odd bug\n //if (self.vt200Mouse && !self.normalMouse) {\n if (self.vt200Mouse) {\n ev.overrideType = 'mouseup';\n sendButton(ev);\n return self.cancel(ev);\n }\n\n // bind events\n if (self.normalMouse) on(self.document, 'mousemove', sendMove);\n\n // x10 compatibility mode can't send button releases\n if (!self.x10Mouse) {\n on(self.document, 'mouseup', function up(ev) {\n sendButton(ev);\n if (self.normalMouse) off(self.document, 'mousemove', sendMove);\n off(self.document, 'mouseup', up);\n return self.cancel(ev);\n });\n }\n\n return self.cancel(ev);\n });\n\n //if (self.normalMouse) {\n // on(self.document, 'mousemove', sendMove);\n //}\n\n on(el, 'wheel', function(ev) {\n if (!self.mouseEvents) return;\n if (self.x10Mouse\n || self.vt300Mouse\n || self.decLocator) return;\n sendButton(ev);\n return self.cancel(ev);\n });\n\n // allow wheel scrolling in\n // the shell for example\n on(el, 'wheel', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onWheel(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchstart', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchStart(ev);\n return self.cancel(ev);\n });\n\n on(el, 'touchmove', function(ev) {\n if (self.mouseEvents) return;\n self.viewport.onTouchMove(ev);\n return self.cancel(ev);\n });\n};\n\n/**\n * Destroys the terminal.\n */\nTerminal.prototype.destroy = function() {\n this.readable = false;\n this.writable = false;\n this._events = {};\n this.handler = function() {};\n this.write = function() {};\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n //this.emit('close');\n};\n\n/**\n * Tells the renderer to refresh terminal content between two rows (inclusive) at the next\n * opportunity.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.refresh = function(start, end) {\n if (this.renderer) {\n this.renderer.queueRefresh(start, end);\n }\n};\n\n/**\n * Queues linkification for the specified rows.\n * @param {number} start The row to start from (between 0 and this.rows - 1).\n * @param {number} end The row to end at (between start and this.rows - 1).\n */\nTerminal.prototype.queueLinkification = function(start, end) {\n if (this.linkifier) {\n for (let i = start; i <= end; i++) {\n this.linkifier.linkifyRow(i);\n }\n }\n};\n\n/**\n * Display the cursor element\n */\nTerminal.prototype.showCursor = function() {\n if (!this.cursorState) {\n this.cursorState = 1;\n this.refresh(this.buffer.y, this.buffer.y);\n }\n};\n\n/**\n * Scroll the terminal down 1 row, creating a blank line.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous\n * line.\n */\nTerminal.prototype.scroll = function(isWrapped) {\n var row;\n\n // Make room for the new row in lines\n if (this.buffer.lines.length === this.buffer.lines.maxLength) {\n this.buffer.lines.trimStart(1);\n this.buffer.ybase--;\n if (this.buffer.ydisp !== 0) {\n this.buffer.ydisp--;\n }\n }\n\n this.buffer.ybase++;\n\n // TODO: Why is this done twice?\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n\n // last line\n row = this.buffer.ybase + this.rows - 1;\n\n // subtract the bottom scroll region\n row -= this.rows - 1 - this.buffer.scrollBottom;\n\n if (row === this.buffer.lines.length) {\n // Optimization: pushing is faster than splicing when they amount to the same behavior\n this.buffer.lines.push(this.blankLine(undefined, isWrapped));\n } else {\n // add our new line\n this.buffer.lines.splice(row, 0, this.blankLine(undefined, isWrapped));\n }\n\n if (this.buffer.scrollTop !== 0) {\n if (this.buffer.ybase !== 0) {\n this.buffer.ybase--;\n if (!this.userScrolling) {\n this.buffer.ydisp = this.buffer.ybase;\n }\n }\n this.buffer.lines.splice(this.buffer.ybase + this.buffer.scrollTop, 1);\n }\n\n // this.maxRange();\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n\n /**\n * This event is emitted whenever the terminal is scrolled.\n * The one parameter passed is the new y display position.\n *\n * @event scroll\n */\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Scroll the display of the terminal\n * @param {number} disp The number of lines to scroll down (negatives scroll up).\n * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used\n * to avoid unwanted events being handled by the veiwport when the event was triggered from the\n * viewport originally.\n */\nTerminal.prototype.scrollDisp = function(disp, suppressScrollEvent) {\n if (disp < 0) {\n if (this.buffer.ydisp === 0) {\n return;\n }\n this.userScrolling = true;\n } else if (disp + this.buffer.ydisp >= this.buffer.ybase) {\n this.userScrolling = false;\n }\n\n const oldYdisp = this.buffer.ydisp;\n this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0);\n\n // No change occurred, don't trigger scroll/refresh\n if (oldYdisp === this.buffer.ydisp) {\n return;\n }\n\n if (!suppressScrollEvent) {\n this.emit('scroll', this.buffer.ydisp);\n }\n\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Scroll the display of the terminal by a number of pages.\n * @param {number} pageCount The number of pages to scroll (negative scrolls up).\n */\nTerminal.prototype.scrollPages = function(pageCount) {\n this.scrollDisp(pageCount * (this.rows - 1));\n};\n\n/**\n * Scrolls the display of the terminal to the top.\n */\nTerminal.prototype.scrollToTop = function() {\n this.scrollDisp(-this.buffer.ydisp);\n};\n\n/**\n * Scrolls the display of the terminal to the bottom.\n */\nTerminal.prototype.scrollToBottom = function() {\n this.scrollDisp(this.buffer.ybase - this.buffer.ydisp);\n};\n\n/**\n * Writes text to the terminal.\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.write = function(data) {\n this.writeBuffer.push(data);\n\n // Send XOFF to pause the pty process if the write buffer becomes too large so\n // xterm.js can catch up before more data is sent. This is necessary in order\n // to keep signals such as ^C responsive.\n if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {\n // XOFF - stop pty pipe\n // XON will be triggered by emulator before processing data chunk\n this.send(C0.DC3);\n this.xoffSentToCatchUp = true;\n }\n\n if (!this.writeInProgress && this.writeBuffer.length > 0) {\n // Kick off a write which will write all data in sequence recursively\n this.writeInProgress = true;\n // Kick off an async innerWrite so more writes can come in while processing data\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n });\n }\n};\n\nTerminal.prototype.innerWrite = function() {\n var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);\n while (writeBatch.length > 0) {\n var data = writeBatch.shift();\n var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;\n\n // If XOFF was sent in order to catch up with the pty process, resume it if\n // the writeBuffer is empty to allow more data to come in.\n if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {\n this.send(C0.DC1);\n this.xoffSentToCatchUp = false;\n }\n\n this.refreshStart = this.buffer.y;\n this.refreshEnd = this.buffer.y;\n\n // HACK: Set the parser state based on it's state at the time of return.\n // This works around the bug #662 which saw the parser state reset in the\n // middle of parsing escape sequence in two chunks. For some reason the\n // state of the parser resets to 0 after exiting parser.parse. This change\n // just sets the state back based on the correct return statement.\n var state = this.parser.parse(data);\n this.parser.setState(state);\n\n this.updateRange(this.buffer.y);\n this.refresh(this.refreshStart, this.refreshEnd);\n }\n if (this.writeBuffer.length > 0) {\n // Allow renderer to catch up before processing the next batch\n var self = this;\n setTimeout(function () {\n self.innerWrite();\n }, 0);\n } else {\n this.writeInProgress = false;\n }\n};\n\n/**\n * Writes text to the terminal, followed by a break line character (\\n).\n * @param {string} data The text to write to the terminal.\n */\nTerminal.prototype.writeln = function(data) {\n this.write(data + '\\r\\n');\n};\n\n/**\n * DEPRECATED: only for backward compatibility. Please use attachCustomKeyEventHandler() instead.\n * @param {function} customKeydownHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeydownHandler = function(customKeydownHandler) {\n let message = 'attachCustomKeydownHandler() is DEPRECATED and will be removed soon. Please use attachCustomKeyEventHandler() instead.';\n console.warn(message);\n this.attachCustomKeyEventHandler(customKeydownHandler);\n};\n\n/**\n * Attaches a custom key event handler which is run before keys are processed, giving consumers of\n * xterm.js ultimate control as to what keys should be processed by the terminal and what keys\n * should not.\n * @param {function} customKeyEventHandler The custom KeyboardEvent handler to attach. This is a\n * function that takes a KeyboardEvent, allowing consumers to stop propogation and/or prevent\n * the default action. The function returns whether the event should be processed by xterm.js.\n */\nTerminal.prototype.attachCustomKeyEventHandler = function(customKeyEventHandler) {\n this.customKeyEventHandler = customKeyEventHandler;\n};\n\n/**\n * Attaches a http(s) link handler, forcing web links to behave differently to\n * regular tags. This will trigger a refresh as links potentially need to be\n * reconstructed. Calling this with null will remove the handler.\n * @param {LinkMatcherHandler} handler The handler callback function.\n */\nTerminal.prototype.setHypertextLinkHandler = function(handler) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');\n }\n this.linkifier.setHypertextLinkHandler(handler);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Attaches a validation callback for hypertext links. This is useful to use\n * validation logic or to do something with the link's element and url.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this can\n * be cleared with null.\n */\nTerminal.prototype.setHypertextValidationCallback = function(callback) {\n if (!this.linkifier) {\n throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');\n }\n this.linkifier.setHypertextValidationCallback(callback);\n // Refresh to force links to refresh\n this.refresh(0, this.rows - 1);\n};\n\n/**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkMatcherHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\nTerminal.prototype.registerLinkMatcher = function(regex, handler, options) {\n if (this.linkifier) {\n var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);\n this.refresh(0, this.rows - 1);\n return matcherId;\n }\n};\n\n/**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n */\nTerminal.prototype.deregisterLinkMatcher = function(matcherId) {\n if (this.linkifier) {\n if (this.linkifier.deregisterLinkMatcher(matcherId)) {\n this.refresh(0, this.rows - 1);\n }\n }\n};\n\n/**\n * Gets whether the terminal has an active selection.\n */\nTerminal.prototype.hasSelection = function() {\n return this.selectionManager ? this.selectionManager.hasSelection : false;\n};\n\n/**\n * Gets the terminal's current selection, this is useful for implementing copy\n * behavior outside of xterm.js.\n */\nTerminal.prototype.getSelection = function() {\n return this.selectionManager ? this.selectionManager.selectionText : '';\n};\n\n/**\n * Clears the current terminal selection.\n */\nTerminal.prototype.clearSelection = function() {\n if (this.selectionManager) {\n this.selectionManager.clearSelection();\n }\n};\n\n/**\n * Selects all text within the terminal.\n */\nTerminal.prototype.selectAll = function() {\n if (this.selectionManager) {\n this.selectionManager.selectAll();\n }\n};\n\n/**\n * Handle a keydown event\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keydown event to be handled.\n */\nTerminal.prototype.keyDown = function(ev) {\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.restartCursorBlinking();\n\n if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n return false;\n }\n\n var self = this;\n var result = this.evaluateKeyEscapeSequence(ev);\n\n if (result.key === C0.DC3) { // XOFF\n this.writeStopped = true;\n } else if (result.key === C0.DC1) { // XON\n this.writeStopped = false;\n }\n\n if (result.scrollDisp) {\n this.scrollDisp(result.scrollDisp);\n return this.cancel(ev, true);\n }\n\n if (isThirdLevelShift(this, ev)) {\n return true;\n }\n\n if (result.cancel) {\n // The event is canceled at the end already, is this necessary?\n this.cancel(ev, true);\n }\n\n if (!result.key) {\n return true;\n }\n\n this.emit('keydown', ev);\n this.emit('key', result.key, ev);\n this.showCursor();\n this.handler(result.key);\n\n return this.cancel(ev, true);\n};\n\n/**\n * Returns an object that determines how a KeyboardEvent should be handled. The key of the\n * returned value is the new key code to pass to the PTY.\n *\n * Reference: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * @param {KeyboardEvent} ev The keyboard event to be translated to key escape sequence.\n */\nTerminal.prototype.evaluateKeyEscapeSequence = function(ev) {\n var result = {\n // Whether to cancel event propogation (NOTE: this may not be needed since the event is\n // canceled at the end of keyDown\n cancel: false,\n // The new key even to emit\n key: undefined,\n // The number of characters to scroll, if this is defined it will cancel the event\n scrollDisp: undefined\n };\n var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;\n switch (ev.keyCode) {\n case 8:\n // backspace\n if (ev.shiftKey) {\n result.key = C0.BS; // ^H\n break;\n }\n result.key = C0.DEL; // ^?\n break;\n case 9:\n // tab\n if (ev.shiftKey) {\n result.key = C0.ESC + '[Z';\n break;\n }\n result.key = C0.HT;\n result.cancel = true;\n break;\n case 13:\n // return/enter\n result.key = C0.CR;\n result.cancel = true;\n break;\n case 27:\n // escape\n result.key = C0.ESC;\n result.cancel = true;\n break;\n case 37:\n // left-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';\n // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3D') {\n result.key = (this.browser.isMac) ? C0.ESC + 'b' : C0.ESC + '[1;5D';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OD';\n } else {\n result.key = C0.ESC + '[D';\n }\n break;\n case 39:\n // right-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';\n // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward\n // http://unix.stackexchange.com/a/108106\n // macOS uses different escape sequences than linux\n if (result.key == C0.ESC + '[1;3C') {\n result.key = (this.browser.isMac) ? C0.ESC + 'f' : C0.ESC + '[1;5C';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OC';\n } else {\n result.key = C0.ESC + '[C';\n }\n break;\n case 38:\n // up-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';\n // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3A') {\n result.key = C0.ESC + '[1;5A';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OA';\n } else {\n result.key = C0.ESC + '[A';\n }\n break;\n case 40:\n // down-arrow\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';\n // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow\n // http://unix.stackexchange.com/a/108106\n if (result.key == C0.ESC + '[1;3B') {\n result.key = C0.ESC + '[1;5B';\n }\n } else if (this.applicationCursor) {\n result.key = C0.ESC + 'OB';\n } else {\n result.key = C0.ESC + '[B';\n }\n break;\n case 45:\n // insert\n if (!ev.shiftKey && !ev.ctrlKey) {\n // or + are used to\n // copy-paste on some systems.\n result.key = C0.ESC + '[2~';\n }\n break;\n case 46:\n // delete\n if (modifiers) {\n result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[3~';\n }\n break;\n case 36:\n // home\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OH';\n else\n result.key = C0.ESC + '[H';\n break;\n case 35:\n // end\n if (modifiers)\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';\n else if (this.applicationCursor)\n result.key = C0.ESC + 'OF';\n else\n result.key = C0.ESC + '[F';\n break;\n case 33:\n // page up\n if (ev.shiftKey) {\n result.scrollDisp = -(this.rows - 1);\n } else {\n result.key = C0.ESC + '[5~';\n }\n break;\n case 34:\n // page down\n if (ev.shiftKey) {\n result.scrollDisp = this.rows - 1;\n } else {\n result.key = C0.ESC + '[6~';\n }\n break;\n case 112:\n // F1-F12\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';\n } else {\n result.key = C0.ESC + 'OP';\n }\n break;\n case 113:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';\n } else {\n result.key = C0.ESC + 'OQ';\n }\n break;\n case 114:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';\n } else {\n result.key = C0.ESC + 'OR';\n }\n break;\n case 115:\n if (modifiers) {\n result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';\n } else {\n result.key = C0.ESC + 'OS';\n }\n break;\n case 116:\n if (modifiers) {\n result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[15~';\n }\n break;\n case 117:\n if (modifiers) {\n result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[17~';\n }\n break;\n case 118:\n if (modifiers) {\n result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[18~';\n }\n break;\n case 119:\n if (modifiers) {\n result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[19~';\n }\n break;\n case 120:\n if (modifiers) {\n result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[20~';\n }\n break;\n case 121:\n if (modifiers) {\n result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[21~';\n }\n break;\n case 122:\n if (modifiers) {\n result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[23~';\n }\n break;\n case 123:\n if (modifiers) {\n result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';\n } else {\n result.key = C0.ESC + '[24~';\n }\n break;\n default:\n // a-z and space\n if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = String.fromCharCode(ev.keyCode - 64);\n } else if (ev.keyCode === 32) {\n // NUL\n result.key = String.fromCharCode(0);\n } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {\n // escape, file sep, group sep, record sep, unit sep\n result.key = String.fromCharCode(ev.keyCode - 51 + 27);\n } else if (ev.keyCode === 56) {\n // delete\n result.key = String.fromCharCode(127);\n } else if (ev.keyCode === 219) {\n // ^[ - Control Sequence Introducer (CSI)\n result.key = String.fromCharCode(27);\n } else if (ev.keyCode === 220) {\n // ^\\ - String Terminator (ST)\n result.key = String.fromCharCode(28);\n } else if (ev.keyCode === 221) {\n // ^] - Operating System Command (OSC)\n result.key = String.fromCharCode(29);\n }\n } else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {\n // On Mac this is a third level shift. Use instead.\n if (ev.keyCode >= 65 && ev.keyCode <= 90) {\n result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32);\n } else if (ev.keyCode === 192) {\n result.key = C0.ESC + '`';\n } else if (ev.keyCode >= 48 && ev.keyCode <= 57) {\n result.key = C0.ESC + (ev.keyCode - 48);\n }\n } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {\n if (ev.keyCode === 65) { // cmd + a\n this.selectAll();\n }\n }\n break;\n }\n\n return result;\n};\n\n/**\n * Set the G level of the terminal\n * @param g\n */\nTerminal.prototype.setgLevel = function(g) {\n this.glevel = g;\n this.charset = this.charsets[g];\n};\n\n/**\n * Set the charset for the given G level of the terminal\n * @param g\n * @param charset\n */\nTerminal.prototype.setgCharset = function(g, charset) {\n this.charsets[g] = charset;\n if (this.glevel === g) {\n this.charset = charset;\n }\n};\n\n/**\n * Handle a keypress event.\n * Key Resources:\n * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent\n * @param {KeyboardEvent} ev The keypress event to be handled.\n */\nTerminal.prototype.keyPress = function(ev) {\n var key;\n\n if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) {\n return false;\n }\n\n this.cancel(ev);\n\n if (ev.charCode) {\n key = ev.charCode;\n } else if (ev.which == null) {\n key = ev.keyCode;\n } else if (ev.which !== 0 && ev.charCode !== 0) {\n key = ev.which;\n } else {\n return false;\n }\n\n if (!key || (\n (ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev)\n )) {\n return false;\n }\n\n key = String.fromCharCode(key);\n\n this.emit('keypress', key, ev);\n this.emit('key', key, ev);\n this.showCursor();\n this.handler(key);\n\n return true;\n};\n\n/**\n * Send data for handling to the terminal\n * @param {string} data\n */\nTerminal.prototype.send = function(data) {\n var self = this;\n\n if (!this.queue) {\n setTimeout(function() {\n self.handler(self.queue);\n self.queue = '';\n }, 1);\n }\n\n this.queue += data;\n};\n\n/**\n * Ring the bell.\n * Note: We could do sweet things with webaudio here\n */\nTerminal.prototype.bell = function() {\n if (!this.visualBell) return;\n var self = this;\n this.element.style.borderColor = 'white';\n setTimeout(function() {\n self.element.style.borderColor = '';\n }, 10);\n if (this.popOnBell) this.focus();\n};\n\n/**\n * Log the current state to the console.\n */\nTerminal.prototype.log = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.log) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.log.apply(this.context.console, args);\n};\n\n/**\n * Log the current state as error to the console.\n */\nTerminal.prototype.error = function() {\n if (!this.debug) return;\n if (!this.context.console || !this.context.console.error) return;\n var args = Array.prototype.slice.call(arguments);\n this.context.console.error.apply(this.context.console, args);\n};\n\n/**\n * Resizes the terminal.\n *\n * @param {number} x The number of columns to resize to.\n * @param {number} y The number of rows to resize to.\n */\nTerminal.prototype.resize = function(x, y) {\n if (isNaN(x) || isNaN(y)) {\n return;\n }\n\n if (y > this.getOption('scrollback')) {\n this.setOption('scrollback', y)\n }\n\n var line\n , el\n , i\n , j\n , ch\n , addToY;\n\n if (x === this.cols && y === this.rows) {\n // Check if we still need to measure the char size (fixes #785).\n if (!this.charMeasure.width || !this.charMeasure.height) {\n this.charMeasure.measure();\n }\n return;\n }\n\n if (x < 1) x = 1;\n if (y < 1) y = 1;\n\n this.buffers.resize(x, y);\n\n // Adjust rows in the DOM to accurately reflect the new dimensions\n while (this.children.length < y) {\n this.insertRow();\n }\n while (this.children.length > y) {\n el = this.children.shift();\n if (!el) continue;\n el.parentNode.removeChild(el);\n }\n\n this.cols = x;\n this.rows = y;\n this.setupStops(this.cols);\n\n this.charMeasure.measure();\n\n this.refresh(0, this.rows - 1);\n\n this.geometry = [this.cols, this.rows];\n this.emit('resize', {terminal: this, cols: x, rows: y});\n};\n\n/**\n * Updates the range of rows to refresh\n * @param {number} y The number of rows to refresh next.\n */\nTerminal.prototype.updateRange = function(y) {\n if (y < this.refreshStart) this.refreshStart = y;\n if (y > this.refreshEnd) this.refreshEnd = y;\n // if (y > this.refreshEnd) {\n // this.refreshEnd = y;\n // if (y > this.rows - 1) {\n // this.refreshEnd = this.rows - 1;\n // }\n // }\n};\n\n/**\n * Set the range of refreshing to the maximum value\n */\nTerminal.prototype.maxRange = function() {\n this.refreshStart = 0;\n this.refreshEnd = this.rows - 1;\n};\n\n\n\n/**\n * Setup the tab stops.\n * @param {number} i\n */\nTerminal.prototype.setupStops = function(i) {\n if (i != null) {\n if (!this.buffer.tabs[i]) {\n i = this.prevStop(i);\n }\n } else {\n this.buffer.tabs = {};\n i = 0;\n }\n\n for (; i < this.cols; i += this.getOption('tabStopWidth')) {\n this.buffer.tabs[i] = true;\n }\n};\n\n\n/**\n * Move the cursor to the previous tab stop from the given position (default is current).\n * @param {number} x The position to move the cursor to the previous tab stop.\n */\nTerminal.prototype.prevStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[--x] && x > 0);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Move the cursor one tab stop forward from the given position (default is current).\n * @param {number} x The position to move the cursor one tab stop forward.\n */\nTerminal.prototype.nextStop = function(x) {\n if (x == null) x = this.buffer.x;\n while (!this.buffer.tabs[++x] && x < this.cols);\n return x >= this.cols\n ? this.cols - 1\n : x < 0 ? 0 : x;\n};\n\n\n/**\n * Erase in the identified line everything from \"x\" to the end of the line (right).\n * @param {number} x The column from which to start erasing to the end of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseRight = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n for (; x < this.cols; x++) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n\n\n/**\n * Erase in the identified line everything from \"x\" to the start of the line (left).\n * @param {number} x The column from which to start erasing to the start of the line.\n * @param {number} y The line in which to operate.\n */\nTerminal.prototype.eraseLeft = function(x, y) {\n var line = this.buffer.lines.get(this.buffer.ybase + y);\n if (!line) {\n return;\n }\n var ch = [this.eraseAttr(), ' ', 1]; // xterm\n x++;\n while (x--) {\n line[x] = ch;\n }\n this.updateRange(y);\n};\n\n/**\n * Clears the entire buffer, making the prompt line the new first line.\n */\nTerminal.prototype.clear = function() {\n if (this.buffer.ybase === 0 && this.buffer.y === 0) {\n // Don't clear if it's already clear\n return;\n }\n this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y));\n this.buffer.lines.length = 1;\n this.buffer.ydisp = 0;\n this.buffer.ybase = 0;\n this.buffer.y = 0;\n for (var i = 1; i < this.rows; i++) {\n this.buffer.lines.push(this.blankLine());\n }\n this.refresh(0, this.rows - 1);\n this.emit('scroll', this.buffer.ydisp);\n};\n\n/**\n * Erase all content in the given line\n * @param {number} y The line to erase all of its contents.\n */\nTerminal.prototype.eraseLine = function(y) {\n this.eraseRight(0, y);\n};\n\n\n/**\n * Return the data array of a blank line\n * @param {number} cur First bunch of data for each \"blank\" character.\n * @param {boolean} isWrapped Whether the new line is wrapped from the previous line.\n */\nTerminal.prototype.blankLine = function(cur, isWrapped, cols) {\n var attr = cur\n ? this.eraseAttr()\n : this.defAttr;\n\n var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character\n , line = []\n , i = 0;\n\n // TODO: It is not ideal that this is a property on an array, a buffer line\n // class should be added that will hold this data and other useful functions.\n if (isWrapped) {\n line.isWrapped = isWrapped;\n }\n\n cols = cols || this.cols;\n for (; i < cols; i++) {\n line[i] = ch;\n }\n\n return line;\n};\n\n\n/**\n * If cur return the back color xterm feature attribute. Else return defAttr.\n * @param {object} cur\n */\nTerminal.prototype.ch = function(cur) {\n return cur\n ? [this.eraseAttr(), ' ', 1]\n : [this.defAttr, ' ', 1];\n};\n\n\n/**\n * Evaluate if the current terminal is the given argument.\n * @param {object} term The terminal to evaluate\n */\nTerminal.prototype.is = function(term) {\n var name = this.termName;\n return (name + '').indexOf(term) === 0;\n};\n\n\n/**\n * Emit the 'data' event and populate the given data.\n * @param {string} data The data to populate in the event.\n */\nTerminal.prototype.handler = function(data) {\n // Prevents all events to pty process if stdin is disabled\n if (this.options.disableStdin) {\n return;\n }\n\n // Clear the selection if the selection manager is available and has an active selection\n if (this.selectionManager && this.selectionManager.hasSelection) {\n this.selectionManager.clearSelection();\n }\n\n // Input is being sent to the terminal, the terminal should focus the prompt.\n if (this.buffer.ybase !== this.buffer.ydisp) {\n this.scrollToBottom();\n }\n this.emit('data', data);\n};\n\n\n/**\n * Emit the 'title' event and populate the given title.\n * @param {string} title The title to populate in the event.\n */\nTerminal.prototype.handleTitle = function(title) {\n /**\n * This event is emitted when the title of the terminal is changed\n * from inside the terminal. The parameter is the new title.\n *\n * @event title\n */\n this.emit('title', title);\n};\n\n\n/**\n * ESC\n */\n\n/**\n * ESC D Index (IND is 0x84).\n */\nTerminal.prototype.index = function() {\n this.buffer.y++;\n if (this.buffer.y > this.buffer.scrollBottom) {\n this.buffer.y--;\n this.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this.buffer.x >= this.cols) {\n this.buffer.x--;\n }\n};\n\n\n/**\n * ESC M Reverse Index (RI is 0x8d).\n *\n * Move the cursor up one row, inserting a new blank line if necessary.\n */\nTerminal.prototype.reverseIndex = function() {\n var j;\n if (this.buffer.y === this.buffer.scrollTop) {\n // possibly move the code below to term.reverseScroll();\n // test: echo -ne '\\e[1;1H\\e[44m\\eM\\e[0m'\n // blankLine(true) is xterm/linux behavior\n this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, this.rows - 1, 1);\n this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.blankLine(true));\n this.updateRange(this.buffer.scrollTop);\n this.updateRange(this.buffer.scrollBottom);\n } else {\n this.buffer.y--;\n }\n};\n\n\n/**\n * ESC c Full Reset (RIS).\n */\nTerminal.prototype.reset = function() {\n this.options.rows = this.rows;\n this.options.cols = this.cols;\n var customKeyEventHandler = this.customKeyEventHandler;\n var cursorBlinkInterval = this.cursorBlinkInterval;\n var inputHandler = this.inputHandler;\n Terminal.call(this, this.options);\n this.customKeyEventHandler = customKeyEventHandler;\n this.cursorBlinkInterval = cursorBlinkInterval;\n this.inputHandler = inputHandler;\n this.refresh(0, this.rows - 1);\n this.viewport.syncScrollArea();\n};\n\n\n/**\n * ESC H Tab Set (HTS is 0x88).\n */\nTerminal.prototype.tabSet = function() {\n this.buffer.tabs[this.buffer.x] = true;\n};\n\n/**\n * Helpers\n */\n\nfunction on(el, type, handler, capture) {\n if (!Array.isArray(el)) {\n el = [el];\n }\n el.forEach(function (element) {\n element.addEventListener(type, handler, capture || false);\n });\n}\n\nfunction off(el, type, handler, capture) {\n el.removeEventListener(type, handler, capture || false);\n}\n\nfunction cancel(ev, force) {\n if (!this.cancelEvents && !force) {\n return;\n }\n ev.preventDefault();\n ev.stopPropagation();\n return false;\n}\n\nfunction inherits(child, parent) {\n function f() {\n this.constructor = child;\n }\n f.prototype = parent.prototype;\n child.prototype = new f;\n}\n\nfunction indexOf(obj, el) {\n var i = obj.length;\n while (i--) {\n if (obj[i] === el) return i;\n }\n return -1;\n}\n\nfunction isThirdLevelShift(term, ev) {\n var thirdLevelKey =\n (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||\n (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);\n\n if (ev.type == 'keypress') {\n return thirdLevelKey;\n }\n\n // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)\n return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);\n}\n\n// Expose to InputHandler (temporary)\nTerminal.prototype.matchColor = matchColor;\n\nfunction matchColor(r1, g1, b1) {\n var hash = (r1 << 16) | (g1 << 8) | b1;\n\n if (matchColor._cache[hash] != null) {\n return matchColor._cache[hash];\n }\n\n var ldiff = Infinity\n , li = -1\n , i = 0\n , c\n , r2\n , g2\n , b2\n , diff;\n\n for (; i < Terminal.vcolors.length; i++) {\n c = Terminal.vcolors[i];\n r2 = c[0];\n g2 = c[1];\n b2 = c[2];\n\n diff = matchColor.distance(r1, g1, b1, r2, g2, b2);\n\n if (diff === 0) {\n li = i;\n break;\n }\n\n if (diff < ldiff) {\n ldiff = diff;\n li = i;\n }\n }\n\n return matchColor._cache[hash] = li;\n}\n\nmatchColor._cache = {};\n\n// http://stackoverflow.com/questions/1633828\nmatchColor.distance = function(r1, g1, b1, r2, g2, b2) {\n return Math.pow(30 * (r1 - r2), 2)\n + Math.pow(59 * (g1 - g2), 2)\n + Math.pow(11 * (b1 - b2), 2);\n};\n\nfunction each(obj, iter, con) {\n if (obj.forEach) return obj.forEach(iter, con);\n for (var i = 0; i < obj.length; i++) {\n iter.call(con, obj[i], i, obj);\n }\n}\n\nfunction wasMondifierKeyOnlyEvent(ev) {\n return ev.keyCode === 16 || // Shift\n ev.keyCode === 17 || // Ctrl\n ev.keyCode === 18; // Alt\n}\n\nfunction keys(obj) {\n if (Object.keys) return Object.keys(obj);\n var key, keys = [];\n for (key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n keys.push(key);\n }\n }\n return keys;\n}\n\n/**\n * Expose\n */\n\nTerminal.translateBufferLineToString = translateBufferLineToString;\nTerminal.EventEmitter = EventEmitter;\nTerminal.inherits = inherits;\n\n/**\n * Adds an event listener to the terminal.\n *\n * @param {string} event The name of the event. TODO: Document all event types\n * @param {function} callback The function to call when the event is triggered.\n */\nTerminal.on = on;\nTerminal.off = off;\nTerminal.cancel = cancel;\n\nmodule.exports = Terminal;\n","/**\n * @license MIT\n */\n\nimport { CharMeasure } from './CharMeasure';\n\nexport function getCoordsRelativeToElement(event: MouseEvent, element: HTMLElement): [number, number] {\n // Ignore browsers that don't support MouseEvent.pageX\n if (event.pageX == null) {\n return null;\n }\n\n let x = event.pageX;\n let y = event.pageY;\n\n // Converts the coordinates from being relative to the document to being\n // relative to the terminal.\n while (element && element !== self.document.documentElement) {\n x -= element.offsetLeft;\n y -= element.offsetTop;\n element = 'offsetParent' in element ? element.offsetParent : element.parentElement;\n }\n return [x, y];\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event. The result\n * is returned as an array in the form [x, y] instead of an object as it's a\n * little faster and this function is used in some low level code.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows n the terminal.\n * @param isSelection Whether the request is for the selection or not. This will\n * apply an offset to the x value such that the left half of the cell will\n * select that cell and the right half will select the next cell.\n */\nexport function getCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {\n // Coordinates cannot be measured if charMeasure has not been initialized\n if (!charMeasure.width || !charMeasure.height) {\n return null;\n }\n\n const coords = getCoordsRelativeToElement(event, rowContainer);\n if (!coords) {\n return null;\n }\n\n // Convert to cols/rows.\n coords[0] = Math.ceil((coords[0] + (isSelection ? charMeasure.width / 2 : 0)) / charMeasure.width);\n coords[1] = Math.ceil(coords[1] / charMeasure.height);\n\n // Ensure coordinates are within the terminal viewport.\n coords[0] = Math.min(Math.max(coords[0], 1), colCount + 1);\n coords[1] = Math.min(Math.max(coords[1], 1), rowCount + 1);\n\n return coords;\n}\n\n/**\n * Gets coordinates within the terminal for a particular mouse event, wrapping\n * them to the bounds of the terminal and adding 32 to both the x and y values\n * as expected by xterm.\n * @param event The mouse event.\n * @param rowContainer The terminal's row container.\n * @param charMeasure The char measure object used to determine character sizes.\n * @param colCount The number of columns in the terminal.\n * @param rowCount The number of rows in the terminal.\n */\nexport function getRawByteCoords(event: MouseEvent, rowContainer: HTMLElement, charMeasure: CharMeasure, colCount: number, rowCount: number): { x: number, y: number } {\n const coords = getCoords(event, rowContainer, charMeasure, colCount, rowCount);\n let x = coords[0];\n let y = coords[1];\n\n // xterm sends raw bytes and starts at 32 (SP) for each.\n x += 32;\n y += 32;\n\n return { x, y };\n}\n","/**\n * Generic utilities module with methods that can be helpful at different parts of the code base.\n * @module xterm/utils/Generic\n * @license MIT\n */\n\n/**\n * Return if the given array contains the given element\n * @param {Array} array The array to search for the given element.\n * @param {Object} el The element to look for into the array\n */\nexport function contains(arr: any[], el: any) {\n return arr.indexOf(el) >= 0;\n};\n","/**\n * @module xterm/utils/DomElementObjectPool\n * @license MIT\n */\n\n/**\n * An object pool that manages acquisition and releasing of DOM elements for\n * when reuse is desirable.\n */\nexport class DomElementObjectPool {\n private static readonly OBJECT_ID_ATTRIBUTE = 'data-obj-id';\n\n private static _objectCount = 0;\n\n private _type: string;\n private _pool: HTMLElement[];\n private _inUse: {[key: string]: HTMLElement};\n\n /**\n * @param type The DOM element type (div, span, etc.).\n */\n constructor(private type: string) {\n this._type = type;\n this._pool = [];\n this._inUse = {};\n }\n\n /**\n * Acquire an element from the pool, creating it if the pool is empty.\n */\n public acquire(): HTMLElement {\n let element: HTMLElement;\n if (this._pool.length === 0) {\n element = this._createNew();\n } else {\n element = this._pool.pop();\n }\n this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;\n return element;\n }\n\n /**\n * Release an element back into the pool. It's up to the caller of this\n * function to ensure that all external references to the element have been\n * removed.\n * @param element The element being released.\n */\n public release(element: HTMLElement): void {\n if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {\n throw new Error('Could not release an element not yet acquired');\n }\n delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];\n this._cleanElement(element);\n this._pool.push(element);\n }\n\n /**\n * Creates a new element for the pool.\n */\n private _createNew(): HTMLElement {\n const element = document.createElement(this._type);\n const id = DomElementObjectPool._objectCount++;\n element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));\n return element;\n }\n\n /**\n * Resets an element back to a \"clean state\".\n * @param element The element to be cleaned.\n */\n private _cleanElement(element: HTMLElement): void {\n element.className = '';\n element.innerHTML = '';\n }\n}\n","/**\n * Represents a circular list; a list with a maximum size that wraps around when push is called,\n * overriding values at the start of the list.\n * @module xterm/utils/CircularList\n * @license MIT\n */\nimport { EventEmitter } from '../EventEmitter';\nimport { ICircularList } from '../Interfaces';\n\nexport class CircularList extends EventEmitter implements ICircularList {\n private _array: T[];\n private _startIndex: number;\n private _length: number;\n\n constructor(maxLength: number) {\n super();\n this._array = new Array(maxLength);\n this._startIndex = 0;\n this._length = 0;\n }\n\n public get maxLength(): number {\n return this._array.length;\n }\n\n public set maxLength(newMaxLength: number) {\n // Reconstruct array, starting at index 0. Only transfer values from the\n // indexes 0 to length.\n let newArray = new Array(newMaxLength);\n for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {\n newArray[i] = this._array[this._getCyclicIndex(i)];\n }\n this._array = newArray;\n this._startIndex = 0;\n }\n\n public get length(): number {\n return this._length;\n }\n\n public set length(newLength: number) {\n if (newLength > this._length) {\n for (let i = this._length; i < newLength; i++) {\n this._array[i] = undefined;\n }\n }\n this._length = newLength;\n }\n\n public get forEach(): (callbackfn: (value: T, index: number) => void) => void {\n return (callbackfn: (value: T, index: number) => void) => {\n let i = 0;\n let length = this.length;\n for (let i = 0; i < length; i++) {\n callbackfn(this.get(i), i);\n }\n };\n }\n\n /**\n * Gets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index of the value to get.\n * @return The value corresponding to the index.\n */\n public get(index: number): T {\n return this._array[this._getCyclicIndex(index)];\n }\n\n /**\n * Sets the value at an index.\n *\n * Note that for performance reasons there is no bounds checking here, the index reference is\n * circular so this should always return a value and never throw.\n * @param index The index to set.\n * @param value The value to set.\n */\n public set(index: number, value: T): void {\n this._array[this._getCyclicIndex(index)] = value;\n }\n\n /**\n * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0\n * if the maximum length is reached.\n * @param value The value to push onto the list.\n */\n public push(value: T): void {\n this._array[this._getCyclicIndex(this._length)] = value;\n if (this._length === this.maxLength) {\n this._startIndex++;\n if (this._startIndex === this.maxLength) {\n this._startIndex = 0;\n }\n this.emit('trim', 1);\n } else {\n this._length++;\n }\n }\n\n /**\n * Removes and returns the last value on the list.\n * @return The popped value.\n */\n public pop(): T {\n return this._array[this._getCyclicIndex(this._length-- - 1)];\n }\n\n /**\n * Deletes and/or inserts items at a particular index (in that order). Unlike\n * Array.prototype.splice, this operation does not return the deleted items as a new array in\n * order to save creating a new array. Note that this operation may shift all values in the list\n * in the worst case.\n * @param start The index to delete and/or insert.\n * @param deleteCount The number of elements to delete.\n * @param items The items to insert.\n */\n public splice(start: number, deleteCount: number, ...items: T[]): void {\n // Delete items\n if (deleteCount) {\n for (let i = start; i < this._length - deleteCount; i++) {\n this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];\n }\n this._length -= deleteCount;\n }\n\n if (items && items.length) {\n // Add items\n for (let i = this._length - 1; i >= start; i--) {\n this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];\n }\n for (let i = 0; i < items.length; i++) {\n this._array[this._getCyclicIndex(start + i)] = items[i];\n }\n\n // Adjust length as needed\n if (this._length + items.length > this.maxLength) {\n const countToTrim = (this._length + items.length) - this.maxLength;\n this._startIndex += countToTrim;\n this._length = this.maxLength;\n this.emit('trim', countToTrim);\n } else {\n this._length += items.length;\n }\n }\n }\n\n /**\n * Trims a number of items from the start of the list.\n * @param count The number of items to remove.\n */\n public trimStart(count: number): void {\n if (count > this._length) {\n count = this._length;\n }\n this._startIndex += count;\n this._length -= count;\n this.emit('trim', count);\n }\n\n public shiftElements(start: number, count: number, offset: number): void {\n if (count <= 0) {\n return;\n }\n if (start < 0 || start >= this._length) {\n throw new Error('start argument out of range');\n }\n if (start + offset < 0) {\n throw new Error('Cannot shift elements in list beyond index 0');\n }\n\n if (offset > 0) {\n for (let i = count - 1; i >= 0; i--) {\n this.set(start + i + offset, this.get(start + i));\n }\n const expandListBy = (start + count + offset) - this._length;\n if (expandListBy > 0) {\n this._length += expandListBy;\n while (this._length > this.maxLength) {\n this._length--;\n this._startIndex++;\n this.emit('trim', 1);\n }\n }\n } else {\n for (let i = 0; i < count; i++) {\n this.set(start + i + offset, this.get(start + i));\n }\n }\n }\n\n /**\n * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the\n * backing array to get the element associated with the regular index.\n * @param index The regular index.\n * @returns The cyclic index.\n */\n private _getCyclicIndex(index: number): number {\n return (this._startIndex + index) % this.maxLength;\n }\n}\n","/**\n * @module xterm/utils/CharMeasure\n * @license MIT\n */\n\nimport { EventEmitter } from '../EventEmitter.js';\n\n/**\n * Utility class that measures the size of a character.\n */\nexport class CharMeasure extends EventEmitter {\n private _document: Document;\n private _parentElement: HTMLElement;\n private _measureElement: HTMLElement;\n private _width: number;\n private _height: number;\n\n constructor(document: Document, parentElement: HTMLElement) {\n super();\n this._document = document;\n this._parentElement = parentElement;\n }\n\n public get width(): number {\n return this._width;\n }\n\n public get height(): number {\n return this._height;\n }\n\n public measure(): void {\n if (!this._measureElement) {\n this._measureElement = this._document.createElement('span');\n this._measureElement.style.position = 'absolute';\n this._measureElement.style.top = '0';\n this._measureElement.style.left = '-9999em';\n this._measureElement.textContent = 'W';\n this._measureElement.setAttribute('aria-hidden', 'true');\n this._parentElement.appendChild(this._measureElement);\n // Perform _doMeasure async if the element was just attached as sometimes\n // getBoundingClientRect does not return accurate values without this.\n setTimeout(() => this._doMeasure(), 0);\n } else {\n this._doMeasure();\n }\n }\n\n private _doMeasure(): void {\n const geometry = this._measureElement.getBoundingClientRect();\n // The element is likely currently display:none, we should retain the\n // previous value.\n if (geometry.width === 0 || geometry.height === 0) {\n return;\n }\n if (this._width !== geometry.width || this._height !== geometry.height) {\n this._width = geometry.width;\n this._height = geometry.height;\n this.emit('charsizechanged');\n }\n }\n}\n","/**\n * @license MIT\n */\n\n// TODO: This module should be merged into a buffer or buffer line class\n\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\n/**\n * Translates a buffer line to a string, with optional start and end columns.\n * Wide characters will count as two columns in the resulting string. This\n * function is useful for getting the actual text underneath the raw selection\n * position.\n * @param line The line being translated.\n * @param trimRight Whether to trim whitespace to the right.\n * @param startCol The column to start at.\n * @param endCol The column to end at.\n */\nexport function translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string {\n // Get full line\n let lineString = '';\n let widthAdjustedStartCol = startCol;\n let widthAdjustedEndCol = endCol;\n for (let i = 0; i < line.length; i++) {\n const char = line[i];\n lineString += char[LINE_DATA_CHAR_INDEX];\n // Adjust start and end cols for wide characters if they affect their\n // column indexes\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n if (startCol >= i) {\n widthAdjustedStartCol--;\n }\n if (endCol >= i) {\n widthAdjustedEndCol--;\n }\n }\n }\n\n // Calculate the final end col by trimming whitespace on the right of the\n // line if needed.\n let finalEndCol = widthAdjustedEndCol || line.length;\n if (trimRight) {\n const rightWhitespaceIndex = lineString.search(/\\s+$/);\n if (rightWhitespaceIndex !== -1) {\n finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);\n }\n // Return the empty string if only trimmed whitespace is selected\n if (finalEndCol <= widthAdjustedStartCol) {\n return '';\n }\n }\n\n return lineString.substring(widthAdjustedStartCol, finalEndCol);\n}\n","/**\n * Attributes and methods to help with identifying the current browser and platform.\n * @module xterm/utils/Browser\n * @license MIT\n */\n\nimport { contains } from './Generic';\n\nconst isNode = (typeof navigator === 'undefined') ? true : false;\nconst userAgent = (isNode) ? 'node' : navigator.userAgent;\nconst platform = (isNode) ? 'node' : navigator.platform;\n\nexport const isFirefox = !!~userAgent.indexOf('Firefox');\nexport const isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');\n\n// Find the users platform. We use this to interpret the meta key\n// and ISO third level shifts.\n// http://stackoverflow.com/q/19877924/577598\nexport const isMac = contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);\nexport const isIpad = platform === 'iPad';\nexport const isIphone = platform === 'iPhone';\nexport const isMSWindows = contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);\nexport const isLinux = platform.indexOf('Linux') >= 0;\n","/**\n * Clipboard handler module: exports methods for handling all clipboard-related events in the\n * terminal.\n * @module xterm/handlers/Clipboard\n * @license MIT\n */\n\nimport { ITerminal, ISelectionManager } from '../Interfaces';\n\ninterface IWindow extends Window {\n clipboardData?: {\n getData(format: string): string;\n setData(format: string, data: string);\n };\n}\n\ndeclare var window: IWindow;\n\n/**\n * Prepares text to be pasted into the terminal by normalizing the line endings\n * @param text The pasted text that needs processing before inserting into the terminal\n */\nexport function prepareTextForTerminal(text: string, isMSWindows: boolean): string {\n if (isMSWindows) {\n return text.replace(/\\r?\\n/g, '\\r');\n }\n return text;\n}\n\n/**\n * Binds copy functionality to the given terminal.\n * @param {ClipboardEvent} ev The original copy event to be handled\n */\nexport function copyHandler(ev: ClipboardEvent, term: ITerminal, selectionManager: ISelectionManager) {\n if (term.browser.isMSIE) {\n window.clipboardData.setData('Text', selectionManager.selectionText);\n } else {\n ev.clipboardData.setData('text/plain', selectionManager.selectionText);\n }\n\n // Prevent or the original text will be copied.\n ev.preventDefault();\n}\n\n/**\n * Redirect the clipboard's data to the terminal's input handler.\n * @param {ClipboardEvent} ev The original paste event to be handled\n * @param {Terminal} term The terminal on which to apply the handled paste event\n */\nexport function pasteHandler(ev: ClipboardEvent, term: ITerminal) {\n ev.stopPropagation();\n\n let text: string;\n\n let dispatchPaste = function(text) {\n text = prepareTextForTerminal(text, term.browser.isMSWindows);\n term.handler(text);\n term.textarea.value = '';\n term.emit('paste', text);\n\n return term.cancel(ev);\n };\n\n if (term.browser.isMSIE) {\n if (window.clipboardData) {\n text = window.clipboardData.getData('Text');\n dispatchPaste(text);\n }\n } else {\n if (ev.clipboardData) {\n text = ev.clipboardData.getData('text/plain');\n dispatchPaste(text);\n }\n }\n}\n\n/**\n * Moves the textarea under the mouse cursor and focuses it.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n */\nexport function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement) {\n // Bring textarea at the cursor position\n textarea.style.position = 'fixed';\n textarea.style.width = '20px';\n textarea.style.height = '20px';\n textarea.style.left = (ev.clientX - 10) + 'px';\n textarea.style.top = (ev.clientY - 10) + 'px';\n textarea.style.zIndex = '1000';\n\n textarea.focus();\n\n // Reset the terminal textarea's styling\n setTimeout(function () {\n textarea.style.position = null;\n textarea.style.width = null;\n textarea.style.height = null;\n textarea.style.left = null;\n textarea.style.top = null;\n textarea.style.zIndex = null;\n }, 4);\n}\n\n/**\n * Bind to right-click event and allow right-click copy and paste.\n * @param ev The original right click event to be handled.\n * @param textarea The terminal's textarea.\n * @param selectionManager The terminal's selection manager.\n */\nexport function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager) {\n moveTextAreaUnderMouseCursor(ev, textarea);\n\n // Get textarea ready to copy from the context menu\n textarea.value = selectionManager.selectionText;\n textarea.select();\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { CharMeasure } from './utils/CharMeasure';\n\n/**\n * Represents the viewport of a terminal, the visible area within the larger buffer of output.\n * Logic for the virtual scroll bar is included in this object.\n */\nexport class Viewport {\n private currentRowHeight: number;\n private lastRecordedBufferLength: number;\n private lastRecordedViewportHeight: number;\n private lastTouchY: number;\n\n /**\n * Creates a new Viewport.\n * @param terminal The terminal this viewport belongs to.\n * @param viewportElement The DOM element acting as the viewport.\n * @param scrollArea The DOM element acting as the scroll area.\n * @param charMeasure A DOM element used to measure the character size of. the terminal.\n */\n constructor(\n private terminal: ITerminal,\n private viewportElement: HTMLElement,\n private scrollArea: HTMLElement,\n private charMeasure: CharMeasure\n ) {\n this.currentRowHeight = 0;\n this.lastRecordedBufferLength = 0;\n this.lastRecordedViewportHeight = 0;\n\n this.terminal.on('scroll', this.syncScrollArea.bind(this));\n this.terminal.on('resize', this.syncScrollArea.bind(this));\n this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));\n\n // Perform this async to ensure the CharMeasure is ready.\n setTimeout(() => this.syncScrollArea(), 0);\n }\n\n /**\n * Refreshes row height, setting line-height, viewport height and scroll area height if\n * necessary.\n */\n private refresh(): void {\n if (this.charMeasure.height > 0) {\n const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;\n if (rowHeightChanged) {\n this.currentRowHeight = this.charMeasure.height;\n this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';\n this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';\n }\n const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;\n if (rowHeightChanged || viewportHeightChanged) {\n this.lastRecordedViewportHeight = this.terminal.rows;\n this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';\n this.terminal.selectionContainer.style.height = this.viewportElement.style.height;\n }\n this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';\n }\n }\n\n /**\n * Updates dimensions and synchronizes the scroll area if necessary.\n */\n public syncScrollArea(): void {\n if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) {\n // If buffer height changed\n this.lastRecordedBufferLength = this.terminal.buffer.lines.length;\n this.refresh();\n } else if (this.lastRecordedViewportHeight !== this.terminal.rows) {\n // If viewport height changed\n this.refresh();\n } else {\n // If size has changed, refresh viewport\n if (this.charMeasure.height !== this.currentRowHeight) {\n this.refresh();\n }\n }\n\n // Sync scrollTop\n const scrollTop = this.terminal.buffer.ydisp * this.currentRowHeight;\n if (this.viewportElement.scrollTop !== scrollTop) {\n this.viewportElement.scrollTop = scrollTop;\n }\n }\n\n /**\n * Handles scroll events on the viewport, calculating the new viewport and requesting the\n * terminal to scroll to it.\n * @param ev The scroll event.\n */\n private onScroll(ev: Event) {\n const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);\n const diff = newRow - this.terminal.buffer.ydisp;\n this.terminal.scrollDisp(diff, true);\n }\n\n /**\n * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual\n * scrolling to `onScroll`, this event needs to be attached manually by the consumer of\n * `Viewport`.\n * @param ev The mouse wheel event.\n */\n public onWheel(ev: WheelEvent) {\n if (ev.deltaY === 0) {\n // Do nothing if it's not a vertical scroll event\n return;\n }\n // Fallback to WheelEvent.DOM_DELTA_PIXEL\n let multiplier = 1;\n if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {\n multiplier = this.currentRowHeight;\n } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {\n multiplier = this.currentRowHeight * this.terminal.rows;\n }\n this.viewportElement.scrollTop += ev.deltaY * multiplier;\n // Prevent the page from scrolling when the terminal scrolls\n ev.preventDefault();\n };\n\n /**\n * Handles the touchstart event, recording the touch occurred.\n * @param ev The touch event.\n */\n public onTouchStart(ev: TouchEvent) {\n this.lastTouchY = ev.touches[0].pageY;\n };\n\n /**\n * Handles the touchmove event, scrolling the viewport if the position shifted.\n * @param ev The touch event.\n */\n public onTouchMove(ev: TouchEvent) {\n let deltaY = this.lastTouchY - ev.touches[0].pageY;\n this.lastTouchY = ev.touches[0].pageY;\n if (deltaY === 0) {\n return;\n }\n this.viewportElement.scrollTop += deltaY;\n ev.preventDefault();\n };\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\n/**\n * Represents a selection within the buffer. This model only cares about column\n * and row coordinates, not wide characters.\n */\nexport class SelectionModel {\n /**\n * Whether select all is currently active.\n */\n public isSelectAllActive: boolean;\n\n /**\n * The [x, y] position the selection starts at.\n */\n public selectionStart: [number, number];\n\n /**\n * The minimal length of the selection from the start position. When double\n * clicking on a word, the word will be selected which makes the selection\n * start at the start of the word and makes this variable the length.\n */\n public selectionStartLength: number;\n\n /**\n * The [x, y] position the selection ends at.\n */\n public selectionEnd: [number, number];\n\n constructor(\n private _terminal: ITerminal\n ) {\n this.clearSelection();\n }\n\n /**\n * Clears the current selection.\n */\n public clearSelection(): void {\n this.selectionStart = null;\n this.selectionEnd = null;\n this.isSelectAllActive = false;\n this.selectionStartLength = 0;\n }\n\n /**\n * The final selection start, taking into consideration select all.\n */\n public get finalSelectionStart(): [number, number] {\n if (this.isSelectAllActive) {\n return [0, 0];\n }\n\n if (!this.selectionEnd || !this.selectionStart) {\n return this.selectionStart;\n }\n\n return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;\n }\n\n /**\n * The final selection end, taking into consideration select all, double click\n * word selection and triple click line selection.\n */\n public get finalSelectionEnd(): [number, number] {\n if (this.isSelectAllActive) {\n return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1];\n }\n\n if (!this.selectionStart) {\n return null;\n }\n\n // Use the selection start if the end doesn't exist or they're reversed\n if (!this.selectionEnd || this.areSelectionValuesReversed()) {\n return [this.selectionStart[0] + this.selectionStartLength, this.selectionStart[1]];\n }\n\n // Ensure the the word/line is selected after a double/triple click\n if (this.selectionStartLength) {\n // Select the larger of the two when start and end are on the same line\n if (this.selectionEnd[1] === this.selectionStart[1]) {\n return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];\n }\n }\n return this.selectionEnd;\n }\n\n /**\n * Returns whether the selection start and end are reversed.\n */\n public areSelectionValuesReversed(): boolean {\n const start = this.selectionStart;\n const end = this.selectionEnd;\n return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n * @return Whether a refresh is necessary.\n */\n public onTrim(amount: number): boolean {\n // Adjust the selection position based on the trimmed amount.\n if (this.selectionStart) {\n this.selectionStart[1] -= amount;\n }\n if (this.selectionEnd) {\n this.selectionEnd[1] -= amount;\n }\n\n // The selection has moved off the buffer, clear it.\n if (this.selectionEnd && this.selectionEnd[1] < 0) {\n this.clearSelection();\n return true;\n }\n\n // If the selection start is trimmed, ensure the start column is 0.\n if (this.selectionStart && this.selectionStart[1] < 0) {\n this.selectionStart[1] = 0;\n }\n return false;\n }\n}\n","/**\n * @license MIT\n */\n\nimport * as Mouse from './utils/Mouse';\nimport * as Browser from './utils/Browser';\nimport { CharMeasure } from './utils/CharMeasure';\nimport { CircularList } from './utils/CircularList';\nimport { EventEmitter } from './EventEmitter';\nimport { ITerminal, ICircularList } from './Interfaces';\nimport { SelectionModel } from './SelectionModel';\nimport { translateBufferLineToString } from './utils/BufferLine';\n\n/**\n * The number of pixels the mouse needs to be above or below the viewport in\n * order to scroll at the maximum speed.\n */\nconst DRAG_SCROLL_MAX_THRESHOLD = 50;\n\n/**\n * The maximum scrolling speed\n */\nconst DRAG_SCROLL_MAX_SPEED = 15;\n\n/**\n * The number of milliseconds between drag scroll updates.\n */\nconst DRAG_SCROLL_INTERVAL = 50;\n\n/**\n * A string containing all characters that are considered word separated by the\n * double click to select work logic.\n */\nconst WORD_SEPARATORS = ' ()[]{}\\'\"';\n\n// TODO: Move these constants elsewhere, they belong in a buffer or buffer\n// data/line class.\nconst LINE_DATA_CHAR_INDEX = 1;\nconst LINE_DATA_WIDTH_INDEX = 2;\n\nconst NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);\nconst ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');\n\n/**\n * Represents a position of a word on a line.\n */\ninterface IWordPosition {\n start: number;\n length: number;\n}\n\n/**\n * A selection mode, this drives how the selection behaves on mouse move.\n */\nenum SelectionMode {\n NORMAL,\n WORD,\n LINE\n}\n\n/**\n * A class that manages the selection of the terminal. With help from\n * SelectionModel, SelectionManager handles with all logic associated with\n * dealing with the selection, including handling mouse interaction, wide\n * characters and fetching the actual text within the selection. Rendering is\n * not handled by the SelectionManager but a 'refresh' event is fired when the\n * selection is ready to be redrawn.\n */\nexport class SelectionManager extends EventEmitter {\n protected _model: SelectionModel;\n\n /**\n * The amount to scroll every drag scroll update (depends on how far the mouse\n * drag is above or below the terminal).\n */\n private _dragScrollAmount: number;\n\n /**\n * The current selection mode.\n */\n private _activeSelectionMode: SelectionMode;\n\n /**\n * A setInterval timer that is active while the mouse is down whose callback\n * scrolls the viewport when necessary.\n */\n private _dragScrollIntervalTimer: NodeJS.Timer;\n\n /**\n * The animation frame ID used for refreshing the selection.\n */\n private _refreshAnimationFrame: number;\n\n /**\n * Whether selection is enabled.\n */\n private _enabled = true;\n\n private _mouseMoveListener: EventListener;\n private _mouseUpListener: EventListener;\n\n constructor(\n private _terminal: ITerminal,\n private _buffer: ICircularList<[number, string, number][]>,\n private _rowContainer: HTMLElement,\n private _charMeasure: CharMeasure\n ) {\n super();\n this._initListeners();\n this.enable();\n\n this._model = new SelectionModel(_terminal);\n this._activeSelectionMode = SelectionMode.NORMAL;\n }\n\n /**\n * Initializes listener variables.\n */\n private _initListeners() {\n this._mouseMoveListener = event => this._onMouseMove(event);\n this._mouseUpListener = event => this._onMouseUp(event);\n\n this._rowContainer.addEventListener('mousedown', event => this._onMouseDown(event));\n\n // Only adjust the selection on trim, shiftElements is rarely used (only in\n // reverseIndex) and delete in a splice is only ever used when the same\n // number of elements was just added. Given this is could actually be\n // beneficial to leave the selection as is for these cases.\n this._buffer.on('trim', (amount: number) => this._onTrim(amount));\n }\n\n /**\n * Disables the selection manager. This is useful for when terminal mouse\n * are enabled.\n */\n public disable() {\n this.clearSelection();\n this._enabled = false;\n }\n\n /**\n * Enable the selection manager.\n */\n public enable() {\n this._enabled = true;\n }\n\n /**\n * Sets the active buffer, this should be called when the alt buffer is\n * switched in or out.\n * @param buffer The active buffer.\n */\n public setBuffer(buffer: ICircularList<[number, string, number][]>): void {\n this._buffer = buffer;\n this.clearSelection();\n }\n\n public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }\n public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }\n\n /**\n * Gets whether there is an active text selection.\n */\n public get hasSelection(): boolean {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return false;\n }\n return start[0] !== end[0] || start[1] !== end[1];\n }\n\n /**\n * Gets the text currently selected.\n */\n public get selectionText(): string {\n const start = this._model.finalSelectionStart;\n const end = this._model.finalSelectionEnd;\n if (!start || !end) {\n return '';\n }\n\n // Get first row\n const startRowEndCol = start[1] === end[1] ? end[0] : null;\n let result: string[] = [];\n result.push(translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));\n\n // Get middle rows\n for (let i = start[1] + 1; i <= end[1] - 1; i++) {\n const bufferLine = this._buffer.get(i);\n const lineText = translateBufferLineToString(bufferLine, true);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Get final row\n if (start[1] !== end[1]) {\n const bufferLine = this._buffer.get(end[1]);\n const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);\n if ((bufferLine).isWrapped) {\n result[result.length - 1] += lineText;\n } else {\n result.push(lineText);\n }\n }\n\n // Format string by replacing non-breaking space chars with regular spaces\n // and joining the array into a multi-line string.\n const formattedResult = result.map(line => {\n return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');\n }).join(Browser.isMSWindows ? '\\r\\n' : '\\n');\n\n return formattedResult;\n }\n\n /**\n * Clears the current terminal selection.\n */\n public clearSelection(): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this.refresh();\n }\n\n /**\n * Queues a refresh, redrawing the selection on the next opportunity.\n * @param isNewSelection Whether the selection should be registered as a new\n * selection on Linux.\n */\n public refresh(isNewSelection?: boolean): void {\n // Queue the refresh for the renderer\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());\n }\n\n // If the platform is Linux and the refresh call comes from a mouse event,\n // we need to update the selection for middle click to paste selection.\n if (Browser.isLinux && isNewSelection) {\n const selectionText = this.selectionText;\n if (selectionText.length) {\n this.emit('newselection', this.selectionText);\n }\n }\n }\n\n /**\n * Fires the refresh event, causing consumers to pick it up and redraw the\n * selection state.\n */\n private _refresh(): void {\n this._refreshAnimationFrame = null;\n this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd });\n }\n\n /**\n * Selects all text within the terminal.\n */\n public selectAll(): void {\n this._model.isSelectAllActive = true;\n this.refresh();\n }\n\n /**\n * Handle the buffer being trimmed, adjust the selection position.\n * @param amount The amount the buffer is being trimmed.\n */\n private _onTrim(amount: number) {\n const needsRefresh = this._model.onTrim(amount);\n if (needsRefresh) {\n this.refresh();\n }\n }\n\n /**\n * Gets the 0-based [x, y] buffer coordinates of the current mouse event.\n * @param event The mouse event.\n */\n private _getMouseBufferCoords(event: MouseEvent): [number, number] {\n const coords = Mouse.getCoords(event, this._rowContainer, this._charMeasure, this._terminal.cols, this._terminal.rows, true);\n if (!coords) {\n return null;\n }\n\n // Convert to 0-based\n coords[0]--;\n coords[1]--;\n // Convert viewport coords to buffer coords\n coords[1] += this._terminal.buffer.ydisp;\n return coords;\n }\n\n /**\n * Gets the amount the viewport should be scrolled based on how far out of the\n * terminal the mouse is.\n * @param event The mouse event.\n */\n private _getMouseEventScrollAmount(event: MouseEvent): number {\n let offset = Mouse.getCoordsRelativeToElement(event, this._rowContainer)[1];\n const terminalHeight = this._terminal.rows * this._charMeasure.height;\n if (offset >= 0 && offset <= terminalHeight) {\n return 0;\n }\n if (offset > terminalHeight) {\n offset -= terminalHeight;\n }\n\n offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);\n offset /= DRAG_SCROLL_MAX_THRESHOLD;\n return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));\n }\n\n /**\n * Handles te mousedown event, setting up for a new selection.\n * @param event The mousedown event.\n */\n private _onMouseDown(event: MouseEvent) {\n // If we have selection, we want the context menu on right click even if the\n // terminal is in mouse mode.\n if (event.button === 2 && this.hasSelection) {\n event.stopPropagation();\n return;\n }\n\n // Only action the primary button\n if (event.button !== 0) {\n return;\n }\n\n // Allow selection when using a specific modifier key, even when disabled\n if (!this._enabled) {\n const shouldForceSelection = Browser.isMac && event.altKey;\n\n if (!shouldForceSelection) {\n return;\n }\n\n // Don't send the mouse down event to the current process, we want to select\n event.stopPropagation();\n }\n\n // Tell the browser not to start a regular selection\n event.preventDefault();\n\n // Reset drag scroll state\n this._dragScrollAmount = 0;\n\n if (this._enabled && event.shiftKey) {\n this._onIncrementalClick(event);\n } else {\n if (event.detail === 1) {\n this._onSingleClick(event);\n } else if (event.detail === 2) {\n this._onDoubleClick(event);\n } else if (event.detail === 3) {\n this._onTripleClick(event);\n }\n }\n\n this._addMouseDownListeners();\n this.refresh(true);\n }\n\n /**\n * Adds listeners when mousedown is triggered.\n */\n private _addMouseDownListeners(): void {\n // Listen on the document so that dragging outside of viewport works\n this._rowContainer.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.addEventListener('mouseup', this._mouseUpListener);\n this._dragScrollIntervalTimer = setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);\n }\n\n /**\n * Removes the listeners that are registered when mousedown is triggered.\n */\n private _removeMouseDownListeners(): void {\n this._rowContainer.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);\n this._rowContainer.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);\n clearInterval(this._dragScrollIntervalTimer);\n this._dragScrollIntervalTimer = null;\n }\n\n /**\n * Performs an incremental click, setting the selection end position to the mouse\n * position.\n * @param event The mouse event.\n */\n private _onIncrementalClick(event: MouseEvent): void {\n if (this._model.selectionStart) {\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n }\n }\n\n /**\n * Performs a single click, resetting relevant state and setting the selection\n * start position.\n * @param event The mouse event.\n */\n private _onSingleClick(event: MouseEvent): void {\n this._model.selectionStartLength = 0;\n this._model.isSelectAllActive = false;\n this._activeSelectionMode = SelectionMode.NORMAL;\n\n // Initialize the new selection\n this._model.selectionStart = this._getMouseBufferCoords(event);\n if (!this._model.selectionStart) {\n return;\n }\n this._model.selectionEnd = null;\n\n // Ensure the line exists\n const line = this._buffer.get(this._model.selectionStart[1]);\n if (!line) {\n return;\n }\n\n // If the mouse is over the second half of a wide character, adjust the\n // selection to cover the whole character\n const char = line[this._model.selectionStart[0]];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n this._model.selectionStart[0]++;\n }\n }\n\n /**\n * Performs a double click, selecting the current work.\n * @param event The mouse event.\n */\n private _onDoubleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.WORD;\n this._selectWordAt(coords);\n }\n }\n\n /**\n * Performs a triple click, selecting the current line and activating line\n * select mode.\n * @param event The mouse event.\n */\n private _onTripleClick(event: MouseEvent): void {\n const coords = this._getMouseBufferCoords(event);\n if (coords) {\n this._activeSelectionMode = SelectionMode.LINE;\n this._selectLineAt(coords[1]);\n }\n }\n\n /**\n * Handles the mousemove event when the mouse button is down, recording the\n * end of the selection and refreshing the selection.\n * @param event The mousemove event.\n */\n private _onMouseMove(event: MouseEvent) {\n // Record the previous position so we know whether to redraw the selection\n // at the end.\n const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;\n\n // Set the initial selection end based on the mouse coordinates\n this._model.selectionEnd = this._getMouseBufferCoords(event);\n if (!this._model.selectionEnd) {\n this.refresh(true);\n return;\n }\n\n // Select the entire line if line select mode is active.\n if (this._activeSelectionMode === SelectionMode.LINE) {\n if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {\n this._model.selectionEnd[0] = 0;\n } else {\n this._model.selectionEnd[0] = this._terminal.cols;\n }\n } else if (this._activeSelectionMode === SelectionMode.WORD) {\n this._selectToWordAt(this._model.selectionEnd);\n }\n\n // Determine the amount of scrolling that will happen.\n this._dragScrollAmount = this._getMouseEventScrollAmount(event);\n\n // If the cursor was above or below the viewport, make sure it's at the\n // start or end of the viewport respectively.\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd[0] = this._terminal.cols - 1;\n } else if (this._dragScrollAmount < 0) {\n this._model.selectionEnd[0] = 0;\n }\n\n // If the character is a wide character include the cell to the right in the\n // selection. Note that selections at the very end of the line will never\n // have a character.\n if (this._model.selectionEnd[1] < this._buffer.length) {\n const char = this._buffer.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]];\n if (char && char[2] === 0) {\n this._model.selectionEnd[0]++;\n }\n }\n\n // Only draw here if the selection changes.\n if (!previousSelectionEnd ||\n previousSelectionEnd[0] !== this._model.selectionEnd[0] ||\n previousSelectionEnd[1] !== this._model.selectionEnd[1]) {\n this.refresh(true);\n }\n }\n\n /**\n * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the\n * scrolling of the viewport.\n */\n private _dragScroll() {\n if (this._dragScrollAmount) {\n this._terminal.scrollDisp(this._dragScrollAmount, false);\n // Re-evaluate selection\n if (this._dragScrollAmount > 0) {\n this._model.selectionEnd = [this._terminal.cols - 1, this._terminal.buffer.ydisp + this._terminal.rows];\n } else {\n this._model.selectionEnd = [0, this._terminal.buffer.ydisp];\n }\n this.refresh();\n }\n }\n\n /**\n * Handles the mouseup event, removing the mousedown listeners.\n * @param event The mouseup event.\n */\n private _onMouseUp(event: MouseEvent) {\n this._removeMouseDownListeners();\n }\n\n /**\n * Converts a viewport column to the character index on the buffer line, the\n * latter takes into account wide characters.\n * @param coords The coordinates to find the 2 index for.\n */\n private _convertViewportColToCharacterIndex(bufferLine: any, coords: [number, number]): number {\n let charIndex = coords[0];\n for (let i = 0; coords[0] >= i; i++) {\n const char = bufferLine[i];\n if (char[LINE_DATA_WIDTH_INDEX] === 0) {\n charIndex--;\n }\n }\n return charIndex;\n }\n\n public setSelection(col: number, row: number, length: number): void {\n this._model.clearSelection();\n this._removeMouseDownListeners();\n this._model.selectionStart = [col, row];\n this._model.selectionStartLength = length;\n this.refresh();\n }\n\n /**\n * Gets positional information for the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _getWordAt(coords: [number, number]): IWordPosition {\n const bufferLine = this._buffer.get(coords[1]);\n if (!bufferLine) {\n return null;\n }\n\n const line = translateBufferLineToString(bufferLine, false);\n\n // Get actual index, taking into consideration wide characters\n let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);\n let startIndex = endIndex;\n\n // Record offset to be used later\n const charOffset = coords[0] - startIndex;\n let leftWideCharCount = 0;\n let rightWideCharCount = 0;\n\n if (line.charAt(startIndex) === ' ') {\n // Expand until non-whitespace is hit\n while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {\n startIndex--;\n }\n while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {\n endIndex++;\n }\n } else {\n // Expand until whitespace is hit. This algorithm works by scanning left\n // and right from the starting position, keeping both the index format\n // (line) and the column format (bufferLine) in sync. When a wide\n // character is hit, it is recorded and the column index is adjusted.\n let startCol = coords[0];\n let endCol = coords[0];\n // Consider the initial position, skip it and increment the wide char\n // variable\n if (bufferLine[startCol][LINE_DATA_WIDTH_INDEX] === 0) {\n leftWideCharCount++;\n startCol--;\n }\n if (bufferLine[endCol][LINE_DATA_WIDTH_INDEX] === 2) {\n rightWideCharCount++;\n endCol++;\n }\n // Expand the string in both directions until a space is hit\n while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) {\n if (bufferLine[startCol - 1][LINE_DATA_WIDTH_INDEX] === 0) {\n // If the next character is a wide char, record it and skip the column\n leftWideCharCount++;\n startCol--;\n }\n startIndex--;\n startCol--;\n }\n while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) {\n if (bufferLine[endCol + 1][LINE_DATA_WIDTH_INDEX] === 2) {\n // If the next character is a wide char, record it and skip the column\n rightWideCharCount++;\n endCol++;\n }\n endIndex++;\n endCol++;\n }\n }\n\n const start = startIndex + charOffset - leftWideCharCount;\n const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols);\n return { start, length };\n }\n\n /**\n * Selects the word at the coordinates specified.\n * @param coords The coordinates to get the word at.\n */\n protected _selectWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionStart = [wordPosition.start, coords[1]];\n this._model.selectionStartLength = wordPosition.length;\n }\n }\n\n /**\n * Sets the selection end to the word at the coordinated specified.\n * @param coords The coordinates to get the word at.\n */\n private _selectToWordAt(coords: [number, number]): void {\n const wordPosition = this._getWordAt(coords);\n if (wordPosition) {\n this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]];\n }\n }\n\n /**\n * Gets whether the character is considered a word separator by the select\n * word logic.\n * @param char The character to check.\n */\n private _isCharWordSeparator(char: string): boolean {\n return WORD_SEPARATORS.indexOf(char) >= 0;\n }\n\n /**\n * Selects the line specified.\n * @param line The line index.\n */\n protected _selectLineAt(line: number): void {\n this._model.selectionStart = [0, line];\n this._model.selectionStartLength = this._terminal.cols;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\nimport { DomElementObjectPool } from './utils/DomElementObjectPool';\n\n/**\n * The maximum number of refresh frames to skip when the write buffer is non-\n * empty. Note that these frames may be intermingled with frames that are\n * skipped via requestAnimationFrame's mechanism.\n */\nconst MAX_REFRESH_FRAME_SKIP = 5;\n\n/**\n * Flags used to render terminal text properly.\n */\nenum FLAGS {\n BOLD = 1,\n UNDERLINE = 2,\n BLINK = 4,\n INVERSE = 8,\n INVISIBLE = 16\n};\n\nlet brokenBold: boolean = null;\n\nexport class Renderer {\n /** A queue of the rows to be refreshed */\n private _refreshRowsQueue: {start: number, end: number}[] = [];\n private _refreshFramesSkipped = 0;\n private _refreshAnimationFrame = null;\n\n private _spanElementObjectPool = new DomElementObjectPool('span');\n\n constructor(private _terminal: ITerminal) {\n // Figure out whether boldness affects\n // the character width of monospace fonts.\n if (brokenBold === null) {\n brokenBold = checkBoldBroken((this._terminal).element);\n }\n this._spanElementObjectPool = new DomElementObjectPool('span');\n\n // TODO: Pull more DOM interactions into Renderer.constructor, element for\n // example should be owned by Renderer (and also exposed by Terminal due to\n // to established public API).\n }\n\n /**\n * Queues a refresh between two rows (inclusive), to be done on next animation\n * frame.\n * @param {number} start The start row.\n * @param {number} end The end row.\n */\n public queueRefresh(start: number, end: number): void {\n this._refreshRowsQueue.push({ start: start, end: end });\n if (!this._refreshAnimationFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n }\n }\n\n /**\n * Performs the refresh loop callback, calling refresh only if a refresh is\n * necessary before queueing up the next one.\n */\n private _refreshLoop(): void {\n // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it\n // will need to be immediately refreshed anyway. This saves a lot of\n // rendering time as the viewport DOM does not need to be refreshed, no\n // scroll events, no layouts, etc.\n const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;\n if (skipFrame) {\n this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));\n return;\n }\n\n this._refreshFramesSkipped = 0;\n let start;\n let end;\n if (this._refreshRowsQueue.length > 4) {\n // Just do a full refresh when 5+ refreshes are queued\n start = 0;\n end = this._terminal.rows - 1;\n } else {\n // Get start and end rows that need refreshing\n start = this._refreshRowsQueue[0].start;\n end = this._refreshRowsQueue[0].end;\n for (let i = 1; i < this._refreshRowsQueue.length; i++) {\n if (this._refreshRowsQueue[i].start < start) {\n start = this._refreshRowsQueue[i].start;\n }\n if (this._refreshRowsQueue[i].end > end) {\n end = this._refreshRowsQueue[i].end;\n }\n }\n }\n this._refreshRowsQueue = [];\n this._refreshAnimationFrame = null;\n this._refresh(start, end);\n }\n\n /**\n * Refreshes (re-renders) terminal content within two rows (inclusive)\n *\n * Rendering Engine:\n *\n * In the screen buffer, each character is stored as a an array with a character\n * and a 32-bit integer:\n * - First value: a utf-16 character.\n * - Second value:\n * - Next 9 bits: background color (0-511).\n * - Next 9 bits: foreground color (0-511).\n * - Next 14 bits: a mask for misc. flags:\n * - 1=bold\n * - 2=underline\n * - 4=blink\n * - 8=inverse\n * - 16=invisible\n *\n * @param {number} start The row to start from (between 0 and terminal's height terminal - 1)\n * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)\n */\n private _refresh(start: number, end: number): void {\n // If this is a big refresh, remove the terminal rows from the DOM for faster calculations\n let parent;\n if (end - start >= this._terminal.rows / 2) {\n parent = this._terminal.element.parentNode;\n if (parent) {\n this._terminal.element.removeChild(this._terminal.rowContainer);\n }\n }\n\n let width = this._terminal.cols;\n let y = start;\n\n if (end >= this._terminal.rows) {\n this._terminal.log('`end` is too large. Most likely a bad CSR.');\n end = this._terminal.rows - 1;\n }\n\n for (; y <= end; y++) {\n let row = y + this._terminal.buffer.ydisp;\n\n let line = this._terminal.buffer.lines.get(row);\n\n let x;\n if (this._terminal.buffer.y === y - (this._terminal.buffer.ybase - this._terminal.buffer.ydisp) &&\n this._terminal.cursorState &&\n !this._terminal.cursorHidden) {\n x = this._terminal.buffer.x;\n } else {\n x = -1;\n }\n\n let attr = this._terminal.defAttr;\n\n const documentFragment = document.createDocumentFragment();\n let innerHTML = '';\n let currentElement;\n\n // Return the row's spans to the pool\n while (this._terminal.children[y].children.length) {\n const child = this._terminal.children[y].children[0];\n this._terminal.children[y].removeChild(child);\n this._spanElementObjectPool.release(child);\n }\n\n for (let i = 0; i < width; i++) {\n // TODO: Could data be a more specific type?\n let data: any = line[i][0];\n const ch = line[i][1];\n const ch_width: any = line[i][2];\n const isCursor: boolean = i === x;\n if (!ch_width) {\n continue;\n }\n\n if (data !== attr || isCursor) {\n if (attr !== this._terminal.defAttr && !isCursor) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n if (data !== this._terminal.defAttr || isCursor) {\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n }\n currentElement = this._spanElementObjectPool.acquire();\n\n let bg = data & 0x1ff;\n let fg = (data >> 9) & 0x1ff;\n let flags = data >> 18;\n\n if (isCursor) {\n currentElement.classList.add('reverse-video');\n currentElement.classList.add('terminal-cursor');\n }\n\n if (flags & FLAGS.BOLD) {\n if (!brokenBold) {\n currentElement.classList.add('xterm-bold');\n }\n // See: XTerm*boldColors\n if (fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.UNDERLINE) {\n currentElement.classList.add('xterm-underline');\n }\n\n if (flags & FLAGS.BLINK) {\n currentElement.classList.add('xterm-blink');\n }\n\n // If inverse flag is on, then swap the foreground and background variables.\n if (flags & FLAGS.INVERSE) {\n let temp = bg;\n bg = fg;\n fg = temp;\n // Should inverse just be before the above boldColors effect instead?\n if ((flags & 1) && fg < 8) {\n fg += 8;\n }\n }\n\n if (flags & FLAGS.INVISIBLE && !isCursor) {\n currentElement.classList.add('xterm-hidden');\n }\n\n /**\n * Weird situation: Invert flag used black foreground and white background results\n * in invalid background color, positioned at the 256 index of the 256 terminal\n * color map. Pin the colors manually in such a case.\n *\n * Source: https://github.com/sourcelair/xterm.js/issues/57\n */\n if (flags & FLAGS.INVERSE) {\n if (bg === 257) {\n bg = 15;\n }\n if (fg === 256) {\n fg = 0;\n }\n }\n\n if (bg < 256) {\n currentElement.classList.add(`xterm-bg-color-${bg}`);\n }\n\n if (fg < 256) {\n currentElement.classList.add(`xterm-color-${fg}`);\n }\n\n }\n }\n\n if (ch_width === 2) {\n // Wrap wide characters so they're sized correctly. It's more difficult to release these\n // from the object pool so just create new ones via innerHTML.\n innerHTML += `${ch}`;\n } else if (ch.charCodeAt(0) > 255) {\n // Wrap any non-wide unicode character as some fonts size them badly\n innerHTML += `${ch}`;\n } else {\n switch (ch) {\n case '&':\n innerHTML += '&';\n break;\n case '<':\n innerHTML += '<';\n break;\n case '>':\n innerHTML += '>';\n break;\n default:\n if (ch <= ' ') {\n innerHTML += ' ';\n } else {\n innerHTML += ch;\n }\n break;\n }\n }\n\n // The cursor needs its own element, therefore we set attr to -1\n // which will cause the next character to be rendered in a new element\n attr = isCursor ? -1 : data;\n\n }\n\n if (innerHTML && !currentElement) {\n currentElement = this._spanElementObjectPool.acquire();\n }\n if (currentElement) {\n if (innerHTML) {\n currentElement.innerHTML = innerHTML;\n innerHTML = '';\n }\n documentFragment.appendChild(currentElement);\n currentElement = null;\n }\n\n this._terminal.children[y].appendChild(documentFragment);\n }\n\n if (parent) {\n this._terminal.element.appendChild(this._terminal.rowContainer);\n }\n\n this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end});\n };\n\n /**\n * Refreshes the selection in the DOM.\n * @param start The selection start.\n * @param end The selection end.\n */\n public refreshSelection(start: [number, number], end: [number, number]) {\n // Remove all selections\n while (this._terminal.selectionContainer.children.length) {\n this._terminal.selectionContainer.removeChild(this._terminal.selectionContainer.children[0]);\n }\n\n // Selection does not exist\n if (!start || !end) {\n return;\n }\n\n // Translate from buffer position to viewport position\n const viewportStartRow = start[1] - this._terminal.buffer.ydisp;\n const viewportEndRow = end[1] - this._terminal.buffer.ydisp;\n const viewportCappedStartRow = Math.max(viewportStartRow, 0);\n const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);\n\n // No need to draw the selection\n if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {\n return;\n }\n\n // Create the selections\n const documentFragment = document.createDocumentFragment();\n // Draw first row\n const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;\n const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));\n // Draw middle rows\n const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));\n // Draw final row\n if (viewportCappedStartRow !== viewportCappedEndRow) {\n // Only draw viewportEndRow if it's not the same as viewporttartRow\n const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;\n documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));\n }\n this._terminal.selectionContainer.appendChild(documentFragment);\n }\n\n /**\n * Creates a selection element at the specified position.\n * @param row The row of the selection.\n * @param colStart The start column.\n * @param colEnd The end columns.\n */\n private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {\n const element = document.createElement('div');\n element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;\n element.style.top = `${row * this._terminal.charMeasure.height}px`;\n element.style.left = `${colStart * this._terminal.charMeasure.width}px`;\n element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;\n return element;\n }\n}\n\n\n// If bold is broken, we can't use it in the terminal.\nfunction checkBoldBroken(terminal) {\n const document = terminal.ownerDocument;\n const el = document.createElement('span');\n el.innerHTML = 'hello world';\n terminal.appendChild(el);\n const w1 = el.offsetWidth;\n const h1 = el.offsetHeight;\n el.style.fontWeight = 'bold';\n const w2 = el.offsetWidth;\n const h2 = el.offsetHeight;\n terminal.removeChild(el);\n return w1 !== w2 || h1 !== h2;\n}\n","/**\n * @license MIT\n */\n\nimport { C0 } from './EscapeSequences';\nimport { IInputHandler } from './Interfaces';\nimport { CHARSETS, DEFAULT_CHARSET } from './Charsets';\n\nconst normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};\nnormalStateHandler[C0.BEL] = (parser, handler) => handler.bell();\nnormalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();\nnormalStateHandler[C0.VT] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.FF] = normalStateHandler[C0.LF];\nnormalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();\nnormalStateHandler[C0.BS] = (parser, handler) => handler.backspace();\nnormalStateHandler[C0.HT] = (parser, handler) => handler.tab();\nnormalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();\nnormalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();\nnormalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);\n\n// TODO: Remove terminal when parser owns params and currentParam\nconst escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};\nescapedStateHandler['['] = (parser, terminal) => {\n // ESC [ Control Sequence Introducer (CSI is 0x9b)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.CSI_PARAM);\n};\nescapedStateHandler[']'] = (parser, terminal) => {\n // ESC ] Operating System Command (OSC is 0x9d)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.OSC);\n};\nescapedStateHandler['P'] = (parser, terminal) => {\n // ESC P Device Control String (DCS is 0x90)\n terminal.params = [];\n terminal.currentParam = 0;\n parser.setState(ParserState.DCS);\n};\nescapedStateHandler['_'] = (parser, terminal) => {\n // ESC _ Application Program Command ( APC is 0x9f).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['^'] = (parser, terminal) => {\n // ESC ^ Privacy Message ( PM is 0x9e).\n parser.setState(ParserState.IGNORE);\n};\nescapedStateHandler['c'] = (parser, terminal) => {\n // ESC c Full Reset (RIS).\n terminal.reset();\n};\nescapedStateHandler['E'] = (parser, terminal) => {\n // ESC E Next Line ( NEL is 0x85).\n terminal.buffer.x = 0;\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['D'] = (parser, terminal) => {\n // ESC D Index ( IND is 0x84).\n terminal.index();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['M'] = (parser, terminal) => {\n // ESC M Reverse Index ( RI is 0x8d).\n terminal.reverseIndex();\n parser.setState(ParserState.NORMAL);\n};\nescapedStateHandler['%'] = (parser, terminal) => {\n // ESC % Select default/utf-8 character set.\n // @ = default, G = utf-8\n terminal.setgLevel(0);\n terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)\n parser.setState(ParserState.NORMAL);\n parser.skipNextChar();\n};\nescapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};\ncsiParamStateHandler['?'] = (parser) => parser.setPrefix('?');\ncsiParamStateHandler['>'] = (parser) => parser.setPrefix('>');\ncsiParamStateHandler['!'] = (parser) => parser.setPrefix('!');\ncsiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);\ncsiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);\ncsiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);\ncsiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);\ncsiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);\ncsiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);\ncsiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);\ncsiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);\ncsiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);\ncsiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);\ncsiParamStateHandler['$'] = (parser) => parser.setPostfix('$');\ncsiParamStateHandler['\"'] = (parser) => parser.setPostfix('\"');\ncsiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');\ncsiParamStateHandler['\\''] = (parser) => parser.setPostfix('\\'');\ncsiParamStateHandler[';'] = (parser) => parser.finalizeParam();\ncsiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);\n\nconst csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};\ncsiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);\ncsiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);\ncsiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);\ncsiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);\ncsiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);\ncsiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);\ncsiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);\ncsiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);\ncsiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);\ncsiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);\ncsiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);\ncsiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);\ncsiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);\ncsiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);\ncsiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);\ncsiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);\ncsiStateHandler['T'] = (handler, params, prefix) => {\n if (params.length < 2 && !prefix) {\n handler.scrollDown(params);\n }\n};\ncsiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);\ncsiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);\ncsiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);\ncsiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);\ncsiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);\ncsiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);\ncsiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);\ncsiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);\ncsiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);\ncsiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);\ncsiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);\ncsiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);\ncsiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);\ncsiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);\ncsiStateHandler['p'] = (handler, params, prefix) => {\n switch (prefix) {\n case '!': handler.softReset(params); break;\n }\n};\ncsiStateHandler['q'] = (handler, params, prefix, postfix) => {\n if (postfix === ' ') {\n handler.setCursorStyle(params);\n }\n};\ncsiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);\ncsiStateHandler['s'] = (handler, params) => handler.saveCursor(params);\ncsiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);\ncsiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);\n\nenum ParserState {\n NORMAL = 0,\n ESCAPED = 1,\n CSI_PARAM = 2,\n CSI = 3,\n OSC = 4,\n CHARSET = 5,\n DCS = 6,\n IGNORE = 7\n}\n\n/**\n * The terminal's parser, all input into the terminal goes through the parser\n * which parses and defers the actual input handling the the IInputHandler\n * specified in the constructor.\n */\nexport class Parser {\n private _state: ParserState;\n private _position: number;\n\n // TODO: Remove terminal when handler can do everything\n constructor(\n private _inputHandler: IInputHandler,\n private _terminal: any\n ) {\n this._state = ParserState.NORMAL;\n }\n\n /**\n * Parse and handle data.\n *\n * @param data The data to parse.\n */\n public parse(data: string): ParserState {\n let l = data.length, j, cs, ch, code, low;\n\n if (this._terminal.debug) {\n this._terminal.log('data: ' + data);\n }\n\n this._position = 0;\n // apply leftover surrogate high from last write\n if (this._terminal.surrogate_high) {\n data = this._terminal.surrogate_high + data;\n this._terminal.surrogate_high = '';\n }\n\n for (; this._position < l; this._position++) {\n ch = data[this._position];\n\n // FIXME: higher chars than 0xa0 are not allowed in escape sequences\n // --> maybe move to default\n code = data.charCodeAt(this._position);\n if (0xD800 <= code && code <= 0xDBFF) {\n // we got a surrogate high\n // get surrogate low (next 2 bytes)\n low = data.charCodeAt(this._position + 1);\n if (isNaN(low)) {\n // end of data stream, save surrogate high\n this._terminal.surrogate_high = ch;\n continue;\n }\n code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;\n ch += data.charAt(this._position + 1);\n }\n // surrogate low - already handled above\n if (0xDC00 <= code && code <= 0xDFFF)\n continue;\n\n switch (this._state) {\n case ParserState.NORMAL:\n if (ch in normalStateHandler) {\n normalStateHandler[ch](this, this._inputHandler);\n } else {\n this._inputHandler.addChar(ch, code);\n }\n break;\n case ParserState.ESCAPED:\n if (ch in escapedStateHandler) {\n escapedStateHandler[ch](this, this._terminal);\n // Skip switch as it was just handled\n break;\n }\n switch (ch) {\n\n // ESC (,),*,+,-,. Designate G0-G2 Character Set.\n case '(': // <-- this seems to get all the attention\n case ')':\n case '*':\n case '+':\n case '-':\n case '.':\n switch (ch) {\n case '(':\n this._terminal.gcharset = 0;\n break;\n case ')':\n this._terminal.gcharset = 1;\n break;\n case '*':\n this._terminal.gcharset = 2;\n break;\n case '+':\n this._terminal.gcharset = 3;\n break;\n case '-':\n this._terminal.gcharset = 1;\n break;\n case '.':\n this._terminal.gcharset = 2;\n break;\n }\n this._state = ParserState.CHARSET;\n break;\n\n // Designate G3 Character Set (VT300).\n // A = ISO Latin-1 Supplemental.\n // Not implemented.\n case '/':\n this._terminal.gcharset = 3;\n this._state = ParserState.CHARSET;\n this._position--;\n break;\n\n // ESC N\n // Single Shift Select of G2 Character Set\n // ( SS2 is 0x8e). This affects next character only.\n case 'N':\n break;\n // ESC O\n // Single Shift Select of G3 Character Set\n // ( SS3 is 0x8f). This affects next character only.\n case 'O':\n break;\n // ESC n\n // Invoke the G2 Character Set as GL (LS2).\n case 'n':\n this._terminal.setgLevel(2);\n break;\n // ESC o\n // Invoke the G3 Character Set as GL (LS3).\n case 'o':\n this._terminal.setgLevel(3);\n break;\n // ESC |\n // Invoke the G3 Character Set as GR (LS3R).\n case '|':\n this._terminal.setgLevel(3);\n break;\n // ESC }\n // Invoke the G2 Character Set as GR (LS2R).\n case '}':\n this._terminal.setgLevel(2);\n break;\n // ESC ~\n // Invoke the G1 Character Set as GR (LS1R).\n case '~':\n this._terminal.setgLevel(1);\n break;\n\n // ESC 7 Save Cursor (DECSC).\n case '7':\n this._inputHandler.saveCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC 8 Restore Cursor (DECRC).\n case '8':\n this._inputHandler.restoreCursor();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC # 3 DEC line height/width\n case '#':\n this._state = ParserState.NORMAL;\n this._position++;\n break;\n\n // ESC H Tab Set (HTS is 0x88).\n case 'H':\n this._terminal.tabSet();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC = Application Keypad (DECKPAM).\n case '=':\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n // ESC > Normal Keypad (DECKPNM).\n case '>':\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n this._state = ParserState.NORMAL;\n break;\n\n default:\n this._state = ParserState.NORMAL;\n this._terminal.error('Unknown ESC control: %s.', ch);\n break;\n }\n break;\n\n case ParserState.CHARSET:\n if (ch in CHARSETS) {\n cs = CHARSETS[ch];\n if (ch === '/') { // ISOLatin is actually /A\n this.skipNextChar();\n }\n } else {\n cs = DEFAULT_CHARSET;\n }\n this._terminal.setgCharset(this._terminal.gcharset, cs);\n this._terminal.gcharset = null;\n this._state = ParserState.NORMAL;\n break;\n\n case ParserState.OSC:\n // OSC Ps ; Pt ST\n // OSC Ps ; Pt BEL\n // Set Text Parameters.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n\n this._terminal.params.push(this._terminal.currentParam);\n\n switch (this._terminal.params[0]) {\n case 0:\n case 1:\n case 2:\n if (this._terminal.params[1]) {\n this._terminal.title = this._terminal.params[1];\n this._terminal.handleTitle(this._terminal.title);\n }\n break;\n case 3:\n // set X property\n break;\n case 4:\n case 5:\n // change dynamic colors\n break;\n case 10:\n case 11:\n case 12:\n case 13:\n case 14:\n case 15:\n case 16:\n case 17:\n case 18:\n case 19:\n // change dynamic ui colors\n break;\n case 46:\n // change log file\n break;\n case 50:\n // dynamic font\n break;\n case 51:\n // emacs shell\n break;\n case 52:\n // manipulate selection data\n break;\n case 104:\n case 105:\n case 110:\n case 111:\n case 112:\n case 113:\n case 114:\n case 115:\n case 116:\n case 117:\n case 118:\n // reset colors\n break;\n }\n\n this._terminal.params = [];\n this._terminal.currentParam = 0;\n this._state = ParserState.NORMAL;\n } else {\n if (!this._terminal.params.length) {\n if (ch >= '0' && ch <= '9') {\n this._terminal.currentParam =\n this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;\n } else if (ch === ';') {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = '';\n }\n } else {\n this._terminal.currentParam += ch;\n }\n }\n break;\n\n case ParserState.CSI_PARAM:\n if (ch in csiParamStateHandler) {\n csiParamStateHandler[ch](this);\n break;\n }\n this.finalizeParam();\n // Fall through the CSI as this character should be the CSI code.\n this._state = ParserState.CSI;\n\n case ParserState.CSI:\n if (ch in csiStateHandler) {\n if (this._terminal.debug) {\n this._terminal.log(`CSI ${this._terminal.prefix ? this._terminal.prefix : ''} ${this._terminal.params ? this._terminal.params.join(';') : ''} ${this._terminal.postfix ? this._terminal.postfix : ''} ${ch}`);\n }\n csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);\n } else {\n this._terminal.error('Unknown CSI code: %s.', ch);\n }\n\n this._state = ParserState.NORMAL;\n this._terminal.prefix = '';\n this._terminal.postfix = '';\n break;\n\n case ParserState.DCS:\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n let pt;\n let valid: boolean;\n\n switch (this._terminal.prefix) {\n // User-Defined Keys (DECUDK).\n case '':\n break;\n\n // Request Status String (DECRQSS).\n // test: echo -e '\\eP$q\"p\\e\\\\'\n case '$q':\n pt = this._terminal.currentParam;\n valid = false;\n\n switch (pt) {\n // DECSCA\n case '\"q':\n pt = '0\"q';\n break;\n\n // DECSCL\n case '\"p':\n pt = '61\"p';\n break;\n\n // DECSTBM\n case 'r':\n pt = ''\n + (this._terminal.buffer.scrollTop + 1)\n + ';'\n + (this._terminal.buffer.scrollBottom + 1)\n + 'r';\n break;\n\n // SGR\n case 'm':\n pt = '0m';\n break;\n\n default:\n this._terminal.error('Unknown DCS Pt: %s.', pt);\n pt = '';\n break;\n }\n\n this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\\\');\n break;\n\n // Set Termcap/Terminfo Data (xterm, experimental).\n case '+p':\n break;\n\n // Request Termcap/Terminfo String (xterm, experimental)\n // Regular xterm does not even respond to this sequence.\n // This can cause a small glitch in vim.\n // test: echo -ne '\\eP+q6b64\\e\\\\'\n case '+q':\n pt = this._terminal.currentParam;\n valid = false;\n\n this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\\\');\n break;\n\n default:\n this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);\n break;\n }\n\n this._terminal.currentParam = 0;\n this._terminal.prefix = '';\n this._state = ParserState.NORMAL;\n } else if (!this._terminal.currentParam) {\n if (!this._terminal.prefix && ch !== '$' && ch !== '+') {\n this._terminal.currentParam = ch;\n } else if (this._terminal.prefix.length === 2) {\n this._terminal.currentParam = ch;\n } else {\n this._terminal.prefix += ch;\n }\n } else {\n this._terminal.currentParam += ch;\n }\n break;\n\n case ParserState.IGNORE:\n // For PM and APC.\n if (ch === C0.ESC || ch === C0.BEL) {\n if (ch === C0.ESC) this._position++;\n this._state = ParserState.NORMAL;\n }\n break;\n }\n }\n return this._state;\n }\n\n /**\n * Set the parser's current parsing state.\n *\n * @param state The new state.\n */\n public setState(state: ParserState): void {\n this._state = state;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'\n * or '!'.\n *\n * @param prefix The prefix.\n */\n public setPrefix(prefix: string): void {\n this._terminal.prefix = prefix;\n }\n\n /**\n * Sets the parsier's current prefix. CSI codes can have postfixes of '$',\n * '\"', ' ', '\\''.\n *\n * @param postfix The postfix.\n */\n public setPostfix(postfix: string): void {\n this._terminal.postfix = postfix;\n }\n\n /**\n * Sets the parser's current parameter.\n *\n * @param param the parameter.\n */\n public setParam(param: number) {\n this._terminal.currentParam = param;\n }\n\n /**\n * Gets the parser's current parameter.\n */\n public getParam(): number {\n return this._terminal.currentParam;\n }\n\n /**\n * Finalizes the parser's current parameter, adding it to the list of\n * parameters and setting the new current parameter to 0.\n */\n public finalizeParam(): void {\n this._terminal.params.push(this._terminal.currentParam);\n this._terminal.currentParam = 0;\n }\n\n /**\n * Tell the parser to skip the next character.\n */\n public skipNextChar(): void {\n this._position++;\n }\n\n /**\n * Tell the parser to repeat parsing the current character (for example if it\n * needs parsing using a different state.\n */\n // public repeatChar(): void {\n // this._position--;\n // }\n}\n","/**\n * @license MIT\n */\n\nimport { LinkMatcherOptions } from './Interfaces';\nimport { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';\n\nconst INVALID_LINK_CLASS = 'xterm-invalid-link';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~]*)*';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\n/**\n * The ID of the built in http(s) link matcher.\n */\nconst HYPERTEXT_LINK_MATCHER_ID = 0;\n\n/**\n * The Linkifier applies links to rows shortly after they have been refreshed.\n */\nexport class Linkifier {\n /**\n * The time to wait after a row is changed before it is linkified. This prevents\n * the costly operation of searching every row multiple times, potentially a\n * huge amount of times.\n */\n protected static TIME_BEFORE_LINKIFY = 200;\n\n protected _linkMatchers: LinkMatcher[];\n\n private _document: Document;\n private _rows: HTMLElement[];\n private _rowTimeoutIds: number[];\n private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;\n\n constructor() {\n this._rowTimeoutIds = [];\n this._linkMatchers = [];\n this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });\n }\n\n /**\n * Attaches the linkifier to the DOM, enabling linkification.\n * @param document The document object.\n * @param rows The array of rows to apply links to.\n */\n public attachToDom(document: Document, rows: HTMLElement[]) {\n this._document = document;\n this._rows = rows;\n }\n\n /**\n * Queues a row for linkification.\n * @param {number} rowIndex The index of the row to linkify.\n */\n public linkifyRow(rowIndex: number): void {\n // Don't attempt linkify if not yet attached to DOM\n if (!this._document) {\n return;\n }\n\n const timeoutId = this._rowTimeoutIds[rowIndex];\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY);\n }\n\n /**\n * Attaches a handler for hypertext links, overriding default behavior\n * for standard http(s) links.\n * @param {LinkHandler} handler The handler to use, this can be cleared with\n * null.\n */\n public setHypertextLinkHandler(handler: LinkMatcherHandler): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;\n }\n\n /**\n * Attaches a validation callback for hypertext links.\n * @param {LinkMatcherValidationCallback} callback The callback to use, this\n * can be cleared with null.\n */\n public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {\n this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;\n }\n\n /**\n * Registers a link matcher, allowing custom link patterns to be matched and\n * handled.\n * @param {RegExp} regex The regular expression to search for, specifically\n * this searches the textContent of the rows. You will want to use \\s to match\n * a space ' ' character for example.\n * @param {LinkHandler} handler The callback when the link is called.\n * @param {LinkMatcherOptions} [options] Options for the link matcher.\n * @return {number} The ID of the new matcher, this can be used to deregister.\n */\n public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number {\n if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {\n throw new Error('handler must be defined');\n }\n const matcher: LinkMatcher = {\n id: this._nextLinkMatcherId++,\n regex,\n handler,\n matchIndex: options.matchIndex,\n validationCallback: options.validationCallback,\n priority: options.priority || 0\n };\n this._addLinkMatcherToList(matcher);\n return matcher.id;\n }\n\n /**\n * Inserts a link matcher to the list in the correct position based on the\n * priority of each link matcher. New link matchers of equal priority are\n * considered after older link matchers.\n * @param matcher The link matcher to be added.\n */\n private _addLinkMatcherToList(matcher: LinkMatcher): void {\n if (this._linkMatchers.length === 0) {\n this._linkMatchers.push(matcher);\n return;\n }\n\n for (let i = this._linkMatchers.length - 1; i >= 0; i--) {\n if (matcher.priority <= this._linkMatchers[i].priority) {\n this._linkMatchers.splice(i + 1, 0, matcher);\n return;\n }\n }\n\n this._linkMatchers.splice(0, 0, matcher);\n }\n\n /**\n * Deregisters a link matcher if it has been registered.\n * @param {number} matcherId The link matcher's ID (returned after register)\n * @return {boolean} Whether a link matcher was found and deregistered.\n */\n public deregisterLinkMatcher(matcherId: number): boolean {\n // ID 0 is the hypertext link matcher which cannot be deregistered\n for (let i = 1; i < this._linkMatchers.length; i++) {\n if (this._linkMatchers[i].id === matcherId) {\n this._linkMatchers.splice(i, 1);\n return true;\n }\n }\n return false;\n }\n\n /**\n * Linkifies a row.\n * @param {number} rowIndex The index of the row to linkify.\n */\n private _linkifyRow(rowIndex: number): void {\n const row = this._rows[rowIndex];\n if (!row) {\n return;\n }\n const text = row.textContent;\n for (let i = 0; i < this._linkMatchers.length; i++) {\n const matcher = this._linkMatchers[i];\n const linkElements = this._doLinkifyRow(row, matcher);\n if (linkElements.length > 0) {\n // Fire validation callback\n if (matcher.validationCallback) {\n for (let j = 0; j < linkElements.length; j++) {\n const element = linkElements[j];\n matcher.validationCallback(element.textContent, element, isValid => {\n if (!isValid) {\n element.classList.add(INVALID_LINK_CLASS);\n }\n });\n }\n }\n // Only allow a single LinkMatcher to trigger on any given row.\n return;\n }\n }\n }\n\n /**\n * Linkifies a row given a specific handler.\n * @param {HTMLElement} row The row to linkify.\n * @param {LinkMatcher} matcher The link matcher for this line.\n * @return The link element(s) that were added.\n */\n private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] {\n // Iterate over nodes as we want to consider text nodes\n let result = [];\n const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;\n const nodes = row.childNodes;\n\n // Find the first match\n let match = row.textContent.match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n // Set the next searches start index\n let rowStartIndex = match.index + uri.length;\n\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const searchIndex = node.textContent.indexOf(uri);\n if (searchIndex >= 0) {\n const linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);\n if (node.textContent.length === uri.length) {\n // Matches entire string\n if (node.nodeType === 3 /*Node.TEXT_NODE*/) {\n this._replaceNode(node, linkElement);\n } else {\n const element = (node);\n if (element.nodeName === 'A') {\n // This row has already been linkified\n return result;\n }\n element.innerHTML = '';\n element.appendChild(linkElement);\n }\n } else if (node.childNodes.length > 1) {\n // Matches part of string in an element with multiple child nodes\n for (let j = 0; j < node.childNodes.length; j++) {\n const childNode = node.childNodes[j];\n const childSearchIndex = childNode.textContent.indexOf(uri);\n if (childSearchIndex !== -1) {\n // Match found in currentNode\n this._replaceNodeSubstringWithNode(childNode, linkElement, uri, childSearchIndex);\n // Don't need to count nodesAdded by replacing the node as this\n // is a child node, not a top-level node.\n break;\n }\n }\n } else {\n // Matches part of string in a single text node\n const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);\n // No need to consider the new nodes\n i += nodesAdded;\n }\n result.push(linkElement);\n\n // Find the next match\n match = row.textContent.substring(rowStartIndex).match(matcher.regex);\n if (!match || match.length === 0) {\n return result;\n }\n uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];\n rowStartIndex += match.index + uri.length;\n }\n }\n return result;\n }\n\n /**\n * Creates a link anchor element.\n * @param {string} uri The uri of the link.\n * @return {HTMLAnchorElement} The link.\n */\n private _createAnchorElement(uri: string, handler: LinkMatcherHandler, isHypertextLinkHandler: boolean): HTMLAnchorElement {\n const element = this._document.createElement('a');\n element.textContent = uri;\n element.draggable = false;\n if (isHypertextLinkHandler) {\n element.href = uri;\n // Force link on another tab so work is not lost\n element.target = '_blank';\n element.addEventListener('click', (event: MouseEvent) => {\n if (handler) {\n return handler(event, uri);\n }\n });\n } else {\n element.addEventListener('click', (event: MouseEvent) => {\n // Don't execute the handler if the link is flagged as invalid\n if (element.classList.contains(INVALID_LINK_CLASS)) {\n return;\n }\n return handler(event, uri);\n });\n }\n return element;\n }\n\n /**\n * Replace a node with 1 or more other nodes.\n * @param {Node} oldNode The node to replace.\n * @param {Node[]} newNodes The new nodes to insert in order.\n */\n private _replaceNode(oldNode: Node, ...newNodes: Node[]): void {\n const parent = oldNode.parentNode;\n for (let i = 0; i < newNodes.length; i++) {\n parent.insertBefore(newNodes[i], oldNode);\n }\n parent.removeChild(oldNode);\n }\n\n /**\n * Replace a substring within a node with a new node.\n * @param {Node} targetNode The target node; either a text node or a \n * containing a single text node.\n * @param {Node} newNode The new node to insert.\n * @param {string} substring The substring to replace.\n * @param {number} substringIndex The index of the substring within the string.\n * @return The number of nodes to skip when searching for the next uri.\n */\n private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number {\n // If the targetNode is a non-text node with a single child, make the child\n // the new targetNode.\n if (targetNode.childNodes.length === 1) {\n targetNode = targetNode.childNodes[0];\n }\n\n // The targetNode will be either a text node or a . The text node\n // (targetNode or its only-child) needs to be replaced with newNode plus new\n // text nodes potentially on either side.\n if (targetNode.nodeType !== 3/*Node.TEXT_NODE*/) {\n throw new Error('targetNode must be a text node or only contain a single text node');\n }\n\n const fullText = targetNode.textContent;\n\n if (substringIndex === 0) {\n // Replace with \n const rightText = fullText.substring(substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, newNode, rightTextNode);\n return 0;\n }\n\n if (substringIndex === targetNode.textContent.length - substring.length) {\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n this._replaceNode(targetNode, leftTextNode, newNode);\n return 0;\n }\n\n // Replace with \n const leftText = fullText.substring(0, substringIndex);\n const leftTextNode = this._document.createTextNode(leftText);\n const rightText = fullText.substring(substringIndex + substring.length);\n const rightTextNode = this._document.createTextNode(rightText);\n this._replaceNode(targetNode, leftTextNode, newNode, rightTextNode);\n return 1;\n }\n}\n","/**\n * @license MIT\n */\n\nimport { IInputHandler, ITerminal } from './Interfaces';\nimport { C0 } from './EscapeSequences';\nimport { DEFAULT_CHARSET } from './Charsets';\n\n/**\n * The terminal's standard implementation of IInputHandler, this handles all\n * input from the Parser.\n *\n * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand\n * each function's header comment.\n */\nexport class InputHandler implements IInputHandler {\n // TODO: We want to type _terminal when it's pulled into TS\n constructor(private _terminal: any) { }\n\n public addChar(char: string, code: number): void {\n if (char >= ' ') {\n // calculate print space\n // expensive call, therefore we save width in line buffer\n const ch_width = wcwidth(code);\n\n if (this._terminal.charset && this._terminal.charset[char]) {\n char = this._terminal.charset[char];\n }\n\n let row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert combining char in last cell\n // FIXME: needs handling after cursor jumps\n if (!ch_width && this._terminal.buffer.x) {\n // dont overflow left\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1]) {\n if (!this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][2]) {\n\n // found empty cell after fullwidth, need to go 2 cells back\n if (this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2])\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 2][1] += char;\n\n } else {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x - 1][1] += char;\n }\n this._terminal.updateRange(this._terminal.buffer.y);\n }\n return;\n }\n\n // goto next line if ch would overflow\n // TODO: needs a global min terminal width of 2\n if (this._terminal.buffer.x + ch_width - 1 >= this._terminal.cols) {\n // autowrap - DECAWM\n if (this._terminal.wraparoundMode) {\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll(true);\n } else {\n // The line already exists (eg. the initial viewport), mark it as a\n // wrapped line\n this._terminal.buffer.lines.get(this._terminal.buffer.y).isWrapped = true;\n }\n } else {\n if (ch_width === 2) // FIXME: check for xterm behavior\n return;\n }\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n // insert mode: move characters to right\n if (this._terminal.insertMode) {\n // do this twice for a fullwidth char\n for (let moves = 0; moves < ch_width; ++moves) {\n // remove last cell, if it's width is 0\n // we have to adjust the second last cell as well\n const removed = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).pop();\n if (removed[2] === 0\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2]\n && this._terminal.buffer.lines.get(row)[this._terminal.cols - 2][2] === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];\n }\n\n // insert empty cell at cursor\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 0, [this._terminal.curAttr, ' ', 1]);\n }\n }\n\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, char, ch_width];\n this._terminal.buffer.x++;\n this._terminal.updateRange(this._terminal.buffer.y);\n\n // fullwidth char - set next cell width to zero and advance cursor\n if (ch_width === 2) {\n this._terminal.buffer.lines.get(row)[this._terminal.buffer.x] = [this._terminal.curAttr, '', 0];\n this._terminal.buffer.x++;\n }\n }\n }\n\n /**\n * BEL\n * Bell (Ctrl-G).\n */\n public bell(): void {\n if (!this._terminal.visualBell) {\n return;\n }\n this._terminal.element.style.borderColor = 'white';\n setTimeout(() => this._terminal.element.style.borderColor = '', 10);\n if (this._terminal.popOnBell) {\n this._terminal.focus();\n }\n }\n\n /**\n * LF\n * Line Feed or New Line (NL). (LF is Ctrl-J).\n */\n public lineFeed(): void {\n if (this._terminal.convertEol) {\n this._terminal.buffer.x = 0;\n }\n this._terminal.buffer.y++;\n if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) {\n this._terminal.buffer.y--;\n this._terminal.scroll();\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n /**\n * This event is emitted whenever the terminal outputs a LF or NL.\n *\n * @event lineFeed\n */\n this._terminal.emit('lineFeed');\n }\n\n /**\n * CR\n * Carriage Return (Ctrl-M).\n */\n public carriageReturn(): void {\n this._terminal.buffer.x = 0;\n }\n\n /**\n * BS\n * Backspace (Ctrl-H).\n */\n public backspace(): void {\n if (this._terminal.buffer.x > 0) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * TAB\n * Horizontal Tab (HT) (Ctrl-I).\n */\n public tab(): void {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n\n /**\n * SO\n * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the\n * G1 character set.\n */\n public shiftOut(): void {\n this._terminal.setgLevel(1);\n }\n\n /**\n * SI\n * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0\n * character set (the default).\n */\n public shiftIn(): void {\n this._terminal.setgLevel(0);\n }\n\n /**\n * CSI Ps @\n * Insert Ps (Blank) Character(s) (default = 1) (ICH).\n */\n public insertChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) param = 1;\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row).splice(j++, 0, ch);\n this._terminal.buffer.lines.get(row).pop();\n }\n }\n\n /**\n * CSI Ps A\n * Cursor Up Ps Times (default = 1) (CUU).\n */\n public cursorUp(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n }\n\n /**\n * CSI Ps B\n * Cursor Down Ps Times (default = 1) (CUD).\n */\n public cursorDown(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps C\n * Cursor Forward Ps Times (default = 1) (CUF).\n */\n public cursorForward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps D\n * Cursor Backward Ps Times (default = 1) (CUB).\n */\n public cursorBackward(params: number[]) {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n this._terminal.buffer.x -= param;\n if (this._terminal.buffer.x < 0) {\n this._terminal.buffer.x = 0;\n }\n }\n\n /**\n * CSI Ps E\n * Cursor Next Line Ps Times (default = 1) (CNL).\n * same as CSI Ps B ?\n */\n public cursorNextLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps F\n * Cursor Preceding Line Ps Times (default = 1) (CNL).\n * reuse CSI Ps A ?\n */\n public cursorPrecedingLine(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y -= param;\n if (this._terminal.buffer.y < 0) {\n this._terminal.buffer.y = 0;\n }\n this._terminal.buffer.x = 0;\n }\n\n\n /**\n * CSI Ps G\n * Cursor Character Absolute [column] (default = [row,1]) (CHA).\n */\n public cursorCharAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n }\n\n /**\n * CSI Ps ; Ps H\n * Cursor Position [row;column] (default = [1,1]) (CUP).\n */\n public cursorPosition(params: number[]): void {\n let row, col;\n\n row = params[0] - 1;\n\n if (params.length >= 2) {\n col = params[1] - 1;\n } else {\n col = 0;\n }\n\n if (row < 0) {\n row = 0;\n } else if (row >= this._terminal.rows) {\n row = this._terminal.rows - 1;\n }\n\n if (col < 0) {\n col = 0;\n } else if (col >= this._terminal.cols) {\n col = this._terminal.cols - 1;\n }\n\n this._terminal.buffer.x = col;\n this._terminal.buffer.y = row;\n }\n\n /**\n * CSI Ps I\n * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).\n */\n public cursorForwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.nextStop();\n }\n }\n\n /**\n * CSI Ps J Erase in Display (ED).\n * Ps = 0 -> Erase Below (default).\n * Ps = 1 -> Erase Above.\n * Ps = 2 -> Erase All.\n * Ps = 3 -> Erase Saved Lines (xterm).\n * CSI ? Ps J\n * Erase in Display (DECSED).\n * Ps = 0 -> Selective Erase Below (default).\n * Ps = 1 -> Selective Erase Above.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInDisplay(params: number[]): void {\n let j;\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y + 1;\n for (; j < this._terminal.rows; j++) {\n this._terminal.eraseLine(j);\n }\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n j = this._terminal.buffer.y;\n while (j--) {\n this._terminal.eraseLine(j);\n }\n break;\n case 2:\n j = this._terminal.rows;\n while (j--) this._terminal.eraseLine(j);\n break;\n case 3:\n // Clear scrollback (everything not in viewport)\n const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;\n if (scrollBackSize > 0) {\n this._terminal.buffer.lines.trimStart(scrollBackSize);\n this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);\n this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);\n // Force a scroll event to refresh viewport\n this._terminal.emit('scroll', 0);\n }\n break;\n }\n }\n\n /**\n * CSI Ps K Erase in Line (EL).\n * Ps = 0 -> Erase to Right (default).\n * Ps = 1 -> Erase to Left.\n * Ps = 2 -> Erase All.\n * CSI ? Ps K\n * Erase in Line (DECSEL).\n * Ps = 0 -> Selective Erase to Right (default).\n * Ps = 1 -> Selective Erase to Left.\n * Ps = 2 -> Selective Erase All.\n */\n public eraseInLine(params: number[]): void {\n switch (params[0]) {\n case 0:\n this._terminal.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 1:\n this._terminal.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);\n break;\n case 2:\n this._terminal.eraseLine(this._terminal.buffer.y);\n break;\n }\n }\n\n /**\n * CSI Ps L\n * Insert Ps Line(s) (default = 1) (IL).\n */\n public insertLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j + 1;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase--;\n this._terminal.buffer.ydisp--;\n row--;\n j--;\n }\n // test: echo -e '\\e[44m\\e[1L\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(row, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(j, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps M\n * Delete Ps Line(s) (default = 1) (DL).\n */\n public deleteLines(params: number[]): void {\n let param, row, j;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n\n j = this._terminal.rows - 1 - this._terminal.buffer.scrollBottom;\n j = this._terminal.rows - 1 + this._terminal.buffer.ybase - j;\n\n while (param--) {\n if (this._terminal.buffer.lines.length === this._terminal.buffer.lines.maxLength) {\n // Trim the start of lines to make room for the new line\n this._terminal.buffer.lines.trimStart(1);\n this._terminal.buffer.ybase -= 1;\n this._terminal.buffer.ydisp -= 1;\n }\n // test: echo -e '\\e[44m\\e[1M\\e[0m'\n // blankLine(true) - xterm/linux behavior\n this._terminal.buffer.lines.splice(j + 1, 0, this._terminal.blankLine(true));\n this._terminal.buffer.lines.splice(row, 1);\n }\n\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.y);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps P\n * Delete Ps Character(s) (default = 1) (DCH).\n */\n public deleteChars(params: number[]): void {\n let param, row, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param--) {\n this._terminal.buffer.lines.get(row).splice(this._terminal.buffer.x, 1);\n this._terminal.buffer.lines.get(row).push(ch);\n }\n }\n\n /**\n * CSI Ps S Scroll up Ps lines (default = 1) (SU).\n */\n public scrollUp(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps T Scroll down Ps lines (default = 1) (SD).\n */\n public scrollDown(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollBottom, 1);\n this._terminal.buffer.lines.splice(this._terminal.buffer.ybase + this._terminal.buffer.scrollTop, 0, this._terminal.blankLine());\n }\n // this.maxRange();\n this._terminal.updateRange(this._terminal.buffer.scrollTop);\n this._terminal.updateRange(this._terminal.buffer.scrollBottom);\n }\n\n /**\n * CSI Ps X\n * Erase Ps Character(s) (default = 1) (ECH).\n */\n public eraseChars(params: number[]): void {\n let param, row, j, ch;\n\n param = params[0];\n if (param < 1) {\n param = 1;\n }\n\n row = this._terminal.buffer.y + this._terminal.buffer.ybase;\n j = this._terminal.buffer.x;\n ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm\n\n while (param-- && j < this._terminal.cols) {\n this._terminal.buffer.lines.get(row)[j++] = ch;\n }\n }\n\n /**\n * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).\n */\n public cursorBackwardTab(params: number[]): void {\n let param = params[0] || 1;\n while (param--) {\n this._terminal.buffer.x = this._terminal.prevStop();\n }\n }\n\n /**\n * CSI Pm ` Character Position Absolute\n * [column] (default = [row,1]) (HPA).\n */\n public charPosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x = param - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Pm a Character Position Relative\n * [columns] (default = [row,col+1]) (HPR)\n * reuse CSI Ps C ?\n */\n public HPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.x += param;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps b Repeat the preceding graphic character Ps times (REP).\n */\n public repeatPrecedingCharacter(params: number[]): void {\n let param = params[0] || 1\n , line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + this._terminal.buffer.y)\n , ch = line[this._terminal.buffer.x - 1] || [this._terminal.defAttr, ' ', 1];\n\n while (param--) {\n line[this._terminal.buffer.x++] = ch;\n }\n }\n\n /**\n * CSI Ps c Send Device Attributes (Primary DA).\n * Ps = 0 or omitted -> request attributes from terminal. The\n * response depends on the decTerminalID resource setting.\n * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')\n * -> CSI ? 1 ; 0 c (``VT101 with No Options'')\n * -> CSI ? 6 c (``VT102'')\n * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')\n * The VT100-style response parameters do not mean anything by\n * themselves. VT220 parameters do, telling the host what fea-\n * tures the terminal supports:\n * Ps = 1 -> 132-columns.\n * Ps = 2 -> Printer.\n * Ps = 6 -> Selective erase.\n * Ps = 8 -> User-defined keys.\n * Ps = 9 -> National replacement character sets.\n * Ps = 1 5 -> Technical characters.\n * Ps = 2 2 -> ANSI color, e.g., VT525.\n * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).\n * CSI > Ps c\n * Send Device Attributes (Secondary DA).\n * Ps = 0 or omitted -> request the terminal's identification\n * code. The response depends on the decTerminalID resource set-\n * ting. It should apply only to VT220 and up, but xterm extends\n * this to VT100.\n * -> CSI > Pp ; Pv ; Pc c\n * where Pp denotes the terminal type\n * Pp = 0 -> ``VT100''.\n * Pp = 1 -> ``VT220''.\n * and Pv is the firmware version (for xterm, this was originally\n * the XFree86 patch number, starting with 95). In a DEC termi-\n * nal, Pc indicates the ROM cartridge registration number and is\n * always zero.\n * More information:\n * xterm/charproc.c - line 2012, for more information.\n * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)\n */\n public sendDeviceAttributes(params: number[]): void {\n if (params[0] > 0) {\n return;\n }\n\n if (!this._terminal.prefix) {\n if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[?1;2c');\n } else if (this._terminal.is('linux')) {\n this._terminal.send(C0.ESC + '[?6c');\n }\n } else if (this._terminal.prefix === '>') {\n // xterm and urxvt\n // seem to spit this\n // out around ~370 times (?).\n if (this._terminal.is('xterm')) {\n this._terminal.send(C0.ESC + '[>0;276;0c');\n } else if (this._terminal.is('rxvt-unicode')) {\n this._terminal.send(C0.ESC + '[>85;95;0c');\n } else if (this._terminal.is('linux')) {\n // not supported by linux console.\n // linux console echoes parameters.\n this._terminal.send(params[0] + 'c');\n } else if (this._terminal.is('screen')) {\n this._terminal.send(C0.ESC + '[>83;40003;0c');\n }\n }\n }\n\n /**\n * CSI Pm d Vertical Position Absolute (VPA)\n * [row] (default = [1,column])\n */\n public linePosAbsolute(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y = param - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n }\n\n /**\n * CSI Pm e Vertical Position Relative (VPR)\n * [rows] (default = [row+1,column])\n * reuse CSI Ps B ?\n */\n public VPositionRelative(params: number[]): void {\n let param = params[0];\n if (param < 1) {\n param = 1;\n }\n this._terminal.buffer.y += param;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n // If the end of the line is hit, prevent this action from wrapping around to the next line.\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x--;\n }\n }\n\n /**\n * CSI Ps ; Ps f\n * Horizontal and Vertical Position [row;column] (default =\n * [1,1]) (HVP).\n */\n public HVPosition(params: number[]): void {\n if (params[0] < 1) params[0] = 1;\n if (params[1] < 1) params[1] = 1;\n\n this._terminal.buffer.y = params[0] - 1;\n if (this._terminal.buffer.y >= this._terminal.rows) {\n this._terminal.buffer.y = this._terminal.rows - 1;\n }\n\n this._terminal.buffer.x = params[1] - 1;\n if (this._terminal.buffer.x >= this._terminal.cols) {\n this._terminal.buffer.x = this._terminal.cols - 1;\n }\n }\n\n /**\n * CSI Ps g Tab Clear (TBC).\n * Ps = 0 -> Clear Current Column (default).\n * Ps = 3 -> Clear All.\n * Potentially:\n * Ps = 2 -> Clear Stops on Line.\n * http://vt100.net/annarbor/aaa-ug/section6.html\n */\n public tabClear(params: number[]): void {\n let param = params[0];\n if (param <= 0) {\n delete this._terminal.buffer.tabs[this._terminal.buffer.x];\n } else if (param === 3) {\n this._terminal.buffer.tabs = {};\n }\n }\n\n /**\n * CSI Pm h Set Mode (SM).\n * Ps = 2 -> Keyboard Action Mode (AM).\n * Ps = 4 -> Insert Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Automatic Newline (LNM).\n * CSI ? Pm h\n * DEC Private Mode Set (DECSET).\n * Ps = 1 -> Application Cursor Keys (DECCKM).\n * Ps = 2 -> Designate USASCII for character sets G0-G3\n * (DECANM), and set VT100 mode.\n * Ps = 3 -> 132 Column Mode (DECCOLM).\n * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).\n * Ps = 5 -> Reverse Video (DECSCNM).\n * Ps = 6 -> Origin Mode (DECOM).\n * Ps = 7 -> Wraparound Mode (DECAWM).\n * Ps = 8 -> Auto-repeat Keys (DECARM).\n * Ps = 9 -> Send Mouse X & Y on button press. See the sec-\n * tion Mouse Tracking.\n * Ps = 1 0 -> Show toolbar (rxvt).\n * Ps = 1 2 -> Start Blinking Cursor (att610).\n * Ps = 1 8 -> Print form feed (DECPFF).\n * Ps = 1 9 -> Set print extent to full screen (DECPEX).\n * Ps = 2 5 -> Show Cursor (DECTCEM).\n * Ps = 3 0 -> Show scrollbar (rxvt).\n * Ps = 3 5 -> Enable font-shifting functions (rxvt).\n * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).\n * Ps = 4 0 -> Allow 80 -> 132 Mode.\n * Ps = 4 1 -> more(1) fix (see curses resource).\n * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-\n * RCM).\n * Ps = 4 4 -> Turn On Margin Bell.\n * Ps = 4 5 -> Reverse-wraparound Mode.\n * Ps = 4 6 -> Start Logging. This is normally disabled by a\n * compile-time option.\n * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 6 6 -> Application keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).\n * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).\n * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Interpret \"meta\" key, sets eighth bit.\n * (enables the eightBitInput resource).\n * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-\n * Lock keys. (This enables the numLock resource).\n * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This\n * enables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete\n * key.\n * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This\n * enables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Keep selection even if not highlighted.\n * (This enables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Enable Urgency window manager hint when\n * Control-G is received. (This enables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Enable raising of the window when Control-G\n * is received. (enables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-\n * abled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate\n * Screen Buffer, clearing it first. (This may be disabled by\n * the titeInhibit resource). This combines the effects of the 1\n * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based\n * applications rather than the 4 7 mode.\n * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Set Sun function-key mode.\n * Ps = 1 0 5 2 -> Set HP function-key mode.\n * Ps = 1 0 5 3 -> Set SCO function-key mode.\n * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.\n * Ps = 2 0 0 4 -> Set bracketed paste mode.\n * Modes:\n * http: *vt100.net/docs/vt220-rm/chapter4.html\n */\n public setMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.setMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = true;\n break;\n case 20:\n // this._terminal.convertEol = true;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = true;\n break;\n case 2:\n this._terminal.setgCharset(0, DEFAULT_CHARSET);\n this._terminal.setgCharset(1, DEFAULT_CHARSET);\n this._terminal.setgCharset(2, DEFAULT_CHARSET);\n this._terminal.setgCharset(3, DEFAULT_CHARSET);\n // set VT100 mode here\n break;\n case 3: // 132 col mode\n this._terminal.savedCols = this._terminal.cols;\n this._terminal.resize(132, this._terminal.rows);\n break;\n case 6:\n this._terminal.originMode = true;\n break;\n case 7:\n this._terminal.wraparoundMode = true;\n break;\n case 12:\n // this.cursorBlink = true;\n break;\n case 66:\n this._terminal.log('Serial port requested application keypad.');\n this._terminal.applicationKeypad = true;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n // no release, no motion, no wheel, no modifiers.\n case 1000: // vt200 mouse\n // no motion.\n // no modifiers, except control on the wheel.\n case 1002: // button event mouse\n case 1003: // any event mouse\n // any event - sends motion events,\n // even if there is no button held down.\n\n // TODO: Why are params[0] compares nested within a switch for params[0]?\n\n this._terminal.x10Mouse = params[0] === 9;\n this._terminal.vt200Mouse = params[0] === 1000;\n this._terminal.normalMouse = params[0] > 1000;\n this._terminal.mouseEvents = true;\n this._terminal.element.classList.add('enable-mouse-events');\n this._terminal.selectionManager.disable();\n this._terminal.log('Binding to mouse events.');\n break;\n case 1004: // send focusin/focusout events\n // focusin: ^[[I\n // focusout: ^[[O\n this._terminal.sendFocus = true;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = true;\n // for wide terminals\n // simply encodes large values as utf8 characters\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = true;\n // for wide terminals\n // does not add 32 to fields\n // press: ^[[ Keyboard Action Mode (AM).\n * Ps = 4 -> Replace Mode (IRM).\n * Ps = 1 2 -> Send/receive (SRM).\n * Ps = 2 0 -> Normal Linefeed (LNM).\n * CSI ? Pm l\n * DEC Private Mode Reset (DECRST).\n * Ps = 1 -> Normal Cursor Keys (DECCKM).\n * Ps = 2 -> Designate VT52 mode (DECANM).\n * Ps = 3 -> 80 Column Mode (DECCOLM).\n * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).\n * Ps = 5 -> Normal Video (DECSCNM).\n * Ps = 6 -> Normal Cursor Mode (DECOM).\n * Ps = 7 -> No Wraparound Mode (DECAWM).\n * Ps = 8 -> No Auto-repeat Keys (DECARM).\n * Ps = 9 -> Don't send Mouse X & Y on button press.\n * Ps = 1 0 -> Hide toolbar (rxvt).\n * Ps = 1 2 -> Stop Blinking Cursor (att610).\n * Ps = 1 8 -> Don't print form feed (DECPFF).\n * Ps = 1 9 -> Limit print to scrolling region (DECPEX).\n * Ps = 2 5 -> Hide Cursor (DECTCEM).\n * Ps = 3 0 -> Don't show scrollbar (rxvt).\n * Ps = 3 5 -> Disable font-shifting functions (rxvt).\n * Ps = 4 0 -> Disallow 80 -> 132 Mode.\n * Ps = 4 1 -> No more(1) fix (see curses resource).\n * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-\n * NRCM).\n * Ps = 4 4 -> Turn Off Margin Bell.\n * Ps = 4 5 -> No Reverse-wraparound Mode.\n * Ps = 4 6 -> Stop Logging. (This is normally disabled by a\n * compile-time option).\n * Ps = 4 7 -> Use Normal Screen Buffer.\n * Ps = 6 6 -> Numeric keypad (DECNKM).\n * Ps = 6 7 -> Backarrow key sends delete (DECBKM).\n * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and\n * release. See the section Mouse Tracking.\n * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.\n * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.\n * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.\n * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.\n * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.\n * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output\n * (rxvt).\n * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).\n * Ps = 1 0 3 4 -> Don't interpret \"meta\" key. (This disables\n * the eightBitInput resource).\n * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-\n * Lock keys. (This disables the numLock resource).\n * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.\n * (This disables the metaSendsEscape resource).\n * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad\n * Delete key.\n * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.\n * (This disables the altSendsEscape resource).\n * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.\n * (This disables the keepSelection resource).\n * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables\n * the selectToClipboard resource).\n * Ps = 1 0 4 2 -> Disable Urgency window manager hint when\n * Control-G is received. (This disables the bellIsUrgent\n * resource).\n * Ps = 1 0 4 3 -> Disable raising of the window when Control-\n * G is received. (This disables the popOnBell resource).\n * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen\n * first if in the Alternate Screen. (This may be disabled by\n * the titeInhibit resource).\n * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be\n * disabled by the titeInhibit resource).\n * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor\n * as in DECRC. (This may be disabled by the titeInhibit\n * resource). This combines the effects of the 1 0 4 7 and 1 0\n * 4 8 modes. Use this with terminfo-based applications rather\n * than the 4 7 mode.\n * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.\n * Ps = 1 0 5 1 -> Reset Sun function-key mode.\n * Ps = 1 0 5 2 -> Reset HP function-key mode.\n * Ps = 1 0 5 3 -> Reset SCO function-key mode.\n * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).\n * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.\n * Ps = 2 0 0 4 -> Reset bracketed paste mode.\n */\n public resetMode(params: number[]): void {\n if (params.length > 1) {\n for (let i = 0; i < params.length; i++) {\n this.resetMode([params[i]]);\n }\n\n return;\n }\n\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 4:\n this._terminal.insertMode = false;\n break;\n case 20:\n // this._terminal.convertEol = false;\n break;\n }\n } else if (this._terminal.prefix === '?') {\n switch (params[0]) {\n case 1:\n this._terminal.applicationCursor = false;\n break;\n case 3:\n if (this._terminal.cols === 132 && this._terminal.savedCols) {\n this._terminal.resize(this._terminal.savedCols, this._terminal.rows);\n }\n delete this._terminal.savedCols;\n break;\n case 6:\n this._terminal.originMode = false;\n break;\n case 7:\n this._terminal.wraparoundMode = false;\n break;\n case 12:\n // this.cursorBlink = false;\n break;\n case 66:\n this._terminal.log('Switching back to normal keypad.');\n this._terminal.applicationKeypad = false;\n this._terminal.viewport.syncScrollArea();\n break;\n case 9: // X10 Mouse\n case 1000: // vt200 mouse\n case 1002: // button event mouse\n case 1003: // any event mouse\n this._terminal.x10Mouse = false;\n this._terminal.vt200Mouse = false;\n this._terminal.normalMouse = false;\n this._terminal.mouseEvents = false;\n this._terminal.element.classList.remove('enable-mouse-events');\n this._terminal.selectionManager.enable();\n break;\n case 1004: // send focusin/focusout events\n this._terminal.sendFocus = false;\n break;\n case 1005: // utf8 ext mode mouse\n this._terminal.utfMouse = false;\n break;\n case 1006: // sgr ext mode mouse\n this._terminal.sgrMouse = false;\n break;\n case 1015: // urxvt ext mode mouse\n this._terminal.urxvtMouse = false;\n break;\n case 25: // hide cursor\n this._terminal.cursorHidden = true;\n break;\n case 1049: // alt screen buffer cursor\n // FALL-THROUGH\n case 47: // normal screen buffer\n case 1047: // normal screen buffer - clearing it first\n // Ensure the selection manager has the correct buffer\n this._terminal.buffers.activateNormalBuffer();\n // TODO: Not sure if we need to save/restore after switching the buffer\n // if (params[0] === 1049) {\n // this.restoreCursor(params);\n // }\n this._terminal.selectionManager.setBuffer(this._terminal.buffer.lines);\n this._terminal.refresh(0, this._terminal.rows - 1);\n this._terminal.viewport.syncScrollArea();\n this._terminal.showCursor();\n break;\n }\n }\n }\n\n /**\n * CSI Pm m Character Attributes (SGR).\n * Ps = 0 -> Normal (default).\n * Ps = 1 -> Bold.\n * Ps = 4 -> Underlined.\n * Ps = 5 -> Blink (appears as Bold).\n * Ps = 7 -> Inverse.\n * Ps = 8 -> Invisible, i.e., hidden (VT300).\n * Ps = 2 2 -> Normal (neither bold nor faint).\n * Ps = 2 4 -> Not underlined.\n * Ps = 2 5 -> Steady (not blinking).\n * Ps = 2 7 -> Positive (not inverse).\n * Ps = 2 8 -> Visible, i.e., not hidden (VT300).\n * Ps = 3 0 -> Set foreground color to Black.\n * Ps = 3 1 -> Set foreground color to Red.\n * Ps = 3 2 -> Set foreground color to Green.\n * Ps = 3 3 -> Set foreground color to Yellow.\n * Ps = 3 4 -> Set foreground color to Blue.\n * Ps = 3 5 -> Set foreground color to Magenta.\n * Ps = 3 6 -> Set foreground color to Cyan.\n * Ps = 3 7 -> Set foreground color to White.\n * Ps = 3 9 -> Set foreground color to default (original).\n * Ps = 4 0 -> Set background color to Black.\n * Ps = 4 1 -> Set background color to Red.\n * Ps = 4 2 -> Set background color to Green.\n * Ps = 4 3 -> Set background color to Yellow.\n * Ps = 4 4 -> Set background color to Blue.\n * Ps = 4 5 -> Set background color to Magenta.\n * Ps = 4 6 -> Set background color to Cyan.\n * Ps = 4 7 -> Set background color to White.\n * Ps = 4 9 -> Set background color to default (original).\n *\n * If 16-color support is compiled, the following apply. Assume\n * that xterm's resources are set so that the ISO color codes are\n * the first 8 of a set of 16. Then the aixterm colors are the\n * bright versions of the ISO colors:\n * Ps = 9 0 -> Set foreground color to Black.\n * Ps = 9 1 -> Set foreground color to Red.\n * Ps = 9 2 -> Set foreground color to Green.\n * Ps = 9 3 -> Set foreground color to Yellow.\n * Ps = 9 4 -> Set foreground color to Blue.\n * Ps = 9 5 -> Set foreground color to Magenta.\n * Ps = 9 6 -> Set foreground color to Cyan.\n * Ps = 9 7 -> Set foreground color to White.\n * Ps = 1 0 0 -> Set background color to Black.\n * Ps = 1 0 1 -> Set background color to Red.\n * Ps = 1 0 2 -> Set background color to Green.\n * Ps = 1 0 3 -> Set background color to Yellow.\n * Ps = 1 0 4 -> Set background color to Blue.\n * Ps = 1 0 5 -> Set background color to Magenta.\n * Ps = 1 0 6 -> Set background color to Cyan.\n * Ps = 1 0 7 -> Set background color to White.\n *\n * If xterm is compiled with the 16-color support disabled, it\n * supports the following, from rxvt:\n * Ps = 1 0 0 -> Set foreground and background color to\n * default.\n *\n * If 88- or 256-color support is compiled, the following apply.\n * Ps = 3 8 ; 5 ; Ps -> Set foreground color to the second\n * Ps.\n * Ps = 4 8 ; 5 ; Ps -> Set background color to the second\n * Ps.\n */\n public charAttributes(params: number[]): void {\n // Optimize a single SGR0.\n if (params.length === 1 && params[0] === 0) {\n this._terminal.curAttr = this._terminal.defAttr;\n return;\n }\n\n let l = params.length\n , i = 0\n , flags = this._terminal.curAttr >> 18\n , fg = (this._terminal.curAttr >> 9) & 0x1ff\n , bg = this._terminal.curAttr & 0x1ff\n , p;\n\n for (; i < l; i++) {\n p = params[i];\n if (p >= 30 && p <= 37) {\n // fg color 8\n fg = p - 30;\n } else if (p >= 40 && p <= 47) {\n // bg color 8\n bg = p - 40;\n } else if (p >= 90 && p <= 97) {\n // fg color 16\n p += 8;\n fg = p - 90;\n } else if (p >= 100 && p <= 107) {\n // bg color 16\n p += 8;\n bg = p - 100;\n } else if (p === 0) {\n // default\n flags = this._terminal.defAttr >> 18;\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n // flags = 0;\n // fg = 0x1ff;\n // bg = 0x1ff;\n } else if (p === 1) {\n // bold text\n flags |= 1;\n } else if (p === 4) {\n // underlined text\n flags |= 2;\n } else if (p === 5) {\n // blink\n flags |= 4;\n } else if (p === 7) {\n // inverse and positive\n // test with: echo -e '\\e[31m\\e[42mhello\\e[7mworld\\e[27mhi\\e[m'\n flags |= 8;\n } else if (p === 8) {\n // invisible\n flags |= 16;\n } else if (p === 22) {\n // not bold\n flags &= ~1;\n } else if (p === 24) {\n // not underlined\n flags &= ~2;\n } else if (p === 25) {\n // not blink\n flags &= ~4;\n } else if (p === 27) {\n // not inverse\n flags &= ~8;\n } else if (p === 28) {\n // not invisible\n flags &= ~16;\n } else if (p === 39) {\n // reset fg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n } else if (p === 49) {\n // reset bg\n bg = this._terminal.defAttr & 0x1ff;\n } else if (p === 38) {\n // fg color 256\n if (params[i + 1] === 2) {\n i += 2;\n fg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (fg === -1) fg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n fg = p;\n }\n } else if (p === 48) {\n // bg color 256\n if (params[i + 1] === 2) {\n i += 2;\n bg = this._terminal.matchColor(\n params[i] & 0xff,\n params[i + 1] & 0xff,\n params[i + 2] & 0xff);\n if (bg === -1) bg = 0x1ff;\n i += 2;\n } else if (params[i + 1] === 5) {\n i += 2;\n p = params[i] & 0xff;\n bg = p;\n }\n } else if (p === 100) {\n // reset fg/bg\n fg = (this._terminal.defAttr >> 9) & 0x1ff;\n bg = this._terminal.defAttr & 0x1ff;\n } else {\n this._terminal.error('Unknown SGR attribute: %d.', p);\n }\n }\n\n this._terminal.curAttr = (flags << 18) | (fg << 9) | bg;\n }\n\n /**\n * CSI Ps n Device Status Report (DSR).\n * Ps = 5 -> Status Report. Result (``OK'') is\n * CSI 0 n\n * Ps = 6 -> Report Cursor Position (CPR) [row;column].\n * Result is\n * CSI r ; c R\n * CSI ? Ps n\n * Device Status Report (DSR, DEC-specific).\n * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI\n * ? r ; c R (assumes page is zero).\n * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).\n * or CSI ? 1 1 n (not ready).\n * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)\n * or CSI ? 2 1 n (locked).\n * Ps = 2 6 -> Report Keyboard status as\n * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).\n * The last two parameters apply to VT400 & up, and denote key-\n * board ready and LK01 respectively.\n * Ps = 5 3 -> Report Locator status as\n * CSI ? 5 3 n Locator available, if compiled-in, or\n * CSI ? 5 0 n No Locator, if not.\n */\n public deviceStatus(params: number[]): void {\n if (!this._terminal.prefix) {\n switch (params[0]) {\n case 5:\n // status report\n this._terminal.send(C0.ESC + '[0n');\n break;\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '['\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n }\n } else if (this._terminal.prefix === '?') {\n // modern xterm doesnt seem to\n // respond to any of these except ?6, 6, and 5\n switch (params[0]) {\n case 6:\n // cursor position\n this._terminal.send(C0.ESC + '[?'\n + (this._terminal.buffer.y + 1)\n + ';'\n + (this._terminal.buffer.x + 1)\n + 'R');\n break;\n case 15:\n // no printer\n // this.send(C0.ESC + '[?11n');\n break;\n case 25:\n // dont support user defined keys\n // this.send(C0.ESC + '[?21n');\n break;\n case 26:\n // north american keyboard\n // this.send(C0.ESC + '[?27;1;0;0n');\n break;\n case 53:\n // no dec locator/mouse\n // this.send(C0.ESC + '[?50n');\n break;\n }\n }\n }\n\n /**\n * CSI ! p Soft terminal reset (DECSTR).\n * http://vt100.net/docs/vt220-rm/table4-10.html\n */\n public softReset(params: number[]): void {\n this._terminal.cursorHidden = false;\n this._terminal.insertMode = false;\n this._terminal.originMode = false;\n this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false\n this._terminal.applicationKeypad = false; // ?\n this._terminal.viewport.syncScrollArea();\n this._terminal.applicationCursor = false;\n this._terminal.buffer.scrollTop = 0;\n this._terminal.buffer.scrollBottom = this._terminal.rows - 1;\n this._terminal.curAttr = this._terminal.defAttr;\n this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?\n this._terminal.charset = null;\n this._terminal.glevel = 0; // ??\n this._terminal.charsets = [null]; // ??\n }\n\n /**\n * CSI Ps SP q Set cursor style (DECSCUSR, VT520).\n * Ps = 0 -> blinking block.\n * Ps = 1 -> blinking block (default).\n * Ps = 2 -> steady block.\n * Ps = 3 -> blinking underline.\n * Ps = 4 -> steady underline.\n * Ps = 5 -> blinking bar (xterm).\n * Ps = 6 -> steady bar (xterm).\n */\n public setCursorStyle(params?: number[]): void {\n const param = params[0] < 1 ? 1 : params[0];\n switch (param) {\n case 1:\n case 2:\n this._terminal.setOption('cursorStyle', 'block');\n break;\n case 3:\n case 4:\n this._terminal.setOption('cursorStyle', 'underline');\n break;\n case 5:\n case 6:\n this._terminal.setOption('cursorStyle', 'bar');\n break;\n }\n const isBlinking = param % 2 === 1;\n this._terminal.setOption('cursorBlink', isBlinking);\n }\n\n /**\n * CSI Ps ; Ps r\n * Set Scrolling Region [top;bottom] (default = full size of win-\n * dow) (DECSTBM).\n * CSI ? Pm r\n */\n public setScrollRegion(params: number[]): void {\n if (this._terminal.prefix) return;\n this._terminal.buffer.scrollTop = (params[0] || 1) - 1;\n this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;\n this._terminal.buffer.x = 0;\n this._terminal.buffer.y = 0;\n }\n\n\n /**\n * CSI s\n * Save cursor (ANSI.SYS).\n */\n public saveCursor(params: number[]): void {\n this._terminal.buffer.savedX = this._terminal.buffer.x;\n this._terminal.buffer.savedY = this._terminal.buffer.y;\n }\n\n\n /**\n * CSI u\n * Restore cursor (ANSI.SYS).\n */\n public restoreCursor(params: number[]): void {\n this._terminal.buffer.x = this._terminal.buffer.savedX || 0;\n this._terminal.buffer.y = this._terminal.buffer.savedY || 0;\n }\n}\n\nexport const wcwidth = (function(opts) {\n // extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c\n // combining characters\n const COMBINING_BMP = [\n [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],\n [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],\n [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],\n [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],\n [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],\n [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],\n [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],\n [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],\n [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],\n [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],\n [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],\n [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],\n [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],\n [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],\n [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],\n [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],\n [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],\n [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],\n [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],\n [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],\n [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],\n [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],\n [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],\n [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],\n [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],\n [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],\n [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],\n [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],\n [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],\n [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],\n [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],\n [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],\n [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],\n [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],\n [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],\n [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],\n [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],\n [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],\n [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],\n [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],\n [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],\n [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],\n [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],\n ];\n const COMBINING_HIGH = [\n [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],\n [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],\n [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],\n [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],\n [0xE0100, 0xE01EF]\n ];\n // binary search\n function bisearch(ucs, data) {\n let min = 0;\n let max = data.length - 1;\n let mid;\n if (ucs < data[0][0] || ucs > data[max][1])\n return false;\n while (max >= min) {\n mid = (min + max) >> 1;\n if (ucs > data[mid][1])\n min = mid + 1;\n else if (ucs < data[mid][0])\n max = mid - 1;\n else\n return true;\n }\n return false;\n }\n function wcwidthBMP(ucs) {\n // test for 8-bit control characters\n if (ucs === 0)\n return opts.nul;\n if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))\n return opts.control;\n // binary search in table of non-spacing characters\n if (bisearch(ucs, COMBINING_BMP))\n return 0;\n // if we arrive here, ucs is not a combining or C0/C1 control character\n if (isWideBMP(ucs)) {\n return 2;\n }\n return 1;\n }\n function isWideBMP(ucs) {\n return (\n ucs >= 0x1100 && (\n ucs <= 0x115f || // Hangul Jamo init. consonants\n ucs === 0x2329 ||\n ucs === 0x232a ||\n (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi\n (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables\n (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs\n (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms\n (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms\n (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms\n (ucs >= 0xffe0 && ucs <= 0xffe6)));\n }\n function wcwidthHigh(ucs) {\n if (bisearch(ucs, COMBINING_HIGH))\n return 0;\n if ((ucs >= 0x20000 && ucs <= 0x2fffd) || (ucs >= 0x30000 && ucs <= 0x3fffd)) {\n return 2;\n }\n return 1;\n }\n const control = opts.control | 0;\n let table = null;\n function init_table() {\n // lookup table for BMP\n const CODEPOINTS = 65536; // BMP holds 65536 codepoints\n const BITWIDTH = 2; // a codepoint can have a width of 0, 1 or 2\n const ITEMSIZE = 32; // using uint32_t\n const CONTAINERSIZE = CODEPOINTS * BITWIDTH / ITEMSIZE;\n const CODEPOINTS_PER_ITEM = ITEMSIZE / BITWIDTH;\n table = (typeof Uint32Array === 'undefined')\n ? new Array(CONTAINERSIZE)\n : new Uint32Array(CONTAINERSIZE);\n for (let i = 0; i < CONTAINERSIZE; ++i) {\n let num = 0;\n let pos = CODEPOINTS_PER_ITEM;\n while (pos--)\n num = (num << 2) | wcwidthBMP(CODEPOINTS_PER_ITEM * i + pos);\n table[i] = num;\n }\n return table;\n }\n // get width from lookup table\n // position in container : num / CODEPOINTS_PER_ITEM\n // ==> n = table[Math.floor(num / 16)]\n // ==> n = table[num >> 4]\n // 16 codepoints per number: FFEEDDCCBBAA99887766554433221100\n // position in number : (num % CODEPOINTS_PER_ITEM) * BITWIDTH\n // ==> m = (n % 16) * 2\n // ==> m = (num & 15) << 1\n // right shift to position m\n // ==> n = n >> m e.g. m=12 000000000000FFEEDDCCBBAA99887766\n // we are only interested in 2 LSBs, cut off higher bits\n // ==> n = n & 3 e.g. 000000000000000000000000000000XX\n return function (num) {\n num = num | 0; // get asm.js like optimization under V8\n if (num < 32)\n return control | 0;\n if (num < 127)\n return 1;\n let t = table || init_table();\n if (num < 65536)\n return t[num >> 4] >> ((num & 15) << 1) & 3;\n // do a full search for high codepoints\n return wcwidthHigh(num);\n };\n})({nul: 0, control: 0}); // configurable options\n","/**\n * @license MIT\n */\n\nimport { IEventEmitter } from './Interfaces';\n\ninterface ListenerType {\n (): void;\n listener?: () => void;\n};\n\nexport class EventEmitter implements IEventEmitter {\n private _events: {[type: string]: ListenerType[]};\n\n constructor() {\n // Restore the previous events if available, this will happen if the\n // constructor is called multiple times on the same object (terminal reset).\n this._events = this._events || {};\n }\n\n public on(type, listener): void {\n this._events[type] = this._events[type] || [];\n this._events[type].push(listener);\n }\n\n public off(type, listener): void {\n if (!this._events[type]) {\n return;\n }\n\n let obj = this._events[type];\n let i = obj.length;\n\n while (i--) {\n if (obj[i] === listener || obj[i].listener === listener) {\n obj.splice(i, 1);\n return;\n }\n }\n }\n\n public removeAllListeners(type): void {\n if (this._events[type]) {\n delete this._events[type];\n }\n }\n\n public once(type, listener): any {\n function on() {\n let args = Array.prototype.slice.call(arguments);\n this.off(type, on);\n return listener.apply(this, args);\n }\n (on).listener = listener;\n return this.on(type, on);\n }\n\n public emit(type: string, ...args: any[]): void {\n if (!this._events[type]) {\n return;\n }\n let obj = this._events[type];\n for (let i = 0; i < obj.length; i++) {\n obj[i].apply(this, args);\n }\n }\n\n public listeners(type): ListenerType[] {\n return this._events[type] || [];\n }\n}\n","/**\n * @license MIT\n */\n\n/**\n * C0 control codes\n * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes\n */\nexport namespace C0 {\n /** Null (Caret = ^@, C = \\0) */\n export const NUL = '\\x00';\n /** Start of Heading (Caret = ^A) */\n export const SOH = '\\x01';\n /** Start of Text (Caret = ^B) */\n export const STX = '\\x02';\n /** End of Text (Caret = ^C) */\n export const ETX = '\\x03';\n /** End of Transmission (Caret = ^D) */\n export const EOT = '\\x04';\n /** Enquiry (Caret = ^E) */\n export const ENQ = '\\x05';\n /** Acknowledge (Caret = ^F) */\n export const ACK = '\\x06';\n /** Bell (Caret = ^G, C = \\a) */\n export const BEL = '\\x07';\n /** Backspace (Caret = ^H, C = \\b) */\n export const BS = '\\x08';\n /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \\t) */\n export const HT = '\\x09';\n /** Line Feed (Caret = ^J, C = \\n) */\n export const LF = '\\x0a';\n /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \\v) */\n export const VT = '\\x0b';\n /** Form Feed (Caret = ^L, C = \\f) */\n export const FF = '\\x0c';\n /** Carriage Return (Caret = ^M, C = \\r) */\n export const CR = '\\x0d';\n /** Shift Out (Caret = ^N) */\n export const SO = '\\x0e';\n /** Shift In (Caret = ^O) */\n export const SI = '\\x0f';\n /** Data Link Escape (Caret = ^P) */\n export const DLE = '\\x10';\n /** Device Control One (XON) (Caret = ^Q) */\n export const DC1 = '\\x11';\n /** Device Control Two (Caret = ^R) */\n export const DC2 = '\\x12';\n /** Device Control Three (XOFF) (Caret = ^S) */\n export const DC3 = '\\x13';\n /** Device Control Four (Caret = ^T) */\n export const DC4 = '\\x14';\n /** Negative Acknowledge (Caret = ^U) */\n export const NAK = '\\x15';\n /** Synchronous Idle (Caret = ^V) */\n export const SYN = '\\x16';\n /** End of Transmission Block (Caret = ^W) */\n export const ETB = '\\x17';\n /** Cancel (Caret = ^X) */\n export const CAN = '\\x18';\n /** End of Medium (Caret = ^Y) */\n export const EM = '\\x19';\n /** Substitute (Caret = ^Z) */\n export const SUB = '\\x1a';\n /** Escape (Caret = ^[, C = \\e) */\n export const ESC = '\\x1b';\n /** File Separator (Caret = ^\\) */\n export const FS = '\\x1c';\n /** Group Separator (Caret = ^]) */\n export const GS = '\\x1d';\n /** Record Separator (Caret = ^^) */\n export const RS = '\\x1e';\n /** Unit Separator (Caret = ^_) */\n export const US = '\\x1f';\n /** Space */\n export const SP = '\\x20';\n /** Delete (Caret = ^?) */\n export const DEL = '\\x7f';\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal } from './Interfaces';\n\ninterface IPosition {\n start: number;\n end: number;\n}\n\n/**\n * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend\n * events, displaying the in-progress composition to the UI and forwarding the final composition\n * to the handler.\n */\nexport class CompositionHelper {\n /**\n * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or\n * IME. This variable determines whether the compositionText should be displayed on the UI.\n */\n private isComposing: boolean;\n\n /**\n * The position within the input textarea's value of the current composition.\n */\n private compositionPosition: IPosition;\n\n /**\n * Whether a composition is in the process of being sent, setting this to false will cancel any\n * in-progress composition.\n */\n private isSendingComposition: boolean;\n\n /**\n * Creates a new CompositionHelper.\n * @param textarea The textarea that xterm uses for input.\n * @param compositionView The element to display the in-progress composition in.\n * @param terminal The Terminal to forward the finished composition to.\n */\n constructor(\n private textarea: HTMLTextAreaElement,\n private compositionView: HTMLElement,\n private terminal: ITerminal\n ) {\n this.isComposing = false;\n this.isSendingComposition = false;\n this.compositionPosition = { start: null, end: null };\n }\n\n /**\n * Handles the compositionstart event, activating the composition view.\n */\n public compositionstart() {\n this.isComposing = true;\n this.compositionPosition.start = this.textarea.value.length;\n this.compositionView.textContent = '';\n this.compositionView.classList.add('active');\n }\n\n /**\n * Handles the compositionupdate event, updating the composition view.\n * @param {CompositionEvent} ev The event.\n */\n public compositionupdate(ev: CompositionEvent) {\n this.compositionView.textContent = ev.data;\n this.updateCompositionElements();\n setTimeout(() => {\n this.compositionPosition.end = this.textarea.value.length;\n }, 0);\n }\n\n /**\n * Handles the compositionend event, hiding the composition view and sending the composition to\n * the handler.\n */\n public compositionend() {\n this.finalizeComposition(true);\n }\n\n /**\n * Handles the keydown event, routing any necessary events to the CompositionHelper functions.\n * @param ev The keydown event.\n * @return Whether the Terminal should continue processing the keydown event.\n */\n public keydown(ev: KeyboardEvent) {\n if (this.isComposing || this.isSendingComposition) {\n if (ev.keyCode === 229) {\n // Continue composing if the keyCode is the \"composition character\"\n return false;\n } else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {\n // Continue composing if the keyCode is a modifier key\n return false;\n } else {\n // Finish composition immediately. This is mainly here for the case where enter is\n // pressed and the handler needs to be triggered before the command is executed.\n this.finalizeComposition(false);\n }\n }\n\n if (ev.keyCode === 229) {\n // If the \"composition character\" is used but gets to this point it means a non-composition\n // character (eg. numbers and punctuation) was pressed when the IME was active.\n this.handleAnyTextareaChanges();\n return false;\n }\n\n return true;\n }\n\n /**\n * Finalizes the composition, resuming regular input actions. This is called when a composition\n * is ending.\n * @param waitForPropogation Whether to wait for events to propogate before sending\n * the input. This should be false if a non-composition keystroke is entered before the\n * compositionend event is triggered, such as enter, so that the composition is send before\n * the command is executed.\n */\n private finalizeComposition(waitForPropogation: boolean) {\n this.compositionView.classList.remove('active');\n this.isComposing = false;\n this.clearTextareaPosition();\n\n if (!waitForPropogation) {\n // Cancel any delayed composition send requests and send the input immediately.\n this.isSendingComposition = false;\n const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);\n this.terminal.handler(input);\n } else {\n // Make a deep copy of the composition position here as a new compositionstart event may\n // fire before the setTimeout executes.\n const currentCompositionPosition = {\n start: this.compositionPosition.start,\n end: this.compositionPosition.end,\n };\n\n // Since composition* events happen before the changes take place in the textarea on most\n // browsers, use a setTimeout with 0ms time to allow the native compositionend event to\n // complete. This ensures the correct character is retrieved, this solution was used\n // because:\n // - The compositionend event's data property is unreliable, at least on Chromium\n // - The last compositionupdate event's data property does not always accurately describe\n // the character, a counter example being Korean where an ending consonsant can move to\n // the following character if the following input is a vowel.\n this.isSendingComposition = true;\n setTimeout(() => {\n // Ensure that the input has not already been sent\n if (this.isSendingComposition) {\n this.isSendingComposition = false;\n let input;\n if (this.isComposing) {\n // Use the end position to get the string if a new composition has started.\n input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);\n } else {\n // Don't use the end position here in order to pick up any characters after the\n // composition has finished, for example when typing a non-composition character\n // (eg. 2) after a composition character.\n input = this.textarea.value.substring(currentCompositionPosition.start);\n }\n this.terminal.handler(input);\n }\n }, 0);\n }\n }\n\n /**\n * Apply any changes made to the textarea after the current event chain is allowed to complete.\n * This should be called when not currently composing but a keydown event with the \"composition\n * character\" (229) is triggered, in order to allow non-composition text to be entered when an\n * IME is active.\n */\n private handleAnyTextareaChanges() {\n const oldValue = this.textarea.value;\n setTimeout(() => {\n // Ignore if a composition has started since the timeout\n if (!this.isComposing) {\n const newValue = this.textarea.value;\n const diff = newValue.replace(oldValue, '');\n if (diff.length > 0) {\n this.terminal.handler(diff);\n }\n }\n }, 0);\n }\n\n /**\n * Positions the composition view on top of the cursor and the textarea just below it (so the\n * IME helper dialog is positioned correctly).\n * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is\n * necessary as the IME events across browsers are not consistently triggered.\n */\n public updateCompositionElements(dontRecurse?: boolean) {\n if (!this.isComposing) {\n return;\n }\n const cursor = this.terminal.element.querySelector('.terminal-cursor');\n if (cursor) {\n // Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within\n // the .xterm element.\n const xtermRows = this.terminal.element.querySelector('.xterm-rows');\n const cursorTop = xtermRows.offsetTop + cursor.offsetTop;\n\n this.compositionView.style.left = cursor.offsetLeft + 'px';\n this.compositionView.style.top = cursorTop + 'px';\n this.compositionView.style.height = cursor.offsetHeight + 'px';\n this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';\n // Sync the textarea to the exact position of the composition view so the IME knows where the\n // text is.\n const compositionViewBounds = this.compositionView.getBoundingClientRect();\n this.textarea.style.left = cursor.offsetLeft + 'px';\n this.textarea.style.top = cursorTop + 'px';\n this.textarea.style.width = compositionViewBounds.width + 'px';\n this.textarea.style.height = compositionViewBounds.height + 'px';\n this.textarea.style.lineHeight = compositionViewBounds.height + 'px';\n }\n if (!dontRecurse) {\n setTimeout(() => this.updateCompositionElements(true), 0);\n }\n };\n\n /**\n * Clears the textarea's position so that the cursor does not blink on IE.\n * @private\n */\n private clearTextareaPosition() {\n this.textarea.style.left = '';\n this.textarea.style.top = '';\n };\n}\n","/**\n * @license MIT\n */\n\n/**\n * The character sets supported by the terminal. These enable several languages\n * to be represented within the terminal with only 8-bit encoding. See ISO 2022\n * for a discussion on character sets. Only VT100 character sets are supported.\n */\nexport const CHARSETS: {[key: string]: {[key: string]: string}} = {};\n\n/**\n * The default character set, US.\n */\nexport const DEFAULT_CHARSET = CHARSETS['B'];\n\n/**\n * DEC Special Character and Line Drawing Set.\n * Reference: http://vt100.net/docs/vt102-ug/table5-13.html\n * A lot of curses apps use this if they see TERM=xterm.\n * testing: echo -e '\\e(0a\\e(B'\n * The xterm output sometimes seems to conflict with the\n * reference above. xterm seems in line with the reference\n * when running vttest however.\n * The table below now uses xterm's output from vttest.\n */\nCHARSETS['0'] = {\n '`': '\\u25c6', // '◆'\n 'a': '\\u2592', // '▒'\n 'b': '\\u0009', // '\\t'\n 'c': '\\u000c', // '\\f'\n 'd': '\\u000d', // '\\r'\n 'e': '\\u000a', // '\\n'\n 'f': '\\u00b0', // '°'\n 'g': '\\u00b1', // '±'\n 'h': '\\u2424', // '\\u2424' (NL)\n 'i': '\\u000b', // '\\v'\n 'j': '\\u2518', // '┘'\n 'k': '\\u2510', // '┐'\n 'l': '\\u250c', // '┌'\n 'm': '\\u2514', // '└'\n 'n': '\\u253c', // '┼'\n 'o': '\\u23ba', // '⎺'\n 'p': '\\u23bb', // '⎻'\n 'q': '\\u2500', // '─'\n 'r': '\\u23bc', // '⎼'\n 's': '\\u23bd', // '⎽'\n 't': '\\u251c', // '├'\n 'u': '\\u2524', // '┤'\n 'v': '\\u2534', // '┴'\n 'w': '\\u252c', // '┬'\n 'x': '\\u2502', // '│'\n 'y': '\\u2264', // '≤'\n 'z': '\\u2265', // '≥'\n '{': '\\u03c0', // 'π'\n '|': '\\u2260', // '≠'\n '}': '\\u00a3', // '£'\n '~': '\\u00b7' // '·'\n};\n\n/**\n * British character set\n * ESC (A\n * Reference: http://vt100.net/docs/vt220-rm/table2-5.html\n */\nCHARSETS['A'] = {\n '#': '£'\n};\n\n/**\n * United States character set\n * ESC (B\n */\nCHARSETS['B'] = null;\n\n/**\n * Dutch character set\n * ESC (4\n * Reference: http://vt100.net/docs/vt220-rm/table2-6.html\n */\nCHARSETS['4'] = {\n '#': '£',\n '@': '¾',\n '[': 'ij',\n '\\\\': '½',\n ']': '|',\n '{': '¨',\n '|': 'f',\n '}': '¼',\n '~': '´'\n};\n\n/**\n * Finnish character set\n * ESC (C or ESC (5\n * Reference: http://vt100.net/docs/vt220-rm/table2-7.html\n */\nCHARSETS['C'] =\nCHARSETS['5'] = {\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * French character set\n * ESC (R\n * Reference: http://vt100.net/docs/vt220-rm/table2-8.html\n */\nCHARSETS['R'] = {\n '#': '£',\n '@': 'à',\n '[': '°',\n '\\\\': 'ç',\n ']': '§',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': '¨'\n};\n\n/**\n * French Canadian character set\n * ESC (Q\n * Reference: http://vt100.net/docs/vt220-rm/table2-9.html\n */\nCHARSETS['Q'] = {\n '@': 'à',\n '[': 'â',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '`': 'ô',\n '{': 'é',\n '|': 'ù',\n '}': 'è',\n '~': 'û'\n};\n\n/**\n * German character set\n * ESC (K\n * Reference: http://vt100.net/docs/vt220-rm/table2-10.html\n */\nCHARSETS['K'] = {\n '@': '§',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Ü',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'ß'\n};\n\n/**\n * Italian character set\n * ESC (Y\n * Reference: http://vt100.net/docs/vt220-rm/table2-11.html\n */\nCHARSETS['Y'] = {\n '#': '£',\n '@': '§',\n '[': '°',\n '\\\\': 'ç',\n ']': 'é',\n '`': 'ù',\n '{': 'à',\n '|': 'ò',\n '}': 'è',\n '~': 'ì'\n};\n\n/**\n * Norwegian/Danish character set\n * ESC (E or ESC (6\n * Reference: http://vt100.net/docs/vt220-rm/table2-12.html\n */\nCHARSETS['E'] =\nCHARSETS['6'] = {\n '@': 'Ä',\n '[': 'Æ',\n '\\\\': 'Ø',\n ']': 'Å',\n '^': 'Ü',\n '`': 'ä',\n '{': 'æ',\n '|': 'ø',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Spanish character set\n * ESC (Z\n * Reference: http://vt100.net/docs/vt220-rm/table2-13.html\n */\nCHARSETS['Z'] = {\n '#': '£',\n '@': '§',\n '[': '¡',\n '\\\\': 'Ñ',\n ']': '¿',\n '{': '°',\n '|': 'ñ',\n '}': 'ç'\n};\n\n/**\n * Swedish character set\n * ESC (H or ESC (7\n * Reference: http://vt100.net/docs/vt220-rm/table2-14.html\n */\nCHARSETS['H'] =\nCHARSETS['7'] = {\n '@': 'É',\n '[': 'Ä',\n '\\\\': 'Ö',\n ']': 'Å',\n '^': 'Ü',\n '`': 'é',\n '{': 'ä',\n '|': 'ö',\n '}': 'å',\n '~': 'ü'\n};\n\n/**\n * Swiss character set\n * ESC (=\n * Reference: http://vt100.net/docs/vt220-rm/table2-15.html\n */\nCHARSETS['='] = {\n '#': 'ù',\n '@': 'à',\n '[': 'é',\n '\\\\': 'ç',\n ']': 'ê',\n '^': 'î',\n '_': 'è',\n '`': 'ô',\n '{': 'ä',\n '|': 'ö',\n '}': 'ü',\n '~': 'û'\n};\n","/**\n * @license MIT\n */\n\nimport { ITerminal, IBufferSet } from './Interfaces';\nimport { Buffer } from './Buffer';\nimport { EventEmitter } from './EventEmitter';\n\n/**\n * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and\n * provides also utilities for working with them.\n */\nexport class BufferSet extends EventEmitter implements IBufferSet {\n private _normal: Buffer;\n private _alt: Buffer;\n private _activeBuffer: Buffer;\n\n /**\n * Create a new BufferSet for the given terminal.\n * @param {Terminal} terminal - The terminal the BufferSet will belong to\n */\n constructor(private _terminal: ITerminal) {\n super();\n this._normal = new Buffer(this._terminal);\n this._normal.fillViewportRows();\n this._alt = new Buffer(this._terminal);\n this._activeBuffer = this._normal;\n }\n\n /**\n * Returns the alt Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get alt(): Buffer {\n return this._alt;\n }\n\n /**\n * Returns the normal Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get active(): Buffer {\n return this._activeBuffer;\n }\n\n /**\n * Returns the currently active Buffer of the BufferSet\n * @returns {Buffer}\n */\n public get normal(): Buffer {\n return this._normal;\n }\n\n /**\n * Sets the normal Buffer of the BufferSet as its currently active Buffer\n */\n public activateNormalBuffer(): void {\n // The alt buffer should always be cleared when we switch to the normal\n // buffer. This frees up memory since the alt buffer should always be new\n // when activated.\n this._alt.clear();\n\n this._activeBuffer = this._normal;\n this.emit('activate', this._normal);\n }\n\n /**\n * Sets the alt Buffer of the BufferSet as its currently active Buffer\n */\n public activateAltBuffer(): void {\n // Since the alt buffer is always cleared when the normal buffer is\n // activated, we want to fill it when switching to it.\n this._alt.fillViewportRows();\n\n this._activeBuffer = this._alt;\n this.emit('activate', this._alt);\n }\n\n /**\n * Resizes both normal and alt buffers, adjusting their data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n this._normal.resize(newCols, newRows);\n this._alt.resize(newCols, newRows);\n }\n}\n","/**\n * @license MIT\n */\n\nimport { ITerminal, IBuffer } from './Interfaces';\nimport { CircularList } from './utils/CircularList';\n\n/**\n * This class represents a terminal buffer (an internal state of the terminal), where the\n * following information is stored (in high-level):\n * - text content of this particular buffer\n * - cursor position\n * - scroll position\n */\nexport class Buffer implements IBuffer {\n private _lines: CircularList<[number, string, number][]>;\n\n public ydisp: number;\n public ybase: number;\n public y: number;\n public x: number;\n public scrollBottom: number;\n public scrollTop: number;\n public tabs: any;\n public savedY: number;\n public savedX: number;\n\n /**\n * Create a new Buffer.\n * @param {Terminal} _terminal - The terminal the Buffer will belong to\n * @param {number} ydisp - The scroll position of the Buffer in the viewport\n * @param {number} ybase - The scroll position of the y cursor (ybase + y = the y position within the Buffer)\n * @param {number} y - The cursor's y position after ybase\n * @param {number} x - The cursor's x position after ybase\n */\n constructor(\n private _terminal: ITerminal\n ) {\n this.clear();\n }\n\n public get lines(): CircularList<[number, string, number][]> {\n return this._lines;\n }\n\n /**\n * Fills the buffer's viewport with blank lines.\n */\n public fillViewportRows(): void {\n if (this._lines.length === 0) {\n let i = this._terminal.rows;\n while (i--) {\n this.lines.push(this._terminal.blankLine());\n }\n }\n }\n\n /**\n * Clears the buffer to it's initial state, discarding all previous data.\n */\n public clear(): void {\n this.ydisp = 0;\n this.ybase = 0;\n this.y = 0;\n this.x = 0;\n this.scrollBottom = 0;\n this.scrollTop = 0;\n this.tabs = {};\n this._lines = new CircularList<[number, string, number][]>(this._terminal.scrollback);\n this.scrollBottom = this._terminal.rows - 1;\n }\n\n /**\n * Resizes the buffer, adjusting its data accordingly.\n * @param newCols The new number of columns.\n * @param newRows The new number of rows.\n */\n public resize(newCols: number, newRows: number): void {\n // Don't resize the buffer if it's empty and hasn't been used yet.\n if (this._lines.length === 0) {\n return;\n }\n\n // Deal with columns increasing (we don't do anything when columns reduce)\n if (this._terminal.cols < newCols) {\n const ch: [number, string, number] = [this._terminal.defAttr, ' ', 1]; // does xterm use the default attr?\n for (let i = 0; i < this._lines.length; i++) {\n // TODO: This should be removed, with tests setup for the case that was\n // causing the underlying bug, see https://github.com/sourcelair/xterm.js/issues/824\n if (this._lines.get(i) === undefined) {\n this._lines.set(i, this._terminal.blankLine(undefined, undefined, newCols));\n }\n while (this._lines.get(i).length < newCols) {\n this._lines.get(i).push(ch);\n }\n }\n }\n\n // Resize rows in both directions as needed\n let addToY = 0;\n if (this._terminal.rows < newRows) {\n for (let y = this._terminal.rows; y < newRows; y++) {\n if (this._lines.length < newRows + this.ybase) {\n if (this.ybase > 0 && this._lines.length <= this.ybase + this.y + addToY + 1) {\n // There is room above the buffer and there are no empty elements below the line,\n // scroll up\n this.ybase--;\n addToY++;\n if (this.ydisp > 0) {\n // Viewport is at the top of the buffer, must increase downwards\n this.ydisp--;\n }\n } else {\n // Add a blank line if there is no buffer left at the top to scroll to, or if there\n // are blank lines after the cursor\n this._lines.push(this._terminal.blankLine(undefined, undefined, newCols));\n }\n }\n }\n } else { // (this._terminal.rows >= newRows)\n for (let y = this._terminal.rows; y > newRows; y--) {\n if (this._lines.length > newRows + this.ybase) {\n if (this._lines.length > this.ybase + this.y + 1) {\n // The line is a blank line below the cursor, remove it\n this._lines.pop();\n } else {\n // The line is the cursor, scroll down\n this.ybase++;\n this.ydisp++;\n }\n }\n }\n }\n\n // Make sure that the cursor stays on screen\n if (this.y >= newRows) {\n this.y = newRows - 1;\n }\n if (addToY) {\n this.y += addToY;\n }\n\n if (this.x >= newCols) {\n this.x = newCols - 1;\n }\n\n this.scrollTop = 0;\n this.scrollBottom = newRows - 1;\n }\n}\n",null],"names":[],"mappings":"AsBAA;;;ADKA;AASA;AAqBA;AACA;AAEA;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAvIa;;;;;;;;;;;;;;;;;ADTb;AACA;AAMA;AAAA;AASA;AAAA;AAAA;AAEA;AACA;AACA;AACA;;AACA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;;;AAAA;AAKA;AAIA;AAEA;AACA;AACA;AAKA;AAGA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AA3Ea;;;;;;;ADHA;AAKA;AAYb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;AD3OA;AAwBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AAUA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AACA;AAUA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AAAA;AACA;AAAA;AApNa;;;;;;;ADRb;AAAA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AAEA;AACA;AAAC;;;;;;;ADpEA;AAED;AAGA;AAGA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AA3Da;;;;;;;ADNb;AACA;AASA;AAEA;AAAA;AAAA;AAEA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AAGA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AACA;AACA;AAuCA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAAA;AAGA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAwFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAoFA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAkEA;AAEA;AACA;AACA;AACA;AAEA;AAOA;AACA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAIA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAyBA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AAGA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AA96Ca;AAg7CA;AAGb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;;;;;;ADnlDA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAKA;AAeA;AAFA;AAGA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAMA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAYA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAPA;AAAA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAQA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAAA;AAAA;;AACA;AACA;AACA;AACA;AACA;AACA;AAWA;AAGA;AACA;AACA;AAKA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAhUmB;AANN;;;;;;;ADhCb;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAKA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AAIA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAIA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AAIA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AAMA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAQA;AACA;AACA;AAQA;AACA;AACA;AAOA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AASA;AAAA;AA9da;;;;;;;ADjKb;AAOA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAC;AAED;AAEA;AAQA;AAAA;AANA;AACA;AACA;AAEA;AAKA;AACA;AACA;AACA;AAKA;AAQA;AACA;AACA;AACA;AACA;AACA;AAMA;AAKA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAuBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAIA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAAA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAQA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AApWa;AAwWb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;AD3YA;AACA;AAGA;AAEA;AACA;AAMA;AAKA;AAKA;AAMA;AAIA;AACA;AAEA;AACA;AAaA;AAAA;AACA;AACA;AACA;AACA;AAUA;AAAA;AAiCA;AAAA;AACA;AACA;AACA;AACA;AATA;AAYA;AACA;AAEA;AACA;;AACA;AAKA;AAAA;AACA;AACA;AAEA;AAMA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AAEA;AAAA;;;AAAA;AACA;AAAA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AACA;AAEA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAAA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAGA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAKA;AAAA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AAIA;AACA;AACA;AAAA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AAGA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAKA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AA1lBa;;;;;;;AD1Db;AAuBA;AACA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;;;AAAA;AAMA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAKA;AACA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AArHa;;;;;;;ADCb;AAaA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAMA;AACA;AACA;AAAA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AArIa;;;;;;;ADWb;AACA;AACA;AACA;AACA;AACA;AALA;AAWA;AACA;AACA;AACA;AAAA;AACA;AACA;AAGA;AACA;AATA;AAgBA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAzBA;AAgCA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApBA;AA4BA;AACA;AAGA;AACA;AACA;AANA;;;;;;;ADvGA;AAEA;AACA;AACA;AAEa;AACA;AAKA;AACA;AACA;AACA;AACA;;;;;;;ADhBb;AACA;AAYA;AAAA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAnCA;;;;;;;;;;;;;;;;;ADdA;AAKA;AAAA;AAOA;AAAA;AAEA;AACA;;AACA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;;;AAAA;AAEA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAnDa;;;;;;;;;;;;;;;;;ADJb;AAGA;AAAA;AAKA;AAAA;AAEA;AACA;AACA;;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAXA;AAaA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AATA;AAWA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAAA;AAUA;AACA;AACA;AAUA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAWA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAAA;AAhMa;;;;;;;ADAb;AAYA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAAA;AAhE0B;AAET;AAHJ;;;;;;;ADEb;AACA;AACA;AAFA;AAEC;;;;;;;ADPD;AAEA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAjBA;AAgCA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AAGA;AACA;AAEA;AACA;AApBA;AAgCA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAVA;;;;;;;AD1DA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAcA;AAOA;AAMA;AAOA;AAkBA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAEA;AAAA;AACA;AAEA;AACA;AAEA;AAIA;AAIA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAQA;AAGA;AAGA;AAGA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAKA;AAEA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AAKA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AAAA;AACA;AAEA;AACA;AACA;AAGA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAQA;AAAA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAIA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AAGA;AAIA;AAMA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAIA;AAOA;AACA;AAQA;AACA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AAYA;AACA;AAKA;AACA;AAIA;AAGA;AACA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AAIA;AACA;AACA;AAIA;AACA;AAGA;AACA;AAAA;AAIA;AAEA;AACA;AAIA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AAQA;AAOA;AAGA;AACA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAaA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAGA;AAEA;AACA;AAAA;AACA;AACA;AAGA;AAEA;AACA;AAEA;AAIA;AACA;AAEA;AAAA;AAGA;AAIA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAIA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAQA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AACA;AAOA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AAGA;AAGA;AAEA;AAEA;AACA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAQA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AAKA;AAGA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAUA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA;AAEA;AACA;AAYA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AAMA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAKA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AASA;AACA;AAGA;AAEA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAIA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AAQA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AAGA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAMA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAKA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAOA;AAEA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AAAA;AAEA;AAGA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AAMA;AACA;AAAA;AACA;AAAA;AAOA;AAKA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAOA;AACA;AAAA;AACA;AAAA;AACA;AACA;AACA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAOA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AAGA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AAOA;AAOA;AACA;AAUA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAQA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AAGA;AACA;AAGA;AAEA;AACA;AAEA;AACA;AACA;AAEA;AASA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAGA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AAQA;AACA;AACA;AAEA;;;"} \ No newline at end of file From dc42d1caa26e5c40931d7f3910e38d12e2cd0f59 Mon Sep 17 00:00:00 2001 From: halo Date: Wed, 27 Apr 2022 22:56:34 +0800 Subject: [PATCH 074/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9ssh=5Fclient?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E9=80=89=E9=A1=B9=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 ++-- apps/locale/ja/LC_MESSAGES/django.po | 4 ++-- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 4 ++-- apps/settings/serializers/terminal.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 52b16b3bd..afbb57bf7 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2c88ade4bfae213bdcdafad656af73f764e3b1b3f2b0c59aa39626e967730ca -size 125911 +oid sha256:8f5891533a7cdfa3938ef057364f22b1df73685d423f9fa55bc46cd17439e56e +size 125915 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 8aa00bb46..dfb2f3c56 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -4247,8 +4247,8 @@ msgid "Enable XRDP" msgstr "XRDPの有効化" #: settings/serializers/terminal.py:38 -msgid "Enable KoKo SSH" -msgstr "KoKo SSHの有効化" +msgid "Enable SSH Client" +msgstr "SSH Clientの有効化" #: settings/utils/ldap.py:419 msgid "ldap:// or ldaps:// protocol is used." diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 915796d03..58ce5f865 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c75e0a1f2a047dac1374916c630bc0e8ef5ad5eea7518ffc21e93f747fc1235e -size 104165 +oid sha256:c6cf24f38fd82ad87d6062c4b36a771cb9fdeb76975b8b60f335a5a4fd9fd30c +size 104169 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 79cb6c940..049fb5daa 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -4185,8 +4185,8 @@ msgid "Enable XRDP" msgstr "启用 XRDP 服务" #: settings/serializers/terminal.py:38 -msgid "Enable KoKo SSH" -msgstr "启用 KoKo SSH" +msgid "Enable SSH Client" +msgstr "启用 SSH Client" #: settings/utils/ldap.py:419 msgid "ldap:// or ldaps:// protocol is used." diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index 8cdb9e065..f6799eca1 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -35,4 +35,4 @@ class TerminalSettingSerializer(serializers.Serializer): ) TERMINAL_MAGNUS_ENABLED = serializers.BooleanField(label=_("Enable database proxy")) XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP")) - TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField(label=_("Enable KoKo SSH")) + TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField(label=_("Enable SSH Client")) From 86e6982383bf06dc5ab446ab0cb05763910a835f Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 27 Apr 2022 17:16:30 +0800 Subject: [PATCH 075/258] =?UTF-8?q?fix:=20=E7=BB=84=E7=BB=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=20=E6=B7=BB=E5=8A=A0=20view=20platform=20per?= =?UTF-8?q?m?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/rbac/const.py b/apps/rbac/const.py index b84b7c69e..1a3cc93b4 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -91,7 +91,7 @@ exclude_permissions = ( only_system_permissions = ( - ('assets', 'platform', '*', '*'), + ('assets', 'platform', 'add,change,delete', 'platform'), ('users', 'user', 'delete', 'user'), ('rbac', 'role', 'delete,add,change', 'role'), ('rbac', 'systemrole', '*', '*'), From b1aadf1ee9e5ea090be6cea0ca2c36b0db35fc5e Mon Sep 17 00:00:00 2001 From: xiaziheng <1192939559@qq.com> Date: Thu, 28 Apr 2022 16:16:06 +0800 Subject: [PATCH 076/258] Fix oidc (#8165) --- apps/authentication/backends/oidc/backends.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index d1d7bd48c..450de565c 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -103,9 +103,23 @@ class OIDCAuthCodeBackend(OIDCBaseBackend): # Prepares the token payload that will be used to request an authentication token to the # token endpoint of the OIDC provider. logger.debug(log_prompt.format('Prepares token payload')) + + """ The reason for need not client_id and client_secret in token_payload. + + OIDC protocol indicate client's token_endpoint_auth_method only accept one type in + - client_secret_basic + - client_secret_post + - client_secret_jwt + - private_key_jwt + - none + If the client offer more than one auth method type to OIDC, OIDC will auth client failed. + OIDC default use client_secret_basic, this type only need in headers add Authorization=Basic xxx. + More info see: https://github.com/jumpserver/jumpserver/issues/8165 + + """ token_payload = { - 'client_id': settings.AUTH_OPENID_CLIENT_ID, - 'client_secret': settings.AUTH_OPENID_CLIENT_SECRET, + # 'client_id': settings.AUTH_OPENID_CLIENT_ID, + # 'client_secret': settings.AUTH_OPENID_CLIENT_SECRET, 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': build_absolute_uri( From 00ed7bb025e7113aa04b9de9e88cd52a2bfa9734 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 29 Apr 2022 12:54:15 +0800 Subject: [PATCH 077/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20OIDC=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=80=89=E6=8B=A9=E8=AE=A4=E8=AF=81=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/oidc/backends.py | 35 ++-- apps/jumpserver/conf.py | 2 + apps/jumpserver/settings/auth.py | 1 + apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 155 +++++++++--------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 151 ++++++++--------- apps/settings/serializers/auth/oidc.py | 8 + 8 files changed, 191 insertions(+), 169 deletions(-) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index 450de565c..8d4f2f629 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -103,9 +103,8 @@ class OIDCAuthCodeBackend(OIDCBaseBackend): # Prepares the token payload that will be used to request an authentication token to the # token endpoint of the OIDC provider. logger.debug(log_prompt.format('Prepares token payload')) - - """ The reason for need not client_id and client_secret in token_payload. - + """ + The reason for need not client_id and client_secret in token_payload. OIDC protocol indicate client's token_endpoint_auth_method only accept one type in - client_secret_basic - client_secret_post @@ -113,25 +112,35 @@ class OIDCAuthCodeBackend(OIDCBaseBackend): - private_key_jwt - none If the client offer more than one auth method type to OIDC, OIDC will auth client failed. - OIDC default use client_secret_basic, this type only need in headers add Authorization=Basic xxx. + OIDC default use client_secret_basic, + this type only need in headers add Authorization=Basic xxx. + More info see: https://github.com/jumpserver/jumpserver/issues/8165 - + More info see: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication """ token_payload = { - # 'client_id': settings.AUTH_OPENID_CLIENT_ID, - # 'client_secret': settings.AUTH_OPENID_CLIENT_SECRET, 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': build_absolute_uri( request, path=reverse(settings.AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME) ) } - - # Prepares the token headers that will be used to request an authentication token to the - # token endpoint of the OIDC provider. - logger.debug(log_prompt.format('Prepares token headers')) - basic_token = "{}:{}".format(settings.AUTH_OPENID_CLIENT_ID, settings.AUTH_OPENID_CLIENT_SECRET) - headers = {"Authorization": "Basic {}".format(base64.b64encode(basic_token.encode()).decode())} + if settings.AUTH_OPENID_CLIENT_AUTH_METHOD == 'client_secret_post': + token_payload.update({ + 'client_id': settings.AUTH_OPENID_CLIENT_ID, + 'client_secret': settings.AUTH_OPENID_CLIENT_SECRET, + }) + headers = None + else: + # Prepares the token headers that will be used to request an authentication token to the + # token endpoint of the OIDC provider. + logger.debug(log_prompt.format('Prepares token headers')) + basic_token = "{}:{}".format( + settings.AUTH_OPENID_CLIENT_ID, settings.AUTH_OPENID_CLIENT_SECRET + ) + headers = { + "Authorization": "Basic {}".format(base64.b64encode(basic_token.encode()).decode()) + } # Calls the token endpoint. logger.debug(log_prompt.format('Call the token endpoint')) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index fa4a211d4..18b998ab2 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -187,6 +187,8 @@ class Config(dict): 'BASE_SITE_URL': None, 'AUTH_OPENID_CLIENT_ID': 'client-id', 'AUTH_OPENID_CLIENT_SECRET': 'client-secret', + # https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + 'AUTH_OPENID_CLIENT_AUTH_METHOD': 'client_secret_basic', 'AUTH_OPENID_SHARE_SESSION': True, 'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True, diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index e07883d55..fb067078f 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -55,6 +55,7 @@ AUTH_OPENID = CONFIG.AUTH_OPENID BASE_SITE_URL = CONFIG.BASE_SITE_URL AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET +AUTH_OPENID_CLIENT_AUTH_METHOD = CONFIG.AUTH_OPENID_CLIENT_AUTH_METHOD AUTH_OPENID_PROVIDER_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_ENDPOINT AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index afbb57bf7..7feeaece9 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f5891533a7cdfa3938ef057364f22b1df73685d423f9fa55bc46cd17439e56e -size 125915 +oid sha256:e70a491494af861945bde8a0b03c9b6e78dde7016446236ead362362b76b09a8 +size 125713 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index dfb2f3c56..a0b05f272 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-20 16:35+0800\n" +"POT-Creation-Date: 2022-04-29 12:49+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -88,7 +88,7 @@ msgstr "ログイン確認" #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 #: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:110 rbac/models/rolebinding.py:40 +#: rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -1360,7 +1360,7 @@ msgstr "監査" #: audits/models.py:27 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:168 +#: rbac/tree.py:228 msgid "Delete" msgstr "削除" @@ -1413,11 +1413,11 @@ msgstr "ファイル転送ログ" #: audits/models.py:55 #: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:165 +#: rbac/tree.py:225 msgid "Create" msgstr "作成" -#: audits/models.py:56 rbac/tree.py:167 templates/_csv_import_export.html:18 +#: audits/models.py:56 rbac/tree.py:227 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" @@ -2181,7 +2181,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:299 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2642,11 +2642,11 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:298 +#: jumpserver/conf.py:300 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:300 +#: jumpserver/conf.py:302 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -2901,12 +2901,12 @@ msgstr "" msgid "The organization have resource ({}) cannot be deleted" msgstr "組織のリソース ({}) は削除できません" -#: orgs/apps.py:7 rbac/tree.py:114 +#: orgs/apps.py:7 rbac/tree.py:115 msgid "App organizations" msgstr "アプリ組織" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 -#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:47 +#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:59 #: tickets/serializers/ticket/ticket.py:77 msgid "Organization" @@ -2920,7 +2920,7 @@ msgstr "グローバル組織" msgid "Can view root org" msgstr "グローバル組織を表示できます" -#: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:43 +#: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:44 #: users/models/user.py:671 msgid "Role" msgstr "ロール" @@ -3133,27 +3133,27 @@ msgstr "{} 少なくとも1つのシステムロール" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:101 +#: rbac/builtin.py:109 msgid "SystemAdmin" msgstr "システム管理者" -#: rbac/builtin.py:104 +#: rbac/builtin.py:112 msgid "SystemAuditor" msgstr "システム監査人" -#: rbac/builtin.py:107 +#: rbac/builtin.py:115 msgid "SystemComponent" msgstr "システムコンポーネント" -#: rbac/builtin.py:113 +#: rbac/builtin.py:121 msgid "OrgAdmin" msgstr "組織管理者" -#: rbac/builtin.py:116 +#: rbac/builtin.py:124 msgid "OrgAuditor" msgstr "監査員を組織する" -#: rbac/builtin.py:119 +#: rbac/builtin.py:127 msgid "OrgUser" msgstr "組織ユーザー" @@ -3185,7 +3185,7 @@ msgstr "ファイルマネージャを表示できます" msgid "Permission" msgstr "権限" -#: rbac/models/role.py:31 rbac/models/rolebinding.py:37 +#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 msgid "Scope" msgstr "スコープ" @@ -3205,22 +3205,22 @@ msgstr "システムの役割" msgid "Organization role" msgstr "組織の役割" -#: rbac/models/rolebinding.py:52 +#: rbac/models/rolebinding.py:53 msgid "Role binding" msgstr "ロールバインディング" -#: rbac/models/rolebinding.py:151 +#: rbac/models/rolebinding.py:159 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "" "ユーザーの最後のロールは削除できません。ユーザーを組織から削除できます。" -#: rbac/models/rolebinding.py:158 +#: rbac/models/rolebinding.py:166 msgid "Organization role binding" msgstr "組織の役割バインディング" -#: rbac/models/rolebinding.py:173 +#: rbac/models/rolebinding.py:181 msgid "System role binding" msgstr "システムロールバインディング" @@ -3244,91 +3244,91 @@ msgstr "ロール表示" msgid "Has bound this role" msgstr "この役割をバインドしました" -#: rbac/tree.py:19 rbac/tree.py:20 +#: rbac/tree.py:20 rbac/tree.py:21 msgid "All permissions" msgstr "すべての権限" -#: rbac/tree.py:26 +#: rbac/tree.py:27 msgid "Console view" msgstr "コンソールビュー" -#: rbac/tree.py:27 +#: rbac/tree.py:28 msgid "Workbench view" msgstr "ワークスペースビュー" -#: rbac/tree.py:28 +#: rbac/tree.py:29 msgid "Audit view" msgstr "監査ビュー" -#: rbac/tree.py:29 settings/models.py:140 +#: rbac/tree.py:30 settings/models.py:140 msgid "System setting" msgstr "システム設定" -#: rbac/tree.py:30 +#: rbac/tree.py:31 msgid "Other" msgstr "その他" -#: rbac/tree.py:38 +#: rbac/tree.py:39 msgid "Accounts" msgstr "アカウント" -#: rbac/tree.py:42 +#: rbac/tree.py:43 msgid "Session audits" msgstr "セッション監査" -#: rbac/tree.py:52 +#: rbac/tree.py:53 msgid "Cloud import" msgstr "クラウドインポート" -#: rbac/tree.py:53 +#: rbac/tree.py:54 msgid "Backup account" msgstr "バックアップアカウント" -#: rbac/tree.py:54 +#: rbac/tree.py:55 msgid "Gather account" msgstr "アカウントを集める" -#: rbac/tree.py:55 +#: rbac/tree.py:56 msgid "App change auth" msgstr "応用改密" -#: rbac/tree.py:56 +#: rbac/tree.py:57 msgid "Asset change auth" msgstr "資産の改ざん" -#: rbac/tree.py:57 +#: rbac/tree.py:58 msgid "Terminal setting" msgstr "ターミナル設定" -#: rbac/tree.py:58 +#: rbac/tree.py:59 msgid "My assets" msgstr "私の資産" -#: rbac/tree.py:59 +#: rbac/tree.py:60 msgid "My apps" msgstr "マイアプリ" -#: rbac/tree.py:115 +#: rbac/tree.py:116 msgid "Ticket comment" msgstr "チケットコメント" -#: rbac/tree.py:116 tickets/models/ticket.py:163 +#: rbac/tree.py:117 tickets/models/ticket.py:163 msgid "Ticket" msgstr "チケット" -#: rbac/tree.py:117 +#: rbac/tree.py:118 msgid "Common setting" msgstr "共通設定" -#: rbac/tree.py:118 +#: rbac/tree.py:119 msgid "View permission tree" msgstr "権限ツリーの表示" -#: rbac/tree.py:119 +#: rbac/tree.py:120 msgid "Execute batch command" msgstr "バッチ実行コマンド" -#: rbac/tree.py:166 +#: rbac/tree.py:226 msgid "View" msgstr "表示" @@ -3454,7 +3454,7 @@ msgstr "ログインリダイレクトの有効化msg" msgid "Enable CAS Auth" msgstr "CAS 認証の有効化" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:32 +#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:40 msgid "Server url" msgstr "サービス側アドレス" @@ -3556,79 +3556,83 @@ msgstr "クライアントID" msgid "Client Secret" msgstr "クライアント秘密" -#: settings/serializers/auth/oidc.py:20 +#: settings/serializers/auth/oidc.py:26 +msgid "Client authentication method" +msgstr "クライアント認証方式" + +#: settings/serializers/auth/oidc.py:28 msgid "Share session" msgstr "セッションの共有" -#: settings/serializers/auth/oidc.py:22 +#: settings/serializers/auth/oidc.py:30 msgid "Ignore ssl verification" msgstr "Ssl検証を無視する" -#: settings/serializers/auth/oidc.py:29 +#: settings/serializers/auth/oidc.py:37 msgid "Use Keycloak" msgstr "Keycloakを使用する" -#: settings/serializers/auth/oidc.py:35 +#: settings/serializers/auth/oidc.py:43 msgid "Realm name" msgstr "レルム名" -#: settings/serializers/auth/oidc.py:41 +#: settings/serializers/auth/oidc.py:49 msgid "Enable OPENID Auth" msgstr "OIDC認証の有効化" -#: settings/serializers/auth/oidc.py:43 +#: settings/serializers/auth/oidc.py:51 msgid "Provider endpoint" msgstr "プロバイダーエンドポイント" -#: settings/serializers/auth/oidc.py:46 +#: settings/serializers/auth/oidc.py:54 msgid "Provider auth endpoint" msgstr "認証エンドポイントアドレス" -#: settings/serializers/auth/oidc.py:49 +#: settings/serializers/auth/oidc.py:57 msgid "Provider token endpoint" msgstr "プロバイダートークンエンドポイント" -#: settings/serializers/auth/oidc.py:52 +#: settings/serializers/auth/oidc.py:60 msgid "Provider jwks endpoint" msgstr "プロバイダーjwksエンドポイント" -#: settings/serializers/auth/oidc.py:55 +#: settings/serializers/auth/oidc.py:63 msgid "Provider userinfo endpoint" msgstr "プロバイダーuserinfoエンドポイント" -#: settings/serializers/auth/oidc.py:58 +#: settings/serializers/auth/oidc.py:66 msgid "Provider end session endpoint" msgstr "プロバイダーのセッション終了エンドポイント" -#: settings/serializers/auth/oidc.py:61 +#: settings/serializers/auth/oidc.py:69 msgid "Provider sign alg" msgstr "プロビダーサインalg" -#: settings/serializers/auth/oidc.py:64 +#: settings/serializers/auth/oidc.py:72 msgid "Provider sign key" msgstr "プロバイダ署名キー" -#: settings/serializers/auth/oidc.py:66 +#: settings/serializers/auth/oidc.py:74 msgid "Scopes" msgstr "スコープ" -#: settings/serializers/auth/oidc.py:68 +#: settings/serializers/auth/oidc.py:76 msgid "Id token max age" msgstr "IDトークンの最大年齢" -#: settings/serializers/auth/oidc.py:71 +#: settings/serializers/auth/oidc.py:79 msgid "Id token include claims" msgstr "IDトークンにはクレームが含まれます" -#: settings/serializers/auth/oidc.py:73 +#: settings/serializers/auth/oidc.py:81 msgid "Use state" msgstr "使用状態" -#: settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oidc.py:82 msgid "Use nonce" msgstr "Nonceを使用" -#: settings/serializers/auth/oidc.py:76 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oidc.py:84 settings/serializers/auth/saml2.py:33 msgid "Always update user" msgstr "常にユーザーを更新" @@ -4513,9 +4517,7 @@ msgstr "ホームページ" msgid "Cancel" msgstr "キャンセル" -#: templates/resource_download.html:18 templates/resource_download.html:24 -#: templates/resource_download.html:25 templates/resource_download.html:30 -#: templates/resource_download.html:40 +#: templates/resource_download.html:18 templates/resource_download.html:30 msgid "Client" msgstr "クライアント" @@ -4544,19 +4546,11 @@ msgstr "" "MacOSは、Windowsに付属のRDPアセットを接続するためにクライアントをダウンロード" "する必要があります" -#: templates/resource_download.html:42 -msgid "" -"Windows needs to download the client to connect SSH assets, and the MacOS " -"system uses its own terminal" -msgstr "" -"WindowsはクライアントをダウンロードしてSSH資産に接続する必要があり、macOSシス" -"テムは独自のTerminalを採用している。" - -#: templates/resource_download.html:53 +#: templates/resource_download.html:41 msgid "Windows Remote application publisher tools" msgstr "Windowsリモートアプリケーション発行者ツール" -#: templates/resource_download.html:54 +#: templates/resource_download.html:42 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -6732,3 +6726,10 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" + +#~ msgid "" +#~ "Windows needs to download the client to connect SSH assets, and the MacOS " +#~ "system uses its own terminal" +#~ msgstr "" +#~ "WindowsはクライアントをダウンロードしてSSH資産に接続する必要があり、macOS" +#~ "システムは独自のTerminalを採用している。" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 58ce5f865..c651fb2c5 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6cf24f38fd82ad87d6062c4b36a771cb9fdeb76975b8b60f335a5a4fd9fd30c -size 104169 +oid sha256:95e9f6addbdb6811647fd2bb5ae64bfc2572a80702c371eab0a1bb041a1e8476 +size 104032 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 049fb5daa..e091a35c8 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-20 16:35+0800\n" +"POT-Creation-Date: 2022-04-29 12:49+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -87,7 +87,7 @@ msgstr "登录复核" #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 #: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 -#: rbac/builtin.py:110 rbac/models/rolebinding.py:40 +#: rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -1348,7 +1348,7 @@ msgstr "日志审计" #: audits/models.py:27 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:168 +#: rbac/tree.py:228 msgid "Delete" msgstr "删除" @@ -1401,11 +1401,11 @@ msgstr "文件管理" #: audits/models.py:55 #: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:165 +#: rbac/tree.py:225 msgid "Create" msgstr "创建" -#: audits/models.py:56 rbac/tree.py:167 templates/_csv_import_export.html:18 +#: audits/models.py:56 rbac/tree.py:227 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" @@ -2160,7 +2160,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:299 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2612,11 +2612,11 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:298 +#: jumpserver/conf.py:300 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:300 +#: jumpserver/conf.py:302 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -2865,12 +2865,12 @@ msgstr "LDAP 同步设置组织为当前组织,请切换其他组织后再进 msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" -#: orgs/apps.py:7 rbac/tree.py:114 +#: orgs/apps.py:7 rbac/tree.py:115 msgid "App organizations" msgstr "组织管理" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 -#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:47 +#: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:59 #: tickets/serializers/ticket/ticket.py:77 msgid "Organization" @@ -2884,7 +2884,7 @@ msgstr "全局组织" msgid "Can view root org" msgstr "可以查看全局组织" -#: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:43 +#: orgs/models.py:216 rbac/models/role.py:46 rbac/models/rolebinding.py:44 #: users/models/user.py:671 msgid "Role" msgstr "角色" @@ -3095,27 +3095,27 @@ msgstr "{} 至少有一个系统角色" msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:101 +#: rbac/builtin.py:109 msgid "SystemAdmin" msgstr "系统管理员" -#: rbac/builtin.py:104 +#: rbac/builtin.py:112 msgid "SystemAuditor" msgstr "系统审计员" -#: rbac/builtin.py:107 +#: rbac/builtin.py:115 msgid "SystemComponent" msgstr "系统组件" -#: rbac/builtin.py:113 +#: rbac/builtin.py:121 msgid "OrgAdmin" msgstr "组织管理员" -#: rbac/builtin.py:116 +#: rbac/builtin.py:124 msgid "OrgAuditor" msgstr "组织审计员" -#: rbac/builtin.py:119 +#: rbac/builtin.py:127 msgid "OrgUser" msgstr "组织用户" @@ -3147,7 +3147,7 @@ msgstr "文件管理" msgid "Permission" msgstr "权限" -#: rbac/models/role.py:31 rbac/models/rolebinding.py:37 +#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 msgid "Scope" msgstr "范围" @@ -3167,21 +3167,21 @@ msgstr "系统角色" msgid "Organization role" msgstr "组织角色" -#: rbac/models/rolebinding.py:52 +#: rbac/models/rolebinding.py:53 msgid "Role binding" msgstr "角色绑定" -#: rbac/models/rolebinding.py:151 +#: rbac/models/rolebinding.py:159 msgid "" "User last role in org, can not be delete, you can remove user from org " "instead" msgstr "用户最后一个角色,不能删除,你可以将用户从组织移除" -#: rbac/models/rolebinding.py:158 +#: rbac/models/rolebinding.py:166 msgid "Organization role binding" msgstr "组织角色绑定" -#: rbac/models/rolebinding.py:173 +#: rbac/models/rolebinding.py:181 msgid "System role binding" msgstr "系统角色绑定" @@ -3205,91 +3205,91 @@ msgstr "角色显示" msgid "Has bound this role" msgstr "已经绑定" -#: rbac/tree.py:19 rbac/tree.py:20 +#: rbac/tree.py:20 rbac/tree.py:21 msgid "All permissions" msgstr "所有权限" -#: rbac/tree.py:26 +#: rbac/tree.py:27 msgid "Console view" msgstr "控制台" -#: rbac/tree.py:27 +#: rbac/tree.py:28 msgid "Workbench view" msgstr "工作台" -#: rbac/tree.py:28 +#: rbac/tree.py:29 msgid "Audit view" msgstr "审计台" -#: rbac/tree.py:29 settings/models.py:140 +#: rbac/tree.py:30 settings/models.py:140 msgid "System setting" msgstr "系统设置" -#: rbac/tree.py:30 +#: rbac/tree.py:31 msgid "Other" msgstr "其它" -#: rbac/tree.py:38 +#: rbac/tree.py:39 msgid "Accounts" msgstr "账号管理" -#: rbac/tree.py:42 +#: rbac/tree.py:43 msgid "Session audits" msgstr "会话审计" -#: rbac/tree.py:52 +#: rbac/tree.py:53 msgid "Cloud import" msgstr "云同步" -#: rbac/tree.py:53 +#: rbac/tree.py:54 msgid "Backup account" msgstr "备份账号" -#: rbac/tree.py:54 +#: rbac/tree.py:55 msgid "Gather account" msgstr "收集账号" -#: rbac/tree.py:55 +#: rbac/tree.py:56 msgid "App change auth" msgstr "应用改密" -#: rbac/tree.py:56 +#: rbac/tree.py:57 msgid "Asset change auth" msgstr "资产改密" -#: rbac/tree.py:57 +#: rbac/tree.py:58 msgid "Terminal setting" msgstr "终端设置" -#: rbac/tree.py:58 +#: rbac/tree.py:59 msgid "My assets" msgstr "我的资产" -#: rbac/tree.py:59 +#: rbac/tree.py:60 msgid "My apps" msgstr "我的应用" -#: rbac/tree.py:115 +#: rbac/tree.py:116 msgid "Ticket comment" msgstr "工单评论" -#: rbac/tree.py:116 tickets/models/ticket.py:163 +#: rbac/tree.py:117 tickets/models/ticket.py:163 msgid "Ticket" msgstr "工单管理" -#: rbac/tree.py:117 +#: rbac/tree.py:118 msgid "Common setting" msgstr "一般设置" -#: rbac/tree.py:118 +#: rbac/tree.py:119 msgid "View permission tree" msgstr "查看授权树" -#: rbac/tree.py:119 +#: rbac/tree.py:120 msgid "Execute batch command" msgstr "执行批量命令" -#: rbac/tree.py:166 +#: rbac/tree.py:226 msgid "View" msgstr "查看" @@ -3415,7 +3415,7 @@ msgstr "启用登录跳转提示" msgid "Enable CAS Auth" msgstr "启用 CAS 认证" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:32 +#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:40 msgid "Server url" msgstr "服务端地址" @@ -3517,79 +3517,83 @@ msgstr "客户端 ID" msgid "Client Secret" msgstr "客户端密钥" -#: settings/serializers/auth/oidc.py:20 +#: settings/serializers/auth/oidc.py:26 +msgid "Client authentication method" +msgstr "客户端认证方式" + +#: settings/serializers/auth/oidc.py:28 msgid "Share session" msgstr "共享会话" -#: settings/serializers/auth/oidc.py:22 +#: settings/serializers/auth/oidc.py:30 msgid "Ignore ssl verification" msgstr "忽略 SSL 证书验证" -#: settings/serializers/auth/oidc.py:29 +#: settings/serializers/auth/oidc.py:37 msgid "Use Keycloak" msgstr "使用 Keycloak" -#: settings/serializers/auth/oidc.py:35 +#: settings/serializers/auth/oidc.py:43 msgid "Realm name" msgstr "域" -#: settings/serializers/auth/oidc.py:41 +#: settings/serializers/auth/oidc.py:49 msgid "Enable OPENID Auth" msgstr "启用 OIDC 认证" -#: settings/serializers/auth/oidc.py:43 +#: settings/serializers/auth/oidc.py:51 msgid "Provider endpoint" msgstr "端点地址" -#: settings/serializers/auth/oidc.py:46 +#: settings/serializers/auth/oidc.py:54 msgid "Provider auth endpoint" msgstr "授权端点地址" -#: settings/serializers/auth/oidc.py:49 +#: settings/serializers/auth/oidc.py:57 msgid "Provider token endpoint" msgstr "token 端点地址" -#: settings/serializers/auth/oidc.py:52 +#: settings/serializers/auth/oidc.py:60 msgid "Provider jwks endpoint" msgstr "jwks 端点地址" -#: settings/serializers/auth/oidc.py:55 +#: settings/serializers/auth/oidc.py:63 msgid "Provider userinfo endpoint" msgstr "用户信息端点地址" -#: settings/serializers/auth/oidc.py:58 +#: settings/serializers/auth/oidc.py:66 msgid "Provider end session endpoint" msgstr "注销会话端点地址" -#: settings/serializers/auth/oidc.py:61 +#: settings/serializers/auth/oidc.py:69 msgid "Provider sign alg" msgstr "签名算法" -#: settings/serializers/auth/oidc.py:64 +#: settings/serializers/auth/oidc.py:72 msgid "Provider sign key" msgstr "签名 Key" -#: settings/serializers/auth/oidc.py:66 +#: settings/serializers/auth/oidc.py:74 msgid "Scopes" msgstr "连接范围" -#: settings/serializers/auth/oidc.py:68 +#: settings/serializers/auth/oidc.py:76 msgid "Id token max age" msgstr "令牌有效时间" -#: settings/serializers/auth/oidc.py:71 +#: settings/serializers/auth/oidc.py:79 msgid "Id token include claims" msgstr "声明" -#: settings/serializers/auth/oidc.py:73 +#: settings/serializers/auth/oidc.py:81 msgid "Use state" msgstr "使用状态" -#: settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oidc.py:82 msgid "Use nonce" msgstr "临时使用" -#: settings/serializers/auth/oidc.py:76 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oidc.py:84 settings/serializers/auth/saml2.py:33 msgid "Always update user" msgstr "总是更新用户信息" @@ -4446,9 +4450,7 @@ msgstr "首页" msgid "Cancel" msgstr "取消" -#: templates/resource_download.html:18 templates/resource_download.html:24 -#: templates/resource_download.html:25 templates/resource_download.html:30 -#: templates/resource_download.html:40 +#: templates/resource_download.html:18 templates/resource_download.html:30 msgid "Client" msgstr "客户端" @@ -4474,17 +4476,11 @@ msgid "" "Windows" msgstr "macOS 需要下载客户端来连接 RDP 资产,Windows 系统默认安装了该程序" -#: templates/resource_download.html:42 -msgid "" -"Windows needs to download the client to connect SSH assets, and the MacOS " -"system uses its own terminal" -msgstr "Windows 需要下载客户端来连接SSH资产,macOS系统采用自带的Terminal" - -#: templates/resource_download.html:53 +#: templates/resource_download.html:41 msgid "Windows Remote application publisher tools" msgstr "Windows 远程应用发布服务器工具" -#: templates/resource_download.html:54 +#: templates/resource_download.html:42 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -6640,3 +6636,8 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" + +#~ msgid "" +#~ "Windows needs to download the client to connect SSH assets, and the MacOS " +#~ "system uses its own terminal" +#~ msgstr "Windows 需要下载客户端来连接SSH资产,macOS系统采用自带的Terminal" diff --git a/apps/settings/serializers/auth/oidc.py b/apps/settings/serializers/auth/oidc.py index 21f0f989e..a3777fb25 100644 --- a/apps/settings/serializers/auth/oidc.py +++ b/apps/settings/serializers/auth/oidc.py @@ -17,6 +17,14 @@ class CommonSettingSerializer(serializers.Serializer): AUTH_OPENID_CLIENT_SECRET = serializers.CharField( required=False, max_length=1024, write_only=True, label=_('Client Secret') ) + AUTH_OPENID_CLIENT_AUTH_METHOD = serializers.ChoiceField( + default='client_secret_basic', + choices=( + ('client_secret_basic', 'Client Secret Basic'), + ('client_secret_post', 'Client Secret Post') + ), + label=_('Client authentication method') + ) AUTH_OPENID_SHARE_SESSION = serializers.BooleanField(required=False, label=_('Share session')) AUTH_OPENID_IGNORE_SSL_VERIFICATION = serializers.BooleanField( required=False, label=_('Ignore ssl verification') From 2493647e5c49112805279001d6d9974b9b6c1e8c Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 29 Apr 2022 16:05:23 +0800 Subject: [PATCH 078/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dwindows?= =?UTF-8?q?=E6=89=A7=E8=A1=8Cansible=E6=98=BE=E7=A4=BAsudo=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index c80c65ce6..85ac6c918 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -301,7 +301,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin 'private_key': auth_user.private_key_file } - if not with_become: + if not with_become or self.is_windows(): return info if become_user: From d23953932f2ea6ec3537f45c9323c075ed574308 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Sun, 24 Apr 2022 17:00:48 +0800 Subject: [PATCH 079/258] =?UTF-8?q?perf:=20connection=20token=20=E5=88=86a?= =?UTF-8?q?pi=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 111 +++++++++--------- .../serializers/connect_token.py | 25 ++-- apps/authentication/urls/api_urls.py | 1 + 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 9e16d31ba..ef69890e5 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -7,7 +7,6 @@ import os import base64 import ctypes -from django.conf import settings from django.core.cache import cache from django.shortcuts import get_object_or_404 from django.http import HttpResponse @@ -33,11 +32,11 @@ from perms.utils.asset.permission import get_asset_actions from common.const.http import PATCH from terminal.models import EndpointRule from ..serializers import ( - ConnectionTokenSerializer, ConnectionTokenSecretSerializer, + ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer ) logger = get_logger(__name__) -__all__ = ['UserConnectionTokenViewSet', 'TokenCacheMixin'] +__all__ = ['UserConnectionTokenViewSet', 'UserSuperConnectionTokenViewSet', 'TokenCacheMixin'] class ClientProtocolMixin: @@ -70,8 +69,7 @@ class ClientProtocolMixin: system_user = serializer.validated_data['system_user'] user = serializer.validated_data.get('user') - if not user or not self.request.user.is_superuser: - user = self.request.user + user = user if user else self.request.user return asset, application, system_user, user @staticmethod @@ -105,7 +103,7 @@ class ClientProtocolMixin: 'bookmarktype:i': '3', 'use redirection server name:i': '0', 'smart sizing:i': '1', - #'drivestoredirect:s': '*', + # 'drivestoredirect:s': '*', # 'domain:s': '' # 'alternate shell:s:': '||MySQLWorkbench', # 'remoteapplicationname:s': 'Firefox', @@ -206,21 +204,6 @@ class ClientProtocolMixin: rst = rst.decode('ascii') return rst - @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') - def get_rdp_file(self, request, *args, **kwargs): - if self.request.method == 'GET': - data = self.request.query_params - else: - data = self.request.data - serializer = self.get_serializer(data=data) - serializer.is_valid(raise_exception=True) - name, data = self.get_rdp_file_content(serializer) - response = HttpResponse(data, content_type='application/octet-stream') - filename = "{}-{}-jumpserver.rdp".format(self.request.user.username, name) - filename = urllib.parse.quote(filename) - response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % filename - return response - def get_valid_serializer(self): if self.request.method == 'GET': data = self.request.query_params @@ -252,6 +235,21 @@ class ClientProtocolMixin: } return data + @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') + def get_rdp_file(self, request, *args, **kwargs): + if self.request.method == 'GET': + data = self.request.query_params + else: + data = self.request.data + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + name, data = self.get_rdp_file_content(serializer) + response = HttpResponse(data, content_type='application/octet-stream') + filename = "{}-{}-jumpserver.rdp".format(self.request.user.username, name) + filename = urllib.parse.quote(filename) + response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % filename + return response + @action(methods=['POST', 'GET'], detail=False, url_path='client-url') def get_client_protocol_url(self, request, *args, **kwargs): serializer = self.get_valid_serializer() @@ -370,7 +368,7 @@ class TokenCacheMixin: key = self.get_token_cache_key(token) return cache.ttl(key) - def set_token_to_cache(self, token, value, ttl=5*60): + def set_token_to_cache(self, token, value, ttl=5 * 60): key = self.get_token_cache_key(token) cache.set(key, value, timeout=ttl) @@ -379,7 +377,7 @@ class TokenCacheMixin: value = cache.get(key, None) return value - def renewal_token(self, token, ttl=5*60): + def renewal_token(self, token, ttl=5 * 60): value = self.get_token_from_cache(token) if value: pre_ttl = self.get_token_ttl(token) @@ -397,22 +395,10 @@ class TokenCacheMixin: return data -class UserConnectionTokenViewSet( +class BaseUserConnectionTokenViewSet( RootOrgViewMixin, SerializerMixin, ClientProtocolMixin, - SecretDetailMixin, TokenCacheMixin, GenericViewSet + TokenCacheMixin, GenericViewSet ): - serializer_classes = { - 'default': ConnectionTokenSerializer, - 'get_secret_detail': ConnectionTokenSecretSerializer, - } - rbac_perms = { - 'GET': 'authentication.view_connectiontoken', - 'create': 'authentication.add_connectiontoken', - 'renewal': 'authentication.add_superconnectiontoken', - 'get_secret_detail': 'authentication.view_connectiontokensecret', - 'get_rdp_file': 'authentication.add_connectiontoken', - 'get_client_protocol_url': 'authentication.add_connectiontoken', - } @staticmethod def check_resource_permission(user, asset, application, system_user): @@ -429,22 +415,7 @@ class UserConnectionTokenViewSet( raise PermissionDenied(error) return True - @action(methods=[PATCH], detail=False) - def renewal(self, request, *args, **kwargs): - """ 续期 Token """ - perm_required = 'authentication.add_superconnectiontoken' - if not request.user.has_perm(perm_required): - raise PermissionDenied('No permissions for authentication.add_superconnectiontoken') - token = request.data.get('token', '') - data = self.renewal_token(token) - status_code = 200 if data.get('ok') else 404 - return Response(data=data, status=status_code) - - def create_token(self, user, asset, application, system_user, ttl=5*60): - # 再次强调一下权限 - perm_required = 'authentication.add_superconnectiontoken' - if user != self.request.user and not self.request.user.has_perm(perm_required): - raise PermissionDenied('Only can create user token') + def create_token(self, user, asset, application, system_user, ttl=5 * 60): self.check_resource_permission(user, asset, application, system_user) token = random_string(36) secret = random_string(16) @@ -489,6 +460,20 @@ class UserConnectionTokenViewSet( } return Response(data, status=201) + +class UserConnectionTokenViewSet(BaseUserConnectionTokenViewSet, SecretDetailMixin): + serializer_classes = { + 'default': ConnectionTokenSerializer, + 'get_secret_detail': ConnectionTokenSecretSerializer, + } + rbac_perms = { + 'GET': 'authentication.view_connectiontoken', + 'create': 'authentication.add_connectiontoken', + 'get_secret_detail': 'authentication.view_connectiontokensecret', + 'get_rdp_file': 'authentication.add_connectiontoken', + 'get_client_protocol_url': 'authentication.add_connectiontoken', + } + def valid_token(self, token): from users.models import User from assets.models import SystemUser, Asset @@ -526,3 +511,23 @@ class UserConnectionTokenViewSet( if not value: return Response('', status=404) return Response(value) + + +class UserSuperConnectionTokenViewSet( + BaseUserConnectionTokenViewSet, TokenCacheMixin, GenericViewSet +): + serializer_classes = { + 'default': SuperConnectionTokenSerializer, + } + rbac_perms = { + 'create': 'authentication.add_superconnectiontoken', + 'renewal': 'authentication.add_superconnectiontoken' + } + + @action(methods=[PATCH], detail=False) + def renewal(self, request, *args, **kwargs): + """ 续期 Token """ + token = request.data.get('token', '') + data = self.renewal_token(token) + status_code = 200 if data.get('ok') else 404 + return Response(data=data, status=status_code) diff --git a/apps/authentication/serializers/connect_token.py b/apps/authentication/serializers/connect_token.py index c8f94909d..d9694b5bd 100644 --- a/apps/authentication/serializers/connect_token.py +++ b/apps/authentication/serializers/connect_token.py @@ -13,24 +13,16 @@ __all__ = [ 'ConnectionTokenUserSerializer', 'ConnectionTokenFilterRuleSerializer', 'ConnectionTokenAssetSerializer', 'ConnectionTokenSystemUserSerializer', 'ConnectionTokenDomainSerializer', 'ConnectionTokenRemoteAppSerializer', - 'ConnectionTokenGatewaySerializer', 'ConnectionTokenSecretSerializer' + 'ConnectionTokenGatewaySerializer', 'ConnectionTokenSecretSerializer', + 'SuperConnectionTokenSerializer' ] class ConnectionTokenSerializer(serializers.Serializer): - user = serializers.CharField(max_length=128, required=False, allow_blank=True) system_user = serializers.CharField(max_length=128, required=True) asset = serializers.CharField(max_length=128, required=False) application = serializers.CharField(max_length=128, required=False) - @staticmethod - def validate_user(user_id): - from users.models import User - user = User.objects.filter(id=user_id).first() - if user is None: - raise serializers.ValidationError('user id not exist') - return user - @staticmethod def validate_system_user(system_user_id): from assets.models import SystemUser @@ -65,6 +57,18 @@ class ConnectionTokenSerializer(serializers.Serializer): return super().validate(attrs) +class SuperConnectionTokenSerializer(ConnectionTokenSerializer): + user = serializers.CharField(max_length=128, required=False, allow_blank=True) + + @staticmethod + def validate_user(user_id): + from users.models import User + user = User.objects.filter(id=user_id).first() + if user is None: + raise serializers.ValidationError('user id not exist') + return user + + class ConnectionTokenUserSerializer(serializers.ModelSerializer): class Meta: model = User @@ -114,7 +118,6 @@ class ConnectionTokenDomainSerializer(serializers.ModelSerializer): class ConnectionTokenFilterRuleSerializer(serializers.ModelSerializer): - class Meta: model = CommandFilterRule fields = [ diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index 920988ee4..754b7c793 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -11,6 +11,7 @@ router.register('access-keys', api.AccessKeyViewSet, 'access-key') router.register('sso', api.SSOViewSet, 'sso') router.register('temp-tokens', api.TempTokenViewSet, 'temp-token') router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-token') +router.register('super-connection-token', api.UserSuperConnectionTokenViewSet, 'super-connection-token') urlpatterns = [ From c56179e9e4dbe34e7e269c6ed8e101ff191a23db Mon Sep 17 00:00:00 2001 From: jiangweidong <80373698+F2C-Jiang@users.noreply.github.com> Date: Thu, 5 May 2022 13:07:48 +0800 Subject: [PATCH 080/258] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BC=81?= =?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=81=E9=92=89=E9=92=89=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E5=AE=A1=E6=89=B9=E5=B7=A5=E5=8D=95=20(#8115)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/views/dingtalk.py | 10 +- apps/authentication/views/mixins.py | 12 ++ apps/authentication/views/wecom.py | 10 +- apps/jumpserver/urls.py | 1 + apps/locale/ja/LC_MESSAGES/django.mo | 3 - apps/locale/ja/LC_MESSAGES/django.po | 113 +++++++++++++----- apps/locale/zh/LC_MESSAGES/django.mo | 3 - apps/locale/zh/LC_MESSAGES/django.po | 107 ++++++++++++----- apps/templates/_base_double_screen.html | 42 +++++++ apps/tickets/notifications.py | 36 +++++- .../templates/tickets/_msg_ticket.html | 10 +- .../tickets/approve_check_password.html | 52 ++++++++ apps/tickets/urls/view_urls.py | 12 ++ apps/tickets/views/__init__.py | 1 + apps/tickets/views/approve.py | 93 ++++++++++++++ 15 files changed, 424 insertions(+), 81 deletions(-) create mode 100644 apps/authentication/views/mixins.py delete mode 100644 apps/locale/ja/LC_MESSAGES/django.mo delete mode 100644 apps/locale/zh/LC_MESSAGES/django.mo create mode 100644 apps/templates/_base_double_screen.html create mode 100644 apps/tickets/templates/tickets/approve_check_password.html create mode 100644 apps/tickets/urls/view_urls.py create mode 100644 apps/tickets/views/__init__.py create mode 100644 apps/tickets/views/approve.py diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index b4fd2cede..0ca03749e 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -21,6 +21,7 @@ from authentication.mixins import AuthMixin from common.sdk.im.dingtalk import DingTalk from common.utils.common import get_request_ip from authentication.notifications import OAuthBindMessage +from .mixins import METAMixin logger = get_logger(__file__) @@ -200,14 +201,17 @@ class DingTalkEnableStartView(UserVerifyPasswordView): return success_url -class DingTalkQRLoginView(DingTalkQRMixin, View): +class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View): permission_classes = (AllowAny,) def get(self, request: HttpRequest): redirect_url = request.GET.get('redirect_url') redirect_uri = reverse('authentication:dingtalk-qr-login-callback', external=True) - redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) + redirect_uri += '?' + urlencode({ + 'redirect_url': redirect_url, + 'next': self.get_next_url_from_meta() + }) url = self.get_qr_url(redirect_uri) return HttpResponseRedirect(url) @@ -305,4 +309,4 @@ class DingTalkOAuthLoginCallbackView(AuthMixin, DingTalkOAuthMixin, View): response = self.get_failed_response(login_url, title=msg, msg=msg) return response - return self.redirect_to_guard_view() \ No newline at end of file + return self.redirect_to_guard_view() diff --git a/apps/authentication/views/mixins.py b/apps/authentication/views/mixins.py new file mode 100644 index 000000000..3571dfac7 --- /dev/null +++ b/apps/authentication/views/mixins.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# + +class METAMixin: + def get_next_url_from_meta(self): + request_meta = self.request.META or {} + next_url = None + referer = request_meta.get('HTTP_REFERER', '') + next_url_item = referer.rsplit('next=', 1) + if len(next_url_item) > 1: + next_url = next_url_item[-1] + return next_url diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index 87afd08ea..5546bf619 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -21,6 +21,7 @@ from common.utils.common import get_request_ip from authentication import errors from authentication.mixins import AuthMixin from authentication.notifications import OAuthBindMessage +from .mixins import METAMixin logger = get_logger(__file__) @@ -196,14 +197,17 @@ class WeComEnableStartView(UserVerifyPasswordView): return success_url -class WeComQRLoginView(WeComQRMixin, View): +class WeComQRLoginView(WeComQRMixin, METAMixin, View): permission_classes = (AllowAny,) def get(self, request: HttpRequest): redirect_url = request.GET.get('redirect_url') redirect_uri = reverse('authentication:wecom-qr-login-callback', external=True) - redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) + redirect_uri += '?' + urlencode({ + 'redirect_url': redirect_url, + 'next': self.get_next_url_from_meta() + }) url = self.get_qr_url(redirect_uri) return HttpResponseRedirect(url) @@ -301,4 +305,4 @@ class WeComOAuthLoginCallbackView(AuthMixin, WeComOAuthMixin, View): response = self.get_failed_response(login_url, title=msg, msg=msg) return response - return self.redirect_to_guard_view() \ No newline at end of file + return self.redirect_to_guard_view() diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 6a9ae69a9..be8c059ce 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -31,6 +31,7 @@ api_v1 = [ app_view_patterns = [ path('auth/', include('authentication.urls.view_urls'), name='auth'), path('ops/', include('ops.urls.view_urls'), name='ops'), + path('tickets/', include('tickets.urls.view_urls'), name='tickets'), path('common/', include('common.urls.view_urls'), name='common'), re_path(r'flower/(?P.*)', views.celery_flower_view, name='flower-view'), path('download/', views.ResourceDownload.as_view(), name='download'), diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo deleted file mode 100644 index 7feeaece9..000000000 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e70a491494af861945bde8a0b03c9b6e78dde7016446236ead362362b76b09a8 -size 125713 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a0b05f272..e26cb7538 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-29 12:49+0800\n" +"POT-Creation-Date: 2022-05-05 13:03+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1402,6 +1402,7 @@ msgid "Filename" msgstr "ファイル名" #: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90 +#: tickets/views/approve.py:93 #: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/asset.py:197 msgid "Success" @@ -1557,12 +1558,12 @@ msgid "Auth Token" msgstr "認証トークン" #: audits/signal_handlers.py:71 authentication/notifications.py:73 -#: authentication/views/login.py:164 authentication/views/wecom.py:181 +#: authentication/views/login.py:164 authentication/views/wecom.py:182 #: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企業微信" -#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182 +#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" @@ -1740,7 +1741,7 @@ msgstr "{ApplicationPermission} 追加 {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}" -#: authentication/api/connection_token.py:328 +#: authentication/api/connection_token.py:326 msgid "Invalid token" msgstr "無効なトークン" @@ -2184,6 +2185,7 @@ msgstr "コードエラー" #: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 +#: tickets/templates/tickets/approve_check_password.html:23 #: users/templates/users/_msg_account_expire_reminder.html:4 #: users/templates/users/_msg_password_expire_reminder.html:4 #: users/templates/users/_msg_reset_mfa.html:4 @@ -2323,54 +2325,54 @@ msgstr "返品" msgid "Copy success" msgstr "コピー成功" -#: authentication/views/dingtalk.py:39 +#: authentication/views/dingtalk.py:40 msgid "DingTalk Error, Please contact your system administrator" msgstr "DingTalkエラー、システム管理者に連絡してください" -#: authentication/views/dingtalk.py:42 +#: authentication/views/dingtalk.py:43 msgid "DingTalk Error" msgstr "DingTalkエラー" -#: authentication/views/dingtalk.py:54 authentication/views/feishu.py:50 -#: authentication/views/wecom.py:54 +#: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50 +#: authentication/views/wecom.py:55 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "システム設定が正しくありません。管理者に連絡してください" -#: authentication/views/dingtalk.py:78 +#: authentication/views/dingtalk.py:79 msgid "DingTalk is already bound" msgstr "DingTalkはすでにバインドされています" -#: authentication/views/dingtalk.py:127 authentication/views/feishu.py:99 -#: authentication/views/wecom.py:127 +#: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99 +#: authentication/views/wecom.py:128 msgid "Please verify your password first" msgstr "最初にパスワードを確認してください" -#: authentication/views/dingtalk.py:151 authentication/views/wecom.py:151 +#: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152 msgid "Invalid user_id" msgstr "無効なuser_id" -#: authentication/views/dingtalk.py:167 +#: authentication/views/dingtalk.py:168 msgid "DingTalk query user failed" msgstr "DingTalkクエリユーザーが失敗しました" -#: authentication/views/dingtalk.py:176 +#: authentication/views/dingtalk.py:177 msgid "The DingTalk is already bound to another user" msgstr "DingTalkはすでに別のユーザーにバインドされています" -#: authentication/views/dingtalk.py:183 +#: authentication/views/dingtalk.py:184 msgid "Binding DingTalk successfully" msgstr "DingTalkのバインドに成功" -#: authentication/views/dingtalk.py:235 authentication/views/dingtalk.py:289 +#: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293 msgid "Failed to get user from DingTalk" msgstr "DingTalkからユーザーを取得できませんでした" -#: authentication/views/dingtalk.py:241 authentication/views/dingtalk.py:295 +#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299 msgid "DingTalk is not bound" msgstr "DingTalkはバインドされていません" -#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296 +#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 msgid "Please login with a password and then bind the DingTalk" msgstr "パスワードでログインし、DingTalkをバインドしてください" @@ -2443,39 +2445,39 @@ msgstr "ログアウト成功" msgid "Logout success, return login page" msgstr "ログアウト成功、ログインページを返す" -#: authentication/views/wecom.py:39 +#: authentication/views/wecom.py:40 msgid "WeCom Error, Please contact your system administrator" msgstr "企業微信エラー、システム管理者に連絡してください" -#: authentication/views/wecom.py:42 +#: authentication/views/wecom.py:43 msgid "WeCom Error" msgstr "企業微信エラー" -#: authentication/views/wecom.py:78 +#: authentication/views/wecom.py:79 msgid "WeCom is already bound" msgstr "企業の微信はすでにバインドされています" -#: authentication/views/wecom.py:166 +#: authentication/views/wecom.py:167 msgid "WeCom query user failed" msgstr "企業微信ユーザーの問合せに失敗しました" -#: authentication/views/wecom.py:175 +#: authentication/views/wecom.py:176 msgid "The WeCom is already bound to another user" msgstr "この企業の微信はすでに他のユーザーをバインドしている。" -#: authentication/views/wecom.py:182 +#: authentication/views/wecom.py:183 msgid "Binding WeCom successfully" msgstr "企業の微信のバインドに成功" -#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 +#: authentication/views/wecom.py:235 authentication/views/wecom.py:289 msgid "Failed to get user from WeCom" msgstr "企業の微信からユーザーを取得できませんでした" -#: authentication/views/wecom.py:237 authentication/views/wecom.py:291 +#: authentication/views/wecom.py:241 authentication/views/wecom.py:295 msgid "WeCom is not bound" msgstr "企業の微信をバインドしていません" -#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 +#: authentication/views/wecom.py:242 authentication/views/wecom.py:296 msgid "Please login with a password and then bind the WeCom" msgstr "パスワードでログインしてからWeComをバインドしてください" @@ -4564,7 +4566,7 @@ msgstr "フィルター" #: terminal/api/endpoint.py:63 msgid "Not found protocol query params" -msgstr "" +msgstr "プロトコルクエリパラメータが見つかりません" #: terminal/api/session.py:210 msgid "Session does not exist: {}" @@ -5295,19 +5297,19 @@ msgstr "もう一度お試しください" msgid "Super ticket" msgstr "スーパーチケット" -#: tickets/notifications.py:63 +#: tickets/notifications.py:72 msgid "Your has a new ticket, applicant - {}" msgstr "新しいチケットがあります- {}" -#: tickets/notifications.py:69 +#: tickets/notifications.py:78 msgid "New Ticket - {} ({})" msgstr "新しいチケット- {} ({})" -#: tickets/notifications.py:91 +#: tickets/notifications.py:123 msgid "Your ticket has been processed, processor - {}" msgstr "チケットが処理されました。プロセッサー- {}" -#: tickets/notifications.py:95 +#: tickets/notifications.py:127 msgid "Ticket has processed - {} ({})" msgstr "チケットが処理済み- {} ({})" @@ -5444,10 +5446,55 @@ msgid "The current organization type already exists" msgstr "現在の組織タイプは既に存在します。" #: tickets/templates/tickets/_base_ticket_body.html:17 -#: tickets/templates/tickets/_msg_ticket.html:12 msgid "Click here to review" msgstr "こちらをクリックしてご覧ください" +#: tickets/templates/tickets/_msg_ticket.html:12 +msgid "View details" +msgstr "詳細の表示" + +#: tickets/templates/tickets/_msg_ticket.html:17 +msgid "Direct approval" +msgstr "直接承認" + +#: tickets/templates/tickets/approve_check_password.html:11 +msgid "Ticket information" +msgstr "作業指示情報" + +#: tickets/templates/tickets/approve_check_password.html:19 +#, fuzzy +#| msgid "Ticket flow approval rule" +msgid "Ticket approval" +msgstr "作業指示の承認" + +#: tickets/templates/tickets/approve_check_password.html:35 +#: tickets/views/approve.py:25 +msgid "Ticket direct approval" +msgstr "作業指示の直接承認" + +#: tickets/templates/tickets/approve_check_password.html:40 +msgid "Go Login" +msgstr "ログイン" + +#: tickets/views/approve.py:26 +msgid "" +"This ticket does not exist, the process has ended, or this link has expired" +msgstr "" +"このワークシートが存在しないか、ワークシートが終了したか、このリンクが無効に" +"なっています" + +#: tickets/views/approve.py:55 +msgid "Click the button to approve directly" +msgstr "ボタンをクリックすると、直接承認に成功します。" + +#: tickets/views/approve.py:57 +msgid "After successful authentication, this ticket can be approved directly" +msgstr "認証に成功した後、作業指示書は直接承認することができる。" + +#: tickets/views/approve.py:84 +msgid "This user is not authorized to approve this ticket" +msgstr "このユーザーはこの作業指示を承認する権限がありません" + #: users/api/user.py:183 msgid "Could not reset self otp, use profile reset instead" msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo deleted file mode 100644 index c651fb2c5..000000000 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:95e9f6addbdb6811647fd2bb5ae64bfc2572a80702c371eab0a1bb041a1e8476 -size 104032 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index e091a35c8..e362871c6 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-29 12:49+0800\n" +"POT-Creation-Date: 2022-05-05 13:03+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -1390,6 +1390,7 @@ msgid "Filename" msgstr "文件名" #: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90 +#: tickets/views/approve.py:93 #: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/asset.py:197 msgid "Success" @@ -1545,12 +1546,12 @@ msgid "Auth Token" msgstr "认证令牌" #: audits/signal_handlers.py:71 authentication/notifications.py:73 -#: authentication/views/login.py:164 authentication/views/wecom.py:181 +#: authentication/views/login.py:164 authentication/views/wecom.py:182 #: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企业微信" -#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182 +#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" @@ -1728,7 +1729,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" -#: authentication/api/connection_token.py:328 +#: authentication/api/connection_token.py:326 msgid "Invalid token" msgstr "无效的令牌" @@ -2163,6 +2164,7 @@ msgstr "代码错误" #: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 +#: tickets/templates/tickets/approve_check_password.html:23 #: users/templates/users/_msg_account_expire_reminder.html:4 #: users/templates/users/_msg_password_expire_reminder.html:4 #: users/templates/users/_msg_reset_mfa.html:4 @@ -2293,54 +2295,54 @@ msgstr "返回" msgid "Copy success" msgstr "复制成功" -#: authentication/views/dingtalk.py:39 +#: authentication/views/dingtalk.py:40 msgid "DingTalk Error, Please contact your system administrator" msgstr "钉钉错误,请联系系统管理员" -#: authentication/views/dingtalk.py:42 +#: authentication/views/dingtalk.py:43 msgid "DingTalk Error" msgstr "钉钉错误" -#: authentication/views/dingtalk.py:54 authentication/views/feishu.py:50 -#: authentication/views/wecom.py:54 +#: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50 +#: authentication/views/wecom.py:55 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "企业配置错误,请联系系统管理员" -#: authentication/views/dingtalk.py:78 +#: authentication/views/dingtalk.py:79 msgid "DingTalk is already bound" msgstr "钉钉已经绑定" -#: authentication/views/dingtalk.py:127 authentication/views/feishu.py:99 -#: authentication/views/wecom.py:127 +#: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99 +#: authentication/views/wecom.py:128 msgid "Please verify your password first" msgstr "请检查密码" -#: authentication/views/dingtalk.py:151 authentication/views/wecom.py:151 +#: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152 msgid "Invalid user_id" msgstr "无效的 user_id" -#: authentication/views/dingtalk.py:167 +#: authentication/views/dingtalk.py:168 msgid "DingTalk query user failed" msgstr "钉钉查询用户失败" -#: authentication/views/dingtalk.py:176 +#: authentication/views/dingtalk.py:177 msgid "The DingTalk is already bound to another user" msgstr "该钉钉已经绑定其他用户" -#: authentication/views/dingtalk.py:183 +#: authentication/views/dingtalk.py:184 msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:235 authentication/views/dingtalk.py:289 +#: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:241 authentication/views/dingtalk.py:295 +#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" -#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296 +#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 msgid "Please login with a password and then bind the DingTalk" msgstr "请使用密码登录,然后绑定钉钉" @@ -2413,39 +2415,39 @@ msgstr "退出登录成功" msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: authentication/views/wecom.py:39 +#: authentication/views/wecom.py:40 msgid "WeCom Error, Please contact your system administrator" msgstr "企业微信错误,请联系系统管理员" -#: authentication/views/wecom.py:42 +#: authentication/views/wecom.py:43 msgid "WeCom Error" msgstr "企业微信错误" -#: authentication/views/wecom.py:78 +#: authentication/views/wecom.py:79 msgid "WeCom is already bound" msgstr "企业微信已经绑定" -#: authentication/views/wecom.py:166 +#: authentication/views/wecom.py:167 msgid "WeCom query user failed" msgstr "企业微信查询用户失败" -#: authentication/views/wecom.py:175 +#: authentication/views/wecom.py:176 msgid "The WeCom is already bound to another user" msgstr "该企业微信已经绑定其他用户" -#: authentication/views/wecom.py:182 +#: authentication/views/wecom.py:183 msgid "Binding WeCom successfully" msgstr "绑定 企业微信 成功" -#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 +#: authentication/views/wecom.py:235 authentication/views/wecom.py:289 msgid "Failed to get user from WeCom" msgstr "从企业微信获取用户失败" -#: authentication/views/wecom.py:237 authentication/views/wecom.py:291 +#: authentication/views/wecom.py:241 authentication/views/wecom.py:295 msgid "WeCom is not bound" msgstr "没有绑定企业微信" -#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 +#: authentication/views/wecom.py:242 authentication/views/wecom.py:296 msgid "Please login with a password and then bind the WeCom" msgstr "请使用密码登录,然后绑定企业微信" @@ -5221,19 +5223,19 @@ msgstr "请再次尝试" msgid "Super ticket" msgstr "超级工单" -#: tickets/notifications.py:63 +#: tickets/notifications.py:72 msgid "Your has a new ticket, applicant - {}" msgstr "你有一个新的工单, 申请人 - {}" -#: tickets/notifications.py:69 +#: tickets/notifications.py:78 msgid "New Ticket - {} ({})" msgstr "新工单 - {} ({})" -#: tickets/notifications.py:91 +#: tickets/notifications.py:123 msgid "Your ticket has been processed, processor - {}" msgstr "你的工单已被处理, 处理人 - {}" -#: tickets/notifications.py:95 +#: tickets/notifications.py:127 msgid "Ticket has processed - {} ({})" msgstr "你的工单已被处理, 处理人 - {} ({})" @@ -5368,10 +5370,51 @@ msgid "The current organization type already exists" msgstr "当前组织已存在该类型" #: tickets/templates/tickets/_base_ticket_body.html:17 -#: tickets/templates/tickets/_msg_ticket.html:12 msgid "Click here to review" msgstr "点击查看" +#: tickets/templates/tickets/_msg_ticket.html:12 +msgid "View details" +msgstr "查看详情" + +#: tickets/templates/tickets/_msg_ticket.html:17 +msgid "Direct approval" +msgstr "直接批准" + +#: tickets/templates/tickets/approve_check_password.html:11 +msgid "Ticket information" +msgstr "工单信息" + +#: tickets/templates/tickets/approve_check_password.html:19 +msgid "Ticket approval" +msgstr "工单审批" + +#: tickets/templates/tickets/approve_check_password.html:35 +#: tickets/views/approve.py:25 +msgid "Ticket direct approval" +msgstr "工单直接审批" + +#: tickets/templates/tickets/approve_check_password.html:40 +msgid "Go Login" +msgstr "去登录" + +#: tickets/views/approve.py:26 +msgid "" +"This ticket does not exist, the process has ended, or this link has expired" +msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期" + +#: tickets/views/approve.py:55 +msgid "Click the button to approve directly" +msgstr "点击按钮,即可直接审批成功" + +#: tickets/views/approve.py:57 +msgid "After successful authentication, this ticket can be approved directly" +msgstr "认证成功后,工单可直接审批" + +#: tickets/views/approve.py:84 +msgid "This user is not authorized to approve this ticket" +msgstr "此用户无权审批此工单" + #: users/api/user.py:183 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置" diff --git a/apps/templates/_base_double_screen.html b/apps/templates/_base_double_screen.html new file mode 100644 index 000000000..cd610fea5 --- /dev/null +++ b/apps/templates/_base_double_screen.html @@ -0,0 +1,42 @@ +{% load static %} +{% load i18n %} + + + + + + + + {% block html_title %}{% endblock %} + + {% include '_head_css_js.html' %} + + + + {% block custom_head_css_js %} {% endblock %} + + + +
    +
    +
    + {% block content %} {% endblock %} +
    +
    +
    +
    +
    + {% include '_copyright.html' %} +
    +
    +
    + +{% include '_foot_js.html' %} +{% block custom_foot_js %} {% endblock %} + diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index ffca227be..c29c8d00e 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -1,13 +1,15 @@ from urllib.parse import urljoin from django.conf import settings +from django.core.cache import cache +from django.shortcuts import reverse from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ -from . import const from notifications.notifications import UserMessage -from common.utils import get_logger +from common.utils import get_logger, random_string from .models import Ticket +from . import const logger = get_logger(__file__) @@ -57,6 +59,13 @@ class TicketAppliedToAssignee(BaseTicketMessage): def __init__(self, user, ticket): self.ticket = ticket super().__init__(user) + self._token = None + + @property + def token(self): + if self._token is None: + self._token = random_string(32) + return self._token @property def content_title(self): @@ -71,6 +80,29 @@ class TicketAppliedToAssignee(BaseTicketMessage): ) return title + def get_ticket_approval_url(self): + url = reverse('tickets:direct-approve', kwargs={'token': self.token}) + return urljoin(settings.SITE_URL, url) + + def get_html_msg(self) -> dict: + body = self.ticket.body.replace('\n', '
    ') + context = dict( + title=self.content_title, + ticket_detail_url=self.ticket_detail_url, + body=body, + ) + + ticket_approval_url = self.get_ticket_approval_url() + context.update({'ticket_approval_url': ticket_approval_url}) + message = render_to_string('tickets/_msg_ticket.html', context) + cache.set(self.token, { + 'body': body, 'ticket_id': self.ticket.id + }, 3600) + return { + 'subject': self.subject, + 'message': message + } + @classmethod def gen_test_msg(cls): from .models import Ticket diff --git a/apps/tickets/templates/tickets/_msg_ticket.html b/apps/tickets/templates/tickets/_msg_ticket.html index 15e2b2a3f..5f268592c 100644 --- a/apps/tickets/templates/tickets/_msg_ticket.html +++ b/apps/tickets/templates/tickets/_msg_ticket.html @@ -9,7 +9,13 @@
    - \ No newline at end of file + diff --git a/apps/tickets/templates/tickets/approve_check_password.html b/apps/tickets/templates/tickets/approve_check_password.html new file mode 100644 index 000000000..66ee91e1b --- /dev/null +++ b/apps/tickets/templates/tickets/approve_check_password.html @@ -0,0 +1,52 @@ +{% extends '_base_double_screen.html' %} +{% load bootstrap3 %} +{% load static %} +{% load i18n %} + + +{% block content %} +
    +
    +
    +

    {% trans 'Ticket information' %}

    +

    +
    {{ ticket_info | safe }}
    +
    +
    +
    +
    + +

    {% trans 'Ticket approval' %}

    +

    +
    +

    + {% trans 'Hello' %} {{ user.name }}, +

    +

    + {{ prompt_msg }} +

    +
    +
    + {% csrf_token %} +
    + {% if user.is_authenticated %} + + {% else %} + + {% trans 'Go Login' %} + + {% endif %} +
    +
    +
    +
    +
    +
    + +{% endblock %} diff --git a/apps/tickets/urls/view_urls.py b/apps/tickets/urls/view_urls.py new file mode 100644 index 000000000..f9fcaab84 --- /dev/null +++ b/apps/tickets/urls/view_urls.py @@ -0,0 +1,12 @@ +# coding:utf-8 +# + +from django.urls import path + +from .. import views + +app_name = 'tickets' + +urlpatterns = [ + path('direct-approve//', views.TicketDirectApproveView.as_view(), name='direct-approve'), +] diff --git a/apps/tickets/views/__init__.py b/apps/tickets/views/__init__.py new file mode 100644 index 000000000..ece19ff38 --- /dev/null +++ b/apps/tickets/views/__init__.py @@ -0,0 +1 @@ +from .approve import * diff --git a/apps/tickets/views/approve.py b/apps/tickets/views/approve.py new file mode 100644 index 000000000..8c014c425 --- /dev/null +++ b/apps/tickets/views/approve.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# + +from __future__ import unicode_literals +from django.views.generic.base import TemplateView +from django.shortcuts import redirect, reverse +from django.core.cache import cache +from django.utils.translation import ugettext as _ + +from tickets.models import Ticket +from tickets.errors import AlreadyClosed +from common.utils import get_logger, FlashMessageUtil + +logger = get_logger(__name__) +__all__ = ['TicketDirectApproveView'] + + +class TicketDirectApproveView(TemplateView): + template_name = 'tickets/approve_check_password.html' + redirect_field_name = 'next' + + @property + def message_data(self): + return { + 'title': _('Ticket direct approval'), + 'error': _("This ticket does not exist, " + "the process has ended, or this link has expired"), + 'redirect_url': self.login_url, + 'auto_redirect': False + } + + @property + def login_url(self): + return reverse('authentication:login') + '?admin=1' + + def redirect_message_response(self, **kwargs): + message_data = self.message_data + for key, value in kwargs.items(): + if isinstance(value, str): + message_data[key] = value + if message_data.get('message'): + message_data.pop('error') + redirect_url = FlashMessageUtil.gen_message_url(message_data) + return redirect(redirect_url) + + @staticmethod + def clear(token): + cache.delete(token) + + def get_context_data(self, **kwargs): + # 放入工单信息 + token = kwargs.get('token') + ticket_info = cache.get(token, {}).get('body', '') + if self.request.user.is_authenticated: + prompt_msg = _('Click the button to approve directly') + else: + prompt_msg = _('After successful authentication, this ticket can be approved directly') + kwargs.update({ + 'ticket_info': ticket_info, 'prompt_msg': prompt_msg, + 'login_url': '%s&next=%s' % ( + self.login_url, + reverse('tickets:direct-approve', kwargs={'token': token}) + ), + }) + return super().get_context_data(**kwargs) + + def get(self, request, *args, **kwargs): + token = kwargs.get('token') + ticket_info = cache.get(token) + if not ticket_info: + return self.redirect_message_response(redirect_url=self.login_url) + return super().get(request, *args, **kwargs) + + def post(self, request, **kwargs): + user = request.user + token = kwargs.get('token') + ticket_info = cache.get(token) + if not ticket_info: + return self.redirect_message_response(redirect_url=self.login_url) + try: + ticket_id = ticket_info.get('ticket_id') + ticket = Ticket.all().get(id=ticket_id) + if not ticket.has_current_assignee(user): + raise Exception(_("This user is not authorized to approve this ticket")) + ticket.approve(user) + except AlreadyClosed as e: + self.clear(token) + return self.redirect_message_response(error=str(e), redirect_url=self.login_url) + except Exception as e: + return self.redirect_message_response(error=str(e), redirect_url=self.login_url) + + self.clear(token) + return self.redirect_message_response(message=_("Success"), redirect_url=self.login_url) From e1515487016fe8b569f7c531ac53c14e20f31e40 Mon Sep 17 00:00:00 2001 From: jiangweidong <80373698+F2C-Jiang@users.noreply.github.com> Date: Thu, 5 May 2022 14:42:09 +0800 Subject: [PATCH 081/258] =?UTF-8?q?perf:=20=E8=B4=A6=E5=8F=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E4=B8=AD=E6=9F=A5=E7=9C=8B=E5=AF=86=E7=A0=81=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=97=A5=E5=BF=97=20(#8157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/account.py | 3 +- apps/assets/api/accounts.py | 3 +- apps/audits/const.py | 20 ++++ apps/audits/models.py | 2 + apps/audits/signal_handlers.py | 44 +------- apps/audits/utils.py | 36 +++++- apps/common/mixins/views.py | 37 ++++++- apps/locale/ja/LC_MESSAGES/django.po | 159 +++++++++++++++------------ apps/locale/zh/LC_MESSAGES/django.po | 147 ++++++++++++++----------- 9 files changed, 263 insertions(+), 188 deletions(-) diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py index 4e66a730c..0a1581b28 100644 --- a/apps/applications/api/account.py +++ b/apps/applications/api/account.py @@ -6,6 +6,7 @@ from django.db.models import F, Q from common.drf.filters import BaseFilterSet from common.drf.api import JMSBulkModelViewSet +from common.mixins import RecordViewLogMixin from rbac.permissions import RBACPermission from assets.models import SystemUser from ..models import Account @@ -54,7 +55,7 @@ class SystemUserAppRelationViewSet(ApplicationAccountViewSet): perm_model = SystemUser -class ApplicationAccountSecretViewSet(ApplicationAccountViewSet): +class ApplicationAccountSecretViewSet(RecordViewLogMixin, ApplicationAccountViewSet): serializer_class = serializers.AppAccountSecretSerializer permission_classes = [RBACPermission, NeedMFAVerify] http_method_names = ['get', 'options'] diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index 9989599b8..4a3629e46 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -8,6 +8,7 @@ from rest_framework.generics import CreateAPIView from orgs.mixins.api import OrgBulkModelViewSet from rbac.permissions import RBACPermission from common.drf.filters import BaseFilterSet +from common.mixins import RecordViewLogMixin from common.permissions import NeedMFAVerify from ..tasks.account_connectivity import test_accounts_connectivity_manual from ..models import AuthBook, Node @@ -79,7 +80,7 @@ class AccountViewSet(OrgBulkModelViewSet): return Response(data={'task': task.id}) -class AccountSecretsViewSet(AccountViewSet): +class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): """ 因为可能要导出所有账号,所以单独建立了一个 viewset """ diff --git a/apps/audits/const.py b/apps/audits/const.py index 97f6cc6b1..9ff993556 100644 --- a/apps/audits/const.py +++ b/apps/audits/const.py @@ -3,3 +3,23 @@ from django.utils.translation import ugettext_lazy as _ DEFAULT_CITY = _("Unknown") + +MODELS_NEED_RECORD = ( + # users + 'User', 'UserGroup', + # acls + 'LoginACL', 'LoginAssetACL', 'LoginConfirmSetting', + # assets + 'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', + 'CommandFilter', 'Platform', 'AuthBook', + # applications + 'Application', + # orgs + 'Organization', + # settings + 'Setting', + # perms + 'AssetPermission', 'ApplicationPermission', + # xpack + 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask', +) diff --git a/apps/audits/models.py b/apps/audits/models.py index 0cd17ede2..5dd8eb0a2 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -49,10 +49,12 @@ class FTPLog(OrgModelMixin): class OperateLog(OrgModelMixin): ACTION_CREATE = 'create' + ACTION_VIEW = 'view' ACTION_UPDATE = 'update' ACTION_DELETE = 'delete' ACTION_CHOICES = ( (ACTION_CREATE, _("Create")), + (ACTION_VIEW, _("View")), (ACTION_UPDATE, _("Update")), (ACTION_DELETE, _("Delete")) ) diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 031b1e25c..5177cfca7 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -21,7 +21,7 @@ from jumpserver.utils import current_request from users.models import User from users.signals import post_user_change_password from terminal.models import Session, Command -from .utils import write_login_log +from .utils import write_login_log, create_operate_log from . import models, serializers from .models import OperateLog from orgs.utils import current_org @@ -36,26 +36,6 @@ logger = get_logger(__name__) sys_logger = get_syslogger(__name__) json_render = JSONRenderer() -MODELS_NEED_RECORD = ( - # users - 'User', 'UserGroup', - # acls - 'LoginACL', 'LoginAssetACL', 'LoginConfirmSetting', - # assets - 'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', - 'CommandFilter', 'Platform', 'AuthBook', - # applications - 'Application', - # orgs - 'Organization', - # settings - 'Setting', - # perms - 'AssetPermission', 'ApplicationPermission', - # xpack - 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask', -) - class AuthBackendLabelMapping(LazyObject): @staticmethod @@ -80,28 +60,6 @@ class AuthBackendLabelMapping(LazyObject): AUTH_BACKEND_LABEL_MAPPING = AuthBackendLabelMapping() -def create_operate_log(action, sender, resource): - user = current_request.user if current_request else None - if not user or not user.is_authenticated: - return - model_name = sender._meta.object_name - if model_name not in MODELS_NEED_RECORD: - return - with translation.override('en'): - resource_type = sender._meta.verbose_name - remote_addr = get_request_ip(current_request) - - data = { - "user": str(user), 'action': action, 'resource_type': resource_type, - 'resource': str(resource), 'remote_addr': remote_addr, - } - with transaction.atomic(): - try: - models.OperateLog.objects.create(**data) - except Exception as e: - logger.error("Create operate log error: {}".format(e)) - - M2M_NEED_RECORD = { User.groups.through._meta.object_name: ( _('User and Group'), diff --git a/apps/audits/utils.py b/apps/audits/utils.py index e569a7ebb..1fadbccee 100644 --- a/apps/audits/utils.py +++ b/apps/audits/utils.py @@ -1,9 +1,17 @@ import csv import codecs -from django.http import HttpResponse -from .const import DEFAULT_CITY -from common.utils import validate_ip, get_ip_city +from django.http import HttpResponse +from django.db import transaction +from django.utils import translation + +from audits.models import OperateLog +from common.utils import validate_ip, get_ip_city, get_request_ip, get_logger +from jumpserver.utils import current_request +from .const import DEFAULT_CITY, MODELS_NEED_RECORD + + +logger = get_logger(__name__) def get_excel_response(filename): @@ -36,3 +44,25 @@ def write_login_log(*args, **kwargs): city = get_ip_city(ip) or DEFAULT_CITY kwargs.update({'ip': ip, 'city': city}) UserLoginLog.objects.create(**kwargs) + + +def create_operate_log(action, sender, resource): + user = current_request.user if current_request else None + if not user or not user.is_authenticated: + return + model_name = sender._meta.object_name + if model_name not in MODELS_NEED_RECORD: + return + with translation.override('en'): + resource_type = sender._meta.verbose_name + remote_addr = get_request_ip(current_request) + + data = { + "user": str(user), 'action': action, 'resource_type': resource_type, + 'resource': str(resource), 'remote_addr': remote_addr, + } + with transaction.atomic(): + try: + OperateLog.objects.create(**data) + except Exception as e: + logger.error("Create operate log error: {}".format(e)) diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index 4fc1dbf3c..0028ccf0a 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.mixins import UserPassesTestMixin from rest_framework import permissions from rest_framework.decorators import action @@ -7,8 +8,10 @@ from rest_framework.request import Request from rest_framework.response import Response from common.permissions import IsValidUser +from audits.utils import create_operate_log +from audits.models import OperateLog -__all__ = ["PermissionsMixin"] +__all__ = ["PermissionsMixin", "RecordViewLogMixin"] class PermissionsMixin(UserPassesTestMixin): @@ -24,3 +27,35 @@ class PermissionsMixin(UserPassesTestMixin): if not permission_class().has_permission(self.request, self): return False return True + + +class RecordViewLogMixin: + ACTION = OperateLog.ACTION_VIEW + + @staticmethod + def get_resource_display(request): + query_params = dict(request.query_params) + if query_params.get('format'): + query_params.pop('format') + spm_filter = query_params.pop('spm') if query_params.get('spm') else None + if not query_params and not spm_filter: + display_message = _('Export all') + elif spm_filter: + display_message = _('Export only selected items') + else: + query = ','.join( + ['%s=%s' % (key, value) for key, value in query_params.items()] + ) + display_message = _('Export filtered: %s') % query + return display_message + + def list(self, request, *args, **kwargs): + response = super().list(request, *args, **kwargs) + resource = self.get_resource_display(request) + create_operate_log(self.ACTION, self.model, resource) + return response + + def retrieve(self, request, *args, **kwargs): + response = super().retrieve(request, *args, **kwargs) + create_operate_log(self.ACTION, self.model, self.get_object()) + return response diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index e26cb7538..b23c9cde9 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -86,7 +86,7 @@ msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 +#: audits/models.py:62 audits/models.py:87 audits/serializers.py:100 #: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 #: rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 @@ -103,7 +103,7 @@ msgstr "ルール" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 #: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 -#: assets/models/cmd_filter.py:89 audits/models.py:61 audits/serializers.py:51 +#: assets/models/cmd_filter.py:89 audits/models.py:63 audits/serializers.py:51 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "アクション" @@ -152,7 +152,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 -#: assets/models/gathered_user.py:15 audits/models.py:119 +#: assets/models/gathered_user.py:15 audits/models.py:121 #: authentication/forms.py:15 authentication/forms.py:17 #: authentication/models.py:69 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -711,7 +711,7 @@ msgstr "アカウントのバックアップスナップショット" msgid "Trigger mode" msgstr "トリガーモード" -#: assets/models/backup.py:119 audits/models.py:125 +#: assets/models/backup.py:119 audits/models.py:127 #: terminal/models/sharing.py:94 #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 @@ -741,7 +741,7 @@ msgstr "不明" msgid "Ok" msgstr "OK" -#: assets/models/base.py:32 audits/models.py:116 +#: assets/models/base.py:32 audits/models.py:118 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 #: xpack/plugins/cloud/const.py:31 @@ -756,7 +756,7 @@ msgstr "接続性" msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:177 audits/signal_handlers.py:68 +#: assets/models/base.py:177 audits/signal_handlers.py:48 #: authentication/forms.py:22 #: authentication/templates/authentication/login.html:181 #: settings/serializers/auth/ldap.py:43 users/forms/profile.py:21 @@ -1358,7 +1358,7 @@ msgstr "一致する資産がない、タスクを停止" msgid "Audits" msgstr "監査" -#: audits/models.py:27 audits/models.py:57 +#: audits/models.py:27 audits/models.py:59 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:228 msgid "Delete" @@ -1388,7 +1388,7 @@ msgstr "Mkdir" msgid "Symlink" msgstr "Symlink" -#: audits/models.py:38 audits/models.py:64 audits/models.py:87 +#: audits/models.py:38 audits/models.py:66 audits/models.py:89 #: terminal/models/session.py:51 terminal/models/sharing.py:82 msgid "Remote addr" msgstr "リモートaddr" @@ -1412,95 +1412,100 @@ msgstr "成功" msgid "File transfer log" msgstr "ファイル転送ログ" -#: audits/models.py:55 +#: audits/models.py:56 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:225 msgid "Create" msgstr "作成" #: audits/models.py:56 rbac/tree.py:227 templates/_csv_import_export.html:18 +#: audits/models.py:57 rbac/tree.py:226 +msgid "View" +msgstr "表示" + +#: audits/models.py:58 rbac/tree.py:227 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" -#: audits/models.py:62 audits/serializers.py:63 +#: audits/models.py:64 audits/serializers.py:63 msgid "Resource Type" msgstr "リソースタイプ" -#: audits/models.py:63 +#: audits/models.py:65 msgid "Resource" msgstr "リソース" -#: audits/models.py:65 audits/models.py:88 +#: audits/models.py:67 audits/models.py:90 #: terminal/backends/command/serializers.py:39 msgid "Datetime" msgstr "時間" -#: audits/models.py:80 +#: audits/models.py:82 msgid "Operate log" msgstr "ログの操作" -#: audits/models.py:86 +#: audits/models.py:88 msgid "Change by" msgstr "による変更" -#: audits/models.py:94 +#: audits/models.py:96 msgid "Password change log" msgstr "パスワード変更ログ" -#: audits/models.py:109 +#: audits/models.py:111 msgid "Disabled" msgstr "無効" -#: audits/models.py:110 settings/models.py:33 +#: audits/models.py:112 settings/models.py:33 msgid "Enabled" msgstr "有効化" -#: audits/models.py:111 +#: audits/models.py:113 msgid "-" msgstr "-" -#: audits/models.py:120 +#: audits/models.py:122 msgid "Login type" msgstr "ログインタイプ" -#: audits/models.py:121 +#: audits/models.py:123 #: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:14 msgid "Login ip" msgstr "ログインIP" -#: audits/models.py:122 +#: audits/models.py:124 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:17 msgid "Login city" msgstr "ログイン都市" -#: audits/models.py:123 audits/serializers.py:44 +#: audits/models.py:125 audits/serializers.py:44 msgid "User agent" msgstr "ユーザーエージェント" -#: audits/models.py:124 +#: audits/models.py:126 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:64 users/models/user.py:684 #: users/serializers/profile.py:121 msgid "MFA" msgstr "MFA" -#: audits/models.py:126 terminal/models/status.py:33 +#: audits/models.py:128 terminal/models/status.py:33 #: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 #: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "ステータス" -#: audits/models.py:127 +#: audits/models.py:129 msgid "Date login" msgstr "日付ログイン" -#: audits/models.py:128 audits/serializers.py:46 +#: audits/models.py:130 audits/serializers.py:46 msgid "Authentication backend" msgstr "認証バックエンド" -#: audits/models.py:167 +#: audits/models.py:169 msgid "User login log" msgstr "ユーザーログインログ" @@ -1545,20 +1550,21 @@ msgstr "ディスプレイとして実行する" msgid "User display" msgstr "ユーザー表示" -#: audits/signal_handlers.py:67 +#: audits/signal_handlers.py:47 msgid "SSH Key" msgstr "SSHキー" -#: audits/signal_handlers.py:69 +#: audits/signal_handlers.py:49 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:70 +#: audits/signal_handlers.py:50 msgid "Auth Token" msgstr "認証トークン" #: audits/signal_handlers.py:71 authentication/notifications.py:73 -#: authentication/views/login.py:164 authentication/views/wecom.py:182 +#: audits/signal_handlers.py:51 authentication/notifications.py:73 +#: authentication/views/login.py:164 authentication/views/wecom.py:181 #: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企業微信" @@ -1569,174 +1575,174 @@ msgstr "企業微信" msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:73 authentication/models.py:76 +#: audits/signal_handlers.py:53 authentication/models.py:76 msgid "Temporary token" msgstr "仮パスワード" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:65 msgid "User and Group" msgstr "ユーザーとグループ" -#: audits/signal_handlers.py:108 +#: audits/signal_handlers.py:66 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} に参加 {UserGroup}" -#: audits/signal_handlers.py:109 +#: audits/signal_handlers.py:67 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} のそばを通る {UserGroup}" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:70 msgid "Asset and SystemUser" msgstr "資産およびシステム・ユーザー" -#: audits/signal_handlers.py:113 +#: audits/signal_handlers.py:71 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 追加 {SystemUser}" -#: audits/signal_handlers.py:114 +#: audits/signal_handlers.py:72 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 削除 {SystemUser}" -#: audits/signal_handlers.py:117 +#: audits/signal_handlers.py:75 msgid "Node and Asset" msgstr "ノードと資産" -#: audits/signal_handlers.py:118 +#: audits/signal_handlers.py:76 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 追加 {Asset}" -#: audits/signal_handlers.py:119 +#: audits/signal_handlers.py:77 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 削除 {Asset}" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:80 msgid "User asset permissions" msgstr "ユーザー資産の権限" -#: audits/signal_handlers.py:123 +#: audits/signal_handlers.py:81 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 追加 {User}" -#: audits/signal_handlers.py:124 +#: audits/signal_handlers.py:82 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 削除 {User}" -#: audits/signal_handlers.py:127 +#: audits/signal_handlers.py:85 msgid "User group asset permissions" msgstr "ユーザーグループの資産権限" -#: audits/signal_handlers.py:128 +#: audits/signal_handlers.py:86 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:129 +#: audits/signal_handlers.py:87 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:132 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:90 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "資産権限" -#: audits/signal_handlers.py:133 +#: audits/signal_handlers.py:91 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 追加 {Asset}" -#: audits/signal_handlers.py:134 +#: audits/signal_handlers.py:92 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 削除 {Asset}" -#: audits/signal_handlers.py:137 +#: audits/signal_handlers.py:95 msgid "Node permission" msgstr "ノード権限" -#: audits/signal_handlers.py:138 +#: audits/signal_handlers.py:96 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 追加 {Node}" -#: audits/signal_handlers.py:139 +#: audits/signal_handlers.py:97 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 削除 {Node}" -#: audits/signal_handlers.py:142 +#: audits/signal_handlers.py:100 msgid "Asset permission and SystemUser" msgstr "資産権限とSystemUser" -#: audits/signal_handlers.py:143 +#: audits/signal_handlers.py:101 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:144 +#: audits/signal_handlers.py:102 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 削除 {SystemUser}" -#: audits/signal_handlers.py:147 +#: audits/signal_handlers.py:105 msgid "User application permissions" msgstr "ユーザーアプリケーションの権限" -#: audits/signal_handlers.py:148 +#: audits/signal_handlers.py:106 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 追加 {User}" -#: audits/signal_handlers.py:149 +#: audits/signal_handlers.py:107 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 削除 {User}" -#: audits/signal_handlers.py:152 +#: audits/signal_handlers.py:110 msgid "User group application permissions" msgstr "ユーザーグループアプリケーションの権限" -#: audits/signal_handlers.py:153 +#: audits/signal_handlers.py:111 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:154 +#: audits/signal_handlers.py:112 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:157 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:115 perms/models/application_permission.py:38 msgid "Application permission" msgstr "申請許可" -#: audits/signal_handlers.py:158 +#: audits/signal_handlers.py:116 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 追加 {Application}" -#: audits/signal_handlers.py:159 +#: audits/signal_handlers.py:117 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 削除 {Application}" -#: audits/signal_handlers.py:162 +#: audits/signal_handlers.py:120 msgid "Application permission and SystemUser" msgstr "アプリケーション権限とSystemUser" -#: audits/signal_handlers.py:163 +#: audits/signal_handlers.py:121 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:164 +#: audits/signal_handlers.py:122 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}" @@ -2580,6 +2586,19 @@ msgstr "は破棄されます" msgid "discard time" msgstr "時間を捨てる" +#: common/mixins/views.py:41 +msgid "Export all" +msgstr "すべてエクスポート" + +#: common/mixins/views.py:43 +msgid "Export only selected items" +msgstr "選択項目のみエクスポート" + +#: common/mixins/views.py:48 +#, python-format +msgid "Export filtered: %s" +msgstr "検索のエクスポート: %s" + #: common/sdk/im/exceptions.py:23 msgid "Network error, please contact system administrator" msgstr "ネットワークエラー、システム管理者に連絡してください" @@ -3330,10 +3349,6 @@ msgstr "権限ツリーの表示" msgid "Execute batch command" msgstr "バッチ実行コマンド" -#: rbac/tree.py:226 -msgid "View" -msgstr "表示" - #: settings/api/alibaba_sms.py:31 settings/api/tencent_sms.py:35 msgid "test_phone is required" msgstr "携帯番号をテストこのフィールドは必須です" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index e362871c6..bc6f0a6ae 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -85,7 +85,7 @@ msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 +#: audits/models.py:62 audits/models.py:87 audits/serializers.py:100 #: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 #: rbac/builtin.py:118 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 @@ -102,7 +102,7 @@ msgstr "规则" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 #: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 -#: assets/models/cmd_filter.py:89 audits/models.py:61 audits/serializers.py:51 +#: assets/models/cmd_filter.py:89 audits/models.py:63 audits/serializers.py:51 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "动作" @@ -151,7 +151,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 -#: assets/models/gathered_user.py:15 audits/models.py:119 +#: assets/models/gathered_user.py:15 audits/models.py:121 #: authentication/forms.py:15 authentication/forms.py:17 #: authentication/models.py:69 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -706,7 +706,7 @@ msgstr "账号备份快照" msgid "Trigger mode" msgstr "触发模式" -#: assets/models/backup.py:119 audits/models.py:125 +#: assets/models/backup.py:119 audits/models.py:127 #: terminal/models/sharing.py:94 #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 @@ -736,7 +736,7 @@ msgstr "未知" msgid "Ok" msgstr "成功" -#: assets/models/base.py:32 audits/models.py:116 +#: assets/models/base.py:32 audits/models.py:118 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 #: xpack/plugins/cloud/const.py:31 @@ -751,7 +751,7 @@ msgstr "可连接性" msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:177 audits/signal_handlers.py:68 +#: assets/models/base.py:177 audits/signal_handlers.py:48 #: authentication/forms.py:22 #: authentication/templates/authentication/login.html:181 #: settings/serializers/auth/ldap.py:43 users/forms/profile.py:21 @@ -1346,7 +1346,7 @@ msgstr "没有匹配到资产,结束任务" msgid "Audits" msgstr "日志审计" -#: audits/models.py:27 audits/models.py:57 +#: audits/models.py:27 audits/models.py:59 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:228 msgid "Delete" @@ -1376,7 +1376,7 @@ msgstr "创建目录" msgid "Symlink" msgstr "建立软链接" -#: audits/models.py:38 audits/models.py:64 audits/models.py:87 +#: audits/models.py:38 audits/models.py:66 audits/models.py:89 #: terminal/models/session.py:51 terminal/models/sharing.py:82 msgid "Remote addr" msgstr "远端地址" @@ -1400,7 +1400,7 @@ msgstr "成功" msgid "File transfer log" msgstr "文件管理" -#: audits/models.py:55 +#: audits/models.py:56 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:225 msgid "Create" @@ -1411,84 +1411,84 @@ msgstr "创建" msgid "Update" msgstr "更新" -#: audits/models.py:62 audits/serializers.py:63 +#: audits/models.py:64 audits/serializers.py:63 msgid "Resource Type" msgstr "资源类型" -#: audits/models.py:63 +#: audits/models.py:65 msgid "Resource" msgstr "资源" -#: audits/models.py:65 audits/models.py:88 +#: audits/models.py:67 audits/models.py:90 #: terminal/backends/command/serializers.py:39 msgid "Datetime" msgstr "日期" -#: audits/models.py:80 +#: audits/models.py:82 msgid "Operate log" msgstr "操作日志" -#: audits/models.py:86 +#: audits/models.py:88 msgid "Change by" msgstr "修改者" -#: audits/models.py:94 +#: audits/models.py:96 msgid "Password change log" msgstr "改密日志" -#: audits/models.py:109 +#: audits/models.py:111 msgid "Disabled" msgstr "禁用" -#: audits/models.py:110 settings/models.py:33 +#: audits/models.py:112 settings/models.py:33 msgid "Enabled" msgstr "启用" -#: audits/models.py:111 +#: audits/models.py:113 msgid "-" msgstr "-" -#: audits/models.py:120 +#: audits/models.py:122 msgid "Login type" msgstr "登录方式" -#: audits/models.py:121 +#: audits/models.py:123 #: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:14 msgid "Login ip" msgstr "登录IP" -#: audits/models.py:122 +#: audits/models.py:124 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:17 msgid "Login city" msgstr "登录城市" -#: audits/models.py:123 audits/serializers.py:44 +#: audits/models.py:125 audits/serializers.py:44 msgid "User agent" msgstr "用户代理" -#: audits/models.py:124 +#: audits/models.py:126 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:64 users/models/user.py:684 #: users/serializers/profile.py:121 msgid "MFA" msgstr "MFA" -#: audits/models.py:126 terminal/models/status.py:33 +#: audits/models.py:128 terminal/models/status.py:33 #: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 #: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "状态" -#: audits/models.py:127 +#: audits/models.py:129 msgid "Date login" msgstr "登录日期" -#: audits/models.py:128 audits/serializers.py:46 +#: audits/models.py:130 audits/serializers.py:46 msgid "Authentication backend" msgstr "认证方式" -#: audits/models.py:167 +#: audits/models.py:169 msgid "User login log" msgstr "用户登录日志" @@ -1533,15 +1533,15 @@ msgstr "运行用户名称" msgid "User display" msgstr "用户名称" -#: audits/signal_handlers.py:67 +#: audits/signal_handlers.py:47 msgid "SSH Key" msgstr "SSH 密钥" -#: audits/signal_handlers.py:69 +#: audits/signal_handlers.py:49 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:70 +#: audits/signal_handlers.py:50 msgid "Auth Token" msgstr "认证令牌" @@ -1557,174 +1557,174 @@ msgstr "企业微信" msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:73 authentication/models.py:76 +#: audits/signal_handlers.py:53 authentication/models.py:76 msgid "Temporary token" msgstr "临时密码" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:65 msgid "User and Group" msgstr "用户与用户组" -#: audits/signal_handlers.py:108 +#: audits/signal_handlers.py:66 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} 加入 {UserGroup}" -#: audits/signal_handlers.py:109 +#: audits/signal_handlers.py:67 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} 离开 {UserGroup}" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:70 msgid "Asset and SystemUser" msgstr "资产与系统用户" -#: audits/signal_handlers.py:113 +#: audits/signal_handlers.py:71 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 添加 {SystemUser}" -#: audits/signal_handlers.py:114 +#: audits/signal_handlers.py:72 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 移除 {SystemUser}" -#: audits/signal_handlers.py:117 +#: audits/signal_handlers.py:75 msgid "Node and Asset" msgstr "节点与资产" -#: audits/signal_handlers.py:118 +#: audits/signal_handlers.py:76 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 添加 {Asset}" -#: audits/signal_handlers.py:119 +#: audits/signal_handlers.py:77 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 移除 {Asset}" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:80 msgid "User asset permissions" msgstr "用户资产授权" -#: audits/signal_handlers.py:123 +#: audits/signal_handlers.py:81 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 添加 {User}" -#: audits/signal_handlers.py:124 +#: audits/signal_handlers.py:82 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 移除 {User}" -#: audits/signal_handlers.py:127 +#: audits/signal_handlers.py:85 msgid "User group asset permissions" msgstr "用户组资产授权" -#: audits/signal_handlers.py:128 +#: audits/signal_handlers.py:86 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:129 +#: audits/signal_handlers.py:87 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:132 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:90 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "资产授权" -#: audits/signal_handlers.py:133 +#: audits/signal_handlers.py:91 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 添加 {Asset}" -#: audits/signal_handlers.py:134 +#: audits/signal_handlers.py:92 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 移除 {Asset}" -#: audits/signal_handlers.py:137 +#: audits/signal_handlers.py:95 msgid "Node permission" msgstr "节点授权" -#: audits/signal_handlers.py:138 +#: audits/signal_handlers.py:96 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 添加 {Node}" -#: audits/signal_handlers.py:139 +#: audits/signal_handlers.py:97 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 移除 {Node}" -#: audits/signal_handlers.py:142 +#: audits/signal_handlers.py:100 msgid "Asset permission and SystemUser" msgstr "资产授权与系统用户" -#: audits/signal_handlers.py:143 +#: audits/signal_handlers.py:101 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:144 +#: audits/signal_handlers.py:102 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 移除 {SystemUser}" -#: audits/signal_handlers.py:147 +#: audits/signal_handlers.py:105 msgid "User application permissions" msgstr "用户应用授权" -#: audits/signal_handlers.py:148 +#: audits/signal_handlers.py:106 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 添加 {User}" -#: audits/signal_handlers.py:149 +#: audits/signal_handlers.py:107 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 移除 {User}" -#: audits/signal_handlers.py:152 +#: audits/signal_handlers.py:110 msgid "User group application permissions" msgstr "用户组应用授权" -#: audits/signal_handlers.py:153 +#: audits/signal_handlers.py:111 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:154 +#: audits/signal_handlers.py:112 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:157 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:115 perms/models/application_permission.py:38 msgid "Application permission" msgstr "应用授权" -#: audits/signal_handlers.py:158 +#: audits/signal_handlers.py:116 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 添加 {Application}" -#: audits/signal_handlers.py:159 +#: audits/signal_handlers.py:117 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 移除 {Application}" -#: audits/signal_handlers.py:162 +#: audits/signal_handlers.py:120 msgid "Application permission and SystemUser" msgstr "应用授权与系统用户" -#: audits/signal_handlers.py:163 +#: audits/signal_handlers.py:121 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:164 +#: audits/signal_handlers.py:122 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" @@ -2550,6 +2550,19 @@ msgstr "忽略的" msgid "discard time" msgstr "忽略时间" +#: common/mixins/views.py:41 +msgid "Export all" +msgstr "导出所有" + +#: common/mixins/views.py:43 +msgid "Export only selected items" +msgstr "仅导出选择项" + +#: common/mixins/views.py:48 +#, python-format +msgid "Export filtered: %s" +msgstr "导出搜素: %s" + #: common/sdk/im/exceptions.py:23 msgid "Network error, please contact system administrator" msgstr "网络错误,请联系系统管理员" From 56862a965daeea689ae357fcfbdc3c9f3ed5a885 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Sat, 7 May 2022 10:40:12 +0800 Subject: [PATCH 082/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dsystem-role?= =?UTF-8?q?=E8=8E=B7=E5=8F=96users=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20(#8196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jiangjie.Bai --- apps/rbac/api/role.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py index f077964a6..f398f0315 100644 --- a/apps/rbac/api/role.py +++ b/apps/rbac/api/role.py @@ -8,6 +8,7 @@ from ..filters import RoleFilter from ..serializers import RoleSerializer, RoleUserSerializer from ..models import Role, SystemRole, OrgRole from .permission import PermissionViewSet +from common.mixins.api import PaginatedResponseMixin __all__ = [ 'RoleViewSet', 'SystemRoleViewSet', 'OrgRoleViewSet', @@ -15,7 +16,7 @@ __all__ = [ ] -class RoleViewSet(JMSModelViewSet): +class RoleViewSet(PaginatedResponseMixin, JMSModelViewSet): queryset = Role.objects.all() serializer_classes = { 'default': RoleSerializer, @@ -54,7 +55,7 @@ class RoleViewSet(JMSModelViewSet): def users(self, *args, **kwargs): role = self.get_object() queryset = role.users - return self.get_paginated_response_with_query_set(queryset) + return self.get_paginated_response_from_queryset(queryset) class SystemRoleViewSet(RoleViewSet): From 3f856e68f0b68fb700b33dffa704e73e379f4da2 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 6 May 2022 16:27:14 +0800 Subject: [PATCH 083/258] =?UTF-8?q?feat:=20public=20settings=20=E5=8C=BA?= =?UTF-8?q?=E5=88=86=20public=20=E5=92=8C=20open?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/api/public.py | 115 ++++++++++++++++------------ apps/settings/serializers/public.py | 36 ++++++++- apps/settings/urls/api_urls.py | 1 + 3 files changed, 102 insertions(+), 50 deletions(-) diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index b9076618d..108970c59 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -1,5 +1,5 @@ from rest_framework import generics -from rest_framework.permissions import AllowAny +from rest_framework.permissions import AllowAny, IsAuthenticated from django.conf import settings from jumpserver.utils import has_valid_xpack_license, get_xpack_license_info @@ -9,10 +9,10 @@ from ..utils import get_interface_setting logger = get_logger(__name__) -__all__ = ['PublicSettingApi'] +__all__ = ['PublicSettingApi', 'OpenPublicSettingApi'] -class PublicSettingApi(generics.RetrieveAPIView): +class OpenPublicSettingApi(generics.RetrieveAPIView): permission_classes = (AllowAny,) serializer_class = serializers.PublicSettingSerializer @@ -27,49 +27,68 @@ class PublicSettingApi(generics.RetrieveAPIView): interface = get_interface_setting() return interface['login_title'] - def get_object(self): - instance = { - "data": { - # Security - "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD, - "OLD_PASSWORD_HISTORY_LIMIT_COUNT": settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT, - "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, - "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, - "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, - "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, - "SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME, - "SECURITY_LUNA_REMEMBER_AUTH": settings.SECURITY_LUNA_REMEMBER_AUTH, - "PASSWORD_RULE": { - 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, - 'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH': settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH, - 'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE, - 'SECURITY_PASSWORD_LOWER_CASE': settings.SECURITY_PASSWORD_LOWER_CASE, - 'SECURITY_PASSWORD_NUMBER': settings.SECURITY_PASSWORD_NUMBER, - 'SECURITY_PASSWORD_SPECIAL_CHAR': settings.SECURITY_PASSWORD_SPECIAL_CHAR, - }, - 'SECURITY_WATERMARK_ENABLED': settings.SECURITY_WATERMARK_ENABLED, - 'SECURITY_SESSION_SHARE': settings.SECURITY_SESSION_SHARE, - # XPACK - "XPACK_ENABLED": settings.XPACK_ENABLED, - "XPACK_LICENSE_IS_VALID": has_valid_xpack_license(), - "XPACK_LICENSE_INFO": get_xpack_license_info(), - # Performance - "LOGIN_TITLE": self.get_login_title(), - "LOGO_URLS": self.get_logo_urls(), - "HELP_DOCUMENT_URL": settings.HELP_DOCUMENT_URL, - "HELP_SUPPORT_URL": settings.HELP_SUPPORT_URL, - # Auth - "AUTH_WECOM": settings.AUTH_WECOM, - "AUTH_DINGTALK": settings.AUTH_DINGTALK, - "AUTH_FEISHU": settings.AUTH_FEISHU, - # Terminal - "XRDP_ENABLED": settings.XRDP_ENABLED, - "TERMINAL_MAGNUS_ENABLED": settings.TERMINAL_MAGNUS_ENABLED, - "TERMINAL_KOKO_SSH_ENABLED": settings.TERMINAL_KOKO_SSH_ENABLED, - # Announcement - "ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED, - "ANNOUNCEMENT": settings.ANNOUNCEMENT, - "AUTH_TEMP_TOKEN": settings.AUTH_TEMP_TOKEN, - } + def get_open_public_settings(self): + return { + "XPACK_ENABLED": settings.XPACK_ENABLED, + "LOGIN_TITLE": self.get_login_title(), + "LOGO_URLS": self.get_logo_urls(), + 'SECURITY_WATERMARK_ENABLED': settings.SECURITY_WATERMARK_ENABLED, } - return instance + + def get_object(self): + return self.get_open_public_settings() + + +class PublicSettingApi(OpenPublicSettingApi): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.PrivateSettingSerializer + + @staticmethod + def get_public_settings(): + return { + # Security + "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD, + "OLD_PASSWORD_HISTORY_LIMIT_COUNT": settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT, + "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, + "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, + "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, + "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, + "SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME, + "SECURITY_LUNA_REMEMBER_AUTH": settings.SECURITY_LUNA_REMEMBER_AUTH, + "PASSWORD_RULE": { + 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, + 'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH': settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH, + 'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE, + 'SECURITY_PASSWORD_LOWER_CASE': settings.SECURITY_PASSWORD_LOWER_CASE, + 'SECURITY_PASSWORD_NUMBER': settings.SECURITY_PASSWORD_NUMBER, + 'SECURITY_PASSWORD_SPECIAL_CHAR': settings.SECURITY_PASSWORD_SPECIAL_CHAR, + }, + 'SECURITY_SESSION_SHARE': settings.SECURITY_SESSION_SHARE, + # XPACK + "XPACK_LICENSE_IS_VALID": has_valid_xpack_license(), + "XPACK_LICENSE_INFO": get_xpack_license_info(), + # Performance + "HELP_DOCUMENT_URL": settings.HELP_DOCUMENT_URL, + "HELP_SUPPORT_URL": settings.HELP_SUPPORT_URL, + # Auth + "AUTH_WECOM": settings.AUTH_WECOM, + "AUTH_DINGTALK": settings.AUTH_DINGTALK, + "AUTH_FEISHU": settings.AUTH_FEISHU, + # Terminal + "XRDP_ENABLED": settings.XRDP_ENABLED, + "TERMINAL_MAGNUS_ENABLED": settings.TERMINAL_MAGNUS_ENABLED, + "TERMINAL_KOKO_SSH_ENABLED": settings.TERMINAL_KOKO_SSH_ENABLED, + # Announcement + "ANNOUNCEMENT_ENABLED": settings.ANNOUNCEMENT_ENABLED, + "ANNOUNCEMENT": settings.ANNOUNCEMENT, + "AUTH_TEMP_TOKEN": settings.AUTH_TEMP_TOKEN, + } + + def get_object(self): + open_public = self.get_open_public_settings() + public = self.get_public_settings() + return { + **open_public, + **public + } + diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index 52e39a954..852f9ccca 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -3,8 +3,40 @@ from rest_framework import serializers -__all__ = ['PublicSettingSerializer'] +__all__ = ['PublicSettingSerializer', 'PrivateSettingSerializer'] class PublicSettingSerializer(serializers.Serializer): - data = serializers.DictField(read_only=True) + XPACK_ENABLED = serializers.BooleanField() + SECURITY_WATERMARK_ENABLED = serializers.BooleanField() + LOGIN_TITLE = serializers.CharField() + LOGO_URLS = serializers.DictField() + + +class PrivateSettingSerializer(PublicSettingSerializer): + WINDOWS_SKIP_ALL_MANUAL_PASSWORD = serializers.BooleanField() + OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField() + SECURITY_MAX_IDLE_TIME = serializers.IntegerField() + SECURITY_VIEW_AUTH_NEED_MFA = serializers.BooleanField() + SECURITY_MFA_VERIFY_TTL = serializers.IntegerField() + SECURITY_COMMAND_EXECUTION = serializers.BooleanField() + SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField() + SECURITY_LUNA_REMEMBER_AUTH = serializers.BooleanField() + PASSWORD_RULE = serializers.DictField() + SECURITY_SESSION_SHARE = serializers.BooleanField() + XPACK_LICENSE_IS_VALID = serializers.BooleanField() + XPACK_LICENSE_INFO = serializers.DictField() + HELP_DOCUMENT_URL = serializers.CharField() + HELP_SUPPORT_URL = serializers.CharField() + + AUTH_WECOM = serializers.BooleanField() + AUTH_DINGTALK = serializers.BooleanField() + AUTH_FEISHU = serializers.BooleanField() + AUTH_TEMP_TOKEN = serializers.BooleanField() + + XRDP_ENABLED = serializers.BooleanField() + TERMINAL_MAGNUS_ENABLED = serializers.BooleanField() + TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField() + + ANNOUNCEMENT_ENABLED = serializers.BooleanField() + ANNOUNCEMENT = serializers.CharField() \ No newline at end of file diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py index 22825f4e8..728baf0ae 100644 --- a/apps/settings/urls/api_urls.py +++ b/apps/settings/urls/api_urls.py @@ -22,4 +22,5 @@ urlpatterns = [ path('setting/', api.SettingsApi.as_view(), name='settings-setting'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'), + path('public/open/', api.OpenPublicSettingApi.as_view(), name='open-public-setting'), ] From 031077c2983300ed539d37462861c2048ecd7e11 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Sat, 7 May 2022 16:20:12 +0800 Subject: [PATCH 084/258] =?UTF-8?q?perf:=20password=20=E7=AD=89=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20rsa=20=E5=8A=A0=E5=AF=86=E4=BC=A0=E8=BE=93=20(#8188?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 修改 model fields 路径 * stash it * pref: 统一加密方式,密码字段采用 rsa 加密 * pref: 临时密码使用 rsa * perf: 去掉 debug msg * perf: 去掉 Debug * perf: 去掉 debug * perf: 抽出来 Co-authored-by: ibuler --- apps/applications/migrations/0001_initial.py | 4 +- .../0010_appaccount_historicalappaccount.py | 14 +-- apps/assets/api/system_user.py | 17 +--- .../migrations/0032_auto_20190624_2108.py | 26 +++--- .../migrations/0035_auto_20190711_2018.py | 10 +-- apps/assets/migrations/0044_platform.py | 4 +- .../migrations/0072_historicalauthbook.py | 8 +- apps/assets/models/asset.py | 2 +- apps/assets/models/base.py | 2 +- apps/assets/serializers/base.py | 7 +- .../migrations/0014_auto_20220505_1902.py | 18 ++++ apps/authentication/api/token.py | 4 +- apps/authentication/forms.py | 16 +++- apps/authentication/middleware.py | 29 +++++++ apps/authentication/mixins.py | 86 +++---------------- .../serializers/password_mfa.py | 4 +- .../templates/authentication/login.html | 13 +-- apps/authentication/utils.py | 48 +---------- apps/authentication/views/login.py | 4 +- apps/common/{fields/model.py => db/fields.py} | 2 +- apps/common/drf/fields.py | 9 +- apps/common/fields/__init__.py | 4 - apps/common/utils/crypto.py | 56 +++++++++++- apps/jumpserver/settings/base.py | 1 + apps/jumpserver/settings/custom.py | 3 + .../ops/migrations/0010_auto_20191217_1758.py | 12 +-- .../ops/migrations/0011_auto_20200106_1534.py | 4 +- apps/ops/models/adhoc.py | 4 +- apps/settings/serializers/auth/ldap.py | 4 +- apps/static/js/jumpserver.js | 16 ++++ .../0016_commandstorage_replaystorage.py | 6 +- .../migrations/0048_endpoint_endpointrule.py | 17 ++-- apps/terminal/models/endpoint.py | 2 +- apps/terminal/models/storage.py | 2 +- apps/tickets/migrations/0001_initial.py | 4 +- apps/users/forms/profile.py | 9 +- .../migrations/0021_auto_20190625_1104.py | 8 +- apps/users/migrations/0037_user_secret_key.py | 4 +- apps/users/models/user.py | 2 +- apps/users/serializers/profile.py | 17 ++-- .../users/templates/users/reset_password.html | 15 +++- .../templates/users/user_password_verify.html | 15 +++- apps/users/views/profile/password.py | 4 +- 43 files changed, 291 insertions(+), 245 deletions(-) create mode 100644 apps/audits/migrations/0014_auto_20220505_1902.py rename apps/common/{fields/model.py => db/fields.py} (99%) delete mode 100644 apps/common/fields/__init__.py diff --git a/apps/applications/migrations/0001_initial.py b/apps/applications/migrations/0001_initial.py index 221fd5e22..26948fc73 100644 --- a/apps/applications/migrations/0001_initial.py +++ b/apps/applications/migrations/0001_initial.py @@ -1,6 +1,6 @@ # Generated by Django 2.1.7 on 2019-05-20 11:04 -import common.fields.model +import common.db.fields from django.db import migrations, models import django.db.models.deletion import uuid @@ -23,7 +23,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=128, verbose_name='Name')), ('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')), ('path', models.CharField(max_length=128, verbose_name='App path')), - ('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')), + ('params', common.db.fields.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')), ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), diff --git a/apps/applications/migrations/0010_appaccount_historicalappaccount.py b/apps/applications/migrations/0010_appaccount_historicalappaccount.py index fc2cf2ab9..f79fd2475 100644 --- a/apps/applications/migrations/0010_appaccount_historicalappaccount.py +++ b/apps/applications/migrations/0010_appaccount_historicalappaccount.py @@ -1,7 +1,7 @@ # Generated by Django 3.1.12 on 2021-08-26 09:07 import assets.models.base -import common.fields.model +import common.db.fields from django.conf import settings import django.core.validators from django.db import migrations, models @@ -26,9 +26,9 @@ class Migration(migrations.Migration): ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), - ('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), - ('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), @@ -56,9 +56,9 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), - ('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), - ('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index f679b4e3f..f95303a5e 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -4,7 +4,6 @@ from rest_framework.response import Response from rest_framework.decorators import action from common.utils import get_logger, get_object_or_none -from common.utils.crypto import get_aes_crypto from common.permissions import IsValidUser from common.mixins.api import SuggestionMixin from orgs.mixins.api import OrgBulkModelViewSet @@ -102,27 +101,17 @@ class SystemUserTempAuthInfoApi(generics.CreateAPIView): permission_classes = (IsValidUser,) serializer_class = SystemUserTempAuthSerializer - def decrypt_data_if_need(self, data): - csrf_token = self.request.META.get('CSRF_COOKIE') - aes = get_aes_crypto(csrf_token, 'ECB') - password = data.get('password', '') - try: - data['password'] = aes.decrypt(password) - except: - pass - return data - def create(self, request, *args, **kwargs): serializer = super().get_serializer(data=request.data) serializer.is_valid(raise_exception=True) pk = kwargs.get('pk') - data = self.decrypt_data_if_need(serializer.validated_data) - instance_id = data.get('instance_id') + data = serializer.validated_data + asset_or_app_id = data.get('instance_id') with tmp_to_root_org(): instance = get_object_or_404(SystemUser, pk=pk) - instance.set_temp_auth(instance_id, self.request.user.id, data) + instance.set_temp_auth(asset_or_app_id, self.request.user.id, data) return Response(serializer.data, status=201) diff --git a/apps/assets/migrations/0032_auto_20190624_2108.py b/apps/assets/migrations/0032_auto_20190624_2108.py index 441f13cdb..275308e99 100644 --- a/apps/assets/migrations/0032_auto_20190624_2108.py +++ b/apps/assets/migrations/0032_auto_20190624_2108.py @@ -1,7 +1,7 @@ # Generated by Django 2.1.7 on 2019-06-24 13:08 import assets.models.utils -import common.fields.model +import common.db.fields from django.db import migrations @@ -15,61 +15,61 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='adminuser', name='_password', - field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), ), migrations.AlterField( model_name='adminuser', name='_private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), ), migrations.AlterField( model_name='adminuser', name='_public_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), ), migrations.AlterField( model_name='authbook', name='_password', - field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), ), migrations.AlterField( model_name='authbook', name='_private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), ), migrations.AlterField( model_name='authbook', name='_public_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), ), migrations.AlterField( model_name='gateway', name='_password', - field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), ), migrations.AlterField( model_name='gateway', name='_private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), ), migrations.AlterField( model_name='gateway', name='_public_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), ), migrations.AlterField( model_name='systemuser', name='_password', - field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), + field=common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'), ), migrations.AlterField( model_name='systemuser', name='_private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'), ), migrations.AlterField( model_name='systemuser', name='_public_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'), ), ] diff --git a/apps/assets/migrations/0035_auto_20190711_2018.py b/apps/assets/migrations/0035_auto_20190711_2018.py index 9dcbad1db..00eb41fe5 100644 --- a/apps/assets/migrations/0035_auto_20190711_2018.py +++ b/apps/assets/migrations/0035_auto_20190711_2018.py @@ -1,6 +1,6 @@ # Generated by Django 2.1.7 on 2019-07-11 12:18 -import common.fields.model +import common.db.fields from django.db import migrations @@ -14,21 +14,21 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='adminuser', name='private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), ), migrations.AlterField( model_name='authbook', name='private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), ), migrations.AlterField( model_name='gateway', name='private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), ), migrations.AlterField( model_name='systemuser', name='private_key', - field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'), ), ] diff --git a/apps/assets/migrations/0044_platform.py b/apps/assets/migrations/0044_platform.py index 8d45a8ee3..2e1ab723e 100644 --- a/apps/assets/migrations/0044_platform.py +++ b/apps/assets/migrations/0044_platform.py @@ -1,6 +1,6 @@ # Generated by Django 2.2.7 on 2019-12-06 07:26 -import common.fields.model +import common.db.fields from django.db import migrations, models @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')), ('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')), ('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')), - ('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')), + ('meta', common.db.fields.JsonDictTextField(blank=True, null=True, verbose_name='Meta')), ('internal', models.BooleanField(default=False, verbose_name='Internal')), ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')), ], diff --git a/apps/assets/migrations/0072_historicalauthbook.py b/apps/assets/migrations/0072_historicalauthbook.py index 978584824..e28949c1f 100644 --- a/apps/assets/migrations/0072_historicalauthbook.py +++ b/apps/assets/migrations/0072_historicalauthbook.py @@ -1,6 +1,6 @@ # Generated by Django 3.1.6 on 2021-06-05 16:10 -import common.fields.model +import common.db.fields from django.conf import settings import django.core.validators from django.db import migrations, models @@ -58,9 +58,9 @@ class Migration(migrations.Migration): ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), - ('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), - ('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 85ac6c918..7dde0862f 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -11,7 +11,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import ValidationError -from common.fields.model import JsonDictTextField +from common.db.fields import JsonDictTextField from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 38b7218c4..493036efc 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -19,7 +19,7 @@ from common.utils import ( ) from common.utils.encode import ssh_pubkey_gen from common.validators import alphanumeric -from common import fields +from common.db import fields from orgs.mixins.models import OrgModelMixin diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 2f3486d01..7c8760562 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -6,12 +6,13 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key +from common.drf.fields import EncryptedField from assets.models import Type class AuthSerializer(serializers.ModelSerializer): - password = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=1024) - private_key = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=4096) + password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024) + private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=4096) def gen_keys(self, private_key=None, password=None): if private_key is None: @@ -31,6 +32,8 @@ class AuthSerializer(serializers.ModelSerializer): class AuthSerializerMixin(serializers.ModelSerializer): + password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024) + private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=4096) passphrase = serializers.CharField( allow_blank=True, allow_null=True, required=False, max_length=512, write_only=True, label=_('Key password') diff --git a/apps/audits/migrations/0014_auto_20220505_1902.py b/apps/audits/migrations/0014_auto_20220505_1902.py new file mode 100644 index 000000000..8c483560c --- /dev/null +++ b/apps/audits/migrations/0014_auto_20220505_1902.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2022-05-05 11:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0013_auto_20211130_1037'), + ] + + operations = [ + migrations.AlterField( + model_name='operatelog', + name='action', + field=models.CharField(choices=[('create', 'Create'), ('view', 'View'), ('update', 'Update'), ('delete', 'Delete')], max_length=16, verbose_name='Action'), + ), + ] diff --git a/apps/authentication/api/token.py b/apps/authentication/api/token.py index e5fe8bf2c..3bc8a33d1 100644 --- a/apps/authentication/api/token.py +++ b/apps/authentication/api/token.py @@ -27,8 +27,10 @@ class TokenCreateApi(AuthMixin, CreateAPIView): def create(self, request, *args, **kwargs): self.create_session_if_need() # 如果认证没有过,检查账号密码 + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) try: - user = self.check_user_auth_if_need() + user = self.get_user_or_auth(serializer.validated_data) self.check_user_mfa_if_need(user) self.check_user_login_confirm_if_need(user) self.send_auth_signal(success=True, user=user) diff --git a/apps/authentication/forms.py b/apps/authentication/forms.py index 16ca659c0..bd7fa7d67 100644 --- a/apps/authentication/forms.py +++ b/apps/authentication/forms.py @@ -1,15 +1,25 @@ # -*- coding: utf-8 -*- # - from django import forms from django.conf import settings from django.utils.translation import ugettext_lazy as _ from captcha.fields import CaptchaField, CaptchaTextInput +from common.utils import get_logger, rsa_decrypt_by_session_pkey + +logger = get_logger(__name__) + + +class EncryptedField(forms.CharField): + def to_python(self, value): + value = super().to_python(value) + return rsa_decrypt_by_session_pkey(value) + class UserLoginForm(forms.Form): days_auto_login = int(settings.SESSION_COOKIE_AGE / 3600 / 24) - disable_days_auto_login = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE or days_auto_login < 1 + disable_days_auto_login = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE \ + or days_auto_login < 1 username = forms.CharField( label=_('Username'), max_length=100, @@ -18,7 +28,7 @@ class UserLoginForm(forms.Form): 'autofocus': 'autofocus' }) ) - password = forms.CharField( + password = EncryptedField( label=_('Password'), widget=forms.PasswordInput, max_length=1024, strip=False ) diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index 42bbb27cb..d2b4ff19e 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -1,8 +1,12 @@ +import base64 + from django.shortcuts import redirect, reverse from django.utils.deprecation import MiddlewareMixin from django.http import HttpResponse from django.conf import settings +from common.utils import gen_key_pair + class MFAMiddleware: """ @@ -48,3 +52,28 @@ class SessionCookieMiddleware(MiddlewareMixin): return response response.set_cookie(key, value) return response + + +class EncryptedMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + @staticmethod + def check_key_pair(request, response): + pub_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME + public_key = request.session.get(pub_key_name) + cookie_key = request.COOKIES.get(pub_key_name) + if public_key and public_key == cookie_key: + return + + pri_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME + private_key, public_key = gen_key_pair() + public_key_decode = base64.b64encode(public_key.encode()).decode() + request.session[pub_key_name] = public_key_decode + request.session[pri_key_name] = private_key + response.set_cookie(pub_key_name, public_key_decode) + + def __call__(self, request): + response = self.get_response(request) + self.check_key_pair(request, response) + return response diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 56216e91f..698601d4c 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -23,9 +23,7 @@ from acls.models import LoginACL from users.models import User from users.utils import LoginBlockUtil, MFABlockUtils, LoginIpBlockUtil from . import errors -from .utils import rsa_decrypt, gen_key_pair from .signals import post_auth_success, post_auth_failed -from .const import RSA_PRIVATE_KEY, RSA_PUBLIC_KEY logger = get_logger(__name__) @@ -91,46 +89,8 @@ def authenticate(request=None, **credentials): auth.authenticate = authenticate -class PasswordEncryptionViewMixin: - request = None - - def get_decrypted_password(self, password=None, username=None): - request = self.request - if hasattr(request, 'data'): - data = request.data - else: - data = request.POST - - username = username or data.get('username') - password = password or data.get('password') - - password = self.decrypt_passwd(password) - if not password: - self.raise_password_decrypt_failed(username=username) - return password - - def raise_password_decrypt_failed(self, username): - ip = self.get_request_ip() - raise errors.CredentialError( - error=errors.reason_password_decrypt_failed, - username=username, ip=ip, request=self.request - ) - - def decrypt_passwd(self, raw_passwd): - # 获取解密密钥,对密码进行解密 - rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY) - if rsa_private_key is None: - return raw_passwd - - try: - return rsa_decrypt(raw_passwd, rsa_private_key) - except Exception as e: - logger.error(e, exc_info=True) - logger.error( - f'Decrypt password failed: password[{raw_passwd}] ' - f'rsa_private_key[{rsa_private_key}]' - ) - return None +class CommonMixin: + request: Request def get_request_ip(self): ip = '' @@ -139,26 +99,6 @@ class PasswordEncryptionViewMixin: ip = ip or get_request_ip(self.request) return ip - def get_context_data(self, **kwargs): - # 生成加解密密钥对,public_key传递给前端,private_key存入session中供解密使用 - rsa_public_key = self.request.session.get(RSA_PUBLIC_KEY) - rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY) - if not all([rsa_private_key, rsa_public_key]): - rsa_private_key, rsa_public_key = gen_key_pair() - rsa_public_key = rsa_public_key.replace('\n', '\\n') - self.request.session[RSA_PRIVATE_KEY] = rsa_private_key - self.request.session[RSA_PUBLIC_KEY] = rsa_public_key - - kwargs.update({ - 'rsa_public_key': rsa_public_key, - }) - return super().get_context_data(**kwargs) - - -class CommonMixin(PasswordEncryptionViewMixin): - request: Request - get_request_ip: Callable - def raise_credential_error(self, error): raise self.partial_credential_error(error=error) @@ -193,20 +133,13 @@ class CommonMixin(PasswordEncryptionViewMixin): user.backend = self.request.session.get("auth_backend") return user - def get_auth_data(self, decrypt_passwd=False): + def get_auth_data(self, data): request = self.request - if hasattr(request, 'data'): - data = request.data - else: - data = request.POST items = ['username', 'password', 'challenge', 'public_key', 'auto_login'] username, password, challenge, public_key, auto_login = bulk_get(data, items, default='') ip = self.get_request_ip() self._set_partial_credential_error(username=username, ip=ip, request=request) - - if decrypt_passwd: - password = self.get_decrypted_password() password = password + challenge.strip() return username, password, public_key, ip, auto_login @@ -482,10 +415,10 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost need = cache.get(self.key_prefix_captcha.format(ip)) return need - def check_user_auth(self, decrypt_passwd=False): + def check_user_auth(self, valid_data=None): # pre check self.check_is_block() - username, password, public_key, ip, auto_login = self.get_auth_data(decrypt_passwd) + username, password, public_key, ip, auto_login = self.get_auth_data(valid_data) self._check_only_allow_exists_user_auth(username) # check auth @@ -537,11 +470,12 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost self.mark_password_ok(user, False) return user - def check_user_auth_if_need(self, decrypt_passwd=False): + def get_user_or_auth(self, valid_data): request = self.request - if not request.session.get('auth_password'): - return self.check_user_auth(decrypt_passwd=decrypt_passwd) - return self.get_user_from_session() + if request.session.get('auth_password'): + return self.get_user_from_session() + else: + return self.check_user_auth(valid_data) def clear_auth_mark(self): keys = ['auth_password', 'user_id', 'auth_confirm', 'auth_ticket_id'] diff --git a/apps/authentication/serializers/password_mfa.py b/apps/authentication/serializers/password_mfa.py index c4c0679c6..cf3452af3 100644 --- a/apps/authentication/serializers/password_mfa.py +++ b/apps/authentication/serializers/password_mfa.py @@ -2,6 +2,8 @@ # from rest_framework import serializers +from common.drf.fields import EncryptedField + __all__ = [ 'OtpVerifySerializer', 'MFAChallengeSerializer', 'MFASelectTypeSerializer', @@ -10,7 +12,7 @@ __all__ = [ class PasswordVerifySerializer(serializers.Serializer): - password = serializers.CharField() + password = EncryptedField() class MFASelectTypeSerializer(serializers.Serializer): diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 677ee70d4..477df7af4 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -161,6 +161,7 @@ {{ JMS_TITLE }}
    +
    {% csrf_token %}
    @@ -241,20 +242,10 @@ {% include '_foot_js.html' %} + {% endblock %} diff --git a/apps/users/templates/users/user_password_verify.html b/apps/users/templates/users/user_password_verify.html index bfc72ee87..65dacc75d 100644 --- a/apps/users/templates/users/user_password_verify.html +++ b/apps/users/templates/users/user_password_verify.html @@ -15,10 +15,23 @@ {% endif %} {% csrf_token %}
    - +
    + {% endblock %} + diff --git a/apps/users/views/profile/password.py b/apps/users/views/profile/password.py index 7c69dccd3..bb51b8aa4 100644 --- a/apps/users/views/profile/password.py +++ b/apps/users/views/profile/password.py @@ -7,7 +7,7 @@ from django.shortcuts import redirect from django.utils.translation import ugettext as _ from django.views.generic.edit import FormView -from authentication.mixins import PasswordEncryptionViewMixin, AuthMixin +from authentication.mixins import AuthMixin from authentication import errors from common.utils import get_logger @@ -31,7 +31,7 @@ class UserVerifyPasswordView(AuthMixin, FormView): return redirect('authentication:login') try: - password = self.get_decrypted_password(username=user.username) + password = form.cleaned_data['password'] except errors.AuthFailedError as e: form.add_error("password", _(f"Password invalid") + f'({e.msg})') return self.form_invalid(form) From 5e70a8af15135b826cbaded89205d47d28c2908b Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Fri, 15 Apr 2022 14:39:37 +0800 Subject: [PATCH 085/258] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=85=B3=E8=81=94=E7=AE=97=E6=B3=95=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81AIX=E6=94=B9=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset.py | 4 ++++ apps/assets/tasks/push_system_user.py | 22 +++++++++++++--------- apps/common/utils/encode.py | 23 ++++++++++++++++++++--- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 7dde0862f..7d46a4b25 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -142,6 +142,10 @@ class Platform(models.Model): internal = models.BooleanField(default=False, verbose_name=_("Internal")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + @property + def algorithm(self): + return self.meta.get('algorithm') + @classmethod def default(cls): linux, created = cls.objects.get_or_create( diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py index 4270f5e0f..46734e4be 100644 --- a/apps/assets/tasks/push_system_user.py +++ b/apps/assets/tasks/push_system_user.py @@ -32,9 +32,9 @@ def _dump_args(args: dict): return ' '.join([f'{k}={v}' for k, v in args.items() if v is not Empty]) -def get_push_unixlike_system_user_tasks(system_user, username=None): +def get_push_unixlike_system_user_tasks(system_user, username=None, **kwargs): comment = system_user.name - + algorithm = kwargs.get('algorithm') if username is None: username = system_user.username @@ -104,7 +104,7 @@ def get_push_unixlike_system_user_tasks(system_user, username=None): 'module': 'user', 'args': 'name={} shell={} state=present password={}'.format( username, system_user.shell, - encrypt_password(password, salt="K3mIlKK"), + encrypt_password(password, salt="K3mIlKK", algorithm=algorithm), ), } }) @@ -138,7 +138,7 @@ def get_push_unixlike_system_user_tasks(system_user, username=None): return tasks -def get_push_windows_system_user_tasks(system_user: SystemUser, username=None): +def get_push_windows_system_user_tasks(system_user: SystemUser, username=None, **kwargs): if username is None: username = system_user.username password = system_user.password @@ -176,7 +176,7 @@ def get_push_windows_system_user_tasks(system_user: SystemUser, username=None): return tasks -def get_push_system_user_tasks(system_user, platform="unixlike", username=None): +def get_push_system_user_tasks(system_user, platform="unixlike", username=None, algorithm=None): """ 获取推送系统用户的 ansible 命令,跟资产无关 :param system_user: @@ -190,16 +190,16 @@ def get_push_system_user_tasks(system_user, platform="unixlike", username=None): } get_tasks = get_task_map.get(platform, get_push_unixlike_system_user_tasks) if not system_user.username_same_with_user: - return get_tasks(system_user) + return get_tasks(system_user, algorithm=algorithm) tasks = [] # 仅推送这个username if username is not None: - tasks.extend(get_tasks(system_user, username)) + tasks.extend(get_tasks(system_user, username, algorithm=algorithm)) return tasks users = system_user.users.all().values_list('username', flat=True) print(_("System user is dynamic: {}").format(list(users))) for _username in users: - tasks.extend(get_tasks(system_user, _username)) + tasks.extend(get_tasks(system_user, _username, algorithm=algorithm)) return tasks @@ -244,7 +244,11 @@ def push_system_user_util(system_user, assets, task_name, username=None): for u in usernames: for a in _assets: system_user.load_asset_special_auth(a, u) - tasks = get_push_system_user_tasks(system_user, platform, username=u) + algorithm = a.platform.algorithm + tasks = get_push_system_user_tasks( + system_user, platform, username=u, + algorithm=algorithm + ) run_task(tasks, [a]) diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index d108a2094..4178e4a0d 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -186,10 +186,27 @@ def make_signature(access_key_secret, date=None): return content_md5(data) -def encrypt_password(password, salt=None): - from passlib.hash import sha512_crypt - if password: +def encrypt_password(password, salt=None, algorithm='sha512'): + from passlib.hash import sha512_crypt, des_crypt + + def sha512(): return sha512_crypt.using(rounds=5000).hash(password, salt=salt) + + def des(): + return des_crypt.hash(password, salt=salt[:2]) + + support_algorithm = { + 'sha512': sha512, 'des': des + } + + if isinstance(algorithm, str): + algorithm = algorithm.lower() + + if algorithm not in support_algorithm.keys(): + algorithm = 'sha512' + + if password and support_algorithm[algorithm]: + return support_algorithm[algorithm]() return None From 55e04e8e9f69c8880a51714b1f802697812945f8 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 21 Apr 2022 11:05:15 +0800 Subject: [PATCH 086/258] =?UTF-8?q?feat:=20=E5=86=85=E7=BD=AEAIX=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=EF=BC=8C=E6=A0=B9=E6=8D=AE=E7=B3=BB=E7=BB=9F=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=AE=97=E6=B3=95=E5=8A=A0=E5=AF=86=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0090_auto_20220412_1145.py | 15 +++++++++++++++ apps/assets/models/asset.py | 4 ---- apps/assets/tasks/push_system_user.py | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/assets/migrations/0090_auto_20220412_1145.py b/apps/assets/migrations/0090_auto_20220412_1145.py index c2cb879aa..434b7b3cf 100644 --- a/apps/assets/migrations/0090_auto_20220412_1145.py +++ b/apps/assets/migrations/0090_auto_20220412_1145.py @@ -3,6 +3,20 @@ from django.db import migrations, models +def create_internal_platform(apps, schema_editor): + model = apps.get_model("assets", "Platform") + db_alias = schema_editor.connection.alias + type_platforms = ( + ('AIX', 'Unix', None), + ) + for name, base, meta in type_platforms: + defaults = {'name': name, 'base': base, 'meta': meta, 'internal': True} + model.objects.using(db_alias).update_or_create( + name=name, defaults=defaults + ) + migrations.RunPython(create_internal_platform) + + class Migration(migrations.Migration): dependencies = [ @@ -15,4 +29,5 @@ class Migration(migrations.Migration): name='number', field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Asset number'), ), + migrations.RunPython(create_internal_platform) ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 7d46a4b25..7dde0862f 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -142,10 +142,6 @@ class Platform(models.Model): internal = models.BooleanField(default=False, verbose_name=_("Internal")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) - @property - def algorithm(self): - return self.meta.get('algorithm') - @classmethod def default(cls): linux, created = cls.objects.get_or_create( diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py index 46734e4be..98d2a0922 100644 --- a/apps/assets/tasks/push_system_user.py +++ b/apps/assets/tasks/push_system_user.py @@ -244,7 +244,7 @@ def push_system_user_util(system_user, assets, task_name, username=None): for u in usernames: for a in _assets: system_user.load_asset_special_auth(a, u) - algorithm = a.platform.algorithm + algorithm = 'des' if a.platform.name == 'AIX' else 'sha512' tasks = get_push_system_user_tasks( system_user, platform, username=u, algorithm=algorithm From ab737ae09b49e76c7dbb1c8982144783c1cab8c4 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Sat, 7 May 2022 17:50:49 +0800 Subject: [PATCH 087/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=B1=BB=E5=9E=8B=E4=B8=BAnull=E7=9A=84=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E6=98=BE=E7=A4=BA=E4=B8=8D=E6=94=AF=E6=8C=81=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 修复获取类型为null的命令显示不支持的问题 --- apps/terminal/models/storage.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/storage.py index e9d4f429b..e0c815c04 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/storage.py @@ -89,16 +89,20 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): return Terminal.objects.filter(command_storage=self.name, is_deleted=False).exists() def get_command_queryset(self): + if self.type_null: + return Command.objects.none() + if self.type_server: - qs = Command.objects.all() - else: - if self.type not in TYPE_ENGINE_MAPPING: - logger.error(f'Command storage `{self.type}` not support') - return Command.objects.none() + return Command.objects.all() + + if self.type in TYPE_ENGINE_MAPPING: engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type]) qs = engine_mod.QuerySet(self.config) qs.model = Command - return qs + return qs + + logger.error(f'Command storage `{self.type}` not support') + return Command.objects.none() def save(self, force_insert=False, force_update=False, using=None, update_fields=None): From 64eda5f28b52c80555f0a5ea4499cde1744beada Mon Sep 17 00:00:00 2001 From: jiangweidong <80373698+F2C-Jiang@users.noreply.github.com> Date: Mon, 9 May 2022 16:37:31 +0800 Subject: [PATCH 088/258] =?UTF-8?q?perf:=20=E5=91=BD=E4=BB=A4=E5=AD=98?= =?UTF-8?q?=E5=82=A8ES=E5=8F=AF=E6=A0=B9=E6=8D=AE=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E5=BB=BA=E7=AB=8B=E7=B4=A2=E5=BC=95=20(#8180?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 命令存储ES可根据日期动态建立索引 * perf: 优化合并字段 * feat: 修改逻辑 --- apps/common/utils/timezone.py | 4 +++ apps/locale/ja/LC_MESSAGES/django.po | 30 +++++++++++++++------- apps/locale/zh/LC_MESSAGES/django.po | 30 +++++++++++++++------- apps/terminal/backends/command/es.py | 37 ++++++++++++++++++++++------ apps/terminal/models/storage.py | 18 ++++++++++++++ apps/terminal/models/terminal.py | 2 +- apps/terminal/serializers/storage.py | 4 +++ 7 files changed, 98 insertions(+), 27 deletions(-) diff --git a/apps/common/utils/timezone.py b/apps/common/utils/timezone.py index c38fcdc92..4b60008af 100644 --- a/apps/common/utils/timezone.py +++ b/apps/common/utils/timezone.py @@ -32,6 +32,10 @@ def local_now_display(fmt='%Y-%m-%d %H:%M:%S'): return local_now().strftime(fmt) +def local_now_date_display(fmt='%Y-%m-%d'): + return local_now().strftime(fmt) + + _rest_dt_field = DateTimeField() dt_parser = _rest_dt_field.to_internal_value dt_formatter = _rest_dt_field.to_representation diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index b23c9cde9..368cf98fb 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -30,7 +30,7 @@ msgstr "Acls" #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 -#: terminal/models/storage.py:23 terminal/models/task.py:16 +#: terminal/models/storage.py:24 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:32 #: users/models/group.py:15 users/models/user.py:661 #: xpack/plugins/cloud/models.py:28 @@ -62,7 +62,7 @@ msgstr "アクティブ" #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 -#: terminal/models/storage.py:26 terminal/models/terminal.py:114 +#: terminal/models/storage.py:27 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 @@ -305,7 +305,7 @@ msgstr "カテゴリ" #: assets/models/cmd_filter.py:82 assets/models/user.py:246 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:55 terminal/models/storage.py:119 +#: terminal/models/storage.py:56 terminal/models/storage.py:136 #: tickets/models/flow.py:56 tickets/models/ticket.py:131 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29 #: xpack/plugins/change_auth_plan/models/app.py:28 @@ -4639,7 +4639,7 @@ msgstr "オンラインセッションを持つ" msgid "Terminals" msgstr "ターミナル管理" -#: terminal/backends/command/es.py:26 +#: terminal/backends/command/es.py:28 msgid "Invalid elasticsearch config" msgstr "無効なElasticsearch構成" @@ -4883,15 +4883,15 @@ msgstr "スレッド" msgid "Boot Time" msgstr "ブート時間" -#: terminal/models/storage.py:25 +#: terminal/models/storage.py:26 msgid "Default storage" msgstr "デフォルトのストレージ" -#: terminal/models/storage.py:113 terminal/models/terminal.py:108 +#: terminal/models/storage.py:130 terminal/models/terminal.py:108 msgid "Command storage" msgstr "コマンドストレージ" -#: terminal/models/storage.py:173 terminal/models/terminal.py:109 +#: terminal/models/storage.py:190 terminal/models/terminal.py:109 msgid "Replay storage" msgstr "再生ストレージ" @@ -5008,14 +5008,26 @@ msgid "Port invalid" msgstr "ポートが無効" #: terminal/serializers/storage.py:159 +msgid "Index by date" +msgstr "日付による索引付け" + +#: terminal/serializers/storage.py:160 +msgid "Whether to create an index by date" +msgstr "現在の日付に基づいてインデックスを動的に作成するかどうか" + +#: terminal/serializers/storage.py:163 +msgid "Index prefix" +msgstr "インデックス接頭辞" + +#: terminal/serializers/storage.py:166 msgid "Index" msgstr "インデックス" -#: terminal/serializers/storage.py:161 +#: terminal/serializers/storage.py:168 msgid "Doc type" msgstr "Docタイプ" -#: terminal/serializers/storage.py:163 +#: terminal/serializers/storage.py:170 msgid "Ignore Certificate Verification" msgstr "証明書の検証を無視する" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index bc6f0a6ae..ad6b041be 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -29,7 +29,7 @@ msgstr "访问控制" #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 -#: terminal/models/storage.py:23 terminal/models/task.py:16 +#: terminal/models/storage.py:24 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:32 #: users/models/group.py:15 users/models/user.py:661 #: xpack/plugins/cloud/models.py:28 @@ -61,7 +61,7 @@ msgstr "激活中" #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 -#: terminal/models/storage.py:26 terminal/models/terminal.py:114 +#: terminal/models/storage.py:27 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 @@ -300,7 +300,7 @@ msgstr "类别" #: assets/models/cmd_filter.py:82 assets/models/user.py:246 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:55 terminal/models/storage.py:119 +#: terminal/models/storage.py:56 terminal/models/storage.py:136 #: tickets/models/flow.py:56 tickets/models/ticket.py:131 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29 #: xpack/plugins/change_auth_plan/models/app.py:28 @@ -4565,7 +4565,7 @@ msgstr "有在线会话" msgid "Terminals" msgstr "终端管理" -#: terminal/backends/command/es.py:26 +#: terminal/backends/command/es.py:28 msgid "Invalid elasticsearch config" msgstr "无效的 Elasticsearch 配置" @@ -4809,15 +4809,15 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models/storage.py:25 +#: terminal/models/storage.py:26 msgid "Default storage" msgstr "默认存储" -#: terminal/models/storage.py:113 terminal/models/terminal.py:108 +#: terminal/models/storage.py:130 terminal/models/terminal.py:108 msgid "Command storage" msgstr "命令存储" -#: terminal/models/storage.py:173 terminal/models/terminal.py:109 +#: terminal/models/storage.py:190 terminal/models/terminal.py:109 msgid "Replay storage" msgstr "录像存储" @@ -4934,14 +4934,26 @@ msgid "Port invalid" msgstr "端口无效" #: terminal/serializers/storage.py:159 +msgid "Index by date" +msgstr "按日期建索引" + +#: terminal/serializers/storage.py:160 +msgid "Whether to create an index by date" +msgstr "是否根据日期动态建立索引" + +#: terminal/serializers/storage.py:163 +msgid "Index prefix" +msgstr "索引前缀" + +#: terminal/serializers/storage.py:166 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:161 +#: terminal/serializers/storage.py:168 msgid "Doc type" msgstr "文档类型" -#: terminal/serializers/storage.py:163 +#: terminal/serializers/storage.py:170 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index c7ae7060e..997bd30fd 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- # +import pytz +import inspect + from datetime import datetime from functools import reduce, partial from itertools import groupby -import pytz from uuid import UUID -import inspect from django.utils.translation import gettext_lazy as _ from django.db.models import QuerySet as DJQuerySet @@ -15,6 +16,7 @@ from elasticsearch.exceptions import RequestError, NotFoundError from common.utils.common import lazyproperty from common.utils import get_logger +from common.utils.timezone import local_now_date_display, utc_now from common.exceptions import JMSException from .models import AbstractSessionCommand @@ -28,12 +30,13 @@ class InvalidElasticsearch(JMSException): class CommandStore(object): def __init__(self, config): - hosts = config.get("HOSTS") - kwargs = config.get("OTHER", {}) - self.index = config.get("INDEX") or 'jumpserver' self.doc_type = config.get("DOC_TYPE") or '_doc' + self.index_prefix = config.get('INDEX') or 'jumpserver' + self.is_index_by_date = bool(config.get('INDEX_BY_DATE')) self.exact_fields = {} self.match_fields = {} + hosts = config.get("HOSTS") + kwargs = config.get("OTHER", {}) ignore_verify_certs = kwargs.pop('IGNORE_VERIFY_CERTS', False) if ignore_verify_certs: @@ -50,6 +53,17 @@ class CommandStore(object): else: self.match_fields.update(may_exact_fields) + self.init_index(config) + + def init_index(self, config): + if self.is_index_by_date: + date = local_now_date_display() + self.index = '%s-%s' % (self.index_prefix, date) + self.query_index = '%s-alias' % self.index_prefix + else: + self.index = config.get("INDEX") or 'jumpserver' + self.query_index = config.get("INDEX") or 'jumpserver' + def is_new_index_type(self): if not self.ping(timeout=3): return False @@ -101,11 +115,18 @@ class CommandStore(object): else: mappings = {'mappings': {'properties': properties}} + if self.is_index_by_date: + mappings['aliases'] = { + self.query_index: {} + } try: self.es.indices.create(self.index, body=mappings) return except RequestError as e: - logger.exception(e) + if e.error == 'resource_already_exists_exception': + logger.warning(e) + else: + logger.exception(e) @staticmethod def make_data(command): @@ -141,7 +162,7 @@ class CommandStore(object): body = self.get_query_body(**query) data = self.es.search( - index=self.index, doc_type=self.doc_type, body=body, from_=from_, size=size, + index=self.query_index, doc_type=self.doc_type, body=body, from_=from_, size=size, sort=sort ) source_data = [] @@ -154,7 +175,7 @@ class CommandStore(object): def count(self, **query): body = self.get_query_body(**query) - data = self.es.count(index=self.index, doc_type=self.doc_type, body=body) + data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body) return data["count"] def __getattr__(self, item): diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/storage.py index e0c815c04..311a55fda 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/storage.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import os + from importlib import import_module import jms_storage @@ -10,6 +11,7 @@ from django.conf import settings from common.mixins import CommonModelMixin from common.utils import get_logger from common.db.fields import EncryptJsonDictTextField +from common.utils.timezone import local_now_date_display from terminal.backends import TYPE_ENGINE_MAPPING from .terminal import Terminal from .command import Command @@ -63,6 +65,10 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): def type_server(self): return self.type == const.CommandStorageTypeChoices.server.value + @property + def type_es(self): + return self.type == const.CommandStorageTypeChoices.es.value + @property def type_null_or_server(self): return self.type_null or self.type_server @@ -73,6 +79,18 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): config.update({'TYPE': self.type}) return config + @property + def valid_config(self): + config = self.config + if self.type_es and config.get('INDEX_BY_DATE'): + engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type]) + store = engine_mod.CommandStore(config) + store._ensure_index_exists() + index_prefix = config.get('INDEX') or 'jumpserver' + date = local_now_date_display() + config['INDEX'] = '%s-%s' % (index_prefix, date) + return config + def is_valid(self): if self.type_null_or_server: return True diff --git a/apps/terminal/models/terminal.py b/apps/terminal/models/terminal.py index b5a753c96..4b585cfff 100644 --- a/apps/terminal/models/terminal.py +++ b/apps/terminal/models/terminal.py @@ -68,7 +68,7 @@ class StorageMixin: def get_command_storage_config(self): s = self.get_command_storage() if s: - config = s.config + config = s.valid_config else: config = settings.DEFAULT_TERMINAL_COMMAND_STORAGE return config diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index 81c885862..d04232d38 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -155,6 +155,10 @@ class CommandStorageTypeESSerializer(serializers.Serializer): child=serializers.CharField(validators=[command_storage_es_host_format_validator]), label=_('Hosts'), help_text=_(hosts_help_text), allow_null=True ) + INDEX_BY_DATE = serializers.BooleanField( + default=False, label=_('Index by date'), + help_text=_('Whether to create an index by date') + ) INDEX = serializers.CharField( max_length=1024, default='jumpserver', label=_('Index'), allow_null=True ) From 094446c548b1ab4ce48086783f6059e412440faa Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 10 May 2022 10:35:48 +0800 Subject: [PATCH 089/258] =?UTF-8?q?chore:=20=E5=8E=BB=E6=8E=89=E4=B8=80?= =?UTF-8?q?=E4=B8=AAworkflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lgtm.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/workflows/lgtm.yml diff --git a/.github/workflows/lgtm.yml b/.github/workflows/lgtm.yml deleted file mode 100644 index 379ca5135..000000000 --- a/.github/workflows/lgtm.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Send LGTM reaction - -on: - issue_comment: - types: [created] - pull_request_review: - types: [submitted] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@1.0.0 - - uses: micnncim/action-lgtm-reaction@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - trigger: '["^.?lgtm$"]' From b44fa6499464af9ab79166915065d97a5104bcf7 Mon Sep 17 00:00:00 2001 From: jiangweidong <80373698+F2C-Jiang@users.noreply.github.com> Date: Tue, 10 May 2022 16:30:25 +0800 Subject: [PATCH 090/258] =?UTF-8?q?perf:=20=E4=BC=81=E4=B8=9A=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E3=80=81=E9=92=89=E9=92=89=E5=B7=A5=E5=8D=95=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E5=A2=9E=E5=8A=A0=E6=8B=92=E7=BB=9D=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20(#8208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 工单直接审批增加拒绝功能 * feat: 翻译 * perf: 修改动作名词 * perf: 修改翻译 --- apps/locale/ja/LC_MESSAGES/django.po | 264 ++++++++--------- apps/locale/zh/LC_MESSAGES/django.po | 270 +++++++++--------- apps/templates/_base_double_screen.html | 1 - .../tickets/approve_check_password.html | 6 +- apps/tickets/views/approve.py | 11 +- 5 files changed, 282 insertions(+), 270 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 368cf98fb..ef8e52bb1 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-05 13:03+0800\n" +"POT-Creation-Date: 2022-05-10 10:46+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -30,8 +30,8 @@ msgstr "Acls" #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 -#: terminal/models/storage.py:24 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:32 +#: terminal/models/storage.py:25 terminal/models/task.py:16 +#: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:661 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -62,7 +62,7 @@ msgstr "アクティブ" #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 -#: terminal/models/storage.py:27 terminal/models/terminal.py:114 +#: terminal/models/storage.py:28 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 @@ -153,11 +153,11 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:121 -#: authentication/forms.py:15 authentication/forms.py:17 +#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/models.py:69 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 +#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:659 #: users/templates/users/_msg_user_created.html:12 #: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:195 @@ -305,7 +305,7 @@ msgstr "カテゴリ" #: assets/models/cmd_filter.py:82 assets/models/user.py:246 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:56 terminal/models/storage.py:136 +#: terminal/models/storage.py:57 terminal/models/storage.py:141 #: tickets/models/flow.py:56 tickets/models/ticket.py:131 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29 #: xpack/plugins/change_auth_plan/models/app.py:28 @@ -757,9 +757,9 @@ msgid "Date verified" msgstr "確認済みの日付" #: assets/models/base.py:177 audits/signal_handlers.py:48 -#: authentication/forms.py:22 -#: authentication/templates/authentication/login.html:181 -#: settings/serializers/auth/ldap.py:43 users/forms/profile.py:21 +#: authentication/forms.py:32 +#: authentication/templates/authentication/login.html:182 +#: settings/serializers/auth/ldap.py:45 users/forms/profile.py:22 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -1108,7 +1108,7 @@ msgid "Actions" msgstr "アクション" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:62 +#: settings/serializers/auth/ldap.py:64 #: xpack/plugins/change_auth_plan/serializers/base.py:42 msgid "Periodic perform" msgstr "定期的なパフォーマンス" @@ -1118,11 +1118,11 @@ msgstr "定期的なパフォーマンス" msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/base.py:36 +#: assets/serializers/base.py:39 msgid "Key password" msgstr "キーパスワード" -#: assets/serializers/base.py:49 +#: assets/serializers/base.py:52 msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" @@ -1313,11 +1313,11 @@ msgstr "プラットフォームのプッシュシステムユーザーを開始 msgid "Hosts count: {}" msgstr "ホスト数: {}" -#: assets/tasks/push_system_user.py:259 assets/tasks/push_system_user.py:292 +#: assets/tasks/push_system_user.py:263 assets/tasks/push_system_user.py:296 msgid "Push system users to assets: " msgstr "システムユーザーを資産にプッシュする:" -#: assets/tasks/push_system_user.py:271 +#: assets/tasks/push_system_user.py:275 msgid "Push system users to asset: " msgstr "システムユーザーをアセットにプッシュする:" @@ -1401,8 +1401,8 @@ msgstr "操作" msgid "Filename" msgstr "ファイル名" -#: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90 -#: tickets/views/approve.py:93 +#: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:90 +#: tickets/views/approve.py:98 #: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/asset.py:197 msgid "Success" @@ -1418,7 +1418,6 @@ msgstr "ファイル転送ログ" msgid "Create" msgstr "作成" -#: audits/models.py:56 rbac/tree.py:227 templates/_csv_import_export.html:18 #: audits/models.py:57 rbac/tree.py:226 msgid "View" msgstr "表示" @@ -1486,8 +1485,8 @@ msgstr "ユーザーエージェント" #: audits/models.py:126 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:64 users/models/user.py:684 -#: users/serializers/profile.py:121 +#: users/forms/profile.py:65 users/models/user.py:684 +#: users/serializers/profile.py:124 msgid "MFA" msgstr "MFA" @@ -1562,14 +1561,13 @@ msgstr "SSO" msgid "Auth Token" msgstr "認証トークン" -#: audits/signal_handlers.py:71 authentication/notifications.py:73 #: audits/signal_handlers.py:51 authentication/notifications.py:73 -#: authentication/views/login.py:164 authentication/views/wecom.py:181 +#: authentication/views/login.py:164 authentication/views/wecom.py:182 #: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企業微信" -#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183 +#: audits/signal_handlers.py:52 authentication/views/dingtalk.py:183 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" @@ -1936,15 +1934,15 @@ msgstr "期間は許可されていません" msgid "SSO auth closed" msgstr "SSO authは閉鎖されました" -#: authentication/errors.py:300 authentication/mixins.py:372 +#: authentication/errors.py:300 authentication/mixins.py:305 msgid "Your password is too simple, please change it for security" msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください" -#: authentication/errors.py:309 authentication/mixins.py:379 +#: authentication/errors.py:309 authentication/mixins.py:312 msgid "You should to change your password before login" msgstr "ログインする前にパスワードを変更する必要があります" -#: authentication/errors.py:318 authentication/mixins.py:386 +#: authentication/errors.py:318 authentication/mixins.py:319 msgid "Your password has expired, please reset before logging in" msgstr "" "パスワードの有効期限が切れました。ログインする前にリセットしてください。" @@ -1965,23 +1963,23 @@ msgstr "SMSコードを入力してください" msgid "Phone not set" msgstr "電話が設定されていない" -#: authentication/forms.py:35 +#: authentication/forms.py:45 msgid "{} days auto login" msgstr "{} 日自動ログイン" -#: authentication/forms.py:46 +#: authentication/forms.py:56 msgid "MFA Code" msgstr "MFAコード" -#: authentication/forms.py:47 +#: authentication/forms.py:57 msgid "MFA type" msgstr "MFAタイプ" -#: authentication/forms.py:60 users/forms/profile.py:27 +#: authentication/forms.py:70 users/forms/profile.py:28 msgid "MFA code" msgstr "MFAコード" -#: authentication/forms.py:62 +#: authentication/forms.py:72 msgid "Dynamic code" msgstr "動的コード" @@ -2037,11 +2035,11 @@ msgstr "電話番号を設定して有効にする" msgid "Clear phone number to disable" msgstr "無効にする電話番号をクリアする" -#: authentication/mixins.py:322 +#: authentication/mixins.py:255 msgid "The MFA type ({}) is not enabled" msgstr "MFAタイプ ({}) が有効になっていない" -#: authentication/mixins.py:362 +#: authentication/mixins.py:295 msgid "Please change your password" msgstr "パスワードを変更してください" @@ -2134,13 +2132,13 @@ msgstr "表示" #: authentication/templates/authentication/_access_key_modal.html:66 #: settings/serializers/security.py:39 users/models/user.py:556 -#: users/serializers/profile.py:111 users/templates/users/mfa_setting.html:61 +#: users/serializers/profile.py:114 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "無効化" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:557 users/serializers/profile.py:112 +#: users/models/user.py:557 users/serializers/profile.py:115 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2161,7 +2159,7 @@ msgid "Play CAPTCHA as audio file" msgstr "CAPTCHAをオーディオファイルとして再生する" #: authentication/templates/authentication/_captcha_field.html:15 -#: users/forms/profile.py:102 +#: users/forms/profile.py:103 msgid "Captcha" msgstr "キャプチャ" @@ -2279,23 +2277,23 @@ msgstr "" "公開鍵の更新が開始されなかった場合、アカウントにセキュリティ上の問題がある可" "能性があります" -#: authentication/templates/authentication/login.html:173 +#: authentication/templates/authentication/login.html:174 msgid "Welcome back, please enter username and password to login" msgstr "" "おかえりなさい、ログインするためにユーザー名とパスワードを入力してください" -#: authentication/templates/authentication/login.html:209 +#: authentication/templates/authentication/login.html:210 #: users/templates/users/forgot_password.html:15 #: users/templates/users/forgot_password.html:16 msgid "Forgot password" msgstr "パスワードを忘れた" -#: authentication/templates/authentication/login.html:216 +#: authentication/templates/authentication/login.html:217 #: templates/_header_bar.html:89 msgid "Login" msgstr "ログイン" -#: authentication/templates/authentication/login.html:223 +#: authentication/templates/authentication/login.html:224 msgid "More login options" msgstr "その他のログインオプション" @@ -2501,6 +2499,34 @@ msgstr "%(name)s は正常に更新されました" msgid "ugettext_lazy" msgstr "ugettext_lazy" +#: common/db/fields.py:80 +msgid "Marshal dict data to char field" +msgstr "チャーフィールドへのマーシャルディクトデータ" + +#: common/db/fields.py:84 +msgid "Marshal dict data to text field" +msgstr "テキストフィールドへのマーシャルディクトデータ" + +#: common/db/fields.py:96 +msgid "Marshal list data to char field" +msgstr "元帥リストデータをチャーフィールドに" + +#: common/db/fields.py:100 +msgid "Marshal list data to text field" +msgstr "マーシャルリストデータをテキストフィールドに" + +#: common/db/fields.py:104 +msgid "Marshal data to char field" +msgstr "チャーフィールドへのマーシャルデータ" + +#: common/db/fields.py:108 +msgid "Marshal data to text field" +msgstr "テキストフィールドへのマーシャルデータ" + +#: common/db/fields.py:150 +msgid "Encrypt field using Secret Key" +msgstr "Secret Keyを使用したフィールドの暗号化" + #: common/db/models.py:112 msgid "Updated by" msgstr "によって更新" @@ -2546,34 +2572,6 @@ msgstr "このアクションでは、MFAの確認が必要です。" msgid "Unexpect error occur" msgstr "予期しないエラーが発生します" -#: common/fields/model.py:80 -msgid "Marshal dict data to char field" -msgstr "チャーフィールドへのマーシャルディクトデータ" - -#: common/fields/model.py:84 -msgid "Marshal dict data to text field" -msgstr "テキストフィールドへのマーシャルディクトデータ" - -#: common/fields/model.py:96 -msgid "Marshal list data to char field" -msgstr "元帥リストデータをチャーフィールドに" - -#: common/fields/model.py:100 -msgid "Marshal list data to text field" -msgstr "マーシャルリストデータをテキストフィールドに" - -#: common/fields/model.py:104 -msgid "Marshal data to char field" -msgstr "チャーフィールドへのマーシャルデータ" - -#: common/fields/model.py:108 -msgid "Marshal data to text field" -msgstr "テキストフィールドへのマーシャルデータ" - -#: common/fields/model.py:150 -msgid "Encrypt field using Secret Key" -msgstr "Secret Keyを使用したフィールドの暗号化" - #: common/mixins/api/action.py:52 msgid "Request file format may be wrong" msgstr "リクエストファイルの形式が間違っている可能性があります" @@ -2586,15 +2584,15 @@ msgstr "は破棄されます" msgid "discard time" msgstr "時間を捨てる" -#: common/mixins/views.py:41 +#: common/mixins/views.py:42 msgid "Export all" msgstr "すべてエクスポート" -#: common/mixins/views.py:43 +#: common/mixins/views.py:44 msgid "Export only selected items" msgstr "選択項目のみエクスポート" -#: common/mixins/views.py:48 +#: common/mixins/views.py:49 #, python-format msgid "Export filtered: %s" msgstr "検索のエクスポート: %s" @@ -2711,7 +2709,7 @@ msgstr "" msgid "Notifications" msgstr "通知" -#: notifications/backends/__init__.py:10 users/forms/profile.py:101 +#: notifications/backends/__init__.py:10 users/forms/profile.py:102 #: users/models/user.py:663 msgid "Email" msgstr "メール" @@ -2733,12 +2731,12 @@ msgid "App ops" msgstr "アプリ操作" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:69 +#: settings/serializers/auth/ldap.py:71 msgid "Cycle perform" msgstr "サイクル実行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 -#: settings/serializers/auth/ldap.py:66 +#: settings/serializers/auth/ldap.py:68 msgid "Regularly perform" msgstr "定期的に実行する" @@ -2928,7 +2926,7 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:59 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:61 #: tickets/serializers/ticket/ticket.py:77 msgid "Organization" msgstr "組織" @@ -3134,15 +3132,15 @@ msgstr "質問があったら、管理者に連絡して下さい" msgid "My applications" msgstr "私のアプリケーション" -#: rbac/api/role.py:33 +#: rbac/api/role.py:34 msgid "Internal role, can't be destroy" msgstr "内部の役割は、破壊することはできません" -#: rbac/api/role.py:37 +#: rbac/api/role.py:38 msgid "The role has been bound to users, can't be destroy" msgstr "ロールはユーザーにバインドされており、破壊することはできません" -#: rbac/api/role.py:44 +#: rbac/api/role.py:45 msgid "Internal role, can't be update" msgstr "内部ロール、更新できません" @@ -3245,7 +3243,7 @@ msgstr "組織の役割バインディング" msgid "System role binding" msgstr "システムロールバインディング" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:127 +#: rbac/serializers/permission.py:26 users/serializers/profile.py:130 msgid "Perms" msgstr "パーマ" @@ -3507,40 +3505,40 @@ msgstr "ピン認証の有効化" msgid "Enable FeiShu Auth" msgstr "飛本認証の有効化" -#: settings/serializers/auth/ldap.py:38 +#: settings/serializers/auth/ldap.py:40 msgid "LDAP server" msgstr "LDAPサーバー" -#: settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/ldap.py:41 msgid "eg: ldap://localhost:389" msgstr "例: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:41 +#: settings/serializers/auth/ldap.py:43 msgid "Bind DN" msgstr "DN のバインド" -#: settings/serializers/auth/ldap.py:46 +#: settings/serializers/auth/ldap.py:48 msgid "User OU" msgstr "ユーザー OU" -#: settings/serializers/auth/ldap.py:47 +#: settings/serializers/auth/ldap.py:49 msgid "Use | split multi OUs" msgstr "使用 | splitマルチ OU" -#: settings/serializers/auth/ldap.py:50 +#: settings/serializers/auth/ldap.py:52 msgid "User search filter" msgstr "ユーザー検索フィルター" -#: settings/serializers/auth/ldap.py:51 +#: settings/serializers/auth/ldap.py:53 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)" -#: settings/serializers/auth/ldap.py:54 +#: settings/serializers/auth/ldap.py:56 msgid "User attr map" msgstr "ユーザー属性マッピング" -#: settings/serializers/auth/ldap.py:55 +#: settings/serializers/auth/ldap.py:57 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -3548,15 +3546,15 @@ msgstr "" "ユーザー属性マッピングは、LDAPのユーザー属性をjumpserverユーザーにマッピング" "する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" -#: settings/serializers/auth/ldap.py:73 +#: settings/serializers/auth/ldap.py:75 msgid "Connect timeout" msgstr "接続タイムアウト" -#: settings/serializers/auth/ldap.py:75 +#: settings/serializers/auth/ldap.py:77 msgid "Search paged size" msgstr "ページサイズを検索" -#: settings/serializers/auth/ldap.py:77 +#: settings/serializers/auth/ldap.py:79 msgid "Enable LDAP auth" msgstr "LDAP認証の有効化" @@ -4420,7 +4418,7 @@ msgstr "ドキュメント" msgid "Commercial support" msgstr "商用サポート" -#: templates/_header_bar.html:76 users/forms/profile.py:43 +#: templates/_header_bar.html:76 users/forms/profile.py:44 msgid "Profile" msgstr "プロフィール" @@ -4883,15 +4881,15 @@ msgstr "スレッド" msgid "Boot Time" msgstr "ブート時間" -#: terminal/models/storage.py:26 +#: terminal/models/storage.py:27 msgid "Default storage" msgstr "デフォルトのストレージ" -#: terminal/models/storage.py:130 terminal/models/terminal.py:108 +#: terminal/models/storage.py:135 terminal/models/terminal.py:108 msgid "Command storage" msgstr "コマンドストレージ" -#: terminal/models/storage.py:190 terminal/models/terminal.py:109 +#: terminal/models/storage.py:195 terminal/models/terminal.py:109 msgid "Replay storage" msgstr "再生ストレージ" @@ -5016,18 +5014,14 @@ msgid "Whether to create an index by date" msgstr "現在の日付に基づいてインデックスを動的に作成するかどうか" #: terminal/serializers/storage.py:163 -msgid "Index prefix" -msgstr "インデックス接頭辞" - -#: terminal/serializers/storage.py:166 msgid "Index" msgstr "インデックス" -#: terminal/serializers/storage.py:168 +#: terminal/serializers/storage.py:165 msgid "Doc type" msgstr "Docタイプ" -#: terminal/serializers/storage.py:170 +#: terminal/serializers/storage.py:167 msgid "Ignore Certificate Verification" msgstr "証明書の検証を無視する" @@ -5489,17 +5483,19 @@ msgid "Ticket information" msgstr "作業指示情報" #: tickets/templates/tickets/approve_check_password.html:19 -#, fuzzy -#| msgid "Ticket flow approval rule" +#: tickets/views/approve.py:25 msgid "Ticket approval" msgstr "作業指示の承認" #: tickets/templates/tickets/approve_check_password.html:35 -#: tickets/views/approve.py:25 msgid "Ticket direct approval" msgstr "作業指示の直接承認" -#: tickets/templates/tickets/approve_check_password.html:40 +#: tickets/templates/tickets/approve_check_password.html:39 +msgid "Ticket direct reject" +msgstr "作業指示の直接却下" + +#: tickets/templates/tickets/approve_check_password.html:44 msgid "Go Login" msgstr "ログイン" @@ -5511,14 +5507,18 @@ msgstr "" "なっています" #: tickets/views/approve.py:55 -msgid "Click the button to approve directly" -msgstr "ボタンをクリックすると、直接承認に成功します。" +msgid "Click the button below to approve or reject" +msgstr "下のボタンをクリックして同意または拒否。" #: tickets/views/approve.py:57 msgid "After successful authentication, this ticket can be approved directly" msgstr "認証に成功した後、作業指示書は直接承認することができる。" -#: tickets/views/approve.py:84 +#: tickets/views/approve.py:79 +msgid "Illegal approval action" +msgstr "無効な承認アクション" + +#: tickets/views/approve.py:89 msgid "This user is not authorized to approve this ticket" msgstr "このユーザーはこの作業指示を承認する権限がありません" @@ -5558,7 +5558,7 @@ msgstr "MFAが有効化されていません" msgid "MFA method not support" msgstr "MFAメソッドはサポートしていません" -#: users/forms/profile.py:49 +#: users/forms/profile.py:50 msgid "" "When enabled, you will enter the MFA binding process the next time you log " "in. you can also directly bind in \"personal information -> quick " @@ -5567,11 +5567,11 @@ msgstr "" "有効にすると、次回のログイン時にマルチファクタ認証バインドプロセスに入りま" "す。(個人情報->クイック修正->MFAマルチファクタ認証の設定)で直接バインド!" -#: users/forms/profile.py:60 +#: users/forms/profile.py:61 msgid "* Enable MFA to make the account more secure." msgstr "* アカウントをより安全にするためにMFAを有効にします。" -#: users/forms/profile.py:69 +#: users/forms/profile.py:70 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -5580,56 +5580,56 @@ msgstr "" "あなたとあなたの会社を保護するために、アカウント、パスワード、キーの機密情報" "を適切に保管してください。(例: 複雑なパスワードの設定、MFAの有効化)" -#: users/forms/profile.py:76 +#: users/forms/profile.py:77 msgid "Finish" msgstr "仕上げ" -#: users/forms/profile.py:83 +#: users/forms/profile.py:84 msgid "New password" msgstr "新しいパスワード" -#: users/forms/profile.py:88 +#: users/forms/profile.py:89 msgid "Confirm password" msgstr "パスワードの確認" -#: users/forms/profile.py:96 +#: users/forms/profile.py:97 msgid "Password does not match" msgstr "パスワードが一致しない" -#: users/forms/profile.py:108 +#: users/forms/profile.py:109 msgid "Old password" msgstr "古いパスワード" -#: users/forms/profile.py:118 +#: users/forms/profile.py:119 msgid "Old password error" msgstr "古いパスワードエラー" -#: users/forms/profile.py:128 +#: users/forms/profile.py:129 msgid "Automatically configure and download the SSH key" msgstr "SSHキーの自動設定とダウンロード" -#: users/forms/profile.py:130 +#: users/forms/profile.py:131 msgid "ssh public key" msgstr "ssh公開キー" -#: users/forms/profile.py:131 +#: users/forms/profile.py:132 msgid "ssh-rsa AAAA..." msgstr "ssh-rsa AAAA.." -#: users/forms/profile.py:132 +#: users/forms/profile.py:133 msgid "Paste your id_rsa.pub here." msgstr "ここにid_rsa.pubを貼り付けます。" -#: users/forms/profile.py:145 +#: users/forms/profile.py:146 msgid "Public key should not be the same as your old one." msgstr "公開鍵は古いものと同じであってはなりません。" -#: users/forms/profile.py:149 users/serializers/profile.py:95 -#: users/serializers/profile.py:178 users/serializers/profile.py:205 +#: users/forms/profile.py:150 users/serializers/profile.py:98 +#: users/serializers/profile.py:181 users/serializers/profile.py:208 msgid "Not a valid ssh public key" msgstr "有効なssh公開鍵ではありません" -#: users/forms/profile.py:160 users/models/user.py:692 +#: users/forms/profile.py:161 users/models/user.py:692 msgid "Public key" msgstr "公開キー" @@ -5724,23 +5724,23 @@ msgstr "SSHキーのリセット" msgid "Reset MFA" msgstr "MFAのリセット" -#: users/serializers/profile.py:29 +#: users/serializers/profile.py:30 msgid "The old password is incorrect" msgstr "古いパスワードが正しくありません" -#: users/serializers/profile.py:36 users/serializers/profile.py:192 +#: users/serializers/profile.py:37 users/serializers/profile.py:195 msgid "Password does not match security rules" msgstr "パスワードがセキュリティルールと一致しない" -#: users/serializers/profile.py:40 +#: users/serializers/profile.py:41 msgid "The new password cannot be the last {} passwords" msgstr "新しいパスワードを最後の {} 個のパスワードにすることはできません" -#: users/serializers/profile.py:46 users/serializers/profile.py:66 +#: users/serializers/profile.py:49 users/serializers/profile.py:69 msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:144 users/serializers/user.py:140 +#: users/serializers/profile.py:147 users/serializers/user.py:140 msgid "Is first login" msgstr "最初のログインです" @@ -6773,11 +6773,11 @@ msgstr "ログアウトページのロゴ" msgid "Interface setting" msgstr "インターフェイスの設定" -#: xpack/plugins/license/api.py:43 +#: xpack/plugins/license/api.py:50 msgid "License import successfully" msgstr "ライセンスのインポートに成功" -#: xpack/plugins/license/api.py:44 +#: xpack/plugins/license/api.py:51 msgid "License is invalid" msgstr "ライセンスが無効です" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ad6b041be..daa3e5df9 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-05 13:03+0800\n" +"POT-Creation-Date: 2022-05-10 10:45+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -29,8 +29,8 @@ msgstr "访问控制" #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 -#: terminal/models/storage.py:24 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:32 +#: terminal/models/storage.py:25 terminal/models/task.py:16 +#: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:661 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -61,7 +61,7 @@ msgstr "激活中" #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 -#: terminal/models/storage.py:27 terminal/models/terminal.py:114 +#: terminal/models/storage.py:28 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 @@ -152,11 +152,11 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:121 -#: authentication/forms.py:15 authentication/forms.py:17 +#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/models.py:69 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:659 +#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/models/user.py:659 #: users/templates/users/_msg_user_created.html:12 #: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:195 @@ -300,7 +300,7 @@ msgstr "类别" #: assets/models/cmd_filter.py:82 assets/models/user.py:246 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:56 terminal/models/storage.py:136 +#: terminal/models/storage.py:57 terminal/models/storage.py:141 #: tickets/models/flow.py:56 tickets/models/ticket.py:131 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29 #: xpack/plugins/change_auth_plan/models/app.py:28 @@ -752,9 +752,9 @@ msgid "Date verified" msgstr "校验日期" #: assets/models/base.py:177 audits/signal_handlers.py:48 -#: authentication/forms.py:22 -#: authentication/templates/authentication/login.html:181 -#: settings/serializers/auth/ldap.py:43 users/forms/profile.py:21 +#: authentication/forms.py:32 +#: authentication/templates/authentication/login.html:182 +#: settings/serializers/auth/ldap.py:45 users/forms/profile.py:22 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -1100,7 +1100,7 @@ msgid "Actions" msgstr "动作" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:62 +#: settings/serializers/auth/ldap.py:64 #: xpack/plugins/change_auth_plan/serializers/base.py:42 msgid "Periodic perform" msgstr "定时执行" @@ -1110,11 +1110,11 @@ msgstr "定时执行" msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/base.py:36 +#: assets/serializers/base.py:39 msgid "Key password" msgstr "密钥密码" -#: assets/serializers/base.py:49 +#: assets/serializers/base.py:52 msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" @@ -1301,11 +1301,11 @@ msgstr "推送系统用户到平台: [{}]" msgid "Hosts count: {}" msgstr "主机数量: {}" -#: assets/tasks/push_system_user.py:259 assets/tasks/push_system_user.py:292 +#: assets/tasks/push_system_user.py:263 assets/tasks/push_system_user.py:296 msgid "Push system users to assets: " msgstr "推送系统用户到入资产: " -#: assets/tasks/push_system_user.py:271 +#: assets/tasks/push_system_user.py:275 msgid "Push system users to asset: " msgstr "推送系统用户到入资产: " @@ -1389,8 +1389,8 @@ msgstr "操作" msgid "Filename" msgstr "文件名" -#: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90 -#: tickets/views/approve.py:93 +#: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:90 +#: tickets/views/approve.py:98 #: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/asset.py:197 msgid "Success" @@ -1406,7 +1406,11 @@ msgstr "文件管理" msgid "Create" msgstr "创建" -#: audits/models.py:56 rbac/tree.py:227 templates/_csv_import_export.html:18 +#: audits/models.py:57 rbac/tree.py:226 +msgid "View" +msgstr "查看" + +#: audits/models.py:58 rbac/tree.py:227 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" @@ -1469,8 +1473,8 @@ msgstr "用户代理" #: audits/models.py:126 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:64 users/models/user.py:684 -#: users/serializers/profile.py:121 +#: users/forms/profile.py:65 users/models/user.py:684 +#: users/serializers/profile.py:124 msgid "MFA" msgstr "MFA" @@ -1545,13 +1549,13 @@ msgstr "SSO" msgid "Auth Token" msgstr "认证令牌" -#: audits/signal_handlers.py:71 authentication/notifications.py:73 +#: audits/signal_handlers.py:51 authentication/notifications.py:73 #: authentication/views/login.py:164 authentication/views/wecom.py:182 #: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企业微信" -#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183 +#: audits/signal_handlers.py:52 authentication/views/dingtalk.py:183 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" @@ -1910,15 +1914,15 @@ msgstr "该 时间段 不被允许登录" msgid "SSO auth closed" msgstr "SSO 认证关闭了" -#: authentication/errors.py:300 authentication/mixins.py:372 +#: authentication/errors.py:300 authentication/mixins.py:305 msgid "Your password is too simple, please change it for security" msgstr "你的密码过于简单,为了安全,请修改" -#: authentication/errors.py:309 authentication/mixins.py:379 +#: authentication/errors.py:309 authentication/mixins.py:312 msgid "You should to change your password before login" msgstr "登录完成前,请先修改密码" -#: authentication/errors.py:318 authentication/mixins.py:386 +#: authentication/errors.py:318 authentication/mixins.py:319 msgid "Your password has expired, please reset before logging in" msgstr "您的密码已过期,先修改再登录" @@ -1938,23 +1942,23 @@ msgstr "请输入短信验证码" msgid "Phone not set" msgstr "手机号没有设置" -#: authentication/forms.py:35 +#: authentication/forms.py:45 msgid "{} days auto login" msgstr "{} 天内自动登录" -#: authentication/forms.py:46 +#: authentication/forms.py:56 msgid "MFA Code" msgstr "MFA 验证码" -#: authentication/forms.py:47 +#: authentication/forms.py:57 msgid "MFA type" msgstr "MFA 类型" -#: authentication/forms.py:60 users/forms/profile.py:27 +#: authentication/forms.py:70 users/forms/profile.py:28 msgid "MFA code" msgstr "MFA 验证码" -#: authentication/forms.py:62 +#: authentication/forms.py:72 msgid "Dynamic code" msgstr "动态码" @@ -2010,11 +2014,11 @@ msgstr "设置手机号码启用" msgid "Clear phone number to disable" msgstr "清空手机号码禁用" -#: authentication/mixins.py:322 +#: authentication/mixins.py:255 msgid "The MFA type ({}) is not enabled" msgstr "该 MFA ({}) 方式没有启用" -#: authentication/mixins.py:362 +#: authentication/mixins.py:295 msgid "Please change your password" msgstr "请修改密码" @@ -2107,13 +2111,13 @@ msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 #: settings/serializers/security.py:39 users/models/user.py:556 -#: users/serializers/profile.py:111 users/templates/users/mfa_setting.html:61 +#: users/serializers/profile.py:114 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:557 users/serializers/profile.py:112 +#: users/models/user.py:557 users/serializers/profile.py:115 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2134,7 +2138,7 @@ msgid "Play CAPTCHA as audio file" msgstr "语言播放验证码" #: authentication/templates/authentication/_captcha_field.html:15 -#: users/forms/profile.py:102 +#: users/forms/profile.py:103 msgid "Captcha" msgstr "验证码" @@ -2244,22 +2248,22 @@ msgid "" "security issues" msgstr "如果这次公钥更新不是由你发起的,那么你的账号可能存在安全问题" -#: authentication/templates/authentication/login.html:173 +#: authentication/templates/authentication/login.html:174 msgid "Welcome back, please enter username and password to login" msgstr "欢迎回来,请输入用户名和密码登录" -#: authentication/templates/authentication/login.html:209 +#: authentication/templates/authentication/login.html:210 #: users/templates/users/forgot_password.html:15 #: users/templates/users/forgot_password.html:16 msgid "Forgot password" msgstr "忘记密码" -#: authentication/templates/authentication/login.html:216 +#: authentication/templates/authentication/login.html:217 #: templates/_header_bar.html:89 msgid "Login" msgstr "登录" -#: authentication/templates/authentication/login.html:223 +#: authentication/templates/authentication/login.html:224 msgid "More login options" msgstr "更多登录方式" @@ -2465,6 +2469,34 @@ msgstr "%(name)s 更新成功" msgid "ugettext_lazy" msgstr "ugettext_lazy" +#: common/db/fields.py:80 +msgid "Marshal dict data to char field" +msgstr "编码 dict 为 char" + +#: common/db/fields.py:84 +msgid "Marshal dict data to text field" +msgstr "编码 dict 为 text" + +#: common/db/fields.py:96 +msgid "Marshal list data to char field" +msgstr "编码 list 为 char" + +#: common/db/fields.py:100 +msgid "Marshal list data to text field" +msgstr "编码 list 为 text" + +#: common/db/fields.py:104 +msgid "Marshal data to char field" +msgstr "编码数据为 char" + +#: common/db/fields.py:108 +msgid "Marshal data to text field" +msgstr "编码数据为 text" + +#: common/db/fields.py:150 +msgid "Encrypt field using Secret Key" +msgstr "加密的字段" + #: common/db/models.py:112 msgid "Updated by" msgstr "更新人" @@ -2510,34 +2542,6 @@ msgstr "这个操作需要验证 MFA" msgid "Unexpect error occur" msgstr "发生意外错误" -#: common/fields/model.py:80 -msgid "Marshal dict data to char field" -msgstr "编码 dict 为 char" - -#: common/fields/model.py:84 -msgid "Marshal dict data to text field" -msgstr "编码 dict 为 text" - -#: common/fields/model.py:96 -msgid "Marshal list data to char field" -msgstr "编码 list 为 char" - -#: common/fields/model.py:100 -msgid "Marshal list data to text field" -msgstr "编码 list 为 text" - -#: common/fields/model.py:104 -msgid "Marshal data to char field" -msgstr "编码数据为 char" - -#: common/fields/model.py:108 -msgid "Marshal data to text field" -msgstr "编码数据为 text" - -#: common/fields/model.py:150 -msgid "Encrypt field using Secret Key" -msgstr "加密的字段" - #: common/mixins/api/action.py:52 msgid "Request file format may be wrong" msgstr "上传的文件格式错误 或 其它类型资源的文件" @@ -2550,15 +2554,15 @@ msgstr "忽略的" msgid "discard time" msgstr "忽略时间" -#: common/mixins/views.py:41 +#: common/mixins/views.py:42 msgid "Export all" msgstr "导出所有" -#: common/mixins/views.py:43 +#: common/mixins/views.py:44 msgid "Export only selected items" msgstr "仅导出选择项" -#: common/mixins/views.py:48 +#: common/mixins/views.py:49 #, python-format msgid "Export filtered: %s" msgstr "导出搜素: %s" @@ -2670,7 +2674,7 @@ msgstr "" msgid "Notifications" msgstr "通知" -#: notifications/backends/__init__.py:10 users/forms/profile.py:101 +#: notifications/backends/__init__.py:10 users/forms/profile.py:102 #: users/models/user.py:663 msgid "Email" msgstr "邮件" @@ -2692,12 +2696,12 @@ msgid "App ops" msgstr "作业中心" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:69 +#: settings/serializers/auth/ldap.py:71 msgid "Cycle perform" msgstr "周期执行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 -#: settings/serializers/auth/ldap.py:66 +#: settings/serializers/auth/ldap.py:68 msgid "Regularly perform" msgstr "定期执行" @@ -2886,7 +2890,7 @@ msgstr "组织管理" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:59 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:61 #: tickets/serializers/ticket/ticket.py:77 msgid "Organization" msgstr "组织" @@ -3090,15 +3094,15 @@ msgstr "如果有疑问或需求,请联系系统管理员" msgid "My applications" msgstr "我的应用" -#: rbac/api/role.py:33 +#: rbac/api/role.py:34 msgid "Internal role, can't be destroy" msgstr "内部角色,不能删除" -#: rbac/api/role.py:37 +#: rbac/api/role.py:38 msgid "The role has been bound to users, can't be destroy" msgstr "角色已绑定用户,不能删除" -#: rbac/api/role.py:44 +#: rbac/api/role.py:45 msgid "Internal role, can't be update" msgstr "内部角色,不能更新" @@ -3200,7 +3204,7 @@ msgstr "组织角色绑定" msgid "System role binding" msgstr "系统角色绑定" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:127 +#: rbac/serializers/permission.py:26 users/serializers/profile.py:130 msgid "Perms" msgstr "权限" @@ -3304,10 +3308,6 @@ msgstr "查看授权树" msgid "Execute batch command" msgstr "执行批量命令" -#: rbac/tree.py:226 -msgid "View" -msgstr "查看" - #: settings/api/alibaba_sms.py:31 settings/api/tencent_sms.py:35 msgid "test_phone is required" msgstr "测试手机号 该字段是必填项。" @@ -3466,40 +3466,40 @@ msgstr "启用钉钉认证" msgid "Enable FeiShu Auth" msgstr "启用飞书认证" -#: settings/serializers/auth/ldap.py:38 +#: settings/serializers/auth/ldap.py:40 msgid "LDAP server" msgstr "LDAP 地址" -#: settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/ldap.py:41 msgid "eg: ldap://localhost:389" msgstr "如: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:41 +#: settings/serializers/auth/ldap.py:43 msgid "Bind DN" msgstr "绑定 DN" -#: settings/serializers/auth/ldap.py:46 +#: settings/serializers/auth/ldap.py:48 msgid "User OU" msgstr "用户 OU" -#: settings/serializers/auth/ldap.py:47 +#: settings/serializers/auth/ldap.py:49 msgid "Use | split multi OUs" msgstr "多个 OU 使用 | 分割" -#: settings/serializers/auth/ldap.py:50 +#: settings/serializers/auth/ldap.py:52 msgid "User search filter" msgstr "用户过滤器" -#: settings/serializers/auth/ldap.py:51 +#: settings/serializers/auth/ldap.py:53 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: settings/serializers/auth/ldap.py:54 +#: settings/serializers/auth/ldap.py:56 msgid "User attr map" msgstr "用户属性映射" -#: settings/serializers/auth/ldap.py:55 +#: settings/serializers/auth/ldap.py:57 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -3507,15 +3507,15 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的用户需要属性" -#: settings/serializers/auth/ldap.py:73 +#: settings/serializers/auth/ldap.py:75 msgid "Connect timeout" msgstr "连接超时时间" -#: settings/serializers/auth/ldap.py:75 +#: settings/serializers/auth/ldap.py:77 msgid "Search paged size" msgstr "搜索分页数量" -#: settings/serializers/auth/ldap.py:77 +#: settings/serializers/auth/ldap.py:79 msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" @@ -4352,7 +4352,7 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:76 users/forms/profile.py:43 +#: templates/_header_bar.html:76 users/forms/profile.py:44 msgid "Profile" msgstr "个人信息" @@ -4809,15 +4809,15 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models/storage.py:26 +#: terminal/models/storage.py:27 msgid "Default storage" msgstr "默认存储" -#: terminal/models/storage.py:130 terminal/models/terminal.py:108 +#: terminal/models/storage.py:135 terminal/models/terminal.py:108 msgid "Command storage" msgstr "命令存储" -#: terminal/models/storage.py:190 terminal/models/terminal.py:109 +#: terminal/models/storage.py:195 terminal/models/terminal.py:109 msgid "Replay storage" msgstr "录像存储" @@ -4942,18 +4942,14 @@ msgid "Whether to create an index by date" msgstr "是否根据日期动态建立索引" #: terminal/serializers/storage.py:163 -msgid "Index prefix" -msgstr "索引前缀" - -#: terminal/serializers/storage.py:166 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:168 +#: terminal/serializers/storage.py:165 msgid "Doc type" msgstr "文档类型" -#: terminal/serializers/storage.py:170 +#: terminal/serializers/storage.py:167 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" @@ -5411,15 +5407,19 @@ msgid "Ticket information" msgstr "工单信息" #: tickets/templates/tickets/approve_check_password.html:19 +#: tickets/views/approve.py:25 msgid "Ticket approval" msgstr "工单审批" #: tickets/templates/tickets/approve_check_password.html:35 -#: tickets/views/approve.py:25 msgid "Ticket direct approval" msgstr "工单直接审批" -#: tickets/templates/tickets/approve_check_password.html:40 +#: tickets/templates/tickets/approve_check_password.html:39 +msgid "Ticket direct reject" +msgstr "工单直接拒绝" + +#: tickets/templates/tickets/approve_check_password.html:44 msgid "Go Login" msgstr "去登录" @@ -5429,14 +5429,18 @@ msgid "" msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期" #: tickets/views/approve.py:55 -msgid "Click the button to approve directly" -msgstr "点击按钮,即可直接审批成功" +msgid "Click the button below to approve or reject" +msgstr "点击下方按钮同意或者拒绝" #: tickets/views/approve.py:57 msgid "After successful authentication, this ticket can be approved directly" msgstr "认证成功后,工单可直接审批" -#: tickets/views/approve.py:84 +#: tickets/views/approve.py:79 +msgid "Illegal approval action" +msgstr "无效的审批动作" + +#: tickets/views/approve.py:89 msgid "This user is not authorized to approve this ticket" msgstr "此用户无权审批此工单" @@ -5476,7 +5480,7 @@ msgstr "MFA 多因子认证没有开启" msgid "MFA method not support" msgstr "不支持该 MFA 方式" -#: users/forms/profile.py:49 +#: users/forms/profile.py:50 msgid "" "When enabled, you will enter the MFA binding process the next time you log " "in. you can also directly bind in \"personal information -> quick " @@ -5485,11 +5489,11 @@ msgstr "" "启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速" "修改->设置 MFA 多因子认证)中直接绑定!" -#: users/forms/profile.py:60 +#: users/forms/profile.py:61 msgid "* Enable MFA to make the account more secure." msgstr "* 启用 MFA 多因子认证,使账号更加安全。" -#: users/forms/profile.py:69 +#: users/forms/profile.py:70 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -5498,56 +5502,56 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息;(如:" "设置复杂密码,并启用 MFA 多因子认证)" -#: users/forms/profile.py:76 +#: users/forms/profile.py:77 msgid "Finish" msgstr "完成" -#: users/forms/profile.py:83 +#: users/forms/profile.py:84 msgid "New password" msgstr "新密码" -#: users/forms/profile.py:88 +#: users/forms/profile.py:89 msgid "Confirm password" msgstr "确认密码" -#: users/forms/profile.py:96 +#: users/forms/profile.py:97 msgid "Password does not match" msgstr "密码不一致" -#: users/forms/profile.py:108 +#: users/forms/profile.py:109 msgid "Old password" msgstr "原来密码" -#: users/forms/profile.py:118 +#: users/forms/profile.py:119 msgid "Old password error" msgstr "原来密码错误" -#: users/forms/profile.py:128 +#: users/forms/profile.py:129 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms/profile.py:130 +#: users/forms/profile.py:131 msgid "ssh public key" msgstr "SSH公钥" -#: users/forms/profile.py:131 +#: users/forms/profile.py:132 msgid "ssh-rsa AAAA..." msgstr "ssh-rsa AAAA..." -#: users/forms/profile.py:132 +#: users/forms/profile.py:133 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms/profile.py:145 +#: users/forms/profile.py:146 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms/profile.py:149 users/serializers/profile.py:95 -#: users/serializers/profile.py:178 users/serializers/profile.py:205 +#: users/forms/profile.py:150 users/serializers/profile.py:98 +#: users/serializers/profile.py:181 users/serializers/profile.py:208 msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/profile.py:160 users/models/user.py:692 +#: users/forms/profile.py:161 users/models/user.py:692 msgid "Public key" msgstr "SSH公钥" @@ -5642,23 +5646,23 @@ msgstr "重置 SSH 密钥" msgid "Reset MFA" msgstr "重置 MFA" -#: users/serializers/profile.py:29 +#: users/serializers/profile.py:30 msgid "The old password is incorrect" msgstr "旧密码错误" -#: users/serializers/profile.py:36 users/serializers/profile.py:192 +#: users/serializers/profile.py:37 users/serializers/profile.py:195 msgid "Password does not match security rules" msgstr "密码不满足安全规则" -#: users/serializers/profile.py:40 +#: users/serializers/profile.py:41 msgid "The new password cannot be the last {} passwords" msgstr "新密码不能是最近 {} 次的密码" -#: users/serializers/profile.py:46 users/serializers/profile.py:66 +#: users/serializers/profile.py:49 users/serializers/profile.py:69 msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:144 users/serializers/user.py:140 +#: users/serializers/profile.py:147 users/serializers/user.py:140 msgid "Is first login" msgstr "首次登录" @@ -6677,11 +6681,11 @@ msgstr "退出页面logo" msgid "Interface setting" msgstr "界面设置" -#: xpack/plugins/license/api.py:43 +#: xpack/plugins/license/api.py:50 msgid "License import successfully" msgstr "许可证导入成功" -#: xpack/plugins/license/api.py:44 +#: xpack/plugins/license/api.py:51 msgid "License is invalid" msgstr "无效的许可证" diff --git a/apps/templates/_base_double_screen.html b/apps/templates/_base_double_screen.html index cd610fea5..a78f9810e 100644 --- a/apps/templates/_base_double_screen.html +++ b/apps/templates/_base_double_screen.html @@ -14,7 +14,6 @@
    -

    JumpServer {% trans 'Client' %} v1.1.4

    +

    JumpServer {% trans 'Client' %} v1.1.5

    {% trans 'JumpServer Client, currently used to launch the client, now only support launch RDP SSH client, The Telnet client will next' %}

    From 4f37b2b920acbb8cdb9a527ad0a4c9b4b03f19fe Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 12 May 2022 11:27:32 +0800 Subject: [PATCH 097/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20setting=20?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=EF=BC=8C=E9=81=BF=E5=85=8D=E9=81=97=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/api/public.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index 724001e28..9753c1bdd 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -41,14 +41,6 @@ class PublicSettingApi(OpenPublicSettingApi): def get_object(self): values = super().get_object() - - serializer = self.serializer_class() - field_names = list(serializer.fields.keys()) - - for name in field_names: - if hasattr(settings, name): - values[name] = getattr(settings, name) - values.update({ "XPACK_LICENSE_IS_VALID": has_valid_xpack_license(), "XPACK_LICENSE_INFO": get_xpack_license_info(), @@ -61,6 +53,15 @@ class PublicSettingApi(OpenPublicSettingApi): 'SECURITY_PASSWORD_SPECIAL_CHAR': settings.SECURITY_PASSWORD_SPECIAL_CHAR, }, }) + + serializer = self.serializer_class() + field_names = list(serializer.fields.keys()) + for name in field_names: + if name in values: + continue + # 提前把异常爆出来 + values[name] = getattr(settings, name) return values + From 6409b7deee81969095ff8333a94b5987289b7da5 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 12 May 2022 14:47:35 +0800 Subject: [PATCH 098/258] =?UTF-8?q?feat:=20Endpoint=E6=B7=BB=E5=8A=A0Redis?= =?UTF-8?q?=20Port=20(#8225)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jiangjie.Bai Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com> --- apps/locale/ja/LC_MESSAGES/django.po | 110 +++++++++--------- apps/locale/zh/LC_MESSAGES/django.po | 110 +++++++++--------- .../migrations/0049_endpoint_redis_port.py | 20 ++++ apps/terminal/models/endpoint.py | 1 + apps/terminal/serializers/endpoint.py | 3 +- 5 files changed, 137 insertions(+), 107 deletions(-) create mode 100644 apps/terminal/migrations/0049_endpoint_redis_port.py diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a4c8fddc7..a8145faa8 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-11 10:44+0800\n" +"POT-Creation-Date: 2022-05-12 14:39+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,7 +29,7 @@ msgstr "Acls" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:59 #: terminal/models/storage.py:25 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:661 @@ -38,12 +38,12 @@ msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:61 +#: assets/models/user.py:247 terminal/models/endpoint.py:62 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:62 +#: assets/models/user.py:247 terminal/models/endpoint.py:63 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" @@ -61,7 +61,7 @@ msgstr "アクティブ" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 -#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 +#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:69 #: terminal/models/storage.py:28 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 @@ -182,7 +182,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:37 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:38 msgid "IP" msgstr "IP" @@ -759,7 +759,7 @@ msgstr "確認済みの日付" #: assets/models/base.py:177 audits/signal_handlers.py:48 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:182 -#: settings/serializers/auth/ldap.py:45 users/forms/profile.py:22 +#: settings/serializers/auth/ldap.py:46 users/forms/profile.py:22 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -1108,7 +1108,7 @@ msgid "Actions" msgstr "アクション" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:64 +#: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:42 msgid "Periodic perform" msgstr "定期的なパフォーマンス" @@ -2731,12 +2731,12 @@ msgid "App ops" msgstr "アプリ操作" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:71 +#: settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "サイクル実行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 -#: settings/serializers/auth/ldap.py:68 +#: settings/serializers/auth/ldap.py:69 msgid "Regularly perform" msgstr "定期的に実行する" @@ -2926,7 +2926,7 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:61 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 #: tickets/serializers/ticket/ticket.py:77 msgid "Organization" msgstr "組織" @@ -3505,40 +3505,40 @@ msgstr "ピン認証の有効化" msgid "Enable FeiShu Auth" msgstr "飛本認証の有効化" -#: settings/serializers/auth/ldap.py:40 +#: settings/serializers/auth/ldap.py:41 msgid "LDAP server" msgstr "LDAPサーバー" -#: settings/serializers/auth/ldap.py:41 +#: settings/serializers/auth/ldap.py:42 msgid "eg: ldap://localhost:389" msgstr "例: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:43 +#: settings/serializers/auth/ldap.py:44 msgid "Bind DN" msgstr "DN のバインド" -#: settings/serializers/auth/ldap.py:48 +#: settings/serializers/auth/ldap.py:49 msgid "User OU" msgstr "ユーザー OU" -#: settings/serializers/auth/ldap.py:49 +#: settings/serializers/auth/ldap.py:50 msgid "Use | split multi OUs" msgstr "使用 | splitマルチ OU" -#: settings/serializers/auth/ldap.py:52 +#: settings/serializers/auth/ldap.py:53 msgid "User search filter" msgstr "ユーザー検索フィルター" -#: settings/serializers/auth/ldap.py:53 +#: settings/serializers/auth/ldap.py:54 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)" -#: settings/serializers/auth/ldap.py:56 +#: settings/serializers/auth/ldap.py:57 msgid "User attr map" msgstr "ユーザー属性マッピング" -#: settings/serializers/auth/ldap.py:57 +#: settings/serializers/auth/ldap.py:58 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -3546,15 +3546,15 @@ msgstr "" "ユーザー属性マッピングは、LDAPのユーザー属性をjumpserverユーザーにマッピング" "する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" -#: settings/serializers/auth/ldap.py:75 +#: settings/serializers/auth/ldap.py:76 msgid "Connect timeout" msgstr "接続タイムアウト" -#: settings/serializers/auth/ldap.py:77 +#: settings/serializers/auth/ldap.py:78 msgid "Search paged size" msgstr "ページサイズを検索" -#: settings/serializers/auth/ldap.py:79 +#: settings/serializers/auth/ldap.py:80 msgid "Enable LDAP auth" msgstr "LDAP認証の有効化" @@ -4269,104 +4269,104 @@ msgstr "XRDPの有効化" msgid "Enable SSH Client" msgstr "SSH Clientの有効化" -#: settings/utils/ldap.py:419 +#: settings/utils/ldap.py:467 msgid "ldap:// or ldaps:// protocol is used." msgstr "ldap:// または ldaps:// プロトコルが使用されます。" -#: settings/utils/ldap.py:430 +#: settings/utils/ldap.py:478 msgid "Host or port is disconnected: {}" msgstr "ホストまたはポートが切断されました: {}" -#: settings/utils/ldap.py:432 +#: settings/utils/ldap.py:480 msgid "The port is not the port of the LDAP service: {}" msgstr "ポートはLDAPサービスのポートではありません: {}" -#: settings/utils/ldap.py:434 +#: settings/utils/ldap.py:482 msgid "Please add certificate: {}" msgstr "証明書を追加してください: {}" -#: settings/utils/ldap.py:438 settings/utils/ldap.py:465 -#: settings/utils/ldap.py:495 settings/utils/ldap.py:523 +#: settings/utils/ldap.py:486 settings/utils/ldap.py:513 +#: settings/utils/ldap.py:543 settings/utils/ldap.py:571 msgid "Unknown error: {}" msgstr "不明なエラー: {}" -#: settings/utils/ldap.py:452 +#: settings/utils/ldap.py:500 msgid "Bind DN or Password incorrect" msgstr "DNまたはパスワードのバインドが正しくありません" -#: settings/utils/ldap.py:459 +#: settings/utils/ldap.py:507 msgid "Please enter Bind DN: {}" msgstr "バインドDN: {} を入力してください" -#: settings/utils/ldap.py:461 +#: settings/utils/ldap.py:509 msgid "Please enter Password: {}" msgstr "パスワードを入力してください: {}" -#: settings/utils/ldap.py:463 +#: settings/utils/ldap.py:511 msgid "Please enter correct Bind DN and Password: {}" msgstr "正しいバインドDNとパスワードを入力してください: {}" -#: settings/utils/ldap.py:481 +#: settings/utils/ldap.py:529 msgid "Invalid User OU or User search filter: {}" msgstr "無効なユーザー OU またはユーザー検索フィルター: {}" -#: settings/utils/ldap.py:512 +#: settings/utils/ldap.py:560 msgid "LDAP User attr map not include: {}" msgstr "LDAP ユーザーattrマップは含まれません: {}" -#: settings/utils/ldap.py:519 +#: settings/utils/ldap.py:567 msgid "LDAP User attr map is not dict" msgstr "LDAPユーザーattrマップはdictではありません" -#: settings/utils/ldap.py:538 +#: settings/utils/ldap.py:586 msgid "LDAP authentication is not enabled" msgstr "LDAP 認証が有効になっていない" -#: settings/utils/ldap.py:556 +#: settings/utils/ldap.py:604 msgid "Error (Invalid LDAP server): {}" msgstr "エラー (LDAPサーバーが無効): {}" -#: settings/utils/ldap.py:558 +#: settings/utils/ldap.py:606 msgid "Error (Invalid Bind DN): {}" msgstr "エラー (DNのバインドが無効): {}" -#: settings/utils/ldap.py:560 +#: settings/utils/ldap.py:608 msgid "Error (Invalid LDAP User attr map): {}" msgstr "エラー (LDAPユーザーattrマップが無効): {}" -#: settings/utils/ldap.py:562 +#: settings/utils/ldap.py:610 msgid "Error (Invalid User OU or User search filter): {}" msgstr "エラー (ユーザーOUまたはユーザー検索フィルターが無効): {}" -#: settings/utils/ldap.py:564 +#: settings/utils/ldap.py:612 msgid "Error (Not enabled LDAP authentication): {}" msgstr "エラー (LDAP認証が有効化されていません): {}" -#: settings/utils/ldap.py:566 +#: settings/utils/ldap.py:614 msgid "Error (Unknown): {}" msgstr "エラー (不明): {}" -#: settings/utils/ldap.py:569 +#: settings/utils/ldap.py:617 msgid "Succeed: Match {} s user" msgstr "成功: {} 人のユーザーに一致" -#: settings/utils/ldap.py:602 +#: settings/utils/ldap.py:650 msgid "Authentication failed (configuration incorrect): {}" msgstr "認証に失敗しました (設定が正しくありません): {}" -#: settings/utils/ldap.py:604 +#: settings/utils/ldap.py:652 msgid "Authentication failed (before login check failed): {}" msgstr "認証に失敗しました (ログインチェックが失敗する前): {}" -#: settings/utils/ldap.py:606 +#: settings/utils/ldap.py:654 msgid "Authentication failed (username or password incorrect): {}" msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" -#: settings/utils/ldap.py:608 +#: settings/utils/ldap.py:656 msgid "Authentication failed (Unknown): {}" msgstr "認証に失敗しました (不明): {}" -#: settings/utils/ldap.py:611 +#: settings/utils/ldap.py:659 msgid "Authentication success: {}" msgstr "認証成功: {}" @@ -4741,18 +4741,22 @@ msgstr "MariaDB ポート" msgid "PostgreSQL Port" msgstr "PostgreSQL ポート" -#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:66 -#: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 +#: terminal/models/endpoint.py:20 +msgid "Redis Port" +msgstr "Redis ポート" + +#: terminal/models/endpoint.py:26 terminal/models/endpoint.py:67 +#: terminal/serializers/endpoint.py:41 terminal/serializers/storage.py:37 #: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 #: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/endpoint.py:59 +#: terminal/models/endpoint.py:60 msgid "IP group" msgstr "IP グループ" -#: terminal/models/endpoint.py:71 +#: terminal/models/endpoint.py:72 msgid "Endpoint rule" msgstr "エンドポイントルール" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 1ad70f740..9f099484e 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-11 10:44+0800\n" +"POT-Creation-Date: 2022-05-12 14:39+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -28,7 +28,7 @@ msgstr "访问控制" #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:58 +#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:59 #: terminal/models/storage.py:25 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:661 @@ -37,12 +37,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:61 +#: assets/models/user.py:247 terminal/models/endpoint.py:62 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:62 +#: assets/models/user.py:247 terminal/models/endpoint.py:63 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -60,7 +60,7 @@ msgstr "激活中" #: assets/models/domain.py:64 assets/models/group.py:23 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 -#: terminal/models/endpoint.py:20 terminal/models/endpoint.py:68 +#: terminal/models/endpoint.py:21 terminal/models/endpoint.py:69 #: terminal/models/storage.py:28 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 @@ -180,7 +180,7 @@ msgstr "" #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:37 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:38 msgid "IP" msgstr "IP" @@ -754,7 +754,7 @@ msgstr "校验日期" #: assets/models/base.py:177 audits/signal_handlers.py:48 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:182 -#: settings/serializers/auth/ldap.py:45 users/forms/profile.py:22 +#: settings/serializers/auth/ldap.py:46 users/forms/profile.py:22 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -1100,7 +1100,7 @@ msgid "Actions" msgstr "动作" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:64 +#: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:42 msgid "Periodic perform" msgstr "定时执行" @@ -2696,12 +2696,12 @@ msgid "App ops" msgstr "作业中心" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:71 +#: settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "周期执行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 -#: settings/serializers/auth/ldap.py:68 +#: settings/serializers/auth/ldap.py:69 msgid "Regularly perform" msgstr "定期执行" @@ -2890,7 +2890,7 @@ msgstr "组织管理" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:80 #: orgs/models.py:211 rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:61 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 #: tickets/serializers/ticket/ticket.py:77 msgid "Organization" msgstr "组织" @@ -3466,40 +3466,40 @@ msgstr "启用钉钉认证" msgid "Enable FeiShu Auth" msgstr "启用飞书认证" -#: settings/serializers/auth/ldap.py:40 +#: settings/serializers/auth/ldap.py:41 msgid "LDAP server" msgstr "LDAP 地址" -#: settings/serializers/auth/ldap.py:41 +#: settings/serializers/auth/ldap.py:42 msgid "eg: ldap://localhost:389" msgstr "如: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:43 +#: settings/serializers/auth/ldap.py:44 msgid "Bind DN" msgstr "绑定 DN" -#: settings/serializers/auth/ldap.py:48 +#: settings/serializers/auth/ldap.py:49 msgid "User OU" msgstr "用户 OU" -#: settings/serializers/auth/ldap.py:49 +#: settings/serializers/auth/ldap.py:50 msgid "Use | split multi OUs" msgstr "多个 OU 使用 | 分割" -#: settings/serializers/auth/ldap.py:52 +#: settings/serializers/auth/ldap.py:53 msgid "User search filter" msgstr "用户过滤器" -#: settings/serializers/auth/ldap.py:53 +#: settings/serializers/auth/ldap.py:54 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: settings/serializers/auth/ldap.py:56 +#: settings/serializers/auth/ldap.py:57 msgid "User attr map" msgstr "用户属性映射" -#: settings/serializers/auth/ldap.py:57 +#: settings/serializers/auth/ldap.py:58 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -3507,15 +3507,15 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的用户需要属性" -#: settings/serializers/auth/ldap.py:75 +#: settings/serializers/auth/ldap.py:76 msgid "Connect timeout" msgstr "连接超时时间" -#: settings/serializers/auth/ldap.py:77 +#: settings/serializers/auth/ldap.py:78 msgid "Search paged size" msgstr "搜索分页数量" -#: settings/serializers/auth/ldap.py:79 +#: settings/serializers/auth/ldap.py:80 msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" @@ -4207,104 +4207,104 @@ msgstr "启用 XRDP 服务" msgid "Enable SSH Client" msgstr "启用 SSH Client" -#: settings/utils/ldap.py:419 +#: settings/utils/ldap.py:467 msgid "ldap:// or ldaps:// protocol is used." msgstr "使用 ldap:// 或 ldaps:// 协议" -#: settings/utils/ldap.py:430 +#: settings/utils/ldap.py:478 msgid "Host or port is disconnected: {}" msgstr "主机或端口不可连接: {}" -#: settings/utils/ldap.py:432 +#: settings/utils/ldap.py:480 msgid "The port is not the port of the LDAP service: {}" msgstr "端口不是LDAP服务端口: {}" -#: settings/utils/ldap.py:434 +#: settings/utils/ldap.py:482 msgid "Please add certificate: {}" msgstr "请添加证书" -#: settings/utils/ldap.py:438 settings/utils/ldap.py:465 -#: settings/utils/ldap.py:495 settings/utils/ldap.py:523 +#: settings/utils/ldap.py:486 settings/utils/ldap.py:513 +#: settings/utils/ldap.py:543 settings/utils/ldap.py:571 msgid "Unknown error: {}" msgstr "未知错误: {}" -#: settings/utils/ldap.py:452 +#: settings/utils/ldap.py:500 msgid "Bind DN or Password incorrect" msgstr "绑定DN或密码错误" -#: settings/utils/ldap.py:459 +#: settings/utils/ldap.py:507 msgid "Please enter Bind DN: {}" msgstr "请输入绑定DN: {}" -#: settings/utils/ldap.py:461 +#: settings/utils/ldap.py:509 msgid "Please enter Password: {}" msgstr "请输入密码: {}" -#: settings/utils/ldap.py:463 +#: settings/utils/ldap.py:511 msgid "Please enter correct Bind DN and Password: {}" msgstr "请输入正确的绑定DN和密码: {}" -#: settings/utils/ldap.py:481 +#: settings/utils/ldap.py:529 msgid "Invalid User OU or User search filter: {}" msgstr "不合法的用户OU或用户过滤器: {}" -#: settings/utils/ldap.py:512 +#: settings/utils/ldap.py:560 msgid "LDAP User attr map not include: {}" msgstr "LDAP属性映射没有包含: {}" -#: settings/utils/ldap.py:519 +#: settings/utils/ldap.py:567 msgid "LDAP User attr map is not dict" msgstr "LDAP属性映射不合法" -#: settings/utils/ldap.py:538 +#: settings/utils/ldap.py:586 msgid "LDAP authentication is not enabled" msgstr "LDAP认证没有启用" -#: settings/utils/ldap.py:556 +#: settings/utils/ldap.py:604 msgid "Error (Invalid LDAP server): {}" msgstr "错误 (不合法的LDAP服务器地址): {}" -#: settings/utils/ldap.py:558 +#: settings/utils/ldap.py:606 msgid "Error (Invalid Bind DN): {}" msgstr "错误(不合法的绑定DN): {}" -#: settings/utils/ldap.py:560 +#: settings/utils/ldap.py:608 msgid "Error (Invalid LDAP User attr map): {}" msgstr "错误(不合法的LDAP属性映射): {}" -#: settings/utils/ldap.py:562 +#: settings/utils/ldap.py:610 msgid "Error (Invalid User OU or User search filter): {}" msgstr "错误(不合法的用户OU或用户过滤器): {}" -#: settings/utils/ldap.py:564 +#: settings/utils/ldap.py:612 msgid "Error (Not enabled LDAP authentication): {}" msgstr "错误(没有启用LDAP认证): {}" -#: settings/utils/ldap.py:566 +#: settings/utils/ldap.py:614 msgid "Error (Unknown): {}" msgstr "错误(未知): {}" -#: settings/utils/ldap.py:569 +#: settings/utils/ldap.py:617 msgid "Succeed: Match {} s user" msgstr "成功匹配 {} 个用户" -#: settings/utils/ldap.py:602 +#: settings/utils/ldap.py:650 msgid "Authentication failed (configuration incorrect): {}" msgstr "认证失败(配置错误): {}" -#: settings/utils/ldap.py:604 +#: settings/utils/ldap.py:652 msgid "Authentication failed (before login check failed): {}" msgstr "认证失败(登录前检查失败): {}" -#: settings/utils/ldap.py:606 +#: settings/utils/ldap.py:654 msgid "Authentication failed (username or password incorrect): {}" msgstr "认证失败 (用户名或密码不正确): {}" -#: settings/utils/ldap.py:608 +#: settings/utils/ldap.py:656 msgid "Authentication failed (Unknown): {}" msgstr "认证失败: (未知): {}" -#: settings/utils/ldap.py:611 +#: settings/utils/ldap.py:659 msgid "Authentication success: {}" msgstr "认证成功: {}" @@ -4669,18 +4669,22 @@ msgstr "MariaDB 端口" msgid "PostgreSQL Port" msgstr "PostgreSQL 端口" -#: terminal/models/endpoint.py:25 terminal/models/endpoint.py:66 -#: terminal/serializers/endpoint.py:40 terminal/serializers/storage.py:37 +#: terminal/models/endpoint.py:20 +msgid "Redis Port" +msgstr "Redis 端口" + +#: terminal/models/endpoint.py:26 terminal/models/endpoint.py:67 +#: terminal/serializers/endpoint.py:41 terminal/serializers/storage.py:37 #: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 #: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 msgid "Endpoint" msgstr "端点" -#: terminal/models/endpoint.py:59 +#: terminal/models/endpoint.py:60 msgid "IP group" msgstr "IP 组" -#: terminal/models/endpoint.py:71 +#: terminal/models/endpoint.py:72 msgid "Endpoint rule" msgstr "端点规则" diff --git a/apps/terminal/migrations/0049_endpoint_redis_port.py b/apps/terminal/migrations/0049_endpoint_redis_port.py new file mode 100644 index 000000000..d1b1b38f1 --- /dev/null +++ b/apps/terminal/migrations/0049_endpoint_redis_port.py @@ -0,0 +1,20 @@ +# Generated by Django 3.1.14 on 2022-05-12 06:35 + +import common.db.fields +import django.core.validators +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0048_endpoint_endpointrule'), + ] + + operations = [ + migrations.AddField( + model_name='endpoint', + name='redis_port', + field=common.db.fields.PortField(default=63790, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Redis Port'), + ), + ] diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/endpoint.py index c95bfb752..ec2f0bef6 100644 --- a/apps/terminal/models/endpoint.py +++ b/apps/terminal/models/endpoint.py @@ -17,6 +17,7 @@ class Endpoint(JMSModel): mysql_port = PortField(default=33060, verbose_name=_('MySQL Port')) mariadb_port = PortField(default=33061, verbose_name=_('MariaDB Port')) postgresql_port = PortField(default=54320, verbose_name=_('PostgreSQL Port')) + redis_port = PortField(default=63790, verbose_name=_('Redis Port')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) default_id = '00000000-0000-0000-0000-000000000001' diff --git a/apps/terminal/serializers/endpoint.py b/apps/terminal/serializers/endpoint.py index 822c6b5c2..1c51c1604 100644 --- a/apps/terminal/serializers/endpoint.py +++ b/apps/terminal/serializers/endpoint.py @@ -16,7 +16,7 @@ class EndpointSerializer(BulkModelSerializer): 'host', 'https_port', 'http_port', 'ssh_port', 'rdp_port', 'mysql_port', 'mariadb_port', - 'postgresql_port', + 'postgresql_port', 'redis_port', ] fields = fields_mini + fields_small + [ 'comment', 'date_created', 'date_updated', 'created_by' @@ -29,6 +29,7 @@ class EndpointSerializer(BulkModelSerializer): 'mysql_port': {'default': 33060}, 'mariadb_port': {'default': 33061}, 'postgresql_port': {'default': 54320}, + 'redis_port': {'default': 63790}, } From 475678e29b0789173de71f8e4ae532a134d6ae97 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 13 May 2022 17:10:29 +0800 Subject: [PATCH 099/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AF=86?= =?UTF-8?q?=E7=A0=81=20write=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/fields.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 98919f2df..abddeca01 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -27,6 +27,10 @@ class ReadableHiddenField(serializers.HiddenField): class EncryptedField(serializers.CharField): + def __init__(self, **kwargs): + kwargs['write_only'] = True + super().__init__(**kwargs) + def to_internal_value(self, value): value = super().to_internal_value(value) return decrypt_password(value) From 1e6e59d8152833bd1b77727325458324012bf7da Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 16 May 2022 10:04:41 +0800 Subject: [PATCH 100/258] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20ipdb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 35ca802a3..24d9e1cd5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ *.mmdb filter=lfs diff=lfs merge=lfs -text *.mo filter=lfs diff=lfs merge=lfs -text +*.ipdb filter=lfs diff=lfs merge=lfs -text From 2a8f8dd709f296747bbf9a4aee1e49eece812b88 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 16 May 2022 12:18:23 +0800 Subject: [PATCH 101/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E4=B8=A4=E4=B8=AA=20ip=20=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/__init__.py | 1 - apps/common/utils/{geoip => ip}/__init__.py | 0 .../utils/{ => ip}/geoip/GeoLite2-City.mmdb | 0 apps/common/utils/ip/geoip/__init__.py | 1 + apps/common/utils/{ => ip}/geoip/utils.py | 20 ++++++++-------- apps/common/utils/ip/ipip/__init__.py | 3 +++ apps/common/utils/ip/ipip/ipipfree.ipdb | 3 +++ apps/common/utils/ip/ipip/utils.py | 23 +++++++++++++++++++ apps/common/utils/{ip.py => ip/utils.py} | 18 +++++++++++++++ 9 files changed, 57 insertions(+), 12 deletions(-) rename apps/common/utils/{geoip => ip}/__init__.py (100%) rename apps/common/utils/{ => ip}/geoip/GeoLite2-City.mmdb (100%) create mode 100644 apps/common/utils/ip/geoip/__init__.py rename apps/common/utils/{ => ip}/geoip/utils.py (69%) create mode 100644 apps/common/utils/ip/ipip/__init__.py create mode 100644 apps/common/utils/ip/ipip/ipipfree.ipdb create mode 100644 apps/common/utils/ip/ipip/utils.py rename apps/common/utils/{ip.py => ip/utils.py} (75%) diff --git a/apps/common/utils/__init__.py b/apps/common/utils/__init__.py index d76814506..87d36595b 100644 --- a/apps/common/utils/__init__.py +++ b/apps/common/utils/__init__.py @@ -9,4 +9,3 @@ from .crypto import * from .random import * from .jumpserver import * from .ip import * -from .geoip import * diff --git a/apps/common/utils/geoip/__init__.py b/apps/common/utils/ip/__init__.py similarity index 100% rename from apps/common/utils/geoip/__init__.py rename to apps/common/utils/ip/__init__.py diff --git a/apps/common/utils/geoip/GeoLite2-City.mmdb b/apps/common/utils/ip/geoip/GeoLite2-City.mmdb similarity index 100% rename from apps/common/utils/geoip/GeoLite2-City.mmdb rename to apps/common/utils/ip/geoip/GeoLite2-City.mmdb diff --git a/apps/common/utils/ip/geoip/__init__.py b/apps/common/utils/ip/geoip/__init__.py new file mode 100644 index 000000000..16281fe0b --- /dev/null +++ b/apps/common/utils/ip/geoip/__init__.py @@ -0,0 +1 @@ +from .utils import * diff --git a/apps/common/utils/geoip/utils.py b/apps/common/utils/ip/geoip/utils.py similarity index 69% rename from apps/common/utils/geoip/utils.py rename to apps/common/utils/ip/geoip/utils.py index e80c0b262..5e829f610 100644 --- a/apps/common/utils/geoip/utils.py +++ b/apps/common/utils/ip/geoip/utils.py @@ -8,11 +8,11 @@ from geoip2.errors import GeoIP2Error from django.utils.translation import ugettext_lazy as _ from django.conf import settings -__all__ = ['get_ip_city'] +__all__ = ['get_ip_city_by_geoip'] reader = None -def get_ip_city(ip): +def get_ip_city_by_geoip(ip): if not ip or '.' not in ip or not isinstance(ip, str): return _("Invalid ip") if ':' in ip: @@ -32,15 +32,13 @@ def get_ip_city(ip): try: response = reader.city(ip) except GeoIP2Error: - return _("Unknown ip") + return {} - names = response.city.names - if not names: - names = response.country.names + city_names = response.city.names or {} + lang = settings.LANGUAGE_CODE[:2] + if lang == 'zh': + lang = 'zh-CN' + city = city_names.get(lang, _("Unknown")) + return city - if 'en' in settings.LANGUAGE_CODE and 'en' in names: - return names['en'] - elif 'zh-CN' in names: - return names['zh-CN'] - return _("Unknown ip") diff --git a/apps/common/utils/ip/ipip/__init__.py b/apps/common/utils/ip/ipip/__init__.py new file mode 100644 index 000000000..103b36654 --- /dev/null +++ b/apps/common/utils/ip/ipip/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# +from .utils import * diff --git a/apps/common/utils/ip/ipip/ipipfree.ipdb b/apps/common/utils/ip/ipip/ipipfree.ipdb new file mode 100644 index 000000000..ddd90522c --- /dev/null +++ b/apps/common/utils/ip/ipip/ipipfree.ipdb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b82b874152c798dda407ffe7544e1f5ec67efa1f5c334efc0d3893b8053b4be1 +size 3649897 diff --git a/apps/common/utils/ip/ipip/utils.py b/apps/common/utils/ip/ipip/utils.py new file mode 100644 index 000000000..ef97f4794 --- /dev/null +++ b/apps/common/utils/ip/ipip/utils.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +import os +from django.utils.translation import ugettext as _ + +import ipdb + +__all__ = ['get_ip_city_by_ipip'] +ipip_db = None + + +def get_ip_city_by_ipip(ip): + global ipip_db + if not ip or not isinstance(ip, str): + return _("Invalid ip") + if ':' in ip: + return 'IPv6' + if ipip_db is None: + ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb') + ipip_db = ipdb.City(ipip_db_path) + + info = ipip_db.find_info(ip, 'CN') + return {'city': info.city_name, 'country': info.country_name} diff --git a/apps/common/utils/ip.py b/apps/common/utils/ip/utils.py similarity index 75% rename from apps/common/utils/ip.py rename to apps/common/utils/ip/utils.py index 0000a5da7..e75ecb85c 100644 --- a/apps/common/utils/ip.py +++ b/apps/common/utils/ip/utils.py @@ -1,4 +1,9 @@ from ipaddress import ip_network, ip_address +from django.conf import settings +from django.utils.translation import gettext_lazy as _ + +from .ipip import get_ip_city_by_ipip +from .geoip import get_ip_city_by_geoip def is_ip_address(address): @@ -66,3 +71,16 @@ def contains_ip(ip, ip_group): return True return False + + +def get_ip_city(ip): + info = get_ip_city_by_ipip(ip) + city = info.get('city', _("Unknown")) + country = info.get('country') + + # 国内城市 并且 语言是中文就使用国内 + is_zh = settings.LANGUAGE_CODE.startswith('zh') + if country == '中国' and is_zh: + return city + else: + return get_ip_city_by_geoip(ip) From 525538e775295126de982c92d0169067654890c0 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 16 May 2022 17:28:02 +0800 Subject: [PATCH 102/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=AF=86=E9=92=A5=E7=BF=BB=E8=AF=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/base.py | 8 ++++++-- apps/users/serializers/user.py | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 7c8760562..841ebeaae 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -32,8 +32,12 @@ class AuthSerializer(serializers.ModelSerializer): class AuthSerializerMixin(serializers.ModelSerializer): - password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024) - private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=4096) + password = EncryptedField( + label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024 + ) + private_key = EncryptedField( + label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, max_length=4096 + ) passphrase = serializers.CharField( allow_blank=True, allow_null=True, required=False, max_length=512, write_only=True, label=_('Key password') diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index b1a9cf1d4..98abb47a6 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -88,7 +88,9 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer can_public_key_auth = serializers.ReadOnlyField( source='can_use_ssh_key_login', label=_('Can public key authentication') ) - password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024) + password = EncryptedField( + label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024 + ) # Todo: 这里看看该怎么搞 # can_update = serializers.SerializerMethodField(label=_('Can update')) # can_delete = serializers.SerializerMethodField(label=_('Can delete')) From 3b253e276cb92bbf846fca80637527bf0713c772 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 16 May 2022 17:50:28 +0800 Subject: [PATCH 103/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=20(#8244)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ibuler Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com> --- apps/assets/serializers/base.py | 4 ++-- apps/settings/serializers/auth/ldap.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 841ebeaae..c20b1abb8 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -11,8 +11,8 @@ from assets.models import Type class AuthSerializer(serializers.ModelSerializer): - password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024) - private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=4096) + password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password')) + private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=4096, label=_('Private key')) def gen_keys(self, private_key=None, password=None): if private_key is None: diff --git a/apps/settings/serializers/auth/ldap.py b/apps/settings/serializers/auth/ldap.py index 2278925c1..2f2d710c9 100644 --- a/apps/settings/serializers/auth/ldap.py +++ b/apps/settings/serializers/auth/ldap.py @@ -22,7 +22,7 @@ class LDAPTestConfigSerializer(serializers.Serializer): class LDAPTestLoginSerializer(serializers.Serializer): username = serializers.CharField(max_length=1024, required=True) - password = EncryptedField(max_length=2014, required=True) + password = EncryptedField(max_length=2014, required=True, label=_("Password")) class LDAPUserSerializer(serializers.Serializer): From d87dc7cbd648ccff50f1d6c601b172a119b26aab Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 16 May 2022 16:47:02 +0800 Subject: [PATCH 104/258] fix: import ipdb --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 24aa5cf6a..ce530e0de 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -131,3 +131,4 @@ pandas==1.3.5 pyjwkest==1.4.2 jsonfield2==4.0.0.post0 bce-python-sdk==0.8.64 +ipdb==0.13.9 From 2a5497de14f87a5b2f2a8f004b1fc28afe3fc4fb Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 16 May 2022 17:44:45 +0800 Subject: [PATCH 105/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E5=AE=A1=E6=89=B9=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 144 +++++++++--------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 144 +++++++++--------- .../tickets/approve_check_password.html | 4 +- 5 files changed, 148 insertions(+), 152 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index eba651a80..c78d49bb5 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90a70c14fd3b546cb1ef6a96da4cd7a2acde947128bbb773527ed1845510511c -size 127420 +oid sha256:01a52223f421d736b00a600f623d28ac4a43e97a30f5e9cbebc3e6d18ed4527e +size 127324 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a8145faa8..287e88567 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-12 14:39+0800\n" +"POT-Creation-Date: 2022-05-16 17:43+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -33,7 +33,7 @@ msgstr "Acls" #: terminal/models/storage.py:25 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:661 -#: xpack/plugins/cloud/models.py:27 +#: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名前" @@ -66,12 +66,13 @@ msgstr "アクティブ" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "コメント" #: acls/models/login_acl.py:18 tickets/const.py:38 +#: tickets/templates/tickets/approve_check_password.html:39 msgid "Reject" msgstr "拒否" @@ -134,7 +135,7 @@ msgstr "システムユーザー" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:222 +#: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "資産" @@ -318,7 +319,7 @@ msgstr "タイプ" msgid "Domain" msgstr "ドメイン" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 #: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "ツールバーの" @@ -357,7 +358,7 @@ msgstr "タイプ表示" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:915 -#: xpack/plugins/cloud/models.py:124 +#: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "作成された日付" @@ -572,7 +573,7 @@ msgstr "ホスト名生" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" @@ -612,7 +613,7 @@ msgstr "ラベル" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "によって作成された" @@ -716,7 +717,7 @@ msgstr "トリガーモード" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:178 +#: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "理由" @@ -734,6 +735,7 @@ msgid "Account backup execution" msgstr "アカウントバックアップの実行" #: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 +#: common/utils/ip/geoip/utils.py:41 common/utils/ip/utils.py:78 msgid "Unknown" msgstr "不明" @@ -954,7 +956,7 @@ msgid "Parent key" msgstr "親キー" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -1491,8 +1493,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 -#: xpack/plugins/cloud/models.py:226 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "ステータス" @@ -1529,7 +1531,7 @@ msgid "Hosts display" msgstr "ホスト表示" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:172 +#: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "結果" @@ -2100,7 +2102,7 @@ msgstr "バインディングリマインダー" #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:143 msgid "Is valid" msgstr "有効です" @@ -2637,18 +2639,15 @@ msgstr "確認コードが正しくありません" msgid "Please wait {} seconds before sending" msgstr "{} 秒待ってから送信してください" -#: common/utils/geoip/utils.py:17 common/utils/geoip/utils.py:30 +#: common/utils/ip/geoip/utils.py:17 common/utils/ip/geoip/utils.py:30 +#: common/utils/ip/ipip/utils.py:15 msgid "Invalid ip" msgstr "無効なIP" -#: common/utils/geoip/utils.py:28 +#: common/utils/ip/geoip/utils.py:28 msgid "LAN" msgstr "LAN" -#: common/utils/geoip/utils.py:35 common/utils/geoip/utils.py:45 -msgid "Unknown ip" -msgstr "不明なip" - #: common/validators.py:32 msgid "This field must be unique." msgstr "このフィールドは一意である必要があります。" @@ -3063,8 +3062,8 @@ msgstr "Organization {} のアプリケーション権限" #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 -#: perms/serializers/asset/permission.py:44 users/serializers/user.py:86 -#: users/serializers/user.py:143 +#: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 +#: users/serializers/user.py:145 msgid "Is expired" msgstr "期限切れです" @@ -4977,7 +4976,7 @@ msgstr "バケット" msgid "Secret key" msgstr "秘密キー" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "リージョン" @@ -5492,12 +5491,8 @@ msgid "Ticket approval" msgstr "作業指示の承認" #: tickets/templates/tickets/approve_check_password.html:35 -msgid "Ticket direct approval" -msgstr "作業指示の直接承認" - -#: tickets/templates/tickets/approve_check_password.html:39 -msgid "Ticket direct reject" -msgstr "作業指示の直接却下" +msgid "Approval" +msgstr "承認" #: tickets/templates/tickets/approve_check_password.html:44 msgid "Go Login" @@ -5645,7 +5640,7 @@ msgstr "強制有効" msgid "Local" msgstr "ローカル" -#: users/models/user.py:673 users/serializers/user.py:142 +#: users/models/user.py:673 users/serializers/user.py:144 msgid "Is service account" msgstr "サービスアカウントです" @@ -5744,101 +5739,101 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:147 users/serializers/user.py:140 +#: users/serializers/profile.py:147 users/serializers/user.py:142 msgid "Is first login" msgstr "最初のログインです" -#: users/serializers/user.py:25 users/serializers/user.py:32 +#: users/serializers/user.py:26 users/serializers/user.py:33 msgid "System roles" msgstr "システムの役割" -#: users/serializers/user.py:30 users/serializers/user.py:33 +#: users/serializers/user.py:31 users/serializers/user.py:34 msgid "Org roles" msgstr "組織ロール" -#: users/serializers/user.py:78 +#: users/serializers/user.py:79 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:22 msgid "Password strategy" msgstr "パスワード戦略" -#: users/serializers/user.py:80 +#: users/serializers/user.py:81 msgid "MFA enabled" msgstr "MFA有効化" -#: users/serializers/user.py:81 +#: users/serializers/user.py:82 msgid "MFA force enabled" msgstr "MFAフォース有効化" -#: users/serializers/user.py:83 +#: users/serializers/user.py:84 msgid "MFA level display" msgstr "MFAレベル表示" -#: users/serializers/user.py:85 +#: users/serializers/user.py:86 msgid "Login blocked" msgstr "ログインブロック" -#: users/serializers/user.py:88 +#: users/serializers/user.py:89 msgid "Can public key authentication" msgstr "公開鍵認証が可能" -#: users/serializers/user.py:144 +#: users/serializers/user.py:146 msgid "Avatar url" msgstr "アバターURL" -#: users/serializers/user.py:146 +#: users/serializers/user.py:148 msgid "Groups name" msgstr "グループ名" -#: users/serializers/user.py:147 +#: users/serializers/user.py:149 msgid "Source name" msgstr "ソース名" -#: users/serializers/user.py:148 +#: users/serializers/user.py:150 msgid "Organization role name" msgstr "組織の役割名" -#: users/serializers/user.py:149 +#: users/serializers/user.py:151 msgid "Super role name" msgstr "スーパーロール名" -#: users/serializers/user.py:150 +#: users/serializers/user.py:152 msgid "Total role name" msgstr "合計ロール名" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Is wecom bound" msgstr "企業の微信をバインドしているかどうか" -#: users/serializers/user.py:153 +#: users/serializers/user.py:155 msgid "Is dingtalk bound" msgstr "ピンをバインドしているかどうか" -#: users/serializers/user.py:154 +#: users/serializers/user.py:156 msgid "Is feishu bound" msgstr "飛本を縛ったかどうか" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "Is OTP bound" msgstr "仮想MFAがバインドされているか" -#: users/serializers/user.py:157 +#: users/serializers/user.py:159 msgid "System role name" msgstr "システムロール名" -#: users/serializers/user.py:197 +#: users/serializers/user.py:199 msgid "User cannot self-update fields: {}" msgstr "ユーザーは自分のフィールドを更新できません: {}" -#: users/serializers/user.py:254 +#: users/serializers/user.py:256 msgid "Select users" msgstr "ユーザーの選択" -#: users/serializers/user.py:255 +#: users/serializers/user.py:257 msgid "For security, only list several users" msgstr "セキュリティのために、複数のユーザーのみをリストします" -#: users/serializers/user.py:290 +#: users/serializers/user.py:292 msgid "name not unique" msgstr "名前が一意ではない" @@ -6371,79 +6366,79 @@ msgstr "リリース済み" msgid "Cloud center" msgstr "クラウドセンター" -#: xpack/plugins/cloud/models.py:29 +#: xpack/plugins/cloud/models.py:30 msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/models.py:34 msgid "Validity" msgstr "有効性" -#: xpack/plugins/cloud/models.py:38 +#: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "クラウドアカウント" -#: xpack/plugins/cloud/models.py:40 +#: xpack/plugins/cloud/models.py:41 msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "アカウント" -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "リージョン" -#: xpack/plugins/cloud/models.py:90 +#: xpack/plugins/cloud/models.py:91 msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix adminユーザー" -#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows管理者" -#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "常に更新" -#: xpack/plugins/cloud/models.py:118 +#: xpack/plugins/cloud/models.py:119 msgid "Date last sync" msgstr "最終同期日" -#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 msgid "Sync instance task" msgstr "インスタンスの同期タスク" -#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 msgid "Date sync" msgstr "日付の同期" -#: xpack/plugins/cloud/models.py:185 +#: xpack/plugins/cloud/models.py:186 msgid "Sync instance task execution" msgstr "インスタンスタスクの同期実行" -#: xpack/plugins/cloud/models.py:209 +#: xpack/plugins/cloud/models.py:210 msgid "Sync task" msgstr "同期タスク" -#: xpack/plugins/cloud/models.py:213 +#: xpack/plugins/cloud/models.py:214 msgid "Sync instance task history" msgstr "インスタンスタスク履歴の同期" -#: xpack/plugins/cloud/models.py:216 +#: xpack/plugins/cloud/models.py:217 msgid "Instance" msgstr "インスタンス" -#: xpack/plugins/cloud/models.py:233 +#: xpack/plugins/cloud/models.py:234 msgid "Sync instance detail" msgstr "同期インスタンスの詳細" @@ -6805,6 +6800,9 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#~ msgid "Unknown ip" +#~ msgstr "不明なip" + #~ msgid "" #~ "Windows needs to download the client to connect SSH assets, and the MacOS " #~ "system uses its own terminal" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 5d4b3e1b9..3a91677a1 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f181a41eb4dd8a30a576f7903e5c9f519da2042e5e095ac27146d7b4002ba3df -size 105303 +oid sha256:e4a00b4e1a3bc944c968987fd3c65798fb39fa552e91457693ec8fcb597820f0 +size 105225 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 9f099484e..feda0fbe7 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-12 14:39+0800\n" +"POT-Creation-Date: 2022-05-16 17:43+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -32,7 +32,7 @@ msgstr "访问控制" #: terminal/models/storage.py:25 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:661 -#: xpack/plugins/cloud/models.py:27 +#: xpack/plugins/cloud/models.py:28 msgid "Name" msgstr "名称" @@ -65,12 +65,13 @@ msgstr "激活中" #: tickets/models/comment.py:24 tickets/models/ticket.py:154 #: users/models/group.py:16 users/models/user.py:698 #: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:34 xpack/plugins/cloud/models.py:115 +#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" #: acls/models/login_acl.py:18 tickets/const.py:38 +#: tickets/templates/tickets/approve_check_password.html:39 msgid "Reject" msgstr "拒绝" @@ -133,7 +134,7 @@ msgstr "系统用户" #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:222 +#: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "资产" @@ -313,7 +314,7 @@ msgstr "类型" msgid "Domain" msgstr "网域" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:32 +#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 #: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "属性" @@ -352,7 +353,7 @@ msgstr "类型名称" #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 #: users/models/group.py:18 users/models/user.py:915 -#: xpack/plugins/cloud/models.py:124 +#: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "创建日期" @@ -567,7 +568,7 @@ msgstr "主机名原始" #: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:42 +#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" @@ -607,7 +608,7 @@ msgstr "标签管理" #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:706 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:121 xpack/plugins/gathered_user/models.py:30 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -711,7 +712,7 @@ msgstr "触发模式" #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:179 -#: xpack/plugins/cloud/models.py:178 +#: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "原因" @@ -729,6 +730,7 @@ msgid "Account backup execution" msgstr "账号备份执行" #: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 +#: common/utils/ip/geoip/utils.py:41 common/utils/ip/utils.py:78 msgid "Unknown" msgstr "未知" @@ -949,7 +951,7 @@ msgid "Parent key" msgstr "ssh私钥" #: assets/models/node.py:559 assets/serializers/system_user.py:263 -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -1479,8 +1481,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 terminal/models/status.py:33 -#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:174 -#: xpack/plugins/cloud/models.py:226 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:175 +#: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "状态" @@ -1517,7 +1519,7 @@ msgid "Hosts display" msgstr "主机名称" #: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:172 +#: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "结果" @@ -2079,7 +2081,7 @@ msgstr "绑定提醒" #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:141 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:143 msgid "Is valid" msgstr "账号是否有效" @@ -2607,18 +2609,15 @@ msgstr "验证码错误" msgid "Please wait {} seconds before sending" msgstr "请在 {} 秒后发送" -#: common/utils/geoip/utils.py:17 common/utils/geoip/utils.py:30 +#: common/utils/ip/geoip/utils.py:17 common/utils/ip/geoip/utils.py:30 +#: common/utils/ip/ipip/utils.py:15 msgid "Invalid ip" msgstr "无效IP" -#: common/utils/geoip/utils.py:28 +#: common/utils/ip/geoip/utils.py:28 msgid "LAN" msgstr "LAN" -#: common/utils/geoip/utils.py:35 common/utils/geoip/utils.py:45 -msgid "Unknown ip" -msgstr "未知ip" - #: common/validators.py:32 msgid "This field must be unique." msgstr "字段必须唯一" @@ -3027,8 +3026,8 @@ msgstr "组织 ({}) 的应用授权" #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 -#: perms/serializers/asset/permission.py:44 users/serializers/user.py:86 -#: users/serializers/user.py:143 +#: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 +#: users/serializers/user.py:145 msgid "Is expired" msgstr "已过期" @@ -4905,7 +4904,7 @@ msgstr "桶名称" msgid "Secret key" msgstr "密钥" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:219 +#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "地域" @@ -5416,12 +5415,8 @@ msgid "Ticket approval" msgstr "工单审批" #: tickets/templates/tickets/approve_check_password.html:35 -msgid "Ticket direct approval" -msgstr "工单直接审批" - -#: tickets/templates/tickets/approve_check_password.html:39 -msgid "Ticket direct reject" -msgstr "工单直接拒绝" +msgid "Approval" +msgstr "同意" #: tickets/templates/tickets/approve_check_password.html:44 msgid "Go Login" @@ -5567,7 +5562,7 @@ msgstr "强制启用" msgid "Local" msgstr "数据库" -#: users/models/user.py:673 users/serializers/user.py:142 +#: users/models/user.py:673 users/serializers/user.py:144 msgid "Is service account" msgstr "服务账号" @@ -5666,101 +5661,101 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:147 users/serializers/user.py:140 +#: users/serializers/profile.py:147 users/serializers/user.py:142 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:25 users/serializers/user.py:32 +#: users/serializers/user.py:26 users/serializers/user.py:33 msgid "System roles" msgstr "系统角色" -#: users/serializers/user.py:30 users/serializers/user.py:33 +#: users/serializers/user.py:31 users/serializers/user.py:34 msgid "Org roles" msgstr "组织角色" -#: users/serializers/user.py:78 +#: users/serializers/user.py:79 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:22 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:80 +#: users/serializers/user.py:81 msgid "MFA enabled" msgstr "MFA" -#: users/serializers/user.py:81 +#: users/serializers/user.py:82 msgid "MFA force enabled" msgstr "强制 MFA" -#: users/serializers/user.py:83 +#: users/serializers/user.py:84 msgid "MFA level display" msgstr "MFA 等级名称" -#: users/serializers/user.py:85 +#: users/serializers/user.py:86 msgid "Login blocked" msgstr "登录被阻塞" -#: users/serializers/user.py:88 +#: users/serializers/user.py:89 msgid "Can public key authentication" msgstr "能否公钥认证" -#: users/serializers/user.py:144 +#: users/serializers/user.py:146 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:146 +#: users/serializers/user.py:148 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:147 +#: users/serializers/user.py:149 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:148 +#: users/serializers/user.py:150 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:149 +#: users/serializers/user.py:151 msgid "Super role name" msgstr "超级角色名称" -#: users/serializers/user.py:150 +#: users/serializers/user.py:152 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Is wecom bound" msgstr "是否绑定了企业微信" -#: users/serializers/user.py:153 +#: users/serializers/user.py:155 msgid "Is dingtalk bound" msgstr "是否绑定了钉钉" -#: users/serializers/user.py:154 +#: users/serializers/user.py:156 msgid "Is feishu bound" msgstr "是否绑定了飞书" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" -#: users/serializers/user.py:157 +#: users/serializers/user.py:159 msgid "System role name" msgstr "系统角色名称" -#: users/serializers/user.py:197 +#: users/serializers/user.py:199 msgid "User cannot self-update fields: {}" msgstr "用户不能更新自己的字段: {}" -#: users/serializers/user.py:254 +#: users/serializers/user.py:256 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:255 +#: users/serializers/user.py:257 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:290 +#: users/serializers/user.py:292 msgid "name not unique" msgstr "名称重复" @@ -6280,79 +6275,79 @@ msgstr "已释放" msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:29 +#: xpack/plugins/cloud/models.py:30 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/models.py:34 msgid "Validity" msgstr "有效" -#: xpack/plugins/cloud/models.py:38 +#: xpack/plugins/cloud/models.py:39 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:40 +#: xpack/plugins/cloud/models.py:41 msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "账号" -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:90 +#: xpack/plugins/cloud/models.py:91 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:99 xpack/plugins/cloud/serializers/task.py:67 +#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:67 msgid "Unix admin user" msgstr "Unix 管理员" -#: xpack/plugins/cloud/models.py:103 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:68 msgid "Windows admin user" msgstr "Windows 管理员" -#: xpack/plugins/cloud/models.py:109 xpack/plugins/cloud/serializers/task.py:45 +#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:45 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:71 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:118 +#: xpack/plugins/cloud/models.py:119 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/models.py:170 +#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:181 xpack/plugins/cloud/models.py:229 +#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:185 +#: xpack/plugins/cloud/models.py:186 msgid "Sync instance task execution" msgstr "同步实例任务执行" -#: xpack/plugins/cloud/models.py:209 +#: xpack/plugins/cloud/models.py:210 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:213 +#: xpack/plugins/cloud/models.py:214 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:216 +#: xpack/plugins/cloud/models.py:217 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/models.py:233 +#: xpack/plugins/cloud/models.py:234 msgid "Sync instance detail" msgstr "同步实例详情" @@ -6713,6 +6708,9 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Unknown ip" +#~ msgstr "未知ip" + #~ msgid "" #~ "Windows needs to download the client to connect SSH assets, and the MacOS " #~ "system uses its own terminal" diff --git a/apps/tickets/templates/tickets/approve_check_password.html b/apps/tickets/templates/tickets/approve_check_password.html index 3e9d9afeb..d75e9f956 100644 --- a/apps/tickets/templates/tickets/approve_check_password.html +++ b/apps/tickets/templates/tickets/approve_check_password.html @@ -32,11 +32,11 @@ {% if user.is_authenticated %} {% else %} Date: Mon, 16 May 2022 16:12:13 +0800 Subject: [PATCH 106/258] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3LDAP=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E7=94=A8=E6=88=B7=E4=BB=AA=E8=A1=A8=E7=9B=98=E6=80=BB?= =?UTF-8?q?=E6=95=B0=E6=B2=A1=E6=9C=89=E5=88=B7=E6=96=B0=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/signal_handlers/cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 546d11311..9de31bbb0 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -9,7 +9,7 @@ from users.models import UserGroup, User from users.signals import pre_user_leave_org from applications.models import Application from terminal.models import Session -from rbac.models import OrgRoleBinding, SystemRoleBinding +from rbac.models import OrgRoleBinding, SystemRoleBinding, RoleBinding from assets.models import Asset, SystemUser, Domain, Gateway from orgs.caches import OrgResourceStatisticsCache from orgs.utils import current_org @@ -85,6 +85,7 @@ class OrgResourceStatisticsRefreshUtil: Node: ['nodes_amount'], Asset: ['assets_amount'], UserGroup: ['groups_amount'], + RoleBinding: ['users_amount'] } @classmethod From e80b6936a2ba2e926572978ff46c7aa9f62751af Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Fri, 13 May 2022 15:50:01 +0800 Subject: [PATCH 107/258] =?UTF-8?q?perf:=20=E5=85=BC=E5=AE=B9AWS=E4=B8=8Ar?= =?UTF-8?q?edis[ssl]=E6=97=A0=E8=AF=81=E4=B9=A6=E6=97=A0=E6=B3=95=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/connection.py | 2 +- apps/jumpserver/rewriting/session.py | 2 +- apps/jumpserver/settings/base.py | 7 ++++++- apps/jumpserver/settings/libs.py | 7 ++++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/common/utils/connection.py b/apps/common/utils/connection.py index 291d4516d..242b0cb21 100644 --- a/apps/common/utils/connection.py +++ b/apps/common/utils/connection.py @@ -19,7 +19,7 @@ def get_redis_client(db=0): 'password': CONFIG.REDIS_PASSWORD, 'db': db, "ssl": is_true(CONFIG.REDIS_USE_SSL), - 'ssl_cert_reqs': CONFIG.REDIS_SSL_REQUIRED, + 'ssl_cert_reqs': getattr(settings, 'REDIS_SSL_REQUIRED'), 'ssl_keyfile': getattr(settings, 'REDIS_SSL_KEYFILE'), 'ssl_certfile': getattr(settings, 'REDIS_SSL_CERTFILE'), 'ssl_ca_certs': getattr(settings, 'REDIS_SSL_CA_CERTS'), diff --git a/apps/jumpserver/rewriting/session.py b/apps/jumpserver/rewriting/session.py index d7e0428aa..f3b432657 100644 --- a/apps/jumpserver/rewriting/session.py +++ b/apps/jumpserver/rewriting/session.py @@ -18,7 +18,7 @@ class RedisServer(_RedisServer): ssl_params = {} if CONFIG.REDIS_USE_SSL: ssl_params = { - 'ssl_cert_reqs': CONFIG.REDIS_SSL_REQUIRED, + 'ssl_cert_reqs': getattr(settings, 'REDIS_SSL_REQUIRED'), 'ssl_keyfile': getattr(settings, 'REDIS_SSL_KEYFILE'), 'ssl_certfile': getattr(settings, 'REDIS_SSL_CERTFILE'), 'ssl_ca_certs': getattr(settings, 'REDIS_SSL_CA_CERTS'), diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 7c3657f35..327c3ea97 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -277,6 +277,11 @@ REDIS_SSL_CA_CERTS = os.path.join(PROJECT_DIR, 'data', 'certs', 'redis_ca.crt') if not os.path.exists(REDIS_SSL_CA_CERTS): REDIS_SSL_CA_CERTS = os.path.join(PROJECT_DIR, 'data', 'certs', 'redis_ca.pem') +if not os.path.exists(REDIS_SSL_CA_CERTS): + REDIS_SSL_CA_CERTS = None + +REDIS_SSL_REQUIRED = CONFIG.REDIS_SSL_REQUIRED or 'none' + CACHES = { 'default': { # 'BACKEND': 'redis_cache.RedisCache', @@ -291,7 +296,7 @@ CACHES = { 'OPTIONS': { "REDIS_CLIENT_KWARGS": {"health_check_interval": 30}, "CONNECTION_POOL_KWARGS": { - 'ssl_cert_reqs': CONFIG.REDIS_SSL_REQUIRED, + 'ssl_cert_reqs': REDIS_SSL_REQUIRED, "ssl_keyfile": REDIS_SSL_KEYFILE, "ssl_certfile": REDIS_SSL_CERTFILE, "ssl_ca_certs": REDIS_SSL_CA_CERTS diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 59446b9de..c67c21f0b 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -3,7 +3,7 @@ import os import ssl -from .base import REDIS_SSL_CA_CERTS, REDIS_SSL_CERTFILE, REDIS_SSL_KEYFILE +from .base import REDIS_SSL_CA_CERTS, REDIS_SSL_CERTFILE, REDIS_SSL_KEYFILE, REDIS_SSL_REQUIRED from ..const import CONFIG, PROJECT_DIR REST_FRAMEWORK = { @@ -90,7 +90,8 @@ if not CONFIG.REDIS_USE_SSL: else: context = ssl.SSLContext() context.check_hostname = bool(CONFIG.REDIS_SSL_REQUIRED) - context.load_verify_locations(REDIS_SSL_CA_CERTS) + if REDIS_SSL_CA_CERTS: + context.load_verify_locations(REDIS_SSL_CA_CERTS) if REDIS_SSL_CERTFILE and REDIS_SSL_KEYFILE: context.load_cert_chain(REDIS_SSL_CERTFILE, REDIS_SSL_KEYFILE) @@ -140,7 +141,7 @@ CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = "INFO" CELERY_TASK_SOFT_TIME_LIMIT = 3600 if CONFIG.REDIS_USE_SSL: CELERY_BROKER_USE_SSL = CELERY_REDIS_BACKEND_USE_SSL = { - 'ssl_cert_reqs': CONFIG.REDIS_SSL_REQUIRED, + 'ssl_cert_reqs': REDIS_SSL_REQUIRED, 'ssl_ca_certs': REDIS_SSL_CA_CERTS, 'ssl_certfile': REDIS_SSL_CERTFILE, 'ssl_keyfile': REDIS_SSL_KEYFILE From 521ec0245b61ff8bbbe88ea5bcdc52d8a75a53c2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 16 May 2022 19:55:49 +0800 Subject: [PATCH 108/258] =?UTF-8?q?fix:=20ipdb=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ce530e0de..139cfbcd5 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -131,4 +131,4 @@ pandas==1.3.5 pyjwkest==1.4.2 jsonfield2==4.0.0.post0 bce-python-sdk==0.8.64 -ipdb==0.13.9 +ipip-ipdb==1.2.1 From 83571718e9f9cacdb4ef3573b25c3c6ada76979c Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 16 May 2022 19:59:46 +0800 Subject: [PATCH 109/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 139cfbcd5..c301148ea 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -131,4 +131,4 @@ pandas==1.3.5 pyjwkest==1.4.2 jsonfield2==4.0.0.post0 bce-python-sdk==0.8.64 -ipip-ipdb==1.2.1 +ipip-ipdb==1.6.1 From 2c73611cb4ec140fdb4e98ac86e668e9553d4070 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 17 May 2022 11:28:43 +0800 Subject: [PATCH 110/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=85=AC?= =?UTF-8?q?=E5=91=8A=E4=B8=8D=E6=98=BE=E7=A4=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/serializers/public.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index 877230489..afc4fbec2 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -40,4 +40,4 @@ class PrivateSettingSerializer(PublicSettingSerializer): TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField() ANNOUNCEMENT_ENABLED = serializers.BooleanField() - ANNOUNCEMENT = serializers.CharField() + ANNOUNCEMENT = serializers.DictField() From d675b1d4fc3c310b5e7874cdedb4f5bcceaa5ec7 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 17 May 2022 16:53:15 +0800 Subject: [PATCH 111/258] =?UTF-8?q?fix:=20k8s=20token=20=E8=A7=A3=E5=AF=86?= =?UTF-8?q?=20(#8252)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/assets/serializers/system_user.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 850870762..ab07533d0 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -4,6 +4,7 @@ from django.db.models import Count from common.mixins.serializers import BulkSerializerMixin from common.utils import ssh_pubkey_gen +from common.drf.fields import EncryptedField from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import SystemUser, Asset @@ -26,6 +27,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) ssh_key_fingerprint = serializers.ReadOnlyField(label=_('SSH key fingerprint')) + token = EncryptedField( + label=_('Token'), required=False, write_only=True, style={'base_template': 'textarea.html'} + ) applications_amount = serializers.IntegerField( source='apps_amount', read_only=True, label=_('Apps amount') ) From 07779c5a7a59984b534e28ef7df5f853cd48ebbb Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 17 May 2022 18:57:04 +0800 Subject: [PATCH 112/258] =?UTF-8?q?perf:=20=E5=B7=A5=E5=8D=95=E5=90=AF?= =?UTF-8?q?=E7=94=A8=20(#8254)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ibuler Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com> --- apps/jumpserver/conf.py | 2 + apps/jumpserver/settings/custom.py | 1 + apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 65 +++++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 65 +++++++++++++++------------- apps/settings/serializers/basic.py | 1 + apps/settings/serializers/public.py | 4 +- 8 files changed, 83 insertions(+), 63 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 18b998ab2..6f84397ac 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -389,6 +389,8 @@ class Config(dict): 'FTP_LOG_KEEP_DAYS': 200, 'CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS': 30, + 'TICKETS_ENABLED': True, + # 废弃的 'DEFAULT_ORG_SHOW_ALL_USERS': True, 'ORG_CHANGE_TO_URL': '', diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index c4dfdcc24..b2768f2d8 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -119,6 +119,7 @@ CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABL DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S' +TICKETS_ENABLED = CONFIG.TICKETS_ENABLED REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index c78d49bb5..b6af2bdf7 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01a52223f421d736b00a600f623d28ac4a43e97a30f5e9cbebc3e6d18ed4527e -size 127324 +oid sha256:843b6dffe6af09073053e21f65be4c8264e6dee05509b375c8191dde8c9079b6 +size 127386 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 287e88567..6b97b95d0 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-16 17:43+0800\n" +"POT-Creation-Date: 2022-05-17 18:02+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -758,10 +758,12 @@ msgstr "接続性" msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:177 audits/signal_handlers.py:48 +#: assets/models/base.py:177 assets/serializers/base.py:14 +#: assets/serializers/base.py:36 audits/signal_handlers.py:48 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:182 -#: settings/serializers/auth/ldap.py:46 users/forms/profile.py:22 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 +#: users/forms/profile.py:22 users/serializers/user.py:92 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -771,7 +773,8 @@ msgstr "確認済みの日付" msgid "Password" msgstr "パスワード" -#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:53 +#: assets/models/base.py:178 assets/serializers/base.py:39 +#: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:206 msgid "SSH private key" @@ -1120,11 +1123,15 @@ msgstr "定期的なパフォーマンス" msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/base.py:39 +#: assets/serializers/base.py:15 users/models/user.py:689 +msgid "Private key" +msgstr "ssh秘密鍵" + +#: assets/serializers/base.py:43 msgid "Key password" msgstr "キーパスワード" -#: assets/serializers/base.py:52 +#: assets/serializers/base.py:56 msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" @@ -2102,7 +2109,7 @@ msgstr "バインディングリマインダー" #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:143 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:145 msgid "Is valid" msgstr "有効です" @@ -3063,7 +3070,7 @@ msgstr "Organization {} のアプリケーション権限" #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 #: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 -#: users/serializers/user.py:145 +#: users/serializers/user.py:147 msgid "Is expired" msgstr "期限切れです" @@ -3773,6 +3780,10 @@ msgstr "アナウンスの有効化" msgid "Announcement" msgstr "発表" +#: settings/serializers/basic.py:46 +msgid "Enable tickets" +msgstr "チケットを有効にする" + #: settings/serializers/cleaning.py:10 msgid "Login log keep days" msgstr "ログインログは日数を保持します" @@ -5640,7 +5651,7 @@ msgstr "強制有効" msgid "Local" msgstr "ローカル" -#: users/models/user.py:673 users/serializers/user.py:144 +#: users/models/user.py:673 users/serializers/user.py:146 msgid "Is service account" msgstr "サービスアカウントです" @@ -5652,10 +5663,6 @@ msgstr "アバター" msgid "Wechat" msgstr "微信" -#: users/models/user.py:689 -msgid "Private key" -msgstr "ssh秘密鍵" - #: users/models/user.py:711 msgid "Source" msgstr "ソース" @@ -5739,7 +5746,7 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:147 users/serializers/user.py:142 +#: users/serializers/profile.py:147 users/serializers/user.py:144 msgid "Is first login" msgstr "最初のログインです" @@ -5777,63 +5784,63 @@ msgstr "ログインブロック" msgid "Can public key authentication" msgstr "公開鍵認証が可能" -#: users/serializers/user.py:146 +#: users/serializers/user.py:148 msgid "Avatar url" msgstr "アバターURL" -#: users/serializers/user.py:148 +#: users/serializers/user.py:150 msgid "Groups name" msgstr "グループ名" -#: users/serializers/user.py:149 +#: users/serializers/user.py:151 msgid "Source name" msgstr "ソース名" -#: users/serializers/user.py:150 +#: users/serializers/user.py:152 msgid "Organization role name" msgstr "組織の役割名" -#: users/serializers/user.py:151 +#: users/serializers/user.py:153 msgid "Super role name" msgstr "スーパーロール名" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Total role name" msgstr "合計ロール名" -#: users/serializers/user.py:154 +#: users/serializers/user.py:156 msgid "Is wecom bound" msgstr "企業の微信をバインドしているかどうか" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "Is dingtalk bound" msgstr "ピンをバインドしているかどうか" -#: users/serializers/user.py:156 +#: users/serializers/user.py:158 msgid "Is feishu bound" msgstr "飛本を縛ったかどうか" -#: users/serializers/user.py:157 +#: users/serializers/user.py:159 msgid "Is OTP bound" msgstr "仮想MFAがバインドされているか" -#: users/serializers/user.py:159 +#: users/serializers/user.py:161 msgid "System role name" msgstr "システムロール名" -#: users/serializers/user.py:199 +#: users/serializers/user.py:201 msgid "User cannot self-update fields: {}" msgstr "ユーザーは自分のフィールドを更新できません: {}" -#: users/serializers/user.py:256 +#: users/serializers/user.py:258 msgid "Select users" msgstr "ユーザーの選択" -#: users/serializers/user.py:257 +#: users/serializers/user.py:259 msgid "For security, only list several users" msgstr "セキュリティのために、複数のユーザーのみをリストします" -#: users/serializers/user.py:292 +#: users/serializers/user.py:294 msgid "name not unique" msgstr "名前が一意ではない" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 3a91677a1..88b5034ea 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4a00b4e1a3bc944c968987fd3c65798fb39fa552e91457693ec8fcb597820f0 -size 105225 +oid sha256:a78975a5a6669bfcc0f99bc4d47811be82a0620e873e51e4a17d06548e3b1e7f +size 105269 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index feda0fbe7..20a071d1c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-16 17:43+0800\n" +"POT-Creation-Date: 2022-05-17 18:02+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -753,10 +753,12 @@ msgstr "可连接性" msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:177 audits/signal_handlers.py:48 +#: assets/models/base.py:177 assets/serializers/base.py:14 +#: assets/serializers/base.py:36 audits/signal_handlers.py:48 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:182 -#: settings/serializers/auth/ldap.py:46 users/forms/profile.py:22 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 +#: users/forms/profile.py:22 users/serializers/user.py:92 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -766,7 +768,8 @@ msgstr "校验日期" msgid "Password" msgstr "密码" -#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:53 +#: assets/models/base.py:178 assets/serializers/base.py:39 +#: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:206 msgid "SSH private key" @@ -1112,11 +1115,15 @@ msgstr "定时执行" msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/base.py:39 +#: assets/serializers/base.py:15 users/models/user.py:689 +msgid "Private key" +msgstr "ssh私钥" + +#: assets/serializers/base.py:43 msgid "Key password" msgstr "密钥密码" -#: assets/serializers/base.py:52 +#: assets/serializers/base.py:56 msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" @@ -2081,7 +2088,7 @@ msgstr "绑定提醒" #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:143 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:145 msgid "Is valid" msgstr "账号是否有效" @@ -3027,7 +3034,7 @@ msgstr "组织 ({}) 的应用授权" #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 #: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 -#: users/serializers/user.py:145 +#: users/serializers/user.py:147 msgid "Is expired" msgstr "已过期" @@ -3733,6 +3740,10 @@ msgstr "启用公告" msgid "Announcement" msgstr "公告" +#: settings/serializers/basic.py:46 +msgid "Enable tickets" +msgstr "启用工单" + #: settings/serializers/cleaning.py:10 msgid "Login log keep days" msgstr "登录日志" @@ -5562,7 +5573,7 @@ msgstr "强制启用" msgid "Local" msgstr "数据库" -#: users/models/user.py:673 users/serializers/user.py:144 +#: users/models/user.py:673 users/serializers/user.py:146 msgid "Is service account" msgstr "服务账号" @@ -5574,10 +5585,6 @@ msgstr "头像" msgid "Wechat" msgstr "微信" -#: users/models/user.py:689 -msgid "Private key" -msgstr "ssh私钥" - #: users/models/user.py:711 msgid "Source" msgstr "来源" @@ -5661,7 +5668,7 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:147 users/serializers/user.py:142 +#: users/serializers/profile.py:147 users/serializers/user.py:144 msgid "Is first login" msgstr "首次登录" @@ -5699,63 +5706,63 @@ msgstr "登录被阻塞" msgid "Can public key authentication" msgstr "能否公钥认证" -#: users/serializers/user.py:146 +#: users/serializers/user.py:148 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:148 +#: users/serializers/user.py:150 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:149 +#: users/serializers/user.py:151 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:150 +#: users/serializers/user.py:152 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:151 +#: users/serializers/user.py:153 msgid "Super role name" msgstr "超级角色名称" -#: users/serializers/user.py:152 +#: users/serializers/user.py:154 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:154 +#: users/serializers/user.py:156 msgid "Is wecom bound" msgstr "是否绑定了企业微信" -#: users/serializers/user.py:155 +#: users/serializers/user.py:157 msgid "Is dingtalk bound" msgstr "是否绑定了钉钉" -#: users/serializers/user.py:156 +#: users/serializers/user.py:158 msgid "Is feishu bound" msgstr "是否绑定了飞书" -#: users/serializers/user.py:157 +#: users/serializers/user.py:159 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" -#: users/serializers/user.py:159 +#: users/serializers/user.py:161 msgid "System role name" msgstr "系统角色名称" -#: users/serializers/user.py:199 +#: users/serializers/user.py:201 msgid "User cannot self-update fields: {}" msgstr "用户不能更新自己的字段: {}" -#: users/serializers/user.py:256 +#: users/serializers/user.py:258 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:257 +#: users/serializers/user.py:259 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:292 +#: users/serializers/user.py:294 msgid "name not unique" msgstr "名称重复" diff --git a/apps/settings/serializers/basic.py b/apps/settings/serializers/basic.py index e0672f0df..3208ad1be 100644 --- a/apps/settings/serializers/basic.py +++ b/apps/settings/serializers/basic.py @@ -43,6 +43,7 @@ class BasicSettingSerializer(serializers.Serializer): ) ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True) ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement")) + TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets")) @staticmethod def validate_SITE_URL(s): diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index afc4fbec2..fed540bc0 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -40,4 +40,6 @@ class PrivateSettingSerializer(PublicSettingSerializer): TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField() ANNOUNCEMENT_ENABLED = serializers.BooleanField() - ANNOUNCEMENT = serializers.DictField() + ANNOUNCEMENT = serializers.CharField() + + TICKETS_ENABLED = serializers.BooleanField() From 0fc5a33983fe9ad8970bd0dc2875b7ee6eeaf79e Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 17 May 2022 18:50:16 +0800 Subject: [PATCH 113/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BC=81?= =?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E3=80=81=E9=92=89=E9=92=89=E3=80=81?= =?UTF-8?q?=E9=A3=9E=E4=B9=A6=E7=99=BB=E5=BD=95=E8=B7=B3=E8=BD=AC=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/views/dingtalk.py | 5 +++-- apps/authentication/views/feishu.py | 7 ++++--- apps/authentication/views/wecom.py | 6 +++--- apps/users/utils.py | 12 +++++++----- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 0ca03749e..e97424aee 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -205,12 +205,13 @@ class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View): permission_classes = (AllowAny,) def get(self, request: HttpRequest): - redirect_url = request.GET.get('redirect_url') + redirect_url = request.GET.get('redirect_url') or reverse('index') + next_url = self.get_next_url_from_meta() or reverse('index') redirect_uri = reverse('authentication:dingtalk-qr-login-callback', external=True) redirect_uri += '?' + urlencode({ 'redirect_url': redirect_url, - 'next': self.get_next_url_from_meta() + 'next': next_url, }) url = self.get_qr_url(redirect_uri) diff --git a/apps/authentication/views/feishu.py b/apps/authentication/views/feishu.py index 88ba6636e..172aa2603 100644 --- a/apps/authentication/views/feishu.py +++ b/apps/authentication/views/feishu.py @@ -170,10 +170,11 @@ class FeiShuQRLoginView(FeiShuQRMixin, View): permission_classes = (AllowAny,) def get(self, request: HttpRequest): - redirect_url = request.GET.get('redirect_url') - + redirect_url = request.GET.get('redirect_url') or reverse('index') redirect_uri = reverse('authentication:feishu-qr-login-callback', external=True) - redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) + redirect_uri += '?' + urlencode({ + 'redirect_url': redirect_url, + }) url = self.get_qr_url(redirect_uri) return HttpResponseRedirect(url) diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index 5546bf619..9b7963de8 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -201,12 +201,12 @@ class WeComQRLoginView(WeComQRMixin, METAMixin, View): permission_classes = (AllowAny,) def get(self, request: HttpRequest): - redirect_url = request.GET.get('redirect_url') - + redirect_url = request.GET.get('redirect_url') or reverse('index') + next_url = self.get_next_url_from_meta() or reverse('index') redirect_uri = reverse('authentication:wecom-qr-login-callback', external=True) redirect_uri += '?' + urlencode({ 'redirect_url': redirect_url, - 'next': self.get_next_url_from_meta() + 'next': next_url, }) url = self.get_qr_url(redirect_uri) diff --git a/apps/users/utils.py b/apps/users/utils.py index f11d233e4..a2fb9afac 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -46,11 +46,13 @@ def get_user_or_pre_auth_user(request): def redirect_user_first_login_or_index(request, redirect_field_name): - url_in_post = request.POST.get(redirect_field_name) - if url_in_post: - return url_in_post - url_in_get = request.GET.get(redirect_field_name, reverse('index')) - return url_in_get + url = request.POST.get(redirect_field_name) + if not url: + url = request.GET.get(redirect_field_name) + # 防止 next 地址为 None + if not url or url.lower() in ['none']: + url = reverse('index') + return url def generate_otp_uri(username, otp_secret_key=None, issuer="JumpServer"): From 7eec50804c1901390784bf05adf49c802e172f3b Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 17 May 2022 19:34:15 +0800 Subject: [PATCH 114/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20encrypted?= =?UTF-8?q?=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/fields.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index abddeca01..f97925f43 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -27,8 +27,10 @@ class ReadableHiddenField(serializers.HiddenField): class EncryptedField(serializers.CharField): - def __init__(self, **kwargs): - kwargs['write_only'] = True + def __init__(self, write_only=None, **kwargs): + if write_only is None: + write_only = True + kwargs['write_only'] = write_only super().__init__(**kwargs) def to_internal_value(self, value): From 14710e9c9ee263e0e9d3b4ee231ec916125765a1 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 17 May 2022 20:32:36 +0800 Subject: [PATCH 115/258] =?UTF-8?q?feat:=20=E5=B7=A5=E5=8D=95=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E4=BA=BA=E4=B8=AD=E5=8E=BB=E9=99=A4=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/login_acl.py | 6 ++++-- apps/acls/models/login_asset_acl.py | 2 +- apps/assets/models/cmd_filter.py | 6 ++++-- apps/tickets/api/ticket.py | 7 ++++--- apps/tickets/models/ticket.py | 15 ++++++++++++--- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index 6e1fbff73..f9b24e426 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -123,6 +123,8 @@ class LoginACL(BaseACL): 'org_id': Organization.ROOT_ID, } ticket = Ticket.objects.create(**data) - ticket.create_process_map_and_node(self.reviewers.all()) - ticket.open(self.user) + applicant = self.user + assignees = self.reviewers.all() + ticket.create_process_map_and_node(assignees, applicant) + ticket.open(applicant) return ticket diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 0bde3c14f..dda9d97c1 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -97,7 +97,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): 'org_id': org_id, } ticket = Ticket.objects.create(**data) - ticket.create_process_map_and_node(assignees) + ticket.create_process_map_and_node(assignees, user) ticket.open(applicant=user) return ticket diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 82fd50e89..c92a25109 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -181,8 +181,10 @@ class CommandFilterRule(OrgModelMixin): 'org_id': org_id, } ticket = Ticket.objects.create(**data) - ticket.create_process_map_and_node(self.reviewers.all()) - ticket.open(applicant=session.user_obj) + applicant = session.user_obj + assignees = self.reviewers.all() + ticket.create_process_map_and_node(assignees, applicant) + ticket.open(applicant) return ticket @classmethod diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 8e7923e8e..586188898 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -53,9 +53,10 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): def perform_create(self, serializer): instance = serializer.save() - instance.create_related_node() - instance.process_map = instance.create_process_map() - instance.open(applicant=self.request.user) + applicant = self.request.user + instance.create_related_node(applicant) + instance.process_map = instance.create_process_map(applicant) + instance.open(applicant) @action(detail=False, methods=[POST], permission_classes=[RBACPermission, ]) def open(self, request, *args, **kwargs): diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py index aa536584d..c05fa8b3b 100644 --- a/apps/tickets/models/ticket.py +++ b/apps/tickets/models/ticket.py @@ -188,22 +188,30 @@ class Ticket(CommonModelMixin, StatusMixin, OrgModelMixin): .exclude(state=ProcessStatus.notified).first() return processor.assignee if processor else None - def create_related_node(self): + def ignore_applicant(self, assignees, applicant=None): + applicant = applicant if applicant else self.applicant + if len(assignees) != 1: + assignees = set(assignees) - {applicant, } + return list(assignees) + + def create_related_node(self, applicant=None): org_id = self.flow.org_id approval_rule = self.get_current_ticket_flow_approve() ticket_step = TicketStep.objects.create(ticket=self, level=self.approval_step) ticket_assignees = [] assignees = approval_rule.get_assignees(org_id=org_id) + assignees = self.ignore_applicant(assignees, applicant) for assignee in assignees: ticket_assignees.append(TicketAssignee(step=ticket_step, assignee=assignee)) TicketAssignee.objects.bulk_create(ticket_assignees) - def create_process_map(self): + def create_process_map(self, applicant=None): org_id = self.flow.org_id approval_rules = self.flow.rules.order_by('level') nodes = list() for node in approval_rules: assignees = node.get_assignees(org_id=org_id) + assignees = self.ignore_applicant(assignees, applicant) assignee_ids = [assignee.id for assignee in assignees] assignees_display = [str(assignee) for assignee in assignees] nodes.append( @@ -217,7 +225,8 @@ class Ticket(CommonModelMixin, StatusMixin, OrgModelMixin): return nodes # TODO 兼容不存在流的工单 - def create_process_map_and_node(self, assignees): + def create_process_map_and_node(self, assignees, applicant): + assignees = self.ignore_applicant(assignees, applicant) self.process_map = [{ 'approval_level': 1, 'state': 'notified', From 0c71190337c0959bc7bfe9eac7d181b4fe085a0c Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 17 May 2022 21:12:59 +0800 Subject: [PATCH 116/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20EncryptedFi?= =?UTF-8?q?eld=20=E5=AD=97=E6=AE=B5=E7=9A=84=20write=5Fonly=20=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=20(#8259)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修改 EncryptedField 字段的 write_only 属性 fix: 修改 EncryptedField 字段的 write_only 属性 * fix: 修改 EncryptedField 字段的 write_only 属性 Co-authored-by: Jiangjie.Bai Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com> --- apps/applications/serializers/application.py | 4 ++-- apps/assets/serializers/account.py | 3 ++- apps/common/drf/serializers.py | 21 +++++++++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 9b62d1dc1..35a07e262 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from assets.serializers.base import AuthSerializerMixin -from common.drf.serializers import MethodSerializer +from common.drf.serializers import MethodSerializer, SecretReadableMixin from .attrs import ( category_serializer_classes_mapping, type_serializer_classes_mapping, @@ -152,7 +152,7 @@ class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResou return super().to_representation(instance) -class AppAccountSecretSerializer(AppAccountSerializer): +class AppAccountSecretSerializer(SecretReadableMixin, AppAccountSerializer): class Meta(AppAccountSerializer.Meta): fields_backup = [ 'id', 'app_display', 'attrs', 'username', 'password', 'private_key', diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index daa7f38f8..bc4bea563 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -7,6 +7,7 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .base import AuthSerializerMixin from .utils import validate_password_contains_left_double_curly_bracket from common.utils.encode import ssh_pubkey_gen +from common.drf.serializers import SecretReadableMixin class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): @@ -70,7 +71,7 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): return super().to_representation(instance) -class AccountSecretSerializer(AccountSerializer): +class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): fields_backup = [ 'hostname', 'ip', 'platform', 'protocols', 'username', 'password', diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index 4beb59fe7..1dde4a77e 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -8,10 +8,12 @@ from common.mixins import BulkListSerializerMixin from django.utils.functional import cached_property from rest_framework.utils.serializer_helpers import BindingDict from common.mixins.serializers import BulkSerializerMixin +from common.drf.fields import EncryptedField __all__ = [ 'MethodSerializer', - 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer' + 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer', + 'SecretReadableMixin' ] @@ -83,3 +85,20 @@ class CeleryTaskSerializer(serializers.Serializer): task = serializers.CharField(read_only=True) +class SecretReadableMixin(serializers.Serializer): + """ 加密字段 (EncryptedField) 可读性 """ + + def __init__(self, *args, **kwargs): + super(SecretReadableMixin, self).__init__(*args, **kwargs) + if not hasattr(self, 'Meta') or not hasattr(self.Meta, 'extra_kwargs'): + return + extra_kwargs = self.Meta.extra_kwargs + for field_name, serializer_field in self.fields.items(): + if not isinstance(serializer_field, EncryptedField): + continue + if field_name not in extra_kwargs: + continue + field_extra_kwargs = extra_kwargs[field_name] + if 'write_only' not in field_extra_kwargs: + continue + serializer_field.write_only = field_extra_kwargs['write_only'] From c3c0f87c015a5e1d6a12a8f8e48d48ada55fd8e4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 17 May 2022 21:17:17 +0800 Subject: [PATCH 117/258] =?UTF-8?q?perf:=20domain=20gateway=20=E4=B9=9F?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/domain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 6932572f8..fa1a39574 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from common.validators import alphanumeric from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from common.drf.serializers import SecretReadableMixin from ..models import Domain, Gateway from .base import AuthSerializerMixin @@ -67,7 +68,7 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): } -class GatewayWithAuthSerializer(GatewaySerializer): +class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): class Meta(GatewaySerializer.Meta): extra_kwargs = { 'password': {'write_only': False}, From b76920a4bf09cdec43739182e5446195cfbacd25 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 17 May 2022 22:09:08 +0800 Subject: [PATCH 118/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E8=B5=84=E6=BA=90=E7=BB=9F=E8=AE=A1=E6=97=B6=20org=20?= =?UTF-8?q?=E4=B8=BANone=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/signal_handlers/cache.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 9de31bbb0..8bd2ca609 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -91,10 +91,11 @@ class OrgResourceStatisticsRefreshUtil: @classmethod def refresh_if_need(cls, instance): cache_field_name = cls.model_cache_field_mapper.get(type(instance)) - if cache_field_name: - org_cache = OrgResourceStatisticsCache(instance.org) - org_cache.expire(*cache_field_name) - OrgResourceStatisticsCache(Organization.root()).expire(*cache_field_name) + if not cache_field_name: + return + OrgResourceStatisticsCache(Organization.root()).expire(*cache_field_name) + if instance.org: + OrgResourceStatisticsCache(instance.org).expire(*cache_field_name) @receiver(post_save) From fe3059c1fd0904e3996db0734a3f3bccd4bc6bc4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 17 May 2022 23:49:06 +0800 Subject: [PATCH 119/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=AF=86=E7=A0=81=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/system_user.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index ab07533d0..278a99d87 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -5,6 +5,7 @@ from django.db.models import Count from common.mixins.serializers import BulkSerializerMixin from common.utils import ssh_pubkey_gen from common.drf.fields import EncryptedField +from common.drf.serializers import SecretReadableMixin from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import SystemUser, Asset @@ -252,7 +253,7 @@ class MiniSystemUserSerializer(serializers.ModelSerializer): fields = SystemUserSerializer.Meta.fields_mini -class SystemUserWithAuthInfoSerializer(SystemUserSerializer): +class SystemUserWithAuthInfoSerializer(SecretReadableMixin, SystemUserSerializer): class Meta(SystemUserSerializer.Meta): fields_mini = ['id', 'name', 'username'] fields_write_only = ['password', 'public_key', 'private_key'] @@ -268,6 +269,9 @@ class SystemUserWithAuthInfoSerializer(SystemUserSerializer): 'assets_amount': {'label': _('Asset')}, 'login_mode_display': {'label': _('Login mode display')}, 'created_by': {'read_only': True}, + 'password': {'write_only': False}, + 'private_key': {'write_only': False}, + 'token': {'write_only': False} } From 44ffd099244a3a9de7b6c4dad7d06c88b2624cf8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 17 May 2022 22:21:37 +0800 Subject: [PATCH 120/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E7=9A=84=20decode=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/crypto.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index 4fd9360f6..f6a690a82 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -91,12 +91,13 @@ class AESCrypto: def encrypt(self, text): aes = self.aes() - return str(base64.encodebytes(aes.encrypt(self.to_16(text))), - encoding='utf8').replace('\n', '') # 加密 + cipher = base64.encodebytes(aes.encrypt(self.to_16(text))) + return str(cipher, encoding='utf8').replace('\n', '') # 加密 def decrypt(self, text): aes = self.aes() - return str(aes.decrypt(base64.decodebytes(bytes(text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密 + text_decoded = base64.decodebytes(bytes(text, encoding='utf8')) + return str(aes.decrypt(text_decoded).rstrip(b'\0').decode("utf8")) class AESCryptoGCM: @@ -234,6 +235,8 @@ def rsa_decrypt(cipher_text, rsa_private_key=None): def rsa_decrypt_by_session_pkey(value): from jumpserver.utils import current_request + if not current_request: + return value private_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME private_key = current_request.session.get(private_key_name) @@ -254,7 +257,11 @@ def decrypt_password(value): key_cipher, password_cipher = cipher aes_key = rsa_decrypt_by_session_pkey(key_cipher) aes = get_aes_crypto(aes_key, 'ECB') - password = aes.decrypt(password_cipher) + try: + password = aes.decrypt(password_cipher) + except UnicodeDecodeError as e: + logging.error("Decript password error: {}, {}".format(password_cipher, e)) + return value return password From e5f4b8000e173b40543fec9be1656ece3b5fc946 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 17 May 2022 17:02:16 +0800 Subject: [PATCH 121/258] stash --- apps/authentication/middleware.py | 30 ++++++++++++------------------ apps/jumpserver/settings/base.py | 1 - 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index d2b4ff19e..96d0017e9 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -45,21 +45,7 @@ class MFAMiddleware: class SessionCookieMiddleware(MiddlewareMixin): @staticmethod - def process_response(request, response: HttpResponse): - key = settings.SESSION_COOKIE_NAME_PREFIX_KEY - value = settings.SESSION_COOKIE_NAME_PREFIX - if request.COOKIES.get(key) == value: - return response - response.set_cookie(key, value) - return response - - -class EncryptedMiddleware: - def __init__(self, get_response): - self.get_response = get_response - - @staticmethod - def check_key_pair(request, response): + def set_cookie_public_key(request, response): pub_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME public_key = request.session.get(pub_key_name) cookie_key = request.COOKIES.get(pub_key_name) @@ -73,7 +59,15 @@ class EncryptedMiddleware: request.session[pri_key_name] = private_key response.set_cookie(pub_key_name, public_key_decode) - def __call__(self, request): - response = self.get_response(request) - self.check_key_pair(request, response) + @staticmethod + def set_session_cooke_prefix(request, response): + key = settings.SESSION_COOKIE_NAME_PREFIX_KEY + value = settings.SESSION_COOKIE_NAME_PREFIX + if request.COOKIES.get(key) == value: + return response + response.set_cookie(key, value) + + def process_response(self, request, response: HttpResponse): + self.set_session_cooke_prefix(request, response) + self.set_session_cooke_prefix(request, response) return response diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 327c3ea97..c1baf2882 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -95,7 +95,6 @@ MIDDLEWARE = [ 'authentication.backends.cas.middleware.CASMiddleware', 'authentication.middleware.MFAMiddleware', 'authentication.middleware.SessionCookieMiddleware', - 'authentication.middleware.EncryptedMiddleware', 'simple_history.middleware.HistoryRequestMiddleware', ] From aa7540045b00d072a1fe5a469c9b68caca9e0905 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 18 May 2022 14:42:54 +0800 Subject: [PATCH 122/258] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20session=20?= =?UTF-8?q?guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/middleware.py | 20 +++++++++++++++++--- apps/authentication/signal_handlers.py | 3 +++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index 96d0017e9..f1f60bbc5 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -60,14 +60,28 @@ class SessionCookieMiddleware(MiddlewareMixin): response.set_cookie(pub_key_name, public_key_decode) @staticmethod - def set_session_cooke_prefix(request, response): + def set_cookie_session_prefix(request, response): key = settings.SESSION_COOKIE_NAME_PREFIX_KEY value = settings.SESSION_COOKIE_NAME_PREFIX if request.COOKIES.get(key) == value: return response response.set_cookie(key, value) + @staticmethod + def set_cookie_session_expire(request, response): + if not request.session.get('auth_session_expiration_required'): + return + value = 'age' + if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE or \ + not request.session.get('auto_login', False): + value = 'close' + + age = request.session.get_expiry_age() + response.set_cookie('jms_session_expire', value, max_age=age) + request.session.pop('auth_session_expiration_required', None) + def process_response(self, request, response: HttpResponse): - self.set_session_cooke_prefix(request, response) - self.set_session_cooke_prefix(request, response) + self.set_cookie_session_prefix(request, response) + self.set_cookie_public_key(request, response) + self.set_cookie_session_expire(request, response) return response diff --git a/apps/authentication/signal_handlers.py b/apps/authentication/signal_handlers.py index 0d2a617f9..ac155dcf0 100644 --- a/apps/authentication/signal_handlers.py +++ b/apps/authentication/signal_handlers.py @@ -35,6 +35,9 @@ def on_user_auth_login_success(sender, user, request, **kwargs): session.delete() cache.set(lock_key, request.session.session_key, None) + # 标记登录,设置 cookie,前端可以控制刷新, Middleware 会拦截这个生成 cookie + request.session['auth_session_expiration_required'] = 1 + @receiver(openid_user_login_success) def on_oidc_user_login_success(sender, request, user, create=False, **kwargs): From c8d7c7c56f454bcdfadd4d4a409b81116ff41f0d Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 18 May 2022 18:32:53 +0800 Subject: [PATCH 123/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Doidc=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E4=B8=8D=E5=8C=BA=E5=88=86=E5=A4=A7=E5=B0=8F=E5=86=99?= =?UTF-8?q?=20(#8267)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/authentication/backends/oidc/backends.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index 8d4f2f629..daa614ec8 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -281,6 +281,11 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend): try: claims_response.raise_for_status() claims = claims_response.json() + preferred_username = claims.get('preferred_username') + if preferred_username and \ + preferred_username.lower() == username.lower() and \ + preferred_username != username: + return except Exception as e: error = "Json claims response error, claims response " \ "content is: {}, error is: {}".format(claims_response.content, str(e)) @@ -309,5 +314,3 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend): openid_user_login_failed.send( sender=self.__class__, request=request, username=username, reason="User is invalid" ) - return None - From 99e1b2cf92f419902c37aa1884d36b7be6fd90de Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 18 May 2022 19:52:22 +0800 Subject: [PATCH 124/258] =?UTF-8?q?fix:=20=E4=B8=8D=E6=94=AF=E6=8C=81es8?= =?UTF-8?q?=20=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 ++-- apps/locale/ja/LC_MESSAGES/django.po | 4 ++++ apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 4 ++++ apps/terminal/backends/command/es.py | 14 ++++++++++++-- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index b6af2bdf7..29bcfa164 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:843b6dffe6af09073053e21f65be4c8264e6dee05509b375c8191dde8c9079b6 -size 127386 +oid sha256:5effe3cb5eb97d51bf886d21dfbe785bb789722f30774a6595eac7aa79b6315a +size 127478 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 6b97b95d0..c976bf085 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -4651,6 +4651,10 @@ msgstr "ターミナル管理" msgid "Invalid elasticsearch config" msgstr "無効なElasticsearch構成" +#: terminal/backends/command/es.py:33 +msgid "Not Support Elasticsearch8" +msgstr "サポートされていません Elasticsearch8" + #: terminal/backends/command/models.py:16 msgid "Ordinary" msgstr "普通" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 88b5034ea..317e02a96 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a78975a5a6669bfcc0f99bc4d47811be82a0620e873e51e4a17d06548e3b1e7f -size 105269 +oid sha256:d30f8d3abc215c35bb2dd889374eeef896a193f8010ccd5ae8e27aa408f045c7 +size 105337 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 20a071d1c..8dee918fb 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -4579,6 +4579,10 @@ msgstr "终端管理" msgid "Invalid elasticsearch config" msgstr "无效的 Elasticsearch 配置" +#: terminal/backends/command/es.py:33 +msgid "Not Support Elasticsearch8" +msgstr "不支持 Elasticsearch8" + #: terminal/backends/command/models.py:16 msgid "Ordinary" msgstr "普通" diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 997bd30fd..ae76b2974 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -28,6 +28,11 @@ class InvalidElasticsearch(JMSException): default_detail = _('Invalid elasticsearch config') +class NotSupportElasticsearch8(JMSException): + default_code = 'not_support_elasticsearch8' + default_detail = _('Not Support Elasticsearch8') + + class CommandStore(object): def __init__(self, config): self.doc_type = config.get("DOC_TYPE") or '_doc' @@ -68,13 +73,18 @@ class CommandStore(object): if not self.ping(timeout=3): return False + info = self.es.info() + version = info['version']['number'].split('.')[0] + + if version == '8': + raise NotSupportElasticsearch8 + try: # 获取索引信息,如果没有定义,直接返回 data = self.es.indices.get_mapping(self.index) except NotFoundError: return False - info = self.es.info() - version = info['version']['number'].split('.')[0] + try: if version == '6': # 检测索引是不是新的类型 es6 From 10295569025b12b054e7692deda55105d1e07283 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 20 May 2022 10:01:41 +0800 Subject: [PATCH 125/258] =?UTF-8?q?perf:=20remote=20app=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E4=B9=9F=E5=8A=A0=E5=AF=86=20(#8274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: remote app 字段也加密 * perf: 修改一些加密字段 Co-authored-by: ibuler --- .../attrs/application_type/chrome.py | 19 +- .../attrs/application_type/custom.py | 13 +- .../attrs/application_type/mysql_workbench.py | 13 +- .../attrs/application_type/vmware_client.py | 13 +- apps/locale/ja/LC_MESSAGES/django.mo | 3 - apps/locale/ja/LC_MESSAGES/django.po | 366 ++++++++--------- apps/locale/zh/LC_MESSAGES/django.mo | 3 - apps/locale/zh/LC_MESSAGES/django.po | 368 +++++++++--------- apps/settings/serializers/auth/dingtalk.py | 4 +- apps/settings/serializers/auth/feishu.py | 4 +- apps/settings/serializers/auth/ldap.py | 6 +- apps/settings/serializers/auth/oidc.py | 6 +- apps/settings/serializers/auth/radius.py | 10 +- apps/settings/serializers/auth/sms.py | 7 +- apps/settings/serializers/auth/wecom.py | 6 +- apps/settings/serializers/email.py | 8 +- apps/terminal/serializers/storage.py | 17 +- apps/users/serializers/profile.py | 20 +- 18 files changed, 456 insertions(+), 430 deletions(-) delete mode 100644 apps/locale/ja/LC_MESSAGES/django.mo delete mode 100644 apps/locale/zh/LC_MESSAGES/django.mo diff --git a/apps/applications/serializers/attrs/application_type/chrome.py b/apps/applications/serializers/attrs/application_type/chrome.py index 96b4587e7..08035bc31 100644 --- a/apps/applications/serializers/attrs/application_type/chrome.py +++ b/apps/applications/serializers/attrs/application_type/chrome.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField from ..application_category import RemoteAppSerializer __all__ = ['ChromeSerializer', 'ChromeSecretSerializer'] @@ -13,19 +14,21 @@ class ChromeSerializer(RemoteAppSerializer): max_length=128, label=_('Application path'), default=CHROME_PATH, allow_null=True, ) chrome_target = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Target URL'), allow_null=True, + max_length=128, allow_blank=True, required=False, + label=_('Target URL'), allow_null=True, ) chrome_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Chrome username'), allow_null=True, + max_length=128, allow_blank=True, required=False, + label=_('Chrome username'), allow_null=True, ) - chrome_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Chrome password'), - allow_null=True + chrome_password = EncryptedField( + max_length=128, allow_blank=True, required=False, + label=_('Chrome password'), allow_null=True ) class ChromeSecretSerializer(ChromeSerializer): - chrome_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, read_only=True, label=_('Chrome password'), - allow_null=True + chrome_password = EncryptedField( + max_length=128, allow_blank=True, required=False, + label=_('Chrome password'), allow_null=True, write_only=False ) diff --git a/apps/applications/serializers/attrs/application_type/custom.py b/apps/applications/serializers/attrs/application_type/custom.py index 0a58c28b1..cfef59d5f 100644 --- a/apps/applications/serializers/attrs/application_type/custom.py +++ b/apps/applications/serializers/attrs/application_type/custom.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField from ..application_category import RemoteAppSerializer __all__ = ['CustomSerializer', 'CustomSecretSerializer'] @@ -19,14 +20,14 @@ class CustomSerializer(RemoteAppSerializer): max_length=128, allow_blank=True, required=False, label=_('Custom Username'), allow_null=True, ) - custom_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Custom password'), - allow_null=True, + custom_password = EncryptedField( + max_length=128, allow_blank=True, required=False, + label=_('Custom password'), allow_null=True, ) class CustomSecretSerializer(RemoteAppSerializer): - custom_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, read_only=True, label=_('Custom password'), - allow_null=True, + custom_password = EncryptedField( + max_length=128, allow_blank=True, required=False, write_only=False, + label=_('Custom password'), allow_null=True, ) diff --git a/apps/applications/serializers/attrs/application_type/mysql_workbench.py b/apps/applications/serializers/attrs/application_type/mysql_workbench.py index bc41a689b..6092b2ed1 100644 --- a/apps/applications/serializers/attrs/application_type/mysql_workbench.py +++ b/apps/applications/serializers/attrs/application_type/mysql_workbench.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField from ..application_category import RemoteAppSerializer __all__ = ['MySQLWorkbenchSerializer', 'MySQLWorkbenchSecretSerializer'] @@ -29,14 +30,14 @@ class MySQLWorkbenchSerializer(RemoteAppSerializer): max_length=128, allow_blank=True, required=False, label=_('Mysql workbench username'), allow_null=True, ) - mysql_workbench_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Mysql workbench password'), - allow_null=True, + mysql_workbench_password = EncryptedField( + max_length=128, allow_blank=True, required=False, + label=_('Mysql workbench password'), allow_null=True, ) class MySQLWorkbenchSecretSerializer(RemoteAppSerializer): - mysql_workbench_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, read_only=True, label=_('Mysql workbench password'), - allow_null=True, + mysql_workbench_password = EncryptedField( + max_length=128, allow_blank=True, required=False, write_only=False, + label=_('Mysql workbench password'), allow_null=True, ) diff --git a/apps/applications/serializers/attrs/application_type/vmware_client.py b/apps/applications/serializers/attrs/application_type/vmware_client.py index 6ec3975cb..d6b9cef0b 100644 --- a/apps/applications/serializers/attrs/application_type/vmware_client.py +++ b/apps/applications/serializers/attrs/application_type/vmware_client.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField from ..application_category import RemoteAppSerializer __all__ = ['VMwareClientSerializer', 'VMwareClientSecretSerializer'] @@ -25,14 +26,14 @@ class VMwareClientSerializer(RemoteAppSerializer): max_length=128, allow_blank=True, required=False, label=_('Vmware username'), allow_null=True ) - vmware_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Vmware password'), - allow_null=True + vmware_password = EncryptedField( + max_length=128, allow_blank=True, required=False, + label=_('Vmware password'), allow_null=True ) class VMwareClientSecretSerializer(RemoteAppSerializer): - vmware_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, read_only=True, label=_('Vmware password'), - allow_null=True + vmware_password = EncryptedField( + max_length=128, allow_blank=True, required=False, write_only=False, + label=_('Vmware password'), allow_null=True ) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo deleted file mode 100644 index 29bcfa164..000000000 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5effe3cb5eb97d51bf886d21dfbe785bb789722f30774a6595eac7aa79b6315a -size 127478 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index c976bf085..d850a1cc1 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-17 18:02+0800\n" +"POT-Creation-Date: 2022-05-19 13:16+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -129,7 +129,7 @@ msgstr "システムユーザー" #: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:264 audits/models.py:39 +#: assets/serializers/system_user.py:269 audits/models.py:39 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 @@ -162,7 +162,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: users/templates/users/_msg_user_created.html:12 #: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:195 -#: xpack/plugins/cloud/serializers/account_attrs.py:22 +#: xpack/plugins/cloud/serializers/account_attrs.py:24 msgid "Username" msgstr "ユーザー名" @@ -177,9 +177,9 @@ msgstr "" "db8:1a:1110:::/64 (ドメイン名サポート)" #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 -#: applications/serializers/attrs/application_type/mysql_workbench.py:17 +#: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:210 assets/models/domain.py:60 -#: assets/serializers/account.py:13 +#: assets/serializers/account.py:14 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -188,7 +188,7 @@ msgid "IP" msgstr "IP" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 -#: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 +#: assets/serializers/account.py:15 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 msgid "Hostname" msgstr "ホスト名" @@ -203,7 +203,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 #: assets/models/domain.py:62 assets/models/user.py:248 -#: terminal/serializers/session.py:30 terminal/serializers/storage.py:67 +#: terminal/serializers/session.py:30 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "プロトコル" @@ -245,7 +245,7 @@ msgstr "アプリケーション" #: applications/const.py:8 #: applications/serializers/attrs/application_category/db.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:25 +#: applications/serializers/attrs/application_type/mysql_workbench.py:26 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "データベース" @@ -341,7 +341,7 @@ msgstr "カテゴリ表示" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:27 +#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:29 #: audits/serializers.py:29 perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 @@ -353,7 +353,7 @@ msgstr "タイプ表示" #: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 +#: assets/serializers/account.py:19 assets/serializers/cmd_filter.py:28 #: assets/serializers/cmd_filter.py:48 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 @@ -363,7 +363,7 @@ msgid "Date created" msgstr "作成された日付" #: applications/serializers/application.py:104 assets/models/base.py:182 -#: assets/models/gathered_user.py:20 assets/serializers/account.py:21 +#: assets/models/gathered_user.py:20 assets/serializers/account.py:22 #: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 #: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 @@ -387,88 +387,88 @@ msgstr "クラスター" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 #: terminal/models/endpoint.py:11 -#: xpack/plugins/cloud/serializers/account_attrs.py:68 +#: xpack/plugins/cloud/serializers/account_attrs.py:70 msgid "Host" msgstr "ホスト" #: applications/serializers/attrs/application_category/db.py:12 #: applications/serializers/attrs/application_type/mongodb.py:10 #: applications/serializers/attrs/application_type/mysql.py:10 -#: applications/serializers/attrs/application_type/mysql_workbench.py:21 +#: applications/serializers/attrs/application_type/mysql_workbench.py:22 #: applications/serializers/attrs/application_type/oracle.py:10 #: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/sqlserver.py:10 #: assets/models/asset.py:214 assets/models/domain.py:61 #: settings/serializers/auth/radius.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:69 +#: xpack/plugins/cloud/serializers/account_attrs.py:71 msgid "Port" msgstr "ポート" #: applications/serializers/attrs/application_category/remote_app.py:39 -#: applications/serializers/attrs/application_type/chrome.py:13 -#: applications/serializers/attrs/application_type/mysql_workbench.py:13 -#: applications/serializers/attrs/application_type/vmware_client.py:17 +#: applications/serializers/attrs/application_type/chrome.py:14 +#: applications/serializers/attrs/application_type/mysql_workbench.py:14 +#: applications/serializers/attrs/application_type/vmware_client.py:18 msgid "Application path" msgstr "アプリケーションパス" #: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:163 +#: assets/serializers/system_user.py:168 #: xpack/plugins/change_auth_plan/serializers/asset.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:69 #: xpack/plugins/change_auth_plan/serializers/asset.py:72 #: xpack/plugins/change_auth_plan/serializers/asset.py:103 -#: xpack/plugins/cloud/serializers/account_attrs.py:52 +#: xpack/plugins/cloud/serializers/account_attrs.py:54 msgid "This field is required." msgstr "このフィールドは必須です。" -#: applications/serializers/attrs/application_type/chrome.py:16 -#: applications/serializers/attrs/application_type/vmware_client.py:21 +#: applications/serializers/attrs/application_type/chrome.py:18 +#: applications/serializers/attrs/application_type/vmware_client.py:22 msgid "Target URL" msgstr "ターゲットURL" -#: applications/serializers/attrs/application_type/chrome.py:19 +#: applications/serializers/attrs/application_type/chrome.py:22 msgid "Chrome username" msgstr "Chromeユーザー名" -#: applications/serializers/attrs/application_type/chrome.py:22 -#: applications/serializers/attrs/application_type/chrome.py:29 +#: applications/serializers/attrs/application_type/chrome.py:26 +#: applications/serializers/attrs/application_type/chrome.py:33 msgid "Chrome password" msgstr "Chromeパスワード" -#: applications/serializers/attrs/application_type/custom.py:11 +#: applications/serializers/attrs/application_type/custom.py:12 msgid "Operating parameter" msgstr "操作パラメータ" -#: applications/serializers/attrs/application_type/custom.py:15 +#: applications/serializers/attrs/application_type/custom.py:16 msgid "Target url" msgstr "ターゲットURL" -#: applications/serializers/attrs/application_type/custom.py:19 +#: applications/serializers/attrs/application_type/custom.py:20 msgid "Custom Username" msgstr "カスタムユーザー名" -#: applications/serializers/attrs/application_type/custom.py:23 -#: applications/serializers/attrs/application_type/custom.py:30 +#: applications/serializers/attrs/application_type/custom.py:25 +#: applications/serializers/attrs/application_type/custom.py:32 #: xpack/plugins/change_auth_plan/models/base.py:27 msgid "Custom password" msgstr "カスタムパスワード" -#: applications/serializers/attrs/application_type/mysql_workbench.py:29 +#: applications/serializers/attrs/application_type/mysql_workbench.py:30 msgid "Mysql workbench username" msgstr "Mysql workbench のユーザー名" -#: applications/serializers/attrs/application_type/mysql_workbench.py:33 -#: applications/serializers/attrs/application_type/mysql_workbench.py:40 +#: applications/serializers/attrs/application_type/mysql_workbench.py:35 +#: applications/serializers/attrs/application_type/mysql_workbench.py:42 msgid "Mysql workbench password" msgstr "Mysql workbench パスワード" -#: applications/serializers/attrs/application_type/vmware_client.py:25 +#: applications/serializers/attrs/application_type/vmware_client.py:26 msgid "Vmware username" msgstr "Vmware ユーザー名" -#: applications/serializers/attrs/application_type/vmware_client.py:29 -#: applications/serializers/attrs/application_type/vmware_client.py:36 +#: applications/serializers/attrs/application_type/vmware_client.py:31 +#: applications/serializers/attrs/application_type/vmware_client.py:38 msgid "Vmware password" msgstr "Vmware パスワード" @@ -510,7 +510,7 @@ msgid "Internal" msgstr "内部" #: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:15 assets/serializers/asset.py:63 +#: assets/serializers/account.py:16 assets/serializers/asset.py:63 #: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "プラットフォーム" @@ -571,7 +571,7 @@ msgstr "システムアーキテクチャ" msgid "Hostname raw" msgstr "ホスト名生" -#: assets/models/asset.py:215 assets/serializers/account.py:16 +#: assets/models/asset.py:215 assets/serializers/account.py:17 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" @@ -750,7 +750,7 @@ msgstr "OK" msgid "Failed" msgstr "失敗しました" -#: assets/models/base.py:38 assets/serializers/domain.py:46 +#: assets/models/base.py:38 assets/serializers/domain.py:47 msgid "Connectivity" msgstr "接続性" @@ -769,7 +769,7 @@ msgstr "確認済みの日付" #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/models/base.py:196 -#: xpack/plugins/cloud/serializers/account_attrs.py:24 +#: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Password" msgstr "パスワード" @@ -833,7 +833,7 @@ msgstr "デフォルトクラスター" msgid "User group" msgstr "ユーザーグループ" -#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:54 +#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:59 msgid "Command filter" msgstr "コマンドフィルター" @@ -958,7 +958,7 @@ msgstr "フルバリュー" msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:559 assets/serializers/system_user.py:263 +#: assets/models/node.py:559 assets/serializers/system_user.py:268 #: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -983,7 +983,7 @@ msgstr "共通ユーザー" msgid "Username same with user" msgstr "ユーザーと同じユーザー名" -#: assets/models/user.py:240 assets/serializers/domain.py:29 +#: assets/models/user.py:240 assets/serializers/domain.py:30 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:39 msgid "Assets" @@ -1017,7 +1017,8 @@ msgstr "ログインモード" msgid "SFTP Root" msgstr "SFTPルート" -#: assets/models/user.py:254 authentication/models.py:49 +#: assets/models/user.py:254 assets/serializers/system_user.py:32 +#: authentication/models.py:49 msgid "Token" msgstr "トークン" @@ -1068,7 +1069,7 @@ msgstr "" "されていません-個人情報にアクセスしてください-> ファイル暗号化パスワードを設" "定してください暗号化パスワード" -#: assets/serializers/account.py:40 assets/serializers/account.py:83 +#: assets/serializers/account.py:41 assets/serializers/account.py:84 msgid "System user display" msgstr "システムユーザー表示" @@ -1143,17 +1144,17 @@ msgstr "アクション表示" msgid "Pattern" msgstr "パターン" -#: assets/serializers/domain.py:13 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:59 +#: assets/serializers/domain.py:14 assets/serializers/label.py:12 +#: assets/serializers/system_user.py:64 #: perms/serializers/asset/permission.py:49 msgid "Assets amount" msgstr "資産額" -#: assets/serializers/domain.py:14 +#: assets/serializers/domain.py:15 msgid "Applications amount" msgstr "申し込み金額" -#: assets/serializers/domain.py:15 +#: assets/serializers/domain.py:16 msgid "Gateways count" msgstr "ゲートウェイ数" @@ -1169,78 +1170,78 @@ msgstr "含まれない:/" msgid "The same level node name cannot be the same" msgstr "同じレベルのノード名を同じにすることはできません。" -#: assets/serializers/system_user.py:28 +#: assets/serializers/system_user.py:30 msgid "SSH key fingerprint" msgstr "SSHキー指紋" -#: assets/serializers/system_user.py:30 +#: assets/serializers/system_user.py:35 #: perms/serializers/application/permission.py:46 msgid "Apps amount" msgstr "アプリの量" -#: assets/serializers/system_user.py:58 +#: assets/serializers/system_user.py:63 #: perms/serializers/asset/permission.py:50 msgid "Nodes amount" msgstr "ノード量" -#: assets/serializers/system_user.py:60 assets/serializers/system_user.py:265 +#: assets/serializers/system_user.py:65 assets/serializers/system_user.py:270 msgid "Login mode display" msgstr "ログインモード表示" -#: assets/serializers/system_user.py:62 +#: assets/serializers/system_user.py:67 msgid "Ad domain" msgstr "広告ドメイン" -#: assets/serializers/system_user.py:63 +#: assets/serializers/system_user.py:68 msgid "Is asset protocol" msgstr "資産プロトコルです" -#: assets/serializers/system_user.py:64 +#: assets/serializers/system_user.py:69 msgid "Only ssh and automatic login system users are supported" msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" -#: assets/serializers/system_user.py:104 +#: assets/serializers/system_user.py:109 msgid "Username same with user with protocol {} only allow 1" msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" -#: assets/serializers/system_user.py:117 common/validators.py:14 +#: assets/serializers/system_user.py:122 common/validators.py:14 msgid "Special char not allowed" msgstr "特別なcharは許可されていません" -#: assets/serializers/system_user.py:127 +#: assets/serializers/system_user.py:132 msgid "* Automatic login mode must fill in the username." msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" -#: assets/serializers/system_user.py:142 +#: assets/serializers/system_user.py:147 msgid "Path should starts with /" msgstr "パスは/で始まる必要があります" -#: assets/serializers/system_user.py:154 +#: assets/serializers/system_user.py:159 msgid "Password or private key required" msgstr "パスワードまたは秘密鍵が必要" -#: assets/serializers/system_user.py:168 +#: assets/serializers/system_user.py:173 msgid "Only ssh protocol system users are allowed" msgstr "Sshプロトコルシステムユーザーのみが許可されています" -#: assets/serializers/system_user.py:172 +#: assets/serializers/system_user.py:177 msgid "The protocol must be consistent with the current user: {}" msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" -#: assets/serializers/system_user.py:176 +#: assets/serializers/system_user.py:181 msgid "Only system users with automatic login are allowed" msgstr "自動ログインを持つシステムユーザーのみが許可されます" -#: assets/serializers/system_user.py:281 +#: assets/serializers/system_user.py:289 msgid "System user name" msgstr "システムユーザー名" -#: assets/serializers/system_user.py:282 orgs/mixins/serializers.py:26 +#: assets/serializers/system_user.py:290 orgs/mixins/serializers.py:26 #: rbac/serializers/rolebinding.py:23 msgid "Org name" msgstr "組織名" -#: assets/serializers/system_user.py:291 +#: assets/serializers/system_user.py:299 msgid "Asset hostname" msgstr "資産ホスト名" @@ -1495,7 +1496,7 @@ msgstr "ユーザーエージェント" #: audits/models.py:126 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:684 -#: users/serializers/profile.py:124 +#: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" @@ -1542,7 +1543,7 @@ msgstr "ホスト表示" msgid "Result" msgstr "結果" -#: audits/serializers.py:98 terminal/serializers/storage.py:156 +#: audits/serializers.py:98 terminal/serializers/storage.py:157 msgid "Hosts" msgstr "ホスト" @@ -2052,7 +2053,7 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:34 terminal/serializers/storage.py:28 +#: authentication/models.py:34 msgid "Access key" msgstr "アクセスキー" @@ -2141,13 +2142,13 @@ msgstr "表示" #: authentication/templates/authentication/_access_key_modal.html:66 #: settings/serializers/security.py:39 users/models/user.py:556 -#: users/serializers/profile.py:114 users/templates/users/mfa_setting.html:61 +#: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "無効化" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:557 users/serializers/profile.py:115 +#: users/models/user.py:557 users/serializers/profile.py:117 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2377,15 +2378,15 @@ msgstr "DingTalkはすでに別のユーザーにバインドされています" msgid "Binding DingTalk successfully" msgstr "DingTalkのバインドに成功" -#: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293 +#: authentication/views/dingtalk.py:240 authentication/views/dingtalk.py:294 msgid "Failed to get user from DingTalk" msgstr "DingTalkからユーザーを取得できませんでした" -#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299 +#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 msgid "DingTalk is not bound" msgstr "DingTalkはバインドされていません" -#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 +#: authentication/views/dingtalk.py:247 authentication/views/dingtalk.py:301 msgid "Please login with a password and then bind the DingTalk" msgstr "パスワードでログインし、DingTalkをバインドしてください" @@ -2414,15 +2415,15 @@ msgstr "本を飛ばす" msgid "Binding FeiShu successfully" msgstr "本を飛ばすのバインドに成功" -#: authentication/views/feishu.py:200 +#: authentication/views/feishu.py:201 msgid "Failed to get user from FeiShu" msgstr "本を飛ばすからユーザーを取得できませんでした" -#: authentication/views/feishu.py:206 +#: authentication/views/feishu.py:207 msgid "FeiShu is not bound" msgstr "本を飛ばすは拘束されていません" -#: authentication/views/feishu.py:207 +#: authentication/views/feishu.py:208 msgid "Please login with a password and then bind the FeiShu" msgstr "パスワードでログインしてから本を飛ばすをバインドしてください" @@ -3249,7 +3250,7 @@ msgstr "組織の役割バインディング" msgid "System role binding" msgstr "システムロールバインディング" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:130 +#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 msgid "Perms" msgstr "パーマ" @@ -3475,7 +3476,7 @@ msgstr "ログインリダイレクトの有効化msg" msgid "Enable CAS Auth" msgstr "CAS 認証の有効化" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:40 +#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:42 msgid "Server url" msgstr "サービス側アドレス" @@ -3503,11 +3504,11 @@ msgstr "マッピングのプロパティ" msgid "Create user if not" msgstr "そうでない場合はユーザーを作成" -#: settings/serializers/auth/dingtalk.py:11 +#: settings/serializers/auth/dingtalk.py:13 msgid "Enable DingTalk Auth" msgstr "ピン認証の有効化" -#: settings/serializers/auth/feishu.py:10 +#: settings/serializers/auth/feishu.py:12 msgid "Enable FeiShu Auth" msgstr "飛本認証の有効化" @@ -3564,96 +3565,96 @@ msgstr "ページサイズを検索" msgid "Enable LDAP auth" msgstr "LDAP認証の有効化" -#: settings/serializers/auth/oidc.py:12 +#: settings/serializers/auth/oidc.py:14 msgid "Base site url" msgstr "ベースサイトのアドレス" -#: settings/serializers/auth/oidc.py:15 +#: settings/serializers/auth/oidc.py:17 msgid "Client Id" msgstr "クライアントID" -#: settings/serializers/auth/oidc.py:18 -#: xpack/plugins/cloud/serializers/account_attrs.py:34 +#: settings/serializers/auth/oidc.py:20 +#: xpack/plugins/cloud/serializers/account_attrs.py:36 msgid "Client Secret" msgstr "クライアント秘密" -#: settings/serializers/auth/oidc.py:26 +#: settings/serializers/auth/oidc.py:28 msgid "Client authentication method" msgstr "クライアント認証方式" -#: settings/serializers/auth/oidc.py:28 +#: settings/serializers/auth/oidc.py:30 msgid "Share session" msgstr "セッションの共有" -#: settings/serializers/auth/oidc.py:30 +#: settings/serializers/auth/oidc.py:32 msgid "Ignore ssl verification" msgstr "Ssl検証を無視する" -#: settings/serializers/auth/oidc.py:37 +#: settings/serializers/auth/oidc.py:39 msgid "Use Keycloak" msgstr "Keycloakを使用する" -#: settings/serializers/auth/oidc.py:43 +#: settings/serializers/auth/oidc.py:45 msgid "Realm name" msgstr "レルム名" -#: settings/serializers/auth/oidc.py:49 +#: settings/serializers/auth/oidc.py:51 msgid "Enable OPENID Auth" msgstr "OIDC認証の有効化" -#: settings/serializers/auth/oidc.py:51 +#: settings/serializers/auth/oidc.py:53 msgid "Provider endpoint" msgstr "プロバイダーエンドポイント" -#: settings/serializers/auth/oidc.py:54 +#: settings/serializers/auth/oidc.py:56 msgid "Provider auth endpoint" msgstr "認証エンドポイントアドレス" -#: settings/serializers/auth/oidc.py:57 +#: settings/serializers/auth/oidc.py:59 msgid "Provider token endpoint" msgstr "プロバイダートークンエンドポイント" -#: settings/serializers/auth/oidc.py:60 +#: settings/serializers/auth/oidc.py:62 msgid "Provider jwks endpoint" msgstr "プロバイダーjwksエンドポイント" -#: settings/serializers/auth/oidc.py:63 +#: settings/serializers/auth/oidc.py:65 msgid "Provider userinfo endpoint" msgstr "プロバイダーuserinfoエンドポイント" -#: settings/serializers/auth/oidc.py:66 +#: settings/serializers/auth/oidc.py:68 msgid "Provider end session endpoint" msgstr "プロバイダーのセッション終了エンドポイント" -#: settings/serializers/auth/oidc.py:69 +#: settings/serializers/auth/oidc.py:71 msgid "Provider sign alg" msgstr "プロビダーサインalg" -#: settings/serializers/auth/oidc.py:72 +#: settings/serializers/auth/oidc.py:74 msgid "Provider sign key" msgstr "プロバイダ署名キー" -#: settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oidc.py:76 msgid "Scopes" msgstr "スコープ" -#: settings/serializers/auth/oidc.py:76 +#: settings/serializers/auth/oidc.py:78 msgid "Id token max age" msgstr "IDトークンの最大年齢" -#: settings/serializers/auth/oidc.py:79 +#: settings/serializers/auth/oidc.py:81 msgid "Id token include claims" msgstr "IDトークンにはクレームが含まれます" -#: settings/serializers/auth/oidc.py:81 +#: settings/serializers/auth/oidc.py:83 msgid "Use state" msgstr "使用状態" -#: settings/serializers/auth/oidc.py:82 +#: settings/serializers/auth/oidc.py:84 msgid "Use nonce" msgstr "Nonceを使用" -#: settings/serializers/auth/oidc.py:84 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oidc.py:86 settings/serializers/auth/saml2.py:33 msgid "Always update user" msgstr "常にユーザーを更新" @@ -3689,25 +3690,25 @@ msgstr "SP プライベートキー" msgid "SP cert" msgstr "SP 証明書" -#: settings/serializers/auth/sms.py:10 +#: settings/serializers/auth/sms.py:11 msgid "Enable SMS" msgstr "SMSの有効化" -#: settings/serializers/auth/sms.py:12 +#: settings/serializers/auth/sms.py:13 msgid "SMS provider" msgstr "SMSプロバイダ" -#: settings/serializers/auth/sms.py:17 settings/serializers/auth/sms.py:35 -#: settings/serializers/auth/sms.py:43 settings/serializers/email.py:63 +#: settings/serializers/auth/sms.py:18 settings/serializers/auth/sms.py:36 +#: settings/serializers/auth/sms.py:44 settings/serializers/email.py:65 msgid "Signature" msgstr "署名" -#: settings/serializers/auth/sms.py:18 settings/serializers/auth/sms.py:36 -#: settings/serializers/auth/sms.py:44 +#: settings/serializers/auth/sms.py:19 settings/serializers/auth/sms.py:37 +#: settings/serializers/auth/sms.py:45 msgid "Template code" msgstr "テンプレートコード" -#: settings/serializers/auth/sms.py:22 +#: settings/serializers/auth/sms.py:23 msgid "Test phone" msgstr "テスト電話" @@ -3728,7 +3729,7 @@ msgstr "Token有効期間" msgid "Unit: second" msgstr "単位: 秒" -#: settings/serializers/auth/wecom.py:11 +#: settings/serializers/auth/wecom.py:13 msgid "Enable WeCom Auth" msgstr "企業微信認証の有効化" @@ -3822,69 +3823,69 @@ msgstr "" "単位:日。セッション、録画、コマンドレコードがそれを超えると削除されます(デー" "タベースストレージにのみ影響します。ossなどは影響しません」影響を受ける)" -#: settings/serializers/email.py:18 +#: settings/serializers/email.py:20 msgid "SMTP host" msgstr "SMTPホスト" -#: settings/serializers/email.py:19 +#: settings/serializers/email.py:21 msgid "SMTP port" msgstr "SMTPポート" -#: settings/serializers/email.py:20 +#: settings/serializers/email.py:22 msgid "SMTP account" msgstr "SMTPアカウント" -#: settings/serializers/email.py:22 +#: settings/serializers/email.py:24 msgid "SMTP password" msgstr "SMTPパスワード" -#: settings/serializers/email.py:23 +#: settings/serializers/email.py:25 msgid "Tips: Some provider use token except password" msgstr "ヒント: 一部のプロバイダーはパスワード以外のトークンを使用します" -#: settings/serializers/email.py:26 +#: settings/serializers/email.py:28 msgid "Send user" msgstr "ユーザーを送信" -#: settings/serializers/email.py:27 +#: settings/serializers/email.py:29 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "" "ヒント: 送信メールアカウント、送信アカウントとしてのデフォルトのSMTPアカウン" "ト" -#: settings/serializers/email.py:30 +#: settings/serializers/email.py:32 msgid "Test recipient" msgstr "テスト受信者" -#: settings/serializers/email.py:31 +#: settings/serializers/email.py:33 msgid "Tips: Used only as a test mail recipient" msgstr "ヒント: テストメールの受信者としてのみ使用" -#: settings/serializers/email.py:34 +#: settings/serializers/email.py:36 msgid "Use SSL" msgstr "SSLの使用" -#: settings/serializers/email.py:35 +#: settings/serializers/email.py:37 msgid "If SMTP port is 465, may be select" msgstr "SMTPポートが465の場合は、" -#: settings/serializers/email.py:38 +#: settings/serializers/email.py:40 msgid "Use TLS" msgstr "TLSの使用" -#: settings/serializers/email.py:39 +#: settings/serializers/email.py:41 msgid "If SMTP port is 587, may be select" msgstr "SMTPポートが587の場合は、" -#: settings/serializers/email.py:42 +#: settings/serializers/email.py:44 msgid "Subject prefix" msgstr "件名プレフィックス" -#: settings/serializers/email.py:49 +#: settings/serializers/email.py:51 msgid "Create user email subject" msgstr "ユーザーメール件名の作成" -#: settings/serializers/email.py:50 +#: settings/serializers/email.py:52 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" @@ -3892,20 +3893,20 @@ msgstr "" "ヒント: ユーザーを作成するときに、メールの件名を送信します (例: アカウントを" "正常に作成)" -#: settings/serializers/email.py:54 +#: settings/serializers/email.py:56 msgid "Create user honorific" msgstr "ユーザー敬語の作成" -#: settings/serializers/email.py:55 +#: settings/serializers/email.py:57 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "" "ヒント: ユーザーを作成するときは、メールの敬語を送信します (例: こんにちは)" -#: settings/serializers/email.py:59 +#: settings/serializers/email.py:61 msgid "Create user email content" msgstr "ユーザーのメールコンテンツを作成する" -#: settings/serializers/email.py:60 +#: settings/serializers/email.py:62 #, python-brace-format msgid "" "Tips: When creating a user, send the content of the email, support " @@ -3914,7 +3915,7 @@ msgstr "" "ヒント:ユーザーの作成時にパスワード設定メールの内容を送信し、{username}{name}" "{email}ラベルをサポートします。" -#: settings/serializers/email.py:64 +#: settings/serializers/email.py:66 msgid "Tips: Email signature (eg:jumpserver)" msgstr "ヒント: メール署名 (例: jumpserver)" @@ -4760,9 +4761,9 @@ msgid "Redis Port" msgstr "Redis ポート" #: terminal/models/endpoint.py:26 terminal/models/endpoint.py:67 -#: terminal/serializers/endpoint.py:41 terminal/serializers/storage.py:37 -#: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 -#: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 +#: terminal/serializers/endpoint.py:41 terminal/serializers/storage.py:38 +#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 +#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "エンドポイント" @@ -4979,67 +4980,73 @@ msgstr "終了できます" msgid "Command amount" msgstr "コマンド量" -#: terminal/serializers/storage.py:19 +#: terminal/serializers/storage.py:20 msgid "Endpoint invalid: remove path `{}`" msgstr "エンドポイントが無効: パス '{}' を削除" -#: terminal/serializers/storage.py:25 +#: terminal/serializers/storage.py:26 msgid "Bucket" msgstr "バケット" -#: terminal/serializers/storage.py:32 users/models/user.py:695 -msgid "Secret key" -msgstr "秘密キー" +#: terminal/serializers/storage.py:30 +#: xpack/plugins/cloud/serializers/account_attrs.py:15 +msgid "Access key id" +msgstr "アクセスキー" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:34 +#: xpack/plugins/cloud/serializers/account_attrs.py:18 +msgid "Access key secret" +msgstr "アクセスキーシークレット" + +#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "リージョン" -#: terminal/serializers/storage.py:108 +#: terminal/serializers/storage.py:109 msgid "Container name" msgstr "コンテナー名" -#: terminal/serializers/storage.py:110 +#: terminal/serializers/storage.py:111 msgid "Account name" msgstr "アカウント名" -#: terminal/serializers/storage.py:111 +#: terminal/serializers/storage.py:112 msgid "Account key" msgstr "アカウントキー" -#: terminal/serializers/storage.py:114 +#: terminal/serializers/storage.py:115 msgid "Endpoint suffix" msgstr "エンドポイントサフィックス" -#: terminal/serializers/storage.py:134 +#: terminal/serializers/storage.py:135 msgid "The address format is incorrect" msgstr "アドレス形式が正しくありません" -#: terminal/serializers/storage.py:141 +#: terminal/serializers/storage.py:142 msgid "Host invalid" msgstr "ホスト無効" -#: terminal/serializers/storage.py:144 +#: terminal/serializers/storage.py:145 msgid "Port invalid" msgstr "ポートが無効" -#: terminal/serializers/storage.py:159 +#: terminal/serializers/storage.py:160 msgid "Index by date" msgstr "日付による索引付け" -#: terminal/serializers/storage.py:160 +#: terminal/serializers/storage.py:161 msgid "Whether to create an index by date" msgstr "現在の日付に基づいてインデックスを動的に作成するかどうか" -#: terminal/serializers/storage.py:163 +#: terminal/serializers/storage.py:164 msgid "Index" msgstr "インデックス" -#: terminal/serializers/storage.py:165 +#: terminal/serializers/storage.py:166 msgid "Doc type" msgstr "Docタイプ" -#: terminal/serializers/storage.py:167 +#: terminal/serializers/storage.py:168 msgid "Ignore Certificate Verification" msgstr "証明書の検証を無視する" @@ -5328,11 +5335,11 @@ msgstr "プロセス" msgid "TicketFlow" msgstr "作業指示プロセス" -#: tickets/models/ticket.py:311 +#: tickets/models/ticket.py:320 msgid "Please try again" msgstr "もう一度お試しください" -#: tickets/models/ticket.py:319 +#: tickets/models/ticket.py:328 msgid "Super ticket" msgstr "スーパーチケット" @@ -5638,8 +5645,8 @@ msgstr "ここにid_rsa.pubを貼り付けます。" msgid "Public key should not be the same as your old one." msgstr "公開鍵は古いものと同じであってはなりません。" -#: users/forms/profile.py:150 users/serializers/profile.py:98 -#: users/serializers/profile.py:181 users/serializers/profile.py:208 +#: users/forms/profile.py:150 users/serializers/profile.py:100 +#: users/serializers/profile.py:183 users/serializers/profile.py:210 msgid "Not a valid ssh public key" msgstr "有効なssh公開鍵ではありません" @@ -5667,6 +5674,10 @@ msgstr "アバター" msgid "Wechat" msgstr "微信" +#: users/models/user.py:695 +msgid "Secret key" +msgstr "秘密キー" + #: users/models/user.py:711 msgid "Source" msgstr "ソース" @@ -5738,7 +5749,7 @@ msgstr "MFAのリセット" msgid "The old password is incorrect" msgstr "古いパスワードが正しくありません" -#: users/serializers/profile.py:37 users/serializers/profile.py:195 +#: users/serializers/profile.py:37 users/serializers/profile.py:197 msgid "Password does not match security rules" msgstr "パスワードがセキュリティルールと一致しない" @@ -5746,11 +5757,11 @@ msgstr "パスワードがセキュリティルールと一致しない" msgid "The new password cannot be the last {} passwords" msgstr "新しいパスワードを最後の {} 個のパスワードにすることはできません" -#: users/serializers/profile.py:49 users/serializers/profile.py:69 +#: users/serializers/profile.py:49 users/serializers/profile.py:71 msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:147 users/serializers/user.py:144 +#: users/serializers/profile.py:149 users/serializers/user.py:144 msgid "Is first login" msgstr "最初のログインです" @@ -6651,48 +6662,40 @@ msgstr "有効表示" msgid "Provider display" msgstr "プロバイダ表示" -#: xpack/plugins/cloud/serializers/account_attrs.py:13 -msgid "AccessKey ID" -msgstr "アクセスキーID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:16 -msgid "AccessKey Secret" -msgstr "アクセスキーシークレット" - -#: xpack/plugins/cloud/serializers/account_attrs.py:31 +#: xpack/plugins/cloud/serializers/account_attrs.py:33 msgid "Client ID" msgstr "クライアントID" -#: xpack/plugins/cloud/serializers/account_attrs.py:37 +#: xpack/plugins/cloud/serializers/account_attrs.py:39 msgid "Tenant ID" msgstr "テナントID" -#: xpack/plugins/cloud/serializers/account_attrs.py:40 +#: xpack/plugins/cloud/serializers/account_attrs.py:42 msgid "Subscription ID" msgstr "サブスクリプションID" -#: xpack/plugins/cloud/serializers/account_attrs.py:91 -#: xpack/plugins/cloud/serializers/account_attrs.py:96 +#: xpack/plugins/cloud/serializers/account_attrs.py:93 +#: xpack/plugins/cloud/serializers/account_attrs.py:98 msgid "API Endpoint" msgstr "APIエンドポイント" -#: xpack/plugins/cloud/serializers/account_attrs.py:102 +#: xpack/plugins/cloud/serializers/account_attrs.py:104 msgid "Auth url" msgstr "認証アドレス" -#: xpack/plugins/cloud/serializers/account_attrs.py:103 +#: xpack/plugins/cloud/serializers/account_attrs.py:105 msgid "eg: http://openstack.example.com:5000/v3" msgstr "例えば: http://openstack.example.com:5000/v3" -#: xpack/plugins/cloud/serializers/account_attrs.py:106 +#: xpack/plugins/cloud/serializers/account_attrs.py:108 msgid "User domain" msgstr "ユーザードメイン" -#: xpack/plugins/cloud/serializers/account_attrs.py:113 +#: xpack/plugins/cloud/serializers/account_attrs.py:115 msgid "Service account key" msgstr "サービスアカウントキー" -#: xpack/plugins/cloud/serializers/account_attrs.py:114 +#: xpack/plugins/cloud/serializers/account_attrs.py:116 msgid "The file is in JSON format" msgstr "ファイルはJSON形式です。" @@ -6811,6 +6814,9 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#~ msgid "AccessKey ID" +#~ msgstr "アクセスキーID" + #~ msgid "Unknown ip" #~ msgstr "不明なip" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo deleted file mode 100644 index 317e02a96..000000000 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d30f8d3abc215c35bb2dd889374eeef896a193f8010ccd5ae8e27aa408f045c7 -size 105337 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 8dee918fb..2ee578f43 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-17 18:02+0800\n" +"POT-Creation-Date: 2022-05-19 13:16+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -128,7 +128,7 @@ msgstr "系统用户" #: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:264 audits/models.py:39 +#: assets/serializers/system_user.py:269 audits/models.py:39 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 @@ -161,7 +161,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: users/templates/users/_msg_user_created.html:12 #: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:195 -#: xpack/plugins/cloud/serializers/account_attrs.py:22 +#: xpack/plugins/cloud/serializers/account_attrs.py:24 msgid "Username" msgstr "用户名" @@ -175,9 +175,9 @@ msgstr "" "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 -#: applications/serializers/attrs/application_type/mysql_workbench.py:17 +#: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:210 assets/models/domain.py:60 -#: assets/serializers/account.py:13 +#: assets/serializers/account.py:14 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -186,7 +186,7 @@ msgid "IP" msgstr "IP" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 -#: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 +#: assets/serializers/account.py:15 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 msgid "Hostname" msgstr "主机名" @@ -199,7 +199,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议 #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 #: assets/models/domain.py:62 assets/models/user.py:248 -#: terminal/serializers/session.py:30 terminal/serializers/storage.py:67 +#: terminal/serializers/session.py:30 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "协议" @@ -240,7 +240,7 @@ msgstr "应用管理" #: applications/const.py:8 #: applications/serializers/attrs/application_category/db.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:25 +#: applications/serializers/attrs/application_type/mysql_workbench.py:26 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "数据库" @@ -336,7 +336,7 @@ msgstr "类别名称" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:27 +#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:29 #: audits/serializers.py:29 perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 @@ -348,7 +348,7 @@ msgstr "类型名称" #: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 +#: assets/serializers/account.py:19 assets/serializers/cmd_filter.py:28 #: assets/serializers/cmd_filter.py:48 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 @@ -358,7 +358,7 @@ msgid "Date created" msgstr "创建日期" #: applications/serializers/application.py:104 assets/models/base.py:182 -#: assets/models/gathered_user.py:20 assets/serializers/account.py:21 +#: assets/models/gathered_user.py:20 assets/serializers/account.py:22 #: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 #: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 @@ -382,88 +382,88 @@ msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 #: terminal/models/endpoint.py:11 -#: xpack/plugins/cloud/serializers/account_attrs.py:68 +#: xpack/plugins/cloud/serializers/account_attrs.py:70 msgid "Host" msgstr "主机" #: applications/serializers/attrs/application_category/db.py:12 #: applications/serializers/attrs/application_type/mongodb.py:10 #: applications/serializers/attrs/application_type/mysql.py:10 -#: applications/serializers/attrs/application_type/mysql_workbench.py:21 +#: applications/serializers/attrs/application_type/mysql_workbench.py:22 #: applications/serializers/attrs/application_type/oracle.py:10 #: applications/serializers/attrs/application_type/pgsql.py:10 #: applications/serializers/attrs/application_type/redis.py:10 #: applications/serializers/attrs/application_type/sqlserver.py:10 #: assets/models/asset.py:214 assets/models/domain.py:61 #: settings/serializers/auth/radius.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:69 +#: xpack/plugins/cloud/serializers/account_attrs.py:71 msgid "Port" msgstr "端口" #: applications/serializers/attrs/application_category/remote_app.py:39 -#: applications/serializers/attrs/application_type/chrome.py:13 -#: applications/serializers/attrs/application_type/mysql_workbench.py:13 -#: applications/serializers/attrs/application_type/vmware_client.py:17 +#: applications/serializers/attrs/application_type/chrome.py:14 +#: applications/serializers/attrs/application_type/mysql_workbench.py:14 +#: applications/serializers/attrs/application_type/vmware_client.py:18 msgid "Application path" msgstr "应用路径" #: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:163 +#: assets/serializers/system_user.py:168 #: xpack/plugins/change_auth_plan/serializers/asset.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:69 #: xpack/plugins/change_auth_plan/serializers/asset.py:72 #: xpack/plugins/change_auth_plan/serializers/asset.py:103 -#: xpack/plugins/cloud/serializers/account_attrs.py:52 +#: xpack/plugins/cloud/serializers/account_attrs.py:54 msgid "This field is required." msgstr "该字段是必填项。" -#: applications/serializers/attrs/application_type/chrome.py:16 -#: applications/serializers/attrs/application_type/vmware_client.py:21 +#: applications/serializers/attrs/application_type/chrome.py:18 +#: applications/serializers/attrs/application_type/vmware_client.py:22 msgid "Target URL" msgstr "目标URL" -#: applications/serializers/attrs/application_type/chrome.py:19 +#: applications/serializers/attrs/application_type/chrome.py:22 msgid "Chrome username" msgstr "Chrome 用户名" -#: applications/serializers/attrs/application_type/chrome.py:22 -#: applications/serializers/attrs/application_type/chrome.py:29 +#: applications/serializers/attrs/application_type/chrome.py:26 +#: applications/serializers/attrs/application_type/chrome.py:33 msgid "Chrome password" msgstr "Chrome 密码" -#: applications/serializers/attrs/application_type/custom.py:11 +#: applications/serializers/attrs/application_type/custom.py:12 msgid "Operating parameter" msgstr "运行参数" -#: applications/serializers/attrs/application_type/custom.py:15 +#: applications/serializers/attrs/application_type/custom.py:16 msgid "Target url" msgstr "目标URL" -#: applications/serializers/attrs/application_type/custom.py:19 +#: applications/serializers/attrs/application_type/custom.py:20 msgid "Custom Username" msgstr "自定义用户名" -#: applications/serializers/attrs/application_type/custom.py:23 -#: applications/serializers/attrs/application_type/custom.py:30 +#: applications/serializers/attrs/application_type/custom.py:25 +#: applications/serializers/attrs/application_type/custom.py:32 #: xpack/plugins/change_auth_plan/models/base.py:27 msgid "Custom password" msgstr "自定义密码" -#: applications/serializers/attrs/application_type/mysql_workbench.py:29 +#: applications/serializers/attrs/application_type/mysql_workbench.py:30 msgid "Mysql workbench username" msgstr "Mysql 工作台 用户名" -#: applications/serializers/attrs/application_type/mysql_workbench.py:33 -#: applications/serializers/attrs/application_type/mysql_workbench.py:40 +#: applications/serializers/attrs/application_type/mysql_workbench.py:35 +#: applications/serializers/attrs/application_type/mysql_workbench.py:42 msgid "Mysql workbench password" msgstr "Mysql 工作台 密码" -#: applications/serializers/attrs/application_type/vmware_client.py:25 +#: applications/serializers/attrs/application_type/vmware_client.py:26 msgid "Vmware username" msgstr "Vmware 用户名" -#: applications/serializers/attrs/application_type/vmware_client.py:29 -#: applications/serializers/attrs/application_type/vmware_client.py:36 +#: applications/serializers/attrs/application_type/vmware_client.py:31 +#: applications/serializers/attrs/application_type/vmware_client.py:38 msgid "Vmware password" msgstr "Vmware 密码" @@ -505,7 +505,7 @@ msgid "Internal" msgstr "内部的" #: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:15 assets/serializers/asset.py:63 +#: assets/serializers/account.py:16 assets/serializers/asset.py:63 #: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "系统平台" @@ -566,7 +566,7 @@ msgstr "系统架构" msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:215 assets/serializers/account.py:16 +#: assets/models/asset.py:215 assets/serializers/account.py:17 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" @@ -745,7 +745,7 @@ msgstr "成功" msgid "Failed" msgstr "失败" -#: assets/models/base.py:38 assets/serializers/domain.py:46 +#: assets/models/base.py:38 assets/serializers/domain.py:47 msgid "Connectivity" msgstr "可连接性" @@ -764,7 +764,7 @@ msgstr "校验日期" #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/models/base.py:196 -#: xpack/plugins/cloud/serializers/account_attrs.py:24 +#: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Password" msgstr "密码" @@ -828,7 +828,7 @@ msgstr "默认Cluster" msgid "User group" msgstr "用户组" -#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:54 +#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:59 msgid "Command filter" msgstr "命令过滤器" @@ -953,7 +953,7 @@ msgstr "全称" msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 assets/serializers/system_user.py:263 +#: assets/models/node.py:559 assets/serializers/system_user.py:268 #: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -978,7 +978,7 @@ msgstr "普通用户" msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:240 assets/serializers/domain.py:29 +#: assets/models/user.py:240 assets/serializers/domain.py:30 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:39 msgid "Assets" @@ -1012,7 +1012,8 @@ msgstr "认证方式" msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:254 authentication/models.py:49 +#: assets/models/user.py:254 assets/serializers/system_user.py:32 +#: authentication/models.py:49 msgid "Token" msgstr "Token" @@ -1060,7 +1061,7 @@ msgstr "" "{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" "置加密密码" -#: assets/serializers/account.py:40 assets/serializers/account.py:83 +#: assets/serializers/account.py:41 assets/serializers/account.py:84 msgid "System user display" msgstr "系统用户名称" @@ -1135,17 +1136,17 @@ msgstr "动作名称" msgid "Pattern" msgstr "模式" -#: assets/serializers/domain.py:13 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:59 +#: assets/serializers/domain.py:14 assets/serializers/label.py:12 +#: assets/serializers/system_user.py:64 #: perms/serializers/asset/permission.py:49 msgid "Assets amount" msgstr "资产数量" -#: assets/serializers/domain.py:14 +#: assets/serializers/domain.py:15 msgid "Applications amount" msgstr "应用数量" -#: assets/serializers/domain.py:15 +#: assets/serializers/domain.py:16 msgid "Gateways count" msgstr "网关数量" @@ -1161,78 +1162,78 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:28 +#: assets/serializers/system_user.py:30 msgid "SSH key fingerprint" msgstr "密钥指纹" -#: assets/serializers/system_user.py:30 +#: assets/serializers/system_user.py:35 #: perms/serializers/application/permission.py:46 msgid "Apps amount" msgstr "应用数量" -#: assets/serializers/system_user.py:58 +#: assets/serializers/system_user.py:63 #: perms/serializers/asset/permission.py:50 msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:60 assets/serializers/system_user.py:265 +#: assets/serializers/system_user.py:65 assets/serializers/system_user.py:270 msgid "Login mode display" msgstr "认证方式名称" -#: assets/serializers/system_user.py:62 +#: assets/serializers/system_user.py:67 msgid "Ad domain" msgstr "Ad 网域" -#: assets/serializers/system_user.py:63 +#: assets/serializers/system_user.py:68 msgid "Is asset protocol" msgstr "资产协议" -#: assets/serializers/system_user.py:64 +#: assets/serializers/system_user.py:69 msgid "Only ssh and automatic login system users are supported" msgstr "仅支持ssh协议和自动登录的系统用户" -#: assets/serializers/system_user.py:104 +#: assets/serializers/system_user.py:109 msgid "Username same with user with protocol {} only allow 1" msgstr "用户名和用户相同的一种协议只允许存在一个" -#: assets/serializers/system_user.py:117 common/validators.py:14 +#: assets/serializers/system_user.py:122 common/validators.py:14 msgid "Special char not allowed" msgstr "不能包含特殊字符" -#: assets/serializers/system_user.py:127 +#: assets/serializers/system_user.py:132 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:142 +#: assets/serializers/system_user.py:147 msgid "Path should starts with /" msgstr "路径应该以 / 开头" -#: assets/serializers/system_user.py:154 +#: assets/serializers/system_user.py:159 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" -#: assets/serializers/system_user.py:168 +#: assets/serializers/system_user.py:173 msgid "Only ssh protocol system users are allowed" msgstr "仅允许ssh协议的系统用户" -#: assets/serializers/system_user.py:172 +#: assets/serializers/system_user.py:177 msgid "The protocol must be consistent with the current user: {}" msgstr "协议必须和当前用户保持一致: {}" -#: assets/serializers/system_user.py:176 +#: assets/serializers/system_user.py:181 msgid "Only system users with automatic login are allowed" msgstr "仅允许自动登录的系统用户" -#: assets/serializers/system_user.py:281 +#: assets/serializers/system_user.py:289 msgid "System user name" msgstr "系统用户名称" -#: assets/serializers/system_user.py:282 orgs/mixins/serializers.py:26 +#: assets/serializers/system_user.py:290 orgs/mixins/serializers.py:26 #: rbac/serializers/rolebinding.py:23 msgid "Org name" msgstr "组织名称" -#: assets/serializers/system_user.py:291 +#: assets/serializers/system_user.py:299 msgid "Asset hostname" msgstr "资产主机名" @@ -1483,7 +1484,7 @@ msgstr "用户代理" #: audits/models.py:126 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:684 -#: users/serializers/profile.py:124 +#: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" @@ -1530,7 +1531,7 @@ msgstr "主机名称" msgid "Result" msgstr "结果" -#: audits/serializers.py:98 terminal/serializers/storage.py:156 +#: audits/serializers.py:98 terminal/serializers/storage.py:157 msgid "Hosts" msgstr "主机" @@ -2031,9 +2032,9 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:34 terminal/serializers/storage.py:28 +#: authentication/models.py:34 msgid "Access key" -msgstr "API key" +msgstr "Access key" #: authentication/models.py:41 msgid "Private Token" @@ -2120,13 +2121,13 @@ msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 #: settings/serializers/security.py:39 users/models/user.py:556 -#: users/serializers/profile.py:114 users/templates/users/mfa_setting.html:61 +#: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 #: users/templates/users/user_verify_mfa.html:36 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:557 users/serializers/profile.py:115 +#: users/models/user.py:557 users/serializers/profile.py:117 #: users/templates/users/mfa_setting.html:26 #: users/templates/users/mfa_setting.html:68 msgid "Enable" @@ -2347,15 +2348,15 @@ msgstr "该钉钉已经绑定其他用户" msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293 +#: authentication/views/dingtalk.py:240 authentication/views/dingtalk.py:294 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299 +#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" -#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 +#: authentication/views/dingtalk.py:247 authentication/views/dingtalk.py:301 msgid "Please login with a password and then bind the DingTalk" msgstr "请使用密码登录,然后绑定钉钉" @@ -2384,15 +2385,15 @@ msgstr "飞书" msgid "Binding FeiShu successfully" msgstr "绑定 飞书 成功" -#: authentication/views/feishu.py:200 +#: authentication/views/feishu.py:201 msgid "Failed to get user from FeiShu" msgstr "从飞书获取用户失败" -#: authentication/views/feishu.py:206 +#: authentication/views/feishu.py:207 msgid "FeiShu is not bound" msgstr "没有绑定飞书" -#: authentication/views/feishu.py:207 +#: authentication/views/feishu.py:208 msgid "Please login with a password and then bind the FeiShu" msgstr "请使用密码登录,然后绑定飞书" @@ -3210,7 +3211,7 @@ msgstr "组织角色绑定" msgid "System role binding" msgstr "系统角色绑定" -#: rbac/serializers/permission.py:26 users/serializers/profile.py:130 +#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 msgid "Perms" msgstr "权限" @@ -3436,7 +3437,7 @@ msgstr "启用登录跳转提示" msgid "Enable CAS Auth" msgstr "启用 CAS 认证" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:40 +#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:42 msgid "Server url" msgstr "服务端地址" @@ -3464,11 +3465,11 @@ msgstr "映射属性" msgid "Create user if not" msgstr "创建用户(如果不存在)" -#: settings/serializers/auth/dingtalk.py:11 +#: settings/serializers/auth/dingtalk.py:13 msgid "Enable DingTalk Auth" msgstr "启用钉钉认证" -#: settings/serializers/auth/feishu.py:10 +#: settings/serializers/auth/feishu.py:12 msgid "Enable FeiShu Auth" msgstr "启用飞书认证" @@ -3525,96 +3526,96 @@ msgstr "搜索分页数量" msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" -#: settings/serializers/auth/oidc.py:12 +#: settings/serializers/auth/oidc.py:14 msgid "Base site url" msgstr "JumpServer 地址" -#: settings/serializers/auth/oidc.py:15 +#: settings/serializers/auth/oidc.py:17 msgid "Client Id" msgstr "客户端 ID" -#: settings/serializers/auth/oidc.py:18 -#: xpack/plugins/cloud/serializers/account_attrs.py:34 +#: settings/serializers/auth/oidc.py:20 +#: xpack/plugins/cloud/serializers/account_attrs.py:36 msgid "Client Secret" msgstr "客户端密钥" -#: settings/serializers/auth/oidc.py:26 +#: settings/serializers/auth/oidc.py:28 msgid "Client authentication method" msgstr "客户端认证方式" -#: settings/serializers/auth/oidc.py:28 +#: settings/serializers/auth/oidc.py:30 msgid "Share session" msgstr "共享会话" -#: settings/serializers/auth/oidc.py:30 +#: settings/serializers/auth/oidc.py:32 msgid "Ignore ssl verification" msgstr "忽略 SSL 证书验证" -#: settings/serializers/auth/oidc.py:37 +#: settings/serializers/auth/oidc.py:39 msgid "Use Keycloak" msgstr "使用 Keycloak" -#: settings/serializers/auth/oidc.py:43 +#: settings/serializers/auth/oidc.py:45 msgid "Realm name" msgstr "域" -#: settings/serializers/auth/oidc.py:49 +#: settings/serializers/auth/oidc.py:51 msgid "Enable OPENID Auth" msgstr "启用 OIDC 认证" -#: settings/serializers/auth/oidc.py:51 +#: settings/serializers/auth/oidc.py:53 msgid "Provider endpoint" msgstr "端点地址" -#: settings/serializers/auth/oidc.py:54 +#: settings/serializers/auth/oidc.py:56 msgid "Provider auth endpoint" msgstr "授权端点地址" -#: settings/serializers/auth/oidc.py:57 +#: settings/serializers/auth/oidc.py:59 msgid "Provider token endpoint" msgstr "token 端点地址" -#: settings/serializers/auth/oidc.py:60 +#: settings/serializers/auth/oidc.py:62 msgid "Provider jwks endpoint" msgstr "jwks 端点地址" -#: settings/serializers/auth/oidc.py:63 +#: settings/serializers/auth/oidc.py:65 msgid "Provider userinfo endpoint" msgstr "用户信息端点地址" -#: settings/serializers/auth/oidc.py:66 +#: settings/serializers/auth/oidc.py:68 msgid "Provider end session endpoint" msgstr "注销会话端点地址" -#: settings/serializers/auth/oidc.py:69 +#: settings/serializers/auth/oidc.py:71 msgid "Provider sign alg" msgstr "签名算法" -#: settings/serializers/auth/oidc.py:72 +#: settings/serializers/auth/oidc.py:74 msgid "Provider sign key" msgstr "签名 Key" -#: settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oidc.py:76 msgid "Scopes" msgstr "连接范围" -#: settings/serializers/auth/oidc.py:76 +#: settings/serializers/auth/oidc.py:78 msgid "Id token max age" msgstr "令牌有效时间" -#: settings/serializers/auth/oidc.py:79 +#: settings/serializers/auth/oidc.py:81 msgid "Id token include claims" msgstr "声明" -#: settings/serializers/auth/oidc.py:81 +#: settings/serializers/auth/oidc.py:83 msgid "Use state" msgstr "使用状态" -#: settings/serializers/auth/oidc.py:82 +#: settings/serializers/auth/oidc.py:84 msgid "Use nonce" msgstr "临时使用" -#: settings/serializers/auth/oidc.py:84 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oidc.py:86 settings/serializers/auth/saml2.py:33 msgid "Always update user" msgstr "总是更新用户信息" @@ -3650,25 +3651,25 @@ msgstr "SP 密钥" msgid "SP cert" msgstr "SP 证书" -#: settings/serializers/auth/sms.py:10 +#: settings/serializers/auth/sms.py:11 msgid "Enable SMS" msgstr "启用 SMS" -#: settings/serializers/auth/sms.py:12 +#: settings/serializers/auth/sms.py:13 msgid "SMS provider" msgstr "短信服务商" -#: settings/serializers/auth/sms.py:17 settings/serializers/auth/sms.py:35 -#: settings/serializers/auth/sms.py:43 settings/serializers/email.py:63 +#: settings/serializers/auth/sms.py:18 settings/serializers/auth/sms.py:36 +#: settings/serializers/auth/sms.py:44 settings/serializers/email.py:65 msgid "Signature" msgstr "签名" -#: settings/serializers/auth/sms.py:18 settings/serializers/auth/sms.py:36 -#: settings/serializers/auth/sms.py:44 +#: settings/serializers/auth/sms.py:19 settings/serializers/auth/sms.py:37 +#: settings/serializers/auth/sms.py:45 msgid "Template code" msgstr "模板" -#: settings/serializers/auth/sms.py:22 +#: settings/serializers/auth/sms.py:23 msgid "Test phone" msgstr "测试手机号" @@ -3688,7 +3689,7 @@ msgstr "Token 有效期" msgid "Unit: second" msgstr "单位: 秒" -#: settings/serializers/auth/wecom.py:11 +#: settings/serializers/auth/wecom.py:13 msgid "Enable WeCom Auth" msgstr "启用企业微信认证" @@ -3782,85 +3783,85 @@ msgstr "" "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" "受影响)" -#: settings/serializers/email.py:18 +#: settings/serializers/email.py:20 msgid "SMTP host" msgstr "SMTP 主机" -#: settings/serializers/email.py:19 +#: settings/serializers/email.py:21 msgid "SMTP port" msgstr "SMTP 端口" -#: settings/serializers/email.py:20 +#: settings/serializers/email.py:22 msgid "SMTP account" msgstr "SMTP 账号" -#: settings/serializers/email.py:22 +#: settings/serializers/email.py:24 msgid "SMTP password" msgstr "SMTP 密码" -#: settings/serializers/email.py:23 +#: settings/serializers/email.py:25 msgid "Tips: Some provider use token except password" msgstr "提示:一些邮件提供商需要输入的是授权码" -#: settings/serializers/email.py:26 +#: settings/serializers/email.py:28 msgid "Send user" msgstr "发件人" -#: settings/serializers/email.py:27 +#: settings/serializers/email.py:29 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" -#: settings/serializers/email.py:30 +#: settings/serializers/email.py:32 msgid "Test recipient" msgstr "测试收件人" -#: settings/serializers/email.py:31 +#: settings/serializers/email.py:33 msgid "Tips: Used only as a test mail recipient" msgstr "提示:仅用来作为测试邮件收件人" -#: settings/serializers/email.py:34 +#: settings/serializers/email.py:36 msgid "Use SSL" msgstr "使用 SSL" -#: settings/serializers/email.py:35 +#: settings/serializers/email.py:37 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用 SSL" -#: settings/serializers/email.py:38 +#: settings/serializers/email.py:40 msgid "Use TLS" msgstr "使用 TLS" -#: settings/serializers/email.py:39 +#: settings/serializers/email.py:41 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用 TLS" -#: settings/serializers/email.py:42 +#: settings/serializers/email.py:44 msgid "Subject prefix" msgstr "主题前缀" -#: settings/serializers/email.py:49 +#: settings/serializers/email.py:51 msgid "Create user email subject" msgstr "邮件主题" -#: settings/serializers/email.py:50 +#: settings/serializers/email.py:52 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" -#: settings/serializers/email.py:54 +#: settings/serializers/email.py:56 msgid "Create user honorific" msgstr "邮件问候语" -#: settings/serializers/email.py:55 +#: settings/serializers/email.py:57 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 你好)" -#: settings/serializers/email.py:59 +#: settings/serializers/email.py:61 msgid "Create user email content" msgstr "邮件的内容" -#: settings/serializers/email.py:60 +#: settings/serializers/email.py:62 #, python-brace-format msgid "" "Tips: When creating a user, send the content of the email, support " @@ -3868,7 +3869,7 @@ msgid "" msgstr "" "提示: 创建用户时,发送设置密码邮件的内容, 支持 {username} {name} {email} 标签" -#: settings/serializers/email.py:64 +#: settings/serializers/email.py:66 msgid "Tips: Email signature (eg:jumpserver)" msgstr "邮件署名 (如:jumpserver)" @@ -4688,9 +4689,9 @@ msgid "Redis Port" msgstr "Redis 端口" #: terminal/models/endpoint.py:26 terminal/models/endpoint.py:67 -#: terminal/serializers/endpoint.py:41 terminal/serializers/storage.py:37 -#: terminal/serializers/storage.py:49 terminal/serializers/storage.py:79 -#: terminal/serializers/storage.py:89 terminal/serializers/storage.py:97 +#: terminal/serializers/endpoint.py:41 terminal/serializers/storage.py:38 +#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 +#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "端点" @@ -4907,67 +4908,73 @@ msgstr "是否可中断" msgid "Command amount" msgstr "命令数量" -#: terminal/serializers/storage.py:19 +#: terminal/serializers/storage.py:20 msgid "Endpoint invalid: remove path `{}`" msgstr "端点无效: 移除路径 `{}`" -#: terminal/serializers/storage.py:25 +#: terminal/serializers/storage.py:26 msgid "Bucket" msgstr "桶名称" -#: terminal/serializers/storage.py:32 users/models/user.py:695 -msgid "Secret key" -msgstr "密钥" +#: terminal/serializers/storage.py:30 +#: xpack/plugins/cloud/serializers/account_attrs.py:15 +msgid "Access key id" +msgstr "访问密钥 ID(AK)" -#: terminal/serializers/storage.py:64 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:34 +#: xpack/plugins/cloud/serializers/account_attrs.py:18 +msgid "Access key secret" +msgstr "访问密钥密文(SK)" + +#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:220 msgid "Region" msgstr "地域" -#: terminal/serializers/storage.py:108 +#: terminal/serializers/storage.py:109 msgid "Container name" msgstr "容器名称" -#: terminal/serializers/storage.py:110 +#: terminal/serializers/storage.py:111 msgid "Account name" msgstr "账号名称" -#: terminal/serializers/storage.py:111 +#: terminal/serializers/storage.py:112 msgid "Account key" msgstr "账号密钥" -#: terminal/serializers/storage.py:114 +#: terminal/serializers/storage.py:115 msgid "Endpoint suffix" msgstr "端点后缀" -#: terminal/serializers/storage.py:134 +#: terminal/serializers/storage.py:135 msgid "The address format is incorrect" msgstr "地址格式不正确" -#: terminal/serializers/storage.py:141 +#: terminal/serializers/storage.py:142 msgid "Host invalid" msgstr "主机无效" -#: terminal/serializers/storage.py:144 +#: terminal/serializers/storage.py:145 msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:159 +#: terminal/serializers/storage.py:160 msgid "Index by date" msgstr "按日期建索引" -#: terminal/serializers/storage.py:160 +#: terminal/serializers/storage.py:161 msgid "Whether to create an index by date" msgstr "是否根据日期动态建立索引" -#: terminal/serializers/storage.py:163 +#: terminal/serializers/storage.py:164 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:165 +#: terminal/serializers/storage.py:166 msgid "Doc type" msgstr "文档类型" -#: terminal/serializers/storage.py:167 +#: terminal/serializers/storage.py:168 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" @@ -5254,11 +5261,11 @@ msgstr "流程" msgid "TicketFlow" msgstr "工单流程" -#: tickets/models/ticket.py:311 +#: tickets/models/ticket.py:320 msgid "Please try again" msgstr "请再次尝试" -#: tickets/models/ticket.py:319 +#: tickets/models/ticket.py:328 msgid "Super ticket" msgstr "超级工单" @@ -5560,8 +5567,8 @@ msgstr "复制你的公钥到这里" msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms/profile.py:150 users/serializers/profile.py:98 -#: users/serializers/profile.py:181 users/serializers/profile.py:208 +#: users/forms/profile.py:150 users/serializers/profile.py:100 +#: users/serializers/profile.py:183 users/serializers/profile.py:210 msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" @@ -5589,6 +5596,10 @@ msgstr "头像" msgid "Wechat" msgstr "微信" +#: users/models/user.py:695 +msgid "Secret key" +msgstr "Secret key" + #: users/models/user.py:711 msgid "Source" msgstr "来源" @@ -5660,7 +5671,7 @@ msgstr "重置 MFA" msgid "The old password is incorrect" msgstr "旧密码错误" -#: users/serializers/profile.py:37 users/serializers/profile.py:195 +#: users/serializers/profile.py:37 users/serializers/profile.py:197 msgid "Password does not match security rules" msgstr "密码不满足安全规则" @@ -5668,11 +5679,11 @@ msgstr "密码不满足安全规则" msgid "The new password cannot be the last {} passwords" msgstr "新密码不能是最近 {} 次的密码" -#: users/serializers/profile.py:49 users/serializers/profile.py:69 +#: users/serializers/profile.py:49 users/serializers/profile.py:71 msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:147 users/serializers/user.py:144 +#: users/serializers/profile.py:149 users/serializers/user.py:144 msgid "Is first login" msgstr "首次登录" @@ -6560,48 +6571,40 @@ msgstr "有效性显示" msgid "Provider display" msgstr "服务商显示" -#: xpack/plugins/cloud/serializers/account_attrs.py:13 -msgid "AccessKey ID" -msgstr "Access key ID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:16 -msgid "AccessKey Secret" -msgstr "Access key secret" - -#: xpack/plugins/cloud/serializers/account_attrs.py:31 +#: xpack/plugins/cloud/serializers/account_attrs.py:33 msgid "Client ID" msgstr "客户端 ID" -#: xpack/plugins/cloud/serializers/account_attrs.py:37 +#: xpack/plugins/cloud/serializers/account_attrs.py:39 msgid "Tenant ID" msgstr "租户 ID" -#: xpack/plugins/cloud/serializers/account_attrs.py:40 +#: xpack/plugins/cloud/serializers/account_attrs.py:42 msgid "Subscription ID" msgstr "订阅 ID" -#: xpack/plugins/cloud/serializers/account_attrs.py:91 -#: xpack/plugins/cloud/serializers/account_attrs.py:96 +#: xpack/plugins/cloud/serializers/account_attrs.py:93 +#: xpack/plugins/cloud/serializers/account_attrs.py:98 msgid "API Endpoint" msgstr "API 端点" -#: xpack/plugins/cloud/serializers/account_attrs.py:102 +#: xpack/plugins/cloud/serializers/account_attrs.py:104 msgid "Auth url" msgstr "认证地址" -#: xpack/plugins/cloud/serializers/account_attrs.py:103 +#: xpack/plugins/cloud/serializers/account_attrs.py:105 msgid "eg: http://openstack.example.com:5000/v3" msgstr "如: http://openstack.example.com:5000/v3" -#: xpack/plugins/cloud/serializers/account_attrs.py:106 +#: xpack/plugins/cloud/serializers/account_attrs.py:108 msgid "User domain" msgstr "用户域" -#: xpack/plugins/cloud/serializers/account_attrs.py:113 +#: xpack/plugins/cloud/serializers/account_attrs.py:115 msgid "Service account key" msgstr "服务账号密钥" -#: xpack/plugins/cloud/serializers/account_attrs.py:114 +#: xpack/plugins/cloud/serializers/account_attrs.py:116 msgid "The file is in JSON format" msgstr "JSON 格式的文件" @@ -6719,6 +6722,9 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "AccessKey ID" +#~ msgstr "Access key ID" + #~ msgid "Unknown ip" #~ msgstr "未知ip" diff --git a/apps/settings/serializers/auth/dingtalk.py b/apps/settings/serializers/auth/dingtalk.py index 062f19f26..37875bba3 100644 --- a/apps/settings/serializers/auth/dingtalk.py +++ b/apps/settings/serializers/auth/dingtalk.py @@ -1,11 +1,13 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField + __all__ = ['DingTalkSettingSerializer'] class DingTalkSettingSerializer(serializers.Serializer): DINGTALK_AGENTID = serializers.CharField(max_length=256, required=True, label='AgentId') DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='AppKey') - DINGTALK_APPSECRET = serializers.CharField(max_length=256, required=False, label='AppSecret', write_only=True) + DINGTALK_APPSECRET = EncryptedField(max_length=256, required=False, label='AppSecret') AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('Enable DingTalk Auth')) diff --git a/apps/settings/serializers/auth/feishu.py b/apps/settings/serializers/auth/feishu.py index 68b7ee2b1..67478bae5 100644 --- a/apps/settings/serializers/auth/feishu.py +++ b/apps/settings/serializers/auth/feishu.py @@ -1,11 +1,13 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField + __all__ = ['FeiShuSettingSerializer'] class FeiShuSettingSerializer(serializers.Serializer): FEISHU_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID') - FEISHU_APP_SECRET = serializers.CharField(max_length=256, required=False, label='App Secret', write_only=True) + FEISHU_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret') AUTH_FEISHU = serializers.BooleanField(default=False, label=_('Enable FeiShu Auth')) diff --git a/apps/settings/serializers/auth/ldap.py b/apps/settings/serializers/auth/ldap.py index 2f2d710c9..ddf963443 100644 --- a/apps/settings/serializers/auth/ldap.py +++ b/apps/settings/serializers/auth/ldap.py @@ -12,7 +12,7 @@ __all__ = [ class LDAPTestConfigSerializer(serializers.Serializer): AUTH_LDAP_SERVER_URI = serializers.CharField(max_length=1024) AUTH_LDAP_BIND_DN = serializers.CharField(max_length=1024, required=False, allow_blank=True) - AUTH_LDAP_BIND_PASSWORD = serializers.CharField(required=False, allow_blank=True) + AUTH_LDAP_BIND_PASSWORD = EncryptedField(required=False, allow_blank=True) AUTH_LDAP_SEARCH_OU = serializers.CharField() AUTH_LDAP_SEARCH_FILTER = serializers.CharField() AUTH_LDAP_USER_ATTR_MAP = serializers.CharField() @@ -42,8 +42,8 @@ class LDAPSettingSerializer(serializers.Serializer): help_text=_('eg: ldap://localhost:389') ) AUTH_LDAP_BIND_DN = serializers.CharField(required=False, max_length=1024, label=_('Bind DN')) - AUTH_LDAP_BIND_PASSWORD = serializers.CharField( - max_length=1024, write_only=True, required=False, label=_('Password') + AUTH_LDAP_BIND_PASSWORD = EncryptedField( + max_length=1024, required=False, label=_('Password') ) AUTH_LDAP_SEARCH_OU = serializers.CharField( max_length=1024, allow_blank=True, required=False, label=_('User OU'), diff --git a/apps/settings/serializers/auth/oidc.py b/apps/settings/serializers/auth/oidc.py index a3777fb25..5ab73ea04 100644 --- a/apps/settings/serializers/auth/oidc.py +++ b/apps/settings/serializers/auth/oidc.py @@ -1,6 +1,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField + __all__ = [ 'OIDCSettingSerializer', 'KeycloakSettingSerializer', ] @@ -14,8 +16,8 @@ class CommonSettingSerializer(serializers.Serializer): AUTH_OPENID_CLIENT_ID = serializers.CharField( required=False, max_length=1024, label=_('Client Id') ) - AUTH_OPENID_CLIENT_SECRET = serializers.CharField( - required=False, max_length=1024, write_only=True, label=_('Client Secret') + AUTH_OPENID_CLIENT_SECRET = EncryptedField( + required=False, max_length=1024, label=_('Client Secret') ) AUTH_OPENID_CLIENT_AUTH_METHOD = serializers.ChoiceField( default='client_secret_basic', diff --git a/apps/settings/serializers/auth/radius.py b/apps/settings/serializers/auth/radius.py index fa5633fc8..4d56510bf 100644 --- a/apps/settings/serializers/auth/radius.py +++ b/apps/settings/serializers/auth/radius.py @@ -4,16 +4,16 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -__all__ = [ - 'RadiusSettingSerializer', -] +from common.drf.fields import EncryptedField + +__all__ = ['RadiusSettingSerializer'] class RadiusSettingSerializer(serializers.Serializer): AUTH_RADIUS = serializers.BooleanField(required=False, label=_('Enable Radius Auth')) RADIUS_SERVER = serializers.CharField(required=False, allow_blank=True, max_length=1024, label=_('Host')) RADIUS_PORT = serializers.IntegerField(required=False, label=_('Port')) - RADIUS_SECRET = serializers.CharField( - required=False, max_length=1024, allow_null=True, label=_('Secret'), write_only=True + RADIUS_SECRET = EncryptedField( + required=False, max_length=1024, allow_null=True, label=_('Secret'), ) OTP_IN_RADIUS = serializers.BooleanField(required=False, label=_('OTP in Radius')) diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index 246c11da6..cd3bef74c 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField from common.sdk.sms import BACKENDS __all__ = ['SMSSettingSerializer', 'AlibabaSMSSettingSerializer', 'TencentSMSSettingSerializer'] @@ -29,8 +30,8 @@ class BaseSMSSettingSerializer(serializers.Serializer): class AlibabaSMSSettingSerializer(BaseSMSSettingSerializer): ALIBABA_ACCESS_KEY_ID = serializers.CharField(max_length=256, required=True, label='AccessKeyId') - ALIBABA_ACCESS_KEY_SECRET = serializers.CharField( - max_length=256, required=False, label='AccessKeySecret', write_only=True + ALIBABA_ACCESS_KEY_SECRET = EncryptedField( + max_length=256, required=False, label='AccessKeySecret', ) ALIBABA_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature')) ALIBABA_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code')) @@ -38,7 +39,7 @@ class AlibabaSMSSettingSerializer(BaseSMSSettingSerializer): class TencentSMSSettingSerializer(BaseSMSSettingSerializer): TENCENT_SECRET_ID = serializers.CharField(max_length=256, required=True, label='Secret id') - TENCENT_SECRET_KEY = serializers.CharField(max_length=256, required=False, label='Secret key', write_only=True) + TENCENT_SECRET_KEY = EncryptedField(max_length=256, required=False, label='Secret key') TENCENT_SDKAPPID = serializers.CharField(max_length=256, required=True, label='SDK app id') TENCENT_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature')) TENCENT_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code')) diff --git a/apps/settings/serializers/auth/wecom.py b/apps/settings/serializers/auth/wecom.py index ceb83aa85..bd1498105 100644 --- a/apps/settings/serializers/auth/wecom.py +++ b/apps/settings/serializers/auth/wecom.py @@ -1,11 +1,13 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField + __all__ = ['WeComSettingSerializer'] class WeComSettingSerializer(serializers.Serializer): WECOM_CORPID = serializers.CharField(max_length=256, required=True, label='corpid') WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='agentid') - WECOM_SECRET = serializers.CharField(max_length=256, required=False, label='secret', write_only=True) - AUTH_WECOM = serializers.BooleanField(default=False, label=_('Enable WeCom Auth')) \ No newline at end of file + WECOM_SECRET = EncryptedField(max_length=256, required=False, label='secret') + AUTH_WECOM = serializers.BooleanField(default=False, label=_('Enable WeCom Auth')) diff --git a/apps/settings/serializers/email.py b/apps/settings/serializers/email.py index 4395d7473..c67cf2216 100644 --- a/apps/settings/serializers/email.py +++ b/apps/settings/serializers/email.py @@ -1,9 +1,11 @@ # coding: utf-8 -# +# from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.drf.fields import EncryptedField + __all__ = ['MailTestSerializer', 'EmailSettingSerializer', 'EmailContentSettingSerializer'] @@ -18,8 +20,8 @@ class EmailSettingSerializer(serializers.Serializer): EMAIL_HOST = serializers.CharField(max_length=1024, required=True, label=_("SMTP host")) EMAIL_PORT = serializers.CharField(max_length=5, required=True, label=_("SMTP port")) EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True, label=_("SMTP account")) - EMAIL_HOST_PASSWORD = serializers.CharField( - max_length=1024, write_only=True, required=False, label=_("SMTP password"), + EMAIL_HOST_PASSWORD = EncryptedField( + max_length=1024, required=False, label=_("SMTP password"), help_text=_("Tips: Some provider use token except password") ) EMAIL_FROM = serializers.CharField( diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index d04232d38..ff7d386de 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers +from rest_framework.validators import UniqueValidator from urllib.parse import urlparse from django.utils.translation import ugettext_lazy as _ from django.db.models import TextChoices + from common.drf.serializers import MethodSerializer -from common.drf.fields import ReadableHiddenField +from common.drf.fields import ReadableHiddenField, EncryptedField from ..models import ReplayStorage, CommandStorage from .. import const -from rest_framework.validators import UniqueValidator # Replay storage serializers @@ -25,12 +26,12 @@ class ReplayStorageTypeBaseSerializer(serializers.Serializer): required=True, max_length=1024, label=_('Bucket'), allow_null=True ) ACCESS_KEY = serializers.CharField( - max_length=1024, required=False, allow_blank=True, write_only=True, label=_('Access key'), - allow_null=True, + max_length=1024, required=False, allow_blank=True, + label=_('Access key id'), allow_null=True, ) - SECRET_KEY = serializers.CharField( - max_length=1024, required=False, allow_blank=True, write_only=True, label=_('Secret key'), - allow_null=True, + SECRET_KEY = EncryptedField( + max_length=1024, required=False, allow_blank=True, + label=_('Access key secret'), allow_null=True, ) ENDPOINT = serializers.CharField( validators=[replay_storage_endpoint_format_validator], @@ -108,7 +109,7 @@ class ReplayStorageTypeAzureSerializer(serializers.Serializer): max_length=1024, label=_('Container name'), allow_null=True ) ACCOUNT_NAME = serializers.CharField(max_length=1024, label=_('Account name'), allow_null=True) - ACCOUNT_KEY = serializers.CharField(max_length=1024, label=_('Account key'), allow_null=True) + ACCOUNT_KEY = EncryptedField(max_length=1024, label=_('Account key'), allow_null=True) ENDPOINT_SUFFIX = serializers.ChoiceField( choices=EndpointSuffixChoices.choices, default=EndpointSuffixChoices.china.value, label=_('Endpoint suffix'), allow_null=True, diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py index d6cd9055f..c8dfb7782 100644 --- a/apps/users/serializers/profile.py +++ b/apps/users/serializers/profile.py @@ -17,9 +17,9 @@ class UserOrgSerializer(serializers.Serializer): class UserUpdatePasswordSerializer(serializers.ModelSerializer): - old_password = EncryptedField(required=True, max_length=128, write_only=True) - new_password = EncryptedField(required=True, max_length=128, write_only=True) - new_password_again = EncryptedField(required=True, max_length=128, write_only=True) + old_password = EncryptedField(required=True, max_length=128) + new_password = EncryptedField(required=True, max_length=128) + new_password_again = EncryptedField(required=True, max_length=128) class Meta: model = User @@ -57,18 +57,20 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer): class UserUpdateSecretKeySerializer(serializers.ModelSerializer): - new_secret_key = serializers.CharField(required=True, max_length=128, write_only=True) - new_secret_key_again = serializers.CharField(required=True, max_length=128, write_only=True) + new_secret_key = EncryptedField(required=True, max_length=128) + new_secret_key_again = EncryptedField(required=True, max_length=128) class Meta: model = User fields = ['new_secret_key', 'new_secret_key_again'] - def validate_new_secret_key_again(self, value): - if value != self.initial_data.get('new_secret_key', ''): + def validate(self, values): + new_secret_key = values.get('new_secret_key', '') + new_secret_key_again = values.get('new_secret_key_again', '') + if new_secret_key != new_secret_key_again: msg = _('The newly set password is inconsistent') - raise serializers.ValidationError(msg) - return value + raise serializers.ValidationError({'new_secret_key_again': msg}) + return values def update(self, instance, validated_data): new_secret_key = self.validated_data.get('new_secret_key') From 0a04f0f35146fdd1bde364ddf9e6cd66f4d768e4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 20 May 2022 00:10:22 +0800 Subject: [PATCH 126/258] =?UTF-8?q?perf:=20=E4=B8=8B=E8=BD=BD=20ip=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/file.py | 10 ++++++++++ jms | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/apps/common/utils/file.py b/apps/common/utils/file.py index c00293de5..9529fbdca 100644 --- a/apps/common/utils/file.py +++ b/apps/common/utils/file.py @@ -1,6 +1,8 @@ import os import csv + import pyzipper +import requests def create_csv_file(filename, headers, rows, ): @@ -18,3 +20,11 @@ def encrypt_and_compress_zip_file(filename, secret_password, encrypted_filenames for encrypted_filename in encrypted_filenames: with open(encrypted_filename, 'rb') as f: zf.writestr(os.path.basename(encrypted_filename), f.read()) + + +def download_file(src, path): + with requests.get(src, stream=True) as r: + r.raise_for_status() + with open(path, 'wb') as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) diff --git a/jms b/jms index 517fb0602..5d5b5c433 100755 --- a/jms +++ b/jms @@ -8,6 +8,7 @@ import time import argparse import sys import django +import requests from django.core import management from django.db.utils import OperationalError @@ -33,6 +34,7 @@ except ImportError as e: try: from jumpserver.const import CONFIG + from common.utils.file import download_file except ImportError as e: print("Import error: {}".format(e)) print("Could not find config file, `cp config_example.yml config.yml`") @@ -105,6 +107,20 @@ def compile_i18n_file(): logging.info("Compile i18n files done") +def download_ip_db(): + db_base_dir = os.path.join(APP_DIR, 'common', 'utils', 'ip') + db_path_url_mapper = { + ('geoip', 'GeoLite2-City.mmdb'): 'https://jms-pkg.oss-cn-beijing.aliyuncs.com/ip/GeoLite2-City.mmdb', + ('ipip', 'ipipfree.ipdb'): 'https://jms-pkg.oss-cn-beijing.aliyuncs.com/ip/ipipfree.ipdb' + } + for p, src in db_path_url_mapper.items(): + path = os.path.join(db_base_dir, *p) + if os.path.isfile(path) and os.path.getsize(path) > 1000: + continue + print("Download ip db: {}".format(path)) + download_file(src, path) + + def upgrade_db(): collect_static() perform_db_migrate() @@ -114,6 +130,7 @@ def prepare(): check_database_connection() upgrade_db() expire_caches() + download_ip_db() def start_services(): From 26cf64ad2d9c66b8f492ec2929500304c38ed73f Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 20 May 2022 11:40:06 +0800 Subject: [PATCH 127/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20i18?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 3 +++ apps/locale/zh/LC_MESSAGES/django.mo | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 apps/locale/ja/LC_MESSAGES/django.mo create mode 100644 apps/locale/zh/LC_MESSAGES/django.mo diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo new file mode 100644 index 000000000..2f0a7842c --- /dev/null +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82cdb805f6d681577806fb6fc41178b5dd28eafc68b1348f433ba7f7f5ce9920 +size 127478 diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo new file mode 100644 index 000000000..65a32959d --- /dev/null +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b03f7f9c1d450f8a3012330c00d084c88465d99e355c42ec10f568a8ffa7611c +size 105357 From 09ef72a4a8bde20b94302b32c2324d5986377806 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 24 May 2022 10:18:20 +0800 Subject: [PATCH 128/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Migrations?= =?UTF-8?q?=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0090_auto_20220412_1145.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/assets/migrations/0090_auto_20220412_1145.py b/apps/assets/migrations/0090_auto_20220412_1145.py index 434b7b3cf..3259cd37b 100644 --- a/apps/assets/migrations/0090_auto_20220412_1145.py +++ b/apps/assets/migrations/0090_auto_20220412_1145.py @@ -14,7 +14,6 @@ def create_internal_platform(apps, schema_editor): model.objects.using(db_alias).update_or_create( name=name, defaults=defaults ) - migrations.RunPython(create_internal_platform) class Migration(migrations.Migration): From a08dd5ee722dfe2e0249833066ff731c4ce1344c Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 24 May 2022 11:03:12 +0800 Subject: [PATCH 129/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=9B=B4=E6=96=B0=E8=87=AA=E5=B7=B1=E5=AF=86=E7=A0=81?= =?UTF-8?q?=20url=20=E4=B8=8D=E5=87=86=E7=A1=AE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/users/notifications.py b/apps/users/notifications.py index 99aeb77b7..645e206cd 100644 --- a/apps/users/notifications.py +++ b/apps/users/notifications.py @@ -143,7 +143,7 @@ class PasswordExpirationReminderMsg(UserMessage): subject = _('Password is about expire') date_password_expired_local = timezone.localtime(user.date_password_expired) - update_password_url = urljoin(settings.SITE_URL, '/ui/#/users/profile/?activeTab=PasswordUpdate') + update_password_url = urljoin(settings.SITE_URL, '/ui/#/profile/setting/?activeTab=PasswordUpdate') date_password_expired = date_password_expired_local.strftime('%Y-%m-%d %H:%M:%S') context = { 'name': user.name, From 041302d5d261fe1e71781efd703961eddea4cf01 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 24 May 2022 12:31:16 +0800 Subject: [PATCH 130/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=20city=20=E6=97=B6=E5=8F=AF=E8=83=BD=E7=9A=84?= =?UTF-8?q?=E6=8A=A5=E9=94=99=20(#8294)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ibuler --- apps/common/utils/ip/geoip/utils.py | 6 +----- apps/common/utils/ip/ipip/utils.py | 13 ++++++------- apps/common/utils/ip/utils.py | 23 ++++++++++++++--------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/common/utils/ip/geoip/utils.py b/apps/common/utils/ip/geoip/utils.py index 5e829f610..64752a3a1 100644 --- a/apps/common/utils/ip/geoip/utils.py +++ b/apps/common/utils/ip/geoip/utils.py @@ -13,10 +13,6 @@ reader = None def get_ip_city_by_geoip(ip): - if not ip or '.' not in ip or not isinstance(ip, str): - return _("Invalid ip") - if ':' in ip: - return 'IPv6' global reader if reader is None: path = os.path.join(os.path.dirname(__file__), 'GeoLite2-City.mmdb') @@ -32,7 +28,7 @@ def get_ip_city_by_geoip(ip): try: response = reader.city(ip) except GeoIP2Error: - return {} + return _("Unknown") city_names = response.city.names or {} lang = settings.LANGUAGE_CODE[:2] diff --git a/apps/common/utils/ip/ipip/utils.py b/apps/common/utils/ip/ipip/utils.py index ef97f4794..e94009b70 100644 --- a/apps/common/utils/ip/ipip/utils.py +++ b/apps/common/utils/ip/ipip/utils.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # import os -from django.utils.translation import ugettext as _ import ipdb @@ -11,13 +10,13 @@ ipip_db = None def get_ip_city_by_ipip(ip): global ipip_db - if not ip or not isinstance(ip, str): - return _("Invalid ip") - if ':' in ip: - return 'IPv6' if ipip_db is None: ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb') ipip_db = ipdb.City(ipip_db_path) - - info = ipip_db.find_info(ip, 'CN') + try: + info = ipip_db.find_info(ip, 'CN') + except ValueError: + return None + if not info: + raise None return {'city': info.city_name, 'country': info.country_name} diff --git a/apps/common/utils/ip/utils.py b/apps/common/utils/ip/utils.py index e75ecb85c..d62cba00d 100644 --- a/apps/common/utils/ip/utils.py +++ b/apps/common/utils/ip/utils.py @@ -74,13 +74,18 @@ def contains_ip(ip, ip_group): def get_ip_city(ip): - info = get_ip_city_by_ipip(ip) - city = info.get('city', _("Unknown")) - country = info.get('country') + if not ip or not isinstance(ip, str): + return _("Invalid ip") + if ':' in ip: + return 'IPv6' - # 国内城市 并且 语言是中文就使用国内 - is_zh = settings.LANGUAGE_CODE.startswith('zh') - if country == '中国' and is_zh: - return city - else: - return get_ip_city_by_geoip(ip) + info = get_ip_city_by_ipip(ip) + if info: + city = info.get('city', _("Unknown")) + country = info.get('country') + + # 国内城市 并且 语言是中文就使用国内 + is_zh = settings.LANGUAGE_CODE.startswith('zh') + if country == '中国' and is_zh: + return city + return get_ip_city_by_geoip(ip) From b24d2f628aefe44076d75fc7e032cfe54df37f72 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 27 May 2022 14:14:47 +0800 Subject: [PATCH 131/258] perf: update download (#8304) Co-authored-by: feng626 <1304903146@qq.com> --- apps/templates/resource_download.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index 7f59d521c..09215b532 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -21,8 +21,8 @@ p {

    From 42202bd528faa5af225d41aece891a290f139a7a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 27 May 2022 17:21:03 +0800 Subject: [PATCH 132/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20public=20se?= =?UTF-8?q?ttings=20API=E5=85=AC=E5=91=8A=E5=AD=97=E6=AE=B5=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=B8=BA=20dict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/serializers/public.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index fed540bc0..22fe55142 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -40,6 +40,6 @@ class PrivateSettingSerializer(PublicSettingSerializer): TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField() ANNOUNCEMENT_ENABLED = serializers.BooleanField() - ANNOUNCEMENT = serializers.CharField() + ANNOUNCEMENT = serializers.DictField() TICKETS_ENABLED = serializers.BooleanField() From 1322106c9113c66fbd9a256c571e4c8d22861324 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Sun, 29 May 2022 14:03:19 +0300 Subject: [PATCH 133/258] bumping redis-py to 4.3.1 (latest) --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c301148ea..6387c2dd9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -59,7 +59,7 @@ PyNaCl==1.5.0 python-dateutil==2.8.2 pytz==2018.3 PyYAML==6.0 -redis==3.5.3 +redis==4.3.1 requests==2.25.1 jms-storage==0.0.42 s3transfer==0.5.0 From 992c1407b6f55e1e27c7878995c50a3d18772958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 30 May 2022 14:51:29 +0800 Subject: [PATCH 134/258] Update README.md (#8316) * Update README.md * Update README.md * Update README.md * Update README.md --- README.md | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 813327bfc..751dec240 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ License: GPLv3  release Codacy + GitHub last commit Stars

    @@ -55,12 +56,15 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 - [手动安装](https://github.com/jumpserver/installer) ### 组件项目 -- [Lina](https://github.com/jumpserver/lina) JumpServer Web UI 项目 -- [Luna](https://github.com/jumpserver/luna) JumpServer Web Terminal 项目 -- [KoKo](https://github.com/jumpserver/koko) JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) -- [Lion](https://github.com/jumpserver/lion-release) JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) -- [Clients](https://github.com/jumpserver/clients) JumpServer 客户端 项目 -- [Installer](https://github.com/jumpserver/installer) JumpServer 安装包 项目 +| 项目 | 状态 | 描述 | +| --------------------------------------------------------------------------- | ------------------- | ---------------------------------------- | +| [Lina](https://github.com/jumpserver/lina) | Lina release | JumpServer Web UI 项目 | +| [Luna](https://github.com/jumpserver/luna) | Luna release | JumpServer Web Terminal 项目 | +| [KoKo](https://github.com/jumpserver/koko) | Koko release | JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) | +| [Lion](https://github.com/jumpserver/lion-release) | Lion release | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) | +| [Magnus](https://github.com/jumpserver/magnus-release) | Magnus release | JumpServer 数据库 Proxy Connector 项目) | +| [Clients](https://github.com/jumpserver/clients) | Clients release | JumpServer 客户端 项目 | +| [Installer](https://github.com/jumpserver/installer)| Installer release | JumpServer 安装包 项目 | ### 社区 @@ -75,21 +79,7 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 感谢以下贡献者,让 JumpServer 更加完善 - - - - - - - - - - - - - - - + From 021635b8507f4bda8e6d4076ca995753d03c0015 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 30 May 2022 15:07:05 +0800 Subject: [PATCH 135/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 751dec240..70dac07d1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -

    JumpServer

    +

    + JumpServer +

    多云环境下更好用的堡垒机

    @@ -16,7 +18,7 @@ JumpServer 是全球首款开源的堡垒机,使用 GPLv3 开源协议,是符合 4A 规范的运维安全审计系统。 -JumpServer 使用 Python 开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。 +JumpServer 使用 Python 开发,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。 JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。 @@ -29,9 +31,9 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 - 开源: 零门槛,线上快速获取和安装; - 分布式: 轻松支持大规模并发访问; - 无插件: 仅需浏览器,极致的 Web Terminal 使用体验; +- 多租户: 一套系统,多个子公司或部门同时使用; - 多云支持: 一套系统,同时管理不同云上面的资产; - 云端存储: 审计录像云端存储,永不丢失; -- 多租户: 一套系统,多个子公司和部门同时使用; - 多应用支持: 数据库,Windows远程应用,Kubernetes。 ### UI 展示 @@ -62,7 +64,7 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 | [Luna](https://github.com/jumpserver/luna) | Luna release | JumpServer Web Terminal 项目 | | [KoKo](https://github.com/jumpserver/koko) | Koko release | JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) | | [Lion](https://github.com/jumpserver/lion-release) | Lion release | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) | -| [Magnus](https://github.com/jumpserver/magnus-release) | Magnus release | JumpServer 数据库 Proxy Connector 项目) | +| [Magnus](https://github.com/jumpserver/magnus-release) | Magnus release | JumpServer 数据库代理 Connector 项目 | | [Clients](https://github.com/jumpserver/clients) | Clients release | JumpServer 客户端 项目 | | [Installer](https://github.com/jumpserver/installer)| Installer release | JumpServer 安装包 项目 | @@ -79,13 +81,13 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 感谢以下贡献者,让 JumpServer 更加完善 - + ### 致谢 -- [Apache Guacamole](https://guacamole.apache.org/) Web页面连接 RDP, SSH, VNC协议设备,JumpServer 图形化组件 Lion 依赖 -- [OmniDB](https://omnidb.org/) Web页面连接使用数据库,JumpServer Web数据库依赖 +- [Apache Guacamole](https://guacamole.apache.org/) Web页面连接 RDP, SSH, VNC 协议设备,JumpServer 图形化组件 Lion 依赖 +- [OmniDB](https://omnidb.org/) Web 页面连接使用数据库,JumpServer Web 数据库依赖 ### JumpServer 企业版 @@ -93,14 +95,14 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 ### 案例研究 -- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147); -- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882); -- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851); -- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516); -- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732); -- [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708); -- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687); -- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)。 +- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147) +- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882) +- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851) +- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516) +- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732) +- [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708) +- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687) +- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666) ### 安全说明 From 15423291ccb5379b5399030702f3a30bb99b5cca Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 30 May 2022 15:25:54 +0800 Subject: [PATCH 136/258] =?UTF-8?q?fix:=20=20=E4=BF=AE=E5=A4=8Dldap?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E6=97=B6=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=BB=84=E4=B8=8D=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/ldap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py index 895226e58..354d6211e 100644 --- a/apps/authentication/backends/ldap.py +++ b/apps/authentication/backends/ldap.py @@ -157,6 +157,8 @@ class LDAPUser(_LDAPUser): def _populate_user_from_attributes(self): for field, attr in self.settings.USER_ATTR_MAP.items(): + if field in ['groups']: + continue try: value = self.attrs[attr][0] value = value.strip() From 196663f2055c268e98711253d9509956807a7e3b Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 30 May 2022 16:01:57 +0800 Subject: [PATCH 137/258] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=81=87=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/generate_fake_data/generate.py | 4 +- utils/generate_fake_data/resources/system.py | 4 +- .../generate_fake_data/resources/terminal.py | 45 ++++++++++++++++++- utils/generate_fake_data/resources/users.py | 2 - 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/utils/generate_fake_data/generate.py b/utils/generate_fake_data/generate.py index fb41d7e1d..97340e8e6 100644 --- a/utils/generate_fake_data/generate.py +++ b/utils/generate_fake_data/generate.py @@ -15,8 +15,7 @@ django.setup() from resources.assets import AssetsGenerator, NodesGenerator, SystemUsersGenerator, AdminUsersGenerator from resources.users import UserGroupGenerator, UserGenerator from resources.perms import AssetPermissionGenerator -from resources.terminal import CommandGenerator -# from resources.system import StatGenerator +from resources.terminal import CommandGenerator, SessionGenerator resource_generator_mapper = { @@ -28,6 +27,7 @@ resource_generator_mapper = { 'user_group': UserGroupGenerator, 'asset_permission': AssetPermissionGenerator, 'command': CommandGenerator, + 'session': SessionGenerator # 'stat': StatGenerator } diff --git a/utils/generate_fake_data/resources/system.py b/utils/generate_fake_data/resources/system.py index 69a12d144..fb40ce83a 100644 --- a/utils/generate_fake_data/resources/system.py +++ b/utils/generate_fake_data/resources/system.py @@ -1,7 +1,7 @@ import random from .base import FakeDataGenerator -from system.models import * +from terminal.models import * class StatGenerator(FakeDataGenerator): @@ -66,4 +66,4 @@ class StatGenerator(FakeDataGenerator): 'datetime': datetime }) items.append(Stat(**node)) - Stat.objects.bulk_create(items, ignore_conflicts=True) \ No newline at end of file + Stat.objects.bulk_create(items, ignore_conflicts=True) diff --git a/utils/generate_fake_data/resources/terminal.py b/utils/generate_fake_data/resources/terminal.py index 48d825b47..481e98f6d 100644 --- a/utils/generate_fake_data/resources/terminal.py +++ b/utils/generate_fake_data/resources/terminal.py @@ -1,5 +1,9 @@ from .base import FakeDataGenerator -from terminal.models import Command +from users.models import * +from assets.models import * +from terminal.models import * +import forgery_py +import random class CommandGenerator(FakeDataGenerator): @@ -8,3 +12,42 @@ class CommandGenerator(FakeDataGenerator): def do_generate(self, batch, batch_size): Command.generate_fake(len(batch), self.org) + +class SessionGenerator(FakeDataGenerator): + resource = 'session' + users: list + assets: list + system_users: list + + def pre_generate(self): + self.users = list(User.objects.all()) + self.assets = list(Asset.objects.all()) + self.system_users = list(SystemUser.objects.all()) + + def do_generate(self, batch, batch_size): + user = random.choice(self.users) + asset = random.choice(self.assets) + system_user = random.choice(self.system_users) + + objects = [] + + now = timezone.now() + timedelta = list(range(30)) + for i in batch: + delta = random.choice(timedelta) + date_start = now - timezone.timedelta(days=delta, seconds=delta * 60) + date_end = date_start + timezone.timedelta(seconds=delta * 60) + data = dict( + user=user.name, + user_id=user.id, + asset=asset.hostname, + asset_id=asset.id, + system_user=system_user.name, + system_user_id=system_user.id, + org_id=self.org.id, + date_start=date_start, + date_end=date_end, + ) + objects.append(Session(**data)) + creates = Session.objects.bulk_create(objects, ignore_conflicts=True) + diff --git a/utils/generate_fake_data/resources/users.py b/utils/generate_fake_data/resources/users.py index 887c7240c..c973fec5e 100644 --- a/utils/generate_fake_data/resources/users.py +++ b/utils/generate_fake_data/resources/users.py @@ -24,7 +24,6 @@ class UserGenerator(FakeDataGenerator): group_ids: list def pre_generate(self): - self.roles = list(dict(User.ROLE.choices).keys()) self.group_ids = list(UserGroup.objects.all().values_list('id', flat=True)) def set_org(self, users): @@ -53,7 +52,6 @@ class UserGenerator(FakeDataGenerator): username=username, email=email, name=username.title(), - role=choice(self.roles), created_by='Faker' ) users.append(u) From 327c6beab4c175a5d85a4033c53f42c6f5e6aee8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 30 May 2022 16:20:34 +0800 Subject: [PATCH 138/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=81=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=9E=84=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/generate_fake_data/resources/terminal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/generate_fake_data/resources/terminal.py b/utils/generate_fake_data/resources/terminal.py index 481e98f6d..fde3cd267 100644 --- a/utils/generate_fake_data/resources/terminal.py +++ b/utils/generate_fake_data/resources/terminal.py @@ -47,6 +47,7 @@ class SessionGenerator(FakeDataGenerator): org_id=self.org.id, date_start=date_start, date_end=date_end, + is_finished=True ) objects.append(Session(**data)) creates = Session.objects.bulk_create(objects, ignore_conflicts=True) From f7cbcc46f4d8e245ea767a023ddf9555aa40f81f Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 31 May 2022 15:58:14 +0800 Subject: [PATCH 139/258] =?UTF-8?q?perf:=20=E5=8D=87=E7=BA=A7=20ansible=20?= =?UTF-8?q?version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 6387c2dd9..8833b6e60 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,5 @@ amqp==5.0.9 -ansible==2.10 +ansible==2.10.7 asn1crypto==0.24.0 bcrypt==3.1.4 billiard==3.6.4.0 From af1150bb86524b6dfc70bc6e4b982c871c1596f2 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 31 May 2022 15:39:49 +0800 Subject: [PATCH 140/258] =?UTF-8?q?feat:=20OIDC=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B1=9E=E6=80=A7=E6=98=A0=E5=B0=84=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/oidc/backends.py | 24 ++++++++++++------- apps/jumpserver/conf.py | 3 +++ apps/jumpserver/settings/auth.py | 1 + apps/settings/serializers/auth/oidc.py | 5 ++++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index daa614ec8..ec5765510 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -18,6 +18,7 @@ from django.urls import reverse from django.conf import settings from common.utils import get_logger +from users.utils import construct_user_email from ..base import JMSBaseAuthBackend from .utils import validate_and_return_id_token, build_absolute_uri @@ -39,17 +40,22 @@ class UserMixin: logger.debug(log_prompt.format('start')) sub = claims['sub'] - name = claims.get('name', sub) - username = claims.get('preferred_username', sub) - email = claims.get('email', "{}@{}".format(username, 'jumpserver.openid')) - logger.debug( - log_prompt.format( - "sub: {}|name: {}|username: {}|email: {}".format(sub, name, username, email) - ) - ) + + # Construct user attrs value + user_attrs = {} + for field, attr in settings.AUTH_OPENID_USER_ATTR_MAP.items(): + user_attrs[field] = claims.get(attr, sub) + email = user_attrs.get('email', '') + email = construct_user_email(user_attrs.get('username'), email, 'jumpserver.openid') + user_attrs.update({'email': email}) + + logger.debug(log_prompt.format(user_attrs)) + + username = user_attrs.get('username') + name = user_attrs.get('name') user, created = get_user_model().objects.get_or_create( - username=username, defaults={"name": name, "email": email} + username=username, defaults=user_attrs ) logger.debug(log_prompt.format("user: {}|created: {}".format(user, created))) logger.debug(log_prompt.format("Send signal => openid create or update user")) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 6f84397ac..85c3b4598 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -191,6 +191,9 @@ class Config(dict): 'AUTH_OPENID_CLIENT_AUTH_METHOD': 'client_secret_basic', 'AUTH_OPENID_SHARE_SESSION': True, 'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True, + 'AUTH_OPENID_USER_ATTR_MAP': { + 'name': 'name', 'username': 'preferred_username', 'email': 'email' + }, # OpenID 新配置参数 (version >= 1.5.9) 'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://oidc.example.com/', diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index fb067078f..010e92f31 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -73,6 +73,7 @@ AUTH_OPENID_USE_NONCE = CONFIG.AUTH_OPENID_USE_NONCE AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER +AUTH_OPENID_USER_ATTR_MAP = CONFIG.AUTH_OPENID_USER_ATTR_MAP AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login' AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback' AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout' diff --git a/apps/settings/serializers/auth/oidc.py b/apps/settings/serializers/auth/oidc.py index 5ab73ea04..ad30729a0 100644 --- a/apps/settings/serializers/auth/oidc.py +++ b/apps/settings/serializers/auth/oidc.py @@ -31,6 +31,11 @@ class CommonSettingSerializer(serializers.Serializer): AUTH_OPENID_IGNORE_SSL_VERIFICATION = serializers.BooleanField( required=False, label=_('Ignore ssl verification') ) + AUTH_OPENID_USER_ATTR_MAP = serializers.DictField( + required=True, label=_('User attr map'), + help_text=_('User attr map present how to map OpenID user attr to ' + 'jumpserver, username,name,email is jumpserver attr') + ) class KeycloakSettingSerializer(CommonSettingSerializer): From 6c0d0c3e9285d5e144463b71eeee84bd07f32237 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 31 May 2022 16:09:31 +0800 Subject: [PATCH 141/258] =?UTF-8?q?feat:=20OIDC=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B1=9E=E6=80=A7=E6=98=A0=E5=B0=84=E5=80=BC?= =?UTF-8?q?=20(#8327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: OIDC 用户添加属性映射值 * feat: OIDC 用户添加属性映射值 Co-authored-by: Jiangjie.Bai --- apps/locale/ja/LC_MESSAGES/django.po | 139 +++++++++++++++------------ apps/locale/zh/LC_MESSAGES/django.po | 139 +++++++++++++++------------ 2 files changed, 154 insertions(+), 124 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index d850a1cc1..2d7a87994 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-19 13:16+0800\n" +"POT-Creation-Date: 2022-05-31 16:02+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -134,7 +134,7 @@ msgstr "システムユーザー" #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 -#: xpack/plugins/change_auth_plan/serializers/asset.py:180 +#: xpack/plugins/change_auth_plan/serializers/asset.py:181 #: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "資産" @@ -320,7 +320,7 @@ msgid "Domain" msgstr "ドメイン" #: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Attrs" msgstr "ツールバーの" @@ -414,10 +414,10 @@ msgstr "アプリケーションパス" #: applications/serializers/attrs/application_category/remote_app.py:44 #: assets/serializers/system_user.py:168 -#: xpack/plugins/change_auth_plan/serializers/asset.py:66 -#: xpack/plugins/change_auth_plan/serializers/asset.py:69 -#: xpack/plugins/change_auth_plan/serializers/asset.py:72 -#: xpack/plugins/change_auth_plan/serializers/asset.py:103 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:73 +#: xpack/plugins/change_auth_plan/serializers/asset.py:104 #: xpack/plugins/cloud/serializers/account_attrs.py:54 msgid "This field is required." msgstr "このフィールドは必須です。" @@ -665,7 +665,7 @@ msgstr "すべて" #: assets/models/backup.py:52 assets/serializers/backup.py:32 #: xpack/plugins/change_auth_plan/models/app.py:41 #: xpack/plugins/change_auth_plan/models/asset.py:62 -#: xpack/plugins/change_auth_plan/serializers/base.py:44 +#: xpack/plugins/change_auth_plan/serializers/base.py:49 msgid "Recipient" msgstr "受信者" @@ -708,7 +708,7 @@ msgstr "アカウントのバックアップスナップショット" #: assets/models/backup.py:116 assets/serializers/backup.py:40 #: xpack/plugins/change_auth_plan/models/base.py:125 -#: xpack/plugins/change_auth_plan/serializers/base.py:73 +#: xpack/plugins/change_auth_plan/serializers/base.py:82 msgid "Trigger mode" msgstr "トリガーモード" @@ -716,7 +716,7 @@ msgstr "トリガーモード" #: terminal/models/sharing.py:94 #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 -#: xpack/plugins/change_auth_plan/serializers/asset.py:179 +#: xpack/plugins/change_auth_plan/serializers/asset.py:180 #: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "理由" @@ -726,7 +726,7 @@ msgstr "理由" #: terminal/serializers/session.py:35 #: xpack/plugins/change_auth_plan/models/base.py:202 #: xpack/plugins/change_auth_plan/serializers/app.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:181 +#: xpack/plugins/change_auth_plan/serializers/asset.py:182 msgid "Is success" msgstr "成功は" @@ -735,7 +735,8 @@ msgid "Account backup execution" msgstr "アカウントバックアップの実行" #: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 -#: common/utils/ip/geoip/utils.py:41 common/utils/ip/utils.py:78 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 msgid "Unknown" msgstr "不明" @@ -745,8 +746,8 @@ msgstr "OK" #: assets/models/base.py:32 audits/models.py:118 #: xpack/plugins/change_auth_plan/serializers/app.py:88 -#: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/change_auth_plan/serializers/asset.py:199 +#: xpack/plugins/cloud/const.py:32 msgid "Failed" msgstr "失敗しました" @@ -769,6 +770,8 @@ msgstr "確認済みの日付" #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/models/base.py:196 +#: xpack/plugins/change_auth_plan/serializers/base.py:22 +#: xpack/plugins/change_auth_plan/serializers/base.py:77 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Password" msgstr "パスワード" @@ -898,7 +901,7 @@ msgstr "テストゲートウェイ" msgid "Unable to connect to port {port} on {ip}" msgstr "{ip} でポート {port} に接続できません" -#: assets/models/domain.py:133 +#: assets/models/domain.py:133 xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "認証に失敗しました" @@ -1115,12 +1118,12 @@ msgstr "アクション" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 #: settings/serializers/auth/ldap.py:65 -#: xpack/plugins/change_auth_plan/serializers/base.py:42 +#: xpack/plugins/change_auth_plan/serializers/base.py:47 msgid "Periodic perform" msgstr "定期的なパフォーマンス" #: assets/serializers/backup.py:33 -#: xpack/plugins/change_auth_plan/serializers/base.py:45 +#: xpack/plugins/change_auth_plan/serializers/base.py:50 msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" @@ -1414,7 +1417,7 @@ msgstr "ファイル名" #: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:90 #: tickets/views/approve.py:98 #: xpack/plugins/change_auth_plan/serializers/app.py:87 -#: xpack/plugins/change_auth_plan/serializers/asset.py:197 +#: xpack/plugins/change_auth_plan/serializers/asset.py:198 msgid "Success" msgstr "成功" @@ -2196,7 +2199,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:304 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:23 @@ -2647,15 +2650,14 @@ msgstr "確認コードが正しくありません" msgid "Please wait {} seconds before sending" msgstr "{} 秒待ってから送信してください" -#: common/utils/ip/geoip/utils.py:17 common/utils/ip/geoip/utils.py:30 -#: common/utils/ip/ipip/utils.py:15 -msgid "Invalid ip" -msgstr "無効なIP" - -#: common/utils/ip/geoip/utils.py:28 +#: common/utils/ip/geoip/utils.py:24 msgid "LAN" msgstr "LAN" +#: common/utils/ip/geoip/utils.py:26 common/utils/ip/utils.py:78 +msgid "Invalid ip" +msgstr "無効なIP" + #: common/validators.py:32 msgid "This field must be unique." msgstr "このフィールドは一意である必要があります。" @@ -2668,11 +2670,11 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:300 +#: jumpserver/conf.py:303 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:302 +#: jumpserver/conf.py:305 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -3476,7 +3478,7 @@ msgstr "ログインリダイレクトの有効化msg" msgid "Enable CAS Auth" msgstr "CAS 認証の有効化" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:42 +#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:47 msgid "Server url" msgstr "サービス側アドレス" @@ -3541,7 +3543,7 @@ msgstr "ユーザー検索フィルター" msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)" -#: settings/serializers/auth/ldap.py:57 +#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oidc.py:35 msgid "User attr map" msgstr "ユーザー属性マッピング" @@ -3590,71 +3592,79 @@ msgstr "セッションの共有" msgid "Ignore ssl verification" msgstr "Ssl検証を無視する" -#: settings/serializers/auth/oidc.py:39 +#: settings/serializers/auth/oidc.py:36 +msgid "" +"User attr map present how to map OpenID user attr to jumpserver, username," +"name,email is jumpserver attr" +msgstr "" +"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピング" +"する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" + +#: settings/serializers/auth/oidc.py:44 msgid "Use Keycloak" msgstr "Keycloakを使用する" -#: settings/serializers/auth/oidc.py:45 +#: settings/serializers/auth/oidc.py:50 msgid "Realm name" msgstr "レルム名" -#: settings/serializers/auth/oidc.py:51 +#: settings/serializers/auth/oidc.py:56 msgid "Enable OPENID Auth" msgstr "OIDC認証の有効化" -#: settings/serializers/auth/oidc.py:53 +#: settings/serializers/auth/oidc.py:58 msgid "Provider endpoint" msgstr "プロバイダーエンドポイント" -#: settings/serializers/auth/oidc.py:56 +#: settings/serializers/auth/oidc.py:61 msgid "Provider auth endpoint" msgstr "認証エンドポイントアドレス" -#: settings/serializers/auth/oidc.py:59 +#: settings/serializers/auth/oidc.py:64 msgid "Provider token endpoint" msgstr "プロバイダートークンエンドポイント" -#: settings/serializers/auth/oidc.py:62 +#: settings/serializers/auth/oidc.py:67 msgid "Provider jwks endpoint" msgstr "プロバイダーjwksエンドポイント" -#: settings/serializers/auth/oidc.py:65 +#: settings/serializers/auth/oidc.py:70 msgid "Provider userinfo endpoint" msgstr "プロバイダーuserinfoエンドポイント" -#: settings/serializers/auth/oidc.py:68 +#: settings/serializers/auth/oidc.py:73 msgid "Provider end session endpoint" msgstr "プロバイダーのセッション終了エンドポイント" -#: settings/serializers/auth/oidc.py:71 +#: settings/serializers/auth/oidc.py:76 msgid "Provider sign alg" msgstr "プロビダーサインalg" -#: settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oidc.py:79 msgid "Provider sign key" msgstr "プロバイダ署名キー" -#: settings/serializers/auth/oidc.py:76 +#: settings/serializers/auth/oidc.py:81 msgid "Scopes" msgstr "スコープ" -#: settings/serializers/auth/oidc.py:78 +#: settings/serializers/auth/oidc.py:83 msgid "Id token max age" msgstr "IDトークンの最大年齢" -#: settings/serializers/auth/oidc.py:81 +#: settings/serializers/auth/oidc.py:86 msgid "Id token include claims" msgstr "IDトークンにはクレームが含まれます" -#: settings/serializers/auth/oidc.py:83 +#: settings/serializers/auth/oidc.py:88 msgid "Use state" msgstr "使用状態" -#: settings/serializers/auth/oidc.py:84 +#: settings/serializers/auth/oidc.py:89 msgid "Use nonce" msgstr "Nonceを使用" -#: settings/serializers/auth/oidc.py:86 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oidc.py:91 settings/serializers/auth/saml2.py:33 msgid "Always update user" msgstr "常にユーザーを更新" @@ -5775,7 +5785,7 @@ msgstr "組織ロール" #: users/serializers/user.py:79 #: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:22 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" msgstr "パスワード戦略" @@ -6160,7 +6170,7 @@ msgid "Replace (The key generated by JumpServer) " msgstr "置換(JumpServerによって生成された鍵)" #: xpack/plugins/change_auth_plan/models/asset.py:49 -#: xpack/plugins/change_auth_plan/serializers/asset.py:35 +#: xpack/plugins/change_auth_plan/serializers/asset.py:36 msgid "SSH Key strategy" msgstr "SSHキー戦略" @@ -6252,23 +6262,23 @@ msgstr "" "{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" "情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" -#: xpack/plugins/change_auth_plan/serializers/asset.py:32 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "Change Password" msgstr "パスワードの変更" -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +#: xpack/plugins/change_auth_plan/serializers/asset.py:34 msgid "Change SSH Key" msgstr "SSHキーの変更" -#: xpack/plugins/change_auth_plan/serializers/base.py:43 +#: xpack/plugins/change_auth_plan/serializers/base.py:48 msgid "Run times" msgstr "実行時間" -#: xpack/plugins/change_auth_plan/serializers/base.py:57 +#: xpack/plugins/change_auth_plan/serializers/base.py:62 msgid "* Please enter the correct password length" msgstr "* 正しいパスワードの長さを入力してください" -#: xpack/plugins/change_auth_plan/serializers/base.py:60 +#: xpack/plugins/change_auth_plan/serializers/base.py:65 msgid "* Password length range 6-30 bits" msgstr "* パスワードの長さの範囲6-30ビット" @@ -6356,31 +6366,35 @@ msgstr "OpenStack" msgid "Google Cloud Platform" msgstr "谷歌雲" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:23 +msgid "Fusion Compute" +msgstr "" + +#: xpack/plugins/cloud/const.py:27 msgid "Instance name" msgstr "インスタンス名" -#: xpack/plugins/cloud/const.py:27 +#: xpack/plugins/cloud/const.py:28 msgid "Instance name and Partial IP" msgstr "インスタンス名と部分IP" -#: xpack/plugins/cloud/const.py:32 +#: xpack/plugins/cloud/const.py:33 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "Unsync" msgstr "同期していません" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "New Sync" msgstr "新しい同期" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Synced" msgstr "同期済み" -#: xpack/plugins/cloud/const.py:39 +#: xpack/plugins/cloud/const.py:40 msgid "Released" msgstr "リリース済み" @@ -6654,11 +6668,11 @@ msgstr "華南-広州-友好ユーザー環境" msgid "CN East-Suqian" msgstr "華東-宿遷" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Validity display" msgstr "有効表示" -#: xpack/plugins/cloud/serializers/account.py:61 +#: xpack/plugins/cloud/serializers/account.py:62 msgid "Provider display" msgstr "プロバイダ表示" @@ -6676,6 +6690,7 @@ msgstr "サブスクリプションID" #: xpack/plugins/cloud/serializers/account_attrs.py:93 #: xpack/plugins/cloud/serializers/account_attrs.py:98 +#: xpack/plugins/cloud/serializers/account_attrs.py:122 msgid "API Endpoint" msgstr "APIエンドポイント" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 2ee578f43..8560a07ac 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-19 13:16+0800\n" +"POT-Creation-Date: 2022-05-31 16:02+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -133,7 +133,7 @@ msgstr "系统用户" #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:199 -#: xpack/plugins/change_auth_plan/serializers/asset.py:180 +#: xpack/plugins/change_auth_plan/serializers/asset.py:181 #: xpack/plugins/cloud/models.py:223 msgid "Asset" msgstr "资产" @@ -315,7 +315,7 @@ msgid "Domain" msgstr "网域" #: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Attrs" msgstr "属性" @@ -409,10 +409,10 @@ msgstr "应用路径" #: applications/serializers/attrs/application_category/remote_app.py:44 #: assets/serializers/system_user.py:168 -#: xpack/plugins/change_auth_plan/serializers/asset.py:66 -#: xpack/plugins/change_auth_plan/serializers/asset.py:69 -#: xpack/plugins/change_auth_plan/serializers/asset.py:72 -#: xpack/plugins/change_auth_plan/serializers/asset.py:103 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:73 +#: xpack/plugins/change_auth_plan/serializers/asset.py:104 #: xpack/plugins/cloud/serializers/account_attrs.py:54 msgid "This field is required." msgstr "该字段是必填项。" @@ -660,7 +660,7 @@ msgstr "全部" #: assets/models/backup.py:52 assets/serializers/backup.py:32 #: xpack/plugins/change_auth_plan/models/app.py:41 #: xpack/plugins/change_auth_plan/models/asset.py:62 -#: xpack/plugins/change_auth_plan/serializers/base.py:44 +#: xpack/plugins/change_auth_plan/serializers/base.py:49 msgid "Recipient" msgstr "收件人" @@ -703,7 +703,7 @@ msgstr "账号备份快照" #: assets/models/backup.py:116 assets/serializers/backup.py:40 #: xpack/plugins/change_auth_plan/models/base.py:125 -#: xpack/plugins/change_auth_plan/serializers/base.py:73 +#: xpack/plugins/change_auth_plan/serializers/base.py:82 msgid "Trigger mode" msgstr "触发模式" @@ -711,7 +711,7 @@ msgstr "触发模式" #: terminal/models/sharing.py:94 #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 -#: xpack/plugins/change_auth_plan/serializers/asset.py:179 +#: xpack/plugins/change_auth_plan/serializers/asset.py:180 #: xpack/plugins/cloud/models.py:179 msgid "Reason" msgstr "原因" @@ -721,7 +721,7 @@ msgstr "原因" #: terminal/serializers/session.py:35 #: xpack/plugins/change_auth_plan/models/base.py:202 #: xpack/plugins/change_auth_plan/serializers/app.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:181 +#: xpack/plugins/change_auth_plan/serializers/asset.py:182 msgid "Is success" msgstr "是否成功" @@ -730,7 +730,8 @@ msgid "Account backup execution" msgstr "账号备份执行" #: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 -#: common/utils/ip/geoip/utils.py:41 common/utils/ip/utils.py:78 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 msgid "Unknown" msgstr "未知" @@ -740,8 +741,8 @@ msgstr "成功" #: assets/models/base.py:32 audits/models.py:118 #: xpack/plugins/change_auth_plan/serializers/app.py:88 -#: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/change_auth_plan/serializers/asset.py:199 +#: xpack/plugins/cloud/const.py:32 msgid "Failed" msgstr "失败" @@ -764,6 +765,8 @@ msgstr "校验日期" #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/models/base.py:196 +#: xpack/plugins/change_auth_plan/serializers/base.py:22 +#: xpack/plugins/change_auth_plan/serializers/base.py:77 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Password" msgstr "密码" @@ -893,7 +896,7 @@ msgstr "测试网关" msgid "Unable to connect to port {port} on {ip}" msgstr "无法连接到 {ip} 上的端口 {port}" -#: assets/models/domain.py:133 +#: assets/models/domain.py:133 xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "认证失败" @@ -1107,12 +1110,12 @@ msgstr "动作" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 #: settings/serializers/auth/ldap.py:65 -#: xpack/plugins/change_auth_plan/serializers/base.py:42 +#: xpack/plugins/change_auth_plan/serializers/base.py:47 msgid "Periodic perform" msgstr "定时执行" #: assets/serializers/backup.py:33 -#: xpack/plugins/change_auth_plan/serializers/base.py:45 +#: xpack/plugins/change_auth_plan/serializers/base.py:50 msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" @@ -1402,7 +1405,7 @@ msgstr "文件名" #: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:90 #: tickets/views/approve.py:98 #: xpack/plugins/change_auth_plan/serializers/app.py:87 -#: xpack/plugins/change_auth_plan/serializers/asset.py:197 +#: xpack/plugins/change_auth_plan/serializers/asset.py:198 msgid "Success" msgstr "成功" @@ -2175,7 +2178,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:304 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:23 @@ -2617,15 +2620,14 @@ msgstr "验证码错误" msgid "Please wait {} seconds before sending" msgstr "请在 {} 秒后发送" -#: common/utils/ip/geoip/utils.py:17 common/utils/ip/geoip/utils.py:30 -#: common/utils/ip/ipip/utils.py:15 -msgid "Invalid ip" -msgstr "无效IP" - -#: common/utils/ip/geoip/utils.py:28 +#: common/utils/ip/geoip/utils.py:24 msgid "LAN" msgstr "LAN" +#: common/utils/ip/geoip/utils.py:26 common/utils/ip/utils.py:78 +msgid "Invalid ip" +msgstr "无效IP" + #: common/validators.py:32 msgid "This field must be unique." msgstr "字段必须唯一" @@ -2638,11 +2640,11 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:300 +#: jumpserver/conf.py:303 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:302 +#: jumpserver/conf.py:305 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -3437,7 +3439,7 @@ msgstr "启用登录跳转提示" msgid "Enable CAS Auth" msgstr "启用 CAS 认证" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:42 +#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:47 msgid "Server url" msgstr "服务端地址" @@ -3502,7 +3504,7 @@ msgstr "用户过滤器" msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: settings/serializers/auth/ldap.py:57 +#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oidc.py:35 msgid "User attr map" msgstr "用户属性映射" @@ -3551,71 +3553,79 @@ msgstr "共享会话" msgid "Ignore ssl verification" msgstr "忽略 SSL 证书验证" -#: settings/serializers/auth/oidc.py:39 +#: settings/serializers/auth/oidc.py:36 +msgid "" +"User attr map present how to map OpenID user attr to jumpserver, username," +"name,email is jumpserver attr" +msgstr "" +"用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上,username, name," +"email 是jumpserver的用户需要属性" + +#: settings/serializers/auth/oidc.py:44 msgid "Use Keycloak" msgstr "使用 Keycloak" -#: settings/serializers/auth/oidc.py:45 +#: settings/serializers/auth/oidc.py:50 msgid "Realm name" msgstr "域" -#: settings/serializers/auth/oidc.py:51 +#: settings/serializers/auth/oidc.py:56 msgid "Enable OPENID Auth" msgstr "启用 OIDC 认证" -#: settings/serializers/auth/oidc.py:53 +#: settings/serializers/auth/oidc.py:58 msgid "Provider endpoint" msgstr "端点地址" -#: settings/serializers/auth/oidc.py:56 +#: settings/serializers/auth/oidc.py:61 msgid "Provider auth endpoint" msgstr "授权端点地址" -#: settings/serializers/auth/oidc.py:59 +#: settings/serializers/auth/oidc.py:64 msgid "Provider token endpoint" msgstr "token 端点地址" -#: settings/serializers/auth/oidc.py:62 +#: settings/serializers/auth/oidc.py:67 msgid "Provider jwks endpoint" msgstr "jwks 端点地址" -#: settings/serializers/auth/oidc.py:65 +#: settings/serializers/auth/oidc.py:70 msgid "Provider userinfo endpoint" msgstr "用户信息端点地址" -#: settings/serializers/auth/oidc.py:68 +#: settings/serializers/auth/oidc.py:73 msgid "Provider end session endpoint" msgstr "注销会话端点地址" -#: settings/serializers/auth/oidc.py:71 +#: settings/serializers/auth/oidc.py:76 msgid "Provider sign alg" msgstr "签名算法" -#: settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oidc.py:79 msgid "Provider sign key" msgstr "签名 Key" -#: settings/serializers/auth/oidc.py:76 +#: settings/serializers/auth/oidc.py:81 msgid "Scopes" msgstr "连接范围" -#: settings/serializers/auth/oidc.py:78 +#: settings/serializers/auth/oidc.py:83 msgid "Id token max age" msgstr "令牌有效时间" -#: settings/serializers/auth/oidc.py:81 +#: settings/serializers/auth/oidc.py:86 msgid "Id token include claims" msgstr "声明" -#: settings/serializers/auth/oidc.py:83 +#: settings/serializers/auth/oidc.py:88 msgid "Use state" msgstr "使用状态" -#: settings/serializers/auth/oidc.py:84 +#: settings/serializers/auth/oidc.py:89 msgid "Use nonce" msgstr "临时使用" -#: settings/serializers/auth/oidc.py:86 settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oidc.py:91 settings/serializers/auth/saml2.py:33 msgid "Always update user" msgstr "总是更新用户信息" @@ -5697,7 +5707,7 @@ msgstr "组织角色" #: users/serializers/user.py:79 #: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:22 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" msgstr "密码策略" @@ -6069,7 +6079,7 @@ msgid "Replace (The key generated by JumpServer) " msgstr "替换 (由 JumpServer 生成的密钥)" #: xpack/plugins/change_auth_plan/models/asset.py:49 -#: xpack/plugins/change_auth_plan/serializers/asset.py:35 +#: xpack/plugins/change_auth_plan/serializers/asset.py:36 msgid "SSH Key strategy" msgstr "SSH 密钥策略" @@ -6161,23 +6171,23 @@ msgstr "" "{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" "密密码" -#: xpack/plugins/change_auth_plan/serializers/asset.py:32 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "Change Password" msgstr "更改密码" -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +#: xpack/plugins/change_auth_plan/serializers/asset.py:34 msgid "Change SSH Key" msgstr "修改 SSH Key" -#: xpack/plugins/change_auth_plan/serializers/base.py:43 +#: xpack/plugins/change_auth_plan/serializers/base.py:48 msgid "Run times" msgstr "执行次数" -#: xpack/plugins/change_auth_plan/serializers/base.py:57 +#: xpack/plugins/change_auth_plan/serializers/base.py:62 msgid "* Please enter the correct password length" msgstr "* 请输入正确的密码长度" -#: xpack/plugins/change_auth_plan/serializers/base.py:60 +#: xpack/plugins/change_auth_plan/serializers/base.py:65 msgid "* Password length range 6-30 bits" msgstr "* 密码长度范围 6-30 位" @@ -6265,31 +6275,35 @@ msgstr "OpenStack" msgid "Google Cloud Platform" msgstr "谷歌云" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:23 +msgid "Fusion Compute" +msgstr "" + +#: xpack/plugins/cloud/const.py:27 msgid "Instance name" msgstr "实例名称" -#: xpack/plugins/cloud/const.py:27 +#: xpack/plugins/cloud/const.py:28 msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/const.py:32 +#: xpack/plugins/cloud/const.py:33 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "Unsync" msgstr "未同步" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "New Sync" msgstr "新同步" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Synced" msgstr "已同步" -#: xpack/plugins/cloud/const.py:39 +#: xpack/plugins/cloud/const.py:40 msgid "Released" msgstr "已释放" @@ -6563,11 +6577,11 @@ msgstr "华南-广州-友好用户环境" msgid "CN East-Suqian" msgstr "华东-宿迁" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Validity display" msgstr "有效性显示" -#: xpack/plugins/cloud/serializers/account.py:61 +#: xpack/plugins/cloud/serializers/account.py:62 msgid "Provider display" msgstr "服务商显示" @@ -6585,6 +6599,7 @@ msgstr "订阅 ID" #: xpack/plugins/cloud/serializers/account_attrs.py:93 #: xpack/plugins/cloud/serializers/account_attrs.py:98 +#: xpack/plugins/cloud/serializers/account_attrs.py:122 msgid "API Endpoint" msgstr "API 端点" From 810c5004020184df59eab5b77073e99897853cc1 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 31 May 2022 16:20:33 +0800 Subject: [PATCH 142/258] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=20CONNECTION=5FTOKEN=5FEXPIRATION?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 38 +++++++++++---------- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings/auth.py | 2 ++ 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index ef69890e5..acf8dea84 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -18,6 +18,7 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework import serializers +from django.conf import settings from applications.models import Application from authentication.signals import post_auth_failed @@ -361,23 +362,7 @@ class TokenCacheMixin: """ endpoint smart view 用到此类来解析token中的资产、应用 """ CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}' - def get_token_cache_key(self, token): - return self.CACHE_KEY_PREFIX.format(token) - - def get_token_ttl(self, token): - key = self.get_token_cache_key(token) - return cache.ttl(key) - - def set_token_to_cache(self, token, value, ttl=5 * 60): - key = self.get_token_cache_key(token) - cache.set(key, value, timeout=ttl) - - def get_token_from_cache(self, token): - key = self.get_token_cache_key(token) - value = cache.get(key, None) - return value - - def renewal_token(self, token, ttl=5 * 60): + def renewal_token(self, token, ttl=None): value = self.get_token_from_cache(token) if value: pre_ttl = self.get_token_ttl(token) @@ -394,6 +379,23 @@ class TokenCacheMixin: } return data + def get_token_ttl(self, token): + key = self.get_token_cache_key(token) + return cache.ttl(key) + + def set_token_to_cache(self, token, value, ttl=None): + key = self.get_token_cache_key(token) + ttl = ttl or settings.CONNECTION_TOKEN_EXPIRATION + cache.set(key, value, timeout=ttl) + + def get_token_from_cache(self, token): + key = self.get_token_cache_key(token) + value = cache.get(key, None) + return value + + def get_token_cache_key(self, token): + return self.CACHE_KEY_PREFIX.format(token) + class BaseUserConnectionTokenViewSet( RootOrgViewMixin, SerializerMixin, ClientProtocolMixin, @@ -415,7 +417,7 @@ class BaseUserConnectionTokenViewSet( raise PermissionDenied(error) return True - def create_token(self, user, asset, application, system_user, ttl=5 * 60): + def create_token(self, user, asset, application, system_user, ttl=None): self.check_resource_permission(user, asset, application, system_user) token = random_string(36) secret = random_string(16) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 85c3b4598..93aa46806 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -161,6 +161,7 @@ class Config(dict): 'SESSION_COOKIE_AGE': 3600 * 24, 'SESSION_EXPIRE_AT_BROWSER_CLOSE': False, 'LOGIN_URL': reverse_lazy('authentication:login'), + 'CONNECTION_TOKEN_EXPIRATION': 5 * 60, # Custom Config # Auth LDAP settings diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 010e92f31..2a293cb48 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -149,6 +149,8 @@ AUTH_TEMP_TOKEN = CONFIG.AUTH_TEMP_TOKEN # Other setting TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS +# Connection token +CONNECTION_TOKEN_EXPIRATION = CONFIG.CONNECTION_TOKEN_EXPIRATION RBAC_BACKEND = 'rbac.backends.RBACBackend' From 3bc307d666d22a4d09dbcb9a9712c31efa5d7a03 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 1 Jun 2022 18:00:22 +0800 Subject: [PATCH 143/258] =?UTF-8?q?perf:=20=E8=AE=BE=E7=BD=AEConnection=20?= =?UTF-8?q?Token=20=E9=BB=98=E8=AE=A4=E6=9C=80=E5=B0=915=E5=88=86=E9=92=9F?= =?UTF-8?q?=20(#8331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/auth.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 2a293cb48..95a12d01f 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -151,6 +151,9 @@ TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS # Connection token CONNECTION_TOKEN_EXPIRATION = CONFIG.CONNECTION_TOKEN_EXPIRATION +if CONNECTION_TOKEN_EXPIRATION < 5 * 60: + # 最少5分钟 + CONNECTION_TOKEN_EXPIRATION = 5 * 60 RBAC_BACKEND = 'rbac.backends.RBACBackend' From e096244e75fbebbd96f7bb02acc342af2f99ab01 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 6 Jun 2022 13:30:04 +0800 Subject: [PATCH 144/258] =?UTF-8?q?pref:=20app=20tree=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user_permission_nodes_with_assets.py | 5 ++++- apps/perms/tree/app.py | 1 + apps/perms/utils/asset/user_permission.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py b/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py index 5d3940c9f..4d4408355 100644 --- a/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py +++ b/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py @@ -128,6 +128,7 @@ class GrantedNodeChildrenWithAssetsAsTreeApiMixin(SerializeToTreeNodeMixin, nodes = PermNode.objects.none() assets = Asset.objects.none() + all_tree_nodes = [] if not key: nodes = nodes_query_utils.get_top_level_nodes() @@ -142,7 +143,9 @@ class GrantedNodeChildrenWithAssetsAsTreeApiMixin(SerializeToTreeNodeMixin, tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True) tree_assets = self.serialize_assets(assets, key) - return Response(data=[*tree_nodes, *tree_assets]) + all_tree_nodes.extend(tree_nodes) + all_tree_nodes.extend(tree_assets) + return Response(data=all_tree_nodes) class UserGrantedNodeChildrenWithAssetsAsTreeApi(AssetRoleAdminMixin, GrantedNodeChildrenWithAssetsAsTreeApiMixin): diff --git a/apps/perms/tree/app.py b/apps/perms/tree/app.py index e46d05473..cc8611938 100644 --- a/apps/perms/tree/app.py +++ b/apps/perms/tree/app.py @@ -28,6 +28,7 @@ class GrantedAppTreeUtil: 'title': name, 'pId': '', 'open': True, + 'iconSkin': 'applications', 'isParent': True, 'meta': { 'type': 'root' diff --git a/apps/perms/utils/asset/user_permission.py b/apps/perms/utils/asset/user_permission.py index 5baa93d01..dd15c0b38 100644 --- a/apps/perms/utils/asset/user_permission.py +++ b/apps/perms/utils/asset/user_permission.py @@ -5,6 +5,7 @@ import time from django.core.cache import cache from django.conf import settings from django.db.models import Q, QuerySet +from django.utils.translation import gettext as _ from common.db.models import output_as_string, UnionQuerySet from common.utils.common import lazyproperty, timeit @@ -614,6 +615,22 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase): assets_amount = assets_query_utils.get_favorite_assets().values_list('id').count() return PermNode.get_favorite_node(assets_amount) + @staticmethod + def get_root_node(): + name = _('My assets') + node = { + 'id': '', + 'name': name, + 'title': name, + 'pId': '', + 'open': True, + 'isParent': True, + 'meta': { + 'type': 'root' + } + } + return node + def get_special_nodes(self): nodes = [] if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: From dade0cadda9364aa6c40b0d7e621dff9c5f5557a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 6 Jun 2022 15:51:17 +0800 Subject: [PATCH 145/258] =?UTF-8?q?feat:=20=E5=85=8B=E9=9A=86=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rbac/api/role.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py index f398f0315..44cb899db 100644 --- a/apps/rbac/api/role.py +++ b/apps/rbac/api/role.py @@ -39,6 +39,21 @@ class RoleViewSet(PaginatedResponseMixin, JMSModelViewSet): raise PermissionDenied(error) return super().perform_destroy(instance) + def perform_create(self, serializer): + super(RoleViewSet, self).perform_create(serializer) + self.set_permissions_if_need(serializer.instance) + + def set_permissions_if_need(self, instance): + if not isinstance(instance, Role): + return + clone_from = self.request.query_params.get('clone_from') + if not clone_from: + return + clone = Role.objects.filter(id=clone_from).first() + if not clone: + return + instance.permissions.set(clone.permissions.all()) + def perform_update(self, serializer): instance = serializer.instance if instance.builtin: From 2366f02d10fd48b974e9ff2145f14ce5a81e8872 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 6 Jun 2022 18:04:11 +0800 Subject: [PATCH 146/258] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=B1=BB=E5=9E=8B=20razor=20=E5=B9=B6=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=20XRDP=5FENABLED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 3 +-- apps/jumpserver/settings/custom.py | 2 +- .../migrations/0006_remove_setting_enabled.py | 23 +++++++++++++++++++ apps/settings/serializers/public.py | 2 +- apps/settings/serializers/terminal.py | 2 +- apps/terminal/const.py | 1 + .../migrations/0048_endpoint_endpointrule.py | 6 ++--- .../migrations/0050_auto_20220606_1745.py | 18 +++++++++++++++ 8 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 apps/settings/migrations/0006_remove_setting_enabled.py create mode 100644 apps/terminal/migrations/0050_auto_20220606_1745.py diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 93aa46806..df8b5f287 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -324,8 +324,7 @@ class Config(dict): # 保留(Luna还在用) 'TERMINAL_MAGNUS_ENABLED': True, 'TERMINAL_KOKO_SSH_ENABLED': True, - # 保留(Luna还在用) - 'XRDP_ENABLED': True, + 'TERMINAL_RAZOR_ENABLED': True, # 安全配置 'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启 diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index b2768f2d8..9f645617b 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -139,7 +139,7 @@ LOGIN_REDIRECT_MSG_ENABLED = CONFIG.LOGIN_REDIRECT_MSG_ENABLED CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS = CONFIG.CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS -XRDP_ENABLED = CONFIG.XRDP_ENABLED +TERMINAL_RAZOR_ENABLED = CONFIG.TERMINAL_RAZOR_ENABLED TERMINAL_MAGNUS_ENABLED = CONFIG.TERMINAL_MAGNUS_ENABLED TERMINAL_KOKO_SSH_ENABLED = CONFIG.TERMINAL_KOKO_SSH_ENABLED diff --git a/apps/settings/migrations/0006_remove_setting_enabled.py b/apps/settings/migrations/0006_remove_setting_enabled.py new file mode 100644 index 000000000..0982bc0f2 --- /dev/null +++ b/apps/settings/migrations/0006_remove_setting_enabled.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.14 on 2022-06-06 09:45 + +from django.db import migrations + + +def migrate_terminal_razor_enabled(apps, schema_editor): + setting_model = apps.get_model("settings", "Setting") + s = setting_model.objects.filter(name='XRDP_ENABLED').first() + if not s: + return + s.name = 'TERMINAL_RAZOR_ENABLED' + s.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('settings', '0005_auto_20220310_0616'), + ] + + operations = [ + migrations.RunPython(migrate_terminal_razor_enabled), + ] diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index 22fe55142..7e9f46b7d 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -35,7 +35,7 @@ class PrivateSettingSerializer(PublicSettingSerializer): AUTH_FEISHU = serializers.BooleanField() AUTH_TEMP_TOKEN = serializers.BooleanField() - XRDP_ENABLED = serializers.BooleanField() + TERMINAL_RAZOR_ENABLED = serializers.BooleanField() TERMINAL_MAGNUS_ENABLED = serializers.BooleanField() TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField() diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index f6799eca1..1b70fadf3 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -34,5 +34,5 @@ class TerminalSettingSerializer(serializers.Serializer): "if you cannot log in to the device through Telnet, set this parameter") ) TERMINAL_MAGNUS_ENABLED = serializers.BooleanField(label=_("Enable database proxy")) - XRDP_ENABLED = serializers.BooleanField(label=_("Enable XRDP")) + TERMINAL_RAZOR_ENABLED = serializers.BooleanField(label=_("Enable Razor")) TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField(label=_("Enable SSH Client")) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 931a3e887..7289f3180 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -49,6 +49,7 @@ class TerminalTypeChoices(TextChoices): core = 'core', 'Core' celery = 'celery', 'Celery' magnus = 'magnus', 'Magnus' + razor = 'razor', 'Razor' @classmethod def types(cls): diff --git a/apps/terminal/migrations/0048_endpoint_endpointrule.py b/apps/terminal/migrations/0048_endpoint_endpointrule.py index ccdd59564..1ea7c3d03 100644 --- a/apps/terminal/migrations/0048_endpoint_endpointrule.py +++ b/apps/terminal/migrations/0048_endpoint_endpointrule.py @@ -21,7 +21,7 @@ def migrate_endpoints(apps, schema_editor): } Endpoint.objects.create(**default_data) - if not settings.XRDP_ENABLED: + if not settings.TERMINAL_RAZOR_ENABLED: return # migrate xrdp xrdp_addr = settings.TERMINAL_RDP_ADDR @@ -41,7 +41,7 @@ def migrate_endpoints(apps, schema_editor): else: rdp_port = 3389 xrdp_data = { - 'name': 'XRDP', + 'name': 'Razor', 'host': host, 'https_port': 0, 'http_port': 0, @@ -56,7 +56,7 @@ def migrate_endpoints(apps, schema_editor): EndpointRule = apps.get_model("terminal", "EndpointRule") xrdp_rule_data = { - 'name': 'XRDP', + 'name': 'Razor', 'ip_group': ['*'], 'priority': 20, 'endpoint': xrdp_endpoint, diff --git a/apps/terminal/migrations/0050_auto_20220606_1745.py b/apps/terminal/migrations/0050_auto_20220606_1745.py new file mode 100644 index 000000000..88e7cc138 --- /dev/null +++ b/apps/terminal/migrations/0050_auto_20220606_1745.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2022-06-06 09:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0049_endpoint_redis_port'), + ] + + operations = [ + migrations.AlterField( + model_name='terminal', + name='type', + field=models.CharField(choices=[('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'), ('xrdp', 'Xrdp'), ('lion', 'Lion'), ('core', 'Core'), ('celery', 'Celery'), ('magnus', 'Magnus'), ('razor', 'Razor')], default='koko', max_length=64, verbose_name='type'), + ), + ] From a5acdb9f607c472308347a4494013b407611ccb5 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 7 Jun 2022 19:26:07 +0800 Subject: [PATCH 147/258] =?UTF-8?q?perf:=20=E7=BB=9F=E4=B8=80=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E5=BD=93=E5=89=8D=E7=94=A8=E6=88=B7api=20(#8324)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/audits/signal_handlers.py | 1 + apps/authentication/api/__init__.py | 1 + apps/authentication/api/confirm.py | 85 +++++++++++++++++++++ apps/authentication/api/dingtalk.py | 5 +- apps/authentication/api/feishu.py | 4 +- apps/authentication/api/wecom.py | 5 +- apps/authentication/const.py | 8 ++ apps/authentication/serializers/__init__.py | 1 + apps/authentication/serializers/confirm.py | 11 +++ apps/authentication/urls/api_urls.py | 1 + apps/authentication/views/dingtalk.py | 10 +-- apps/authentication/views/feishu.py | 9 +-- apps/authentication/views/wecom.py | 9 +-- apps/users/models/user.py | 5 ++ apps/users/permissions.py | 11 ++- apps/users/utils.py | 13 ++++ 16 files changed, 148 insertions(+), 31 deletions(-) create mode 100644 apps/authentication/api/confirm.py create mode 100644 apps/authentication/serializers/confirm.py diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 5177cfca7..86a593c41 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -274,6 +274,7 @@ def on_user_auth_success(sender, user, request, login_type=None, **kwargs): logger.debug('User login success: {}'.format(user.username)) check_different_city_login_if_need(user, request) data = generate_data(user.username, request, login_type=login_type) + request.session['login_time'] = data['datetime'].strftime("%Y-%m-%d %H:%M:%S") data.update({'mfa': int(user.mfa_enabled), 'status': True}) write_login_log(**data) diff --git a/apps/authentication/api/__init__.py b/apps/authentication/api/__init__.py index 01a4c52e9..85fda3c1e 100644 --- a/apps/authentication/api/__init__.py +++ b/apps/authentication/api/__init__.py @@ -5,6 +5,7 @@ from .connection_token import * from .token import * from .mfa import * from .access_key import * +from .confirm import * from .login_confirm import * from .sso import * from .wecom import * diff --git a/apps/authentication/api/confirm.py b/apps/authentication/api/confirm.py new file mode 100644 index 000000000..c77a1d533 --- /dev/null +++ b/apps/authentication/api/confirm.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +import time +from datetime import datetime + +from django.utils import timezone +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ +from rest_framework.generics import ListCreateAPIView +from rest_framework.response import Response + +from common.permissions import IsValidUser +from ..mfa import MFAOtp +from ..const import ConfirmType +from ..mixins import authenticate +from ..serializers import ConfirmSerializer + + +class ConfirmViewSet(ListCreateAPIView): + permission_classes = (IsValidUser,) + serializer_class = ConfirmSerializer + + def check(self, confirm_type: str): + if confirm_type == ConfirmType.MFA: + return bool(MFAOtp(self.user).is_active()) + + if confirm_type == ConfirmType.PASSWORD: + return self.user.is_password_authenticate() + + if confirm_type == ConfirmType.RELOGIN: + return not self.user.is_password_authenticate() + + def authenticate(self, confirm_type, secret_key): + if confirm_type == ConfirmType.MFA: + ok, msg = MFAOtp(self.user).check_code(secret_key) + return ok, msg + + if confirm_type == ConfirmType.PASSWORD: + ok = authenticate(self.request, username=self.user.username, password=secret_key) + msg = '' if ok else _('Authentication failed password incorrect') + return ok, msg + + if confirm_type == ConfirmType.RELOGIN: + now = timezone.now().strftime("%Y-%m-%d %H:%M:%S") + now = datetime.strptime(now, '%Y-%m-%d %H:%M:%S') + login_time = self.request.session.get('login_time') + SPECIFIED_TIME = 5 + msg = _('Login time has exceeded {} minutes, please login again').format(SPECIFIED_TIME) + if not login_time: + return False, msg + login_time = datetime.strptime(login_time, '%Y-%m-%d %H:%M:%S') + if (now - login_time).seconds >= SPECIFIED_TIME * 60: + return False, msg + return True, '' + + @property + def user(self): + return self.request.user + + def list(self, request, *args, **kwargs): + if not settings.SECURITY_VIEW_AUTH_NEED_MFA: + return Response('ok') + + mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0) + if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL: + return Response('ok') + + data = [] + for i, confirm_type in enumerate(ConfirmType.values, 1): + if self.check(confirm_type): + data.append({'name': confirm_type, 'level': i}) + msg = _('This action require verify your MFA') + return Response({'error': msg, 'backends': data}, status=400) + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + validated_data = serializer.validated_data + confirm_type = validated_data.get('confirm_type') + secret_key = validated_data.get('secret_key') + ok, msg = self.authenticate(confirm_type, secret_key) + if ok: + request.session["MFA_VERIFY_TIME"] = int(time.time()) + return Response('ok') + return Response({'error': msg}, status=400) diff --git a/apps/authentication/api/dingtalk.py b/apps/authentication/api/dingtalk.py index da03f015b..ccc3db64d 100644 --- a/apps/authentication/api/dingtalk.py +++ b/apps/authentication/api/dingtalk.py @@ -2,7 +2,7 @@ from rest_framework.views import APIView from rest_framework.request import Request from rest_framework.response import Response -from users.permissions import IsAuthPasswdTimeValid +from users.permissions import IsAuthConfirmTimeValid from users.models import User from common.utils import get_logger from common.mixins.api import RoleUserMixin, RoleAdminMixin @@ -26,9 +26,8 @@ class DingTalkQRUnBindBase(APIView): class DingTalkQRUnBindForUserApi(RoleUserMixin, DingTalkQRUnBindBase): - permission_classes = (IsAuthPasswdTimeValid,) + permission_classes = (IsAuthConfirmTimeValid,) class DingTalkQRUnBindForAdminApi(RoleAdminMixin, DingTalkQRUnBindBase): user_id_url_kwarg = 'user_id' - \ No newline at end of file diff --git a/apps/authentication/api/feishu.py b/apps/authentication/api/feishu.py index aaed60db9..13637b247 100644 --- a/apps/authentication/api/feishu.py +++ b/apps/authentication/api/feishu.py @@ -2,7 +2,7 @@ from rest_framework.views import APIView from rest_framework.request import Request from rest_framework.response import Response -from users.permissions import IsAuthPasswdTimeValid +from users.permissions import IsAuthConfirmTimeValid from users.models import User from common.utils import get_logger from common.mixins.api import RoleUserMixin, RoleAdminMixin @@ -26,7 +26,7 @@ class FeiShuQRUnBindBase(APIView): class FeiShuQRUnBindForUserApi(RoleUserMixin, FeiShuQRUnBindBase): - permission_classes = (IsAuthPasswdTimeValid,) + permission_classes = (IsAuthConfirmTimeValid,) class FeiShuQRUnBindForAdminApi(RoleAdminMixin, FeiShuQRUnBindBase): diff --git a/apps/authentication/api/wecom.py b/apps/authentication/api/wecom.py index 486efde21..e64bc9919 100644 --- a/apps/authentication/api/wecom.py +++ b/apps/authentication/api/wecom.py @@ -2,7 +2,7 @@ from rest_framework.views import APIView from rest_framework.request import Request from rest_framework.response import Response -from users.permissions import IsAuthPasswdTimeValid +from users.permissions import IsAuthConfirmTimeValid from users.models import User from common.utils import get_logger from common.mixins.api import RoleUserMixin, RoleAdminMixin @@ -26,9 +26,8 @@ class WeComQRUnBindBase(APIView): class WeComQRUnBindForUserApi(RoleUserMixin, WeComQRUnBindBase): - permission_classes = (IsAuthPasswdTimeValid,) + permission_classes = (IsAuthConfirmTimeValid,) class WeComQRUnBindForAdminApi(RoleAdminMixin, WeComQRUnBindBase): user_id_url_kwarg = 'user_id' - \ No newline at end of file diff --git a/apps/authentication/const.py b/apps/authentication/const.py index f5cf56471..4c8578dff 100644 --- a/apps/authentication/const.py +++ b/apps/authentication/const.py @@ -1,2 +1,10 @@ +from django.db.models import TextChoices + RSA_PRIVATE_KEY = 'rsa_private_key' RSA_PUBLIC_KEY = 'rsa_public_key' + + +class ConfirmType(TextChoices): + RELOGIN = 'relogin', 'Re-Login' + PASSWORD = 'password', 'Password' + MFA = 'mfa', 'MFA' diff --git a/apps/authentication/serializers/__init__.py b/apps/authentication/serializers/__init__.py index 7697c46db..aa94f0fc8 100644 --- a/apps/authentication/serializers/__init__.py +++ b/apps/authentication/serializers/__init__.py @@ -1,3 +1,4 @@ from .token import * from .connect_token import * from .password_mfa import * +from .confirm import * diff --git a/apps/authentication/serializers/confirm.py b/apps/authentication/serializers/confirm.py new file mode 100644 index 000000000..fe5984190 --- /dev/null +++ b/apps/authentication/serializers/confirm.py @@ -0,0 +1,11 @@ +from rest_framework import serializers + +from common.drf.fields import EncryptedField +from ..const import ConfirmType + + +class ConfirmSerializer(serializers.Serializer): + confirm_type = serializers.ChoiceField( + required=True, choices=ConfirmType.choices + ) + secret_key = EncryptedField() diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index 754b7c793..44a89bf97 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -26,6 +26,7 @@ urlpatterns = [ path('feishu/event/subscription/callback/', api.FeiShuEventSubscriptionCallback.as_view(), name='feishu-event-subscription-callback'), path('auth/', api.TokenCreateApi.as_view(), name='user-auth'), + path('confirm/', api.ConfirmViewSet.as_view(), name='user-confirm'), path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'), path('mfa/verify/', api.MFAChallengeVerifyApi.as_view(), name='mfa-verify'), path('mfa/challenge/', api.MFAChallengeVerifyApi.as_view(), name='mfa-challenge'), diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index e97424aee..6b93b3d4c 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -9,8 +9,9 @@ from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.exceptions import APIException from users.views import UserVerifyPasswordView -from users.utils import is_auth_password_time_valid +from users.utils import is_auth_confirm_time_valid from users.models import User +from users.permissions import IsAuthConfirmTimeValid from common.utils import get_logger, FlashMessageUtil from common.utils.random import random_string from common.utils.django import reverse, get_object_or_none @@ -118,17 +119,12 @@ class DingTalkOAuthMixin(DingTalkBaseMixin, View): class DingTalkQRBindView(DingTalkQRMixin, View): - permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated, IsAuthConfirmTimeValid) def get(self, request: HttpRequest): user = request.user redirect_url = request.GET.get('redirect_url') - if not is_auth_password_time_valid(request.session): - msg = _('Please verify your password first') - response = self.get_failed_response(redirect_url, msg, msg) - return response - redirect_uri = reverse('authentication:dingtalk-qr-bind-callback', kwargs={'user_id': user.id}, external=True) redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) diff --git a/apps/authentication/views/feishu.py b/apps/authentication/views/feishu.py index 172aa2603..ea0db44e1 100644 --- a/apps/authentication/views/feishu.py +++ b/apps/authentication/views/feishu.py @@ -8,7 +8,7 @@ from django.db.utils import IntegrityError from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.exceptions import APIException -from users.utils import is_auth_password_time_valid +from users.permissions import IsAuthConfirmTimeValid from users.views import UserVerifyPasswordView from users.models import User from common.utils import get_logger, FlashMessageUtil @@ -89,17 +89,12 @@ class FeiShuQRMixin(PermissionsMixin, View): class FeiShuQRBindView(FeiShuQRMixin, View): - permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated, IsAuthConfirmTimeValid) def get(self, request: HttpRequest): user = request.user redirect_url = request.GET.get('redirect_url') - if not is_auth_password_time_valid(request.session): - msg = _('Please verify your password first') - response = self.get_failed_response(redirect_url, msg, msg) - return response - redirect_uri = reverse('authentication:feishu-qr-bind-callback', external=True) redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index 9b7963de8..cb5cc2178 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -9,8 +9,8 @@ from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.exceptions import APIException from users.views import UserVerifyPasswordView -from users.utils import is_auth_password_time_valid from users.models import User +from users.permissions import IsAuthConfirmTimeValid from common.utils import get_logger, FlashMessageUtil from common.utils.random import random_string from common.utils.django import reverse, get_object_or_none @@ -118,17 +118,12 @@ class WeComOAuthMixin(WeComBaseMixin, View): class WeComQRBindView(WeComQRMixin, View): - permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated, IsAuthConfirmTimeValid) def get(self, request: HttpRequest): user = request.user redirect_url = request.GET.get('redirect_url') - if not is_auth_password_time_valid(request.session): - msg = _('Please verify your password first') - response = self.get_failed_response(redirect_url, msg, msg) - return response - redirect_uri = reverse('authentication:wecom-qr-bind-callback', kwargs={'user_id': user.id}, external=True) redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index c22be9523..13c5edb0f 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -791,6 +791,11 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): def is_local(self): return self.source == self.Source.local.value + def is_password_authenticate(self): + cas = self.Source.cas + saml2 = self.Source.saml2 + return self.source not in [cas, saml2] + def set_unprovide_attr_if_need(self): if not self.name: self.name = self.username diff --git a/apps/users/permissions.py b/apps/users/permissions.py index 03534d211..a0df71116 100644 --- a/apps/users/permissions.py +++ b/apps/users/permissions.py @@ -1,10 +1,17 @@ from rest_framework import permissions -from .utils import is_auth_password_time_valid +from .utils import is_auth_password_time_valid, is_auth_confirm_time_valid class IsAuthPasswdTimeValid(permissions.IsAuthenticated): def has_permission(self, request, view): return super().has_permission(request, view) \ - and is_auth_password_time_valid(request.session) + and is_auth_password_time_valid(request.session) + + +class IsAuthConfirmTimeValid(permissions.IsAuthenticated): + + def has_permission(self, request, view): + return super().has_permission(request, view) \ + and is_auth_confirm_time_valid(request.session) diff --git a/apps/users/utils.py b/apps/users/utils.py index a2fb9afac..60485f497 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -255,3 +255,16 @@ def is_auth_password_time_valid(session): def is_auth_otp_time_valid(session): return is_auth_time_valid(session, 'auth_otp_expired_at') + + +def is_confirm_time_valid(session, key): + if not settings.SECURITY_VIEW_AUTH_NEED_MFA: + return True + mfa_verify_time = session.get(key, 0) + if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL: + return True + return False + + +def is_auth_confirm_time_valid(session): + return is_confirm_time_valid(session, 'MFA_VERIFY_TIME') From c6949b4f685fdc20d917ca0684bfbcd7acbce359 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 8 Jun 2022 10:02:47 +0800 Subject: [PATCH 148/258] =?UTF-8?q?perf:=20=E5=8E=BB=E6=8E=89=20remote=20a?= =?UTF-8?q?pp=20=E7=9A=84=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index acf8dea84..828a93be5 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -161,7 +161,6 @@ class ClientProtocolMixin: options['alternate shell:s'] = app options['remoteapplicationprogram:s'] = app options['remoteapplicationname:s'] = name - options['remoteapplicationcmdline:s'] = '- ' + self.get_encrypt_cmdline(application) else: name = '*' From 81ef61482090ef9d1f31482f38be9126c02d2512 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 8 Jun 2022 19:32:50 +0800 Subject: [PATCH 149/258] =?UTF-8?q?fix:=20relogin=E9=87=8D=E7=BD=AEMFA=5FV?= =?UTF-8?q?ERIFY=5FTIME=20(#8348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/audits/signal_handlers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 86a593c41..902c0896e 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # +import time + from django.db.models.signals import ( post_save, m2m_changed, pre_delete ) @@ -275,6 +277,7 @@ def on_user_auth_success(sender, user, request, login_type=None, **kwargs): check_different_city_login_if_need(user, request) data = generate_data(user.username, request, login_type=login_type) request.session['login_time'] = data['datetime'].strftime("%Y-%m-%d %H:%M:%S") + request.session["MFA_VERIFY_TIME"] = int(time.time()) data.update({'mfa': int(user.mfa_enabled), 'status': True}) write_login_log(**data) From a5c6ba6cd6c277052ccc3eca362a95728df0b2ca Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 9 Jun 2022 10:19:34 +0800 Subject: [PATCH 150/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20perm=20app?= =?UTF-8?q?=20node?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/tree/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/perms/tree/app.py b/apps/perms/tree/app.py index cc8611938..0b74c1e9b 100644 --- a/apps/perms/tree/app.py +++ b/apps/perms/tree/app.py @@ -64,7 +64,6 @@ class GrantedAppTreeUtil: return tree_nodes root_node = self.create_root_node() - tree_nodes.append(root_node) organizations = self.filter_organizations(applications) for i, org in enumerate(organizations): From 9d1e94d3c21695e410cb5753c8ab251d09e16ce4 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 10 Jun 2022 18:22:57 +0800 Subject: [PATCH 151/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E7=99=BB=E5=BD=95=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5RemoteApp=E5=BA=94=E7=94=A8=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E4=B8=8D=E5=88=B0=E8=AE=A4=E8=AF=81=E4=BF=A1=E6=81=AF=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/user.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index ce0029768..e20664071 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -133,6 +133,15 @@ class AuthMixin: self.password = password def load_app_more_auth(self, app_id=None, username=None, user_id=None): + # 清除认证信息 + self._clean_auth_info_if_manual_login_mode() + + # 先加载临时认证信息 + if self.login_mode == self.LOGIN_MANUAL: + self._load_tmp_auth_if_has(app_id, user_id) + return + + # Remote app from applications.models import Application app = get_object_or_none(Application, pk=app_id) if app and app.category_remote_app: @@ -141,11 +150,6 @@ class AuthMixin: return # Other app - self._clean_auth_info_if_manual_login_mode() - # 加载临时认证信息 - if self.login_mode == self.LOGIN_MANUAL: - self._load_tmp_auth_if_has(app_id, user_id) - return # 更新用户名 from users.models import User user = get_object_or_none(User, pk=user_id) if user_id else None From 3755f8f33acd1744b5ae3a62e518a2b8e73a48fd Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 13 Jun 2022 10:54:34 +0800 Subject: [PATCH 152/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E5=8A=A8=E6=80=81=E7=94=A8=E6=88=B7=20comment=20?= =?UTF-8?q?=E4=B8=AD=E5=8C=85=E5=90=AB=E7=A9=BA=E6=A0=BC=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks/push_system_user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py index 98d2a0922..ee4af0fd5 100644 --- a/apps/assets/tasks/push_system_user.py +++ b/apps/assets/tasks/push_system_user.py @@ -33,16 +33,17 @@ def _dump_args(args: dict): def get_push_unixlike_system_user_tasks(system_user, username=None, **kwargs): - comment = system_user.name algorithm = kwargs.get('algorithm') if username is None: username = system_user.username + comment = system_user.name if system_user.username_same_with_user: from users.models import User user = User.objects.filter(username=username).only('name', 'username').first() if user: comment = f'{system_user.name}[{str(user)}]' + comment = comment.replace(' ', '') password = system_user.password public_key = system_user.public_key From 0464b1a9e69c2ca407b213a797652460b2620159 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 13 Jun 2022 14:22:07 +0800 Subject: [PATCH 153/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=20rbac=20=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit perf: migrate --- apps/rbac/builtin.py | 30 ++++++---- .../migrations/0004_auto_20211201_1901.py | 53 +++++++++------- utils/test_run_migrations.py | 60 +++++++++++++++++++ 3 files changed, 110 insertions(+), 33 deletions(-) create mode 100644 utils/test_run_migrations.py diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index b736ba226..34733dc59 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -126,6 +126,8 @@ class BuiltinRole: org_user = PredefineRole( '7', ugettext_noop('OrgUser'), Scope.org, user_perms ) + system_role_mapper = None + org_role_mapper = None @classmethod def get_roles(cls): @@ -138,22 +140,24 @@ class BuiltinRole: @classmethod def get_system_role_by_old_name(cls, name): - mapper = { - 'App': cls.system_component, - 'Admin': cls.system_admin, - 'User': cls.system_user, - 'Auditor': cls.system_auditor - } - return mapper[name].get_role() + if not cls.system_role_mapper: + cls.system_role_mapper = { + 'App': cls.system_component.get_role(), + 'Admin': cls.system_admin.get_role(), + 'User': cls.system_user.get_role(), + 'Auditor': cls.system_auditor.get_role() + } + return cls.system_role_mapper[name] @classmethod def get_org_role_by_old_name(cls, name): - mapper = { - 'Admin': cls.org_admin, - 'User': cls.org_user, - 'Auditor': cls.org_auditor, - } - return mapper[name].get_role() + if not cls.org_role_mapper: + cls.org_role_mapper = { + 'Admin': cls.org_admin.get_role(), + 'User': cls.org_user.get_role(), + 'Auditor': cls.org_auditor.get_role(), + } + return cls.org_role_mapper[name] @classmethod def sync_to_db(cls, show_msg=False): diff --git a/apps/rbac/migrations/0004_auto_20211201_1901.py b/apps/rbac/migrations/0004_auto_20211201_1901.py index 36736f254..eafe37539 100644 --- a/apps/rbac/migrations/0004_auto_20211201_1901.py +++ b/apps/rbac/migrations/0004_auto_20211201_1901.py @@ -1,5 +1,4 @@ -# Generated by Django 3.1.13 on 2021-12-01 11:01 - +import time from django.db import migrations from rbac.builtin import BuiltinRole @@ -11,31 +10,45 @@ def migrate_system_role_binding(apps, schema_editor): role_binding_model = apps.get_model('rbac', 'SystemRoleBinding') users = user_model.objects.using(db_alias).all() - role_bindings = [] - for user in users: - role = BuiltinRole.get_system_role_by_old_name(user.role) - role_binding = role_binding_model(scope='system', user_id=user.id, role_id=role.id) - role_bindings.append(role_binding) - role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) + grouped_users = [users[i:i+1000] for i in range(0, len(users), 1000)] + for i, group in enumerate(grouped_users): + role_bindings = [] + start = time.time() + for user in group: + role = BuiltinRole.get_system_role_by_old_name(user.role) + role_binding = role_binding_model(scope='system', user_id=user.id, role_id=role.id) + role_bindings.append(role_binding) + role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) + print("Create role binding: {}-{} using: {:.2f}s".format( + i*1000, i*1000 + len(group), time.time()-start + )) def migrate_org_role_binding(apps, schema_editor): db_alias = schema_editor.connection.alias org_member_model = apps.get_model('orgs', 'OrganizationMember') role_binding_model = apps.get_model('rbac', 'RoleBinding') - members = org_member_model.objects.using(db_alias).all() + members = org_member_model.objects.using(db_alias)\ + .only('role', 'user_id', 'org_id')\ + .all() - role_bindings = [] - for member in members: - role = BuiltinRole.get_org_role_by_old_name(member.role) - role_binding = role_binding_model( - scope='org', - user_id=member.user.id, - role_id=role.id, - org_id=member.org.id - ) - role_bindings.append(role_binding) - role_binding_model.objects.bulk_create(role_bindings) + grouped_members = [members[i:i+1000] for i in range(0, len(members), 1000)] + for i, group in enumerate(grouped_members): + role_bindings = [] + start = time.time() + for member in group: + role = BuiltinRole.get_org_role_by_old_name(member.role) + role_binding = role_binding_model( + scope='org', + user_id=member.user_id, + role_id=role.id, + org_id=member.org_id + ) + role_bindings.append(role_binding) + role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) + print("Create role binding: {}-{} using: {:.2f}s".format( + i*1000, i*1000 + len(group), time.time()-start + )) class Migration(migrations.Migration): diff --git a/utils/test_run_migrations.py b/utils/test_run_migrations.py new file mode 100644 index 000000000..c45c46ec1 --- /dev/null +++ b/utils/test_run_migrations.py @@ -0,0 +1,60 @@ +# Generated by Django 3.1.13 on 2021-12-01 11:01 +import os +import sys +import django +import time + +app_path = '***** Change me *******' +sys.path.insert(0, app_path) +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") +django.setup() + +from django.apps import apps +from django.db import connection + +# ========================== 添加到需要测试的 migrations 上方 ========================== + + +from django.db import migrations + +from rbac.builtin import BuiltinRole + + +def migrate_some_binding(apps, schema_editor): + db_alias = schema_editor.connection.alias + user_model = apps.get_model('users', 'User') + role_binding_model = apps.get_model('rbac', 'SystemRoleBinding') + users = user_model.objects.using(db_alias).all() + + grouped_users = [users[i:i+1000] for i in range(0, len(users), 1000)] + for i, group in enumerate(grouped_users): + role_bindings = [] + start = time.time() + for user in group: + role = BuiltinRole.get_system_role_by_old_name(user.role) + role_binding = role_binding_model(scope='system', user_id=user.id, role_id=role.id) + role_bindings.append(role_binding) + role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) + print("Create role binding: {}-{} using: {:.2f}s".format( + i*1000, i*1000 + len(group), time.time()-start + )) + + +class Migration(migrations.Migration): + + dependencies = [ + ('rbac', '0003_auto_20211130_1037'), + ] + + operations = [ + migrations.RunPython(migrate_some_binding), + ] + + +# ================== 添加到下方 ====================== +def main(): + schema_editor = connection.schema_editor() + migrate_some_binding(apps, schema_editor) + + +# main() From 25ae790f7d14927d7f3268e6de772b1f1e35475b Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 13 Jun 2022 15:45:10 +0800 Subject: [PATCH 154/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9client=20?= =?UTF-8?q?=E7=89=88=E6=9C=AC=20(#8375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/templates/resource_download.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index 09215b532..57570eb8e 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -15,7 +15,7 @@ p {

    -

    JumpServer {% trans 'Client' %} v1.1.5

    +

    JumpServer {% trans 'Client' %} v1.1.6

    {% trans 'JumpServer Client, currently used to launch the client, now only support launch RDP SSH client, The Telnet client will next' %}

    From 95f8b12912a775104c6da2c5962be0cb908de8cb Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 13 Jun 2022 15:33:27 +0800 Subject: [PATCH 155/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=83=A8?= =?UTF-8?q?=E5=88=86=20password=20encrypted=20field=20extra=20kwargs=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E4=B8=8D=E7=94=9F=E6=95=88=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/account.py | 5 ----- apps/assets/serializers/base.py | 4 +++- apps/assets/serializers/system_user.py | 11 +++++------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index bc4bea563..5cbb26d88 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -5,7 +5,6 @@ from assets.models import AuthBook from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .base import AuthSerializerMixin -from .utils import validate_password_contains_left_double_curly_bracket from common.utils.encode import ssh_pubkey_gen from common.drf.serializers import SecretReadableMixin @@ -32,10 +31,6 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): fields = fields_small + fields_fk extra_kwargs = { 'username': {'required': True}, - 'password': { - 'write_only': True, - "validators": [validate_password_contains_left_double_curly_bracket] - }, 'private_key': {'write_only': True}, 'public_key': {'write_only': True}, 'systemuser_display': {'label': _('System user display')} diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index c20b1abb8..92cc65f19 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -8,6 +8,7 @@ from rest_framework import serializers from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key from common.drf.fields import EncryptedField from assets.models import Type +from .utils import validate_password_contains_left_double_curly_bracket class AuthSerializer(serializers.ModelSerializer): @@ -33,7 +34,8 @@ class AuthSerializer(serializers.ModelSerializer): class AuthSerializerMixin(serializers.ModelSerializer): password = EncryptedField( - label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024 + label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, + validators=[validate_password_contains_left_double_curly_bracket] ) private_key = EncryptedField( label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, max_length=4096 diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 278a99d87..68ade0ebd 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -25,6 +25,11 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): """ 系统用户 """ + password = EncryptedField( + label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, + trim_whitespace=False, validators=[validate_password_contains_left_double_curly_bracket], + write_only=True + ) auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) ssh_key_fingerprint = serializers.ReadOnlyField(label=_('SSH key fingerprint')) @@ -51,15 +56,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): fields_m2m = ['cmd_filters', 'assets_amount', 'applications_amount', 'nodes'] fields = fields_small + fields_m2m extra_kwargs = { - 'password': { - "write_only": True, - 'trim_whitespace': False, - "validators": [validate_password_contains_left_double_curly_bracket] - }, 'cmd_filters': {"required": False, 'label': _('Command filter')}, 'public_key': {"write_only": True}, 'private_key': {"write_only": True}, - 'token': {"write_only": True}, 'nodes_amount': {'label': _('Nodes amount')}, 'assets_amount': {'label': _('Assets amount')}, 'login_mode_display': {'label': _('Login mode display')}, From 556ce0a1463b9c5f5fe6779821aa2ff5c0e63eb1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 13 Jun 2022 16:26:36 +0800 Subject: [PATCH 156/258] =?UTF-8?q?perf:=20=E7=BB=A7=E7=BB=AD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=B8=80=E6=B3=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0004_auto_20211201_1901.py | 40 +++++++++++++------ utils/test_run_migrations.py | 24 +++++++---- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/apps/rbac/migrations/0004_auto_20211201_1901.py b/apps/rbac/migrations/0004_auto_20211201_1901.py index eafe37539..9d59d99fc 100644 --- a/apps/rbac/migrations/0004_auto_20211201_1901.py +++ b/apps/rbac/migrations/0004_auto_20211201_1901.py @@ -1,3 +1,5 @@ +# Generated by Django 3.1.13 on 2021-12-01 11:01 + import time from django.db import migrations @@ -8,35 +10,48 @@ def migrate_system_role_binding(apps, schema_editor): db_alias = schema_editor.connection.alias user_model = apps.get_model('users', 'User') role_binding_model = apps.get_model('rbac', 'SystemRoleBinding') - users = user_model.objects.using(db_alias).all() - grouped_users = [users[i:i+1000] for i in range(0, len(users), 1000)] - for i, group in enumerate(grouped_users): + count = 0 + bulk_size = 1000 + while True: + users = user_model.objects.using(db_alias) \ + .only('role', 'id') \ + .all()[count:count+bulk_size] + if not users: + break + role_bindings = [] start = time.time() - for user in group: + for user in users: role = BuiltinRole.get_system_role_by_old_name(user.role) role_binding = role_binding_model(scope='system', user_id=user.id, role_id=role.id) role_bindings.append(role_binding) + role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) print("Create role binding: {}-{} using: {:.2f}s".format( - i*1000, i*1000 + len(group), time.time()-start + count, count + len(users), time.time()-start )) + count += len(users) def migrate_org_role_binding(apps, schema_editor): db_alias = schema_editor.connection.alias org_member_model = apps.get_model('orgs', 'OrganizationMember') role_binding_model = apps.get_model('rbac', 'RoleBinding') - members = org_member_model.objects.using(db_alias)\ - .only('role', 'user_id', 'org_id')\ - .all() - grouped_members = [members[i:i+1000] for i in range(0, len(members), 1000)] - for i, group in enumerate(grouped_members): + count = 0 + bulk_size = 1000 + + while True: + members = org_member_model.objects.using(db_alias)\ + .only('role', 'user_id', 'org_id')\ + .all()[count:count+bulk_size] + if not members: + break role_bindings = [] start = time.time() - for member in group: + + for member in members: role = BuiltinRole.get_org_role_by_old_name(member.role) role_binding = role_binding_model( scope='org', @@ -47,8 +62,9 @@ def migrate_org_role_binding(apps, schema_editor): role_bindings.append(role_binding) role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) print("Create role binding: {}-{} using: {:.2f}s".format( - i*1000, i*1000 + len(group), time.time()-start + count, count + len(members), time.time()-start )) + count += len(members) class Migration(migrations.Migration): diff --git a/utils/test_run_migrations.py b/utils/test_run_migrations.py index c45c46ec1..33c7c3c5c 100644 --- a/utils/test_run_migrations.py +++ b/utils/test_run_migrations.py @@ -20,24 +20,32 @@ from django.db import migrations from rbac.builtin import BuiltinRole -def migrate_some_binding(apps, schema_editor): +def migrate_system_role_binding(apps, schema_editor): db_alias = schema_editor.connection.alias user_model = apps.get_model('users', 'User') role_binding_model = apps.get_model('rbac', 'SystemRoleBinding') - users = user_model.objects.using(db_alias).all() - grouped_users = [users[i:i+1000] for i in range(0, len(users), 1000)] - for i, group in enumerate(grouped_users): + count = 0 + bulk_size = 1000 + while True: + users = user_model.objects.using(db_alias) \ + .only('role', 'id') \ + .all()[count:count+bulk_size] + if not users: + break + role_bindings = [] start = time.time() - for user in group: + for user in users: role = BuiltinRole.get_system_role_by_old_name(user.role) role_binding = role_binding_model(scope='system', user_id=user.id, role_id=role.id) role_bindings.append(role_binding) + role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) print("Create role binding: {}-{} using: {:.2f}s".format( - i*1000, i*1000 + len(group), time.time()-start + count, count + len(users), time.time()-start )) + count += len(users) class Migration(migrations.Migration): @@ -47,14 +55,14 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(migrate_some_binding), + migrations.RunPython(migrate_system_role_binding), ] # ================== 添加到下方 ====================== def main(): schema_editor = connection.schema_editor() - migrate_some_binding(apps, schema_editor) + migrate_system_role_binding(apps, schema_editor) # main() From f91bfedc50d4701e65a6c062db83483b5917272e Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 14 Jun 2022 16:03:42 +0800 Subject: [PATCH 157/258] =?UTF-8?q?perf:=20=E6=8E=88=E6=9D=83=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 366 +++++++++--------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 353 ++++++++--------- apps/locale/zh/LC_MESSAGES/djangojs.po | 1 - apps/perms/notifications.py | 15 +- apps/perms/tasks.py | 64 ++- .../perms/_msg_item_permissions_expire.html | 2 +- .../perms/_msg_permed_items_expire.html | 2 +- 9 files changed, 430 insertions(+), 381 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 2f0a7842c..50947cea2 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82cdb805f6d681577806fb6fc41178b5dd28eafc68b1348f433ba7f7f5ce9920 -size 127478 +oid sha256:8a0e0ef94fd1cf5b3d41c378b54e8af2fb502c7f1e9d6a3e54084e8113978561 +size 127495 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 2d7a87994..11310178e 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-31 16:02+0800\n" +"POT-Creation-Date: 2022-06-14 15:48+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -38,12 +38,12 @@ msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:62 +#: assets/models/user.py:251 terminal/models/endpoint.py:62 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:63 +#: assets/models/user.py:251 terminal/models/endpoint.py:63 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" @@ -93,8 +93,8 @@ msgstr "ログイン確認" #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:883 -#: users/models/user.py:914 users/serializers/group.py:19 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:888 +#: users/models/user.py:919 users/serializers/group.py:19 msgid "User" msgstr "ユーザー" @@ -129,7 +129,7 @@ msgstr "システムユーザー" #: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:269 audits/models.py:39 +#: assets/serializers/system_user.py:268 audits/models.py:39 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 @@ -179,7 +179,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 #: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:210 assets/models/domain.py:60 -#: assets/serializers/account.py:14 +#: assets/serializers/account.py:13 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -188,7 +188,7 @@ msgid "IP" msgstr "IP" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 -#: assets/serializers/account.py:15 assets/serializers/gathered_user.py:23 +#: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 msgid "Hostname" msgstr "ホスト名" @@ -202,7 +202,7 @@ msgstr "" "ション: {}" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:62 assets/models/user.py:248 +#: assets/models/domain.py:62 assets/models/user.py:252 #: terminal/serializers/session.py:30 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "プロトコル" @@ -265,7 +265,7 @@ msgid "Application" msgstr "アプリケーション" #: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 +#: assets/models/cmd_filter.py:42 assets/models/user.py:342 audits/models.py:40 #: perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:35 terminal/models/session.py:48 @@ -303,7 +303,7 @@ msgstr "カテゴリ" #: applications/models/application.py:222 #: applications/serializers/application.py:101 assets/models/backup.py:49 -#: assets/models/cmd_filter.py:82 assets/models/user.py:246 +#: assets/models/cmd_filter.py:82 assets/models/user.py:250 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:57 terminal/models/storage.py:141 @@ -341,7 +341,7 @@ msgstr "カテゴリ表示" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:29 +#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 #: audits/serializers.py:29 perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 @@ -353,17 +353,17 @@ msgstr "タイプ表示" #: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:19 assets/serializers/cmd_filter.py:28 +#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 #: assets/serializers/cmd_filter.py:48 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:915 +#: users/models/group.py:18 users/models/user.py:920 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "作成された日付" #: applications/serializers/application.py:104 assets/models/base.py:182 -#: assets/models/gathered_user.py:20 assets/serializers/account.py:22 +#: assets/models/gathered_user.py:20 assets/serializers/account.py:21 #: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 #: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 @@ -413,7 +413,7 @@ msgid "Application path" msgstr "アプリケーションパス" #: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:168 +#: assets/serializers/system_user.py:167 #: xpack/plugins/change_auth_plan/serializers/asset.py:67 #: xpack/plugins/change_auth_plan/serializers/asset.py:70 #: xpack/plugins/change_auth_plan/serializers/asset.py:73 @@ -510,7 +510,7 @@ msgid "Internal" msgstr "内部" #: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:16 assets/serializers/asset.py:63 +#: assets/serializers/account.py:15 assets/serializers/asset.py:63 #: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "プラットフォーム" @@ -571,13 +571,13 @@ msgstr "システムアーキテクチャ" msgid "Hostname raw" msgstr "ホスト名生" -#: assets/models/asset.py:215 assets/serializers/account.py:17 +#: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" -#: assets/models/asset.py:218 assets/models/user.py:238 +#: assets/models/asset.py:218 assets/models/user.py:242 #: perms/models/asset_permission.py:24 #: xpack/plugins/change_auth_plan/models/asset.py:43 #: xpack/plugins/gathered_user/models.py:24 @@ -590,7 +590,7 @@ msgid "Is active" msgstr "アクティブです。" #: assets/models/asset.py:222 assets/models/cluster.py:19 -#: assets/models/user.py:235 assets/models/user.py:390 +#: assets/models/user.py:239 assets/models/user.py:394 msgid "Admin user" msgstr "管理ユーザー" @@ -759,9 +759,9 @@ msgstr "接続性" msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:177 assets/serializers/base.py:14 -#: assets/serializers/base.py:36 audits/signal_handlers.py:48 -#: authentication/forms.py:32 +#: assets/models/base.py:177 assets/serializers/base.py:15 +#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 +#: audits/signal_handlers.py:50 authentication/forms.py:32 #: authentication/templates/authentication/login.html:182 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 #: users/forms/profile.py:22 users/serializers/user.py:92 @@ -776,7 +776,7 @@ msgstr "確認済みの日付" msgid "Password" msgstr "パスワード" -#: assets/models/base.py:178 assets/serializers/base.py:39 +#: assets/models/base.py:178 assets/serializers/base.py:41 #: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:206 @@ -823,7 +823,7 @@ msgid "Default" msgstr "デフォルト" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:900 +#: users/models/user.py:905 msgid "System" msgstr "システム" @@ -961,7 +961,7 @@ msgstr "フルバリュー" msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:559 assets/serializers/system_user.py:268 +#: assets/models/node.py:559 assets/serializers/system_user.py:267 #: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "ノード" @@ -970,78 +970,78 @@ msgstr "ノード" msgid "Can match node" msgstr "ノードを一致させることができます" -#: assets/models/user.py:229 +#: assets/models/user.py:233 msgid "Automatic managed" msgstr "自動管理" -#: assets/models/user.py:230 +#: assets/models/user.py:234 msgid "Manually input" msgstr "手動入力" -#: assets/models/user.py:234 +#: assets/models/user.py:238 msgid "Common user" msgstr "共通ユーザー" -#: assets/models/user.py:237 +#: assets/models/user.py:241 msgid "Username same with user" msgstr "ユーザーと同じユーザー名" -#: assets/models/user.py:240 assets/serializers/domain.py:30 +#: assets/models/user.py:244 assets/serializers/domain.py:30 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:39 msgid "Assets" msgstr "資産" -#: assets/models/user.py:244 users/apps.py:9 +#: assets/models/user.py:248 users/apps.py:9 msgid "Users" msgstr "ユーザー" -#: assets/models/user.py:245 +#: assets/models/user.py:249 msgid "User groups" msgstr "ユーザーグループ" -#: assets/models/user.py:249 +#: assets/models/user.py:253 msgid "Auto push" msgstr "オートプッシュ" -#: assets/models/user.py:250 +#: assets/models/user.py:254 msgid "Sudo" msgstr "すど" -#: assets/models/user.py:251 +#: assets/models/user.py:255 msgid "Shell" msgstr "シェル" -#: assets/models/user.py:252 +#: assets/models/user.py:256 msgid "Login mode" msgstr "ログインモード" -#: assets/models/user.py:253 +#: assets/models/user.py:257 msgid "SFTP Root" msgstr "SFTPルート" -#: assets/models/user.py:254 assets/serializers/system_user.py:32 +#: assets/models/user.py:258 assets/serializers/system_user.py:37 #: authentication/models.py:49 msgid "Token" msgstr "トークン" -#: assets/models/user.py:255 +#: assets/models/user.py:259 msgid "Home" msgstr "ホーム" -#: assets/models/user.py:256 +#: assets/models/user.py:260 msgid "System groups" msgstr "システムグループ" -#: assets/models/user.py:259 +#: assets/models/user.py:263 msgid "User switch" msgstr "ユーザースイッチ" -#: assets/models/user.py:260 +#: assets/models/user.py:264 msgid "Switch from" msgstr "から切り替え" -#: assets/models/user.py:340 +#: assets/models/user.py:344 msgid "Can match system user" msgstr "システムユーザーに一致できます" @@ -1072,7 +1072,7 @@ msgstr "" "されていません-個人情報にアクセスしてください-> ファイル暗号化パスワードを設" "定してください暗号化パスワード" -#: assets/serializers/account.py:41 assets/serializers/account.py:84 +#: assets/serializers/account.py:36 assets/serializers/account.py:79 msgid "System user display" msgstr "システムユーザー表示" @@ -1127,15 +1127,15 @@ msgstr "定期的なパフォーマンス" msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/base.py:15 users/models/user.py:689 +#: assets/serializers/base.py:16 users/models/user.py:689 msgid "Private key" msgstr "ssh秘密鍵" -#: assets/serializers/base.py:43 +#: assets/serializers/base.py:45 msgid "Key password" msgstr "キーパスワード" -#: assets/serializers/base.py:56 +#: assets/serializers/base.py:58 msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" @@ -1148,7 +1148,7 @@ msgid "Pattern" msgstr "パターン" #: assets/serializers/domain.py:14 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:64 +#: assets/serializers/system_user.py:63 #: perms/serializers/asset/permission.py:49 msgid "Assets amount" msgstr "資産額" @@ -1173,78 +1173,78 @@ msgstr "含まれない:/" msgid "The same level node name cannot be the same" msgstr "同じレベルのノード名を同じにすることはできません。" -#: assets/serializers/system_user.py:30 +#: assets/serializers/system_user.py:35 msgid "SSH key fingerprint" msgstr "SSHキー指紋" -#: assets/serializers/system_user.py:35 +#: assets/serializers/system_user.py:40 #: perms/serializers/application/permission.py:46 msgid "Apps amount" msgstr "アプリの量" -#: assets/serializers/system_user.py:63 +#: assets/serializers/system_user.py:62 #: perms/serializers/asset/permission.py:50 msgid "Nodes amount" msgstr "ノード量" -#: assets/serializers/system_user.py:65 assets/serializers/system_user.py:270 +#: assets/serializers/system_user.py:64 assets/serializers/system_user.py:269 msgid "Login mode display" msgstr "ログインモード表示" -#: assets/serializers/system_user.py:67 +#: assets/serializers/system_user.py:66 msgid "Ad domain" msgstr "広告ドメイン" -#: assets/serializers/system_user.py:68 +#: assets/serializers/system_user.py:67 msgid "Is asset protocol" msgstr "資産プロトコルです" -#: assets/serializers/system_user.py:69 +#: assets/serializers/system_user.py:68 msgid "Only ssh and automatic login system users are supported" msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" -#: assets/serializers/system_user.py:109 +#: assets/serializers/system_user.py:108 msgid "Username same with user with protocol {} only allow 1" msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" -#: assets/serializers/system_user.py:122 common/validators.py:14 +#: assets/serializers/system_user.py:121 common/validators.py:14 msgid "Special char not allowed" msgstr "特別なcharは許可されていません" -#: assets/serializers/system_user.py:132 +#: assets/serializers/system_user.py:131 msgid "* Automatic login mode must fill in the username." msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" -#: assets/serializers/system_user.py:147 +#: assets/serializers/system_user.py:146 msgid "Path should starts with /" msgstr "パスは/で始まる必要があります" -#: assets/serializers/system_user.py:159 +#: assets/serializers/system_user.py:158 msgid "Password or private key required" msgstr "パスワードまたは秘密鍵が必要" -#: assets/serializers/system_user.py:173 +#: assets/serializers/system_user.py:172 msgid "Only ssh protocol system users are allowed" msgstr "Sshプロトコルシステムユーザーのみが許可されています" -#: assets/serializers/system_user.py:177 +#: assets/serializers/system_user.py:176 msgid "The protocol must be consistent with the current user: {}" msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" -#: assets/serializers/system_user.py:181 +#: assets/serializers/system_user.py:180 msgid "Only system users with automatic login are allowed" msgstr "自動ログインを持つシステムユーザーのみが許可されます" -#: assets/serializers/system_user.py:289 +#: assets/serializers/system_user.py:288 msgid "System user name" msgstr "システムユーザー名" -#: assets/serializers/system_user.py:290 orgs/mixins/serializers.py:26 +#: assets/serializers/system_user.py:289 orgs/mixins/serializers.py:26 #: rbac/serializers/rolebinding.py:23 msgid "Org name" msgstr "組織名" -#: assets/serializers/system_user.py:299 +#: assets/serializers/system_user.py:298 msgid "Asset hostname" msgstr "資産ホスト名" @@ -1313,24 +1313,24 @@ msgstr "" "セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ" "ん" -#: assets/tasks/push_system_user.py:200 +#: assets/tasks/push_system_user.py:201 msgid "System user is dynamic: {}" msgstr "システムユーザーは動的です: {}" -#: assets/tasks/push_system_user.py:241 +#: assets/tasks/push_system_user.py:242 msgid "Start push system user for platform: [{}]" msgstr "プラットフォームのプッシュシステムユーザーを開始: [{}]" -#: assets/tasks/push_system_user.py:242 +#: assets/tasks/push_system_user.py:243 #: assets/tasks/system_user_connectivity.py:106 msgid "Hosts count: {}" msgstr "ホスト数: {}" -#: assets/tasks/push_system_user.py:263 assets/tasks/push_system_user.py:296 +#: assets/tasks/push_system_user.py:264 assets/tasks/push_system_user.py:297 msgid "Push system users to assets: " msgstr "システムユーザーを資産にプッシュする:" -#: assets/tasks/push_system_user.py:275 +#: assets/tasks/push_system_user.py:276 msgid "Push system users to asset: " msgstr "システムユーザーをアセットにプッシュする:" @@ -1562,202 +1562,216 @@ msgstr "ディスプレイとして実行する" msgid "User display" msgstr "ユーザー表示" -#: audits/signal_handlers.py:47 +#: audits/signal_handlers.py:49 msgid "SSH Key" msgstr "SSHキー" -#: audits/signal_handlers.py:49 +#: audits/signal_handlers.py:51 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:50 +#: audits/signal_handlers.py:52 msgid "Auth Token" msgstr "認証トークン" -#: audits/signal_handlers.py:51 authentication/notifications.py:73 -#: authentication/views/login.py:164 authentication/views/wecom.py:182 +#: audits/signal_handlers.py:53 authentication/notifications.py:73 +#: authentication/views/login.py:164 authentication/views/wecom.py:177 #: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企業微信" -#: audits/signal_handlers.py:52 authentication/views/dingtalk.py:183 +#: audits/signal_handlers.py:54 authentication/views/dingtalk.py:179 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:53 authentication/models.py:76 +#: audits/signal_handlers.py:55 authentication/models.py:76 msgid "Temporary token" msgstr "仮パスワード" -#: audits/signal_handlers.py:65 +#: audits/signal_handlers.py:67 msgid "User and Group" msgstr "ユーザーとグループ" -#: audits/signal_handlers.py:66 +#: audits/signal_handlers.py:68 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} に参加 {UserGroup}" -#: audits/signal_handlers.py:67 +#: audits/signal_handlers.py:69 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} のそばを通る {UserGroup}" -#: audits/signal_handlers.py:70 +#: audits/signal_handlers.py:72 msgid "Asset and SystemUser" msgstr "資産およびシステム・ユーザー" -#: audits/signal_handlers.py:71 +#: audits/signal_handlers.py:73 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 追加 {SystemUser}" -#: audits/signal_handlers.py:72 +#: audits/signal_handlers.py:74 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 削除 {SystemUser}" -#: audits/signal_handlers.py:75 +#: audits/signal_handlers.py:77 msgid "Node and Asset" msgstr "ノードと資産" -#: audits/signal_handlers.py:76 +#: audits/signal_handlers.py:78 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 追加 {Asset}" -#: audits/signal_handlers.py:77 +#: audits/signal_handlers.py:79 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 削除 {Asset}" -#: audits/signal_handlers.py:80 +#: audits/signal_handlers.py:82 msgid "User asset permissions" msgstr "ユーザー資産の権限" -#: audits/signal_handlers.py:81 +#: audits/signal_handlers.py:83 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 追加 {User}" -#: audits/signal_handlers.py:82 +#: audits/signal_handlers.py:84 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 削除 {User}" -#: audits/signal_handlers.py:85 +#: audits/signal_handlers.py:87 msgid "User group asset permissions" msgstr "ユーザーグループの資産権限" -#: audits/signal_handlers.py:86 +#: audits/signal_handlers.py:88 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:87 +#: audits/signal_handlers.py:89 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:90 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:92 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "資産権限" -#: audits/signal_handlers.py:91 +#: audits/signal_handlers.py:93 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 追加 {Asset}" -#: audits/signal_handlers.py:92 +#: audits/signal_handlers.py:94 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 削除 {Asset}" -#: audits/signal_handlers.py:95 +#: audits/signal_handlers.py:97 msgid "Node permission" msgstr "ノード権限" -#: audits/signal_handlers.py:96 +#: audits/signal_handlers.py:98 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 追加 {Node}" -#: audits/signal_handlers.py:97 +#: audits/signal_handlers.py:99 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 削除 {Node}" -#: audits/signal_handlers.py:100 +#: audits/signal_handlers.py:102 msgid "Asset permission and SystemUser" msgstr "資産権限とSystemUser" -#: audits/signal_handlers.py:101 +#: audits/signal_handlers.py:103 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:102 +#: audits/signal_handlers.py:104 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 削除 {SystemUser}" -#: audits/signal_handlers.py:105 +#: audits/signal_handlers.py:107 msgid "User application permissions" msgstr "ユーザーアプリケーションの権限" -#: audits/signal_handlers.py:106 +#: audits/signal_handlers.py:108 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 追加 {User}" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:109 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 削除 {User}" -#: audits/signal_handlers.py:110 +#: audits/signal_handlers.py:112 msgid "User group application permissions" msgstr "ユーザーグループアプリケーションの権限" -#: audits/signal_handlers.py:111 +#: audits/signal_handlers.py:113 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:114 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:115 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:117 perms/models/application_permission.py:38 msgid "Application permission" msgstr "申請許可" -#: audits/signal_handlers.py:116 +#: audits/signal_handlers.py:118 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 追加 {Application}" -#: audits/signal_handlers.py:117 +#: audits/signal_handlers.py:119 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 削除 {Application}" -#: audits/signal_handlers.py:120 +#: audits/signal_handlers.py:122 msgid "Application permission and SystemUser" msgstr "アプリケーション権限とSystemUser" -#: audits/signal_handlers.py:121 +#: audits/signal_handlers.py:123 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 追加 {SystemUser}" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:124 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}" +#: authentication/api/confirm.py:40 +#, fuzzy +#| msgid "Authentication failed (username or password incorrect): {}" +msgid "Authentication failed password incorrect" +msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" + +#: authentication/api/confirm.py:48 +msgid "Login time has exceeded {} minutes, please login again" +msgstr "" + +#: authentication/api/confirm.py:72 common/exceptions.py:47 +msgid "This action require verify your MFA" +msgstr "このアクションでは、MFAの確認が必要です。" + #: authentication/api/connection_token.py:326 msgid "Invalid token" msgstr "無効なトークン" @@ -2199,7 +2213,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:304 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:305 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:23 @@ -2342,54 +2356,49 @@ msgstr "返品" msgid "Copy success" msgstr "コピー成功" -#: authentication/views/dingtalk.py:40 +#: authentication/views/dingtalk.py:41 msgid "DingTalk Error, Please contact your system administrator" msgstr "DingTalkエラー、システム管理者に連絡してください" -#: authentication/views/dingtalk.py:43 +#: authentication/views/dingtalk.py:44 msgid "DingTalk Error" msgstr "DingTalkエラー" -#: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50 +#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:50 #: authentication/views/wecom.py:55 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "システム設定が正しくありません。管理者に連絡してください" -#: authentication/views/dingtalk.py:79 +#: authentication/views/dingtalk.py:80 msgid "DingTalk is already bound" msgstr "DingTalkはすでにバインドされています" -#: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99 -#: authentication/views/wecom.py:128 -msgid "Please verify your password first" -msgstr "最初にパスワードを確認してください" - -#: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152 +#: authentication/views/dingtalk.py:148 authentication/views/wecom.py:147 msgid "Invalid user_id" msgstr "無効なuser_id" -#: authentication/views/dingtalk.py:168 +#: authentication/views/dingtalk.py:164 msgid "DingTalk query user failed" msgstr "DingTalkクエリユーザーが失敗しました" -#: authentication/views/dingtalk.py:177 +#: authentication/views/dingtalk.py:173 msgid "The DingTalk is already bound to another user" msgstr "DingTalkはすでに別のユーザーにバインドされています" -#: authentication/views/dingtalk.py:184 +#: authentication/views/dingtalk.py:180 msgid "Binding DingTalk successfully" msgstr "DingTalkのバインドに成功" -#: authentication/views/dingtalk.py:240 authentication/views/dingtalk.py:294 +#: authentication/views/dingtalk.py:236 authentication/views/dingtalk.py:290 msgid "Failed to get user from DingTalk" msgstr "DingTalkからユーザーを取得できませんでした" -#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 +#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296 msgid "DingTalk is not bound" msgstr "DingTalkはバインドされていません" -#: authentication/views/dingtalk.py:247 authentication/views/dingtalk.py:301 +#: authentication/views/dingtalk.py:243 authentication/views/dingtalk.py:297 msgid "Please login with a password and then bind the DingTalk" msgstr "パスワードでログインし、DingTalkをバインドしてください" @@ -2401,32 +2410,32 @@ msgstr "FeiShuエラー" msgid "FeiShu is already bound" msgstr "FeiShuはすでにバインドされています" -#: authentication/views/feishu.py:133 +#: authentication/views/feishu.py:128 msgid "FeiShu query user failed" msgstr "FeiShuクエリユーザーが失敗しました" -#: authentication/views/feishu.py:142 +#: authentication/views/feishu.py:137 msgid "The FeiShu is already bound to another user" msgstr "FeiShuはすでに別のユーザーにバインドされています" -#: authentication/views/feishu.py:148 authentication/views/login.py:176 +#: authentication/views/feishu.py:143 authentication/views/login.py:176 #: notifications/backends/__init__.py:14 users/models/user.py:722 msgid "FeiShu" msgstr "本を飛ばす" -#: authentication/views/feishu.py:149 +#: authentication/views/feishu.py:144 msgid "Binding FeiShu successfully" msgstr "本を飛ばすのバインドに成功" -#: authentication/views/feishu.py:201 +#: authentication/views/feishu.py:196 msgid "Failed to get user from FeiShu" msgstr "本を飛ばすからユーザーを取得できませんでした" -#: authentication/views/feishu.py:207 +#: authentication/views/feishu.py:202 msgid "FeiShu is not bound" msgstr "本を飛ばすは拘束されていません" -#: authentication/views/feishu.py:208 +#: authentication/views/feishu.py:203 msgid "Please login with a password and then bind the FeiShu" msgstr "パスワードでログインしてから本を飛ばすをバインドしてください" @@ -2474,27 +2483,27 @@ msgstr "企業微信エラー" msgid "WeCom is already bound" msgstr "企業の微信はすでにバインドされています" -#: authentication/views/wecom.py:167 +#: authentication/views/wecom.py:162 msgid "WeCom query user failed" msgstr "企業微信ユーザーの問合せに失敗しました" -#: authentication/views/wecom.py:176 +#: authentication/views/wecom.py:171 msgid "The WeCom is already bound to another user" msgstr "この企業の微信はすでに他のユーザーをバインドしている。" -#: authentication/views/wecom.py:183 +#: authentication/views/wecom.py:178 msgid "Binding WeCom successfully" msgstr "企業の微信のバインドに成功" -#: authentication/views/wecom.py:235 authentication/views/wecom.py:289 +#: authentication/views/wecom.py:230 authentication/views/wecom.py:284 msgid "Failed to get user from WeCom" msgstr "企業の微信からユーザーを取得できませんでした" -#: authentication/views/wecom.py:241 authentication/views/wecom.py:295 +#: authentication/views/wecom.py:236 authentication/views/wecom.py:290 msgid "WeCom is not bound" msgstr "企業の微信をバインドしていません" -#: authentication/views/wecom.py:242 authentication/views/wecom.py:296 +#: authentication/views/wecom.py:237 authentication/views/wecom.py:291 msgid "Please login with a password and then bind the WeCom" msgstr "パスワードでログインしてからWeComをバインドしてください" @@ -2577,10 +2586,6 @@ msgstr "M2Mリバースは許可されません" msgid "Is referenced by other objects and cannot be deleted" msgstr "他のオブジェクトによって参照され、削除できません。" -#: common/exceptions.py:47 -msgid "This action require verify your MFA" -msgstr "このアクションでは、MFAの確認が必要です。" - #: common/exceptions.py:53 msgid "Unexpect error occur" msgstr "予期しないエラーが発生します" @@ -2670,11 +2675,11 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:303 +#: jumpserver/conf.py:304 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:305 +#: jumpserver/conf.py:306 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -3037,35 +3042,35 @@ msgstr "クリップボードコピーペースト" msgid "From ticket" msgstr "チケットから" -#: perms/notifications.py:17 +#: perms/notifications.py:18 msgid "You permed assets is about to expire" msgstr "パーマ資産の有効期限が近づいています" -#: perms/notifications.py:21 +#: perms/notifications.py:23 msgid "permed assets" msgstr "パーマ資産" -#: perms/notifications.py:59 +#: perms/notifications.py:62 msgid "Asset permissions is about to expire" msgstr "資産権限の有効期限が近づいています" -#: perms/notifications.py:63 +#: perms/notifications.py:67 msgid "asset permissions of organization {}" msgstr "組織 {} の資産権限" -#: perms/notifications.py:89 +#: perms/notifications.py:94 msgid "Your permed applications is about to expire" msgstr "パーマアプリケーションの有効期限が近づいています" -#: perms/notifications.py:92 +#: perms/notifications.py:98 msgid "permed applications" msgstr "Permedアプリケーション" -#: perms/notifications.py:127 +#: perms/notifications.py:134 msgid "Application permissions is about to expire" msgstr "アプリケーション権限の有効期限が近づいています" -#: perms/notifications.py:130 +#: perms/notifications.py:137 msgid "application permissions of organization {}" msgstr "Organization {} のアプリケーション権限" @@ -3123,14 +3128,18 @@ msgstr "システムユーザーの表示" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 -#, python-format +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| " The following %(item_type)s will expire in %(count)s days\n" +#| " " msgid "" "\n" -" The following %(item_type)s will expire in 3 days\n" +" The following %(item_type)s will expire in %%(count)s days\n" " " msgstr "" "\n" -" 次の %(item_type)s は3日以内に期限切れになります\n" +" 次の %(item_type)s は %(count)s 日以内に期限切れになります\n" " " #: perms/templates/perms/_msg_permed_items_expire.html:21 @@ -3141,6 +3150,10 @@ msgstr "質問があったら、管理者に連絡して下さい" msgid "My applications" msgstr "私のアプリケーション" +#: perms/utils/asset/user_permission.py:620 rbac/tree.py:59 +msgid "My assets" +msgstr "私の資産" + #: rbac/api/role.py:34 msgid "Internal role, can't be destroy" msgstr "内部の役割は、破壊することはできません" @@ -3149,7 +3162,7 @@ msgstr "内部の役割は、破壊することはできません" msgid "The role has been bound to users, can't be destroy" msgstr "ロールはユーザーにバインドされており、破壊することはできません" -#: rbac/api/role.py:45 +#: rbac/api/role.py:60 msgid "Internal role, can't be update" msgstr "内部ロール、更新できません" @@ -3328,10 +3341,6 @@ msgstr "資産の改ざん" msgid "Terminal setting" msgstr "ターミナル設定" -#: rbac/tree.py:59 -msgid "My assets" -msgstr "私の資産" - #: rbac/tree.py:60 msgid "My apps" msgstr "マイアプリ" @@ -3597,8 +3606,8 @@ msgid "" "User attr map present how to map OpenID user attr to jumpserver, username," "name,email is jumpserver attr" msgstr "" -"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピング" -"する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" +"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン" +"グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です" #: settings/serializers/auth/oidc.py:44 msgid "Use Keycloak" @@ -4283,7 +4292,9 @@ msgid "Enable database proxy" msgstr "属性マップの有効化" #: settings/serializers/terminal.py:37 -msgid "Enable XRDP" +#, fuzzy +#| msgid "Enable XRDP" +msgid "Enable Razor" msgstr "XRDPの有効化" #: settings/serializers/terminal.py:38 @@ -5700,27 +5711,27 @@ msgstr "最終更新日パスワード" msgid "Need update password" msgstr "更新パスワードが必要" -#: users/models/user.py:885 +#: users/models/user.py:890 msgid "Can invite user" msgstr "ユーザーを招待できます" -#: users/models/user.py:886 +#: users/models/user.py:891 msgid "Can remove user" msgstr "ユーザーを削除できます" -#: users/models/user.py:887 +#: users/models/user.py:892 msgid "Can match user" msgstr "ユーザーに一致できます" -#: users/models/user.py:896 +#: users/models/user.py:901 msgid "Administrator" msgstr "管理者" -#: users/models/user.py:899 +#: users/models/user.py:904 msgid "Administrator is the super user of system" msgstr "管理者はシステムのスーパーユーザーです" -#: users/models/user.py:924 +#: users/models/user.py:929 msgid "User password history" msgstr "ユーザーパスワード履歴" @@ -6829,6 +6840,9 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#~ msgid "Please verify your password first" +#~ msgstr "最初にパスワードを確認してください" + #~ msgid "AccessKey ID" #~ msgstr "アクセスキーID" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 65a32959d..e5b16f047 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b03f7f9c1d450f8a3012330c00d084c88465d99e355c42ec10f568a8ffa7611c -size 105357 +oid sha256:1ab23fa4d87b928318281150ff64d9c2e3782a9169a3c3e6907808b1be63dbc3 +size 105365 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 8560a07ac..634bea865 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-31 16:02+0800\n" +"POT-Creation-Date: 2022-06-14 15:48+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -37,12 +37,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:62 +#: assets/models/user.py:251 terminal/models/endpoint.py:62 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 terminal/models/endpoint.py:63 +#: assets/models/user.py:251 terminal/models/endpoint.py:63 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -92,8 +92,8 @@ msgstr "登录复核" #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:44 #: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:883 -#: users/models/user.py:914 users/serializers/group.py:19 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:888 +#: users/models/user.py:919 users/serializers/group.py:19 msgid "User" msgstr "用户" @@ -128,7 +128,7 @@ msgstr "系统用户" #: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:269 audits/models.py:39 +#: assets/serializers/system_user.py:268 audits/models.py:39 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:46 #: terminal/notifications.py:90 @@ -177,7 +177,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 #: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:210 assets/models/domain.py:60 -#: assets/serializers/account.py:14 +#: assets/serializers/account.py:13 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -186,7 +186,7 @@ msgid "IP" msgstr "IP" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 -#: assets/serializers/account.py:15 assets/serializers/gathered_user.py:23 +#: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 msgid "Hostname" msgstr "主机名" @@ -198,7 +198,7 @@ msgid "" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:62 assets/models/user.py:248 +#: assets/models/domain.py:62 assets/models/user.py:252 #: terminal/serializers/session.py:30 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "协议" @@ -260,7 +260,7 @@ msgid "Application" msgstr "应用程序" #: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 +#: assets/models/cmd_filter.py:42 assets/models/user.py:342 audits/models.py:40 #: perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:35 terminal/models/session.py:48 @@ -298,7 +298,7 @@ msgstr "类别" #: applications/models/application.py:222 #: applications/serializers/application.py:101 assets/models/backup.py:49 -#: assets/models/cmd_filter.py:82 assets/models/user.py:246 +#: assets/models/cmd_filter.py:82 assets/models/user.py:250 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:57 terminal/models/storage.py:141 @@ -336,7 +336,7 @@ msgstr "类别名称" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:29 +#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 #: audits/serializers.py:29 perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 @@ -348,17 +348,17 @@ msgstr "类型名称" #: assets/models/base.py:181 assets/models/cluster.py:26 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:19 assets/serializers/cmd_filter.py:28 +#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 #: assets/serializers/cmd_filter.py:48 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:915 +#: users/models/group.py:18 users/models/user.py:920 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "创建日期" #: applications/serializers/application.py:104 assets/models/base.py:182 -#: assets/models/gathered_user.py:20 assets/serializers/account.py:22 +#: assets/models/gathered_user.py:20 assets/serializers/account.py:21 #: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 #: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 @@ -408,7 +408,7 @@ msgid "Application path" msgstr "应用路径" #: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:168 +#: assets/serializers/system_user.py:167 #: xpack/plugins/change_auth_plan/serializers/asset.py:67 #: xpack/plugins/change_auth_plan/serializers/asset.py:70 #: xpack/plugins/change_auth_plan/serializers/asset.py:73 @@ -505,7 +505,7 @@ msgid "Internal" msgstr "内部的" #: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:16 assets/serializers/asset.py:63 +#: assets/serializers/account.py:15 assets/serializers/asset.py:63 #: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "系统平台" @@ -566,13 +566,13 @@ msgstr "系统架构" msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:215 assets/serializers/account.py:17 +#: assets/models/asset.py:215 assets/serializers/account.py:16 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:218 assets/models/user.py:238 +#: assets/models/asset.py:218 assets/models/user.py:242 #: perms/models/asset_permission.py:24 #: xpack/plugins/change_auth_plan/models/asset.py:43 #: xpack/plugins/gathered_user/models.py:24 @@ -585,7 +585,7 @@ msgid "Is active" msgstr "激活" #: assets/models/asset.py:222 assets/models/cluster.py:19 -#: assets/models/user.py:235 assets/models/user.py:390 +#: assets/models/user.py:239 assets/models/user.py:394 msgid "Admin user" msgstr "特权用户" @@ -754,9 +754,9 @@ msgstr "可连接性" msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:177 assets/serializers/base.py:14 -#: assets/serializers/base.py:36 audits/signal_handlers.py:48 -#: authentication/forms.py:32 +#: assets/models/base.py:177 assets/serializers/base.py:15 +#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 +#: audits/signal_handlers.py:50 authentication/forms.py:32 #: authentication/templates/authentication/login.html:182 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 #: users/forms/profile.py:22 users/serializers/user.py:92 @@ -771,7 +771,7 @@ msgstr "校验日期" msgid "Password" msgstr "密码" -#: assets/models/base.py:178 assets/serializers/base.py:39 +#: assets/models/base.py:178 assets/serializers/base.py:41 #: xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:206 @@ -818,7 +818,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:900 +#: users/models/user.py:905 msgid "System" msgstr "系统" @@ -956,7 +956,7 @@ msgstr "全称" msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 assets/serializers/system_user.py:268 +#: assets/models/node.py:559 assets/serializers/system_user.py:267 #: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:69 msgid "Node" msgstr "节点" @@ -965,78 +965,78 @@ msgstr "节点" msgid "Can match node" msgstr "可以匹配节点" -#: assets/models/user.py:229 +#: assets/models/user.py:233 msgid "Automatic managed" msgstr "托管密码" -#: assets/models/user.py:230 +#: assets/models/user.py:234 msgid "Manually input" msgstr "手动输入" -#: assets/models/user.py:234 +#: assets/models/user.py:238 msgid "Common user" msgstr "普通用户" -#: assets/models/user.py:237 +#: assets/models/user.py:241 msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:240 assets/serializers/domain.py:30 +#: assets/models/user.py:244 assets/serializers/domain.py:30 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:39 msgid "Assets" msgstr "资产" -#: assets/models/user.py:244 users/apps.py:9 +#: assets/models/user.py:248 users/apps.py:9 msgid "Users" msgstr "用户管理" -#: assets/models/user.py:245 +#: assets/models/user.py:249 msgid "User groups" msgstr "用户组" -#: assets/models/user.py:249 +#: assets/models/user.py:253 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:250 +#: assets/models/user.py:254 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:251 +#: assets/models/user.py:255 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:252 +#: assets/models/user.py:256 msgid "Login mode" msgstr "认证方式" -#: assets/models/user.py:253 +#: assets/models/user.py:257 msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:254 assets/serializers/system_user.py:32 +#: assets/models/user.py:258 assets/serializers/system_user.py:37 #: authentication/models.py:49 msgid "Token" msgstr "Token" -#: assets/models/user.py:255 +#: assets/models/user.py:259 msgid "Home" msgstr "家目录" -#: assets/models/user.py:256 +#: assets/models/user.py:260 msgid "System groups" msgstr "用户组" -#: assets/models/user.py:259 +#: assets/models/user.py:263 msgid "User switch" msgstr "用户切换" -#: assets/models/user.py:260 +#: assets/models/user.py:264 msgid "Switch from" msgstr "切换自" -#: assets/models/user.py:340 +#: assets/models/user.py:344 msgid "Can match system user" msgstr "可以匹配系统用户" @@ -1064,7 +1064,7 @@ msgstr "" "{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" "置加密密码" -#: assets/serializers/account.py:41 assets/serializers/account.py:84 +#: assets/serializers/account.py:36 assets/serializers/account.py:79 msgid "System user display" msgstr "系统用户名称" @@ -1119,15 +1119,15 @@ msgstr "定时执行" msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/base.py:15 users/models/user.py:689 +#: assets/serializers/base.py:16 users/models/user.py:689 msgid "Private key" msgstr "ssh私钥" -#: assets/serializers/base.py:43 +#: assets/serializers/base.py:45 msgid "Key password" msgstr "密钥密码" -#: assets/serializers/base.py:56 +#: assets/serializers/base.py:58 msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" @@ -1140,7 +1140,7 @@ msgid "Pattern" msgstr "模式" #: assets/serializers/domain.py:14 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:64 +#: assets/serializers/system_user.py:63 #: perms/serializers/asset/permission.py:49 msgid "Assets amount" msgstr "资产数量" @@ -1165,78 +1165,78 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:30 +#: assets/serializers/system_user.py:35 msgid "SSH key fingerprint" msgstr "密钥指纹" -#: assets/serializers/system_user.py:35 +#: assets/serializers/system_user.py:40 #: perms/serializers/application/permission.py:46 msgid "Apps amount" msgstr "应用数量" -#: assets/serializers/system_user.py:63 +#: assets/serializers/system_user.py:62 #: perms/serializers/asset/permission.py:50 msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:65 assets/serializers/system_user.py:270 +#: assets/serializers/system_user.py:64 assets/serializers/system_user.py:269 msgid "Login mode display" msgstr "认证方式名称" -#: assets/serializers/system_user.py:67 +#: assets/serializers/system_user.py:66 msgid "Ad domain" msgstr "Ad 网域" -#: assets/serializers/system_user.py:68 +#: assets/serializers/system_user.py:67 msgid "Is asset protocol" msgstr "资产协议" -#: assets/serializers/system_user.py:69 +#: assets/serializers/system_user.py:68 msgid "Only ssh and automatic login system users are supported" msgstr "仅支持ssh协议和自动登录的系统用户" -#: assets/serializers/system_user.py:109 +#: assets/serializers/system_user.py:108 msgid "Username same with user with protocol {} only allow 1" msgstr "用户名和用户相同的一种协议只允许存在一个" -#: assets/serializers/system_user.py:122 common/validators.py:14 +#: assets/serializers/system_user.py:121 common/validators.py:14 msgid "Special char not allowed" msgstr "不能包含特殊字符" -#: assets/serializers/system_user.py:132 +#: assets/serializers/system_user.py:131 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:147 +#: assets/serializers/system_user.py:146 msgid "Path should starts with /" msgstr "路径应该以 / 开头" -#: assets/serializers/system_user.py:159 +#: assets/serializers/system_user.py:158 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" -#: assets/serializers/system_user.py:173 +#: assets/serializers/system_user.py:172 msgid "Only ssh protocol system users are allowed" msgstr "仅允许ssh协议的系统用户" -#: assets/serializers/system_user.py:177 +#: assets/serializers/system_user.py:176 msgid "The protocol must be consistent with the current user: {}" msgstr "协议必须和当前用户保持一致: {}" -#: assets/serializers/system_user.py:181 +#: assets/serializers/system_user.py:180 msgid "Only system users with automatic login are allowed" msgstr "仅允许自动登录的系统用户" -#: assets/serializers/system_user.py:289 +#: assets/serializers/system_user.py:288 msgid "System user name" msgstr "系统用户名称" -#: assets/serializers/system_user.py:290 orgs/mixins/serializers.py:26 +#: assets/serializers/system_user.py:289 orgs/mixins/serializers.py:26 #: rbac/serializers/rolebinding.py:23 msgid "Org name" msgstr "组织名称" -#: assets/serializers/system_user.py:299 +#: assets/serializers/system_user.py:298 msgid "Asset hostname" msgstr "资产主机名" @@ -1301,24 +1301,24 @@ msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "自检程序已经在运行,不能重复启动" -#: assets/tasks/push_system_user.py:200 +#: assets/tasks/push_system_user.py:201 msgid "System user is dynamic: {}" msgstr "系统用户是动态的: {}" -#: assets/tasks/push_system_user.py:241 +#: assets/tasks/push_system_user.py:242 msgid "Start push system user for platform: [{}]" msgstr "推送系统用户到平台: [{}]" -#: assets/tasks/push_system_user.py:242 +#: assets/tasks/push_system_user.py:243 #: assets/tasks/system_user_connectivity.py:106 msgid "Hosts count: {}" msgstr "主机数量: {}" -#: assets/tasks/push_system_user.py:263 assets/tasks/push_system_user.py:296 +#: assets/tasks/push_system_user.py:264 assets/tasks/push_system_user.py:297 msgid "Push system users to assets: " msgstr "推送系统用户到入资产: " -#: assets/tasks/push_system_user.py:275 +#: assets/tasks/push_system_user.py:276 msgid "Push system users to asset: " msgstr "推送系统用户到入资产: " @@ -1550,202 +1550,214 @@ msgstr "运行用户名称" msgid "User display" msgstr "用户名称" -#: audits/signal_handlers.py:47 +#: audits/signal_handlers.py:49 msgid "SSH Key" msgstr "SSH 密钥" -#: audits/signal_handlers.py:49 +#: audits/signal_handlers.py:51 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:50 +#: audits/signal_handlers.py:52 msgid "Auth Token" msgstr "认证令牌" -#: audits/signal_handlers.py:51 authentication/notifications.py:73 -#: authentication/views/login.py:164 authentication/views/wecom.py:182 +#: audits/signal_handlers.py:53 authentication/notifications.py:73 +#: authentication/views/login.py:164 authentication/views/wecom.py:177 #: notifications/backends/__init__.py:11 users/models/user.py:720 msgid "WeCom" msgstr "企业微信" -#: audits/signal_handlers.py:52 authentication/views/dingtalk.py:183 +#: audits/signal_handlers.py:54 authentication/views/dingtalk.py:179 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: users/models/user.py:721 msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:53 authentication/models.py:76 +#: audits/signal_handlers.py:55 authentication/models.py:76 msgid "Temporary token" msgstr "临时密码" -#: audits/signal_handlers.py:65 +#: audits/signal_handlers.py:67 msgid "User and Group" msgstr "用户与用户组" -#: audits/signal_handlers.py:66 +#: audits/signal_handlers.py:68 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} 加入 {UserGroup}" -#: audits/signal_handlers.py:67 +#: audits/signal_handlers.py:69 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} 离开 {UserGroup}" -#: audits/signal_handlers.py:70 +#: audits/signal_handlers.py:72 msgid "Asset and SystemUser" msgstr "资产与系统用户" -#: audits/signal_handlers.py:71 +#: audits/signal_handlers.py:73 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 添加 {SystemUser}" -#: audits/signal_handlers.py:72 +#: audits/signal_handlers.py:74 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 移除 {SystemUser}" -#: audits/signal_handlers.py:75 +#: audits/signal_handlers.py:77 msgid "Node and Asset" msgstr "节点与资产" -#: audits/signal_handlers.py:76 +#: audits/signal_handlers.py:78 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 添加 {Asset}" -#: audits/signal_handlers.py:77 +#: audits/signal_handlers.py:79 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 移除 {Asset}" -#: audits/signal_handlers.py:80 +#: audits/signal_handlers.py:82 msgid "User asset permissions" msgstr "用户资产授权" -#: audits/signal_handlers.py:81 +#: audits/signal_handlers.py:83 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 添加 {User}" -#: audits/signal_handlers.py:82 +#: audits/signal_handlers.py:84 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 移除 {User}" -#: audits/signal_handlers.py:85 +#: audits/signal_handlers.py:87 msgid "User group asset permissions" msgstr "用户组资产授权" -#: audits/signal_handlers.py:86 +#: audits/signal_handlers.py:88 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:87 +#: audits/signal_handlers.py:89 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:90 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:92 perms/models/asset_permission.py:29 msgid "Asset permission" msgstr "资产授权" -#: audits/signal_handlers.py:91 +#: audits/signal_handlers.py:93 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 添加 {Asset}" -#: audits/signal_handlers.py:92 +#: audits/signal_handlers.py:94 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 移除 {Asset}" -#: audits/signal_handlers.py:95 +#: audits/signal_handlers.py:97 msgid "Node permission" msgstr "节点授权" -#: audits/signal_handlers.py:96 +#: audits/signal_handlers.py:98 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 添加 {Node}" -#: audits/signal_handlers.py:97 +#: audits/signal_handlers.py:99 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 移除 {Node}" -#: audits/signal_handlers.py:100 +#: audits/signal_handlers.py:102 msgid "Asset permission and SystemUser" msgstr "资产授权与系统用户" -#: audits/signal_handlers.py:101 +#: audits/signal_handlers.py:103 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:102 +#: audits/signal_handlers.py:104 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 移除 {SystemUser}" -#: audits/signal_handlers.py:105 +#: audits/signal_handlers.py:107 msgid "User application permissions" msgstr "用户应用授权" -#: audits/signal_handlers.py:106 +#: audits/signal_handlers.py:108 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 添加 {User}" -#: audits/signal_handlers.py:107 +#: audits/signal_handlers.py:109 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 移除 {User}" -#: audits/signal_handlers.py:110 +#: audits/signal_handlers.py:112 msgid "User group application permissions" msgstr "用户组应用授权" -#: audits/signal_handlers.py:111 +#: audits/signal_handlers.py:113 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:112 +#: audits/signal_handlers.py:114 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:115 perms/models/application_permission.py:38 +#: audits/signal_handlers.py:117 perms/models/application_permission.py:38 msgid "Application permission" msgstr "应用授权" -#: audits/signal_handlers.py:116 +#: audits/signal_handlers.py:118 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 添加 {Application}" -#: audits/signal_handlers.py:117 +#: audits/signal_handlers.py:119 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 移除 {Application}" -#: audits/signal_handlers.py:120 +#: audits/signal_handlers.py:122 msgid "Application permission and SystemUser" msgstr "应用授权与系统用户" -#: audits/signal_handlers.py:121 +#: audits/signal_handlers.py:123 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 添加 {SystemUser}" -#: audits/signal_handlers.py:122 +#: audits/signal_handlers.py:124 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" +#: authentication/api/confirm.py:40 +msgid "Authentication failed password incorrect" +msgstr "认证失败 (用户名或密码不正确): {}" + +#: authentication/api/confirm.py:48 +msgid "Login time has exceeded {} minutes, please login again" +msgstr "" + +#: authentication/api/confirm.py:72 common/exceptions.py:47 +msgid "This action require verify your MFA" +msgstr "这个操作需要验证 MFA" + #: authentication/api/connection_token.py:326 msgid "Invalid token" msgstr "无效的令牌" @@ -2178,7 +2190,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:304 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:305 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:23 @@ -2312,54 +2324,49 @@ msgstr "返回" msgid "Copy success" msgstr "复制成功" -#: authentication/views/dingtalk.py:40 +#: authentication/views/dingtalk.py:41 msgid "DingTalk Error, Please contact your system administrator" msgstr "钉钉错误,请联系系统管理员" -#: authentication/views/dingtalk.py:43 +#: authentication/views/dingtalk.py:44 msgid "DingTalk Error" msgstr "钉钉错误" -#: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50 +#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:50 #: authentication/views/wecom.py:55 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "企业配置错误,请联系系统管理员" -#: authentication/views/dingtalk.py:79 +#: authentication/views/dingtalk.py:80 msgid "DingTalk is already bound" msgstr "钉钉已经绑定" -#: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99 -#: authentication/views/wecom.py:128 -msgid "Please verify your password first" -msgstr "请检查密码" - -#: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152 +#: authentication/views/dingtalk.py:148 authentication/views/wecom.py:147 msgid "Invalid user_id" msgstr "无效的 user_id" -#: authentication/views/dingtalk.py:168 +#: authentication/views/dingtalk.py:164 msgid "DingTalk query user failed" msgstr "钉钉查询用户失败" -#: authentication/views/dingtalk.py:177 +#: authentication/views/dingtalk.py:173 msgid "The DingTalk is already bound to another user" msgstr "该钉钉已经绑定其他用户" -#: authentication/views/dingtalk.py:184 +#: authentication/views/dingtalk.py:180 msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:240 authentication/views/dingtalk.py:294 +#: authentication/views/dingtalk.py:236 authentication/views/dingtalk.py:290 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300 +#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" -#: authentication/views/dingtalk.py:247 authentication/views/dingtalk.py:301 +#: authentication/views/dingtalk.py:243 authentication/views/dingtalk.py:297 msgid "Please login with a password and then bind the DingTalk" msgstr "请使用密码登录,然后绑定钉钉" @@ -2371,32 +2378,32 @@ msgstr "飞书错误" msgid "FeiShu is already bound" msgstr "飞书已经绑定" -#: authentication/views/feishu.py:133 +#: authentication/views/feishu.py:128 msgid "FeiShu query user failed" msgstr "飞书查询用户失败" -#: authentication/views/feishu.py:142 +#: authentication/views/feishu.py:137 msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" -#: authentication/views/feishu.py:148 authentication/views/login.py:176 +#: authentication/views/feishu.py:143 authentication/views/login.py:176 #: notifications/backends/__init__.py:14 users/models/user.py:722 msgid "FeiShu" msgstr "飞书" -#: authentication/views/feishu.py:149 +#: authentication/views/feishu.py:144 msgid "Binding FeiShu successfully" msgstr "绑定 飞书 成功" -#: authentication/views/feishu.py:201 +#: authentication/views/feishu.py:196 msgid "Failed to get user from FeiShu" msgstr "从飞书获取用户失败" -#: authentication/views/feishu.py:207 +#: authentication/views/feishu.py:202 msgid "FeiShu is not bound" msgstr "没有绑定飞书" -#: authentication/views/feishu.py:208 +#: authentication/views/feishu.py:203 msgid "Please login with a password and then bind the FeiShu" msgstr "请使用密码登录,然后绑定飞书" @@ -2444,27 +2451,27 @@ msgstr "企业微信错误" msgid "WeCom is already bound" msgstr "企业微信已经绑定" -#: authentication/views/wecom.py:167 +#: authentication/views/wecom.py:162 msgid "WeCom query user failed" msgstr "企业微信查询用户失败" -#: authentication/views/wecom.py:176 +#: authentication/views/wecom.py:171 msgid "The WeCom is already bound to another user" msgstr "该企业微信已经绑定其他用户" -#: authentication/views/wecom.py:183 +#: authentication/views/wecom.py:178 msgid "Binding WeCom successfully" msgstr "绑定 企业微信 成功" -#: authentication/views/wecom.py:235 authentication/views/wecom.py:289 +#: authentication/views/wecom.py:230 authentication/views/wecom.py:284 msgid "Failed to get user from WeCom" msgstr "从企业微信获取用户失败" -#: authentication/views/wecom.py:241 authentication/views/wecom.py:295 +#: authentication/views/wecom.py:236 authentication/views/wecom.py:290 msgid "WeCom is not bound" msgstr "没有绑定企业微信" -#: authentication/views/wecom.py:242 authentication/views/wecom.py:296 +#: authentication/views/wecom.py:237 authentication/views/wecom.py:291 msgid "Please login with a password and then bind the WeCom" msgstr "请使用密码登录,然后绑定企业微信" @@ -2547,10 +2554,6 @@ msgstr "多对多反向是不被允许的" msgid "Is referenced by other objects and cannot be deleted" msgstr "被其他对象关联,不能删除" -#: common/exceptions.py:47 -msgid "This action require verify your MFA" -msgstr "这个操作需要验证 MFA" - #: common/exceptions.py:53 msgid "Unexpect error occur" msgstr "发生意外错误" @@ -2640,11 +2643,11 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:303 +#: jumpserver/conf.py:304 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:305 +#: jumpserver/conf.py:306 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -3001,35 +3004,35 @@ msgstr "剪贴板复制粘贴" msgid "From ticket" msgstr "来自工单" -#: perms/notifications.py:17 +#: perms/notifications.py:18 msgid "You permed assets is about to expire" msgstr "你授权的资产即将到期" -#: perms/notifications.py:21 +#: perms/notifications.py:23 msgid "permed assets" msgstr "授权的资产" -#: perms/notifications.py:59 +#: perms/notifications.py:62 msgid "Asset permissions is about to expire" msgstr "资产授权规则将要过期" -#: perms/notifications.py:63 +#: perms/notifications.py:67 msgid "asset permissions of organization {}" msgstr "组织 ({}) 的资产授权" -#: perms/notifications.py:89 +#: perms/notifications.py:94 msgid "Your permed applications is about to expire" msgstr "你授权的应用即将过期" -#: perms/notifications.py:92 +#: perms/notifications.py:98 msgid "permed applications" msgstr "授权的应用" -#: perms/notifications.py:127 +#: perms/notifications.py:134 msgid "Application permissions is about to expire" msgstr "应用授权规则即将过期" -#: perms/notifications.py:130 +#: perms/notifications.py:137 msgid "application permissions of organization {}" msgstr "组织 ({}) 的应用授权" @@ -3085,14 +3088,13 @@ msgstr "系统用户名称" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 -#, python-format msgid "" "\n" -" The following %(item_type)s will expire in 3 days\n" +" The following %(item_type)s will expire in %(count)s days\n" " " msgstr "" "\n" -" 以下 %(item_type)s 即将在 3 天后过期\n" +" 以下 %(item_type)s 即将在 %(count)s 天后过期\n" " " #: perms/templates/perms/_msg_permed_items_expire.html:21 @@ -3103,6 +3105,10 @@ msgstr "如果有疑问或需求,请联系系统管理员" msgid "My applications" msgstr "我的应用" +#: perms/utils/asset/user_permission.py:620 rbac/tree.py:59 +msgid "My assets" +msgstr "我的资产" + #: rbac/api/role.py:34 msgid "Internal role, can't be destroy" msgstr "内部角色,不能删除" @@ -3111,7 +3117,7 @@ msgstr "内部角色,不能删除" msgid "The role has been bound to users, can't be destroy" msgstr "角色已绑定用户,不能删除" -#: rbac/api/role.py:45 +#: rbac/api/role.py:60 msgid "Internal role, can't be update" msgstr "内部角色,不能更新" @@ -3289,10 +3295,6 @@ msgstr "资产改密" msgid "Terminal setting" msgstr "终端设置" -#: rbac/tree.py:59 -msgid "My assets" -msgstr "我的资产" - #: rbac/tree.py:60 msgid "My apps" msgstr "我的应用" @@ -4221,7 +4223,7 @@ msgid "Enable database proxy" msgstr "启用数据库组件" #: settings/serializers/terminal.py:37 -msgid "Enable XRDP" +msgid "Enable Razor" msgstr "启用 XRDP 服务" #: settings/serializers/terminal.py:38 @@ -5622,27 +5624,27 @@ msgstr "最后更新密码日期" msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:885 +#: users/models/user.py:890 msgid "Can invite user" msgstr "可以邀请用户" -#: users/models/user.py:886 +#: users/models/user.py:891 msgid "Can remove user" msgstr "可以移除用户" -#: users/models/user.py:887 +#: users/models/user.py:892 msgid "Can match user" msgstr "可以匹配用户" -#: users/models/user.py:896 +#: users/models/user.py:901 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:899 +#: users/models/user.py:904 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models/user.py:924 +#: users/models/user.py:929 msgid "User password history" msgstr "用户密码历史" @@ -6737,6 +6739,9 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Please verify your password first" +#~ msgstr "请检查密码" + #~ msgid "AccessKey ID" #~ msgstr "Access key ID" diff --git a/apps/locale/zh/LC_MESSAGES/djangojs.po b/apps/locale/zh/LC_MESSAGES/djangojs.po index 0cae14858..deddcbe25 100644 --- a/apps/locale/zh/LC_MESSAGES/djangojs.po +++ b/apps/locale/zh/LC_MESSAGES/djangojs.po @@ -3,7 +3,6 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index 54d3f188e..4a7baf318 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -9,14 +9,16 @@ from notifications.notifications import UserMessage class PermedAssetsWillExpireUserMsg(UserMessage): - def __init__(self, user, assets): + def __init__(self, user, assets, day_count=0): super().__init__(user) self.assets = assets + self.count = day_count def get_html_msg(self) -> dict: subject = _("You permed assets is about to expire") context = { 'name': self.user.name, + 'day_count': self.day_count, 'items': [str(asset) for asset in self.assets], 'item_type': _("permed assets"), 'show_help': True @@ -38,10 +40,11 @@ class PermedAssetsWillExpireUserMsg(UserMessage): class AssetPermsWillExpireForOrgAdminMsg(UserMessage): - def __init__(self, user, perms, org): + def __init__(self, user, perms, org, day_count=0): super().__init__(user) self.perms = perms self.org = org + self.count = day_count def get_items_with_url(self): items_with_url = [] @@ -59,6 +62,7 @@ class AssetPermsWillExpireForOrgAdminMsg(UserMessage): subject = _("Asset permissions is about to expire") context = { 'name': self.user.name, + 'day_count': self.day_count, 'items_with_url': items_with_url, 'item_type': _('asset permissions of organization {}').format(self.org) } @@ -81,14 +85,16 @@ class AssetPermsWillExpireForOrgAdminMsg(UserMessage): class PermedAppsWillExpireUserMsg(UserMessage): - def __init__(self, user, apps): + def __init__(self, user, apps, day_count=0): super().__init__(user) self.apps = apps + self.count = day_count def get_html_msg(self) -> dict: subject = _("Your permed applications is about to expire") context = { 'name': self.user.name, + 'day_count': self.day_count, 'item_type': _('permed applications'), 'items': [str(app) for app in self.apps] } @@ -109,10 +115,11 @@ class PermedAppsWillExpireUserMsg(UserMessage): class AppPermsWillExpireForOrgAdminMsg(UserMessage): - def __init__(self, user, perms, org): + def __init__(self, user, perms, org, day_count=0): super().__init__(user) self.perms = perms self.org = org + self.count = day_count def get_items_with_url(self): items_with_url = [] diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 2f5edafed..237eac5ac 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -63,8 +63,8 @@ def check_asset_permission_will_expired(): start = local_now() end = start + timedelta(days=3) - user_asset_mapper = defaultdict(set) - org_perm_mapper = defaultdict(set) + user_asset_remain_day_mapper = defaultdict(dict) + org_perm_remain_day_mapper = defaultdict(dict) asset_perms = AssetPermission.objects.filter( date_expired__gte=start, @@ -72,23 +72,35 @@ def check_asset_permission_will_expired(): ).distinct() for asset_perm in asset_perms: + date_expired = dt_parser(asset_perm.date_expired) + remain_days = (end - date_expired).days + + org = asset_perm.org # 资产授权按照组织分类 - org_perm_mapper[asset_perm.org].add(asset_perm) + if org in org_perm_remain_day_mapper[remain_days]: + org_perm_remain_day_mapper[remain_days][org].add(asset_perm) + else: + org_perm_remain_day_mapper[remain_days][org] = set() # 计算每个用户即将过期的资产 users = asset_perm.get_all_users() assets = asset_perm.get_all_assets() for u in users: - user_asset_mapper[u].update(assets) + if u in user_asset_remain_day_mapper[remain_days]: + user_asset_remain_day_mapper[remain_days][u].update(assets) + else: + user_asset_remain_day_mapper[remain_days][u] = set() - for user, assets in user_asset_mapper.items(): - PermedAssetsWillExpireUserMsg(user, assets).publish_async() + for day_count, user_asset_mapper in user_asset_remain_day_mapper.items(): + for user, assets in user_asset_mapper.items(): + PermedAssetsWillExpireUserMsg(user, assets, day_count).publish_async() - for org, perms in org_perm_mapper.items(): - org_admins = org.admins.all() - for org_admin in org_admins: - AssetPermsWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async() + for day_count, org_perm_mapper in org_perm_remain_day_mapper.items(): + for org, perms in org_perm_mapper.items(): + org_admins = org.admins.all() + for org_admin in org_admins: + AssetPermsWillExpireForOrgAdminMsg(org_admin, perms, org, day_count).publish_async() @register_as_period_task(crontab='0 10 * * *') @@ -104,21 +116,33 @@ def check_app_permission_will_expired(): date_expired__lte=end ).distinct() - user_app_mapper = defaultdict(set) - org_perm_mapper = defaultdict(set) + user_app_remain_day_mapper = defaultdict(dict) + org_perm_remain_day_mapper = defaultdict(dict) for app_perm in app_perms: - org_perm_mapper[app_perm.org].add(app_perm) + date_expired = dt_parser(app_perm.date_expired) + remain_days = (end - date_expired).days + + org = app_perm.org + if org in org_perm_remain_day_mapper[remain_days]: + org_perm_remain_day_mapper[remain_days][org].add(app_perm) + else: + org_perm_remain_day_mapper[remain_days][org] = set() users = app_perm.get_all_users() apps = app_perm.applications.all() for u in users: - user_app_mapper[u].update(apps) + if u in user_app_remain_day_mapper[remain_days]: + user_app_remain_day_mapper[remain_days][u].update(apps) + else: + user_app_remain_day_mapper[remain_days][u] = set() - for user, apps in user_app_mapper.items(): - PermedAppsWillExpireUserMsg(user, apps).publish_async() + for day_count, user_app_mapper in user_app_remain_day_mapper.items(): + for user, apps in user_app_mapper.items(): + PermedAppsWillExpireUserMsg(user, apps, day_count).publish_async() - for org, perms in org_perm_mapper.items(): - org_admins = org.admins.all() - for org_admin in org_admins: - AppPermsWillExpireForOrgAdminMsg(org_admin, perms, org).publish_async() + for day_count, org_perm_mapper in org_perm_remain_day_mapper.items(): + for org, perms in org_perm_mapper.items(): + org_admins = org.admins.all() + for org_admin in org_admins: + AppPermsWillExpireForOrgAdminMsg(org_admin, perms, org, day_count).publish_async() diff --git a/apps/perms/templates/perms/_msg_item_permissions_expire.html b/apps/perms/templates/perms/_msg_item_permissions_expire.html index 139f958b3..aab2a3650 100644 --- a/apps/perms/templates/perms/_msg_item_permissions_expire.html +++ b/apps/perms/templates/perms/_msg_item_permissions_expire.html @@ -5,7 +5,7 @@

    {% blocktranslate %} - The following {{ item_type }} will expire in 3 days + The following {{ item_type }} will expire in %(count)s days {% endblocktranslate %}

    diff --git a/apps/perms/templates/perms/_msg_permed_items_expire.html b/apps/perms/templates/perms/_msg_permed_items_expire.html index f50c59933..1e5ae7b18 100644 --- a/apps/perms/templates/perms/_msg_permed_items_expire.html +++ b/apps/perms/templates/perms/_msg_permed_items_expire.html @@ -5,7 +5,7 @@

    {% blocktranslate %} - The following {{ item_type }} will expire in 3 days + The following {{ item_type }} will expire in %(count)s days {% endblocktranslate %}

    From 2aebfa51b2ec29456fbdd9cea45d18ddca773c2b Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 14 Jun 2022 18:04:53 +0800 Subject: [PATCH 158/258] =?UTF-8?q?fix:=20=E8=BF=87=E6=BB=A4=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=E5=AF=86=E7=A0=81=E8=BF=87=E6=BB=A4?= =?UTF-8?q?ansible=E4=B8=8D=E6=94=AF=E6=8C=81=E7=9A=84=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/base.py | 4 ++-- apps/assets/serializers/system_user.py | 4 ++-- apps/assets/serializers/utils.py | 10 +++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 92cc65f19..2c5e579b6 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -8,7 +8,7 @@ from rest_framework import serializers from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key from common.drf.fields import EncryptedField from assets.models import Type -from .utils import validate_password_contains_left_double_curly_bracket +from .utils import validate_password_for_ansible class AuthSerializer(serializers.ModelSerializer): @@ -35,7 +35,7 @@ class AuthSerializer(serializers.ModelSerializer): class AuthSerializerMixin(serializers.ModelSerializer): password = EncryptedField( label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, - validators=[validate_password_contains_left_double_curly_bracket] + validators=[validate_password_for_ansible] ) private_key = EncryptedField( label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, max_length=4096 diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 68ade0ebd..54aff3e82 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -9,7 +9,7 @@ from common.drf.serializers import SecretReadableMixin from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import SystemUser, Asset -from .utils import validate_password_contains_left_double_curly_bracket +from .utils import validate_password_for_ansible from .base import AuthSerializerMixin __all__ = [ @@ -27,7 +27,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): """ password = EncryptedField( label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, - trim_whitespace=False, validators=[validate_password_contains_left_double_curly_bracket], + trim_whitespace=False, validators=[validate_password_for_ansible], write_only=True ) auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True) diff --git a/apps/assets/serializers/utils.py b/apps/assets/serializers/utils.py index 9110a9978..52527e723 100644 --- a/apps/assets/serializers/utils.py +++ b/apps/assets/serializers/utils.py @@ -2,8 +2,16 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -def validate_password_contains_left_double_curly_bracket(password): +def validate_password_for_ansible(password): + """ 校验 Ansible 不支持的特殊字符 """ # validate password contains left double curly bracket # check password not contains `{{` + # Ansible 推送的时候不支持 if '{{' in password: raise serializers.ValidationError(_('Password can not contains `{{` ')) + # Ansible Windows 推送的时候不支持 + if "'" in password: + raise serializers.ValidationError(_("Password can not contains `'` ")) + if '"' in password: + raise serializers.ValidationError(_('Password can not contains `"` ')) + From 2414f34a5a9713bf3cf33da3e9de2d4507112d25 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 14 Jun 2022 19:59:00 +0800 Subject: [PATCH 159/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20apt=20(#83?= =?UTF-8?q?98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pref: 修改 oracle lib path * perf: 优化 apt Co-authored-by: ibuler --- Dockerfile | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index dfbfbe0eb..de4022338 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,11 +29,12 @@ ARG TOOLS=" \ redis-tools \ telnet \ vim \ + unzip \ wget" RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \ && sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \ - && apt update \ + && apt update && sleep 1 && apt update \ && apt -y install ${BUILD_DEPENDENCIES} \ && apt -y install ${DEPENDENCIES} \ && apt -y install ${TOOLS} \ @@ -47,12 +48,19 @@ RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list \ && mv /bin/sh /bin/sh.bak \ && ln -s /bin/bash /bin/sh +ARG TARGETARCH +ARG ORACLE_LIB_MAJOR=19 +ARG ORACLE_LIB_MINOR=10 +ENV ORACLE_FILE="instantclient-basiclite-linux.${TARGETARCH:-amd64}-${ORACLE_LIB_MAJOR}.${ORACLE_LIB_MINOR}.0.0.0dbru.zip" + RUN mkdir -p /opt/oracle/ \ - && wget https://download.jumpserver.org/public/instantclient-basiclite-linux.x64-21.1.0.0.0.tar \ - && tar xf instantclient-basiclite-linux.x64-21.1.0.0.0.tar -C /opt/oracle/ \ - && echo "/opt/oracle/instantclient_21_1" > /etc/ld.so.conf.d/oracle-instantclient.conf \ + && cd /opt/oracle/ \ + && wget https://download.jumpserver.org/files/oracle/${ORACLE_FILE} \ + && unzip instantclient-basiclite-linux.${TARGETARCH-amd64}-19.10.0.0.0dbru.zip \ + && mv instantclient_${ORACLE_LIB_MAJOR}_${ORACLE_LIB_MINOR} instantclient \ + && echo "/opt/oracle/instantclient" > /etc/ld.so.conf.d/oracle-instantclient.conf \ && ldconfig \ - && rm -f instantclient-basiclite-linux.x64-21.1.0.0.0.tar + && rm -f ${ORACLE_FILE} WORKDIR /tmp/build COPY ./requirements ./requirements From e0a2d03f447e1eeac8afcf6bd51a5d5f37798c8c Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:01:56 +0800 Subject: [PATCH 160/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E8=BF=87=E6=9C=9F=E9=80=9A=E7=9F=A5bug=20(#8404)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng626 <1304903146@qq.com> --- apps/perms/notifications.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index 4a7baf318..a6316eb8a 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -12,13 +12,13 @@ class PermedAssetsWillExpireUserMsg(UserMessage): def __init__(self, user, assets, day_count=0): super().__init__(user) self.assets = assets - self.count = day_count + self.day_count = day_count def get_html_msg(self) -> dict: subject = _("You permed assets is about to expire") context = { 'name': self.user.name, - 'day_count': self.day_count, + 'count': self.day_count, 'items': [str(asset) for asset in self.assets], 'item_type': _("permed assets"), 'show_help': True @@ -44,7 +44,7 @@ class AssetPermsWillExpireForOrgAdminMsg(UserMessage): super().__init__(user) self.perms = perms self.org = org - self.count = day_count + self.day_count = day_count def get_items_with_url(self): items_with_url = [] @@ -62,7 +62,7 @@ class AssetPermsWillExpireForOrgAdminMsg(UserMessage): subject = _("Asset permissions is about to expire") context = { 'name': self.user.name, - 'day_count': self.day_count, + 'count': self.day_count, 'items_with_url': items_with_url, 'item_type': _('asset permissions of organization {}').format(self.org) } @@ -88,13 +88,13 @@ class PermedAppsWillExpireUserMsg(UserMessage): def __init__(self, user, apps, day_count=0): super().__init__(user) self.apps = apps - self.count = day_count + self.day_count = day_count def get_html_msg(self) -> dict: subject = _("Your permed applications is about to expire") context = { 'name': self.user.name, - 'day_count': self.day_count, + 'count': self.day_count, 'item_type': _('permed applications'), 'items': [str(app) for app in self.apps] } @@ -119,7 +119,7 @@ class AppPermsWillExpireForOrgAdminMsg(UserMessage): super().__init__(user) self.perms = perms self.org = org - self.count = day_count + self.day_count = day_count def get_items_with_url(self): items_with_url = [] @@ -134,6 +134,7 @@ class AppPermsWillExpireForOrgAdminMsg(UserMessage): subject = _('Application permissions is about to expire') context = { 'name': self.user.name, + 'count': self.day_count, 'item_type': _('application permissions of organization {}').format(self.org), 'items_with_url': items } From a882ca0d5143800022cb4ee43299fddb1edce79a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 15 Jun 2022 15:15:54 +0800 Subject: [PATCH 161/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E7=B3=BB=E7=BB=9F=E7=94=A8=E6=88=B7=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks/push_system_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py index ee4af0fd5..cd32d8e15 100644 --- a/apps/assets/tasks/push_system_user.py +++ b/apps/assets/tasks/push_system_user.py @@ -274,7 +274,7 @@ def push_system_user_a_asset_manual(system_user, asset, username=None): # if username is None: # username = system_user.username task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format( - system_user.name, username, asset + system_user.name, username or '', asset ) return push_system_user_util(system_user, [asset], task_name=task_name, username=username) From 75c011f1c526c0424319e03a7d7147a3d4d99bce Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 15 Jun 2022 15:27:13 +0800 Subject: [PATCH 162/258] feat: add client linux arm64 version --- apps/templates/resource_download.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/templates/resource_download.html b/apps/templates/resource_download.html index 57570eb8e..dddb1143c 100644 --- a/apps/templates/resource_download.html +++ b/apps/templates/resource_download.html @@ -22,7 +22,8 @@ p {
    From 10adb4e6b776cedea8eed4704385251ba27c277b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 15 Jun 2022 10:31:20 +0800 Subject: [PATCH 163/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/drf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/backends/drf.py b/apps/authentication/backends/drf.py index 595ae35b6..10c191b1b 100644 --- a/apps/authentication/backends/drf.py +++ b/apps/authentication/backends/drf.py @@ -198,6 +198,6 @@ class SignatureAuthentication(signature.SignatureAuthentication): return None, None user, secret = key.user, str(key.secret) return user, secret - except AccessKey.DoesNotExist: + except (AccessKey.DoesNotExist, exceptions.ValidationError): return None, None From 8c839784fb10f203a69479e23c78462579994fcc Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 15 Jun 2022 15:13:51 +0800 Subject: [PATCH 164/258] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E8=8E=B7=E5=8F=96=E5=88=B0=E8=8A=82=E7=82=B9=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/tree/app.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/perms/tree/app.py b/apps/perms/tree/app.py index 0b74c1e9b..6a6d6c74f 100644 --- a/apps/perms/tree/app.py +++ b/apps/perms/tree/app.py @@ -36,6 +36,22 @@ class GrantedAppTreeUtil: }) return node + @staticmethod + def create_empty_node(): + name = _("Empty") + node = TreeNode(**{ + 'id': 'empty', + 'name': name, + 'title': name, + 'pId': '', + 'isParent': True, + 'children': [], + 'meta': { + 'type': 'application' + } + }) + return node + @staticmethod def get_children_nodes(tree_id, parent_info, user): tree_nodes = [] @@ -61,7 +77,7 @@ class GrantedAppTreeUtil: def create_tree_nodes(self, applications): tree_nodes = [] if not applications: - return tree_nodes + return [self.create_empty_node()] root_node = self.create_root_node() organizations = self.filter_organizations(applications) From 9e16b79abe07ed35e69d687fdd32fdb0c6f3289a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 15 Jun 2022 19:27:51 +0800 Subject: [PATCH 165/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dopenid?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E6=97=B6=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E5=90=8E=E7=BC=80=E4=BD=BF=E7=94=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/oidc/backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index ec5765510..9866e84f3 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -46,7 +46,7 @@ class UserMixin: for field, attr in settings.AUTH_OPENID_USER_ATTR_MAP.items(): user_attrs[field] = claims.get(attr, sub) email = user_attrs.get('email', '') - email = construct_user_email(user_attrs.get('username'), email, 'jumpserver.openid') + email = construct_user_email(user_attrs.get('username'), email) user_attrs.update({'email': email}) logger.debug(log_prompt.format(user_attrs)) From a024f267684c1f36dc8d2ed0e514da59000be7c7 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 16 Jun 2022 10:35:32 +0800 Subject: [PATCH 166/258] =?UTF-8?q?fix:=20=E6=8E=88=E6=9D=83=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E6=B6=88=E6=81=AF=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 60 ++++++++++--------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 53 ++++++++++------ .../perms/_msg_item_permissions_expire.html | 2 +- .../perms/_msg_permed_items_expire.html | 2 +- 6 files changed, 73 insertions(+), 52 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 50947cea2..d528ec14b 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a0e0ef94fd1cf5b3d41c378b54e8af2fb502c7f1e9d6a3e54084e8113978561 -size 127495 +oid sha256:f3bc03d3cc4de571f20944b3db7e9d89ffac34cd261bc9bbabc8d3d41e4157a5 +size 128122 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 11310178e..d3859856a 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-14 15:48+0800\n" +"POT-Creation-Date: 2022-06-16 10:20+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -665,7 +665,7 @@ msgstr "すべて" #: assets/models/backup.py:52 assets/serializers/backup.py:32 #: xpack/plugins/change_auth_plan/models/app.py:41 #: xpack/plugins/change_auth_plan/models/asset.py:62 -#: xpack/plugins/change_auth_plan/serializers/base.py:49 +#: xpack/plugins/change_auth_plan/serializers/base.py:45 msgid "Recipient" msgstr "受信者" @@ -708,7 +708,7 @@ msgstr "アカウントのバックアップスナップショット" #: assets/models/backup.py:116 assets/serializers/backup.py:40 #: xpack/plugins/change_auth_plan/models/base.py:125 -#: xpack/plugins/change_auth_plan/serializers/base.py:82 +#: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "トリガーモード" @@ -770,8 +770,8 @@ msgstr "確認済みの日付" #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/models/base.py:196 -#: xpack/plugins/change_auth_plan/serializers/base.py:22 -#: xpack/plugins/change_auth_plan/serializers/base.py:77 +#: xpack/plugins/change_auth_plan/serializers/base.py:21 +#: xpack/plugins/change_auth_plan/serializers/base.py:73 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Password" msgstr "パスワード" @@ -1118,12 +1118,12 @@ msgstr "アクション" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 #: settings/serializers/auth/ldap.py:65 -#: xpack/plugins/change_auth_plan/serializers/base.py:47 +#: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定期的なパフォーマンス" #: assets/serializers/backup.py:33 -#: xpack/plugins/change_auth_plan/serializers/base.py:50 +#: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" @@ -1248,10 +1248,18 @@ msgstr "組織名" msgid "Asset hostname" msgstr "資産ホスト名" -#: assets/serializers/utils.py:9 +#: assets/serializers/utils.py:11 msgid "Password can not contains `{{` " msgstr "パスワードには '{{' を含まない" +#: assets/serializers/utils.py:14 +msgid "Password can not contains `'` " +msgstr "パスワードには '{{' を含まない" + +#: assets/serializers/utils.py:16 +msgid "Password can not contains `\"` " +msgstr "パスワードには '{{' を含まない" + #: assets/tasks/account_connectivity.py:30 msgid "The asset {} system platform {} does not support run Ansible tasks" msgstr "" @@ -1759,8 +1767,6 @@ msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}" #: authentication/api/confirm.py:40 -#, fuzzy -#| msgid "Authentication failed (username or password incorrect): {}" msgid "Authentication failed password incorrect" msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" @@ -3070,7 +3076,7 @@ msgstr "Permedアプリケーション" msgid "Application permissions is about to expire" msgstr "アプリケーション権限の有効期限が近づいています" -#: perms/notifications.py:137 +#: perms/notifications.py:138 msgid "application permissions of organization {}" msgstr "Organization {} のアプリケーション権限" @@ -3128,14 +3134,10 @@ msgstr "システムユーザーの表示" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 -#, fuzzy, python-format -#| msgid "" -#| "\n" -#| " The following %(item_type)s will expire in %(count)s days\n" -#| " " +#, python-format msgid "" "\n" -" The following %(item_type)s will expire in %%(count)s days\n" +" The following %(item_type)s will expire in %(count)s days\n" " " msgstr "" "\n" @@ -3150,6 +3152,10 @@ msgstr "質問があったら、管理者に連絡して下さい" msgid "My applications" msgstr "私のアプリケーション" +#: perms/tree/app.py:41 +msgid "Empty" +msgstr "空" + #: perms/utils/asset/user_permission.py:620 rbac/tree.py:59 msgid "My assets" msgstr "私の資産" @@ -4292,8 +4298,6 @@ msgid "Enable database proxy" msgstr "属性マップの有効化" #: settings/serializers/terminal.py:37 -#, fuzzy -#| msgid "Enable XRDP" msgid "Enable Razor" msgstr "XRDPの有効化" @@ -4564,7 +4568,7 @@ msgstr "ホームページ" msgid "Cancel" msgstr "キャンセル" -#: templates/resource_download.html:18 templates/resource_download.html:30 +#: templates/resource_download.html:18 templates/resource_download.html:31 msgid "Client" msgstr "クライアント" @@ -4577,15 +4581,15 @@ msgstr "" "るために使用されており、現在はRDP SSHクライアントのみをサポートしています。" "「Telnetは将来的にサポートする" -#: templates/resource_download.html:30 +#: templates/resource_download.html:31 msgid "Microsoft" msgstr "マイクロソフト" -#: templates/resource_download.html:30 +#: templates/resource_download.html:31 msgid "Official" msgstr "公式" -#: templates/resource_download.html:32 +#: templates/resource_download.html:33 msgid "" "macOS needs to download the client to connect RDP asset, which comes with " "Windows" @@ -4593,11 +4597,11 @@ msgstr "" "MacOSは、Windowsに付属のRDPアセットを接続するためにクライアントをダウンロード" "する必要があります" -#: templates/resource_download.html:41 +#: templates/resource_download.html:42 msgid "Windows Remote application publisher tools" msgstr "Windowsリモートアプリケーション発行者ツール" -#: templates/resource_download.html:42 +#: templates/resource_download.html:43 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -6281,15 +6285,15 @@ msgstr "パスワードの変更" msgid "Change SSH Key" msgstr "SSHキーの変更" -#: xpack/plugins/change_auth_plan/serializers/base.py:48 +#: xpack/plugins/change_auth_plan/serializers/base.py:44 msgid "Run times" msgstr "実行時間" -#: xpack/plugins/change_auth_plan/serializers/base.py:62 +#: xpack/plugins/change_auth_plan/serializers/base.py:58 msgid "* Please enter the correct password length" msgstr "* 正しいパスワードの長さを入力してください" -#: xpack/plugins/change_auth_plan/serializers/base.py:65 +#: xpack/plugins/change_auth_plan/serializers/base.py:61 msgid "* Password length range 6-30 bits" msgstr "* パスワードの長さの範囲6-30ビット" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e5b16f047..80cdf63bd 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ab23fa4d87b928318281150ff64d9c2e3782a9169a3c3e6907808b1be63dbc3 -size 105365 +oid sha256:db8f93ff4f52dd61dd019fa9f615eb0bef6cfcf6fc0db01a08db7e2f86ca8c82 +size 105699 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 634bea865..c0ce1706c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-14 15:48+0800\n" +"POT-Creation-Date: 2022-06-16 10:20+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -660,7 +660,7 @@ msgstr "全部" #: assets/models/backup.py:52 assets/serializers/backup.py:32 #: xpack/plugins/change_auth_plan/models/app.py:41 #: xpack/plugins/change_auth_plan/models/asset.py:62 -#: xpack/plugins/change_auth_plan/serializers/base.py:49 +#: xpack/plugins/change_auth_plan/serializers/base.py:45 msgid "Recipient" msgstr "收件人" @@ -703,7 +703,7 @@ msgstr "账号备份快照" #: assets/models/backup.py:116 assets/serializers/backup.py:40 #: xpack/plugins/change_auth_plan/models/base.py:125 -#: xpack/plugins/change_auth_plan/serializers/base.py:82 +#: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "触发模式" @@ -765,8 +765,8 @@ msgstr "校验日期" #: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/models/base.py:196 -#: xpack/plugins/change_auth_plan/serializers/base.py:22 -#: xpack/plugins/change_auth_plan/serializers/base.py:77 +#: xpack/plugins/change_auth_plan/serializers/base.py:21 +#: xpack/plugins/change_auth_plan/serializers/base.py:73 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Password" msgstr "密码" @@ -1110,12 +1110,12 @@ msgstr "动作" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 #: settings/serializers/auth/ldap.py:65 -#: xpack/plugins/change_auth_plan/serializers/base.py:47 +#: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定时执行" #: assets/serializers/backup.py:33 -#: xpack/plugins/change_auth_plan/serializers/base.py:50 +#: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" @@ -1240,10 +1240,22 @@ msgstr "组织名称" msgid "Asset hostname" msgstr "资产主机名" -#: assets/serializers/utils.py:9 +#: assets/serializers/utils.py:11 msgid "Password can not contains `{{` " msgstr "密码不能包含 `{{` 字符" +#: assets/serializers/utils.py:14 +#, fuzzy +#| msgid "Password can not contains `{{` " +msgid "Password can not contains `'` " +msgstr "密码不能包含 `{{` 字符" + +#: assets/serializers/utils.py:16 +#, fuzzy +#| msgid "Password can not contains `{{` " +msgid "Password can not contains `\"` " +msgstr "密码不能包含 `{{` 字符" + #: assets/tasks/account_connectivity.py:30 msgid "The asset {} system platform {} does not support run Ansible tasks" msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" @@ -3032,7 +3044,7 @@ msgstr "授权的应用" msgid "Application permissions is about to expire" msgstr "应用授权规则即将过期" -#: perms/notifications.py:137 +#: perms/notifications.py:138 msgid "application permissions of organization {}" msgstr "组织 ({}) 的应用授权" @@ -3088,6 +3100,7 @@ msgstr "系统用户名称" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 +#, python-format msgid "" "\n" " The following %(item_type)s will expire in %(count)s days\n" @@ -3105,6 +3118,10 @@ msgstr "如果有疑问或需求,请联系系统管理员" msgid "My applications" msgstr "我的应用" +#: perms/tree/app.py:41 +msgid "Empty" +msgstr "空" + #: perms/utils/asset/user_permission.py:620 rbac/tree.py:59 msgid "My assets" msgstr "我的资产" @@ -4488,7 +4505,7 @@ msgstr "首页" msgid "Cancel" msgstr "取消" -#: templates/resource_download.html:18 templates/resource_download.html:30 +#: templates/resource_download.html:18 templates/resource_download.html:31 msgid "Client" msgstr "客户端" @@ -4500,25 +4517,25 @@ msgstr "" "JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP SSH " "客户端,Telnet 会在未来支持" -#: templates/resource_download.html:30 +#: templates/resource_download.html:31 msgid "Microsoft" msgstr "微软" -#: templates/resource_download.html:30 +#: templates/resource_download.html:31 msgid "Official" msgstr "官方" -#: templates/resource_download.html:32 +#: templates/resource_download.html:33 msgid "" "macOS needs to download the client to connect RDP asset, which comes with " "Windows" msgstr "macOS 需要下载客户端来连接 RDP 资产,Windows 系统默认安装了该程序" -#: templates/resource_download.html:41 +#: templates/resource_download.html:42 msgid "Windows Remote application publisher tools" msgstr "Windows 远程应用发布服务器工具" -#: templates/resource_download.html:42 +#: templates/resource_download.html:43 msgid "" "Jmservisor is the program used to pull up remote applications in Windows " "Remote Application publisher" @@ -6181,15 +6198,15 @@ msgstr "更改密码" msgid "Change SSH Key" msgstr "修改 SSH Key" -#: xpack/plugins/change_auth_plan/serializers/base.py:48 +#: xpack/plugins/change_auth_plan/serializers/base.py:44 msgid "Run times" msgstr "执行次数" -#: xpack/plugins/change_auth_plan/serializers/base.py:62 +#: xpack/plugins/change_auth_plan/serializers/base.py:58 msgid "* Please enter the correct password length" msgstr "* 请输入正确的密码长度" -#: xpack/plugins/change_auth_plan/serializers/base.py:65 +#: xpack/plugins/change_auth_plan/serializers/base.py:61 msgid "* Password length range 6-30 bits" msgstr "* 密码长度范围 6-30 位" diff --git a/apps/perms/templates/perms/_msg_item_permissions_expire.html b/apps/perms/templates/perms/_msg_item_permissions_expire.html index aab2a3650..9a9dc8244 100644 --- a/apps/perms/templates/perms/_msg_item_permissions_expire.html +++ b/apps/perms/templates/perms/_msg_item_permissions_expire.html @@ -5,7 +5,7 @@

    {% blocktranslate %} - The following {{ item_type }} will expire in %(count)s days + The following {{ item_type }} will expire in {{ count }} days {% endblocktranslate %}

    diff --git a/apps/perms/templates/perms/_msg_permed_items_expire.html b/apps/perms/templates/perms/_msg_permed_items_expire.html index 1e5ae7b18..f4229b7eb 100644 --- a/apps/perms/templates/perms/_msg_permed_items_expire.html +++ b/apps/perms/templates/perms/_msg_permed_items_expire.html @@ -5,7 +5,7 @@

    {% blocktranslate %} - The following {{ item_type }} will expire in %(count)s days + The following {{ item_type }} will expire in {{ count }} days {% endblocktranslate %}

    From 4c2274b14e7873bb84d326d4210970ddac15f27f Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 16 Jun 2022 11:18:27 +0800 Subject: [PATCH 167/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 ++-- apps/locale/ja/LC_MESSAGES/django.po | 22 +++------------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 24 +++--------------------- 4 files changed, 10 insertions(+), 44 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index d528ec14b..4618d1b80 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3bc03d3cc4de571f20944b3db7e9d89ffac34cd261bc9bbabc8d3d41e4157a5 -size 128122 +oid sha256:449dcbd8e4b4f133d45e01642bb42a20477b09309d2b916f1aff9e854e6864e8 +size 128120 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index d3859856a..c79aac704 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-16 10:20+0800\n" +"POT-Creation-Date: 2022-06-16 11:12+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1254,11 +1254,11 @@ msgstr "パスワードには '{{' を含まない" #: assets/serializers/utils.py:14 msgid "Password can not contains `'` " -msgstr "パスワードには '{{' を含まない" +msgstr "パスワードには `'` を含まない" #: assets/serializers/utils.py:16 msgid "Password can not contains `\"` " -msgstr "パスワードには '{{' を含まない" +msgstr "パスワードには `\"` を含まない" #: assets/tasks/account_connectivity.py:30 msgid "The asset {} system platform {} does not support run Ansible tasks" @@ -6843,19 +6843,3 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" - -#~ msgid "Please verify your password first" -#~ msgstr "最初にパスワードを確認してください" - -#~ msgid "AccessKey ID" -#~ msgstr "アクセスキーID" - -#~ msgid "Unknown ip" -#~ msgstr "不明なip" - -#~ msgid "" -#~ "Windows needs to download the client to connect SSH assets, and the MacOS " -#~ "system uses its own terminal" -#~ msgstr "" -#~ "WindowsはクライアントをダウンロードしてSSH資産に接続する必要があり、macOS" -#~ "システムは独自のTerminalを採用している。" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 80cdf63bd..a35dad64a 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db8f93ff4f52dd61dd019fa9f615eb0bef6cfcf6fc0db01a08db7e2f86ca8c82 -size 105699 +oid sha256:2ea5daa59f376f85ad67dde8332499637e0e82999dcf051853589d1ebf22eca3 +size 105893 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index c0ce1706c..3b3aeb02f 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-16 10:20+0800\n" +"POT-Creation-Date: 2022-06-16 11:12+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -1245,16 +1245,12 @@ msgid "Password can not contains `{{` " msgstr "密码不能包含 `{{` 字符" #: assets/serializers/utils.py:14 -#, fuzzy -#| msgid "Password can not contains `{{` " msgid "Password can not contains `'` " -msgstr "密码不能包含 `{{` 字符" +msgstr "密码不能包含 `'` 字符" #: assets/serializers/utils.py:16 -#, fuzzy -#| msgid "Password can not contains `{{` " msgid "Password can not contains `\"` " -msgstr "密码不能包含 `{{` 字符" +msgstr "密码不能包含 `\"` 字符" #: assets/tasks/account_connectivity.py:30 msgid "The asset {} system platform {} does not support run Ansible tasks" @@ -6755,17 +6751,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" - -#~ msgid "Please verify your password first" -#~ msgstr "请检查密码" - -#~ msgid "AccessKey ID" -#~ msgstr "Access key ID" - -#~ msgid "Unknown ip" -#~ msgstr "未知ip" - -#~ msgid "" -#~ "Windows needs to download the client to connect SSH assets, and the MacOS " -#~ "system uses its own terminal" -#~ msgstr "Windows 需要下载客户端来连接SSH资产,macOS系统采用自带的Terminal" From 75a72fb18231b8be4baab37555f65ccf05ac5933 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 16 Jun 2022 11:28:34 +0800 Subject: [PATCH 168/258] fix: user confirm bug --- apps/authentication/api/confirm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/api/confirm.py b/apps/authentication/api/confirm.py index c77a1d533..9041f6e85 100644 --- a/apps/authentication/api/confirm.py +++ b/apps/authentication/api/confirm.py @@ -22,7 +22,7 @@ class ConfirmViewSet(ListCreateAPIView): def check(self, confirm_type: str): if confirm_type == ConfirmType.MFA: - return bool(MFAOtp(self.user).is_active()) + return self.user.mfa_enabled if confirm_type == ConfirmType.PASSWORD: return self.user.is_password_authenticate() From 2be74c4b84d27b4f63cd5cb8adf52461cb2706d5 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 16 Jun 2022 13:16:25 +0800 Subject: [PATCH 169/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=88=97=E8=A1=A8=E6=A8=A1=E7=B3=8A=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=8A=A5=E9=94=99500=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 修复命令列表模糊搜索报错500的问题 --- apps/terminal/backends/command/es.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index ae76b2974..1387f6656 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -297,6 +297,9 @@ class QuerySet(DJQuerySet): self._command_store_config = command_store_config self._storage = CommandStore(command_store_config) + # 命令列表模糊搜索时报错 + super().__init__() + @lazyproperty def _grouped_method_calls(self): _method_calls = {k: list(v) for k, v in groupby(self._method_calls, lambda x: x[0])} From 1e3da5097935cb6aad71a1b689d29a182d3c143d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 16 Jun 2022 16:47:17 +0800 Subject: [PATCH 170/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E5=8A=A0=E5=85=A5=E8=AE=B0=E5=BD=95=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/sharing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/terminal/api/sharing.py b/apps/terminal/api/sharing.py index 114a00da1..1a9545e54 100644 --- a/apps/terminal/api/sharing.py +++ b/apps/terminal/api/sharing.py @@ -43,6 +43,9 @@ class SessionJoinRecordsViewSet(OrgModelViewSet): ) filterset_fields = search_fields model = models.SessionJoinRecord + rbac_perms = { + 'finished': 'terminal.change_sessionjoinrecord' + } def create(self, request, *args, **kwargs): try: From adc8a8f7d3fbc64623fbf6a7c4dec499c6a1c79c Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 16 Jun 2022 14:19:58 +0800 Subject: [PATCH 171/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 ++-- apps/locale/ja/LC_MESSAGES/django.po | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 4618d1b80..d0d6aaf5d 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:449dcbd8e4b4f133d45e01642bb42a20477b09309d2b916f1aff9e854e6864e8 -size 128120 +oid sha256:132e7f59a56d1cf5b2358b21b547861e872fa456164f2e0809120fb2b13f0ec1 +size 128122 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index c79aac704..ab8d5b532 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -4299,7 +4299,7 @@ msgstr "属性マップの有効化" #: settings/serializers/terminal.py:37 msgid "Enable Razor" -msgstr "XRDPの有効化" +msgstr "Razor の有効化" #: settings/serializers/terminal.py:38 msgid "Enable SSH Client" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index a35dad64a..5bafa0526 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ea5daa59f376f85ad67dde8332499637e0e82999dcf051853589d1ebf22eca3 -size 105893 +oid sha256:002f6953ebbe368642f0ea3c383f617b5f998edf2238341be63393123d4be8a9 +size 105894 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 3b3aeb02f..874df532d 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -4237,7 +4237,7 @@ msgstr "启用数据库组件" #: settings/serializers/terminal.py:37 msgid "Enable Razor" -msgstr "启用 XRDP 服务" +msgstr "启用 Razor 服务" #: settings/serializers/terminal.py:38 msgid "Enable SSH Client" From 8e43e9ee2b08ee67eb9806a3096368c208677108 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 16 Jun 2022 17:49:40 +0800 Subject: [PATCH 172/258] =?UTF-8?q?fix:=20=E6=8E=88=E6=9D=83=E8=BF=87?= =?UTF-8?q?=E6=9C=9F=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 237eac5ac..da96beb68 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -80,7 +80,7 @@ def check_asset_permission_will_expired(): if org in org_perm_remain_day_mapper[remain_days]: org_perm_remain_day_mapper[remain_days][org].add(asset_perm) else: - org_perm_remain_day_mapper[remain_days][org] = set() + org_perm_remain_day_mapper[remain_days][org] = {asset_perm, } # 计算每个用户即将过期的资产 users = asset_perm.get_all_users() @@ -90,7 +90,7 @@ def check_asset_permission_will_expired(): if u in user_asset_remain_day_mapper[remain_days]: user_asset_remain_day_mapper[remain_days][u].update(assets) else: - user_asset_remain_day_mapper[remain_days][u] = set() + user_asset_remain_day_mapper[remain_days][u] = set(assets) for day_count, user_asset_mapper in user_asset_remain_day_mapper.items(): for user, assets in user_asset_mapper.items(): @@ -127,7 +127,7 @@ def check_app_permission_will_expired(): if org in org_perm_remain_day_mapper[remain_days]: org_perm_remain_day_mapper[remain_days][org].add(app_perm) else: - org_perm_remain_day_mapper[remain_days][org] = set() + org_perm_remain_day_mapper[remain_days][org] = {app_perm, } users = app_perm.get_all_users() apps = app_perm.applications.all() @@ -135,7 +135,7 @@ def check_app_permission_will_expired(): if u in user_app_remain_day_mapper[remain_days]: user_app_remain_day_mapper[remain_days][u].update(apps) else: - user_app_remain_day_mapper[remain_days][u] = set() + user_app_remain_day_mapper[remain_days][u] = set(apps) for day_count, user_app_mapper in user_app_remain_day_mapper.items(): for user, apps in user_app_mapper.items(): From 298f6ba41d63b8e7f56e7ec415da472cb84e6fca Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 16 Jun 2022 17:23:49 +0800 Subject: [PATCH 173/258] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.po | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index ab8d5b532..6b94dad82 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -1768,11 +1768,11 @@ msgstr "{ApplicationPermission} 削除 {SystemUser}" #: authentication/api/confirm.py:40 msgid "Authentication failed password incorrect" -msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" +msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません)" #: authentication/api/confirm.py:48 msgid "Login time has exceeded {} minutes, please login again" -msgstr "" +msgstr "ログイン時間が {} 分を超えました。もう一度ログインしてください" #: authentication/api/confirm.py:72 common/exceptions.py:47 msgid "This action require verify your MFA" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 874df532d..d0944cb51 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1756,11 +1756,11 @@ msgstr "{ApplicationPermission} 移除 {SystemUser}" #: authentication/api/confirm.py:40 msgid "Authentication failed password incorrect" -msgstr "认证失败 (用户名或密码不正确): {}" +msgstr "认证失败 (用户名或密码不正确)" #: authentication/api/confirm.py:48 msgid "Login time has exceeded {} minutes, please login again" -msgstr "" +msgstr "登录时长已超过 {} 分钟,请重新登录" #: authentication/api/confirm.py:72 common/exceptions.py:47 msgid "This action require verify your MFA" From 81598a526429f58a2d663cc1579808846eabe119 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 16 Jun 2022 18:02:43 +0800 Subject: [PATCH 174/258] =?UTF-8?q?perf:=20=E6=8E=A8=E9=80=81=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=E7=94=A8=E6=88=B7=E5=90=8D=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks/push_system_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py index cd32d8e15..8834a29e9 100644 --- a/apps/assets/tasks/push_system_user.py +++ b/apps/assets/tasks/push_system_user.py @@ -274,7 +274,7 @@ def push_system_user_a_asset_manual(system_user, asset, username=None): # if username is None: # username = system_user.username task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format( - system_user.name, username or '', asset + system_user.name, username or system_user.username, asset ) return push_system_user_util(system_user, [asset], task_name=task_name, username=username) From 3fde31f2e0a3c459fdcc852629b5cf2595974c04 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 17 Jun 2022 15:21:13 +0800 Subject: [PATCH 175/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E8=87=AA=E5=AE=9A=E4=B9=89=E6=90=9C=E7=B4=A2=E6=97=B6?= =?UTF-8?q?500=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/tickets/api/ticket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 586188898..00e30f899 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -27,7 +27,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): } filterset_class = TicketFilter search_fields = [ - 'title', 'action', 'type', 'status', 'applicant_display' + 'title', 'type', 'status', 'applicant_display' ] ordering_fields = ( 'title', 'applicant_display', 'status', 'state', 'action_display', From 710cd0fb3b2bf6359ac93d2695b53e18b6680f2c Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 20 Jun 2022 13:50:16 +0800 Subject: [PATCH 176/258] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8Des?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E7=B4=A2=E5=BC=95=E5=BF=BD=E7=95=A5=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/models/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/storage.py index 311a55fda..857d3c77d 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/storage.py @@ -84,7 +84,7 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): config = self.config if self.type_es and config.get('INDEX_BY_DATE'): engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type]) - store = engine_mod.CommandStore(config) + store = engine_mod.CommandStore(dict(**config)) store._ensure_index_exists() index_prefix = config.get('INDEX') or 'jumpserver' date = local_now_date_display() From 379c7198daa17b2095c27e03551768fecd531abf Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 20 Jun 2022 13:42:23 +0800 Subject: [PATCH 177/258] =?UTF-8?q?pref:=20=E5=8E=BB=E6=8E=89=20django-red?= =?UTF-8?q?is-cache=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8833b6e60..1dd12283b 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -21,7 +21,6 @@ django-celery-beat==2.2.1 django-filter==2.4.0 django-formtools==2.2 django-ranged-response==0.2.0 -django-redis-cache==2.1.1 django-rest-swagger==2.2.0 django-simple-captcha==0.5.13 django-timezone-field==4.1.0 From d1420de4c2d2d65f0f9713a08c573834c21c9fed Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 20 Jun 2022 14:37:31 +0800 Subject: [PATCH 178/258] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Des=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E5=91=BD=E4=BB=A4=E5=AD=98=E5=82=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=BF=BD=E7=95=A5=E8=AF=81=E4=B9=A6=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=B8=8D=E6=88=90=E5=8A=9F=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/models/storage.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/storage.py index 857d3c77d..4ddab1bcc 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/storage.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import copy import os from importlib import import_module @@ -77,14 +78,14 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): def config(self): config = self.meta config.update({'TYPE': self.type}) - return config + return copy.deepcopy(config) @property def valid_config(self): config = self.config if self.type_es and config.get('INDEX_BY_DATE'): engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type]) - store = engine_mod.CommandStore(dict(**config)) + store = engine_mod.CommandStore(config) store._ensure_index_exists() index_prefix = config.get('INDEX') or 'jumpserver' date = local_now_date_display() From 8b819f377993f97e4a377c42fe9d9bc5b0a7614c Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 20 Jun 2022 19:22:48 +0800 Subject: [PATCH 179/258] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=99=BB?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/base.py | 2 + .../templates/authentication/login.html | 44 +++--- apps/authentication/views/login.py | 28 +++- apps/common/utils/encode.py | 1 + apps/jumpserver/settings/base.py | 16 +- apps/jumpserver/settings/libs.py | 2 + requirements/requirements.txt | 139 +++++++++--------- 7 files changed, 129 insertions(+), 103 deletions(-) diff --git a/apps/authentication/backends/base.py b/apps/authentication/backends/base.py index 84cdeab27..351f9edc1 100644 --- a/apps/authentication/backends/base.py +++ b/apps/authentication/backends/base.py @@ -52,6 +52,8 @@ class JMSBaseAuthBackend: logger.debug(info) return allow +from redis_lock.django_cache import RedisCache +from redis import StrictRedis class JMSModelBackend(JMSBaseAuthBackend, ModelBackend): pass diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 823f22201..e5e4a7a4e 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -87,11 +87,11 @@ } .jms-title { - padding: 40px 10px 10px; + padding: 60px 10px 10px 60px; } .no-captcha-challenge .jms-title { - padding: 60px 10px 10px; + padding: 60px 10px 10px 60px; } .no-captcha-challenge .welcome-message { @@ -125,15 +125,27 @@ font-weight: 350 !important; min-height: auto !important; } + + .right-image { + height: 100%; + width: 100% + } + + .jms-title { + font-size: 21px; + font-weight:400; + color: #151515; + letter-spacing: 0; + text-align: left; + } -