feat: 命令过滤器支持多维度绑定

feat: 修改命令过滤规则校验

feat: 修改系统用户命令过滤器迁移文件
pull/7345/head^2
Michael Bai 2021-12-09 17:18:16 +08:00 committed by 老广
parent 3ea4a9fbe6
commit bbf2aff96c
9 changed files with 263 additions and 113 deletions

View File

@ -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

View File

@ -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):

View File

@ -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'),
),
]

View File

@ -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,10 +94,18 @@ class CommandFilterRule(OrgModelMixin):
verbose_name = _("Command filter rule")
@lazyproperty
def _pattern(self):
def pattern(self):
if self.type == 'command':
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 = self.content.replace('\r\n', '\n')
content = content.replace('\r\n', '\n')
for _cmd in content.split('\n'):
cmd = re.sub(r'\s+', ' ', _cmd)
cmd = re.escape(cmd)
@ -90,17 +121,25 @@ class CommandFilterRule(OrgModelMixin):
regex.append(r'\b{0}\b'.format(cmd))
else:
regex.append(r'\b{0}'.format(cmd))
s = r'{}'.format('|'.join(regex))
else:
s = r'{0}'.format(self.content)
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, ''

View File

@ -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)

View File

@ -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):

View File

@ -48,6 +48,7 @@ urlpatterns = [
path('system-users/<uuid:pk>/temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'),
path('system-users/<uuid:pk>/tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'),
path('system-users/<uuid:pk>/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'),

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9471737ad3816416dd8389f66a7530ecd874d0ccf69838b5ab0edae390398c9
size 94354
oid sha256:5a4e8bcfb535a77c85668323bb86f0348d2622f4840ecb42bc0ca9e47fcbef51
size 94514

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-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 <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\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"