diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py index d7ed099ed..1c6f19cbb 100644 --- a/apps/assets/api/cmd_filter.py +++ b/apps/assets/api/cmd_filter.py @@ -29,7 +29,7 @@ class CommandFilterViewSet(OrgBulkModelViewSet): class CommandFilterRuleViewSet(OrgBulkModelViewSet): model = CommandFilterRule - filterset_fields = ("content",) + filterset_fields = ('content',) search_fields = filterset_fields permission_classes = (IsOrgAdmin,) serializer_class = serializers.CommandFilterRuleSerializer diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index b9f1007d9..581800bec 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -1,15 +1,18 @@ # ~*~ coding: utf-8 ~*~ from django.shortcuts import get_object_or_404 from rest_framework.response import Response +from django.db.models import Q -from common.utils import get_logger +from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics from common.mixins.api import SuggestionMixin from orgs.utils import tmp_to_root_org from rest_framework.decorators import action -from ..models import SystemUser, Asset +from users.models import User, UserGroup +from applications.models import Application +from ..models import SystemUser, Asset, CommandFilter, CommandFilterRule from .. import serializers from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer from ..tasks import ( @@ -192,9 +195,42 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView): return CommandFilterRuleSerializer def get_queryset(self): - pk = self.kwargs.get('pk', None) - system_user = get_object_or_404(SystemUser, pk=pk) - return system_user.cmd_filter_rules + user_groups = [] + user_id = self.request.query_params.get('user_id') + user = get_object_or_none(User, pk=user_id) + if user: + user_groups.extend(list(user.groups.all())) + user_group_id = self.request.query_params.get('user_group_id') + user_group = get_object_or_none(UserGroup, pk=user_group_id) + if user_group: + user_groups.append(user_group) + system_user_id = self.kwargs.get('pk', None) + system_user = get_object_or_none(SystemUser, pk=system_user_id) + if not system_user: + system_user_id = self.request.query_params.get('system_user_id') + system_user = get_object_or_none(SystemUser, pk=system_user_id) + asset_id = self.request.query_params.get('asset_id') + asset = get_object_or_none(Asset, pk=asset_id) + application_id = self.request.query_params.get('application_id') + application = get_object_or_none(Application, pk=application_id) + q = Q() + if user: + q |= Q(users=user) + if user_group: + q |= Q(user_groups__in=set(user_groups)) + if system_user: + q |= Q(system_users=system_user) + if asset: + q |= Q(assets=asset) + if application: + q |= Q(applications=application) + if q: + cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True) + rule_ids = cmd_filters.values_list('rules', flat=True) + rules = CommandFilterRule.objects.filter(id__in=rule_ids) + else: + rules = CommandFilterRule.objects.none() + return rules class SystemUserAssetsListView(generics.ListAPIView): diff --git a/apps/assets/migrations/0082_auto_20211209_1440.py b/apps/assets/migrations/0082_auto_20211209_1440.py new file mode 100644 index 000000000..a0576fe13 --- /dev/null +++ b/apps/assets/migrations/0082_auto_20211209_1440.py @@ -0,0 +1,74 @@ +# Generated by Django 3.1.13 on 2021-12-09 06:40 + +from django.conf import settings +from django.db import migrations, models + + +def migrate_system_users_cmd_filters(apps, schema_editor): + system_user_model = apps.get_model("assets", "SystemUser") + cmd_filter_model = apps.get_model("assets", "CommandFilter") + su_through = system_user_model.cmd_filters.through + cf_through = cmd_filter_model.system_users.through + + su_relation_objects = su_through.objects.all() + cf_relation_objects = [ + cf_through(**{ + 'id': su_relation.id, + 'systemuser_id': su_relation.systemuser_id, + 'commandfilter_id': su_relation.commandfilter_id + }) + for su_relation in su_relation_objects + ] + cf_through.objects.bulk_create(cf_relation_objects) + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0014_auto_20211105_1605'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0081_auto_20211105_1605'), + ] + + operations = [ + migrations.AddField( + model_name='commandfilter', + name='applications', + field=models.ManyToManyField(blank=True, related_name='cmd_filters', to='applications.Application', verbose_name='Application'), + ), + migrations.AddField( + model_name='commandfilter', + name='assets', + field=models.ManyToManyField(blank=True, related_name='cmd_filters', to='assets.Asset', verbose_name='Asset'), + ), + migrations.AddField( + model_name='commandfilter', + name='system_users', + field=models.ManyToManyField(blank=True, related_name='cmd_filters_pre', to='assets.SystemUser', verbose_name='System user'), + ), + migrations.AddField( + model_name='commandfilter', + name='users', + field=models.ManyToManyField(blank=True, related_name='cmd_filters', to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + migrations.AddField( + model_name='commandfilter', + name='user_groups', + field=models.ManyToManyField(blank=True, related_name='cmd_filters', to='users.UserGroup', verbose_name='User group'), + ), + migrations.AlterField( + model_name='systemuser', + name='cmd_filters', + field=models.ManyToManyField(blank=True, related_name='system_users_bak', to='assets.CommandFilter', verbose_name='Command filter'), + ), + migrations.RunPython(migrate_system_users_cmd_filters), + migrations.RemoveField( + model_name='systemuser', + name='cmd_filters', + ), + migrations.AlterField( + model_name='commandfilter', + name='system_users', + field=models.ManyToManyField(blank=True, related_name='cmd_filters', to='assets.SystemUser', verbose_name='System user'), + ), + ] diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 829d06ab6..bff1002f8 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -7,9 +7,11 @@ from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator from django.utils.translation import ugettext_lazy as _ -from common.utils import lazyproperty +from common.utils import lazyproperty, get_logger from orgs.mixins.models import OrgModelMixin +logger = get_logger(__file__) + __all__ = [ 'CommandFilter', 'CommandFilterRule' @@ -19,11 +21,32 @@ __all__ = [ class CommandFilter(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=64, verbose_name=_("Name")) + users = models.ManyToManyField( + 'users.User', related_name='cmd_filters', blank=True, + verbose_name=_("User") + ) + user_groups = models.ManyToManyField( + 'users.UserGroup', related_name='cmd_filters', blank=True, + verbose_name=_("User group"), + ) + assets = models.ManyToManyField( + 'assets.Asset', related_name='cmd_filters', blank=True, + verbose_name=_("Asset") + ) + system_users = models.ManyToManyField( + 'assets.SystemUser', related_name='cmd_filters', blank=True, + verbose_name=_("System user")) + applications = models.ManyToManyField( + 'applications.Application', related_name='cmd_filters', blank=True, + verbose_name=_("Application") + ) 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) - created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by')) + created_by = models.CharField( + max_length=128, blank=True, default='', verbose_name=_('Created by') + ) def __str__(self): return self.name @@ -71,36 +94,52 @@ class CommandFilterRule(OrgModelMixin): verbose_name = _("Command filter rule") @lazyproperty - def _pattern(self): + def pattern(self): if self.type == 'command': - regex = [] - content = self.content.replace('\r\n', '\n') - for _cmd in content.split('\n'): - cmd = re.sub(r'\s+', ' ', _cmd) - cmd = re.escape(cmd) - cmd = cmd.replace('\\ ', '\s+') - - # 有空格就不能 铆钉单词了 - if ' ' in _cmd: - regex.append(cmd) - continue - - # 如果是单个字符 - if cmd[-1].isalpha(): - regex.append(r'\b{0}\b'.format(cmd)) - else: - regex.append(r'\b{0}'.format(cmd)) - s = r'{}'.format('|'.join(regex)) + s = self.construct_command_regex(content=self.content) else: s = r'{0}'.format(self.content) + + return s + + @classmethod + def construct_command_regex(cls, content): + regex = [] + content = content.replace('\r\n', '\n') + for _cmd in content.split('\n'): + cmd = re.sub(r'\s+', ' ', _cmd) + cmd = re.escape(cmd) + cmd = cmd.replace('\\ ', '\s+') + + # 有空格就不能 铆钉单词了 + if ' ' in _cmd: + regex.append(cmd) + continue + + # 如果是单个字符 + if cmd[-1].isalpha(): + regex.append(r'\b{0}\b'.format(cmd)) + else: + regex.append(r'\b{0}'.format(cmd)) + s = r'(?i){}'.format('|'.join(regex)) + return s + + @staticmethod + def compile_regex(regex): try: - _pattern = re.compile(s) - except: - _pattern = '' - return _pattern + pattern = re.compile(regex) + except Exception as e: + error = _('The generated regular expression is incorrect: {}').format(str(e)) + logger.error(error) + return False, error, None + return True, '', pattern def match(self, data): - found = self._pattern.search(data) + succeed, error, pattern = self.compile_regex(regex=self.pattern) + if not succeed: + return self.ACTION_UNKNOWN, '' + + found = pattern.search(data) if not found: return self.ACTION_UNKNOWN, '' diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 337ccdf2f..5e0384d01 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -203,7 +203,6 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser): sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) - cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True) sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) token = models.TextField(default='', verbose_name=_('Token')) home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index 2f491aa21..3806dc1b1 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -3,6 +3,7 @@ import re from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ from ..models import CommandFilter, CommandFilterRule from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.utils import tmp_to_root_org @@ -22,16 +23,14 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer): 'comment', 'created_by', ] fields_fk = ['rules'] - fields_m2m = ['system_users'] + fields_m2m = ['users', 'user_groups', 'system_users', 'assets', 'applications'] fields = fields_small + fields_fk + fields_m2m extra_kwargs = { - 'rules': {'read_only': True}, - 'system_users': {'required': False}, + 'rules': {'read_only': True} } class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): - invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]') type_display = serializers.ReadOnlyField(source='get_type_display') action_display = serializers.ReadOnlyField(source='get_action_display') @@ -39,13 +38,13 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): model = CommandFilterRule fields_mini = ['id'] fields_small = fields_mini + [ - 'type', 'type_display', 'content', 'priority', + 'type', 'type_display', 'content', 'pattern', 'priority', 'action', 'action_display', 'reviewers', 'date_created', 'date_updated', 'comment', 'created_by', ] fields_fk = ['filter'] - fields = '__all__' + fields = fields_small + fields_fk def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -61,15 +60,16 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): choices.pop(CommandFilterRule.ActionChoices.confirm, None) action._choices = choices - # def validate_content(self, content): - # tp = self.initial_data.get("type") - # if tp == CommandFilterRule.TYPE_REGEX: - # return content - # if self.invalid_pattern.search(content): - # invalid_char = self.invalid_pattern.pattern.replace('\\', '') - # msg = _("Content should not be contain: {}").format(invalid_char) - # raise serializers.ValidationError(msg) - # return content + def validate_content(self, content): + tp = self.initial_data.get("type") + if tp == CommandFilterRule.TYPE_COMMAND: + regex = CommandFilterRule.construct_command_regex(content) + else: + regex = content + succeed, error, pattern = CommandFilterRule.compile_regex(regex) + if not succeed: + raise serializers.ValidationError(error) + return content class CommandConfirmSerializer(serializers.Serializer): diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 0e3306a36..8d00e6543 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -48,6 +48,7 @@ urlpatterns = [ path('system-users//temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'), path('system-users//tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'), path('system-users//cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), + path('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'), path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'), diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 23879ea62..8b291eba8 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9471737ad3816416dd8389f66a7530ecd874d0ccf69838b5ab0edae390398c9 -size 94354 +oid sha256:5a4e8bcfb535a77c85668323bb86f0348d2622f4840ecb42bc0ca9e47fcbef51 +size 94514 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index eec7e4f07..ad9203c75 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: 2021-12-08 17:30+0800\n" +"POT-Creation-Date: 2021-12-09 17:52+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -20,7 +20,7 @@ msgstr "" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: applications/models/application.py:166 assets/models/asset.py:139 #: assets/models/base.py:175 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:21 assets/models/domain.py:24 +#: assets/models/cmd_filter.py:23 assets/models/domain.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:24 perms/models/base.py:44 settings/models.py:29 #: settings/serializers/sms.py:6 terminal/models/storage.py:23 @@ -34,12 +34,12 @@ msgstr "" msgid "Name" msgstr "名称" -#: acls/models/base.py:27 assets/models/cmd_filter.py:54 +#: acls/models/base.py:27 assets/models/cmd_filter.py:77 #: assets/models/user.py:200 msgid "Priority" msgstr "优先级" -#: acls/models/base.py:28 assets/models/cmd_filter.py:54 +#: acls/models/base.py:28 assets/models/cmd_filter.py:77 #: assets/models/user.py:200 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -54,7 +54,7 @@ msgstr "激活中" #: acls/models/base.py:32 applications/models/application.py:179 #: assets/models/asset.py:144 assets/models/asset.py:232 #: assets/models/base.py:180 assets/models/cluster.py:29 -#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:64 +#: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:87 #: assets/models/domain.py:25 assets/models/domain.py:65 #: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 #: orgs/models.py:27 perms/models/base.py:53 settings/models.py:34 @@ -70,7 +70,7 @@ msgstr "备注" msgid "Reject" msgstr "拒绝" -#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:48 +#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:71 msgid "Allow" msgstr "允许" @@ -80,10 +80,11 @@ msgid "Login confirm" msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 -#: assets/models/label.py:15 audits/models.py:36 audits/models.py:56 -#: audits/models.py:74 audits/serializers.py:94 authentication/models.py:47 -#: orgs/models.py:19 orgs/models.py:433 perms/models/base.py:45 -#: templates/index.html:78 terminal/backends/command/models.py:18 +#: assets/models/cmd_filter.py:26 assets/models/label.py:15 audits/models.py:36 +#: audits/models.py:56 audits/models.py:74 audits/serializers.py:94 +#: authentication/models.py:47 orgs/models.py:19 orgs/models.py:433 +#: perms/models/base.py:45 templates/index.html:78 +#: terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:39 #: terminal/notifications.py:90 terminal/notifications.py:138 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:169 @@ -102,7 +103,7 @@ msgstr "规则" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 #: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 -#: assets/models/cmd_filter.py:57 audits/models.py:57 +#: assets/models/cmd_filter.py:80 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:34 #: users/templates/users/_granted_assets.html:29 #: users/templates/users/user_asset_permission.html:44 @@ -112,7 +113,7 @@ msgid "Action" msgstr "动作" #: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32 -#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:62 +#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:85 msgid "Reviewers" msgstr "审批人" @@ -129,9 +130,10 @@ msgstr "系统用户" #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:37 #: assets/models/asset.py:356 assets/models/authbook.py:18 -#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:259 -#: audits/models.py:38 perms/models/asset_permission.py:99 -#: templates/index.html:82 terminal/backends/command/models.py:19 +#: assets/models/cmd_filter.py:34 assets/models/gathered_user.py:14 +#: assets/serializers/system_user.py:259 audits/models.py:38 +#: perms/models/asset_permission.py:99 templates/index.html:82 +#: terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:41 #: terminal/notifications.py:89 #: users/templates/users/user_asset_permission.html:40 @@ -263,7 +265,7 @@ msgid "Custom" msgstr "自定义" #: applications/models/account.py:11 assets/models/authbook.py:19 -#: assets/models/user.py:292 audits/models.py:39 +#: assets/models/cmd_filter.py:38 assets/models/user.py:291 audits/models.py:39 #: perms/models/application_permission.py:32 #: perms/models/asset_permission.py:101 templates/_nav.html:45 #: terminal/backends/command/models.py:20 @@ -303,7 +305,7 @@ msgid "Category" msgstr "类别" #: applications/models/application.py:171 -#: applications/serializers/application.py:90 assets/models/cmd_filter.py:53 +#: applications/serializers/application.py:90 assets/models/cmd_filter.py:76 #: assets/models/user.py:199 perms/models/application_permission.py:23 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:55 terminal/models/storage.py:116 @@ -323,7 +325,7 @@ msgstr "网域" msgid "Attrs" msgstr "" -#: applications/models/application.py:183 +#: applications/models/application.py:183 assets/models/cmd_filter.py:41 #: perms/models/application_permission.py:27 users/models/user.py:170 msgid "Application" msgstr "应用程序" @@ -528,13 +530,13 @@ msgstr "协议组" msgid "Nodes" msgstr "节点" -#: assets/models/asset.py:220 assets/models/cmd_filter.py:22 +#: assets/models/asset.py:220 assets/models/cmd_filter.py:43 #: assets/models/domain.py:66 assets/models/label.py:22 msgid "Is active" msgstr "激活" #: assets/models/asset.py:223 assets/models/cluster.py:19 -#: assets/models/user.py:188 assets/models/user.py:341 templates/_nav.html:44 +#: assets/models/user.py:188 assets/models/user.py:340 templates/_nav.html:44 msgid "Admin user" msgstr "特权用户" @@ -551,8 +553,8 @@ msgid "Labels" msgstr "标签管理" #: assets/models/asset.py:230 assets/models/base.py:183 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 -#: assets/models/cmd_filter.py:67 assets/models/group.py:21 +#: assets/models/cluster.py:28 assets/models/cmd_filter.py:48 +#: assets/models/cmd_filter.py:90 assets/models/group.py:21 #: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25 #: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:591 #: users/serializers/group.py:33 @@ -656,47 +658,61 @@ msgstr "系统" msgid "Default Cluster" msgstr "默认Cluster" -#: assets/models/cmd_filter.py:33 assets/models/user.py:206 +#: assets/models/cmd_filter.py:30 perms/models/base.py:47 +#: templates/_nav.html:21 users/models/group.py:31 users/models/user.py:553 +#: users/templates/users/_select_user_modal.html:16 +#: users/templates/users/user_asset_permission.html:39 +#: users/templates/users/user_asset_permission.html:67 +#: users/templates/users/user_database_app_permission.html:38 +#: users/templates/users/user_database_app_permission.html:61 +msgid "User group" +msgstr "用户组" + +#: assets/models/cmd_filter.py:56 msgid "Command filter" msgstr "命令过滤器" -#: assets/models/cmd_filter.py:40 +#: assets/models/cmd_filter.py:63 msgid "Regex" msgstr "正则表达式" -#: assets/models/cmd_filter.py:41 ops/models/command.py:25 +#: assets/models/cmd_filter.py:64 ops/models/command.py:25 #: terminal/backends/command/serializers.py:15 terminal/models/session.py:50 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" msgstr "命令" -#: assets/models/cmd_filter.py:47 +#: assets/models/cmd_filter.py:70 msgid "Deny" msgstr "拒绝" -#: assets/models/cmd_filter.py:49 +#: assets/models/cmd_filter.py:72 msgid "Reconfirm" msgstr "复核" -#: assets/models/cmd_filter.py:52 +#: assets/models/cmd_filter.py:75 msgid "Filter" msgstr "过滤器" -#: assets/models/cmd_filter.py:56 settings/serializers/basic.py:10 +#: assets/models/cmd_filter.py:79 settings/serializers/basic.py:10 #: xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" -#: assets/models/cmd_filter.py:56 +#: assets/models/cmd_filter.py:79 msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:71 +#: assets/models/cmd_filter.py:94 msgid "Command filter rule" msgstr "命令过滤规则" -#: assets/models/cmd_filter.py:119 tickets/const.py:13 +#: assets/models/cmd_filter.py:132 +msgid "The generated regular expression is incorrect: {}" +msgstr "生成的正则表达式有误" + +#: assets/models/cmd_filter.py:158 tickets/const.py:13 msgid "Command confirm" msgstr "命令复核" @@ -821,27 +837,27 @@ msgstr "Shell" msgid "Login mode" msgstr "认证方式" -#: assets/models/user.py:207 +#: assets/models/user.py:206 msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:208 authentication/models.py:45 +#: assets/models/user.py:207 authentication/models.py:45 msgid "Token" msgstr "" -#: assets/models/user.py:209 +#: assets/models/user.py:208 msgid "Home" msgstr "家目录" -#: assets/models/user.py:210 +#: assets/models/user.py:209 msgid "System groups" msgstr "用户组" -#: assets/models/user.py:213 +#: assets/models/user.py:212 msgid "User switch" msgstr "用户切换" -#: assets/models/user.py:214 +#: assets/models/user.py:213 msgid "Switch from" msgstr "切换自" @@ -1503,7 +1519,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" -#: authentication/api/connection_token.py:259 +#: authentication/api/connection_token.py:285 msgid "Invalid token" msgstr "无效的令牌" @@ -2638,15 +2654,6 @@ msgstr "未分组" msgid "Favorite" msgstr "收藏夹" -#: perms/models/base.py:47 templates/_nav.html:21 users/models/group.py:31 -#: users/models/user.py:553 users/templates/users/_select_user_modal.html:16 -#: users/templates/users/user_asset_permission.html:39 -#: users/templates/users/user_asset_permission.html:67 -#: users/templates/users/user_database_app_permission.html:38 -#: users/templates/users/user_database_app_permission.html:61 -msgid "User group" -msgstr "用户组" - #: perms/models/base.py:50 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 @@ -4179,19 +4186,19 @@ msgstr "macOS 需要下载客户端来连接 RDP 资产,Windows 系统默认 msgid "Filters" msgstr "过滤" -#: terminal/api/session.py:185 +#: terminal/api/session.py:189 msgid "Session does not exist: {}" msgstr "会话不存在: {}" -#: terminal/api/session.py:188 +#: terminal/api/session.py:192 msgid "Session is finished or the protocol not supported" msgstr "会话已经完成或协议不支持" -#: terminal/api/session.py:193 +#: terminal/api/session.py:197 msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: terminal/api/session.py:197 +#: terminal/api/session.py:201 msgid "User does not have permission" msgstr "用户没有权限" @@ -5780,10 +5787,8 @@ msgid "Qingyun Private Cloud" msgstr "青云私有云" #: xpack/plugins/cloud/const.py:19 -#, fuzzy -#| msgid "Tencent Cloud" msgid "OpenStack Cloud" -msgstr "腾讯云" +msgstr "OpenStack Cloud" #: xpack/plugins/cloud/const.py:20 msgid "Google Cloud Platform" @@ -6063,16 +6068,12 @@ msgid "API Endpoint" msgstr "API 端点" #: xpack/plugins/cloud/serializers/account_attrs.py:101 -#, fuzzy -#| msgid "Target url" -msgid "auth url" -msgstr "目标URL" +msgid "Auth url" +msgstr "认证地址" #: xpack/plugins/cloud/serializers/account_attrs.py:103 -#, fuzzy -#| msgid "Domain name" -msgid "user domain name" -msgstr "网域名称" +msgid "User domain" +msgstr "用户域" #: xpack/plugins/cloud/serializers/account_attrs.py:110 msgid "Service account key"