From 2029e9f8df40bd9f14169e0007875a1cccad31f6 Mon Sep 17 00:00:00 2001 From: jiangweidong <80373698+Hi-JWD@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:22:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=97=A5=E5=BF=97=20(#8941)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:重构操作日志模块 * feat: 改密计划增加操作日志记录 * feat: 支持操作日志接入ES,且接口limit支持自定义限制大小 * feat:翻译 * feat: 生成迁移文件 * feat: 优化迁移文件 * feat: 优化多对多日志记录 * feat: 命令存储ES部分和日志存储ES部分代码优化 * feat: 优化敏感字段脱敏 Co-authored-by: Jiangjie.Bai --- .../migrations/0022_auto_20181012_1717.py | 4 +- apps/assets/models/cmd_filter.py | 4 +- apps/audits/api.py | 30 +- apps/audits/backends/__init__.py | 18 + apps/audits/backends/db.py | 38 + apps/audits/backends/es.py | 85 ++ apps/audits/const.py | 13 +- apps/audits/handler.py | 183 ++++ .../migrations/0015_auto_20221011_1745.py | 24 + apps/audits/models.py | 20 +- apps/audits/serializers.py | 6 + apps/audits/signal_handlers.py | 213 ++-- apps/audits/utils.py | 90 +- .../authentication/migrations/0001_initial.py | 3 +- .../migrations/0006_auto_20211227_1059.py | 4 +- apps/authentication/models.py | 8 +- apps/common/const/signals.py | 2 + apps/common/db/fields.py | 14 +- apps/common/db/models.py | 16 + apps/common/drf/fields.py | 2 + apps/common/local.py | 6 + apps/common/mixins/views.py | 14 +- apps/common/plugins/__init__.py | 0 apps/common/plugins/es.py | 428 ++++++++ apps/jumpserver/rewriting/pagination.py | 6 + apps/jumpserver/settings/custom.py | 4 + apps/jumpserver/settings/libs.py | 2 +- apps/locale/ja/LC_MESSAGES/django.po | 945 ++++++++---------- apps/locale/zh/LC_MESSAGES/django.po | 937 ++++++++--------- apps/notifications/migrations/0001_initial.py | 6 +- .../migrations/0002_auto_20210909_1946.py | 4 +- apps/notifications/models/notification.py | 37 +- .../serializers/notifications.py | 4 + apps/perms/models/base.py | 4 +- apps/rbac/migrations/0001_initial.py | 3 +- .../migrations/0002_auto_20210929_1409.py | 2 +- apps/rbac/models/permission.py | 2 +- apps/rbac/models/rolebinding.py | 6 +- apps/settings/migrations/0001_initial.py | 4 +- apps/settings/models.py | 4 +- apps/settings/serializers/auth/base.py | 2 + apps/settings/serializers/auth/cas.py | 2 + apps/settings/serializers/auth/dingtalk.py | 2 + apps/settings/serializers/auth/feishu.py | 2 + apps/settings/serializers/auth/ldap.py | 1 + apps/settings/serializers/auth/oauth2.py | 2 + apps/settings/serializers/auth/oidc.py | 1 + apps/settings/serializers/auth/radius.py | 2 + apps/settings/serializers/auth/saml2.py | 2 + apps/settings/serializers/auth/sms.py | 4 +- apps/settings/serializers/auth/sso.py | 2 + apps/settings/serializers/auth/wecom.py | 2 + apps/settings/serializers/basic.py | 2 + apps/settings/serializers/cleaning.py | 2 + apps/settings/serializers/email.py | 3 + apps/settings/serializers/other.py | 2 + apps/settings/serializers/security.py | 2 + apps/settings/serializers/settings.py | 28 +- apps/settings/serializers/terminal.py | 2 + apps/terminal/backends/command/es.py | 389 +------ apps/terminal/models/storage.py | 4 +- apps/terminal/models/terminal.py | 6 +- apps/terminal/notifications.py | 3 + .../migrations/0021_auto_20190625_1104.py | 2 +- .../migrations/0032_userpasswordhistory.py | 4 +- apps/users/models/user.py | 10 +- 66 files changed, 2108 insertions(+), 1570 deletions(-) create mode 100644 apps/audits/backends/__init__.py create mode 100644 apps/audits/backends/db.py create mode 100644 apps/audits/backends/es.py create mode 100644 apps/audits/handler.py create mode 100644 apps/audits/migrations/0015_auto_20221011_1745.py create mode 100644 apps/common/plugins/__init__.py create mode 100644 apps/common/plugins/es.py create mode 100644 apps/jumpserver/rewriting/pagination.py diff --git a/apps/assets/migrations/0022_auto_20181012_1717.py b/apps/assets/migrations/0022_auto_20181012_1717.py index db470c599..8849249ea 100644 --- a/apps/assets/migrations/0022_auto_20181012_1717.py +++ b/apps/assets/migrations/0022_auto_20181012_1717.py @@ -21,8 +21,8 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=64, verbose_name='Name')), ('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('date_created', models.DateTimeField(auto_now_add=True)), - ('date_updated', models.DateTimeField(auto_now=True)), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')), ], options={ diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 7f44094e0..cec03827e 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -50,8 +50,8 @@ class CommandFilter(OrgModelMixin): ) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) comment = models.TextField(blank=True, default='', verbose_name=_("Comment")) - date_created = models.DateTimeField(auto_now_add=True) - date_updated = models.DateTimeField(auto_now=True) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) created_by = models.CharField( max_length=128, blank=True, default='', verbose_name=_('Created by') ) diff --git a/apps/audits/api.py b/apps/audits/api.py index ff1bb0309..4cd73f12b 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,21 +1,29 @@ # -*- coding: utf-8 -*- # -from rest_framework.mixins import ListModelMixin, CreateModelMixin +from importlib import import_module + +from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin from django.db.models import F, Value from django.db.models.functions import Concat +from django.conf import settings from rest_framework.permissions import IsAuthenticated from rest_framework import generics from common.drf.api import JMSReadOnlyModelViewSet +from common.plugins.es import QuerySet as ESQuerySet from common.drf.filters import DatetimeRangeFilter from common.api import CommonGenericViewSet from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin from orgs.utils import current_org from ops.models import CommandExecution from . import filters +from .backends import TYPE_ENGINE_MAPPING from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer -from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer +from .serializers import ( + OperateLogSerializer, OperateLogActionDetailSerializer, + PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer +) class FTPLogViewSet(CreateModelMixin, @@ -68,7 +76,7 @@ class MyLoginLogAPIView(UserLoginCommonMixin, generics.ListAPIView): return qs -class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): +class OperateLogViewSet(RetrieveModelMixin, ListModelMixin, OrgGenericViewSet): model = OperateLog serializer_class = OperateLogSerializer extra_filter_backends = [DatetimeRangeFilter] @@ -79,6 +87,22 @@ class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): search_fields = ['resource'] ordering = ['-datetime'] + def get_serializer_class(self): + if self.request.query_params.get('type') == 'action_detail': + return OperateLogActionDetailSerializer + return super().get_serializer_class() + + def get_queryset(self): + qs = OperateLog.objects.all() + es_config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG + if es_config: + engine_mod = import_module(TYPE_ENGINE_MAPPING['es']) + store = engine_mod.OperateLogStore(es_config) + if store.ping(timeout=2): + qs = ESQuerySet(store) + qs.model = OperateLog + return qs + class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): queryset = PasswordChangeLog.objects.all() diff --git a/apps/audits/backends/__init__.py b/apps/audits/backends/__init__.py new file mode 100644 index 000000000..244e2c0f3 --- /dev/null +++ b/apps/audits/backends/__init__.py @@ -0,0 +1,18 @@ +from importlib import import_module + +from django.conf import settings + + +TYPE_ENGINE_MAPPING = { + 'db': 'audits.backends.db', + 'es': 'audits.backends.es', +} + + +def get_operate_log_storage(default=False): + engine_mod = import_module(TYPE_ENGINE_MAPPING['db']) + es_config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG + if not default and es_config: + engine_mod = import_module(TYPE_ENGINE_MAPPING['es']) + storage = engine_mod.OperateLogStore(es_config) + return storage diff --git a/apps/audits/backends/db.py b/apps/audits/backends/db.py new file mode 100644 index 000000000..14222eb13 --- /dev/null +++ b/apps/audits/backends/db.py @@ -0,0 +1,38 @@ +# ~*~ coding: utf-8 ~*~ +from django.utils.translation import ugettext_lazy as _ + +from audits.models import OperateLog + + +class OperateLogStore(object): + def __init__(self, config): + self.model = OperateLog + self.max_length = 1024 + self.max_length_tip_msg = _( + 'The text content is too long. Use Elasticsearch to store operation logs' + ) + + @staticmethod + def ping(timeout=None): + return True + + def save(self, **kwargs): + log_id = kwargs.get('id', '') + before = kwargs.get('before') or {} + after = kwargs.get('after') or {} + if len(str(before)) > self.max_length: + before = {_('Tips'): self.max_length_tip_msg} + if len(str(after)) > self.max_length: + after = {_('Tips'): self.max_length_tip_msg} + + op_log = self.model.objects.filter(pk=log_id).first() + if op_log is not None: + raw_after = op_log.after or {} + raw_before = op_log.before or {} + raw_before.update(before) + raw_after.update(after) + op_log.before = raw_before + op_log.after = raw_after + op_log.save() + else: + self.model.objects.create(**kwargs) diff --git a/apps/audits/backends/es.py b/apps/audits/backends/es.py new file mode 100644 index 000000000..7947b8f04 --- /dev/null +++ b/apps/audits/backends/es.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +import uuid + +from common.utils.timezone import local_now_display +from common.utils import get_logger +from common.utils.encode import Singleton +from common.plugins.es import ES + + +logger = get_logger(__file__) + + +class OperateLogStore(ES, metaclass=Singleton): + def __init__(self, config): + properties = { + "id": { + "type": "keyword" + }, + "user": { + "type": "keyword" + }, + "action": { + "type": "keyword" + }, + "resource_type": { + "type": "keyword" + }, + "org_id": { + "type": "keyword" + }, + "datetime": { + "type": "date", + "format": "yyyy-MM-dd HH:mm:ss" + } + } + exact_fields = {} + match_fields = { + 'id', 'user', 'action', 'resource_type', + 'resource', 'remote_addr', 'org_id' + } + keyword_fields = { + 'id', 'user', 'action', 'resource_type', 'org_id' + } + if not config.get('INDEX'): + config['INDEX'] = 'jumpserver_operate_log' + super().__init__(config, properties, keyword_fields, exact_fields, match_fields) + self.pre_use_check() + + @staticmethod + def make_data(data): + op_id = data.get('id', str(uuid.uuid4())) + datetime_param = data.get('datetime', local_now_display()) + data = { + 'id': op_id, 'user': data['user'], 'action': data['action'], + 'resource_type': data['resource_type'], 'resource': data['resource'], + 'remote_addr': data['remote_addr'], 'datetime': datetime_param, + 'before': data['before'], 'after': data['after'], 'org_id': data['org_id'] + } + return data + + def save(self, **kwargs): + log_id = kwargs.get('id', '') + before = kwargs.get('before') or {} + after = kwargs.get('after') or {} + + op_log = self.get({'id': log_id}) + if op_log is not None: + data = {'doc': {}} + raw_after = op_log.get('after') or {} + raw_before = op_log.get('before') or {} + raw_before.update(before) + raw_after.update(after) + data['doc']['before'] = raw_before + data['doc']['after'] = raw_after + self.es.update( + index=self.index, doc_type=self.doc_type, + id=op_log.get('es_id'), body=data, refresh=True + ) + else: + data = self.make_data(kwargs) + self.es.index( + index=self.index, doc_type=self.doc_type, body=data, + refresh=True + ) diff --git a/apps/audits/const.py b/apps/audits/const.py index 9ff993556..a49363e0a 100644 --- a/apps/audits/const.py +++ b/apps/audits/const.py @@ -7,11 +7,13 @@ DEFAULT_CITY = _("Unknown") MODELS_NEED_RECORD = ( # users 'User', 'UserGroup', + # authentication + 'AccessKey', 'TempToken', # acls 'LoginACL', 'LoginAssetACL', 'LoginConfirmSetting', # assets 'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', - 'CommandFilter', 'Platform', 'AuthBook', + 'CommandFilter', 'Platform', 'Label', # applications 'Application', # orgs @@ -20,6 +22,13 @@ MODELS_NEED_RECORD = ( 'Setting', # perms 'AssetPermission', 'ApplicationPermission', + # notifications + 'SystemMsgSubscription', 'UserMsgSubscription', + # Terminal + 'Terminal', 'Endpoint', 'EndpointRule', 'CommandStorage', 'ReplayStorage', + # rbac + 'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding', # xpack - 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask', + 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'ApplicationChangeAuthPlan', + 'GatherUserTask', 'Interface', ) diff --git a/apps/audits/handler.py b/apps/audits/handler.py new file mode 100644 index 000000000..c0dd0ac95 --- /dev/null +++ b/apps/audits/handler.py @@ -0,0 +1,183 @@ +from datetime import datetime + +from django.db import transaction +from django.core.cache import cache +from django.utils.translation import ugettext_lazy as _ + +from common.utils import get_request_ip, get_logger +from common.utils.timezone import as_current_tz +from common.utils.encode import Singleton +from common.local import encrypted_field_set +from settings.serializers import SettingsSerializer +from jumpserver.utils import current_request +from audits.models import OperateLog +from orgs.utils import get_current_org_id + +from .backends import get_operate_log_storage + + +logger = get_logger(__name__) + + +class ModelClient: + @staticmethod + def save(**kwargs): + log_id = kwargs.get('id', '') + op_log = OperateLog.objects.filter(pk=log_id).first() + if op_log is not None: + raw_after = op_log.after or {} + raw_before = op_log.before or {} + cur_before = kwargs.get('before') or {} + cur_after = kwargs.get('after') or {} + raw_before.update(cur_before) + raw_after.update(cur_after) + op_log.before = raw_before + op_log.after = raw_after + op_log.save() + else: + OperateLog.objects.create(**kwargs) + + +class OperatorLogHandler(metaclass=Singleton): + CACHE_KEY = 'OPERATOR_LOG_CACHE_KEY' + + def __init__(self): + self.log_client = self.get_storage_client() + + @staticmethod + def get_storage_client(): + client = get_operate_log_storage() + return client + + @staticmethod + def _consistent_type_to_str(value1, value2): + if isinstance(value1, datetime): + value1 = as_current_tz(value1).strftime('%Y-%m-%d %H:%M:%S') + if isinstance(value2, datetime): + value2 = as_current_tz(value2).strftime('%Y-%m-%d %H:%M:%S') + return value1, value2 + + def _look_for_two_dict_change(self, left_dict, right_dict): + # 以右边的字典为基础 + before, after = {}, {} + for key, value in right_dict.items(): + pre_value = left_dict.get(key, '') + pre_value, value = self._consistent_type_to_str(pre_value, value) + if sorted(str(value)) == sorted(str(pre_value)): + continue + if pre_value: + before[key] = pre_value + if value: + after[key] = value + return before, after + + def cache_instance_before_data(self, instance_dict): + instance_id = instance_dict.get('id') + if instance_id is None: + return + + key = '%s_%s' % (self.CACHE_KEY, instance_id) + cache.set(key, instance_dict, 3 * 60) + + def get_instance_dict_from_cache(self, instance_id): + if instance_id is None: + return None + + key = '%s_%s' % (self.CACHE_KEY, instance_id) + cache_instance = cache.get(key, {}) + log_id = cache_instance.get('operate_log_id') + return log_id, cache_instance + + def get_instance_current_with_cache_diff(self, current_instance): + log_id, before, after = None, None, None + instance_id = current_instance.get('id') + if instance_id is None: + return log_id, before, after + + log_id, cache_instance = self.get_instance_dict_from_cache(instance_id) + if not cache_instance: + return log_id, before, after + + before, after = self._look_for_two_dict_change( + cache_instance, current_instance + ) + return log_id, before, after + + @staticmethod + def get_resource_display_from_setting(resource): + resource_display = None + setting_serializer = SettingsSerializer() + label = setting_serializer.get_field_label(resource) + if label is not None: + resource_display = label + return resource_display + + def get_resource_display(self, resource): + resource_display = str(resource) + return_value = self.get_resource_display_from_setting(resource_display) + if return_value is not None: + resource_display = return_value + return resource_display + + def __data_processing(self, dict_item, loop=True): + encrypt_value = '******' + for key, value in dict_item.items(): + if isinstance(value, bool): + value = _('Yes') if value else _('No') + elif isinstance(value, (list, tuple)): + value = ','.join(value) + elif isinstance(value, dict) and loop: + self.__data_processing(value, loop=False) + if key in encrypted_field_set: + value = encrypt_value + dict_item[key] = value + return dict_item + + def data_processing(self, before, after): + if before: + before = self.__data_processing(before) + if after: + after = self.__data_processing(after) + return before, after + + def create_or_update_operate_log( + self, action, resource_type, resource=None, + force=False, log_id=None, before=None, after=None + ): + user = current_request.user if current_request else None + if not user or not user.is_authenticated: + return + + remote_addr = get_request_ip(current_request) + resource_display = self.get_resource_display(resource) + before, after = self.data_processing(before, after) + if not force and not any([before, after]): + # 前后都没变化,没必要生成日志,除非手动强制保存 + return + + data = { + 'id': log_id, "user": str(user), 'action': action, + 'resource_type': str(resource_type), 'resource': resource_display, + 'remote_addr': remote_addr, 'before': before, 'after': after, + 'org_id': get_current_org_id(), + } + with transaction.atomic(): + if self.log_client.ping(timeout=1): + client = self.log_client + else: + logger.info('Switch default operate log storage save.') + client = get_operate_log_storage(default=True) + + try: + client.save(**data) + except Exception as e: + error_msg = 'An error occurred saving OperateLog.' \ + 'Error: %s, Data: %s' % (e, data) + logger.error(error_msg) + + +op_handler = OperatorLogHandler() +create_or_update_operate_log = op_handler.create_or_update_operate_log +cache_instance_before_data = op_handler.cache_instance_before_data +get_instance_current_with_cache_diff = op_handler.get_instance_current_with_cache_diff +get_instance_dict_from_cache = op_handler.get_instance_dict_from_cache diff --git a/apps/audits/migrations/0015_auto_20221011_1745.py b/apps/audits/migrations/0015_auto_20221011_1745.py new file mode 100644 index 000000000..dafd35dc8 --- /dev/null +++ b/apps/audits/migrations/0015_auto_20221011_1745.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-10-11 09:45 + +import common.db.encoder +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0014_auto_20220505_1902'), + ] + + operations = [ + migrations.AddField( + model_name='operatelog', + name='after', + field=models.JSONField(default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True), + ), + migrations.AddField( + model_name='operatelog', + name='before', + field=models.JSONField(default=dict, encoder=common.db.encoder.ModelJSONFieldEncoder, null=True), + ), + ] diff --git a/apps/audits/models.py b/apps/audits/models.py index 5dd8eb0a2..affd6045c 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -4,8 +4,9 @@ from django.db import models from django.db.models import Q from django.utils.translation import gettext, ugettext_lazy as _ from django.utils import timezone -from common.utils import lazyproperty +from common.utils import lazyproperty +from common.db.encoder import ModelJSONFieldEncoder from orgs.mixins.models import OrgModelMixin, Organization from orgs.utils import current_org @@ -65,6 +66,8 @@ class OperateLog(OrgModelMixin): resource = models.CharField(max_length=128, verbose_name=_("Resource")) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime'), db_index=True) + before = models.JSONField(default=dict, encoder=ModelJSONFieldEncoder, null=True) + after = models.JSONField(default=dict, encoder=ModelJSONFieldEncoder, null=True) def __str__(self): return "<{}> {} <{}>".format(self.user, self.action, self.resource) @@ -78,6 +81,21 @@ class OperateLog(OrgModelMixin): self.org_id = Organization.ROOT_ID return super(OperateLog, self).save(*args, **kwargs) + @classmethod + def from_dict(cls, d): + self = cls() + for k, v in d.items(): + setattr(self, k, v) + return self + + @classmethod + def from_multi_dict(cls, l): + operate_logs = [] + for d in l: + operate_log = cls.from_dict(d) + operate_logs.append(operate_log) + return operate_logs + class Meta: verbose_name = _("Operate log") diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index bc78ef238..2b6c20686 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -47,6 +47,12 @@ class UserLoginLogSerializer(serializers.ModelSerializer): } +class OperateLogActionDetailSerializer(serializers.ModelSerializer): + class Meta: + model = models.OperateLog + fields = ('before', 'after') + + class OperateLogSerializer(serializers.ModelSerializer): action_display = serializers.CharField(source='get_action_display', label=_('Action')) diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 9ac565bc6..19186b568 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -import time +import uuid from django.db.models.signals import ( - post_save, m2m_changed, pre_delete + post_save, m2m_changed, pre_delete, pre_save ) from django.dispatch import receiver from django.conf import settings @@ -16,24 +16,32 @@ from django.utils import translation from rest_framework.renderers import JSONRenderer from rest_framework.request import Request -from assets.models import Asset, SystemUser +from users.models import User +from assets.models import Asset, SystemUser, CommandFilter +from terminal.models import Session, Command +from perms.models import AssetPermission, ApplicationPermission +from rbac.models import Role + +from audits.utils import model_to_dict_for_operate_log as model_to_dict +from audits.handler import ( + get_instance_current_with_cache_diff, cache_instance_before_data, + create_or_update_operate_log, get_instance_dict_from_cache +) from authentication.signals import post_auth_failed, post_auth_success from authentication.utils import check_different_city_login_if_need 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, create_operate_log +from .utils import write_login_log from . import models, serializers from .models import OperateLog -from orgs.utils import current_org -from perms.models import AssetPermission, ApplicationPermission +from .const import MODELS_NEED_RECORD from terminal.backends.command.serializers import SessionCommandSerializer from terminal.serializers import SessionSerializer -from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR +from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, SKIP_SIGNAL from common.utils import get_request_ip, get_logger, get_syslogger from common.utils.encode import data_to_json + logger = get_logger(__name__) sys_logger = get_syslogger(__name__) json_render = JSONRenderer() @@ -62,70 +70,6 @@ class AuthBackendLabelMapping(LazyObject): AUTH_BACKEND_LABEL_MAPPING = AuthBackendLabelMapping() - -M2M_NEED_RECORD = { - User.groups.through._meta.object_name: ( - _('User and Group'), - _('{User} JOINED {UserGroup}'), - _('{User} LEFT {UserGroup}') - ), - SystemUser.assets.through._meta.object_name: ( - _('Asset and SystemUser'), - _('{Asset} ADD {SystemUser}'), - _('{Asset} REMOVE {SystemUser}') - ), - Asset.nodes.through._meta.object_name: ( - _('Node and Asset'), - _('{Node} ADD {Asset}'), - _('{Node} REMOVE {Asset}') - ), - AssetPermission.users.through._meta.object_name: ( - _('User asset permissions'), - _('{AssetPermission} ADD {User}'), - _('{AssetPermission} REMOVE {User}'), - ), - AssetPermission.user_groups.through._meta.object_name: ( - _('User group asset permissions'), - _('{AssetPermission} ADD {UserGroup}'), - _('{AssetPermission} REMOVE {UserGroup}'), - ), - AssetPermission.assets.through._meta.object_name: ( - _('Asset permission'), - _('{AssetPermission} ADD {Asset}'), - _('{AssetPermission} REMOVE {Asset}'), - ), - AssetPermission.nodes.through._meta.object_name: ( - _('Node permission'), - _('{AssetPermission} ADD {Node}'), - _('{AssetPermission} REMOVE {Node}'), - ), - AssetPermission.system_users.through._meta.object_name: ( - _('Asset permission and SystemUser'), - _('{AssetPermission} ADD {SystemUser}'), - _('{AssetPermission} REMOVE {SystemUser}'), - ), - ApplicationPermission.users.through._meta.object_name: ( - _('User application permissions'), - _('{ApplicationPermission} ADD {User}'), - _('{ApplicationPermission} REMOVE {User}'), - ), - ApplicationPermission.user_groups.through._meta.object_name: ( - _('User group application permissions'), - _('{ApplicationPermission} ADD {UserGroup}'), - _('{ApplicationPermission} REMOVE {UserGroup}'), - ), - ApplicationPermission.applications.through._meta.object_name: ( - _('Application permission'), - _('{ApplicationPermission} ADD {Application}'), - _('{ApplicationPermission} REMOVE {Application}'), - ), - ApplicationPermission.system_users.through._meta.object_name: ( - _('Application permission and SystemUser'), - _('{ApplicationPermission} ADD {SystemUser}'), - _('{ApplicationPermission} REMOVE {SystemUser}'), - ), -} - M2M_ACTION = { POST_ADD: OperateLog.ACTION_CREATE, POST_REMOVE: OperateLog.ACTION_DELETE, @@ -137,60 +81,115 @@ M2M_ACTION = { def on_m2m_changed(sender, action, instance, reverse, model, pk_set, **kwargs): if action not in M2M_ACTION: return - - user = current_request.user if current_request else None - if not user or not user.is_authenticated: + if not instance: return - sender_name = sender._meta.object_name - if sender_name in M2M_NEED_RECORD: - org_id = current_org.id - remote_addr = get_request_ip(current_request) - user = str(user) - resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[sender_name] - action = M2M_ACTION[action] - if action == OperateLog.ACTION_CREATE: - resource_tmpl = resource_tmpl_add - elif action == OperateLog.ACTION_DELETE: - resource_tmpl = resource_tmpl_remove + resource_type = instance._meta.verbose_name + current_instance = model_to_dict(instance, include_model_fields=False) - to_create = [] - objs = model.objects.filter(pk__in=pk_set) + instance_id = current_instance.get('id') + log_id, before_instance = get_instance_dict_from_cache(instance_id) - instance_name = instance._meta.object_name - instance_value = str(instance) + field_name = str(model._meta.verbose_name) + objs = model.objects.filter(pk__in=pk_set) + objs_display = [str(o) for o in objs] + action = M2M_ACTION[action] + changed_field = current_instance.get(field_name, []) - model_name = model._meta.object_name + after, before, before_value = None, None, None + if action == OperateLog.ACTION_CREATE: + before_value = list(set(changed_field) - set(objs_display)) + elif action == OperateLog.ACTION_DELETE: + before_value = list( + set(changed_field).symmetric_difference(set(objs_display)) + ) - for obj in objs: - resource = resource_tmpl.format(**{ - instance_name: instance_value, - model_name: str(obj) - })[:128] # `resource` 字段只有 128 个字符长 😔 + if changed_field: + after = {field_name: changed_field} + if before_value: + before = {field_name: before_value} - to_create.append(OperateLog( - user=user, action=action, resource_type=resource_type, - resource=resource, remote_addr=remote_addr, org_id=org_id - )) - OperateLog.objects.bulk_create(to_create) + if sorted(str(before)) == sorted(str(after)): + return + + create_or_update_operate_log( + OperateLog.ACTION_UPDATE, resource_type, + resource=instance, log_id=log_id, before=before, after=after + ) + + +def signal_of_operate_log_whether_continue(sender, instance, created, update_fields=None): + condition = True + if not instance: + condition = False + if instance and getattr(instance, SKIP_SIGNAL, False): + condition = False + # 终端模型的 create 事件由系统产生,不记录 + if instance._meta.object_name == 'Terminal' and created: + condition = False + # last_login 改变是最后登录日期, 每次登录都会改变 + if instance._meta.object_name == 'User' and \ + update_fields and 'last_login' in update_fields: + condition = False + # 不在记录白名单中,跳过 + if sender._meta.object_name not in MODELS_NEED_RECORD: + condition = False + return condition + + +@receiver(pre_save) +def on_object_pre_create_or_update(sender, instance=None, raw=False, using=None, update_fields=None, **kwargs): + ok = signal_of_operate_log_whether_continue( + sender, instance, False, update_fields + ) + if not ok: + return + instance_before_data = {'id': instance.id} + raw_instance = type(instance).objects.filter(pk=instance.id).first() + if raw_instance: + instance_before_data = model_to_dict(raw_instance) + operate_log_id = str(uuid.uuid4()) + instance_before_data['operate_log_id'] = operate_log_id + setattr(instance, 'operate_log_id', operate_log_id) + cache_instance_before_data(instance_before_data) @receiver(post_save) def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs): - # last_login 改变是最后登录日期, 每次登录都会改变 - if instance._meta.object_name == 'User' and \ - update_fields and 'last_login' in update_fields: + ok = signal_of_operate_log_whether_continue( + sender, instance, created, update_fields + ) + if not ok: return + + log_id, before, after = None, None, None if created: action = models.OperateLog.ACTION_CREATE + after = model_to_dict(instance) + log_id = getattr(instance, 'operate_log_id', None) else: action = models.OperateLog.ACTION_UPDATE - create_operate_log(action, sender, instance) + current_instance = model_to_dict(instance) + log_id, before, after = get_instance_current_with_cache_diff(current_instance) + + resource_type = sender._meta.verbose_name + create_or_update_operate_log( + action, resource_type, resource=instance, + log_id=log_id, before=before, after=after + ) @receiver(pre_delete) def on_object_delete(sender, instance=None, **kwargs): - create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance) + ok = signal_of_operate_log_whether_continue(sender, instance, False) + if not ok: + return + + resource_type = sender._meta.verbose_name + create_or_update_operate_log( + models.OperateLog.ACTION_DELETE, resource_type, + resource=instance, before=model_to_dict(instance) + ) @receiver(post_user_change_password, sender=User) diff --git a/apps/audits/utils.py b/apps/audits/utils.py index 1fadbccee..8ce23a498 100644 --- a/apps/audits/utils.py +++ b/apps/audits/utils.py @@ -1,14 +1,15 @@ import csv import codecs -from django.http import HttpResponse -from django.db import transaction -from django.utils import translation +from itertools import chain -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 +from django.http import HttpResponse +from django.db import models + +from settings.serializers import SettingsSerializer +from common.utils import validate_ip, get_ip_city, get_logger +from common.db import fields +from .const import DEFAULT_CITY logger = get_logger(__name__) @@ -46,23 +47,60 @@ def write_login_log(*args, **kwargs): 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) +def get_resource_display(resource): + resource_display = str(resource) + setting_serializer = SettingsSerializer() + label = setting_serializer.get_field_label(resource_display) + if label is not None: + resource_display = label + return resource_display - 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)) + +def model_to_dict_for_operate_log( + instance, include_model_fields=True, include_related_fields=True +): + need_continue_fields = ['date_updated'] + opts = instance._meta + data = {} + for f in chain(opts.concrete_fields, opts.private_fields): + if isinstance(f, (models.FileField, models.ImageField)): + continue + + if getattr(f, 'attname', None) in need_continue_fields: + continue + + value = getattr(instance, f.name) or getattr(instance, f.attname) + if not isinstance(value, bool) and not value: + continue + + if getattr(f, 'primary_key', False): + f.verbose_name = 'id' + elif isinstance(f, ( + fields.EncryptCharField, fields.EncryptTextField, + fields.EncryptJsonDictCharField, fields.EncryptJsonDictTextField + )) or getattr(f, 'attname', '') == 'password': + value = 'encrypt|%s' % value + elif isinstance(value, list): + value = [str(v) for v in value] + + if include_model_fields or getattr(f, 'primary_key', False): + data[str(f.verbose_name)] = value + + if include_related_fields: + for f in chain(opts.many_to_many, opts.related_objects): + value = [] + if instance.pk is not None: + related_name = getattr(f, 'attname', '') or getattr(f, 'related_name', '') + if related_name: + try: + value = [str(i) for i in getattr(instance, related_name).all()] + except: + pass + if not value: + continue + try: + field_key = getattr(f, 'verbose_name', None) or f.related_model._meta.verbose_name + data[str(field_key)] = value + except: + pass + return data diff --git a/apps/authentication/migrations/0001_initial.py b/apps/authentication/migrations/0001_initial.py index 6e45f932b..4c2251138 100644 --- a/apps/authentication/migrations/0001_initial.py +++ b/apps/authentication/migrations/0001_initial.py @@ -1,5 +1,6 @@ # Generated by Django 2.1.7 on 2019-02-28 08:07 +import common.db.models from django.conf import settings from django.db import migrations, models import django.db.models.deletion @@ -27,7 +28,7 @@ class Migration(migrations.Migration): models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='AccessKeySecret')), ('user', models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, + on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='access_keys', to=settings.AUTH_USER_MODEL, verbose_name='User')), ], diff --git a/apps/authentication/migrations/0006_auto_20211227_1059.py b/apps/authentication/migrations/0006_auto_20211227_1059.py index 945c5ff5c..b01758bbd 100644 --- a/apps/authentication/migrations/0006_auto_20211227_1059.py +++ b/apps/authentication/migrations/0006_auto_20211227_1059.py @@ -1,8 +1,8 @@ # Generated by Django 3.1.13 on 2021-12-27 02:59 +import common.db.models from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='ssotoken', name='user', - field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), + field=models.ForeignKey(db_constraint=False, on_delete=common.db.models.CASCADE_SIGNAL_SKIP, to=settings.AUTH_USER_MODEL, verbose_name='User'), ), ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 518db566c..9b78a8bad 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -16,10 +16,10 @@ class AccessKey(models.Model): default=uuid.uuid4, editable=False) secret = models.UUIDField(verbose_name='AccessKeySecret', default=uuid.uuid4, editable=False) - user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User', - on_delete=models.CASCADE, related_name='access_keys') + user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User'), + on_delete=models.CASCADE_SIGNAL_SKIP, related_name='access_keys') is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_created = models.DateTimeField(auto_now_add=True) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) def get_id(self): return str(self.id) @@ -51,7 +51,7 @@ class SSOToken(models.JMSBaseModel): """ authkey = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('Token')) expired = models.BooleanField(default=False, verbose_name=_('Expired')) - user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'), db_constraint=False) + user = models.ForeignKey('users.User', on_delete=models.CASCADE_SIGNAL_SKIP, verbose_name=_('User'), db_constraint=False) class Meta: verbose_name = _('SSO token') diff --git a/apps/common/const/signals.py b/apps/common/const/signals.py index 690a30161..78117ef13 100644 --- a/apps/common/const/signals.py +++ b/apps/common/const/signals.py @@ -15,3 +15,5 @@ POST_CLEAR = 'post_clear' POST_PREFIX = 'post' PRE_PREFIX = 'pre' + +SKIP_SIGNAL = 'skip_signal' diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 0757bc068..ed00a0931 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text from django.core.validators import MinValueValidator, MaxValueValidator from common.utils import signer, crypto +from common.local import add_encrypted_field_set __all__ = [ @@ -149,6 +150,10 @@ class EncryptMixin: class EncryptTextField(EncryptMixin, models.TextField): description = _("Encrypt field using Secret Key") + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + add_encrypted_field_set(self.verbose_name) + class EncryptCharField(EncryptMixin, models.CharField): @staticmethod @@ -163,6 +168,7 @@ class EncryptCharField(EncryptMixin, models.CharField): def __init__(self, *args, **kwargs): self.change_max_length(kwargs) super().__init__(*args, **kwargs) + add_encrypted_field_set(self.verbose_name) def deconstruct(self): name, path, args, kwargs = super().deconstruct() @@ -174,11 +180,15 @@ class EncryptCharField(EncryptMixin, models.CharField): class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + add_encrypted_field_set(self.verbose_name) class EncryptJsonDictCharField(EncryptMixin, JsonDictCharField): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + add_encrypted_field_set(self.verbose_name) class PortField(models.IntegerField): diff --git a/apps/common/db/models.py b/apps/common/db/models.py index 92b6c4803..c3b00c62c 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -19,6 +19,8 @@ from django.db.models import QuerySet from django.db.models.functions import Concat from django.utils.translation import ugettext_lazy as _ +from ..const.signals import SKIP_SIGNAL + class Choice(str): def __new__(cls, value, label=''): # `deepcopy` 的时候不会传 `label` @@ -124,6 +126,9 @@ class JMSModel(JMSBaseModel): class Meta: abstract = True + def __str__(self): + return str(self.id) + def concated_display(name1, name2): return Concat(F(name1), Value('('), F(name2), Value(')')) @@ -238,3 +243,14 @@ class MultiTableChildQueryset(QuerySet): self._batched_insert(objs, self.model._meta.local_fields, batch_size) return objs + + +def CASCADE_SIGNAL_SKIP(collector, field, sub_objs, using): + # 级联删除时,操作日志标记不保存,以免用户混淆 + try: + for obj in sub_objs: + setattr(obj, SKIP_SIGNAL, True) + except: + pass + + CASCADE(collector, field, sub_objs, using) diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index f97925f43..353b3a75b 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -4,6 +4,7 @@ from rest_framework import serializers from common.utils import decrypt_password +from common.local import add_encrypted_field_set __all__ = [ 'ReadableHiddenField', 'EncryptedField' @@ -32,6 +33,7 @@ class EncryptedField(serializers.CharField): write_only = True kwargs['write_only'] = write_only super().__init__(**kwargs) + add_encrypted_field_set(self.label) def to_internal_value(self, value): value = super().to_internal_value(value) diff --git a/apps/common/local.py b/apps/common/local.py index d0299b43e..947ae3d6b 100644 --- a/apps/common/local.py +++ b/apps/common/local.py @@ -1,7 +1,13 @@ from werkzeug.local import Local thread_local = Local() +encrypted_field_set = set() def _find(attr): return getattr(thread_local, attr, None) + + +def add_encrypted_field_set(label): + if label: + encrypted_field_set.add(str(label)) diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index 9f824e5e2..1965129bb 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -7,7 +7,7 @@ from rest_framework import permissions from rest_framework.request import Request from common.exceptions import UserConfirmRequired -from audits.utils import create_operate_log +from audits.handler import create_or_update_operate_log from audits.models import OperateLog __all__ = ["PermissionsMixin", "RecordViewLogMixin", "UserConfirmRequiredExceptionMixin"] @@ -62,10 +62,18 @@ class RecordViewLogMixin: 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) + resource_type = self.model._meta.verbose_name + create_or_update_operate_log( + self.ACTION, resource_type, force=True, + resource=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()) + resource_type = self.model._meta.verbose_name + create_or_update_operate_log( + self.ACTION, resource_type, force=True, + resource=self.get_object() + ) return response diff --git a/apps/common/plugins/__init__.py b/apps/common/plugins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/common/plugins/es.py b/apps/common/plugins/es.py new file mode 100644 index 000000000..7419e2d1a --- /dev/null +++ b/apps/common/plugins/es.py @@ -0,0 +1,428 @@ +# -*- coding: utf-8 -*- +# +import datetime +import inspect + +from collections.abc import Iterable +from functools import reduce, partial +from itertools import groupby +from uuid import UUID + +from django.utils.translation import gettext_lazy as _ +from django.db.models import QuerySet as DJQuerySet +from elasticsearch import Elasticsearch +from elasticsearch.helpers import bulk +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 +from common.exceptions import JMSException + + +logger = get_logger(__file__) + + +class InvalidElasticsearch(JMSException): + default_code = 'invalid_elasticsearch' + default_detail = _('Invalid elasticsearch config') + + +class NotSupportElasticsearch8(JMSException): + default_code = 'not_support_elasticsearch8' + default_detail = _('Not Support Elasticsearch8') + + +class ES(object): + def __init__(self, config, properties, keyword_fields, exact_fields=None, match_fields=None): + + self.config = config + hosts = self.config.get('HOSTS') + kwargs = self.config.get('OTHER', {}) + + ignore_verify_certs = kwargs.pop('IGNORE_VERIFY_CERTS', False) + if ignore_verify_certs: + kwargs['verify_certs'] = None + self.es = Elasticsearch(hosts=hosts, max_retries=0, **kwargs) + self.index_prefix = self.config.get('INDEX') or 'jumpserver' + self.is_index_by_date = bool(self.config.get('INDEX_BY_DATE', False)) + + self.index = None + self.query_index = None + self.properties = properties + self.exact_fields, self.match_fields, self.keyword_fields = set(), set(), set() + + if isinstance(keyword_fields, Iterable): + self.keyword_fields.update(keyword_fields) + if isinstance(exact_fields, Iterable): + self.exact_fields.update(exact_fields) + if isinstance(match_fields, Iterable): + self.match_fields.update(match_fields) + + self.init_index() + self.doc_type = self.config.get("DOC_TYPE") or '_doc' + if self.is_new_index_type(): + self.doc_type = '_doc' + self.exact_fields.update(self.keyword_fields) + else: + self.match_fields.update(self.keyword_fields) + + def init_index(self): + 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 = self.config.get("INDEX") or 'jumpserver' + self.query_index = self.config.get("INDEX") or 'jumpserver' + + def is_new_index_type(self): + if not self.ping(timeout=2): + 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 + + try: + if version == '6': + # 检测索引是不是新的类型 es6 + properties = data[self.index]['mappings']['data']['properties'] + else: + # 检测索引是不是新的类型 es7 default index type: _doc + properties = data[self.index]['mappings']['properties'] + + for keyword in self.keyword_fields: + if not properties[keyword]['type'] == 'keyword': + break + else: + return True + except KeyError: + return False + + def pre_use_check(self): + if not self.ping(timeout=3): + raise InvalidElasticsearch + self._ensure_index_exists() + + def _ensure_index_exists(self): + info = self.es.info() + version = info['version']['number'].split('.')[0] + if version == '6': + mappings = {'mappings': {'data': {'properties': self.properties}}} + else: + mappings = {'mappings': {'properties': self.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: + if e.error == 'resource_already_exists_exception': + logger.warning(e) + else: + logger.exception(e) + + def make_data(self, data): + return [] + + def save(self, **kwargs): + data = self.make_data(kwargs) + return self.es.index(index=self.index, doc_type=self.doc_type, body=data) + + def bulk_save(self, command_set, raise_on_error=True): + actions = [] + for command in command_set: + data = dict( + _index=self.index, + _type=self.doc_type, + _source=self.make_data(command), + ) + actions.append(data) + return bulk(self.es, actions, index=self.index, raise_on_error=raise_on_error) + + def get(self, query: dict): + item = None + data = self.filter(query, size=1) + if len(data) >= 1: + item = data[0] + return item + + def filter(self, query: dict, from_=None, size=None, sort=None): + try: + data = self._filter(query, from_, size, sort) + except Exception as e: + logger.error('ES filter error: {}'.format(e)) + data = [] + return data + + def _filter(self, query: dict, from_=None, size=None, sort=None): + body = self.get_query_body(**query) + + data = self.es.search( + index=self.query_index, doc_type=self.doc_type, body=body, + from_=from_, size=size, sort=sort + ) + source_data = [] + for item in data['hits']['hits']: + if item: + item['_source'].update({'es_id': item['_id']}) + source_data.append(item['_source']) + + return source_data + + def count(self, **query): + try: + body = self.get_query_body(**query) + data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body) + count = data["count"] + except Exception as e: + logger.error('ES count error: {}'.format(e)) + count = 0 + return count + + def __getattr__(self, item): + return getattr(self.es, item) + + def all(self): + """返回所有数据""" + raise NotImplementedError("Not support") + + def ping(self, timeout=None): + try: + return self.es.ping(request_timeout=timeout) + except Exception: + return False + + @staticmethod + def handler_time_field(data): + datetime__gte = data.get('datetime__gte') + datetime__lte = data.get('datetime__lte') + datetime_range = {} + + if datetime__gte: + if isinstance(datetime__gte, datetime.datetime): + datetime__gte = datetime__gte.strftime('%Y-%m-%d %H:%M:%S') + datetime_range['gte'] = datetime__gte + if datetime__lte: + if isinstance(datetime__lte, datetime.datetime): + datetime__lte = datetime__lte.strftime('%Y-%m-%d %H:%M:%S') + datetime_range['lte'] = datetime__lte + return 'datetime', datetime_range + + def get_query_body(self, **kwargs): + new_kwargs = {} + for k, v in kwargs.items(): + if isinstance(v, UUID): + v = str(v) + if k == 'pk': + k = 'id' + new_kwargs[k] = v + kwargs = new_kwargs + + index_in_field = 'id__in' + exact_fields = self.exact_fields + match_fields = self.match_fields + + match = {} + exact = {} + index = {} + + if index_in_field in kwargs: + index['values'] = kwargs[index_in_field] + + for k, v in kwargs.items(): + if k in exact_fields: + exact[k] = v + elif k in match_fields: + match[k] = v + + # 处理时间 + time_field_name, time_range = self.handler_time_field(kwargs) + + # 处理组织 + should = [] + org_id = match.get('org_id') + + real_default_org_id = '00000000-0000-0000-0000-000000000002' + root_org_id = '00000000-0000-0000-0000-000000000000' + + if org_id == root_org_id: + match.pop('org_id') + elif org_id in (real_default_org_id, ''): + match.pop('org_id') + should.append({ + 'bool': { + 'must_not': [ + { + 'wildcard': {'org_id': '*'} + } + ]} + }) + should.append({'match': {'org_id': real_default_org_id}}) + + # 构建 body + body = { + 'query': { + 'bool': { + 'must': [ + {'match': {k: v}} for k, v in match.items() + ], + 'should': should, + 'filter': [ + { + 'term': {k: v} + } for k, v in exact.items() + ] + [ + { + 'range': { + time_field_name: time_range + } + } + ] + [ + { + 'ids': {k: v} + } for k, v in index.items() + ] + } + }, + } + return body + + +class QuerySet(DJQuerySet): + default_days_ago = 7 + max_result_window = 10000 + + def __init__(self, es_instance): + self._method_calls = [] + self._slice = None # (from_, size) + self._storage = es_instance + + # 命令列表模糊搜索时报错 + 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])} + return _method_calls + + @lazyproperty + def _filter_kwargs(self): + _method_calls = self._grouped_method_calls + filter_calls = _method_calls.get('filter') + if not filter_calls: + return {} + names, multi_args, multi_kwargs = zip(*filter_calls) + kwargs = reduce(lambda x, y: {**x, **y}, multi_kwargs, {}) + + striped_kwargs = {} + for k, v in kwargs.items(): + k = k.replace('__exact', '') + k = k.replace('__startswith', '') + k = k.replace('__icontains', '') + striped_kwargs[k] = v + return striped_kwargs + + @lazyproperty + def _sort(self): + order_by = self._grouped_method_calls.get('order_by') + if order_by: + for call in reversed(order_by): + fields = call[1] + if fields: + field = fields[-1] + + if field.startswith('-'): + direction = 'desc' + else: + direction = 'asc' + field = field.lstrip('-+') + sort = f'{field}:{direction}' + return sort + + def __execute(self): + _filter_kwargs = self._filter_kwargs + _sort = self._sort + from_, size = self._slice or (None, None) + data = self._storage.filter(_filter_kwargs, from_=from_, size=size, sort=_sort) + return self.model.from_multi_dict(data) + + def __stage_method_call(self, item, *args, **kwargs): + _clone = self.__clone() + _clone._method_calls.append((item, args, kwargs)) + return _clone + + def __clone(self): + uqs = QuerySet(self._storage) + uqs._method_calls = self._method_calls.copy() + uqs._slice = self._slice + uqs.model = self.model + return uqs + + def get(self, **kwargs): + kwargs.update(self._filter_kwargs) + return self._storage.get(kwargs) + + def count(self, limit_to_max_result_window=True): + filter_kwargs = self._filter_kwargs + count = self._storage.count(**filter_kwargs) + if limit_to_max_result_window: + count = min(count, self.max_result_window) + return count + + def __getattribute__(self, item): + if any(( + item.startswith('__'), + item in QuerySet.__dict__, + )): + return object.__getattribute__(self, item) + + origin_attr = object.__getattribute__(self, item) + if not inspect.ismethod(origin_attr): + return origin_attr + + attr = partial(self.__stage_method_call, item) + return attr + + def __getitem__(self, item): + max_window = self.max_result_window + if isinstance(item, slice): + if self._slice is None: + clone = self.__clone() + from_ = item.start or 0 + if item.stop is None: + size = self.max_result_window - from_ + else: + size = item.stop - from_ + + if from_ + size > max_window: + if from_ >= max_window: + from_ = max_window + size = 0 + else: + size = max_window - from_ + clone._slice = (from_, size) + return clone + return self.__execute()[item] + + def __repr__(self): + return self.__execute().__repr__() + + def __iter__(self): + return iter(self.__execute()) + + def __len__(self): + return self.count() diff --git a/apps/jumpserver/rewriting/pagination.py b/apps/jumpserver/rewriting/pagination.py new file mode 100644 index 000000000..cd6ad2642 --- /dev/null +++ b/apps/jumpserver/rewriting/pagination.py @@ -0,0 +1,6 @@ +from django.conf import settings +from rest_framework.pagination import LimitOffsetPagination + + +class MaxLimitOffsetPagination(LimitOffsetPagination): + max_limit = settings.MAX_LIMIT_PER_PAGE or 100 diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 4e9676379..5b6b11cf0 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -178,5 +178,9 @@ HELP_SUPPORT_URL = CONFIG.HELP_SUPPORT_URL SESSION_RSA_PRIVATE_KEY_NAME = 'jms_private_key' SESSION_RSA_PUBLIC_KEY_NAME = 'jms_public_key' +OPERATE_LOG_ELASTICSEARCH_CONFIG = CONFIG.OPERATE_LOG_ELASTICSEARCH_CONFIG + +MAX_LIMIT_PER_PAGE = CONFIG.MAX_LIMIT_PER_PAGE + # Magnus DB Port MAGNUS_PORTS = CONFIG.MAGNUS_PORTS diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 20beb5478..ccde975a5 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -47,7 +47,7 @@ REST_FRAMEWORK = { 'SEARCH_PARAM': "search", 'DATETIME_FORMAT': '%Y/%m/%d %H:%M:%S %z', 'DATETIME_INPUT_FORMATS': ['%Y/%m/%d %H:%M:%S %z', 'iso-8601', '%Y-%m-%d %H:%M:%S %z'], - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'DEFAULT_PAGINATION_CLASS': 'jumpserver.rewriting.pagination.MaxLimitOffsetPagination', 'EXCEPTION_HANDLER': 'common.drf.exc_handlers.common_exception_handler', } diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 3a3c8395b..efd3e1163 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -30,8 +30,8 @@ msgstr "Acls" #: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 -#: terminal/models/storage.py:26 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:33 +#: terminal/models/storage.py:27 terminal/models/task.py:16 +#: terminal/models/terminal.py:101 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:669 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -62,9 +62,9 @@ msgstr "アクティブ" #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 #: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 -#: terminal/models/storage.py:29 terminal/models/terminal.py:114 +#: terminal/models/storage.py:30 terminal/models/terminal.py:115 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 -#: users/models/group.py:16 users/models/user.py:706 +#: users/models/group.py:16 users/models/user.py:708 #: 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 @@ -86,15 +86,16 @@ msgid "Login confirm" 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:62 audits/models.py:87 audits/serializers.py:100 -#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:220 -#: perms/models/base.py:84 rbac/builtin.py:120 rbac/models/rolebinding.py:41 -#: terminal/backends/command/models.py:20 +#: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:38 +#: audits/models.py:63 audits/models.py:105 audits/serializers.py:106 +#: authentication/models.py:19 authentication/models.py:54 +#: authentication/models.py:78 notifications/models/notification.py:12 +#: orgs/models.py:220 perms/models/base.py:84 rbac/builtin.py:120 +#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 -#: terminal/models/sharing.py:33 terminal/notifications.py:91 -#: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14 -#: users/models/user.py:899 users/models/user.py:930 +#: terminal/models/sharing.py:33 terminal/notifications.py:94 +#: terminal/notifications.py:142 tickets/models/comment.py:21 users/const.py:14 +#: users/models/user.py:901 users/models/user.py:932 #: users/serializers/group.py:19 msgid "User" msgstr "ユーザー" @@ -105,7 +106,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:93 audits/models.py:63 audits/serializers.py:51 +#: assets/models/cmd_filter.py:93 audits/models.py:64 audits/serializers.py:57 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "アクション" @@ -130,11 +131,11 @@ msgstr "システムユーザー" #: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:42 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:268 audits/models.py:39 +#: assets/serializers/system_user.py:268 audits/models.py:40 #: authentication/models.py:66 authentication/models.py:90 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 -#: terminal/notifications.py:90 +#: terminal/notifications.py:93 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:181 #: xpack/plugins/cloud/models.py:223 @@ -155,7 +156,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:121 +#: assets/models/gathered_user.py:15 audits/models.py:139 #: authentication/forms.py:25 authentication/forms.py:27 #: authentication/models.py:260 authentication/serializers/password_mfa.py:25 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -187,13 +188,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 terminal/serializers/endpoint.py:54 +#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54 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 +#: settings/serializers/terminal.py:9 msgid "Hostname" msgstr "ホスト名" @@ -250,7 +251,6 @@ msgstr "アプリケーション" #: applications/const.py:8 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:26 -#: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "データベース" @@ -266,11 +266,12 @@ msgstr "カスタム" #: assets/models/backup.py:32 assets/models/cmd_filter.py:49 #: authentication/models.py:67 authentication/models.py:95 #: perms/models/application_permission.py:28 +#: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Application" msgstr "アプリケーション" #: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:46 assets/models/user.py:342 audits/models.py:40 +#: assets/models/cmd_filter.py:46 assets/models/user.py:342 audits/models.py:41 #: authentication/models.py:83 perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 @@ -281,7 +282,7 @@ msgid "System user" msgstr "システムユーザー" #: applications/models/account.py:17 assets/models/authbook.py:21 -#: settings/serializers/auth/cas.py:18 +#: settings/serializers/auth/cas.py:20 msgid "Version" msgstr "バージョン" @@ -300,7 +301,7 @@ msgstr "アプリケーションアカウントの秘密を変更できます" #: applications/models/application.py:221 #: applications/serializers/application.py:101 assets/models/label.py:21 #: perms/models/application_permission.py:21 -#: perms/serializers/application/user_permission.py:33 +#: perms/serializers/application/user_permission.py:33 settings/models.py:35 #: tickets/models/ticket/apply_application.py:15 #: xpack/plugins/change_auth_plan/models/app.py:25 msgid "Category" @@ -311,7 +312,7 @@ msgstr "カテゴリ" #: assets/models/cmd_filter.py:86 assets/models/user.py:250 #: authentication/models.py:70 perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:58 terminal/models/storage.py:143 +#: terminal/models/storage.py:59 terminal/models/storage.py:145 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:18 #: tickets/models/ticket/general.py:273 @@ -355,22 +356,25 @@ msgstr "タイプ表示" #: applications/serializers/application.py:105 assets/models/asset.py:230 #: 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/cmd_filter.py:48 common/db/models.py:114 +#: assets/models/cmd_filter.py:53 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:48 +#: authentication/models.py:22 common/db/models.py:116 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:72 orgs/models.py:223 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:931 +#: users/models/group.py:18 users/models/user.py:933 +#: xpack/plugins/change_auth_plan/models/base.py:45 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "作成された日付" #: applications/serializers/application.py:106 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:49 -#: common/db/models.py:115 common/mixins/models.py:51 ops/models/adhoc.py:40 -#: orgs/models.py:224 +#: assets/models/cmd_filter.py:54 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:117 +#: common/mixins/models.py:51 ops/models/adhoc.py:40 orgs/models.py:224 +#: xpack/plugins/change_auth_plan/models/base.py:46 msgid "Date updated" msgstr "更新日" @@ -389,8 +393,8 @@ msgid "Cluster" msgstr "クラスター" #: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:15 +#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:16 +#: settings/serializers/auth/sms.py:67 terminal/models/endpoint.py:15 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "ホスト" @@ -404,13 +408,13 @@ msgstr "ホスト" #: 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:62 -#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:66 +#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "ポート" #: applications/serializers/attrs/application_category/db.py:16 -#: settings/serializers/email.py:36 +#: settings/serializers/email.py:37 msgid "Use SSL" msgstr "SSLの使用" @@ -642,8 +646,8 @@ msgstr "ラベル" #: assets/models/asset.py:229 assets/models/base.py:183 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:56 #: assets/models/cmd_filter.py:103 assets/models/group.py:21 -#: common/db/models.py:112 common/mixins/models.py:49 orgs/models.py:71 -#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:714 +#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71 +#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:716 #: 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 @@ -699,7 +703,7 @@ msgid "Can view asset history account secret" msgstr "資産履歴アカウントパスワードを表示できます" #: assets/models/backup.py:30 perms/models/base.py:54 -#: settings/serializers/terminal.py:12 +#: settings/serializers/terminal.py:14 msgid "All" msgstr "すべて" @@ -724,7 +728,7 @@ msgstr "手動トリガー" msgid "Timing trigger" msgstr "タイミングトリガー" -#: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31 +#: assets/models/backup.py:105 audits/models.py:45 ops/models/command.py:31 #: perms/models/base.py:89 terminal/models/session.py:58 #: tickets/models/ticket/apply_application.py:29 #: tickets/models/ticket/apply_asset.py:23 @@ -753,7 +757,7 @@ msgstr "アカウントのバックアップスナップショット" msgid "Trigger mode" msgstr "トリガーモード" -#: assets/models/backup.py:119 audits/models.py:127 +#: assets/models/backup.py:119 audits/models.py:145 #: terminal/models/sharing.py:108 #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 @@ -762,8 +766,8 @@ msgstr "トリガーモード" msgid "Reason" msgstr "理由" -#: assets/models/backup.py:121 audits/serializers.py:82 -#: audits/serializers.py:97 ops/models/adhoc.py:260 +#: assets/models/backup.py:121 audits/serializers.py:88 +#: audits/serializers.py:103 ops/models/adhoc.py:260 #: terminal/serializers/session.py:36 #: xpack/plugins/change_auth_plan/models/base.py:202 #: xpack/plugins/change_auth_plan/serializers/app.py:67 @@ -785,7 +789,7 @@ msgstr "不明" msgid "Ok" msgstr "OK" -#: assets/models/base.py:32 audits/models.py:118 +#: assets/models/base.py:32 audits/models.py:136 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:199 #: xpack/plugins/cloud/const.py:36 @@ -802,10 +806,10 @@ msgstr "確認済みの日付" #: 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/confirm/password.py:9 +#: audits/signal_handlers.py:58 authentication/confirm/password.py:9 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 -#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 #: users/forms/profile.py:22 users/serializers/user.py:94 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 @@ -868,7 +872,7 @@ msgid "Default" msgstr "デフォルト" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:916 +#: users/models/user.py:918 msgid "System" msgstr "システム" @@ -1166,7 +1170,7 @@ msgid "Actions" msgstr "アクション" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:65 +#: settings/serializers/auth/ldap.py:66 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定期的なパフォーマンス" @@ -1176,7 +1180,7 @@ msgstr "定期的なパフォーマンス" msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/base.py:16 users/models/user.py:697 +#: assets/serializers/base.py:16 users/models/user.py:699 msgid "Private key" msgstr "ssh秘密鍵" @@ -1428,152 +1432,168 @@ msgstr "一致する資産がない、タスクを停止" msgid "Audits" msgstr "監査" -#: audits/models.py:27 audits/models.py:59 +#: audits/backends/db.py:12 +msgid "The text content is too long. Use Elasticsearch to store operation logs" +msgstr "文章の内容が長すぎる。Elasticsearchで操作履歴を保存する" + +#: audits/backends/db.py:24 audits/backends/db.py:26 +msgid "Tips" +msgstr "謎々" + +#: audits/handler.py:128 +msgid "Yes" +msgstr "是" + +#: audits/handler.py:128 +msgid "No" +msgstr "否" + +#: audits/models.py:28 audits/models.py:60 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:226 msgid "Delete" msgstr "削除" -#: audits/models.py:28 +#: audits/models.py:29 msgid "Upload" msgstr "アップロード" -#: audits/models.py:29 +#: audits/models.py:30 msgid "Download" msgstr "ダウンロード" -#: audits/models.py:30 +#: audits/models.py:31 msgid "Rmdir" msgstr "Rmdir" -#: audits/models.py:31 +#: audits/models.py:32 msgid "Rename" msgstr "名前の変更" -#: audits/models.py:32 +#: audits/models.py:33 msgid "Mkdir" msgstr "Mkdir" -#: audits/models.py:33 +#: audits/models.py:34 msgid "Symlink" msgstr "Symlink" -#: audits/models.py:38 audits/models.py:66 audits/models.py:89 +#: audits/models.py:39 audits/models.py:67 audits/models.py:107 #: terminal/models/session.py:51 terminal/models/sharing.py:96 msgid "Remote addr" msgstr "リモートaddr" -#: audits/models.py:41 +#: audits/models.py:42 msgid "Operate" msgstr "操作" -#: audits/models.py:42 +#: audits/models.py:43 msgid "Filename" msgstr "ファイル名" -#: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:104 +#: audits/models.py:44 audits/models.py:135 terminal/models/sharing.py:104 #: tickets/views/approve.py:115 #: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 msgid "Success" msgstr "成功" -#: audits/models.py:47 +#: audits/models.py:48 msgid "File transfer log" msgstr "ファイル転送ログ" -#: audits/models.py:56 +#: audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:223 msgid "Create" msgstr "作成" -#: audits/models.py:57 rbac/tree.py:224 +#: audits/models.py:58 rbac/tree.py:224 msgid "View" msgstr "表示" -#: audits/models.py:58 rbac/tree.py:225 templates/_csv_import_export.html:18 +#: audits/models.py:59 rbac/tree.py:225 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" -#: audits/models.py:64 audits/serializers.py:63 +#: audits/models.py:65 audits/serializers.py:69 msgid "Resource Type" msgstr "リソースタイプ" -#: audits/models.py:65 +#: audits/models.py:66 msgid "Resource" msgstr "リソース" -#: audits/models.py:67 audits/models.py:90 +#: audits/models.py:68 audits/models.py:108 #: terminal/backends/command/serializers.py:40 msgid "Datetime" msgstr "時間" -#: audits/models.py:82 +#: audits/models.py:100 msgid "Operate log" msgstr "ログの操作" -#: audits/models.py:88 +#: audits/models.py:106 msgid "Change by" msgstr "による変更" -#: audits/models.py:96 +#: audits/models.py:114 msgid "Password change log" msgstr "パスワード変更ログ" -#: audits/models.py:111 +#: audits/models.py:129 msgid "Disabled" msgstr "無効" -#: audits/models.py:112 settings/models.py:37 +#: audits/models.py:130 settings/models.py:37 msgid "Enabled" msgstr "有効化" -#: audits/models.py:113 +#: audits/models.py:131 msgid "-" msgstr "-" -#: audits/models.py:122 +#: audits/models.py:140 msgid "Login type" msgstr "ログインタイプ" -#: audits/models.py:123 tickets/models/ticket/login_confirm.py:10 +#: audits/models.py:141 tickets/models/ticket/login_confirm.py:10 msgid "Login ip" msgstr "ログインIP" -#: audits/models.py:124 +#: audits/models.py:142 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/models/ticket/login_confirm.py:11 msgid "Login city" msgstr "ログイン都市" -#: audits/models.py:125 audits/serializers.py:44 +#: audits/models.py:143 audits/serializers.py:44 msgid "User agent" msgstr "ユーザーエージェント" -#: audits/models.py:126 +#: audits/models.py:144 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:692 #: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" -#: audits/models.py:128 terminal/models/status.py:33 +#: audits/models.py:146 terminal/models/status.py:33 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175 #: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "ステータス" -#: audits/models.py:129 +#: audits/models.py:147 msgid "Date login" msgstr "日付ログイン" -#: audits/models.py:130 audits/serializers.py:46 +#: audits/models.py:148 audits/serializers.py:46 msgid "Authentication backend" msgstr "認証バックエンド" -#: audits/models.py:169 +#: audits/models.py:187 msgid "User login log" msgstr "ユーザーログインログ" @@ -1593,234 +1613,67 @@ msgstr "MFAディスプレイ" msgid "Reason display" msgstr "理由表示" -#: audits/serializers.py:84 +#: audits/serializers.py:90 msgid "Hosts display" msgstr "ホスト表示" -#: audits/serializers.py:96 ops/models/command.py:27 +#: audits/serializers.py:102 ops/models/command.py:27 #: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "結果" -#: audits/serializers.py:98 terminal/serializers/storage.py:157 +#: audits/serializers.py:104 terminal/serializers/storage.py:157 msgid "Hosts" msgstr "ホスト" -#: audits/serializers.py:99 +#: audits/serializers.py:105 msgid "Run as" msgstr "として実行" -#: audits/serializers.py:101 +#: audits/serializers.py:107 msgid "Run as display" msgstr "ディスプレイとして実行する" -#: audits/serializers.py:102 authentication/models.py:81 +#: audits/serializers.py:108 authentication/models.py:81 #: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "ユーザー表示" -#: audits/signal_handlers.py:49 +#: audits/signal_handlers.py:57 msgid "SSH Key" msgstr "SSHキー" -#: audits/signal_handlers.py:51 +#: audits/signal_handlers.py:59 settings/serializers/auth/sso.py:10 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:52 +#: audits/signal_handlers.py:60 msgid "Auth Token" msgstr "認証トークン" -#: audits/signal_handlers.py:53 authentication/notifications.py:73 +#: audits/signal_handlers.py:61 authentication/notifications.py:73 #: authentication/views/login.py:73 authentication/views/wecom.py:178 -#: notifications/backends/__init__.py:11 users/models/user.py:728 +#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 +#: users/models/user.py:730 msgid "WeCom" msgstr "企業微信" -#: audits/signal_handlers.py:54 authentication/views/feishu.py:144 +#: audits/signal_handlers.py:62 authentication/views/feishu.py:144 #: authentication/views/login.py:85 notifications/backends/__init__.py:14 -#: users/models/user.py:730 +#: settings/serializers/auth/feishu.py:10 users/models/user.py:732 msgid "FeiShu" msgstr "本を飛ばす" -#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179 +#: audits/signal_handlers.py:63 authentication/views/dingtalk.py:179 #: authentication/views/login.py:79 notifications/backends/__init__.py:12 -#: users/models/user.py:729 +#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:731 msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:56 authentication/models.py:267 +#: audits/signal_handlers.py:64 authentication/models.py:267 msgid "Temporary token" msgstr "仮パスワード" -#: audits/signal_handlers.py:68 -msgid "User and Group" -msgstr "ユーザーとグループ" - -#: audits/signal_handlers.py:69 -#, python-brace-format -msgid "{User} JOINED {UserGroup}" -msgstr "{User} に参加 {UserGroup}" - -#: audits/signal_handlers.py:70 -#, python-brace-format -msgid "{User} LEFT {UserGroup}" -msgstr "{User} のそばを通る {UserGroup}" - -#: audits/signal_handlers.py:73 -msgid "Asset and SystemUser" -msgstr "資産およびシステム・ユーザー" - -#: audits/signal_handlers.py:74 -#, python-brace-format -msgid "{Asset} ADD {SystemUser}" -msgstr "{Asset} 追加 {SystemUser}" - -#: audits/signal_handlers.py:75 -#, python-brace-format -msgid "{Asset} REMOVE {SystemUser}" -msgstr "{Asset} 削除 {SystemUser}" - -#: audits/signal_handlers.py:78 -msgid "Node and Asset" -msgstr "ノードと資産" - -#: audits/signal_handlers.py:79 -#, python-brace-format -msgid "{Node} ADD {Asset}" -msgstr "{Node} 追加 {Asset}" - -#: audits/signal_handlers.py:80 -#, python-brace-format -msgid "{Node} REMOVE {Asset}" -msgstr "{Node} 削除 {Asset}" - -#: audits/signal_handlers.py:83 -msgid "User asset permissions" -msgstr "ユーザー資産の権限" - -#: audits/signal_handlers.py:84 -#, python-brace-format -msgid "{AssetPermission} ADD {User}" -msgstr "{AssetPermission} 追加 {User}" - -#: audits/signal_handlers.py:85 -#, python-brace-format -msgid "{AssetPermission} REMOVE {User}" -msgstr "{AssetPermission} 削除 {User}" - -#: audits/signal_handlers.py:88 -msgid "User group asset permissions" -msgstr "ユーザーグループの資産権限" - -#: audits/signal_handlers.py:89 -#, python-brace-format -msgid "{AssetPermission} ADD {UserGroup}" -msgstr "{AssetPermission} 追加 {UserGroup}" - -#: audits/signal_handlers.py:90 -#, python-brace-format -msgid "{AssetPermission} REMOVE {UserGroup}" -msgstr "{AssetPermission} 削除 {UserGroup}" - -#: audits/signal_handlers.py:93 perms/models/asset_permission.py:29 -msgid "Asset permission" -msgstr "資産権限" - -#: audits/signal_handlers.py:94 -#, python-brace-format -msgid "{AssetPermission} ADD {Asset}" -msgstr "{AssetPermission} 追加 {Asset}" - -#: audits/signal_handlers.py:95 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Asset}" -msgstr "{AssetPermission} 削除 {Asset}" - -#: audits/signal_handlers.py:98 -msgid "Node permission" -msgstr "ノード権限" - -#: audits/signal_handlers.py:99 -#, python-brace-format -msgid "{AssetPermission} ADD {Node}" -msgstr "{AssetPermission} 追加 {Node}" - -#: audits/signal_handlers.py:100 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Node}" -msgstr "{AssetPermission} 削除 {Node}" - -#: audits/signal_handlers.py:103 -msgid "Asset permission and SystemUser" -msgstr "資産権限とSystemUser" - -#: audits/signal_handlers.py:104 -#, python-brace-format -msgid "{AssetPermission} ADD {SystemUser}" -msgstr "{AssetPermission} 追加 {SystemUser}" - -#: audits/signal_handlers.py:105 -#, python-brace-format -msgid "{AssetPermission} REMOVE {SystemUser}" -msgstr "{AssetPermission} 削除 {SystemUser}" - -#: audits/signal_handlers.py:108 -msgid "User application permissions" -msgstr "ユーザーアプリケーションの権限" - -#: audits/signal_handlers.py:109 -#, python-brace-format -msgid "{ApplicationPermission} ADD {User}" -msgstr "{ApplicationPermission} 追加 {User}" - -#: audits/signal_handlers.py:110 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {User}" -msgstr "{ApplicationPermission} 削除 {User}" - -#: audits/signal_handlers.py:113 -msgid "User group application permissions" -msgstr "ユーザーグループアプリケーションの権限" - -#: audits/signal_handlers.py:114 -#, python-brace-format -msgid "{ApplicationPermission} ADD {UserGroup}" -msgstr "{ApplicationPermission} 追加 {UserGroup}" - -#: audits/signal_handlers.py:115 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {UserGroup}" -msgstr "{ApplicationPermission} 削除 {UserGroup}" - -#: audits/signal_handlers.py:118 perms/models/application_permission.py:38 -msgid "Application permission" -msgstr "申請許可" - -#: audits/signal_handlers.py:119 -#, python-brace-format -msgid "{ApplicationPermission} ADD {Application}" -msgstr "{ApplicationPermission} 追加 {Application}" - -#: audits/signal_handlers.py:120 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {Application}" -msgstr "{ApplicationPermission} 削除 {Application}" - -#: audits/signal_handlers.py:123 -msgid "Application permission and SystemUser" -msgstr "アプリケーション権限とSystemUser" - -#: audits/signal_handlers.py:124 -#, python-brace-format -msgid "{ApplicationPermission} ADD {SystemUser}" -msgstr "{ApplicationPermission} 追加 {SystemUser}" - -#: audits/signal_handlers.py:125 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {SystemUser}" -msgstr "{ApplicationPermission} 削除 {SystemUser}" - #: authentication/api/confirm.py:40 msgid "This action require verify your MFA" msgstr "この操作には、MFAを検証する必要があります" @@ -1829,6 +1682,12 @@ msgstr "この操作には、MFAを検証する必要があります" msgid "Current user not support mfa type: {}" msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" +#: authentication/apps.py:7 settings/serializers/auth/base.py:10 +#: settings/serializers/auth/cas.py:10 settings/serializers/auth/dingtalk.py:10 +#: settings/serializers/auth/feishu.py:10 settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/oauth2.py:19 settings/serializers/auth/oidc.py:12 +#: settings/serializers/auth/radius.py:13 settings/serializers/auth/saml2.py:11 +#: settings/serializers/auth/sso.py:10 settings/serializers/auth/wecom.py:10 #: authentication/api/password.py:29 terminal/api/session.py:224 #: users/views/profile/reset.py:74 msgid "User does not exist: {}" @@ -2142,7 +2001,7 @@ msgstr "Radius globalが有効になり、無効にできません" msgid "SMS verify code invalid" msgstr "SMS検証コードが無効" -#: authentication/mfa/sms.py:12 +#: authentication/mfa/sms.py:12 settings/serializers/auth/sms.py:27 msgid "SMS" msgstr "SMS" @@ -2188,13 +2047,13 @@ msgstr "SSO token" #: authentication/models.py:72 authentication/models.py:261 #: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 +#: settings/serializers/auth/radius.py:19 msgid "Secret" msgstr "ひみつ" #: authentication/models.py:74 authentication/models.py:264 #: perms/models/base.py:90 tickets/models/ticket/apply_application.py:30 -#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:711 +#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:713 msgid "Date expired" msgstr "期限切れの日付" @@ -2315,7 +2174,7 @@ msgid "ID" msgstr "ID" #: authentication/templates/authentication/_access_key_modal.html:33 -#: terminal/notifications.py:93 terminal/notifications.py:141 +#: terminal/notifications.py:96 terminal/notifications.py:144 msgid "Date" msgstr "日付" @@ -2686,35 +2545,35 @@ msgstr "%(name)s は正常に更新されました" msgid "ugettext_lazy" msgstr "ugettext_lazy" -#: common/db/fields.py:80 +#: common/db/fields.py:81 msgid "Marshal dict data to char field" msgstr "チャーフィールドへのマーシャルディクトデータ" -#: common/db/fields.py:84 +#: common/db/fields.py:85 msgid "Marshal dict data to text field" msgstr "テキストフィールドへのマーシャルディクトデータ" -#: common/db/fields.py:96 +#: common/db/fields.py:97 msgid "Marshal list data to char field" msgstr "元帥リストデータをチャーフィールドに" -#: common/db/fields.py:100 +#: common/db/fields.py:101 msgid "Marshal list data to text field" msgstr "マーシャルリストデータをテキストフィールドに" -#: common/db/fields.py:104 +#: common/db/fields.py:105 msgid "Marshal data to char field" msgstr "チャーフィールドへのマーシャルデータ" -#: common/db/fields.py:108 +#: common/db/fields.py:109 msgid "Marshal data to text field" msgstr "テキストフィールドへのマーシャルデータ" -#: common/db/fields.py:150 +#: common/db/fields.py:151 msgid "Encrypt field using Secret Key" msgstr "Secret Keyを使用したフィールドの暗号化" -#: common/db/models.py:113 +#: common/db/models.py:115 msgid "Updated by" msgstr "によって更新" @@ -2784,6 +2643,14 @@ msgstr "選択項目のみエクスポート" msgid "Export filtered: %s" msgstr "検索のエクスポート: %s" +#: common/plugins/es.py:28 +msgid "Invalid elasticsearch config" +msgstr "無効なElasticsearch構成" + +#: common/plugins/es.py:33 +msgid "Not Support Elasticsearch8" +msgstr "サポートされていません Elasticsearch8" + #: common/sdk/im/exceptions.py:23 msgid "Network error, please contact system administrator" msgstr "ネットワークエラー、システム管理者に連絡してください" @@ -2904,10 +2771,32 @@ msgstr "" msgid "Notifications" msgstr "通知" +#: notifications/backends/__init__.py:10 settings/serializers/email.py:19 +#: settings/serializers/email.py:50 users/forms/profile.py:102 +#: users/models/user.py:671 +msgid "Email" +msgstr "メール" + #: notifications/backends/__init__.py:13 msgid "Site message" msgstr "サイトメッセージ" +#: notifications/models/notification.py:14 +msgid "receive backend" +msgstr "メッセージのバックエンド" + +#: notifications/models/notification.py:17 +msgid "User message" +msgstr "ユーザメッセージ" + +#: notifications/models/notification.py:20 +msgid "{} subscription" +msgstr "{} 購読" + +#: notifications/models/notification.py:32 +msgid "System message" +msgstr "システムメッセージ" + #: ops/api/celery.py:61 ops/api/celery.py:76 msgid "Waiting task start" msgstr "タスク開始待ち" @@ -2921,12 +2810,12 @@ msgid "App ops" msgstr "アプリ操作" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:72 +#: settings/serializers/auth/ldap.py:73 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:69 +#: settings/serializers/auth/ldap.py:70 msgid "Regularly perform" msgstr "定期的に実行する" @@ -3116,7 +3005,7 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:85 #: orgs/models.py:217 rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 #: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 msgid "Organization" msgstr "組織" @@ -3150,6 +3039,10 @@ msgstr "管理者は権限を変更しています。お待ちください" msgid "The authorization cannot be revoked for the time being" msgstr "当分の間、承認を取り消すことはできません。" +#: perms/models/application_permission.py:38 +msgid "Application permission" +msgstr "申請許可" + #: perms/models/application_permission.py:111 msgid "Permed application" msgstr "許可されたアプリケーション" @@ -3166,6 +3059,10 @@ msgstr "ユーザーアプリを表示できます" msgid "Can view usergroup apps" msgstr "ユーザー・グループ認可の適用を表示できます" +#: perms/models/asset_permission.py:29 +msgid "Asset permission" +msgstr "資産権限" + #: perms/models/asset_permission.py:134 msgid "Ungrouped" msgstr "グループ化されていません" @@ -3407,19 +3304,15 @@ msgstr "Webターミナルを表示できます" msgid "Can view file manager" msgstr "ファイルマネージャを表示できます" -#: rbac/models/permission.py:26 -msgid "Permission" +#: rbac/models/permission.py:26 rbac/models/role.py:34 +msgid "Permissions" msgstr "権限" #: rbac/models/role.py:31 rbac/models/rolebinding.py:38 -#: settings/serializers/auth/oauth2.py:35 +#: settings/serializers/auth/oauth2.py:37 msgid "Scope" msgstr "スコープ" -#: rbac/models/role.py:34 -msgid "Permissions" -msgstr "権限" - #: rbac/models/role.py:36 msgid "Built-in" msgstr "内蔵" @@ -3596,6 +3489,10 @@ msgstr "携帯番号をテストこのフィールドは必須です" msgid "Settings" msgstr "設定" +#: settings/models.py:36 +msgid "Encrypted" +msgstr "暗号化された" + #: settings/models.py:158 msgid "Can change email setting" msgstr "メール設定を変更できます" @@ -3636,122 +3533,134 @@ msgstr "ターミナルの設定を変えることができます" msgid "Can change other setting" msgstr "他の設定を変えることができます" -#: settings/serializers/auth/base.py:10 +#: settings/serializers/auth/base.py:10 settings/serializers/basic.py:27 +msgid "Basic" +msgstr "基本" + +#: settings/serializers/auth/base.py:12 msgid "CAS Auth" msgstr "CAS 認証" -#: settings/serializers/auth/base.py:11 +#: settings/serializers/auth/base.py:13 msgid "OPENID Auth" msgstr "OPENID 認証" -#: settings/serializers/auth/base.py:12 +#: settings/serializers/auth/base.py:14 msgid "RADIUS Auth" msgstr "RADIUS 認証" -#: settings/serializers/auth/base.py:13 +#: settings/serializers/auth/base.py:15 msgid "DingTalk Auth" msgstr "くぎ 認証" -#: settings/serializers/auth/base.py:14 +#: settings/serializers/auth/base.py:16 msgid "FeiShu Auth" msgstr "飛本 認証" -#: settings/serializers/auth/base.py:15 +#: settings/serializers/auth/base.py:17 msgid "WeCom Auth" msgstr "企業微信 認証" -#: settings/serializers/auth/base.py:16 +#: settings/serializers/auth/base.py:18 msgid "SSO Auth" msgstr "SSO Token 認証" -#: settings/serializers/auth/base.py:17 +#: settings/serializers/auth/base.py:19 msgid "SAML2 Auth" msgstr "SAML2 認証" -#: settings/serializers/auth/base.py:20 settings/serializers/basic.py:36 +#: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 msgid "Forgot password url" msgstr "パスワードのURLを忘れた" -#: settings/serializers/auth/base.py:26 +#: settings/serializers/auth/base.py:28 msgid "Enable login redirect msg" msgstr "ログインリダイレクトの有効化msg" #: settings/serializers/auth/cas.py:10 +msgid "CAS" +msgstr "CAS" + +#: settings/serializers/auth/cas.py:12 msgid "Enable CAS Auth" msgstr "CAS 認証の有効化" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:48 +#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 msgid "Server url" msgstr "サービス側アドレス" -#: settings/serializers/auth/cas.py:14 +#: settings/serializers/auth/cas.py:16 msgid "Proxy server url" msgstr "コールバックアドレス" -#: settings/serializers/auth/cas.py:16 settings/serializers/auth/oauth2.py:53 -#: settings/serializers/auth/saml2.py:32 +#: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:55 +#: settings/serializers/auth/saml2.py:34 msgid "Logout completely" msgstr "同期ログアウト" -#: settings/serializers/auth/cas.py:21 +#: settings/serializers/auth/cas.py:23 msgid "Username attr" msgstr "ユーザー名のプロパティ" -#: settings/serializers/auth/cas.py:24 +#: settings/serializers/auth/cas.py:26 msgid "Enable attributes map" msgstr "属性マップの有効化" -#: settings/serializers/auth/cas.py:26 settings/serializers/auth/saml2.py:31 +#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 msgid "Rename attr" msgstr "マッピングのプロパティ" -#: settings/serializers/auth/cas.py:27 +#: settings/serializers/auth/cas.py:29 msgid "Create user if not" msgstr "そうでない場合はユーザーを作成" -#: settings/serializers/auth/dingtalk.py:13 +#: settings/serializers/auth/dingtalk.py:15 msgid "Enable DingTalk Auth" msgstr "ピン認証の有効化" -#: settings/serializers/auth/feishu.py:12 +#: settings/serializers/auth/feishu.py:14 msgid "Enable FeiShu Auth" msgstr "飛本認証の有効化" -#: settings/serializers/auth/ldap.py:41 +#: settings/serializers/auth/ldap.py:39 +msgid "LDAP" +msgstr "LDAP" + +#: settings/serializers/auth/ldap.py:42 msgid "LDAP server" msgstr "LDAPサーバー" -#: settings/serializers/auth/ldap.py:42 +#: settings/serializers/auth/ldap.py:43 msgid "eg: ldap://localhost:389" msgstr "例: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:44 +#: settings/serializers/auth/ldap.py:45 msgid "Bind DN" msgstr "DN のバインド" -#: settings/serializers/auth/ldap.py:49 +#: settings/serializers/auth/ldap.py:50 msgid "User OU" msgstr "ユーザー OU" -#: settings/serializers/auth/ldap.py:50 +#: settings/serializers/auth/ldap.py:51 msgid "Use | split multi OUs" msgstr "使用 | splitマルチ OU" -#: settings/serializers/auth/ldap.py:53 +#: settings/serializers/auth/ldap.py:54 msgid "User search filter" msgstr "ユーザー検索フィルター" -#: settings/serializers/auth/ldap.py:54 +#: settings/serializers/auth/ldap.py:55 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)" -#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oauth2.py:55 -#: settings/serializers/auth/oidc.py:36 +#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/oauth2.py:57 +#: settings/serializers/auth/oidc.py:37 msgid "User attr map" msgstr "ユーザー属性マッピング" -#: settings/serializers/auth/ldap.py:58 +#: settings/serializers/auth/ldap.py:59 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -3759,77 +3668,85 @@ msgstr "" "ユーザー属性マッピングは、LDAPのユーザー属性をjumpserverユーザーにマッピング" "する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" -#: settings/serializers/auth/ldap.py:76 +#: settings/serializers/auth/ldap.py:77 msgid "Connect timeout" msgstr "接続タイムアウト" -#: settings/serializers/auth/ldap.py:78 +#: settings/serializers/auth/ldap.py:79 msgid "Search paged size" msgstr "ページサイズを検索" -#: settings/serializers/auth/ldap.py:80 +#: settings/serializers/auth/ldap.py:81 msgid "Enable LDAP auth" msgstr "LDAP認証の有効化" -#: settings/serializers/auth/oauth2.py:20 +#: settings/serializers/auth/oauth2.py:19 +msgid "OAuth2" +msgstr "OAuth2" + +#: settings/serializers/auth/oauth2.py:22 msgid "Enable OAuth2 Auth" msgstr "OAuth2認証の有効化" -#: settings/serializers/auth/oauth2.py:23 +#: settings/serializers/auth/oauth2.py:25 msgid "Logo" msgstr "アイコン" -#: settings/serializers/auth/oauth2.py:26 +#: settings/serializers/auth/oauth2.py:28 msgid "Service provider" msgstr "サービスプロバイダー" -#: settings/serializers/auth/oauth2.py:29 settings/serializers/auth/oidc.py:18 +#: settings/serializers/auth/oauth2.py:31 settings/serializers/auth/oidc.py:19 msgid "Client Id" msgstr "クライアントID" -#: settings/serializers/auth/oauth2.py:32 settings/serializers/auth/oidc.py:21 +#: settings/serializers/auth/oauth2.py:34 settings/serializers/auth/oidc.py:22 #: xpack/plugins/cloud/serializers/account_attrs.py:38 msgid "Client Secret" msgstr "クライアント秘密" -#: settings/serializers/auth/oauth2.py:38 settings/serializers/auth/oidc.py:62 +#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 msgid "Provider auth endpoint" msgstr "認証エンドポイントアドレス" -#: settings/serializers/auth/oauth2.py:41 settings/serializers/auth/oidc.py:65 +#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 msgid "Provider token endpoint" msgstr "プロバイダートークンエンドポイント" -#: settings/serializers/auth/oauth2.py:44 settings/serializers/auth/oidc.py:29 +#: settings/serializers/auth/oauth2.py:46 settings/serializers/auth/oidc.py:30 msgid "Client authentication method" msgstr "クライアント認証方式" -#: settings/serializers/auth/oauth2.py:48 settings/serializers/auth/oidc.py:71 +#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 msgid "Provider userinfo endpoint" msgstr "プロバイダーuserinfoエンドポイント" -#: settings/serializers/auth/oauth2.py:51 settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 msgid "Provider end session endpoint" msgstr "プロバイダーのセッション終了エンドポイント" -#: settings/serializers/auth/oauth2.py:58 settings/serializers/auth/oidc.py:92 -#: settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 +#: settings/serializers/auth/saml2.py:35 msgid "Always update user" msgstr "常にユーザーを更新" -#: settings/serializers/auth/oidc.py:15 +#: settings/serializers/auth/oidc.py:12 +msgid "OIDC" +msgstr "OIDC" + +#: settings/serializers/auth/oidc.py:16 msgid "Base site url" msgstr "ベースサイトのアドレス" -#: settings/serializers/auth/oidc.py:31 +#: settings/serializers/auth/oidc.py:32 msgid "Share session" msgstr "セッションの共有" -#: settings/serializers/auth/oidc.py:33 +#: settings/serializers/auth/oidc.py:34 msgid "Ignore ssl verification" msgstr "Ssl検証を無視する" -#: settings/serializers/auth/oidc.py:37 +#: settings/serializers/auth/oidc.py:38 msgid "" "User attr map present how to map OpenID user attr to jumpserver, username," "name,email is jumpserver attr" @@ -3837,83 +3754,91 @@ msgstr "" "ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン" "グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です" -#: settings/serializers/auth/oidc.py:45 +#: settings/serializers/auth/oidc.py:46 msgid "Use Keycloak" msgstr "Keycloakを使用する" -#: settings/serializers/auth/oidc.py:51 +#: settings/serializers/auth/oidc.py:52 msgid "Realm name" msgstr "レルム名" -#: settings/serializers/auth/oidc.py:57 +#: settings/serializers/auth/oidc.py:58 msgid "Enable OPENID Auth" msgstr "OIDC認証の有効化" -#: settings/serializers/auth/oidc.py:59 +#: settings/serializers/auth/oidc.py:60 msgid "Provider endpoint" msgstr "プロバイダーエンドポイント" -#: settings/serializers/auth/oidc.py:68 +#: settings/serializers/auth/oidc.py:69 msgid "Provider jwks endpoint" msgstr "プロバイダーjwksエンドポイント" -#: settings/serializers/auth/oidc.py:77 +#: settings/serializers/auth/oidc.py:78 msgid "Provider sign alg" msgstr "プロビダーサインalg" -#: settings/serializers/auth/oidc.py:80 +#: settings/serializers/auth/oidc.py:81 msgid "Provider sign key" msgstr "プロバイダ署名キー" -#: settings/serializers/auth/oidc.py:82 +#: settings/serializers/auth/oidc.py:83 msgid "Scopes" msgstr "スコープ" -#: settings/serializers/auth/oidc.py:84 +#: settings/serializers/auth/oidc.py:85 msgid "Id token max age" msgstr "IDトークンの最大年齢" -#: settings/serializers/auth/oidc.py:87 +#: settings/serializers/auth/oidc.py:88 msgid "Id token include claims" msgstr "IDトークンにはクレームが含まれます" -#: settings/serializers/auth/oidc.py:89 +#: settings/serializers/auth/oidc.py:90 msgid "Use state" msgstr "使用状態" -#: settings/serializers/auth/oidc.py:90 +#: settings/serializers/auth/oidc.py:91 msgid "Use nonce" msgstr "Nonceを使用" #: settings/serializers/auth/radius.py:13 +msgid "Radius" +msgstr "Radius" + +#: settings/serializers/auth/radius.py:15 msgid "Enable Radius Auth" msgstr "Radius認証の有効化" -#: settings/serializers/auth/radius.py:19 +#: settings/serializers/auth/radius.py:21 msgid "OTP in Radius" msgstr "Radius のOTP" -#: settings/serializers/auth/saml2.py:12 +#: settings/serializers/auth/saml2.py:11 +msgid "SAML2" +msgstr "SAML2" + +#: settings/serializers/auth/saml2.py:14 msgid "Enable SAML2 Auth" msgstr "SAML2認証の有効化" -#: settings/serializers/auth/saml2.py:15 +#: settings/serializers/auth/saml2.py:17 msgid "IDP metadata URL" msgstr "IDP metadata アドレス" -#: settings/serializers/auth/saml2.py:18 +#: settings/serializers/auth/saml2.py:20 msgid "IDP metadata XML" msgstr "IDP metadata XML" -#: settings/serializers/auth/saml2.py:21 +#: settings/serializers/auth/saml2.py:23 msgid "SP advanced settings" msgstr "詳細設定" -#: settings/serializers/auth/saml2.py:25 +#: settings/serializers/auth/saml2.py:27 msgid "SP private key" msgstr "SP プライベートキー" -#: settings/serializers/auth/saml2.py:29 +#: settings/serializers/auth/saml2.py:31 msgid "SP cert" msgstr "SP 証明書" @@ -3925,50 +3850,50 @@ msgstr "SMSの有効化" msgid "SMS provider / Protocol" msgstr "SMSプロバイダ / プロトコル" -#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:43 -#: settings/serializers/auth/sms.py:51 settings/serializers/auth/sms.py:60 -#: settings/serializers/auth/sms.py:71 settings/serializers/email.py:65 +#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:45 +#: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 +#: settings/serializers/auth/sms.py:73 settings/serializers/email.py:68 msgid "Signature" msgstr "署名" -#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:44 -#: settings/serializers/auth/sms.py:52 settings/serializers/auth/sms.py:61 +#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:46 +#: settings/serializers/auth/sms.py:54 settings/serializers/auth/sms.py:63 msgid "Template code" msgstr "テンプレートコード" -#: settings/serializers/auth/sms.py:29 +#: settings/serializers/auth/sms.py:31 msgid "Test phone" msgstr "テスト電話" -#: settings/serializers/auth/sms.py:58 +#: settings/serializers/auth/sms.py:60 msgid "App Access Address" msgstr "アプリケーションアドレス" -#: settings/serializers/auth/sms.py:59 +#: settings/serializers/auth/sms.py:61 msgid "Signature channel number" msgstr "署名チャネル番号" -#: settings/serializers/auth/sms.py:67 +#: settings/serializers/auth/sms.py:69 msgid "Enterprise code(SP id)" msgstr "企業コード(SP id)" -#: settings/serializers/auth/sms.py:68 +#: settings/serializers/auth/sms.py:70 msgid "Shared secret(Shared secret)" msgstr "パスワードを共有する(Shared secret)" -#: settings/serializers/auth/sms.py:69 +#: settings/serializers/auth/sms.py:71 msgid "Original number(Src id)" msgstr "元の番号(Src id)" -#: settings/serializers/auth/sms.py:70 +#: settings/serializers/auth/sms.py:72 msgid "Business type(Service id)" msgstr "ビジネス・タイプ(Service id)" -#: settings/serializers/auth/sms.py:73 +#: settings/serializers/auth/sms.py:75 msgid "Template" msgstr "テンプレート" -#: settings/serializers/auth/sms.py:74 +#: settings/serializers/auth/sms.py:76 #, python-brace-format msgid "" "Template need contain {code} and Signature + template length does not exceed " @@ -3979,34 +3904,34 @@ msgstr "" "満です。たとえば、認証コードは{code}で、有効期間は5分です。他の人には言わない" "でください。" -#: settings/serializers/auth/sms.py:83 +#: settings/serializers/auth/sms.py:85 #, python-brace-format msgid "The template needs to contain {code}" msgstr "テンプレートには{code}を含める必要があります" -#: settings/serializers/auth/sms.py:86 +#: settings/serializers/auth/sms.py:88 msgid "Signature + Template must not exceed 65 words" msgstr "署名+テンプレートの長さは65文字以内" -#: settings/serializers/auth/sso.py:11 +#: settings/serializers/auth/sso.py:13 msgid "Enable SSO auth" msgstr "SSO Token認証の有効化" -#: settings/serializers/auth/sso.py:12 +#: settings/serializers/auth/sso.py:14 msgid "Other service can using SSO token login to JumpServer without password" msgstr "" "他のサービスはパスワードなしでJumpServerへのSSOトークンログインを使用できます" -#: settings/serializers/auth/sso.py:15 +#: settings/serializers/auth/sso.py:17 msgid "SSO auth key TTL" msgstr "Token有効期間" -#: settings/serializers/auth/sso.py:15 +#: settings/serializers/auth/sso.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "Unit: second" msgstr "単位: 秒" -#: settings/serializers/auth/wecom.py:13 +#: settings/serializers/auth/wecom.py:15 msgid "Enable WeCom Auth" msgstr "企業微信認証の有効化" @@ -4018,23 +3943,23 @@ msgstr "件名" msgid "More url" msgstr "もっとURL" -#: settings/serializers/basic.py:28 +#: settings/serializers/basic.py:30 msgid "Site url" msgstr "サイトURL" -#: settings/serializers/basic.py:29 +#: settings/serializers/basic.py:31 msgid "eg: http://dev.jumpserver.org:8080" msgstr "例えば: http://dev.jumpserver.org:8080" -#: settings/serializers/basic.py:32 +#: settings/serializers/basic.py:34 msgid "User guide url" msgstr "ユーザーガイドurl" -#: settings/serializers/basic.py:33 +#: settings/serializers/basic.py:35 msgid "User first login update profile done redirect to it" msgstr "ユーザーの最初のログイン更新プロファイルがリダイレクトされました" -#: settings/serializers/basic.py:37 +#: settings/serializers/basic.py:39 msgid "" "The forgot password url on login page, If you use ldap or cas external " "authentication, you can set it" @@ -4042,57 +3967,61 @@ msgstr "" "ログインページでパスワードのURLを忘れてしまいました。ldapまたはcasの外部認証" "を使用している場合は、設定できます。" -#: settings/serializers/basic.py:41 +#: settings/serializers/basic.py:43 msgid "Global organization name" msgstr "グローバル組織名" -#: settings/serializers/basic.py:42 +#: settings/serializers/basic.py:44 msgid "The name of global organization to display" msgstr "表示するグローバル組織の名前" -#: settings/serializers/basic.py:44 +#: settings/serializers/basic.py:46 msgid "Enable announcement" msgstr "アナウンスの有効化" -#: settings/serializers/basic.py:45 +#: settings/serializers/basic.py:47 msgid "Announcement" msgstr "発表" -#: settings/serializers/basic.py:46 +#: settings/serializers/basic.py:48 msgid "Enable tickets" msgstr "チケットを有効にする" -#: settings/serializers/cleaning.py:10 +#: settings/serializers/cleaning.py:8 +msgid "Period clean" +msgstr "定時清掃" + +#: settings/serializers/cleaning.py:12 msgid "Login log keep days" msgstr "ログインログは日数を保持します" -#: settings/serializers/cleaning.py:10 settings/serializers/cleaning.py:14 -#: settings/serializers/cleaning.py:18 settings/serializers/cleaning.py:22 -#: settings/serializers/cleaning.py:26 settings/serializers/other.py:35 +#: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 +#: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 +#: settings/serializers/cleaning.py:28 settings/serializers/other.py:37 msgid "Unit: day" msgstr "単位: 日" -#: settings/serializers/cleaning.py:14 +#: settings/serializers/cleaning.py:16 msgid "Task log keep days" msgstr "タスクログは日数を保持します" -#: settings/serializers/cleaning.py:18 +#: settings/serializers/cleaning.py:20 msgid "Operate log keep days" msgstr "ログ管理日を操作する" -#: settings/serializers/cleaning.py:22 +#: settings/serializers/cleaning.py:24 msgid "FTP log keep days" msgstr "ダウンロードのアップロード" -#: settings/serializers/cleaning.py:26 +#: settings/serializers/cleaning.py:28 msgid "Cloud sync record keep days" msgstr "クラウド同期レコードは日数を保持します" -#: settings/serializers/cleaning.py:29 +#: settings/serializers/cleaning.py:31 msgid "Session keep duration" msgstr "セッション維持期間" -#: settings/serializers/cleaning.py:30 +#: settings/serializers/cleaning.py:32 msgid "" "Unit: days, Session, record, command will be delete if more than duration, " "only in database" @@ -4100,65 +4029,65 @@ msgstr "" "単位:日。セッション、録画、コマンドレコードがそれを超えると削除されます(デー" "タベースストレージにのみ影響します。ossなどは影響しません」影響を受ける)" -#: settings/serializers/email.py:20 +#: settings/serializers/email.py:21 msgid "SMTP host" msgstr "SMTPホスト" -#: settings/serializers/email.py:21 +#: settings/serializers/email.py:22 msgid "SMTP port" msgstr "SMTPポート" -#: settings/serializers/email.py:22 +#: settings/serializers/email.py:23 msgid "SMTP account" msgstr "SMTPアカウント" -#: settings/serializers/email.py:24 +#: settings/serializers/email.py:25 msgid "SMTP password" msgstr "SMTPパスワード" -#: settings/serializers/email.py:25 +#: settings/serializers/email.py:26 msgid "Tips: Some provider use token except password" msgstr "ヒント: 一部のプロバイダーはパスワード以外のトークンを使用します" -#: settings/serializers/email.py:28 +#: settings/serializers/email.py:29 msgid "Send user" msgstr "ユーザーを送信" -#: settings/serializers/email.py:29 +#: settings/serializers/email.py:30 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "" "ヒント: 送信メールアカウント、送信アカウントとしてのデフォルトのSMTPアカウン" "ト" -#: settings/serializers/email.py:32 +#: settings/serializers/email.py:33 msgid "Test recipient" msgstr "テスト受信者" -#: settings/serializers/email.py:33 +#: settings/serializers/email.py:34 msgid "Tips: Used only as a test mail recipient" msgstr "ヒント: テストメールの受信者としてのみ使用" -#: settings/serializers/email.py:37 +#: settings/serializers/email.py:38 msgid "If SMTP port is 465, may be select" msgstr "SMTPポートが465の場合は、" -#: settings/serializers/email.py:40 +#: settings/serializers/email.py:41 msgid "Use TLS" msgstr "TLSの使用" -#: settings/serializers/email.py:41 +#: settings/serializers/email.py:42 msgid "If SMTP port is 587, may be select" msgstr "SMTPポートが587の場合は、" -#: settings/serializers/email.py:44 +#: settings/serializers/email.py:45 msgid "Subject prefix" msgstr "件名プレフィックス" -#: settings/serializers/email.py:51 +#: settings/serializers/email.py:54 msgid "Create user email subject" msgstr "ユーザーメール件名の作成" -#: settings/serializers/email.py:52 +#: settings/serializers/email.py:55 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" @@ -4166,20 +4095,20 @@ msgstr "" "ヒント: ユーザーを作成するときに、メールの件名を送信します (例: アカウントを" "正常に作成)" -#: settings/serializers/email.py:56 +#: settings/serializers/email.py:59 msgid "Create user honorific" msgstr "ユーザー敬語の作成" -#: settings/serializers/email.py:57 +#: settings/serializers/email.py:60 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "" "ヒント: ユーザーを作成するときは、メールの敬語を送信します (例: こんにちは)" -#: settings/serializers/email.py:61 +#: settings/serializers/email.py:64 msgid "Create user email content" msgstr "ユーザーのメールコンテンツを作成する" -#: settings/serializers/email.py:62 +#: settings/serializers/email.py:65 #, python-brace-format msgid "" "Tips: When creating a user, send the content of the email, support " @@ -4188,72 +4117,76 @@ msgstr "" "ヒント:ユーザーの作成時にパスワード設定メールの内容を送信し、{username}{name}" "{email}ラベルをサポートします。" -#: settings/serializers/email.py:66 +#: settings/serializers/email.py:69 msgid "Tips: Email signature (eg:jumpserver)" msgstr "ヒント: メール署名 (例: jumpserver)" -#: settings/serializers/other.py:7 +#: settings/serializers/other.py:6 +msgid "More..." +msgstr "詳細..." + +#: settings/serializers/other.py:9 msgid "Email suffix" msgstr "メールのサフィックス" -#: settings/serializers/other.py:8 +#: settings/serializers/other.py:10 msgid "" "This is used by default if no email is returned during SSO authentication" msgstr "これは、SSO認証中にメールが返されない場合にデフォルトで使用されます。" -#: settings/serializers/other.py:12 +#: settings/serializers/other.py:14 msgid "OTP issuer name" msgstr "OTP発行者名" -#: settings/serializers/other.py:16 +#: settings/serializers/other.py:18 msgid "OTP valid window" msgstr "OTP有効なウィンドウ" -#: settings/serializers/other.py:21 +#: settings/serializers/other.py:23 msgid "CMD" msgstr "CMD" -#: settings/serializers/other.py:22 +#: settings/serializers/other.py:24 msgid "PowerShell" msgstr "PowerShell" -#: settings/serializers/other.py:24 +#: settings/serializers/other.py:26 msgid "Shell (Windows)" msgstr "シェル (Windows)" -#: settings/serializers/other.py:25 +#: settings/serializers/other.py:27 msgid "The shell type used when Windows assets perform ansible tasks" msgstr "" "Windowsアセットが実行可能なタスクを実行するときに使用されるシェルタイプ" -#: settings/serializers/other.py:29 +#: settings/serializers/other.py:31 msgid "Perm ungroup node" msgstr "グループ化されていないノードを表示" -#: settings/serializers/other.py:30 +#: settings/serializers/other.py:32 msgid "Perm single to ungroup node" msgstr "" "グループ化されていないノードに個別に許可された資産を配置し、資産が存在する" "ノードが表示されないようにしますが、そのノードが許可されていないという質問に" "質問" -#: settings/serializers/other.py:35 +#: settings/serializers/other.py:37 msgid "Ticket authorize default time" msgstr "デフォルト製造オーダ承認時間" -#: settings/serializers/other.py:39 +#: settings/serializers/other.py:41 msgid "Help Docs URL" msgstr "ドキュメントリンク" -#: settings/serializers/other.py:40 +#: settings/serializers/other.py:42 msgid "default: http://docs.jumpserver.org" msgstr "デフォルト: http://docs.jumpserver.org" -#: settings/serializers/other.py:44 +#: settings/serializers/other.py:46 msgid "Help Support URL" msgstr "サポートリンク" -#: settings/serializers/other.py:45 +#: settings/serializers/other.py:47 msgid "default: http://www.jumpserver.org/support/" msgstr "デフォルト: http://www.jumpserver.org/support/" @@ -4426,11 +4359,15 @@ msgstr "ログインcaptchaの有効化" msgid "Enable captcha to prevent robot authentication" msgstr "Captchaを有効にしてロボット認証を防止する" -#: settings/serializers/security.py:147 +#: settings/serializers/security.py:146 +msgid "Security" +msgstr "セキュリティ" + +#: settings/serializers/security.py:149 msgid "Enable terminal register" msgstr "ターミナルレジスタの有効化" -#: settings/serializers/security.py:149 +#: settings/serializers/security.py:151 msgid "" "Allow terminal register, after all terminal setup, you should disable this " "for security" @@ -4438,64 +4375,64 @@ msgstr "" "ターミナルレジスタを許可し、すべてのターミナルセットアップの後、セキュリティ" "のためにこれを無効にする必要があります" -#: settings/serializers/security.py:153 +#: settings/serializers/security.py:155 msgid "Enable watermark" msgstr "透かしの有効化" -#: settings/serializers/security.py:154 +#: settings/serializers/security.py:156 msgid "Enabled, the web session and replay contains watermark information" msgstr "Webセッションとリプレイには透かし情報が含まれています。" -#: settings/serializers/security.py:158 +#: settings/serializers/security.py:160 msgid "Connection max idle time" msgstr "接続最大アイドル時間" -#: settings/serializers/security.py:159 +#: settings/serializers/security.py:161 msgid "If idle time more than it, disconnect connection Unit: minute" msgstr "アイドル時間がそれ以上の場合は、接続単位を切断します: 分" -#: settings/serializers/security.py:162 +#: settings/serializers/security.py:164 msgid "Remember manual auth" msgstr "手動入力パスワードの保存" -#: settings/serializers/security.py:165 +#: settings/serializers/security.py:167 msgid "Enable change auth secure mode" msgstr "安全モードの変更を有効にする" -#: settings/serializers/security.py:168 +#: settings/serializers/security.py:170 msgid "Insecure command alert" msgstr "安全でないコマンドアラート" -#: settings/serializers/security.py:171 +#: settings/serializers/security.py:173 msgid "Email recipient" msgstr "メール受信者" -#: settings/serializers/security.py:172 +#: settings/serializers/security.py:174 msgid "Multiple user using , split" msgstr "複数のユーザーを使用して、分割" -#: settings/serializers/security.py:175 +#: settings/serializers/security.py:177 msgid "Batch command execution" msgstr "バッチコマンドの実行" -#: settings/serializers/security.py:176 +#: settings/serializers/security.py:178 msgid "Allow user run batch command or not using ansible" msgstr "ユーザー実行バッチコマンドを許可するか、ansibleを使用しない" -#: settings/serializers/security.py:179 +#: settings/serializers/security.py:181 msgid "Session share" msgstr "セッション共有" -#: settings/serializers/security.py:180 +#: settings/serializers/security.py:182 msgid "Enabled, Allows user active session to be shared with other users" msgstr "" "ユーザーのアクティブなセッションを他のユーザーと共有できるようにします。" -#: settings/serializers/security.py:183 +#: settings/serializers/security.py:185 msgid "Remote Login Protection" msgstr "リモートログイン保護" -#: settings/serializers/security.py:185 +#: settings/serializers/security.py:187 msgid "" "The system determines whether the login IP address belongs to a common login " "city. If the account is logged in from a common login city, the system sends " @@ -4505,19 +4442,28 @@ msgstr "" "します。アカウントが共通のログイン都市からログインしている場合、システムはリ" "モートログインリマインダーを送信します" -#: settings/serializers/terminal.py:13 +#: settings/serializers/settings.py:61 +#: users/templates/users/reset_password.html:29 +msgid "Setting" +msgstr "設定" + +#: settings/serializers/terminal.py:6 terminal/models/terminal.py:185 +msgid "Terminal" +msgstr "ターミナル" + +#: settings/serializers/terminal.py:15 msgid "Auto" msgstr "自動" -#: settings/serializers/terminal.py:19 +#: settings/serializers/terminal.py:21 msgid "Password auth" msgstr "パスワード認証" -#: settings/serializers/terminal.py:21 +#: settings/serializers/terminal.py:23 msgid "Public key auth" msgstr "鍵認証" -#: settings/serializers/terminal.py:22 +#: settings/serializers/terminal.py:24 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" @@ -4525,19 +4471,19 @@ msgstr "" "ヒント: AD/LDAPなどの他の認証方法を使用する場合は、サードパーティ製システムの" "削除後にこの項目を無効にする必要があります, ログインも可能" -#: settings/serializers/terminal.py:26 +#: settings/serializers/terminal.py:28 msgid "List sort by" msgstr "リストの並べ替え" -#: settings/serializers/terminal.py:29 +#: settings/serializers/terminal.py:31 msgid "List page size" msgstr "ページサイズを一覧表示" -#: settings/serializers/terminal.py:32 +#: settings/serializers/terminal.py:34 msgid "Telnet login regex" msgstr "Telnetログインregex" -#: settings/serializers/terminal.py:33 +#: settings/serializers/terminal.py:35 msgid "" "Tips: The login success message varies with devices. if you cannot log in to " "the device through Telnet, set this parameter" @@ -4545,15 +4491,15 @@ msgstr "" "ヒント: ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバ" "イスにログインできない場合は、このパラメーターを設定します。" -#: settings/serializers/terminal.py:36 +#: settings/serializers/terminal.py:38 msgid "Enable database proxy" msgstr "属性マップの有効化" -#: settings/serializers/terminal.py:37 +#: settings/serializers/terminal.py:39 msgid "Enable Razor" msgstr "Razor の有効化" -#: settings/serializers/terminal.py:38 +#: settings/serializers/terminal.py:40 msgid "Enable SSH Client" msgstr "SSH Clientの有効化" @@ -4916,14 +4862,6 @@ msgstr "オンラインセッションを持つ" msgid "Terminals" msgstr "ターミナル管理" -#: terminal/backends/command/es.py:28 -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 "普通" @@ -4965,7 +4903,7 @@ msgstr "リスクレベル表示" msgid "Timestamp" msgstr "タイムスタンプ" -#: terminal/backends/command/serializers.py:41 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:41 terminal/models/terminal.py:106 msgid "Remote Address" msgstr "リモートアドレス" @@ -5001,11 +4939,11 @@ msgstr "コマンドレコード" msgid "HTTPS Port" msgstr "HTTPS ポート" -#: terminal/models/endpoint.py:18 terminal/models/terminal.py:107 +#: terminal/models/endpoint.py:18 terminal/models/terminal.py:108 msgid "HTTP Port" msgstr "HTTP ポート" -#: terminal/models/endpoint.py:19 terminal/models/terminal.py:106 +#: terminal/models/endpoint.py:19 terminal/models/terminal.py:107 msgid "SSH Port" msgstr "SSH ポート" @@ -5153,15 +5091,15 @@ msgstr "スレッド" msgid "Boot Time" msgstr "ブート時間" -#: terminal/models/storage.py:28 +#: terminal/models/storage.py:29 msgid "Default storage" msgstr "デフォルトのストレージ" -#: terminal/models/storage.py:137 terminal/models/terminal.py:108 +#: terminal/models/storage.py:139 terminal/models/terminal.py:109 msgid "Command storage" msgstr "コマンドストレージ" -#: terminal/models/storage.py:197 terminal/models/terminal.py:109 +#: terminal/models/storage.py:199 terminal/models/terminal.py:110 msgid "Replay storage" msgstr "再生ストレージ" @@ -5173,15 +5111,19 @@ msgstr "アルグ" msgid "Kwargs" msgstr "クワーグ" -#: terminal/models/terminal.py:103 +#: terminal/models/terminal.py:104 msgid "type" msgstr "タイプ" -#: terminal/models/terminal.py:183 -msgid "Terminal" -msgstr "ターミナル" +#: terminal/models/terminal.py:111 +msgid "Application User" +msgstr "ユーザーの適用" -#: terminal/models/terminal.py:185 +#: terminal/models/terminal.py:112 +msgid "Is Accepted" +msgstr "受け入れられる" + +#: terminal/models/terminal.py:187 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" @@ -5193,11 +5135,11 @@ msgstr "セッション" msgid "Danger command alert" msgstr "危険コマンドアラート" -#: terminal/notifications.py:92 terminal/notifications.py:140 +#: terminal/notifications.py:95 terminal/notifications.py:143 msgid "Level" msgstr "レベル" -#: terminal/notifications.py:110 +#: terminal/notifications.py:113 msgid "Batch danger command alert" msgstr "一括危険コマンド警告" @@ -5839,7 +5781,7 @@ msgstr "公開鍵は古いものと同じであってはなりません。" msgid "Not a valid ssh public key" msgstr "有効なssh公開鍵ではありません" -#: users/forms/profile.py:164 users/models/user.py:700 +#: users/forms/profile.py:161 users/models/user.py:702 msgid "Public key" msgstr "公開キー" @@ -5863,43 +5805,52 @@ msgstr "アバター" msgid "Wechat" msgstr "微信" -#: users/models/user.py:703 +#: users/models/user.py:695 +msgid "OTP secret key" +msgstr "OTP 秘密" + +#: users/models/user.py:705 msgid "Secret key" msgstr "秘密キー" -#: users/models/user.py:719 +#: users/models/user.py:710 users/serializers/profile.py:149 +#: users/serializers/user.py:146 +msgid "Is first login" +msgstr "最初のログインです" + +#: users/models/user.py:721 msgid "Source" msgstr "ソース" -#: users/models/user.py:723 +#: users/models/user.py:725 msgid "Date password last updated" msgstr "最終更新日パスワード" -#: users/models/user.py:726 +#: users/models/user.py:728 msgid "Need update password" msgstr "更新パスワードが必要" -#: users/models/user.py:901 +#: users/models/user.py:903 msgid "Can invite user" msgstr "ユーザーを招待できます" -#: users/models/user.py:902 +#: users/models/user.py:904 msgid "Can remove user" msgstr "ユーザーを削除できます" -#: users/models/user.py:903 +#: users/models/user.py:905 msgid "Can match user" msgstr "ユーザーに一致できます" -#: users/models/user.py:912 +#: users/models/user.py:914 msgid "Administrator" msgstr "管理者" -#: users/models/user.py:915 +#: users/models/user.py:917 msgid "Administrator is the super user of system" msgstr "管理者はシステムのスーパーユーザーです" -#: users/models/user.py:940 +#: users/models/user.py:942 msgid "User password history" msgstr "ユーザーパスワード履歴" @@ -5950,10 +5901,6 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:149 users/serializers/user.py:146 -msgid "Is first login" -msgstr "最初のログインです" - #: users/serializers/user.py:28 msgid "System roles" msgstr "システムの役割" @@ -6142,10 +6089,6 @@ msgstr "パスワードを満たす必要があります" msgid "Password strength" msgstr "パスワードの強さ" -#: users/templates/users/reset_password.html:29 -msgid "Setting" -msgstr "設定" - #: users/templates/users/reset_password.html:48 msgid "Very weak" msgstr "非常に弱い" @@ -7019,7 +6962,7 @@ msgstr "ログアウトページのロゴ" msgid "Theme" msgstr "テーマ" -#: xpack/plugins/interface/models.py:43 +#: xpack/plugins/interface/models.py:43 xpack/plugins/interface/models.py:84 msgid "Interface setting" msgstr "インターフェイスの設定" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a9a108f37..c71cb52d0 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-11-04 11:37+0800\n" +"POT-Creation-Date: 2022-11-02 10:13+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:70 perms/models/base.py:83 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:14 terminal/models/endpoint.py:87 -#: terminal/models/storage.py:26 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:33 +#: terminal/models/storage.py:27 terminal/models/task.py:16 +#: terminal/models/terminal.py:101 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:669 #: xpack/plugins/cloud/models.py:28 msgid "Name" @@ -61,9 +61,9 @@ msgstr "激活中" #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 #: terminal/models/endpoint.py:22 terminal/models/endpoint.py:97 -#: terminal/models/storage.py:29 terminal/models/terminal.py:114 +#: terminal/models/storage.py:30 terminal/models/terminal.py:115 #: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 -#: users/models/group.py:16 users/models/user.py:706 +#: users/models/group.py:16 users/models/user.py:708 #: 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 @@ -85,15 +85,15 @@ msgid "Login confirm" 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:62 audits/models.py:87 audits/serializers.py:100 +#: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:38 +#: audits/models.py:63 audits/models.py:105 audits/serializers.py:106 #: authentication/models.py:54 authentication/models.py:78 orgs/models.py:220 #: perms/models/base.py:84 rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 -#: terminal/models/sharing.py:33 terminal/notifications.py:91 -#: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14 -#: users/models/user.py:899 users/models/user.py:930 +#: terminal/models/sharing.py:33 terminal/notifications.py:94 +#: terminal/notifications.py:142 tickets/models/comment.py:21 users/const.py:14 +#: users/models/user.py:901 users/models/user.py:932 #: users/serializers/group.py:19 msgid "User" msgstr "用户" @@ -104,7 +104,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:93 audits/models.py:63 audits/serializers.py:51 +#: assets/models/cmd_filter.py:93 audits/models.py:64 audits/serializers.py:57 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "动作" @@ -129,11 +129,11 @@ msgstr "系统用户" #: assets/models/asset.py:386 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:42 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:268 audits/models.py:39 +#: assets/serializers/system_user.py:268 audits/models.py:40 #: authentication/models.py:66 authentication/models.py:90 #: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 -#: terminal/notifications.py:90 +#: terminal/notifications.py:93 #: xpack/plugins/change_auth_plan/models/asset.py:199 #: xpack/plugins/change_auth_plan/serializers/asset.py:181 #: xpack/plugins/cloud/models.py:223 @@ -154,7 +154,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:121 +#: assets/models/gathered_user.py:15 audits/models.py:139 #: authentication/forms.py:25 authentication/forms.py:27 #: authentication/models.py:260 authentication/serializers/password_mfa.py:25 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -185,13 +185,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 terminal/serializers/endpoint.py:54 +#: settings/serializers/terminal.py:10 terminal/serializers/endpoint.py:54 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 +#: settings/serializers/terminal.py:9 msgid "Hostname" msgstr "主机名" @@ -245,7 +245,6 @@ msgstr "应用管理" #: applications/const.py:8 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:26 -#: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "数据库" @@ -261,11 +260,12 @@ msgstr "自定义" #: assets/models/backup.py:32 assets/models/cmd_filter.py:49 #: authentication/models.py:67 authentication/models.py:95 #: perms/models/application_permission.py:28 +#: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Application" msgstr "应用程序" #: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:46 assets/models/user.py:342 audits/models.py:40 +#: assets/models/cmd_filter.py:46 assets/models/user.py:342 audits/models.py:41 #: authentication/models.py:83 perms/models/application_permission.py:33 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 @@ -276,7 +276,7 @@ msgid "System user" msgstr "系统用户" #: applications/models/account.py:17 assets/models/authbook.py:21 -#: settings/serializers/auth/cas.py:18 +#: settings/serializers/auth/cas.py:20 msgid "Version" msgstr "版本" @@ -295,7 +295,7 @@ msgstr "可以查看应用账号密码" #: applications/models/application.py:221 #: applications/serializers/application.py:101 assets/models/label.py:21 #: perms/models/application_permission.py:21 -#: perms/serializers/application/user_permission.py:33 +#: perms/serializers/application/user_permission.py:33 settings/models.py:35 #: tickets/models/ticket/apply_application.py:15 #: xpack/plugins/change_auth_plan/models/app.py:25 msgid "Category" @@ -306,7 +306,7 @@ msgstr "类别" #: assets/models/cmd_filter.py:86 assets/models/user.py:250 #: authentication/models.py:70 perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 -#: terminal/models/storage.py:58 terminal/models/storage.py:143 +#: terminal/models/storage.py:59 terminal/models/storage.py:145 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:18 #: tickets/models/ticket/general.py:273 @@ -350,22 +350,24 @@ msgstr "类型名称" #: applications/serializers/application.py:105 assets/models/asset.py:230 #: 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/cmd_filter.py:48 common/db/models.py:114 -#: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 -#: orgs/models.py:72 orgs/models.py:223 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:931 +#: assets/models/cmd_filter.py:53 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:48 +#: common/db/models.py:116 common/mixins/models.py:50 ops/models/adhoc.py:39 +#: ops/models/command.py:30 orgs/models.py:72 orgs/models.py:223 +#: perms/models/base.py:92 users/models/group.py:18 users/models/user.py:933 +#: xpack/plugins/change_auth_plan/models/base.py:45 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "创建日期" #: applications/serializers/application.py:106 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:49 -#: common/db/models.py:115 common/mixins/models.py:51 ops/models/adhoc.py:40 -#: orgs/models.py:224 +#: assets/models/cmd_filter.py:54 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:117 +#: common/mixins/models.py:51 ops/models/adhoc.py:40 orgs/models.py:224 +#: xpack/plugins/change_auth_plan/models/base.py:46 msgid "Date updated" msgstr "更新日期" @@ -384,8 +386,8 @@ msgid "Cluster" msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:65 terminal/models/endpoint.py:15 +#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:16 +#: settings/serializers/auth/sms.py:67 terminal/models/endpoint.py:15 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "主机" @@ -399,13 +401,13 @@ msgstr "主机" #: 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:62 -#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:66 +#: settings/serializers/auth/radius.py:17 settings/serializers/auth/sms.py:68 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "端口" #: applications/serializers/attrs/application_category/db.py:16 -#: settings/serializers/email.py:36 +#: settings/serializers/email.py:37 msgid "Use SSL" msgstr "使用 SSL" @@ -637,8 +639,8 @@ msgstr "标签管理" #: assets/models/asset.py:229 assets/models/base.py:183 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:56 #: assets/models/cmd_filter.py:103 assets/models/group.py:21 -#: common/db/models.py:112 common/mixins/models.py:49 orgs/models.py:71 -#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:714 +#: common/db/models.py:114 common/mixins/models.py:49 orgs/models.py:71 +#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:716 #: 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 @@ -694,7 +696,7 @@ msgid "Can view asset history account secret" msgstr "可以查看资产历史账号密码" #: assets/models/backup.py:30 perms/models/base.py:54 -#: settings/serializers/terminal.py:12 +#: settings/serializers/terminal.py:14 msgid "All" msgstr "全部" @@ -719,7 +721,7 @@ msgstr "手动触发" msgid "Timing trigger" msgstr "定时触发" -#: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31 +#: assets/models/backup.py:105 audits/models.py:45 ops/models/command.py:31 #: perms/models/base.py:89 terminal/models/session.py:58 #: tickets/models/ticket/apply_application.py:29 #: tickets/models/ticket/apply_asset.py:23 @@ -748,7 +750,7 @@ msgstr "账号备份快照" msgid "Trigger mode" msgstr "触发模式" -#: assets/models/backup.py:119 audits/models.py:127 +#: assets/models/backup.py:119 audits/models.py:145 #: terminal/models/sharing.py:108 #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/serializers/app.py:66 @@ -757,8 +759,8 @@ msgstr "触发模式" msgid "Reason" msgstr "原因" -#: assets/models/backup.py:121 audits/serializers.py:82 -#: audits/serializers.py:97 ops/models/adhoc.py:260 +#: assets/models/backup.py:121 audits/serializers.py:88 +#: audits/serializers.py:103 ops/models/adhoc.py:260 #: terminal/serializers/session.py:36 #: xpack/plugins/change_auth_plan/models/base.py:202 #: xpack/plugins/change_auth_plan/serializers/app.py:67 @@ -780,7 +782,7 @@ msgstr "未知" msgid "Ok" msgstr "成功" -#: assets/models/base.py:32 audits/models.py:118 +#: assets/models/base.py:32 audits/models.py:136 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:199 #: xpack/plugins/cloud/const.py:36 @@ -797,10 +799,10 @@ msgstr "校验日期" #: 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/confirm/password.py:9 +#: audits/signal_handlers.py:58 authentication/confirm/password.py:9 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 -#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 #: users/forms/profile.py:22 users/serializers/user.py:94 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 @@ -863,7 +865,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:916 +#: users/models/user.py:918 msgid "System" msgstr "系统" @@ -1158,7 +1160,7 @@ msgid "Actions" msgstr "动作" #: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 -#: settings/serializers/auth/ldap.py:65 +#: settings/serializers/auth/ldap.py:66 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定时执行" @@ -1168,7 +1170,7 @@ msgstr "定时执行" msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/base.py:16 users/models/user.py:697 +#: assets/serializers/base.py:16 users/models/user.py:699 msgid "Private key" msgstr "ssh私钥" @@ -1416,152 +1418,168 @@ msgstr "没有匹配到资产,结束任务" msgid "Audits" msgstr "日志审计" -#: audits/models.py:27 audits/models.py:59 +#: audits/backends/db.py:12 +msgid "The text content is too long. Use Elasticsearch to store operation logs" +msgstr "文字内容太长。请使用 Elasticsearch 存储操作日志" + +#: audits/backends/db.py:24 audits/backends/db.py:26 +msgid "Tips" +msgstr "提示" + +#: audits/handler.py:127 +msgid "Yes" +msgstr "是" + +#: audits/handler.py:127 +msgid "No" +msgstr "否" + +#: audits/models.py:28 audits/models.py:60 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:226 msgid "Delete" msgstr "删除" -#: audits/models.py:28 +#: audits/models.py:29 msgid "Upload" msgstr "上传文件" -#: audits/models.py:29 +#: audits/models.py:30 msgid "Download" msgstr "下载文件" -#: audits/models.py:30 +#: audits/models.py:31 msgid "Rmdir" msgstr "删除目录" -#: audits/models.py:31 +#: audits/models.py:32 msgid "Rename" msgstr "重命名" -#: audits/models.py:32 +#: audits/models.py:33 msgid "Mkdir" msgstr "创建目录" -#: audits/models.py:33 +#: audits/models.py:34 msgid "Symlink" msgstr "建立软链接" -#: audits/models.py:38 audits/models.py:66 audits/models.py:89 +#: audits/models.py:39 audits/models.py:67 audits/models.py:107 #: terminal/models/session.py:51 terminal/models/sharing.py:96 msgid "Remote addr" msgstr "远端地址" -#: audits/models.py:41 +#: audits/models.py:42 msgid "Operate" msgstr "操作" -#: audits/models.py:42 +#: audits/models.py:43 msgid "Filename" msgstr "文件名" -#: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:104 +#: audits/models.py:44 audits/models.py:135 terminal/models/sharing.py:104 #: tickets/views/approve.py:115 #: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 msgid "Success" msgstr "成功" -#: audits/models.py:47 +#: audits/models.py:48 msgid "File transfer log" msgstr "文件管理" -#: audits/models.py:56 +#: audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:223 msgid "Create" msgstr "创建" -#: audits/models.py:57 rbac/tree.py:224 +#: audits/models.py:58 rbac/tree.py:224 msgid "View" msgstr "查看" -#: audits/models.py:58 rbac/tree.py:225 templates/_csv_import_export.html:18 +#: audits/models.py:59 rbac/tree.py:225 templates/_csv_import_export.html:18 #: templates/_csv_update_modal.html:6 msgid "Update" msgstr "更新" -#: audits/models.py:64 audits/serializers.py:63 +#: audits/models.py:65 audits/serializers.py:69 msgid "Resource Type" msgstr "资源类型" -#: audits/models.py:65 +#: audits/models.py:66 msgid "Resource" msgstr "资源" -#: audits/models.py:67 audits/models.py:90 +#: audits/models.py:68 audits/models.py:108 #: terminal/backends/command/serializers.py:40 msgid "Datetime" msgstr "日期" -#: audits/models.py:82 +#: audits/models.py:100 msgid "Operate log" msgstr "操作日志" -#: audits/models.py:88 +#: audits/models.py:106 msgid "Change by" msgstr "修改者" -#: audits/models.py:96 +#: audits/models.py:114 msgid "Password change log" msgstr "改密日志" -#: audits/models.py:111 +#: audits/models.py:129 msgid "Disabled" msgstr "禁用" -#: audits/models.py:112 settings/models.py:37 +#: audits/models.py:130 settings/models.py:37 msgid "Enabled" msgstr "启用" -#: audits/models.py:113 +#: audits/models.py:131 msgid "-" msgstr "-" -#: audits/models.py:122 +#: audits/models.py:140 msgid "Login type" msgstr "登录方式" -#: audits/models.py:123 tickets/models/ticket/login_confirm.py:10 +#: audits/models.py:141 tickets/models/ticket/login_confirm.py:10 msgid "Login ip" msgstr "登录IP" -#: audits/models.py:124 +#: audits/models.py:142 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/models/ticket/login_confirm.py:11 msgid "Login city" msgstr "登录城市" -#: audits/models.py:125 audits/serializers.py:44 +#: audits/models.py:143 audits/serializers.py:44 msgid "User agent" msgstr "用户代理" -#: audits/models.py:126 +#: audits/models.py:144 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:692 #: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" -#: audits/models.py:128 terminal/models/status.py:33 +#: audits/models.py:146 terminal/models/status.py:33 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175 #: xpack/plugins/cloud/models.py:227 msgid "Status" msgstr "状态" -#: audits/models.py:129 +#: audits/models.py:147 msgid "Date login" msgstr "登录日期" -#: audits/models.py:130 audits/serializers.py:46 +#: audits/models.py:148 audits/serializers.py:46 msgid "Authentication backend" msgstr "认证方式" -#: audits/models.py:169 +#: audits/models.py:187 msgid "User login log" msgstr "用户登录日志" @@ -1581,234 +1599,67 @@ msgstr "MFA名称" msgid "Reason display" msgstr "原因描述" -#: audits/serializers.py:84 +#: audits/serializers.py:90 msgid "Hosts display" msgstr "主机名称" -#: audits/serializers.py:96 ops/models/command.py:27 +#: audits/serializers.py:102 ops/models/command.py:27 #: xpack/plugins/cloud/models.py:173 msgid "Result" msgstr "结果" -#: audits/serializers.py:98 terminal/serializers/storage.py:157 +#: audits/serializers.py:104 terminal/serializers/storage.py:157 msgid "Hosts" msgstr "主机" -#: audits/serializers.py:99 +#: audits/serializers.py:105 msgid "Run as" msgstr "运行用户" -#: audits/serializers.py:101 +#: audits/serializers.py:107 msgid "Run as display" msgstr "运行用户名称" -#: audits/serializers.py:102 authentication/models.py:81 +#: audits/serializers.py:108 authentication/models.py:81 #: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "用户名称" -#: audits/signal_handlers.py:49 +#: audits/signal_handlers.py:57 msgid "SSH Key" msgstr "SSH 密钥" -#: audits/signal_handlers.py:51 +#: audits/signal_handlers.py:59 settings/serializers/auth/sso.py:10 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:52 +#: audits/signal_handlers.py:60 msgid "Auth Token" msgstr "认证令牌" -#: audits/signal_handlers.py:53 authentication/notifications.py:73 +#: audits/signal_handlers.py:61 authentication/notifications.py:73 #: authentication/views/login.py:73 authentication/views/wecom.py:178 -#: notifications/backends/__init__.py:11 users/models/user.py:728 +#: notifications/backends/__init__.py:11 settings/serializers/auth/wecom.py:10 +#: users/models/user.py:730 msgid "WeCom" msgstr "企业微信" -#: audits/signal_handlers.py:54 authentication/views/feishu.py:144 +#: audits/signal_handlers.py:62 authentication/views/feishu.py:144 #: authentication/views/login.py:85 notifications/backends/__init__.py:14 -#: users/models/user.py:730 +#: settings/serializers/auth/feishu.py:10 users/models/user.py:732 msgid "FeiShu" msgstr "飞书" -#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179 +#: audits/signal_handlers.py:63 authentication/views/dingtalk.py:179 #: authentication/views/login.py:79 notifications/backends/__init__.py:12 -#: users/models/user.py:729 +#: settings/serializers/auth/dingtalk.py:10 users/models/user.py:731 msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:56 authentication/models.py:267 +#: audits/signal_handlers.py:64 authentication/models.py:267 msgid "Temporary token" msgstr "临时密码" -#: audits/signal_handlers.py:68 -msgid "User and Group" -msgstr "用户与用户组" - -#: audits/signal_handlers.py:69 -#, python-brace-format -msgid "{User} JOINED {UserGroup}" -msgstr "{User} 加入 {UserGroup}" - -#: audits/signal_handlers.py:70 -#, python-brace-format -msgid "{User} LEFT {UserGroup}" -msgstr "{User} 离开 {UserGroup}" - -#: audits/signal_handlers.py:73 -msgid "Asset and SystemUser" -msgstr "资产与系统用户" - -#: audits/signal_handlers.py:74 -#, python-brace-format -msgid "{Asset} ADD {SystemUser}" -msgstr "{Asset} 添加 {SystemUser}" - -#: audits/signal_handlers.py:75 -#, python-brace-format -msgid "{Asset} REMOVE {SystemUser}" -msgstr "{Asset} 移除 {SystemUser}" - -#: audits/signal_handlers.py:78 -msgid "Node and Asset" -msgstr "节点与资产" - -#: audits/signal_handlers.py:79 -#, python-brace-format -msgid "{Node} ADD {Asset}" -msgstr "{Node} 添加 {Asset}" - -#: audits/signal_handlers.py:80 -#, python-brace-format -msgid "{Node} REMOVE {Asset}" -msgstr "{Node} 移除 {Asset}" - -#: audits/signal_handlers.py:83 -msgid "User asset permissions" -msgstr "用户资产授权" - -#: audits/signal_handlers.py:84 -#, python-brace-format -msgid "{AssetPermission} ADD {User}" -msgstr "{AssetPermission} 添加 {User}" - -#: audits/signal_handlers.py:85 -#, python-brace-format -msgid "{AssetPermission} REMOVE {User}" -msgstr "{AssetPermission} 移除 {User}" - -#: audits/signal_handlers.py:88 -msgid "User group asset permissions" -msgstr "用户组资产授权" - -#: audits/signal_handlers.py:89 -#, python-brace-format -msgid "{AssetPermission} ADD {UserGroup}" -msgstr "{AssetPermission} 添加 {UserGroup}" - -#: audits/signal_handlers.py:90 -#, python-brace-format -msgid "{AssetPermission} REMOVE {UserGroup}" -msgstr "{AssetPermission} 移除 {UserGroup}" - -#: audits/signal_handlers.py:93 perms/models/asset_permission.py:29 -msgid "Asset permission" -msgstr "资产授权" - -#: audits/signal_handlers.py:94 -#, python-brace-format -msgid "{AssetPermission} ADD {Asset}" -msgstr "{AssetPermission} 添加 {Asset}" - -#: audits/signal_handlers.py:95 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Asset}" -msgstr "{AssetPermission} 移除 {Asset}" - -#: audits/signal_handlers.py:98 -msgid "Node permission" -msgstr "节点授权" - -#: audits/signal_handlers.py:99 -#, python-brace-format -msgid "{AssetPermission} ADD {Node}" -msgstr "{AssetPermission} 添加 {Node}" - -#: audits/signal_handlers.py:100 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Node}" -msgstr "{AssetPermission} 移除 {Node}" - -#: audits/signal_handlers.py:103 -msgid "Asset permission and SystemUser" -msgstr "资产授权与系统用户" - -#: audits/signal_handlers.py:104 -#, python-brace-format -msgid "{AssetPermission} ADD {SystemUser}" -msgstr "{AssetPermission} 添加 {SystemUser}" - -#: audits/signal_handlers.py:105 -#, python-brace-format -msgid "{AssetPermission} REMOVE {SystemUser}" -msgstr "{AssetPermission} 移除 {SystemUser}" - -#: audits/signal_handlers.py:108 -msgid "User application permissions" -msgstr "用户应用授权" - -#: audits/signal_handlers.py:109 -#, python-brace-format -msgid "{ApplicationPermission} ADD {User}" -msgstr "{ApplicationPermission} 添加 {User}" - -#: audits/signal_handlers.py:110 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {User}" -msgstr "{ApplicationPermission} 移除 {User}" - -#: audits/signal_handlers.py:113 -msgid "User group application permissions" -msgstr "用户组应用授权" - -#: audits/signal_handlers.py:114 -#, python-brace-format -msgid "{ApplicationPermission} ADD {UserGroup}" -msgstr "{ApplicationPermission} 添加 {UserGroup}" - -#: audits/signal_handlers.py:115 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {UserGroup}" -msgstr "{ApplicationPermission} 移除 {UserGroup}" - -#: audits/signal_handlers.py:118 perms/models/application_permission.py:38 -msgid "Application permission" -msgstr "应用授权" - -#: audits/signal_handlers.py:119 -#, python-brace-format -msgid "{ApplicationPermission} ADD {Application}" -msgstr "{ApplicationPermission} 添加 {Application}" - -#: audits/signal_handlers.py:120 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {Application}" -msgstr "{ApplicationPermission} 移除 {Application}" - -#: audits/signal_handlers.py:123 -msgid "Application permission and SystemUser" -msgstr "应用授权与系统用户" - -#: audits/signal_handlers.py:124 -#, python-brace-format -msgid "{ApplicationPermission} ADD {SystemUser}" -msgstr "{ApplicationPermission} 添加 {SystemUser}" - -#: audits/signal_handlers.py:125 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {SystemUser}" -msgstr "{ApplicationPermission} 移除 {SystemUser}" - #: authentication/api/confirm.py:40 msgid "This action require verify your MFA" msgstr "此操作需要验证您的 MFA" @@ -1817,6 +1668,12 @@ msgstr "此操作需要验证您的 MFA" msgid "Current user not support mfa type: {}" msgstr "当前用户不支持 MFA 类型: {}" +#: authentication/apps.py:7 settings/serializers/auth/base.py:10 +#: settings/serializers/auth/cas.py:10 settings/serializers/auth/dingtalk.py:10 +#: settings/serializers/auth/feishu.py:10 settings/serializers/auth/ldap.py:39 +#: settings/serializers/auth/oauth2.py:19 settings/serializers/auth/oidc.py:12 +#: settings/serializers/auth/radius.py:13 settings/serializers/auth/saml2.py:11 +#: settings/serializers/auth/sso.py:10 settings/serializers/auth/wecom.py:10 #: authentication/api/password.py:29 terminal/api/session.py:224 #: users/views/profile/reset.py:74 msgid "User does not exist: {}" @@ -2119,7 +1976,7 @@ msgstr "Radius MFA 全局开启,无法被禁用" msgid "SMS verify code invalid" msgstr "短信验证码校验失败" -#: authentication/mfa/sms.py:12 +#: authentication/mfa/sms.py:12 settings/serializers/auth/sms.py:27 msgid "SMS" msgstr "短信" @@ -2165,13 +2022,13 @@ msgstr "SSO token" #: authentication/models.py:72 authentication/models.py:261 #: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 +#: settings/serializers/auth/radius.py:19 msgid "Secret" msgstr "密钥" #: authentication/models.py:74 authentication/models.py:264 #: perms/models/base.py:90 tickets/models/ticket/apply_application.py:30 -#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:711 +#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:713 msgid "Date expired" msgstr "失效日期" @@ -2288,7 +2145,7 @@ msgid "ID" msgstr "ID" #: authentication/templates/authentication/_access_key_modal.html:33 -#: terminal/notifications.py:93 terminal/notifications.py:141 +#: terminal/notifications.py:96 terminal/notifications.py:144 msgid "Date" msgstr "日期" @@ -2678,7 +2535,7 @@ msgstr "编码数据为 text" msgid "Encrypt field using Secret Key" msgstr "加密的字段" -#: common/db/models.py:113 +#: common/db/models.py:115 msgid "Updated by" msgstr "更新人" @@ -2748,6 +2605,14 @@ msgstr "仅导出选择项" msgid "Export filtered: %s" msgstr "导出搜素: %s" +#: common/plugins/es.py:28 +msgid "Invalid elasticsearch config" +msgstr "无效的 Elasticsearch 配置" + +#: common/plugins/es.py:33 +msgid "Not Support Elasticsearch8" +msgstr "不支持 Elasticsearch8" + #: common/sdk/im/exceptions.py:23 msgid "Network error, please contact system administrator" msgstr "网络错误,请联系系统管理员" @@ -2863,10 +2728,32 @@ msgstr "" msgid "Notifications" msgstr "通知" +#: notifications/backends/__init__.py:10 settings/serializers/email.py:19 +#: settings/serializers/email.py:50 users/forms/profile.py:102 +#: users/models/user.py:671 +msgid "Email" +msgstr "邮件" + #: notifications/backends/__init__.py:13 msgid "Site message" msgstr "站内信" +#: notifications/models/notification.py:11 +msgid "receive backend" +msgstr "消息后端" + +#: notifications/models/notification.py:14 +msgid "User message" +msgstr "用户消息" + +#: notifications/models/notification.py:17 +msgid "{} subscription" +msgstr "{} 订阅" + +#: notifications/models/notification.py:29 +msgid "System message" +msgstr "系统信息" + #: ops/api/celery.py:61 ops/api/celery.py:76 msgid "Waiting task start" msgstr "等待任务开始" @@ -2880,12 +2767,12 @@ msgid "App ops" msgstr "作业中心" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:72 +#: settings/serializers/auth/ldap.py:73 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:69 +#: settings/serializers/auth/ldap.py:70 msgid "Regularly perform" msgstr "定期执行" @@ -3074,7 +2961,7 @@ msgstr "组织管理" #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:85 #: orgs/models.py:217 rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 +#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:63 #: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 msgid "Organization" msgstr "组织" @@ -3108,6 +2995,10 @@ msgstr "管理员正在修改授权,请稍等" msgid "The authorization cannot be revoked for the time being" msgstr "该授权暂时不能撤销" +#: perms/models/application_permission.py:38 +msgid "Application permission" +msgstr "应用授权" + #: perms/models/application_permission.py:111 msgid "Permed application" msgstr "授权的应用" @@ -3124,6 +3015,10 @@ msgstr "可以查看用户授权的应用" msgid "Can view usergroup apps" msgstr "可以查看用户组授权的应用" +#: perms/models/asset_permission.py:29 +msgid "Asset permission" +msgstr "资产授权" + #: perms/models/asset_permission.py:134 msgid "Ungrouped" msgstr "未分组" @@ -3363,19 +3258,15 @@ msgstr "Web终端" msgid "Can view file manager" msgstr "文件管理" -#: rbac/models/permission.py:26 -msgid "Permission" -msgstr "权限" - -#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 -#: settings/serializers/auth/oauth2.py:35 -msgid "Scope" -msgstr "范围" - -#: rbac/models/role.py:34 +#: rbac/models/permission.py:26 rbac/models/role.py:34 msgid "Permissions" msgstr "授权" +#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 +#: settings/serializers/auth/oauth2.py:37 +msgid "Scope" +msgstr "范围" + #: rbac/models/role.py:36 msgid "Built-in" msgstr "内置" @@ -3551,6 +3442,10 @@ msgstr "测试手机号 该字段是必填项。" msgid "Settings" msgstr "系统设置" +#: settings/models.py:36 +msgid "Encrypted" +msgstr "加密的" + #: settings/models.py:158 msgid "Can change email setting" msgstr "邮件设置" @@ -3591,122 +3486,134 @@ msgstr "终端设置" msgid "Can change other setting" msgstr "其它设置" -#: settings/serializers/auth/base.py:10 +#: settings/serializers/auth/base.py:10 settings/serializers/basic.py:27 +msgid "Basic" +msgstr "基本" + +#: settings/serializers/auth/base.py:12 msgid "CAS Auth" msgstr "CAS 认证" -#: settings/serializers/auth/base.py:11 +#: settings/serializers/auth/base.py:13 msgid "OPENID Auth" msgstr "OIDC 认证" -#: settings/serializers/auth/base.py:12 +#: settings/serializers/auth/base.py:14 msgid "RADIUS Auth" msgstr "RADIUS 认证" -#: settings/serializers/auth/base.py:13 +#: settings/serializers/auth/base.py:15 msgid "DingTalk Auth" msgstr "钉钉 认证" -#: settings/serializers/auth/base.py:14 +#: settings/serializers/auth/base.py:16 msgid "FeiShu Auth" msgstr "飞书 认证" -#: settings/serializers/auth/base.py:15 +#: settings/serializers/auth/base.py:17 msgid "WeCom Auth" msgstr "企业微信 认证" -#: settings/serializers/auth/base.py:16 +#: settings/serializers/auth/base.py:18 msgid "SSO Auth" msgstr "SSO Token 认证" -#: settings/serializers/auth/base.py:17 +#: settings/serializers/auth/base.py:19 msgid "SAML2 Auth" msgstr "SAML2 认证" -#: settings/serializers/auth/base.py:20 settings/serializers/basic.py:36 +#: settings/serializers/auth/base.py:22 settings/serializers/basic.py:38 msgid "Forgot password url" msgstr "忘记密码 URL" -#: settings/serializers/auth/base.py:26 +#: settings/serializers/auth/base.py:28 msgid "Enable login redirect msg" msgstr "启用登录跳转提示" #: settings/serializers/auth/cas.py:10 +msgid "CAS" +msgstr "CAS" + +#: settings/serializers/auth/cas.py:12 msgid "Enable CAS Auth" msgstr "启用 CAS 认证" -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:48 +#: settings/serializers/auth/cas.py:13 settings/serializers/auth/oidc.py:49 msgid "Server url" msgstr "服务端地址" -#: settings/serializers/auth/cas.py:14 +#: settings/serializers/auth/cas.py:16 msgid "Proxy server url" msgstr "回调地址" -#: settings/serializers/auth/cas.py:16 settings/serializers/auth/oauth2.py:53 -#: settings/serializers/auth/saml2.py:32 +#: settings/serializers/auth/cas.py:18 settings/serializers/auth/oauth2.py:55 +#: settings/serializers/auth/saml2.py:34 msgid "Logout completely" msgstr "同步注销" -#: settings/serializers/auth/cas.py:21 +#: settings/serializers/auth/cas.py:23 msgid "Username attr" msgstr "用户名属性" -#: settings/serializers/auth/cas.py:24 +#: settings/serializers/auth/cas.py:26 msgid "Enable attributes map" msgstr "启用属性映射" -#: settings/serializers/auth/cas.py:26 settings/serializers/auth/saml2.py:31 +#: settings/serializers/auth/cas.py:28 settings/serializers/auth/saml2.py:33 msgid "Rename attr" msgstr "映射属性" -#: settings/serializers/auth/cas.py:27 +#: settings/serializers/auth/cas.py:29 msgid "Create user if not" msgstr "创建用户(如果不存在)" -#: settings/serializers/auth/dingtalk.py:13 +#: settings/serializers/auth/dingtalk.py:15 msgid "Enable DingTalk Auth" msgstr "启用钉钉认证" -#: settings/serializers/auth/feishu.py:12 +#: settings/serializers/auth/feishu.py:14 msgid "Enable FeiShu Auth" msgstr "启用飞书认证" -#: settings/serializers/auth/ldap.py:41 +#: settings/serializers/auth/ldap.py:39 +msgid "LDAP" +msgstr "LDAP" + +#: settings/serializers/auth/ldap.py:42 msgid "LDAP server" msgstr "LDAP 地址" -#: settings/serializers/auth/ldap.py:42 +#: settings/serializers/auth/ldap.py:43 msgid "eg: ldap://localhost:389" msgstr "如: ldap://localhost:389" -#: settings/serializers/auth/ldap.py:44 +#: settings/serializers/auth/ldap.py:45 msgid "Bind DN" msgstr "绑定 DN" -#: settings/serializers/auth/ldap.py:49 +#: settings/serializers/auth/ldap.py:50 msgid "User OU" msgstr "用户 OU" -#: settings/serializers/auth/ldap.py:50 +#: settings/serializers/auth/ldap.py:51 msgid "Use | split multi OUs" msgstr "多个 OU 使用 | 分割" -#: settings/serializers/auth/ldap.py:53 +#: settings/serializers/auth/ldap.py:54 msgid "User search filter" msgstr "用户过滤器" -#: settings/serializers/auth/ldap.py:54 +#: settings/serializers/auth/ldap.py:55 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oauth2.py:55 -#: settings/serializers/auth/oidc.py:36 +#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/oauth2.py:57 +#: settings/serializers/auth/oidc.py:37 msgid "User attr map" msgstr "用户属性映射" -#: settings/serializers/auth/ldap.py:58 +#: settings/serializers/auth/ldap.py:59 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -3714,77 +3621,85 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的用户需要属性" -#: settings/serializers/auth/ldap.py:76 +#: settings/serializers/auth/ldap.py:77 msgid "Connect timeout" msgstr "连接超时时间" -#: settings/serializers/auth/ldap.py:78 +#: settings/serializers/auth/ldap.py:79 msgid "Search paged size" msgstr "搜索分页数量" -#: settings/serializers/auth/ldap.py:80 +#: settings/serializers/auth/ldap.py:81 msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" -#: settings/serializers/auth/oauth2.py:20 +#: settings/serializers/auth/oauth2.py:19 +msgid "OAuth2" +msgstr "OAuth2" + +#: settings/serializers/auth/oauth2.py:22 msgid "Enable OAuth2 Auth" msgstr "启用 OAuth2 认证" -#: settings/serializers/auth/oauth2.py:23 +#: settings/serializers/auth/oauth2.py:25 msgid "Logo" msgstr "图标" -#: settings/serializers/auth/oauth2.py:26 +#: settings/serializers/auth/oauth2.py:28 msgid "Service provider" msgstr "服务提供商" -#: settings/serializers/auth/oauth2.py:29 settings/serializers/auth/oidc.py:18 +#: settings/serializers/auth/oauth2.py:31 settings/serializers/auth/oidc.py:19 msgid "Client Id" msgstr "客户端 ID" -#: settings/serializers/auth/oauth2.py:32 settings/serializers/auth/oidc.py:21 +#: settings/serializers/auth/oauth2.py:34 settings/serializers/auth/oidc.py:22 #: xpack/plugins/cloud/serializers/account_attrs.py:38 msgid "Client Secret" msgstr "客户端密钥" -#: settings/serializers/auth/oauth2.py:38 settings/serializers/auth/oidc.py:62 +#: settings/serializers/auth/oauth2.py:40 settings/serializers/auth/oidc.py:63 msgid "Provider auth endpoint" msgstr "授权端点地址" -#: settings/serializers/auth/oauth2.py:41 settings/serializers/auth/oidc.py:65 +#: settings/serializers/auth/oauth2.py:43 settings/serializers/auth/oidc.py:66 msgid "Provider token endpoint" msgstr "token 端点地址" -#: settings/serializers/auth/oauth2.py:44 settings/serializers/auth/oidc.py:29 +#: settings/serializers/auth/oauth2.py:46 settings/serializers/auth/oidc.py:30 msgid "Client authentication method" msgstr "客户端认证方式" -#: settings/serializers/auth/oauth2.py:48 settings/serializers/auth/oidc.py:71 +#: settings/serializers/auth/oauth2.py:50 settings/serializers/auth/oidc.py:72 msgid "Provider userinfo endpoint" msgstr "用户信息端点地址" -#: settings/serializers/auth/oauth2.py:51 settings/serializers/auth/oidc.py:74 +#: settings/serializers/auth/oauth2.py:53 settings/serializers/auth/oidc.py:75 msgid "Provider end session endpoint" msgstr "注销会话端点地址" -#: settings/serializers/auth/oauth2.py:58 settings/serializers/auth/oidc.py:92 -#: settings/serializers/auth/saml2.py:33 +#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:93 +#: settings/serializers/auth/saml2.py:35 msgid "Always update user" msgstr "总是更新用户信息" -#: settings/serializers/auth/oidc.py:15 +#: settings/serializers/auth/oidc.py:12 +msgid "OIDC" +msgstr "OIDC" + +#: settings/serializers/auth/oidc.py:16 msgid "Base site url" msgstr "JumpServer 地址" -#: settings/serializers/auth/oidc.py:31 +#: settings/serializers/auth/oidc.py:32 msgid "Share session" msgstr "共享会话" -#: settings/serializers/auth/oidc.py:33 +#: settings/serializers/auth/oidc.py:34 msgid "Ignore ssl verification" msgstr "忽略 SSL 证书验证" -#: settings/serializers/auth/oidc.py:37 +#: settings/serializers/auth/oidc.py:38 msgid "" "User attr map present how to map OpenID user attr to jumpserver, username," "name,email is jumpserver attr" @@ -3792,83 +3707,91 @@ msgstr "" "用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的用户需要属性" -#: settings/serializers/auth/oidc.py:45 +#: settings/serializers/auth/oidc.py:46 msgid "Use Keycloak" msgstr "使用 Keycloak" -#: settings/serializers/auth/oidc.py:51 +#: settings/serializers/auth/oidc.py:52 msgid "Realm name" msgstr "域" -#: settings/serializers/auth/oidc.py:57 +#: settings/serializers/auth/oidc.py:58 msgid "Enable OPENID Auth" msgstr "启用 OIDC 认证" -#: settings/serializers/auth/oidc.py:59 +#: settings/serializers/auth/oidc.py:60 msgid "Provider endpoint" msgstr "端点地址" -#: settings/serializers/auth/oidc.py:68 +#: settings/serializers/auth/oidc.py:69 msgid "Provider jwks endpoint" msgstr "jwks 端点地址" -#: settings/serializers/auth/oidc.py:77 +#: settings/serializers/auth/oidc.py:78 msgid "Provider sign alg" msgstr "签名算法" -#: settings/serializers/auth/oidc.py:80 +#: settings/serializers/auth/oidc.py:81 msgid "Provider sign key" msgstr "签名 Key" -#: settings/serializers/auth/oidc.py:82 +#: settings/serializers/auth/oidc.py:83 msgid "Scopes" msgstr "连接范围" -#: settings/serializers/auth/oidc.py:84 +#: settings/serializers/auth/oidc.py:85 msgid "Id token max age" msgstr "令牌有效时间" -#: settings/serializers/auth/oidc.py:87 +#: settings/serializers/auth/oidc.py:88 msgid "Id token include claims" msgstr "声明" -#: settings/serializers/auth/oidc.py:89 +#: settings/serializers/auth/oidc.py:90 msgid "Use state" msgstr "使用状态" -#: settings/serializers/auth/oidc.py:90 +#: settings/serializers/auth/oidc.py:91 msgid "Use nonce" msgstr "临时使用" #: settings/serializers/auth/radius.py:13 +msgid "Radius" +msgstr "Radius" + +#: settings/serializers/auth/radius.py:15 msgid "Enable Radius Auth" msgstr "启用 Radius 认证" -#: settings/serializers/auth/radius.py:19 +#: settings/serializers/auth/radius.py:21 msgid "OTP in Radius" msgstr "使用 Radius OTP" -#: settings/serializers/auth/saml2.py:12 +#: settings/serializers/auth/saml2.py:11 +msgid "SAML2" +msgstr "SAML2" + +#: settings/serializers/auth/saml2.py:14 msgid "Enable SAML2 Auth" msgstr "启用 SAML2 认证" -#: settings/serializers/auth/saml2.py:15 +#: settings/serializers/auth/saml2.py:17 msgid "IDP metadata URL" msgstr "IDP metadata 地址" -#: settings/serializers/auth/saml2.py:18 +#: settings/serializers/auth/saml2.py:20 msgid "IDP metadata XML" msgstr "IDP metadata XML" -#: settings/serializers/auth/saml2.py:21 +#: settings/serializers/auth/saml2.py:23 msgid "SP advanced settings" msgstr "高级设置" -#: settings/serializers/auth/saml2.py:25 +#: settings/serializers/auth/saml2.py:27 msgid "SP private key" msgstr "SP 密钥" -#: settings/serializers/auth/saml2.py:29 +#: settings/serializers/auth/saml2.py:31 msgid "SP cert" msgstr "SP 证书" @@ -3880,50 +3803,50 @@ msgstr "启用 SMS" msgid "SMS provider / Protocol" msgstr "短信服务商 / 协议" -#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:43 -#: settings/serializers/auth/sms.py:51 settings/serializers/auth/sms.py:60 -#: settings/serializers/auth/sms.py:71 settings/serializers/email.py:65 +#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:45 +#: settings/serializers/auth/sms.py:53 settings/serializers/auth/sms.py:62 +#: settings/serializers/auth/sms.py:73 settings/serializers/email.py:68 msgid "Signature" msgstr "签名" -#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:44 -#: settings/serializers/auth/sms.py:52 settings/serializers/auth/sms.py:61 +#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:46 +#: settings/serializers/auth/sms.py:54 settings/serializers/auth/sms.py:63 msgid "Template code" msgstr "模板" -#: settings/serializers/auth/sms.py:29 +#: settings/serializers/auth/sms.py:31 msgid "Test phone" msgstr "测试手机号" -#: settings/serializers/auth/sms.py:58 +#: settings/serializers/auth/sms.py:60 msgid "App Access Address" msgstr "应用地址" -#: settings/serializers/auth/sms.py:59 +#: settings/serializers/auth/sms.py:61 msgid "Signature channel number" msgstr "签名通道号" -#: settings/serializers/auth/sms.py:67 +#: settings/serializers/auth/sms.py:69 msgid "Enterprise code(SP id)" msgstr "企业代码(SP id)" -#: settings/serializers/auth/sms.py:68 +#: settings/serializers/auth/sms.py:70 msgid "Shared secret(Shared secret)" msgstr "共享密码(Shared secret)" -#: settings/serializers/auth/sms.py:69 +#: settings/serializers/auth/sms.py:71 msgid "Original number(Src id)" msgstr "原始号码(Src id)" -#: settings/serializers/auth/sms.py:70 +#: settings/serializers/auth/sms.py:72 msgid "Business type(Service id)" msgstr "业务类型(Service id)" -#: settings/serializers/auth/sms.py:73 +#: settings/serializers/auth/sms.py:75 msgid "Template" msgstr "模板" -#: settings/serializers/auth/sms.py:74 +#: settings/serializers/auth/sms.py:76 #, python-brace-format msgid "" "Template need contain {code} and Signature + template length does not exceed " @@ -3933,33 +3856,33 @@ msgstr "" "模板需要包含 {code},并且模板+签名长度不能超过67个字。例如, 您的验证码是 " "{code}, 有效期为5分钟。请不要泄露给其他人。" -#: settings/serializers/auth/sms.py:83 +#: settings/serializers/auth/sms.py:85 #, python-brace-format msgid "The template needs to contain {code}" msgstr "模板需要包含 {code}" -#: settings/serializers/auth/sms.py:86 +#: settings/serializers/auth/sms.py:88 msgid "Signature + Template must not exceed 65 words" msgstr "模板+签名不能超过65个字" -#: settings/serializers/auth/sso.py:11 +#: settings/serializers/auth/sso.py:13 msgid "Enable SSO auth" msgstr "启用 SSO Token 认证" -#: settings/serializers/auth/sso.py:12 +#: settings/serializers/auth/sso.py:14 msgid "Other service can using SSO token login to JumpServer without password" msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过程" -#: settings/serializers/auth/sso.py:15 +#: settings/serializers/auth/sso.py:17 msgid "SSO auth key TTL" msgstr "Token 有效期" -#: settings/serializers/auth/sso.py:15 +#: settings/serializers/auth/sso.py:17 #: xpack/plugins/cloud/serializers/account_attrs.py:176 msgid "Unit: second" msgstr "单位: 秒" -#: settings/serializers/auth/wecom.py:13 +#: settings/serializers/auth/wecom.py:15 msgid "Enable WeCom Auth" msgstr "启用企业微信认证" @@ -3971,23 +3894,23 @@ msgstr "主题" msgid "More url" msgstr "更多信息 URL" -#: settings/serializers/basic.py:28 +#: settings/serializers/basic.py:30 msgid "Site url" msgstr "当前站点URL" -#: settings/serializers/basic.py:29 +#: settings/serializers/basic.py:31 msgid "eg: http://dev.jumpserver.org:8080" msgstr "如: http://dev.jumpserver.org:8080" -#: settings/serializers/basic.py:32 +#: settings/serializers/basic.py:34 msgid "User guide url" msgstr "用户向导URL" -#: settings/serializers/basic.py:33 +#: settings/serializers/basic.py:35 msgid "User first login update profile done redirect to it" msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档" -#: settings/serializers/basic.py:37 +#: settings/serializers/basic.py:39 msgid "" "The forgot password url on login page, If you use ldap or cas external " "authentication, you can set it" @@ -3995,57 +3918,61 @@ msgstr "" "登录页面忘记密码URL, 如果使用了 LDAP, OPENID 等外部认证系统,可以自定义用户重" "置密码访问的地址" -#: settings/serializers/basic.py:41 +#: settings/serializers/basic.py:43 msgid "Global organization name" msgstr "全局组织名" -#: settings/serializers/basic.py:42 +#: settings/serializers/basic.py:44 msgid "The name of global organization to display" msgstr "全局组织的显示名称,默认为 全局组织" -#: settings/serializers/basic.py:44 +#: settings/serializers/basic.py:46 msgid "Enable announcement" msgstr "启用公告" -#: settings/serializers/basic.py:45 +#: settings/serializers/basic.py:47 msgid "Announcement" msgstr "公告" -#: settings/serializers/basic.py:46 +#: settings/serializers/basic.py:48 msgid "Enable tickets" msgstr "启用工单" -#: settings/serializers/cleaning.py:10 +#: settings/serializers/cleaning.py:8 +msgid "Period clean" +msgstr "定時清掃" + +#: settings/serializers/cleaning.py:12 msgid "Login log keep days" msgstr "登录日志" -#: settings/serializers/cleaning.py:10 settings/serializers/cleaning.py:14 -#: settings/serializers/cleaning.py:18 settings/serializers/cleaning.py:22 -#: settings/serializers/cleaning.py:26 settings/serializers/other.py:35 +#: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 +#: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 +#: settings/serializers/cleaning.py:28 settings/serializers/other.py:37 msgid "Unit: day" msgstr "单位: 天" -#: settings/serializers/cleaning.py:14 +#: settings/serializers/cleaning.py:16 msgid "Task log keep days" msgstr "任务日志" -#: settings/serializers/cleaning.py:18 +#: settings/serializers/cleaning.py:20 msgid "Operate log keep days" msgstr "操作日志" -#: settings/serializers/cleaning.py:22 +#: settings/serializers/cleaning.py:24 msgid "FTP log keep days" msgstr "上传下载" -#: settings/serializers/cleaning.py:26 +#: settings/serializers/cleaning.py:28 msgid "Cloud sync record keep days" msgstr "云同步记录" -#: settings/serializers/cleaning.py:29 +#: settings/serializers/cleaning.py:31 msgid "Session keep duration" msgstr "会话日志保存时间" -#: settings/serializers/cleaning.py:30 +#: settings/serializers/cleaning.py:32 msgid "" "Unit: days, Session, record, command will be delete if more than duration, " "only in database" @@ -4053,81 +3980,81 @@ msgstr "" "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" "受影响)" -#: settings/serializers/email.py:20 +#: settings/serializers/email.py:21 msgid "SMTP host" msgstr "SMTP 主机" -#: settings/serializers/email.py:21 +#: settings/serializers/email.py:22 msgid "SMTP port" msgstr "SMTP 端口" -#: settings/serializers/email.py:22 +#: settings/serializers/email.py:23 msgid "SMTP account" msgstr "SMTP 账号" -#: settings/serializers/email.py:24 +#: settings/serializers/email.py:25 msgid "SMTP password" msgstr "SMTP 密码" -#: settings/serializers/email.py:25 +#: settings/serializers/email.py:26 msgid "Tips: Some provider use token except password" msgstr "提示:一些邮件提供商需要输入的是授权码" -#: settings/serializers/email.py:28 +#: settings/serializers/email.py:29 msgid "Send user" msgstr "发件人" -#: settings/serializers/email.py:29 +#: settings/serializers/email.py:30 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" -#: settings/serializers/email.py:32 +#: settings/serializers/email.py:33 msgid "Test recipient" msgstr "测试收件人" -#: settings/serializers/email.py:33 +#: settings/serializers/email.py:34 msgid "Tips: Used only as a test mail recipient" msgstr "提示:仅用来作为测试邮件收件人" -#: settings/serializers/email.py:37 +#: settings/serializers/email.py:38 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用 SSL" -#: settings/serializers/email.py:40 +#: settings/serializers/email.py:41 msgid "Use TLS" msgstr "使用 TLS" -#: settings/serializers/email.py:41 +#: settings/serializers/email.py:42 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用 TLS" -#: settings/serializers/email.py:44 +#: settings/serializers/email.py:45 msgid "Subject prefix" msgstr "主题前缀" -#: settings/serializers/email.py:51 +#: settings/serializers/email.py:54 msgid "Create user email subject" msgstr "邮件主题" -#: settings/serializers/email.py:52 +#: settings/serializers/email.py:55 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" -#: settings/serializers/email.py:56 +#: settings/serializers/email.py:59 msgid "Create user honorific" msgstr "邮件问候语" -#: settings/serializers/email.py:57 +#: settings/serializers/email.py:60 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 你好)" -#: settings/serializers/email.py:61 +#: settings/serializers/email.py:64 msgid "Create user email content" msgstr "邮件的内容" -#: settings/serializers/email.py:62 +#: settings/serializers/email.py:65 #, python-brace-format msgid "" "Tips: When creating a user, send the content of the email, support " @@ -4135,70 +4062,74 @@ msgid "" msgstr "" "提示: 创建用户时,发送设置密码邮件的内容, 支持 {username} {name} {email} 标签" -#: settings/serializers/email.py:66 +#: settings/serializers/email.py:69 msgid "Tips: Email signature (eg:jumpserver)" msgstr "邮件署名 (如:jumpserver)" -#: settings/serializers/other.py:7 +#: settings/serializers/other.py:6 +msgid "More..." +msgstr "更多..." + +#: settings/serializers/other.py:9 msgid "Email suffix" msgstr "邮件后缀" -#: settings/serializers/other.py:8 +#: settings/serializers/other.py:10 msgid "" "This is used by default if no email is returned during SSO authentication" msgstr "SSO认证时,如果没有返回邮件地址,将使用该后缀" -#: settings/serializers/other.py:12 +#: settings/serializers/other.py:14 msgid "OTP issuer name" msgstr "OTP 扫描后的名称" -#: settings/serializers/other.py:16 +#: settings/serializers/other.py:18 msgid "OTP valid window" msgstr "OTP 延迟有效次数" -#: settings/serializers/other.py:21 +#: settings/serializers/other.py:23 msgid "CMD" msgstr "CMD" -#: settings/serializers/other.py:22 +#: settings/serializers/other.py:24 msgid "PowerShell" msgstr "PowerShell" -#: settings/serializers/other.py:24 +#: settings/serializers/other.py:26 msgid "Shell (Windows)" msgstr "Windows shell" -#: settings/serializers/other.py:25 +#: settings/serializers/other.py:27 msgid "The shell type used when Windows assets perform ansible tasks" msgstr "windows 资产执行 Ansible 任务时,使用的 Shell 类型。" -#: settings/serializers/other.py:29 +#: settings/serializers/other.py:31 msgid "Perm ungroup node" msgstr "显示未分组节点" -#: settings/serializers/other.py:30 +#: settings/serializers/other.py:32 msgid "Perm single to ungroup node" msgstr "" "放置单独授权的资产到未分组节点, 避免能看到资产所在节点,但该节点未被授权的问" "题" -#: settings/serializers/other.py:35 +#: settings/serializers/other.py:37 msgid "Ticket authorize default time" msgstr "默认工单授权时间" -#: settings/serializers/other.py:39 +#: settings/serializers/other.py:41 msgid "Help Docs URL" msgstr "文档链接" -#: settings/serializers/other.py:40 +#: settings/serializers/other.py:42 msgid "default: http://docs.jumpserver.org" msgstr "默认: http://dev.jumpserver.org:8080" -#: settings/serializers/other.py:44 +#: settings/serializers/other.py:46 msgid "Help Support URL" msgstr "支持链接" -#: settings/serializers/other.py:45 +#: settings/serializers/other.py:47 msgid "default: http://www.jumpserver.org/support/" msgstr "默认: http://www.jumpserver.org/support/" @@ -4362,73 +4293,77 @@ msgstr "启用登录验证码" msgid "Enable captcha to prevent robot authentication" msgstr "开启验证码,防止机器人登录" -#: settings/serializers/security.py:147 +#: settings/serializers/security.py:146 +msgid "Security" +msgstr "安全" + +#: settings/serializers/security.py:149 msgid "Enable terminal register" msgstr "终端注册" -#: settings/serializers/security.py:149 +#: settings/serializers/security.py:151 msgid "" "Allow terminal register, after all terminal setup, you should disable this " "for security" msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" -#: settings/serializers/security.py:153 +#: settings/serializers/security.py:155 msgid "Enable watermark" msgstr "开启水印" -#: settings/serializers/security.py:154 +#: settings/serializers/security.py:156 msgid "Enabled, the web session and replay contains watermark information" msgstr "启用后,Web 会话和录像将包含水印信息" -#: settings/serializers/security.py:158 +#: settings/serializers/security.py:160 msgid "Connection max idle time" msgstr "连接最大空闲时间" -#: settings/serializers/security.py:159 +#: settings/serializers/security.py:161 msgid "If idle time more than it, disconnect connection Unit: minute" msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" -#: settings/serializers/security.py:162 +#: settings/serializers/security.py:164 msgid "Remember manual auth" msgstr "保存手动输入密码" -#: settings/serializers/security.py:165 +#: settings/serializers/security.py:167 msgid "Enable change auth secure mode" msgstr "启用改密安全模式" -#: settings/serializers/security.py:168 +#: settings/serializers/security.py:170 msgid "Insecure command alert" msgstr "危险命令告警" -#: settings/serializers/security.py:171 +#: settings/serializers/security.py:173 msgid "Email recipient" msgstr "邮件收件人" -#: settings/serializers/security.py:172 +#: settings/serializers/security.py:174 msgid "Multiple user using , split" msgstr "多个用户,使用 , 分割" -#: settings/serializers/security.py:175 +#: settings/serializers/security.py:177 msgid "Batch command execution" msgstr "批量命令执行" -#: settings/serializers/security.py:176 +#: settings/serializers/security.py:178 msgid "Allow user run batch command or not using ansible" msgstr "是否允许用户使用 ansible 执行批量命令" -#: settings/serializers/security.py:179 +#: settings/serializers/security.py:181 msgid "Session share" msgstr "会话分享" -#: settings/serializers/security.py:180 +#: settings/serializers/security.py:182 msgid "Enabled, Allows user active session to be shared with other users" msgstr "开启后允许用户分享已连接的资产会话给他人,协同工作" -#: settings/serializers/security.py:183 +#: settings/serializers/security.py:185 msgid "Remote Login Protection" msgstr "异地登录保护" -#: settings/serializers/security.py:185 +#: settings/serializers/security.py:187 msgid "" "The system determines whether the login IP address belongs to a common login " "city. If the account is logged in from a common login city, the system sends " @@ -4437,19 +4372,28 @@ msgstr "" "根据登录 IP 是否所属常用登录城市进行判断,若账号在非常用城市登录,会发送异地" "登录提醒" -#: settings/serializers/terminal.py:13 +#: settings/serializers/settings.py:61 +#: users/templates/users/reset_password.html:29 +msgid "Setting" +msgstr "设置" + +#: settings/serializers/terminal.py:6 terminal/models/terminal.py:185 +msgid "Terminal" +msgstr "终端" + +#: settings/serializers/terminal.py:15 msgid "Auto" msgstr "自动" -#: settings/serializers/terminal.py:19 +#: settings/serializers/terminal.py:21 msgid "Password auth" msgstr "密码认证" -#: settings/serializers/terminal.py:21 +#: settings/serializers/terminal.py:23 msgid "Public key auth" msgstr "密钥认证" -#: settings/serializers/terminal.py:22 +#: settings/serializers/terminal.py:24 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" @@ -4457,34 +4401,34 @@ msgstr "" "提示:如果你使用其它认证方式,如 AD/LDAP,你应该禁用此项,以避免第三方系统删" "除后,还可以登录" -#: settings/serializers/terminal.py:26 +#: settings/serializers/terminal.py:28 msgid "List sort by" msgstr "资产列表排序" -#: settings/serializers/terminal.py:29 +#: settings/serializers/terminal.py:31 msgid "List page size" msgstr "资产列表每页数量" -#: settings/serializers/terminal.py:32 +#: settings/serializers/terminal.py:34 msgid "Telnet login regex" msgstr "Telnet 成功正则表达式" -#: settings/serializers/terminal.py:33 +#: settings/serializers/terminal.py:35 msgid "" "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 +#: settings/serializers/terminal.py:38 msgid "Enable database proxy" msgstr "启用数据库组件" -#: settings/serializers/terminal.py:37 +#: settings/serializers/terminal.py:39 msgid "Enable Razor" msgstr "启用 Razor 服务" -#: settings/serializers/terminal.py:38 +#: settings/serializers/terminal.py:40 msgid "Enable SSH Client" msgstr "启用 SSH Client" @@ -4837,14 +4781,6 @@ msgstr "有在线会话" msgid "Terminals" msgstr "终端管理" -#: terminal/backends/command/es.py:28 -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 "普通" @@ -4886,7 +4822,7 @@ msgstr "风险等级名称" msgid "Timestamp" msgstr "时间戳" -#: terminal/backends/command/serializers.py:41 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:41 terminal/models/terminal.py:106 msgid "Remote Address" msgstr "远端地址" @@ -4922,11 +4858,11 @@ msgstr "命令记录" msgid "HTTPS Port" msgstr "HTTPS 端口" -#: terminal/models/endpoint.py:18 terminal/models/terminal.py:107 +#: terminal/models/endpoint.py:18 terminal/models/terminal.py:108 msgid "HTTP Port" msgstr "HTTP 端口" -#: terminal/models/endpoint.py:19 terminal/models/terminal.py:106 +#: terminal/models/endpoint.py:19 terminal/models/terminal.py:107 msgid "SSH Port" msgstr "SSH 端口" @@ -5074,15 +5010,15 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models/storage.py:28 +#: terminal/models/storage.py:29 msgid "Default storage" msgstr "默认存储" -#: terminal/models/storage.py:137 terminal/models/terminal.py:108 +#: terminal/models/storage.py:139 terminal/models/terminal.py:109 msgid "Command storage" msgstr "命令存储" -#: terminal/models/storage.py:197 terminal/models/terminal.py:109 +#: terminal/models/storage.py:199 terminal/models/terminal.py:110 msgid "Replay storage" msgstr "录像存储" @@ -5094,15 +5030,19 @@ msgstr "参数" msgid "Kwargs" msgstr "其它参数" -#: terminal/models/terminal.py:103 +#: terminal/models/terminal.py:104 msgid "type" msgstr "类型" -#: terminal/models/terminal.py:183 -msgid "Terminal" -msgstr "终端" +#: terminal/models/terminal.py:111 +msgid "Application User" +msgstr "应用用户" -#: terminal/models/terminal.py:185 +#: terminal/models/terminal.py:112 +msgid "Is Accepted" +msgstr "被接受" + +#: terminal/models/terminal.py:187 msgid "Can view terminal config" msgstr "可以查看终端配置" @@ -5114,11 +5054,11 @@ msgstr "会话管理" msgid "Danger command alert" msgstr "危险命令告警" -#: terminal/notifications.py:92 terminal/notifications.py:140 +#: terminal/notifications.py:95 terminal/notifications.py:143 msgid "Level" msgstr "级别" -#: terminal/notifications.py:110 +#: terminal/notifications.py:113 msgid "Batch danger command alert" msgstr "批量危险命令告警" @@ -5278,7 +5218,7 @@ msgid "" "Magnus listens on in the configuration file." msgstr "没有端口可以使用,检查并修改配置文件中 Magnus 监听的端口数量限制。" -#: terminal/utils/db_port_mapper.py:92 +#: terminal/utils/db_port_mapper.py:83 msgid "All available port count: {}, Already use port count: {}" msgstr "所有可用端口数量:{},已使用端口数量:{}" @@ -5751,7 +5691,7 @@ msgstr "不能和原来的密钥相同" msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/profile.py:164 users/models/user.py:700 +#: users/forms/profile.py:161 users/models/user.py:702 msgid "Public key" msgstr "SSH公钥" @@ -5775,43 +5715,52 @@ msgstr "头像" msgid "Wechat" msgstr "微信" -#: users/models/user.py:703 +#: users/models/user.py:695 +msgid "OTP secret key" +msgstr "OTP 秘钥" + +#: users/models/user.py:705 msgid "Secret key" msgstr "Secret key" -#: users/models/user.py:719 +#: users/models/user.py:710 users/serializers/profile.py:149 +#: users/serializers/user.py:146 +msgid "Is first login" +msgstr "首次登录" + +#: users/models/user.py:721 msgid "Source" msgstr "来源" -#: users/models/user.py:723 +#: users/models/user.py:725 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:726 +#: users/models/user.py:728 msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:901 +#: users/models/user.py:903 msgid "Can invite user" msgstr "可以邀请用户" -#: users/models/user.py:902 +#: users/models/user.py:904 msgid "Can remove user" msgstr "可以移除用户" -#: users/models/user.py:903 +#: users/models/user.py:905 msgid "Can match user" msgstr "可以匹配用户" -#: users/models/user.py:912 +#: users/models/user.py:914 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:915 +#: users/models/user.py:917 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models/user.py:940 +#: users/models/user.py:942 msgid "User password history" msgstr "用户密码历史" @@ -5862,10 +5811,6 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:149 users/serializers/user.py:146 -msgid "Is first login" -msgstr "首次登录" - #: users/serializers/user.py:28 msgid "System roles" msgstr "系统角色" @@ -6052,10 +5997,6 @@ msgstr "您的密码必须满足:" msgid "Password strength" msgstr "密码强度:" -#: users/templates/users/reset_password.html:29 -msgid "Setting" -msgstr "设置" - #: users/templates/users/reset_password.html:48 msgid "Very weak" msgstr "很弱" @@ -6918,7 +6859,7 @@ msgstr "退出页面logo" msgid "Theme" msgstr "主题" -#: xpack/plugins/interface/models.py:43 +#: xpack/plugins/interface/models.py:43 xpack/plugins/interface/models.py:84 msgid "Interface setting" msgstr "界面设置" diff --git a/apps/notifications/migrations/0001_initial.py b/apps/notifications/migrations/0001_initial.py index 8b52fb84f..e63e4f36a 100644 --- a/apps/notifications/migrations/0001_initial.py +++ b/apps/notifications/migrations/0001_initial.py @@ -43,11 +43,11 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('message_type', models.CharField(max_length=128)), - ('receive_backends', models.JSONField(default=list)), + ('receive_backends', models.JSONField(default=list, verbose_name='receive backend')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_msg_subscription', to=settings.AUTH_USER_MODEL)), ], options={ - 'abstract': False, + 'abstract': False, 'verbose_name': 'User message' }, ), migrations.CreateModel( @@ -64,7 +64,7 @@ class Migration(migrations.Migration): ('users', models.ManyToManyField(related_name='system_msg_subscriptions', to=settings.AUTH_USER_MODEL)), ], options={ - 'abstract': False, + 'abstract': False, 'verbose_name': 'System message' }, ), migrations.CreateModel( diff --git a/apps/notifications/migrations/0002_auto_20210909_1946.py b/apps/notifications/migrations/0002_auto_20210909_1946.py index 75f71fad7..88610090d 100644 --- a/apps/notifications/migrations/0002_auto_20210909_1946.py +++ b/apps/notifications/migrations/0002_auto_20210909_1946.py @@ -1,8 +1,8 @@ # Generated by Django 3.1.12 on 2021-09-09 11:46 +import common.db.models from django.conf import settings from django.db import migrations, models -import django.db.models.deletion def init_user_msg_subscription(apps, schema_editor): @@ -49,7 +49,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='usermsgsubscription', name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_msg_subscription', to=settings.AUTH_USER_MODEL), + field=models.OneToOneField(on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='user_msg_subscription', to=settings.AUTH_USER_MODEL), ), migrations.RunPython(init_user_msg_subscription) ] diff --git a/apps/notifications/models/notification.py b/apps/notifications/models/notification.py index d50168576..0d6a8b8a4 100644 --- a/apps/notifications/models/notification.py +++ b/apps/notifications/models/notification.py @@ -1,16 +1,23 @@ from django.db import models +from django.utils.translation import ugettext_lazy as _ -from common.db.models import JMSModel +from common.db.models import JMSModel, CASCADE_SIGNAL_SKIP __all__ = ('SystemMsgSubscription', 'UserMsgSubscription') class UserMsgSubscription(JMSModel): - user = models.OneToOneField('users.User', related_name='user_msg_subscription', on_delete=models.CASCADE) - receive_backends = models.JSONField(default=list) + user = models.OneToOneField( + 'users.User', related_name='user_msg_subscription', on_delete=CASCADE_SIGNAL_SKIP, + verbose_name=_('User') + ) + receive_backends = models.JSONField(default=list, verbose_name=_('receive backend')) + + class Meta: + verbose_name = _('User message') def __str__(self): - return f'{self.user} subscription: {self.receive_backends}' + return _('{} subscription').format(self.user) class SystemMsgSubscription(JMSModel): @@ -21,11 +28,19 @@ class SystemMsgSubscription(JMSModel): message_type_label = '' - def __str__(self): - return f'{self.message_type}' + class Meta: + verbose_name = _('System message') - def __repr__(self): - return self.__str__() + def set_message_type_label(self): + # 采用手动调用,没设置成 property 的方式 + # 因为目前只有界面修改时会用到这个属性,避免实例化时占用资源计算 + from ..notifications import system_msgs + msg_label = '' + for msg in system_msgs: + if msg.get('message_type') == self.message_type: + msg_label = msg.get('message_type_label', '') + break + self.message_type_label = msg_label @property def receivers(self): @@ -47,3 +62,9 @@ class SystemMsgSubscription(JMSModel): receviers.append(recevier) return receviers + + def __str__(self): + return f'{self.message_type_label}' or f'{self.message_type}' + + def __repr__(self): + return self.__str__() diff --git a/apps/notifications/serializers/notifications.py b/apps/notifications/serializers/notifications.py index 191d90a77..fcf8c811c 100644 --- a/apps/notifications/serializers/notifications.py +++ b/apps/notifications/serializers/notifications.py @@ -22,6 +22,10 @@ class SystemMsgSubscriptionSerializer(BulkModelSerializer): 'receive_backends': {'required': True} } + def update(self, instance, validated_data): + instance.set_message_type_label() + return super().update(instance, validated_data) + class SystemMsgSubscriptionByCategorySerializer(serializers.Serializer): category = serializers.CharField() diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py index b2d388717..7132a3475 100644 --- a/apps/perms/models/base.py +++ b/apps/perms/models/base.py @@ -6,11 +6,11 @@ from django.utils.translation import ugettext_lazy as _ from django.db import models from django.db.models import Q from django.utils import timezone -from orgs.mixins.models import OrgModelMixin +from orgs.mixins.models import OrgModelMixin, OrgManager from common.db.models import UnionQuerySet, BitOperationChoice from common.utils import date_expired_default, lazyproperty -from orgs.mixins.models import OrgManager + __all__ = [ 'BasePermission', 'BasePermissionQuerySet', 'Action' diff --git a/apps/rbac/migrations/0001_initial.py b/apps/rbac/migrations/0001_initial.py index d3f94f6f2..8be92f6d3 100644 --- a/apps/rbac/migrations/0001_initial.py +++ b/apps/rbac/migrations/0001_initial.py @@ -1,5 +1,6 @@ # Generated by Django 3.1.13 on 2021-11-19 08:29 +import common.db.models from django.conf import settings import django.contrib.auth.models import django.contrib.contenttypes.models @@ -84,7 +85,7 @@ class Migration(migrations.Migration): ('scope', models.CharField(choices=[('system', 'System'), ('org', 'Organization')], default='system', max_length=128, verbose_name='Scope')), ('org', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to='orgs.organization', verbose_name='Organization')), ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to='rbac.role', verbose_name='Role')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='role_bindings', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('user', models.ForeignKey(on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='role_bindings', to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ 'verbose_name': 'Role binding', diff --git a/apps/rbac/migrations/0002_auto_20210929_1409.py b/apps/rbac/migrations/0002_auto_20210929_1409.py index e280d4f20..514a2d78f 100644 --- a/apps/rbac/migrations/0002_auto_20210929_1409.py +++ b/apps/rbac/migrations/0002_auto_20210929_1409.py @@ -12,7 +12,7 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='permission', - options={'verbose_name': 'Permission'}, + options={'verbose_name': 'Permissions'}, ), migrations.AlterModelOptions( name='role', diff --git a/apps/rbac/models/permission.py b/apps/rbac/models/permission.py index 5b98b6045..0cb29f73a 100644 --- a/apps/rbac/models/permission.py +++ b/apps/rbac/models/permission.py @@ -23,7 +23,7 @@ class Permission(DjangoPermission): """ 权限类 """ class Meta: proxy = True - verbose_name = _('Permission') + verbose_name = _('Permissions') @classmethod def to_perms(cls, queryset): diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index 9b2256332..c1debdbfe 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -5,7 +5,7 @@ from django.conf import settings from django.core.exceptions import ValidationError from rest_framework.serializers import ValidationError -from common.db.models import JMSModel +from common.db.models import JMSModel, CASCADE_SIGNAL_SKIP from common.utils import lazyproperty from orgs.utils import current_org, tmp_to_root_org from .role import Role @@ -38,7 +38,7 @@ class RoleBinding(JMSModel): verbose_name=_('Scope') ) user = models.ForeignKey( - 'users.User', related_name='role_bindings', on_delete=models.CASCADE, verbose_name=_('User') + 'users.User', related_name='role_bindings', on_delete=CASCADE_SIGNAL_SKIP, verbose_name=_('User') ) role = models.ForeignKey( Role, related_name='role_bindings', on_delete=models.CASCADE, verbose_name=_('Role') @@ -56,7 +56,7 @@ class RoleBinding(JMSModel): ] def __str__(self): - display = '{user} & {role}'.format(user=self.user, role=self.role) + display = '{role} -> {user}'.format(user=self.user, role=self.role) if self.org: display += ' | {org}'.format(org=self.org) return display diff --git a/apps/settings/migrations/0001_initial.py b/apps/settings/migrations/0001_initial.py index c5d8c4c36..26ba90b00 100644 --- a/apps/settings/migrations/0001_initial.py +++ b/apps/settings/migrations/0001_initial.py @@ -21,8 +21,8 @@ class Migration(migrations.Migration): verbose_name='Name')), ('value', models.TextField(verbose_name='Value')), ('category', - models.CharField(default='default', max_length=128)), - ('encrypted', models.BooleanField(default=False)), + models.CharField(default='default', max_length=128, verbose_name='Category')), + ('encrypted', models.BooleanField(default=False, verbose_name='Encrypted')), ('enabled', models.BooleanField(default=True, verbose_name='Enabled')), ('comment', models.TextField(verbose_name='Comment')), diff --git a/apps/settings/models.py b/apps/settings/models.py index 8b91765cf..8506a0fa1 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -32,8 +32,8 @@ class SettingManager(models.Manager): class Setting(models.Model): name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) value = models.TextField(verbose_name=_("Value"), null=True, blank=True) - category = models.CharField(max_length=128, default="default") - encrypted = models.BooleanField(default=False) + category = models.CharField(max_length=128, default="default", verbose_name=_('Category')) + encrypted = models.BooleanField(default=False, verbose_name=_('Encrypted')) enabled = models.BooleanField(verbose_name=_("Enabled"), default=True) comment = models.TextField(verbose_name=_("Comment")) diff --git a/apps/settings/serializers/auth/base.py b/apps/settings/serializers/auth/base.py index d4ea37f36..24abb372e 100644 --- a/apps/settings/serializers/auth/base.py +++ b/apps/settings/serializers/auth/base.py @@ -7,6 +7,8 @@ __all__ = [ class AuthSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('Basic')) + AUTH_CAS = serializers.BooleanField(required=False, label=_('CAS Auth')) AUTH_OPENID = serializers.BooleanField(required=False, label=_('OPENID Auth')) AUTH_RADIUS = serializers.BooleanField(required=False, label=_('RADIUS Auth')) diff --git a/apps/settings/serializers/auth/cas.py b/apps/settings/serializers/auth/cas.py index bb1ab9c76..4814a92bb 100644 --- a/apps/settings/serializers/auth/cas.py +++ b/apps/settings/serializers/auth/cas.py @@ -7,6 +7,8 @@ __all__ = [ class CASSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('CAS')) + AUTH_CAS = serializers.BooleanField(required=False, label=_('Enable CAS Auth')) CAS_SERVER_URL = serializers.CharField(required=False, max_length=1024, label=_('Server url')) CAS_ROOT_PROXIED_AS = serializers.CharField( diff --git a/apps/settings/serializers/auth/dingtalk.py b/apps/settings/serializers/auth/dingtalk.py index 37875bba3..ac22cc056 100644 --- a/apps/settings/serializers/auth/dingtalk.py +++ b/apps/settings/serializers/auth/dingtalk.py @@ -7,6 +7,8 @@ __all__ = ['DingTalkSettingSerializer'] class DingTalkSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('DingTalk')) + DINGTALK_AGENTID = serializers.CharField(max_length=256, required=True, label='AgentId') DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='AppKey') DINGTALK_APPSECRET = EncryptedField(max_length=256, required=False, label='AppSecret') diff --git a/apps/settings/serializers/auth/feishu.py b/apps/settings/serializers/auth/feishu.py index 67478bae5..0f5ba93d6 100644 --- a/apps/settings/serializers/auth/feishu.py +++ b/apps/settings/serializers/auth/feishu.py @@ -7,6 +7,8 @@ __all__ = ['FeiShuSettingSerializer'] class FeiShuSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('FeiShu')) + FEISHU_APP_ID = serializers.CharField(max_length=256, required=True, label='App ID') 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 ddf963443..2e371c388 100644 --- a/apps/settings/serializers/auth/ldap.py +++ b/apps/settings/serializers/auth/ldap.py @@ -36,6 +36,7 @@ class LDAPUserSerializer(serializers.Serializer): class LDAPSettingSerializer(serializers.Serializer): # encrypt_fields 现在使用 write_only 来判断了 + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('LDAP')) AUTH_LDAP_SERVER_URI = serializers.CharField( required=True, max_length=1024, label=_('LDAP server'), diff --git a/apps/settings/serializers/auth/oauth2.py b/apps/settings/serializers/auth/oauth2.py index a2230750a..42c310333 100644 --- a/apps/settings/serializers/auth/oauth2.py +++ b/apps/settings/serializers/auth/oauth2.py @@ -16,6 +16,8 @@ class SettingImageField(serializers.ImageField): class OAuth2SettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('OAuth2')) + AUTH_OAUTH2 = serializers.BooleanField( default=False, label=_('Enable OAuth2 Auth') ) diff --git a/apps/settings/serializers/auth/oidc.py b/apps/settings/serializers/auth/oidc.py index 3ecf2bb6c..ea25ca9d7 100644 --- a/apps/settings/serializers/auth/oidc.py +++ b/apps/settings/serializers/auth/oidc.py @@ -9,6 +9,7 @@ __all__ = [ class CommonSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('OIDC')) # OpenID 公有配置参数 (version <= 1.5.8 或 version >= 1.5.8) BASE_SITE_URL = serializers.CharField( required=False, allow_null=True, allow_blank=True, diff --git a/apps/settings/serializers/auth/radius.py b/apps/settings/serializers/auth/radius.py index 4d56510bf..6ef574eef 100644 --- a/apps/settings/serializers/auth/radius.py +++ b/apps/settings/serializers/auth/radius.py @@ -10,6 +10,8 @@ __all__ = ['RadiusSettingSerializer'] class RadiusSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('Radius')) + 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')) diff --git a/apps/settings/serializers/auth/saml2.py b/apps/settings/serializers/auth/saml2.py index d061c4936..e98a8bbe5 100644 --- a/apps/settings/serializers/auth/saml2.py +++ b/apps/settings/serializers/auth/saml2.py @@ -8,6 +8,8 @@ __all__ = [ class SAML2SettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('SAML2')) + AUTH_SAML2 = serializers.BooleanField( default=False, required=False, label=_('Enable SAML2 Auth') ) diff --git a/apps/settings/serializers/auth/sms.py b/apps/settings/serializers/auth/sms.py index dda38b586..1696be723 100644 --- a/apps/settings/serializers/auth/sms.py +++ b/apps/settings/serializers/auth/sms.py @@ -24,6 +24,8 @@ class SignTmplPairSerializer(serializers.Serializer): class BaseSMSSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('SMS') + SMS_TEST_PHONE = serializers.CharField( max_length=256, required=False, validators=[PhoneValidator(), ], allow_blank=True, label=_('Test phone') @@ -38,7 +40,7 @@ class BaseSMSSettingSerializer(serializers.Serializer): class AlibabaSMSSettingSerializer(BaseSMSSettingSerializer): ALIBABA_ACCESS_KEY_ID = serializers.CharField(max_length=256, required=True, label='AccessKeyId') ALIBABA_ACCESS_KEY_SECRET = EncryptedField( - max_length=256, required=False, label='AccessKeySecret', + max_length=256, required=False, label='access_key_secret', ) 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')) diff --git a/apps/settings/serializers/auth/sso.py b/apps/settings/serializers/auth/sso.py index 113391b45..0dad5e25b 100644 --- a/apps/settings/serializers/auth/sso.py +++ b/apps/settings/serializers/auth/sso.py @@ -7,6 +7,8 @@ __all__ = [ class SSOSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('SSO')) + AUTH_SSO = serializers.BooleanField( required=False, label=_('Enable SSO auth'), help_text=_("Other service can using SSO token login to JumpServer without password") diff --git a/apps/settings/serializers/auth/wecom.py b/apps/settings/serializers/auth/wecom.py index bd1498105..38516e790 100644 --- a/apps/settings/serializers/auth/wecom.py +++ b/apps/settings/serializers/auth/wecom.py @@ -7,6 +7,8 @@ __all__ = ['WeComSettingSerializer'] class WeComSettingSerializer(serializers.Serializer): + PREFIX_TITLE = '%s-%s' % (_('Authentication'), _('WeCom')) + WECOM_CORPID = serializers.CharField(max_length=256, required=True, label='corpid') WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='agentid') WECOM_SECRET = EncryptedField(max_length=256, required=False, label='secret') diff --git a/apps/settings/serializers/basic.py b/apps/settings/serializers/basic.py index 3208ad1be..d7d7f7ed9 100644 --- a/apps/settings/serializers/basic.py +++ b/apps/settings/serializers/basic.py @@ -24,6 +24,8 @@ class AnnouncementSerializer(serializers.Serializer): class BasicSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('Basic') + SITE_URL = serializers.URLField( required=True, label=_("Site url"), help_text=_('eg: http://dev.jumpserver.org:8080') diff --git a/apps/settings/serializers/cleaning.py b/apps/settings/serializers/cleaning.py index 68e8092cb..a180a9ac5 100644 --- a/apps/settings/serializers/cleaning.py +++ b/apps/settings/serializers/cleaning.py @@ -5,6 +5,8 @@ __all__ = ['CleaningSerializer'] class CleaningSerializer(serializers.Serializer): + PREFIX_TITLE = _('Period clean') + LOGIN_LOG_KEEP_DAYS = serializers.IntegerField( min_value=1, max_value=9999, label=_("Login log keep days"), help_text=_("Unit: day") diff --git a/apps/settings/serializers/email.py b/apps/settings/serializers/email.py index c67cf2216..42618b611 100644 --- a/apps/settings/serializers/email.py +++ b/apps/settings/serializers/email.py @@ -16,6 +16,7 @@ class MailTestSerializer(serializers.Serializer): class EmailSettingSerializer(serializers.Serializer): # encrypt_fields 现在使用 write_only 来判断了 + PREFIX_TITLE = _('Email') EMAIL_HOST = serializers.CharField(max_length=1024, required=True, label=_("SMTP host")) EMAIL_PORT = serializers.CharField(max_length=5, required=True, label=_("SMTP port")) @@ -46,6 +47,8 @@ class EmailSettingSerializer(serializers.Serializer): class EmailContentSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('Email') + EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField( max_length=1024, allow_blank=True, required=False, label=_('Create user email subject'), diff --git a/apps/settings/serializers/other.py b/apps/settings/serializers/other.py index 775e26dd7..1eb57a673 100644 --- a/apps/settings/serializers/other.py +++ b/apps/settings/serializers/other.py @@ -3,6 +3,8 @@ from rest_framework import serializers class OtherSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('More...') + EMAIL_SUFFIX = serializers.CharField( required=False, max_length=1024, label=_("Email suffix"), help_text=_('This is used by default if no email is returned during SSO authentication') diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py index fb96b3502..25f1d887f 100644 --- a/apps/settings/serializers/security.py +++ b/apps/settings/serializers/security.py @@ -143,6 +143,8 @@ class SecurityAuthSerializer(serializers.Serializer): class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSerializer): + PREFIX_TITLE = _('Security') + SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField( required=True, label=_('Enable terminal register'), help_text=_( diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 3152a5ef5..cb6472de4 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -1,4 +1,6 @@ # coding: utf-8 +from django.core.cache import cache +from django.utils.translation import ugettext_lazy as _ from .basic import BasicSettingSerializer from .other import OtherSettingSerializer @@ -7,7 +9,8 @@ from .auth import ( LDAPSettingSerializer, OIDCSettingSerializer, KeycloakSettingSerializer, CASSettingSerializer, RadiusSettingSerializer, FeiShuSettingSerializer, WeComSettingSerializer, DingTalkSettingSerializer, AlibabaSMSSettingSerializer, - TencentSMSSettingSerializer, CMPP2SMSSettingSerializer + TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, AuthSettingSerializer, + SAML2SettingSerializer, OAuth2SettingSerializer, SSOSettingSerializer ) from .terminal import TerminalSettingSerializer from .security import SecuritySettingSerializer @@ -22,6 +25,7 @@ __all__ = [ class SettingsSerializer( BasicSettingSerializer, LDAPSettingSerializer, + AuthSettingSerializer, TerminalSettingSerializer, SecuritySettingSerializer, WeComSettingSerializer, @@ -31,13 +35,33 @@ class SettingsSerializer( EmailContentSettingSerializer, OtherSettingSerializer, OIDCSettingSerializer, + SAML2SettingSerializer, + OAuth2SettingSerializer, KeycloakSettingSerializer, CASSettingSerializer, RadiusSettingSerializer, + SSOSettingSerializer, CleaningSerializer, AlibabaSMSSettingSerializer, TencentSMSSettingSerializer, CMPP2SMSSettingSerializer, ): + CACHE_KEY = 'SETTING_FIELDS_MAPPING' + # encrypt_fields 现在使用 write_only 来判断了 - pass + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.fields_label_mapping = None + + # 单次计算量不大,搞个缓存,以防操作日志大量写入时,这里影响性能 + def get_field_label(self, field_name): + if self.fields_label_mapping is None: + self.fields_label_mapping = {} + for subclass in SettingsSerializer.__bases__: + prefix = getattr(subclass, 'PREFIX_TITLE', _('Setting')) + fields = subclass().get_fields() + for name, item in fields.items(): + label = '[%s] %s' % (prefix, getattr(item, 'label', '')) + self.fields_label_mapping[name] = label + cache.set(self.CACHE_KEY, self.fields_label_mapping, 3600 * 24) + return self.fields_label_mapping.get(field_name) diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index 1b70fadf3..da7edde9a 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -3,6 +3,8 @@ from rest_framework import serializers class TerminalSettingSerializer(serializers.Serializer): + PREFIX_TITLE = _('Terminal') + SORT_BY_CHOICES = ( ('hostname', _('Hostname')), ('ip', _('IP')) diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 4d830b1b2..3601e3894 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -1,109 +1,18 @@ # -*- coding: utf-8 -*- # import pytz -import inspect from datetime import datetime -from functools import reduce, partial -from itertools import groupby -from uuid import UUID -from django.utils.translation import gettext_lazy as _ -from django.db.models import QuerySet as DJQuerySet -from elasticsearch import Elasticsearch -from elasticsearch.helpers import bulk -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 terminal.models import Command +from common.plugins.es import ES + logger = get_logger(__file__) -class InvalidElasticsearch(JMSException): - default_code = 'invalid_elasticsearch' - default_detail = _('Invalid elasticsearch config') - - -class NotSupportElasticsearch8(JMSException): - default_code = 'not_support_elasticsearch8' - default_detail = _('Not Support Elasticsearch8') - - -class CommandStore(object): +class CommandStore(ES): def __init__(self, config): - 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: - kwargs['verify_certs'] = None - self.es = Elasticsearch(hosts=hosts, max_retries=0, **kwargs) - - self.exact_fields = set() - self.match_fields = {'input', 'risk_level', 'user', 'asset', 'system_user'} - may_exact_fields = {'session', 'org_id'} - - if self.is_new_index_type(): - self.exact_fields.update(may_exact_fields) - self.doc_type = '_doc' - 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 - - 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 - - try: - 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 - except KeyError: - return False - - def pre_use_check(self): - if not self.ping(timeout=3): - raise InvalidElasticsearch - self._ensure_index_exists() - - def _ensure_index_exists(self): properties = { "session": { "type": "keyword" @@ -118,25 +27,11 @@ class CommandStore(object): "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}} + exact_fields = {} + match_fields = {'input', 'risk_level', 'user', 'asset', 'system_user'} + keyword_fields = {'session', 'org_id'} - 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: - if e.error == 'resource_already_exists_exception': - logger.warning(e) - else: - logger.exception(e) + super().__init__(config, properties, keyword_fields, exact_fields, match_fields) @staticmethod def make_data(command): @@ -150,274 +45,14 @@ class CommandStore(object): data["date"] = datetime.fromtimestamp(command['timestamp'], tz=pytz.UTC) return data - def bulk_save(self, command_set, raise_on_error=True): - actions = [] - for command in command_set: - data = dict( - _index=self.index, - _type=self.doc_type, - _source=self.make_data(command), - ) - actions.append(data) - return bulk(self.es, actions, index=self.index, raise_on_error=raise_on_error) - - def save(self, command): - """ - 保存命令到数据库 - """ - data = self.make_data(command) - return self.es.index(index=self.index, doc_type=self.doc_type, body=data) - - def filter(self, query: dict, from_=None, size=None, sort=None): - try: - data = self._filter(query, from_, size, sort) - except Exception as e: - logger.error('ES filter error: {}'.format(e)) - data = [] - return data - - def _filter(self, query: dict, from_=None, size=None, sort=None): - body = self.get_query_body(**query) - - data = self.es.search( - index=self.query_index, doc_type=self.doc_type, body=body, from_=from_, size=size, - sort=sort - ) - source_data = [] - for item in data['hits']['hits']: - if item: - item['_source'].update({'id': item['_id']}) - source_data.append(item['_source']) - - return Command.from_multi_dict(source_data) - - def count(self, **query): - try: - body = self.get_query_body(**query) - data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body) - count = data["count"] - except Exception as e: - logger.error('ES count error: {}'.format(e)) - count = 0 - return count - - def __getattr__(self, item): - return getattr(self.es, item) - - def all(self): - """返回所有数据""" - raise NotImplementedError("Not support") - - def ping(self, timeout=None): - try: - return self.es.ping(request_timeout=timeout) - except Exception: - return False - - def get_query_body(self, **kwargs): - new_kwargs = {} - for k, v in kwargs.items(): - new_kwargs[k] = str(v) if isinstance(v, UUID) else v - kwargs = new_kwargs - - index_in_field = 'id__in' - exact_fields = self.exact_fields - match_fields = self.match_fields - - match = {} - exact = {} - index = {} - - if index_in_field in kwargs: - index['values'] = kwargs[index_in_field] - - for k, v in kwargs.items(): - if k in exact_fields: - exact[k] = v - elif k in match_fields: - match[k] = v - - # 处理时间 - timestamp__gte = kwargs.get('timestamp__gte') - timestamp__lte = kwargs.get('timestamp__lte') + @staticmethod + def handler_time_field(data): + timestamp__gte = data.get('timestamp__gte') + timestamp__lte = data.get('timestamp__lte') timestamp_range = {} if timestamp__gte: timestamp_range['gte'] = timestamp__gte if timestamp__lte: timestamp_range['lte'] = timestamp__lte - - # 处理组织 - should = [] - org_id = match.get('org_id') - - real_default_org_id = '00000000-0000-0000-0000-000000000002' - root_org_id = '00000000-0000-0000-0000-000000000000' - - if org_id == root_org_id: - match.pop('org_id') - elif org_id in (real_default_org_id, ''): - match.pop('org_id') - should.append({ - 'bool': { - 'must_not': [ - { - 'wildcard': {'org_id': '*'} - } - ]} - }) - should.append({'match': {'org_id': real_default_org_id}}) - - # 构建 body - body = { - 'query': { - 'bool': { - 'must': [ - {'match': {k: v}} for k, v in match.items() - ], - '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() - ] - } - }, - } - return body - - -class QuerySet(DJQuerySet): - _method_calls = None - _storage = None - _command_store_config = None - _slice = None # (from_, size) - default_days_ago = 5 - max_result_window = 10000 - - def __init__(self, command_store_config): - self._method_calls = [] - 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])} - return _method_calls - - @lazyproperty - def _filter_kwargs(self): - _method_calls = self._grouped_method_calls - filter_calls = _method_calls.get('filter') - if not filter_calls: - return {} - names, multi_args, multi_kwargs = zip(*filter_calls) - kwargs = reduce(lambda x, y: {**x, **y}, multi_kwargs, {}) - - striped_kwargs = {} - for k, v in kwargs.items(): - k = k.replace('__exact', '') - k = k.replace('__startswith', '') - k = k.replace('__icontains', '') - striped_kwargs[k] = v - return striped_kwargs - - @lazyproperty - def _sort(self): - order_by = self._grouped_method_calls.get('order_by') - if order_by: - for call in reversed(order_by): - fields = call[1] - if fields: - field = fields[-1] - - if field.startswith('-'): - direction = 'desc' - else: - direction = 'asc' - field = field.lstrip('-+') - sort = f'{field}:{direction}' - return sort - - def __execute(self): - _filter_kwargs = self._filter_kwargs - _sort = self._sort - from_, size = self._slice or (None, None) - data = self._storage.filter(_filter_kwargs, from_=from_, size=size, sort=_sort) - return data - - def __stage_method_call(self, item, *args, **kwargs): - _clone = self.__clone() - _clone._method_calls.append((item, args, kwargs)) - return _clone - - def __clone(self): - uqs = QuerySet(self._command_store_config) - uqs._method_calls = self._method_calls.copy() - uqs._slice = self._slice - uqs.model = self.model - return uqs - - def count(self, limit_to_max_result_window=True): - filter_kwargs = self._filter_kwargs - count = self._storage.count(**filter_kwargs) - if limit_to_max_result_window: - count = min(count, self.max_result_window) - return count - - def __getattribute__(self, item): - if any(( - item.startswith('__'), - item in QuerySet.__dict__, - )): - return object.__getattribute__(self, item) - - origin_attr = object.__getattribute__(self, item) - if not inspect.ismethod(origin_attr): - return origin_attr - - attr = partial(self.__stage_method_call, item) - return attr - - def __getitem__(self, item): - max_window = self.max_result_window - if isinstance(item, slice): - if self._slice is None: - clone = self.__clone() - from_ = item.start or 0 - if item.stop is None: - size = self.max_result_window - from_ - else: - size = item.stop - from_ - - if from_ + size > max_window: - if from_ >= max_window: - from_ = max_window - size = 0 - else: - size = max_window - from_ - clone._slice = (from_, size) - return clone - return self.__execute()[item] - - def __repr__(self): - return self.__execute().__repr__() - - def __iter__(self): - return iter(self.__execute()) - - def __len__(self): - return self.count() + return 'timestamp', timestamp_range diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/storage.py index 555c44aea..c333c5498 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/storage.py @@ -10,6 +10,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings from common.mixins import CommonModelMixin +from common.plugins.es import QuerySet as ESQuerySet from common.utils import get_logger from common.db.fields import EncryptJsonDictTextField from common.utils.timezone import local_now_date_display @@ -117,7 +118,8 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): if self.type in TYPE_ENGINE_MAPPING: engine_mod = import_module(TYPE_ENGINE_MAPPING[self.type]) - qs = engine_mod.QuerySet(self.config) + store = engine_mod.CommandStore(self.config) + qs = ESQuerySet(store) qs.model = Command return qs diff --git a/apps/terminal/models/terminal.py b/apps/terminal/models/terminal.py index 4b585cfff..6d7b67815 100644 --- a/apps/terminal/models/terminal.py +++ b/apps/terminal/models/terminal.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from django.conf import settings from common.utils import get_logger +from common.const.signals import SKIP_SIGNAL from users.models import User from orgs.utils import tmp_to_root_org from .status import Status @@ -107,8 +108,8 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000) command_storage = models.CharField(max_length=128, verbose_name=_("Command storage"), default='default') replay_storage = models.CharField(max_length=128, verbose_name=_("Replay storage"), default='default') - user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE) - is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted') + user = models.OneToOneField(User, related_name='terminal', verbose_name=_('Application User'), null=True, on_delete=models.CASCADE) + is_accepted = models.BooleanField(default=False, verbose_name=_('Is Accepted')) is_deleted = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -159,6 +160,7 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): def delete(self, using=None, keep_parents=False): if self.user: + setattr(self.user, SKIP_SIGNAL, True) self.user.delete() self.user = None self.is_deleted = True diff --git a/apps/terminal/notifications.py b/apps/terminal/notifications.py index 91509a901..700bfde9b 100644 --- a/apps/terminal/notifications.py +++ b/apps/terminal/notifications.py @@ -70,6 +70,9 @@ class CommandAlertMessage(CommandAlertMixin, SystemMessage): def __init__(self, command): self.command = command + def __str__(self): + return str(self.message_type_label) + @classmethod def gen_test_msg(cls): command = Command.objects.first().to_dict() diff --git a/apps/users/migrations/0021_auto_20190625_1104.py b/apps/users/migrations/0021_auto_20190625_1104.py index 4112a8693..f2575b933 100644 --- a/apps/users/migrations/0021_auto_20190625_1104.py +++ b/apps/users/migrations/0021_auto_20190625_1104.py @@ -14,7 +14,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='user', name='_otp_secret_key', - field=common.db.fields.EncryptCharField(blank=True, max_length=128, null=True), + field=common.db.fields.EncryptCharField(blank=True, max_length=128, null=True, verbose_name='OTP secret key'), ), migrations.AlterField( model_name='user', diff --git a/apps/users/migrations/0032_userpasswordhistory.py b/apps/users/migrations/0032_userpasswordhistory.py index 8fcf1d2d1..5088b7968 100644 --- a/apps/users/migrations/0032_userpasswordhistory.py +++ b/apps/users/migrations/0032_userpasswordhistory.py @@ -1,8 +1,8 @@ # Generated by Django 3.1 on 2021-04-27 12:43 +import common.db.models from django.conf import settings from django.db import migrations, models -import django.db.models.deletion import uuid @@ -19,7 +19,7 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('password', models.CharField(max_length=128)), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history_passwords', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('user', models.ForeignKey(on_delete=common.db.models.CASCADE_SIGNAL_SKIP, related_name='history_passwords', to=settings.AUTH_USER_MODEL, verbose_name='User')), ], ), ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 6830c2ac5..6158e9572 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -20,7 +20,7 @@ from django.shortcuts import reverse from orgs.utils import current_org from orgs.models import Organization from rbac.const import Scope -from common.db import fields +from common.db import fields, models as jms_models from common.utils import ( date_expired_default, get_logger, lazyproperty, random_string, bulk_create_with_signal ) @@ -691,7 +691,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): mfa_level = models.SmallIntegerField( default=0, choices=MFAMixin.MFA_LEVEL_CHOICES, verbose_name=_('MFA') ) - otp_secret_key = fields.EncryptCharField(max_length=128, blank=True, null=True) + otp_secret_key = fields.EncryptCharField( + max_length=128, blank=True, null=True, verbose_name=_('OTP secret key') + ) # Todo: Auto generate key, let user download private_key = fields.EncryptTextField( blank=True, null=True, verbose_name=_('Private key') @@ -705,7 +707,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): comment = models.TextField( blank=True, null=True, verbose_name=_('Comment') ) - is_first_login = models.BooleanField(default=True) + is_first_login = models.BooleanField(default=True, verbose_name=_('Is first login')) date_expired = models.DateTimeField( default=date_expired_default, blank=True, null=True, db_index=True, verbose_name=_('Date expired') @@ -927,7 +929,7 @@ class UserPasswordHistory(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) password = models.CharField(max_length=128) user = models.ForeignKey("users.User", related_name='history_passwords', - on_delete=models.CASCADE, verbose_name=_('User')) + on_delete=jms_models.CASCADE_SIGNAL_SKIP, verbose_name=_('User')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) def __str__(self):