From dafc416783d4a08eee07d95e7022088f969e15ee Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:48:43 +0800 Subject: [PATCH] Fix rbac (#7728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 重命名 signal handlers * fix: 修复 ticket processor 问题 * perf: 修改 ticket 处理人api * fix: 修复创建系统账号bug * fix: 升级celery_beat==2.2.1和flower==1.0.0;修改celery进程启动参数先后顺序 * perf: 修改 authentication token * fix: 修复上传权限bug * fix: 登录页面增加i18n切换; * fix: 系统角色删除限制 * perf: 修改一下 permissions tree * perf: 生成 i18n * perf: 修改一点点 Co-authored-by: ibuler <ibuler@qq.com> Co-authored-by: feng626 <1304903146@qq.com> Co-authored-by: Jiangjie.Bai <bugatti_it@163.com> --- apps/assets/api/cmd_filter.py | 2 +- apps/assets/apps.py | 4 +- apps/assets/models/asset.py | 4 +- .../__init__.py | 0 .../asset.py | 0 .../authbook.py | 0 .../common.py | 0 .../node_assets_amount.py | 0 .../node_assets_mapping.py | 0 .../system_user.py | 0 apps/audits/apps.py | 2 +- ...{signals_handler.py => signal_handlers.py} | 0 apps/authentication/api/connection_token.py | 16 +- apps/authentication/apps.py | 2 +- .../migrations/0007_connectiontoken.py | 12 +- .../migrations/0008_auto_20220217_2135.py | 21 - .../migrations/0008_superconnectiontoken.py | 25 + apps/authentication/models.py | 11 +- apps/authentication/serializers.py | 14 +- ...signals_handlers.py => signal_handlers.py} | 0 .../templates/authentication/login.html | 27 + .../management/commands/expire_caches.py | 2 +- .../management/commands/services/command.py | 2 +- .../commands/services/services/celery_base.py | 5 +- .../commands/services/services/flower.py | 3 +- apps/jumpserver/context_processor.py | 2 +- apps/jumpserver/urls.py | 3 +- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 582 ++++++++++-------- apps/notifications/apps.py | 2 +- ...{signals_handler.py => signal_handlers.py} | 0 apps/notifications/ws.py | 2 +- apps/ops/apps.py | 4 +- apps/ops/notifications.py | 2 +- ...{signals_handler.py => signal_handlers.py} | 0 apps/orgs/apps.py | 4 +- .../__init__.py | 0 .../cache.py | 0 .../common.py | 0 apps/perms/apps.py | 4 +- .../serializers/application/permission.py | 2 +- .../__init__.py | 0 .../app_permission.py | 0 .../asset_permission.py | 0 .../refresh_perms.py | 0 apps/rbac/api/rolebinding.py | 16 +- apps/rbac/const.py | 10 +- apps/rbac/models/permission.py | 193 +----- apps/rbac/permissions.py | 1 + apps/rbac/tree.py | 387 ++++++++++++ apps/settings/apps.py | 2 +- ...{signals_handler.py => signal_handlers.py} | 0 apps/terminal/apps.py | 2 +- .../migrations/0047_auto_20220302_1951.py | 17 + apps/terminal/models/replay.py | 1 + ...{signals_handler.py => signal_handlers.py} | 0 apps/tickets/api/super_ticket.py | 11 +- apps/tickets/apps.py | 2 +- apps/tickets/models/ticket.py | 7 +- apps/tickets/serializers/super_ticket.py | 13 +- .../__init__.py | 0 .../comment.py | 0 .../ticket.py | 0 apps/users/apps.py | 2 +- apps/users/serializers/user.py | 4 +- ...{signals_handler.py => signal_handlers.py} | 0 jms | 10 +- requirements/requirements.txt | 4 +- utils/start_celery_beat.py | 3 +- 69 files changed, 929 insertions(+), 519 deletions(-) rename apps/assets/{signals_handler => signal_handlers}/__init__.py (100%) rename apps/assets/{signals_handler => signal_handlers}/asset.py (100%) rename apps/assets/{signals_handler => signal_handlers}/authbook.py (100%) rename apps/assets/{signals_handler => signal_handlers}/common.py (100%) rename apps/assets/{signals_handler => signal_handlers}/node_assets_amount.py (100%) rename apps/assets/{signals_handler => signal_handlers}/node_assets_mapping.py (100%) rename apps/assets/{signals_handler => signal_handlers}/system_user.py (100%) rename apps/audits/{signals_handler.py => signal_handlers.py} (100%) delete mode 100644 apps/authentication/migrations/0008_auto_20220217_2135.py create mode 100644 apps/authentication/migrations/0008_superconnectiontoken.py rename apps/authentication/{signals_handlers.py => signal_handlers.py} (100%) rename apps/notifications/{signals_handler.py => signal_handlers.py} (100%) rename apps/ops/{signals_handler.py => signal_handlers.py} (100%) rename apps/orgs/{signals_handler => signal_handlers}/__init__.py (100%) rename apps/orgs/{signals_handler => signal_handlers}/cache.py (100%) rename apps/orgs/{signals_handler => signal_handlers}/common.py (100%) rename apps/perms/{signals_handler => signal_handlers}/__init__.py (100%) rename apps/perms/{signals_handler => signal_handlers}/app_permission.py (100%) rename apps/perms/{signals_handler => signal_handlers}/asset_permission.py (100%) rename apps/perms/{signals_handler => signal_handlers}/refresh_perms.py (100%) create mode 100644 apps/rbac/tree.py rename apps/settings/{signals_handler.py => signal_handlers.py} (100%) create mode 100644 apps/terminal/migrations/0047_auto_20220302_1951.py rename apps/terminal/{signals_handler.py => signal_handlers.py} (100%) rename apps/tickets/{signals_handler => signal_handlers}/__init__.py (100%) rename apps/tickets/{signals_handler => signal_handlers}/comment.py (100%) rename apps/tickets/{signals_handler => signal_handlers}/ticket.py (100%) rename apps/users/{signals_handler.py => signal_handlers.py} (100%) diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py index c9fad91a0..dcb2d77c9 100644 --- a/apps/assets/api/cmd_filter.py +++ b/apps/assets/api/cmd_filter.py @@ -53,7 +53,7 @@ class CommandConfirmAPI(CreateAPIView): run_command=self.serializer.data.get('run_command'), session=self.serializer.session, cmd_filter_rule=self.serializer.cmd_filter_rule, - org_id=self.serializer.org.id + org_id=self.serializer.org.id, ) return ticket diff --git a/apps/assets/apps.py b/apps/assets/apps.py index 5b44e2e92..e1bb43544 100644 --- a/apps/assets/apps.py +++ b/apps/assets/apps.py @@ -6,11 +6,11 @@ from django.apps import AppConfig class AssetsConfig(AppConfig): name = 'assets' - verbose_name = _('Assets') + verbose_name = _('App assets') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def ready(self): super().ready() - from . import signals_handler + from . import signal_handlers diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 905b70aa5..b0872ad0d 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -355,6 +355,6 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin verbose_name = _("Asset") ordering = ["hostname", ] permissions = [ - ('test_assetconnectivity', 'Can test asset connectivity'), - ('push_assetsystemuser', 'Can push system user to asset'), + ('test_assetconnectivity', _('Can test asset connectivity')), + ('push_assetsystemuser', _('Can push system user to asset')), ] diff --git a/apps/assets/signals_handler/__init__.py b/apps/assets/signal_handlers/__init__.py similarity index 100% rename from apps/assets/signals_handler/__init__.py rename to apps/assets/signal_handlers/__init__.py diff --git a/apps/assets/signals_handler/asset.py b/apps/assets/signal_handlers/asset.py similarity index 100% rename from apps/assets/signals_handler/asset.py rename to apps/assets/signal_handlers/asset.py diff --git a/apps/assets/signals_handler/authbook.py b/apps/assets/signal_handlers/authbook.py similarity index 100% rename from apps/assets/signals_handler/authbook.py rename to apps/assets/signal_handlers/authbook.py diff --git a/apps/assets/signals_handler/common.py b/apps/assets/signal_handlers/common.py similarity index 100% rename from apps/assets/signals_handler/common.py rename to apps/assets/signal_handlers/common.py diff --git a/apps/assets/signals_handler/node_assets_amount.py b/apps/assets/signal_handlers/node_assets_amount.py similarity index 100% rename from apps/assets/signals_handler/node_assets_amount.py rename to apps/assets/signal_handlers/node_assets_amount.py diff --git a/apps/assets/signals_handler/node_assets_mapping.py b/apps/assets/signal_handlers/node_assets_mapping.py similarity index 100% rename from apps/assets/signals_handler/node_assets_mapping.py rename to apps/assets/signal_handlers/node_assets_mapping.py diff --git a/apps/assets/signals_handler/system_user.py b/apps/assets/signal_handlers/system_user.py similarity index 100% rename from apps/assets/signals_handler/system_user.py rename to apps/assets/signal_handlers/system_user.py diff --git a/apps/audits/apps.py b/apps/audits/apps.py index e66faa467..904739ec7 100644 --- a/apps/audits/apps.py +++ b/apps/audits/apps.py @@ -9,6 +9,6 @@ class AuditsConfig(AppConfig): verbose_name = _('Audits') def ready(self): - from . import signals_handler + from . import signal_handlers if settings.SYSLOG_ENABLE: post_save.connect(signals_handler.on_audits_log_create) diff --git a/apps/audits/signals_handler.py b/apps/audits/signal_handlers.py similarity index 100% rename from apps/audits/signals_handler.py rename to apps/audits/signal_handlers.py diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 3712bdf51..47ce04ece 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -302,16 +302,26 @@ class SecretDetailMixin: user=user, system_user=system_user, expired_at=expired_at, actions=actions ) + cmd_filter_kwargs = { + 'system_user_id': system_user.id, + 'user_id': user.id, + } if asset: asset_detail = self._get_asset_secret_detail(asset) system_user.load_asset_more_auth(asset.id, user.username, user.id) data['type'] = 'asset' data.update(asset_detail) + cmd_filter_kwargs['asset_id'] = asset.id else: app_detail = self._get_application_secret_detail(app) system_user.load_app_more_auth(app.id, user.username, user.id) data['type'] = 'application' data.update(app_detail) + cmd_filter_kwargs['application_id'] = app.id + + from assets.models import CommandFilterRule + cmd_filter_rules = CommandFilterRule.get_queryset(**cmd_filter_kwargs) + data['cmd_filter_rules'] = cmd_filter_rules serializer = self.get_serializer(data) return Response(data=serializer.data, status=200) @@ -350,8 +360,10 @@ class UserConnectionTokenViewSet( return True def create_token(self, user, asset, application, system_user, ttl=5 * 60): - if not self.request.user.is_superuser and user != self.request.user: - raise PermissionDenied('Only super user can create user token') + # 再次强调一下权限 + perm_required = 'authentication.add_superconnectiontoken' + if user != self.request.user and not self.request.user.has_perm(perm_required): + raise PermissionDenied('Only can create user token') self.check_resource_permission(user, asset, application, system_user) token = random_string(36) secret = random_string(16) diff --git a/apps/authentication/apps.py b/apps/authentication/apps.py index 527c54871..6516ed70e 100644 --- a/apps/authentication/apps.py +++ b/apps/authentication/apps.py @@ -7,7 +7,7 @@ class AuthenticationConfig(AppConfig): verbose_name = _('Authentication') def ready(self): - from . import signals_handlers + from . import signal_handlers from . import notifications super().ready() diff --git a/apps/authentication/migrations/0007_connectiontoken.py b/apps/authentication/migrations/0007_connectiontoken.py index c017c5d76..86341ff5b 100644 --- a/apps/authentication/migrations/0007_connectiontoken.py +++ b/apps/authentication/migrations/0007_connectiontoken.py @@ -19,8 +19,14 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ], - options={ - 'permissions': [('add_superconnectiontoken', 'Can add super connection token'), ('view_connectiontokensecret', 'Can view connect token secret')], - }, + options={'verbose_name': 'Connection token'}, + ), + migrations.AlterModelOptions( + name='accesskey', + options={'verbose_name': 'Access key'}, + ), + migrations.AlterModelOptions( + name='ssotoken', + options={'verbose_name': 'SSO token'}, ), ] diff --git a/apps/authentication/migrations/0008_auto_20220217_2135.py b/apps/authentication/migrations/0008_auto_20220217_2135.py deleted file mode 100644 index 3f6b55248..000000000 --- a/apps/authentication/migrations/0008_auto_20220217_2135.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.1.13 on 2022-02-17 13:35 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0007_connectiontoken'), - ] - - operations = [ - migrations.AlterModelOptions( - name='accesskey', - options={'verbose_name': 'Access key'}, - ), - migrations.AlterModelOptions( - name='ssotoken', - options={'verbose_name': 'SSO token'}, - ), - ] diff --git a/apps/authentication/migrations/0008_superconnectiontoken.py b/apps/authentication/migrations/0008_superconnectiontoken.py new file mode 100644 index 000000000..82e956a24 --- /dev/null +++ b/apps/authentication/migrations/0008_superconnectiontoken.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.14 on 2022-03-02 11:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0007_connectiontoken'), + ] + + operations = [ + migrations.CreateModel( + name='SuperConnectionToken', + fields=[ + ], + options={ + 'verbose_name': 'Super connection token', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('authentication.connectiontoken',), + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index f263973fa..7e407a1dd 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -58,7 +58,10 @@ class ConnectionToken(models.JMSBaseModel): # Todo: add connection token 可能要授权给 普通用户, 或者放开就行 class Meta: - permissions = [ - ('add_superconnectiontoken', _('Can add super connection token')), - ('view_connectiontokensecret', _('Can view connect token secret')) - ] + verbose_name = _('Connection token') + + +class SuperConnectionToken(ConnectionToken): + class Meta: + proxy = True + verbose_name = _("Super connection token") diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index 678b7763b..f6661ba38 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -5,7 +5,7 @@ from rest_framework import serializers from common.utils import get_object_or_none from users.models import User -from assets.models import Asset, SystemUser, Gateway, Domain +from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule from applications.models import Application from users.serializers import UserProfileSerializer from assets.serializers import ProtocolsField @@ -200,6 +200,17 @@ class ConnectionTokenDomainSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'gateways'] +class ConnectionTokenFilterRuleSerializer(serializers.ModelSerializer): + + class Meta: + model = CommandFilterRule + fields = [ + 'id', 'type', 'content', 'ignore_case', 'pattern', + 'priority', 'action', + 'date_created', + ] + + class ConnectionTokenSecretSerializer(serializers.Serializer): id = serializers.CharField(read_only=True) secret = serializers.CharField(read_only=True) @@ -209,6 +220,7 @@ class ConnectionTokenSecretSerializer(serializers.Serializer): remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) application = ConnectionTokenApplicationSerializer(read_only=True) system_user = ConnectionTokenSystemUserSerializer(read_only=True) + cmd_filter_rules = ConnectionTokenFilterRuleSerializer(many=True) domain = ConnectionTokenDomainSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) actions = ActionsField() diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signal_handlers.py similarity index 100% rename from apps/authentication/signals_handlers.py rename to apps/authentication/signal_handlers.py diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index b74217cee..3e24aa610 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -115,10 +115,21 @@ .mfa-div { width: 100%; } + + .login-page-language { + margin-right: -11px !important; + padding-top: 12px !important; + padding-left: 0 !important; + padding-bottom: 8px !important; + color: #666 !important; + font-weight: 350 !important; + min-height: auto !important; + } </style> </head> <body> + <div class="login-content"> <div class="right-image-box"> <a href="{% if not XPACK_ENABLED %}https://github.com/jumpserver/jumpserver{% endif %}"> @@ -127,6 +138,22 @@ </div> <div class="left-form-box {% if not form.challenge and not form.captcha %} no-captcha-challenge {% endif %}"> <div style="background-color: white"> + <ul class="nav navbar-top-links navbar-right"> + <li class="dropdown"> + <a class="dropdown-toggle login-page-language" data-toggle="dropdown" href="#" target="_blank"> + <i class="fa fa-globe fa-lg" style="margin-right: 2px"></i> + {% ifequal request.COOKIES.django_language 'en' %} + <span>English<b class="caret"></b></span> + {% else %} + <span>中文(简体)<b class="caret"></b></span> + {% endifequal %} + </a> + <ul class="dropdown-menu profile-dropdown dropdown-menu-right"> + <li> <a id="switch_cn" href="{% url 'i18n-switch' lang='zh-hans' %}"> <span>中文(简体)</span> </a> </li> + <li> <a id="switch_en" href="{% url 'i18n-switch' lang='en' %}"> <span>English</span> </a> </li> + </ul> + </li> + </ul> <div class="jms-title"> <span style="font-size: 21px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span> </div> diff --git a/apps/common/management/commands/expire_caches.py b/apps/common/management/commands/expire_caches.py index 37fe7605b..bc20a3cf5 100644 --- a/apps/common/management/commands/expire_caches.py +++ b/apps/common/management/commands/expire_caches.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand -from assets.signals_handler.node_assets_mapping import expire_node_assets_mapping_for_memory +from assets.signal_handlers.node_assets_mapping import expire_node_assets_mapping_for_memory from orgs.caches import OrgResourceStatisticsCache from orgs.models import Organization diff --git a/apps/common/management/commands/services/command.py b/apps/common/management/commands/services/command.py index 170eb69ae..dd2cd9cdb 100644 --- a/apps/common/management/commands/services/command.py +++ b/apps/common/management/commands/services/command.py @@ -52,7 +52,7 @@ class Services(TextChoices): @classmethod def export_services_values(cls): - return [cls.all.value, cls.web.value, cls.task.value] + return [cls.all.value, cls.web.value, cls.task.value] + [s.value for s in cls.all_services()] @classmethod def get_service_objects(cls, service_names, **kwargs): diff --git a/apps/common/management/commands/services/services/celery_base.py b/apps/common/management/commands/services/services/celery_base.py index 5542fd72f..435e4ebe2 100644 --- a/apps/common/management/commands/services/services/celery_base.py +++ b/apps/common/management/commands/services/services/celery_base.py @@ -23,9 +23,10 @@ class CeleryBaseService(BaseService): server_hostname = '%h' cmd = [ - 'celery', 'worker', - '-P', 'threads', + 'celery', '-A', 'ops', + 'worker', + '-P', 'threads', '-l', 'INFO', '-c', str(self.num), '-Q', self.queue, diff --git a/apps/common/management/commands/services/services/flower.py b/apps/common/management/commands/services/services/flower.py index df2230776..402e9f37d 100644 --- a/apps/common/management/commands/services/services/flower.py +++ b/apps/common/management/commands/services/services/flower.py @@ -16,8 +16,9 @@ class FlowerService(BaseService): if os.getuid() == 0: os.environ.setdefault('C_FORCE_ROOT', '1') cmd = [ - 'celery', 'flower', + 'celery', '-A', 'ops', + 'flower', '-l', 'INFO', '--url_prefix=/core/flower', '--auto_refresh=False', diff --git a/apps/jumpserver/context_processor.py b/apps/jumpserver/context_processor.py index f714759f0..54a7b856e 100644 --- a/apps/jumpserver/context_processor.py +++ b/apps/jumpserver/context_processor.py @@ -2,7 +2,7 @@ # from django.templatetags.static import static from django.conf import settings -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ default_context = { 'DEFAULT_PK': '00000000-0000-0000-0000-000000000000', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 80948f9f6..6a9ae69a9 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -33,7 +33,8 @@ app_view_patterns = [ path('ops/', include('ops.urls.view_urls'), name='ops'), path('common/', include('common.urls.view_urls'), name='common'), re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'), - path('download/', views.ResourceDownload.as_view(), name='download') + path('download/', views.ResourceDownload.as_view(), name='download'), + path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'), ] if settings.XPACK_ENABLED: diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 7a5c32de9..7c0f80e7b 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:6144c6aa78bcaf3c282ee63f9e48b11fb8c327192aa8ea8f1ff1c2080084304f -size 100589 +oid sha256:8bd2394fc5d9bb9254965db4273a09d4ddabd8051b4855b9642476ff9cab836b +size 101898 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index d93918379..4924dda74 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-02-22 19:38+0800\n" +"POT-Creation-Date: 2022-03-02 19:46+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" @@ -19,7 +19,7 @@ msgstr "" #: acls/apps.py:7 msgid "Acls" -msgstr "" +msgstr "访问控制" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: applications/models/application.py:202 assets/models/asset.py:138 @@ -30,7 +30,7 @@ msgstr "" #: settings/models.py:29 settings/serializers/sms.py:6 #: terminal/models/storage.py:23 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:32 -#: users/models/group.py:15 users/models/user.py:537 +#: users/models/group.py:15 users/models/user.py:550 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -65,8 +65,8 @@ msgstr "激活中" #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:15 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 -#: tickets/models/comment.py:24 tickets/models/ticket.py:78 -#: users/models/group.py:16 users/models/user.py:574 +#: tickets/models/comment.py:24 tickets/models/ticket.py:154 +#: users/models/group.py:16 users/models/user.py:587 #: xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113 #: xpack/plugins/gathered_user/models.py:26 @@ -90,12 +90,12 @@ msgstr "登录复核" #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 #: authentication/models.py:50 orgs/models.py:196 perms/models/base.py:84 -#: rbac/builtin.py:87 rbac/models/rolebinding.py:33 templates/index.html:78 +#: rbac/builtin.py:89 rbac/models/rolebinding.py:33 templates/index.html:78 #: terminal/backends/command/models.py:19 -#: terminal/backends/command/serializers.py:12 terminal/models/session.py:41 +#: terminal/backends/command/serializers.py:12 terminal/models/session.py:42 #: terminal/notifications.py:88 terminal/notifications.py:136 -#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:757 -#: users/models/user.py:788 users/serializers/group.py:19 +#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:769 +#: users/models/user.py:800 users/serializers/group.py:19 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 #: users/templates/users/user_database_app_permission.html:37 @@ -140,7 +140,7 @@ msgstr "系统用户" #: assets/models/gathered_user.py:14 assets/serializers/system_user.py:264 #: audits/models.py:39 perms/models/asset_permission.py:23 #: templates/index.html:82 terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:43 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 #: terminal/notifications.py:87 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 @@ -168,7 +168,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:535 +#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:548 #: users/templates/users/_msg_user_created.html:12 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:34 @@ -283,7 +283,7 @@ msgstr "应用程序" #: assets/models/cmd_filter.py:42 assets/models/user.py:325 audits/models.py:40 #: perms/models/application_permission.py:32 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:45 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -303,7 +303,7 @@ msgstr "版本" #: applications/models/account.py:23 msgid "Application account" -msgstr "应用数量" +msgstr "应用账号" #: applications/models/account.py:26 applications/models/account.py:27 msgid "Can view application account secret" @@ -324,7 +324,7 @@ msgstr "类别" #: perms/models/application_permission.py:23 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:55 terminal/models/storage.py:119 -#: tickets/models/flow.py:56 tickets/models/ticket.py:55 +#: tickets/models/flow.py:56 tickets/models/ticket.py:131 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29 #: xpack/plugins/change_auth_plan/models/app.py:28 #: xpack/plugins/change_auth_plan/models/app.py:153 @@ -368,7 +368,7 @@ msgstr "类型名称" #: assets/serializers/account.py:18 common/db/models.py:113 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 #: orgs/models.py:14 orgs/models.py:199 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:789 +#: users/models/group.py:18 users/models/user.py:801 #: xpack/plugins/cloud/models.py:122 msgid "Date created" msgstr "创建日期" @@ -493,11 +493,9 @@ msgstr "不能删除根节点 ({})" msgid "Deletion failed and the node contains assets" msgstr "删除失败,节点包含资产" -#: assets/apps.py:9 assets/models/user.py:227 assets/serializers/domain.py:29 -#: terminal/templates/terminal/_msg_command_execute_alert.html:16 -#: xpack/plugins/change_auth_plan/models/asset.py:39 -msgid "Assets" -msgstr "资产" +#: assets/apps.py:9 +msgid "App assets" +msgstr "资产管理" #: assets/models/asset.py:139 msgid "Base" @@ -508,7 +506,7 @@ msgid "Charset" msgstr "编码" #: assets/models/asset.py:141 assets/serializers/asset.py:176 -#: tickets/models/ticket.py:57 +#: tickets/models/ticket.py:133 msgid "Meta" msgstr "元数据" @@ -530,7 +528,7 @@ msgstr "制造商" msgid "Model" msgstr "型号" -#: assets/models/asset.py:170 tickets/models/ticket.py:83 +#: assets/models/asset.py:170 tickets/models/ticket.py:159 msgid "Serial number" msgstr "序列号" @@ -617,24 +615,32 @@ msgstr "标签管理" #: assets/models/cluster.py:28 assets/models/cmd_filter.py:52 #: assets/models/cmd_filter.py:99 assets/models/group.py:21 #: common/db/models.py:111 common/mixins/models.py:49 orgs/models.py:13 -#: orgs/models.py:201 perms/models/base.py:91 users/models/user.py:582 +#: orgs/models.py:201 perms/models/base.py:91 users/models/user.py:595 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 #: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" +#: assets/models/asset.py:358 +msgid "Can test asset connectivity" +msgstr "可以测试资产连接性" + +#: assets/models/asset.py:359 +msgid "Can push system user to asset" +msgstr "可以推送系统用户到资产" + #: assets/models/authbook.py:27 msgid "AuthBook" -msgstr "账号" +msgstr "资产账号" #: assets/models/authbook.py:30 msgid "Can view asset account secret" -msgstr "" +msgstr "可以查看资产账号密码" #: assets/models/authbook.py:31 msgid "Can change asset account secret" -msgstr "" +msgstr "可以更改资产账号密码" #: assets/models/backup.py:30 perms/models/base.py:54 #: settings/serializers/terminal.py:12 @@ -650,7 +656,7 @@ msgstr "收件人" #: assets/models/backup.py:62 assets/models/backup.py:124 msgid "Account backup plan" -msgstr "账户备份计划" +msgstr "账号备份计划" #: assets/models/backup.py:100 #: xpack/plugins/change_auth_plan/models/base.py:107 @@ -663,7 +669,7 @@ msgid "Timing trigger" msgstr "定时触发" #: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31 -#: perms/models/base.py:89 terminal/models/session.py:55 +#: perms/models/base.py:89 terminal/models/session.py:56 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 #: xpack/plugins/change_auth_plan/models/base.py:112 @@ -736,7 +742,7 @@ msgstr "可连接性" msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:177 audits/signals_handler.py:68 +#: assets/models/base.py:177 audits/signal_handlers.py:68 #: authentication/forms.py:22 #: authentication/templates/authentication/login.html:151 #: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 @@ -770,7 +776,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:557 +#: assets/models/cluster.py:22 users/models/user.py:570 msgid "Phone" msgstr "手机" @@ -796,7 +802,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:774 +#: users/models/user.py:786 msgid "System" msgstr "系统" @@ -805,7 +811,7 @@ msgid "Default Cluster" msgstr "默认Cluster" #: assets/models/cmd_filter.py:34 perms/models/base.py:86 -#: users/models/group.py:31 users/models/user.py:543 +#: users/models/group.py:31 users/models/user.py:556 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 @@ -823,7 +829,7 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:52 +#: terminal/backends/command/serializers.py:15 terminal/models/session.py:53 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -959,6 +965,12 @@ msgstr "普通用户" msgid "Username same with user" msgstr "用户名与用户相同" +#: assets/models/user.py:227 assets/serializers/domain.py:29 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 +#: xpack/plugins/change_auth_plan/models/asset.py:39 +msgid "Assets" +msgstr "资产" + #: assets/models/user.py:231 users/apps.py:9 msgid "Users" msgstr "用户管理" @@ -1101,7 +1113,6 @@ msgid "Assets amount" msgstr "资产数量" #: assets/serializers/domain.py:14 -#: perms/serializers/application/permission.py:46 msgid "Applications amount" msgstr "应用数量" @@ -1126,6 +1137,7 @@ msgid "SSH key fingerprint" msgstr "密钥指纹" #: assets/serializers/system_user.py:30 +#: perms/serializers/application/permission.py:46 msgid "Apps amount" msgstr "应用数量" @@ -1315,8 +1327,7 @@ msgstr "日志审计" #: audits/models.py:27 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/models/permission.py:129 -#: users/templates/users/user_asset_permission.html:128 +#: rbac/tree.py:254 users/templates/users/user_asset_permission.html:128 #: users/templates/users/user_database_app_permission.html:111 msgid "Delete" msgstr "删除" @@ -1346,7 +1357,7 @@ msgid "Symlink" msgstr "建立软链接" #: audits/models.py:38 audits/models.py:64 audits/models.py:87 -#: terminal/models/session.py:48 terminal/models/sharing.py:82 +#: terminal/models/session.py:49 terminal/models/sharing.py:82 msgid "Remote addr" msgstr "远端地址" @@ -1370,12 +1381,12 @@ msgstr "文件管理" #: audits/models.py:55 #: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/models/permission.py:126 +#: rbac/tree.py:251 msgid "Create" msgstr "创建" -#: audits/models.py:56 rbac/models/permission.py:128 -#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6 +#: audits/models.py:56 rbac/tree.py:253 templates/_csv_import_export.html:18 +#: templates/_csv_update_modal.html:6 #: users/templates/users/user_asset_permission.html:127 #: users/templates/users/user_database_app_permission.html:110 msgid "Update" @@ -1438,13 +1449,13 @@ msgstr "用户代理" #: audits/models.py:124 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:64 users/models/user.py:560 +#: users/forms/profile.py:64 users/models/user.py:573 #: users/serializers/profile.py:121 msgid "MFA" msgstr "MFA" #: audits/models.py:126 terminal/models/status.py:33 -#: tickets/models/ticket.py:64 xpack/plugins/cloud/models.py:172 +#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:172 #: xpack/plugins/cloud/models.py:224 msgid "Status" msgstr "状态" @@ -1486,7 +1497,7 @@ msgstr "主机名称" msgid "Result" msgstr "结果" -#: audits/serializers.py:98 terminal/serializers/storage.py:151 +#: audits/serializers.py:98 terminal/serializers/storage.py:161 msgid "Hosts" msgstr "主机" @@ -1502,195 +1513,195 @@ msgstr "运行用户名称" msgid "User display" msgstr "用户名称" -#: audits/signals_handler.py:67 +#: audits/signal_handlers.py:67 msgid "SSH Key" msgstr "SSH 密钥" -#: audits/signals_handler.py:69 +#: audits/signal_handlers.py:69 msgid "SSO" msgstr "" -#: audits/signals_handler.py:70 +#: audits/signal_handlers.py:70 msgid "Auth Token" msgstr "认证令牌" -#: audits/signals_handler.py:71 authentication/notifications.py:73 +#: audits/signal_handlers.py:71 authentication/notifications.py:73 #: authentication/views/login.py:164 authentication/views/wecom.py:158 -#: notifications/backends/__init__.py:11 users/models/user.py:596 +#: notifications/backends/__init__.py:11 users/models/user.py:609 msgid "WeCom" msgstr "企业微信" -#: audits/signals_handler.py:72 authentication/views/dingtalk.py:160 +#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:160 #: authentication/views/login.py:170 notifications/backends/__init__.py:12 -#: users/models/user.py:597 +#: users/models/user.py:610 msgid "DingTalk" msgstr "钉钉" -#: audits/signals_handler.py:106 +#: audits/signal_handlers.py:106 msgid "User and Group" msgstr "用户与用户组" -#: audits/signals_handler.py:107 +#: audits/signal_handlers.py:107 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} 加入 {UserGroup}" -#: audits/signals_handler.py:108 +#: audits/signal_handlers.py:108 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} 离开 {UserGroup}" -#: audits/signals_handler.py:111 +#: audits/signal_handlers.py:111 msgid "Asset and SystemUser" msgstr "资产与系统用户" -#: audits/signals_handler.py:112 +#: audits/signal_handlers.py:112 #, python-brace-format msgid "{Asset} ADD {SystemUser}" msgstr "{Asset} 添加 {SystemUser}" -#: audits/signals_handler.py:113 +#: audits/signal_handlers.py:113 #, python-brace-format msgid "{Asset} REMOVE {SystemUser}" msgstr "{Asset} 移除 {SystemUser}" -#: audits/signals_handler.py:116 +#: audits/signal_handlers.py:116 msgid "Node and Asset" msgstr "节点与资产" -#: audits/signals_handler.py:117 +#: audits/signal_handlers.py:117 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 添加 {Asset}" -#: audits/signals_handler.py:118 +#: audits/signal_handlers.py:118 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 移除 {Asset}" -#: audits/signals_handler.py:121 +#: audits/signal_handlers.py:121 msgid "User asset permissions" msgstr "用户资产授权" -#: audits/signals_handler.py:122 +#: audits/signal_handlers.py:122 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 添加 {User}" -#: audits/signals_handler.py:123 +#: audits/signal_handlers.py:123 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 移除 {User}" -#: audits/signals_handler.py:126 +#: audits/signal_handlers.py:126 msgid "User group asset permissions" msgstr "用户组资产授权" -#: audits/signals_handler.py:127 +#: audits/signal_handlers.py:127 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 添加 {UserGroup}" -#: audits/signals_handler.py:128 +#: audits/signal_handlers.py:128 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signals_handler.py:131 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:131 perms/models/asset_permission.py:29 #: users/templates/users/_user_detail_nav_header.html:31 msgid "Asset permission" msgstr "资产授权" -#: audits/signals_handler.py:132 +#: audits/signal_handlers.py:132 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 添加 {Asset}" -#: audits/signals_handler.py:133 +#: audits/signal_handlers.py:133 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 移除 {Asset}" -#: audits/signals_handler.py:136 +#: audits/signal_handlers.py:136 msgid "Node permission" msgstr "节点授权" -#: audits/signals_handler.py:137 +#: audits/signal_handlers.py:137 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 添加 {Node}" -#: audits/signals_handler.py:138 +#: audits/signal_handlers.py:138 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 移除 {Node}" -#: audits/signals_handler.py:141 +#: audits/signal_handlers.py:141 msgid "Asset permission and SystemUser" msgstr "资产授权与系统用户" -#: audits/signals_handler.py:142 +#: audits/signal_handlers.py:142 #, python-brace-format msgid "{AssetPermission} ADD {SystemUser}" msgstr "{AssetPermission} 添加 {SystemUser}" -#: audits/signals_handler.py:143 +#: audits/signal_handlers.py:143 #, python-brace-format msgid "{AssetPermission} REMOVE {SystemUser}" msgstr "{AssetPermission} 移除 {SystemUser}" -#: audits/signals_handler.py:146 +#: audits/signal_handlers.py:146 msgid "User application permissions" msgstr "用户应用授权" -#: audits/signals_handler.py:147 +#: audits/signal_handlers.py:147 #, python-brace-format msgid "{ApplicationPermission} ADD {User}" msgstr "{ApplicationPermission} 添加 {User}" -#: audits/signals_handler.py:148 +#: audits/signal_handlers.py:148 #, python-brace-format msgid "{ApplicationPermission} REMOVE {User}" msgstr "{ApplicationPermission} 移除 {User}" -#: audits/signals_handler.py:151 +#: audits/signal_handlers.py:151 msgid "User group application permissions" msgstr "用户组应用授权" -#: audits/signals_handler.py:152 +#: audits/signal_handlers.py:152 #, python-brace-format msgid "{ApplicationPermission} ADD {UserGroup}" msgstr "{ApplicationPermission} 添加 {UserGroup}" -#: audits/signals_handler.py:153 +#: audits/signal_handlers.py:153 #, python-brace-format msgid "{ApplicationPermission} REMOVE {UserGroup}" msgstr "{ApplicationPermission} 移除 {UserGroup}" -#: audits/signals_handler.py:156 perms/models/application_permission.py:37 +#: audits/signal_handlers.py:156 perms/models/application_permission.py:37 msgid "Application permission" msgstr "应用授权" -#: audits/signals_handler.py:157 +#: audits/signal_handlers.py:157 #, python-brace-format msgid "{ApplicationPermission} ADD {Application}" msgstr "{ApplicationPermission} 添加 {Application}" -#: audits/signals_handler.py:158 +#: audits/signal_handlers.py:158 #, python-brace-format msgid "{ApplicationPermission} REMOVE {Application}" msgstr "{ApplicationPermission} 移除 {Application}" -#: audits/signals_handler.py:161 +#: audits/signal_handlers.py:161 msgid "Application permission and SystemUser" msgstr "应用授权与系统用户" -#: audits/signals_handler.py:162 +#: audits/signal_handlers.py:162 #, python-brace-format msgid "{ApplicationPermission} ADD {SystemUser}" msgstr "{ApplicationPermission} 添加 {SystemUser}" -#: audits/signals_handler.py:163 +#: audits/signal_handlers.py:163 #, python-brace-format msgid "{ApplicationPermission} REMOVE {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}" @@ -1711,54 +1722,54 @@ msgstr "验证码无效: {}" msgid "Authentication" msgstr "认证" -#: authentication/backends/api.py:70 +#: authentication/backends/drf.py:56 msgid "Invalid signature header. No credentials provided." msgstr "" -#: authentication/backends/api.py:73 +#: authentication/backends/drf.py:59 msgid "Invalid signature header. Signature string should not contain spaces." msgstr "" -#: authentication/backends/api.py:80 +#: authentication/backends/drf.py:66 msgid "Invalid signature header. Format like AccessKeyId:Signature" msgstr "" -#: authentication/backends/api.py:84 +#: authentication/backends/drf.py:70 msgid "" "Invalid signature header. Signature string should not contain invalid " "characters." msgstr "" -#: authentication/backends/api.py:104 authentication/backends/api.py:120 +#: authentication/backends/drf.py:90 authentication/backends/drf.py:106 msgid "Invalid signature." msgstr "" -#: authentication/backends/api.py:111 +#: authentication/backends/drf.py:97 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" msgstr "" -#: authentication/backends/api.py:116 +#: authentication/backends/drf.py:102 msgid "Expired, more than 15 minutes" msgstr "" -#: authentication/backends/api.py:123 +#: authentication/backends/drf.py:109 msgid "User disabled." msgstr "用户已禁用" -#: authentication/backends/api.py:141 +#: authentication/backends/drf.py:127 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication/backends/api.py:144 +#: authentication/backends/drf.py:130 msgid "Invalid token header. Sign string should not contain spaces." msgstr "" -#: authentication/backends/api.py:151 +#: authentication/backends/drf.py:137 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" -#: authentication/backends/api.py:162 +#: authentication/backends/drf.py:148 msgid "Invalid token or cache refreshed." msgstr "" @@ -1792,11 +1803,11 @@ msgstr "禁用或失效" #: authentication/errors.py:33 msgid "This account is inactive." -msgstr "此账户已禁用" +msgstr "此账号已禁用" #: authentication/errors.py:34 msgid "This account is expired" -msgstr "此账户已过期" +msgstr "此账号已过期" #: authentication/errors.py:35 msgid "Auth backend not match" @@ -1876,15 +1887,15 @@ msgstr "该 时间段 不被允许登录" msgid "SSO auth closed" msgstr "SSO 认证关闭了" -#: authentication/errors.py:300 authentication/mixins.py:364 +#: authentication/errors.py:300 authentication/mixins.py:372 msgid "Your password is too simple, please change it for security" msgstr "你的密码过于简单,为了安全,请修改" -#: authentication/errors.py:309 authentication/mixins.py:371 +#: authentication/errors.py:309 authentication/mixins.py:379 msgid "You should to change your password before login" msgstr "登录完成前,请先修改密码" -#: authentication/errors.py:318 authentication/mixins.py:378 +#: authentication/errors.py:318 authentication/mixins.py:386 msgid "Your password has expired, please reset before logging in" msgstr "您的密码已过期,先修改再登录" @@ -1976,11 +1987,11 @@ msgstr "设置手机号码启用" msgid "Clear phone number to disable" msgstr "清空手机号码禁用" -#: authentication/mixins.py:314 +#: authentication/mixins.py:322 msgid "The MFA type ({}) is not enabled" msgstr "该 MFA ({}) 方式没有启用" -#: authentication/mixins.py:354 +#: authentication/mixins.py:362 msgid "Please change your password" msgstr "请修改密码" @@ -2000,11 +2011,15 @@ msgstr "过期时间" msgid "SSO token" msgstr "" -#: authentication/models.py:62 +#: authentication/models.py:61 +msgid "Connection token" +msgstr "" + +#: authentication/models.py:63 msgid "Can add super connection token" msgstr "可以添加 超级连接Token" -#: authentication/models.py:63 +#: authentication/models.py:64 msgid "Can view connect token secret" msgstr "可以查看 连接Token 密文" @@ -2305,7 +2320,7 @@ msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" #: authentication/views/feishu.py:148 authentication/views/login.py:176 -#: notifications/backends/__init__.py:14 users/models/user.py:598 +#: notifications/backends/__init__.py:14 users/models/user.py:611 msgid "FeiShu" msgstr "飞书" @@ -2337,7 +2352,7 @@ msgstr "正在跳转到 {} 认证" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:265 +#: authentication/views/login.py:263 msgid "" "Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n" " Don't close this page" @@ -2345,15 +2360,15 @@ msgstr "" "等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n" " 不要关闭本页面" -#: authentication/views/login.py:270 +#: authentication/views/login.py:268 msgid "No ticket found" msgstr "没有发现工单" -#: authentication/views/login.py:304 +#: authentication/views/login.py:302 msgid "Logout success" msgstr "退出登录成功" -#: authentication/views/login.py:305 +#: authentication/views/login.py:303 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" @@ -2480,7 +2495,7 @@ msgstr "编码数据为 text" msgid "Encrypt field using Secret Key" msgstr "加密的字段" -#: common/mixins/api/action.py:53 +#: common/mixins/api/action.py:52 msgid "Request file format may be wrong" msgstr "上传的文件格式错误 或 其它类型资源的文件" @@ -2492,6 +2507,10 @@ msgstr "忽略的" msgid "discard time" msgstr "忽略时间" +#: common/models.py:8 +msgid "Can view resource statistics" +msgstr "可以查看资源统计" + #: common/sdk/im/exceptions.py:23 msgid "Network error, please contact system administrator" msgstr "网络错误,请联系系统管理员" @@ -2558,11 +2577,11 @@ msgstr "手机号格式不正确" #: jumpserver/conf.py:292 msgid "Create account successfully" -msgstr "创建账户成功" +msgstr "创建账号成功" #: jumpserver/conf.py:294 msgid "Your account has been created successfully" -msgstr "你的账户已创建成功" +msgstr "你的账号已创建成功" #: jumpserver/context_processor.py:17 msgid "JumpServer Open Source Bastion Host" @@ -2600,7 +2619,7 @@ msgid "Notifications" msgstr "通知" #: notifications/backends/__init__.py:10 users/forms/profile.py:101 -#: users/models/user.py:539 +#: users/models/user.py:552 msgid "Email" msgstr "邮件" @@ -2617,8 +2636,8 @@ msgid "Not has host {} permission" msgstr "没有该主机 {} 权限" #: ops/apps.py:9 ops/notifications.py:16 -msgid "Operations" -msgstr "运维" +msgid "App ops" +msgstr "作业中心" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 #: settings/serializers/auth/ldap.py:66 @@ -2808,8 +2827,8 @@ msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" #: orgs/apps.py:7 -msgid "Organizations" -msgstr "组织" +msgid "App organizations" +msgstr "组织管理" #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:27 #: orgs/models.py:193 rbac/const.py:7 rbac/models/rolebinding.py:40 @@ -2823,16 +2842,16 @@ msgstr "全局组织" #: orgs/models.py:29 msgid "Can view root org" -msgstr "" +msgstr "可以查看全局组织" #: orgs/models.py:198 rbac/models/role.py:46 rbac/models/rolebinding.py:36 -#: users/models/user.py:547 users/templates/users/_select_user_modal.html:15 +#: users/models/user.py:560 users/templates/users/_select_user_modal.html:15 msgid "Role" msgstr "角色" -#: perms/apps.py:9 rbac/models/role.py:34 -msgid "Permissions" -msgstr "授权" +#: perms/apps.py:9 +msgid "App permissions" +msgstr "授权管理" #: perms/exceptions.py:9 msgid "The administrator is modifying permissions. Please wait" @@ -2913,7 +2932,7 @@ msgstr "剪贴板复制粘贴" #: perms/models/base.py:90 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60 -#: users/models/user.py:579 +#: users/models/user.py:592 msgid "Date expired" msgstr "失效日期" @@ -2956,15 +2975,15 @@ msgstr "组织 ({}) 的应用授权" #: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:41 #: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:134 +#: perms/serializers/asset/permission.py:45 users/serializers/user.py:133 msgid "Is valid" -msgstr "账户是否有效" +msgstr "账号是否有效" #: perms/serializers/application/permission.py:21 #: perms/serializers/application/permission.py:40 #: perms/serializers/asset/permission.py:20 -#: perms/serializers/asset/permission.py:44 users/serializers/user.py:83 -#: users/serializers/user.py:136 +#: perms/serializers/asset/permission.py:44 users/serializers/user.py:82 +#: users/serializers/user.py:135 msgid "Is expired" msgstr "已过期" @@ -3034,31 +3053,35 @@ msgstr "" msgid "Internal role, can't be update" msgstr "" +#: rbac/api/rolebinding.py:46 +msgid "{} at least one system role" +msgstr "{} 至少有一个系统角色" + #: rbac/apps.py:7 msgid "RBAC" msgstr "RBAC" -#: rbac/builtin.py:78 +#: rbac/builtin.py:80 msgid "SystemAdmin" msgstr "系统管理员" -#: rbac/builtin.py:81 +#: rbac/builtin.py:83 msgid "SystemAuditor" msgstr "系统审计员" -#: rbac/builtin.py:84 +#: rbac/builtin.py:86 msgid "SystemComponent" msgstr "系统组件" -#: rbac/builtin.py:90 +#: rbac/builtin.py:92 msgid "OrgAdmin" msgstr "组织管理员" -#: rbac/builtin.py:93 +#: rbac/builtin.py:95 msgid "OrgAuditor" msgstr "组织审计员" -#: rbac/builtin.py:96 +#: rbac/builtin.py:98 msgid "OrgUser" msgstr "组织用户" @@ -3067,22 +3090,18 @@ msgid "Menu permission" msgstr "菜单授权" #: rbac/models/menu.py:15 -msgid "Console view" -msgstr "控制台" +msgid "view console view" +msgstr "查看控制台" #: rbac/models/menu.py:16 -msgid "Audit view" -msgstr "安全审计" +msgid "view audit view" +msgstr "查看安全审计" #: rbac/models/menu.py:17 -msgid "Workspace view" -msgstr "控制台" +msgid "view workspace view" +msgstr "查看工作台" -#: rbac/models/permission.py:127 -msgid "View" -msgstr "视图" - -#: rbac/models/permission.py:210 +#: rbac/models/permission.py:22 msgid "Permission" msgstr "授权" @@ -3090,6 +3109,10 @@ msgstr "授权" msgid "Scope" msgstr "范围" +#: rbac/models/role.py:34 +msgid "Permissions" +msgstr "授权" + #: rbac/models/role.py:36 msgid "Built-in" msgstr "内置" @@ -3116,7 +3139,7 @@ msgstr "用户最后一个角色,不能删除,你可以将用户从组织移 msgid "Organization role binding" msgstr "组织角色绑定" -#: rbac/models/rolebinding.py:133 +#: rbac/models/rolebinding.py:132 msgid "System role binding" msgstr "系统角色绑定" @@ -3140,6 +3163,62 @@ msgstr "角色显示" msgid "Has bound this role" msgstr "已经绑定" +#: rbac/tree.py:16 rbac/tree.py:17 +msgid "All permissions" +msgstr "所有权限" + +#: rbac/tree.py:24 +msgid "Console view" +msgstr "控制台" + +#: rbac/tree.py:28 +msgid "Workspace view" +msgstr "工作台" + +#: rbac/tree.py:32 +msgid "Audit view" +msgstr "安全审计" + +#: rbac/tree.py:36 settings/models.py:140 +msgid "System setting" +msgstr "系统设置" + +#: rbac/tree.py:40 +msgid "Other" +msgstr "" + +#: rbac/tree.py:59 +msgid "Accounts" +msgstr "账号管理" + +#: rbac/tree.py:76 +msgid "Session audits" +msgstr "会话审计" + +#: rbac/tree.py:104 +msgid "Cloud import" +msgstr "云同步" + +#: rbac/tree.py:109 +msgid "Backup account" +msgstr "备份账号" + +#: rbac/tree.py:114 +msgid "Gather account" +msgstr "收集账号" + +#: rbac/tree.py:119 +msgid "App change auth" +msgstr "应用改密" + +#: rbac/tree.py:124 +msgid "Asset change auth" +msgstr "资产改密" + +#: rbac/tree.py:252 +msgid "View" +msgstr "查看" + #: settings/api/alibaba_sms.py:31 settings/api/tencent_sms.py:35 msgid "test_phone is required" msgstr "测试手机号 该字段是必填项。" @@ -3178,10 +3257,6 @@ msgstr "成功导入 {} 个用户 ( 组织: {} )" msgid "Settings" msgstr "系统设置" -#: settings/models.py:196 -msgid "System setting" -msgstr "系统设置" - #: settings/serializers/auth/base.py:10 msgid "CAS Auth" msgstr "CAS 认证" @@ -4179,11 +4254,11 @@ msgid "" " " msgstr "" "\n" -" 您的账户已经过期,请联系管理员。 " +" 您的账号已经过期,请联系管理员。 " #: templates/_message.html:13 msgid "Your account will at" -msgstr "您的账户将于" +msgstr "您的账号将于" #: templates/_message.html:13 templates/_message.html:30 msgid "expired. " @@ -4465,19 +4540,19 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远 msgid "Filters" msgstr "过滤" -#: terminal/api/session.py:250 +#: terminal/api/session.py:211 msgid "Session does not exist: {}" msgstr "会话不存在: {}" -#: terminal/api/session.py:253 +#: terminal/api/session.py:214 msgid "Session is finished or the protocol not supported" msgstr "会话已经完成或协议不支持" -#: terminal/api/session.py:258 +#: terminal/api/session.py:219 msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: terminal/api/session.py:266 +#: terminal/api/session.py:227 msgid "User does not have permission" msgstr "用户没有权限" @@ -4511,7 +4586,7 @@ msgstr "测试成功" #: terminal/api/storage.py:125 msgid "Test failure: Account invalid" -msgstr "测试失败: 账户无效" +msgstr "测试失败: 账号无效" #: terminal/api/terminal.py:39 msgid "Have online sessions" @@ -4569,20 +4644,20 @@ msgstr "时间戳" msgid "Remote Address" msgstr "远端地址" -#: terminal/const.py:32 +#: terminal/const.py:33 msgid "Critical" msgstr "严重" -#: terminal/const.py:33 +#: terminal/const.py:34 msgid "High" msgstr "较高" -#: terminal/const.py:34 users/templates/users/reset_password.html:50 +#: terminal/const.py:35 users/templates/users/reset_password.html:50 #: users/templates/users/user_password_update.html:104 msgid "Normal" msgstr "正常" -#: terminal/const.py:35 +#: terminal/const.py:36 msgid "Offline" msgstr "离线" @@ -4598,43 +4673,47 @@ msgstr "存储无效" msgid "Command record" msgstr "命令记录" -#: terminal/models/replay.py:13 +#: terminal/models/replay.py:12 +msgid "Session replay" +msgstr "会话录像" + +#: terminal/models/replay.py:14 msgid "Can upload session replay" msgstr "可以上传会话录像" -#: terminal/models/replay.py:14 +#: terminal/models/replay.py:15 msgid "Can download session replay" msgstr "可以下载会话录像" -#: terminal/models/session.py:47 terminal/models/sharing.py:87 +#: terminal/models/session.py:48 terminal/models/sharing.py:87 msgid "Login from" msgstr "登录来源" -#: terminal/models/session.py:51 +#: terminal/models/session.py:52 msgid "Replay" msgstr "回放" -#: terminal/models/session.py:56 +#: terminal/models/session.py:57 msgid "Date end" msgstr "结束日期" -#: terminal/models/session.py:241 +#: terminal/models/session.py:242 msgid "Session record" msgstr "会话" -#: terminal/models/session.py:243 +#: terminal/models/session.py:244 msgid "Can monitor session" msgstr "可以监控会话" -#: terminal/models/session.py:244 +#: terminal/models/session.py:245 msgid "Can share session" msgstr "可以分享会话" -#: terminal/models/session.py:245 +#: terminal/models/session.py:246 msgid "Can terminate session" msgstr "可以中断会话" -#: terminal/models/session.py:246 +#: terminal/models/session.py:247 msgid "Can validate session action perm" msgstr "可以验证会话动作权限" @@ -4811,12 +4890,13 @@ msgstr "端点无效: 移除路径 `{}`" msgid "Bucket" msgstr "桶名称" -#: terminal/serializers/storage.py:34 users/models/user.py:571 +#: terminal/serializers/storage.py:34 users/models/user.py:584 msgid "Secret key" msgstr "密钥" #: terminal/serializers/storage.py:39 terminal/serializers/storage.py:51 #: terminal/serializers/storage.py:81 terminal/serializers/storage.py:91 +#: terminal/serializers/storage.py:99 msgid "Endpoint" msgstr "端点" @@ -4824,43 +4904,43 @@ msgstr "端点" msgid "Region" msgstr "地域" -#: terminal/serializers/storage.py:101 +#: terminal/serializers/storage.py:110 msgid "Container name" msgstr "容器名称" -#: terminal/serializers/storage.py:103 +#: terminal/serializers/storage.py:112 msgid "Account name" -msgstr "账户名称" +msgstr "账号名称" -#: terminal/serializers/storage.py:104 +#: terminal/serializers/storage.py:113 msgid "Account key" -msgstr "账户密钥" +msgstr "账号密钥" -#: terminal/serializers/storage.py:107 +#: terminal/serializers/storage.py:116 msgid "Endpoint suffix" msgstr "端点后缀" -#: terminal/serializers/storage.py:128 +#: terminal/serializers/storage.py:138 msgid "The address format is incorrect" msgstr "地址格式不正确" -#: terminal/serializers/storage.py:135 +#: terminal/serializers/storage.py:145 msgid "Host invalid" msgstr "主机无效" -#: terminal/serializers/storage.py:138 +#: terminal/serializers/storage.py:148 msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:154 +#: terminal/serializers/storage.py:164 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:156 +#: terminal/serializers/storage.py:166 msgid "Doc type" msgstr "文档类型" -#: terminal/serializers/storage.py:158 +#: terminal/serializers/storage.py:168 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" @@ -5087,7 +5167,7 @@ msgid "Body" msgstr "内容" #: tickets/models/flow.py:19 tickets/models/flow.py:61 -#: tickets/models/ticket.py:29 +#: tickets/models/ticket.py:30 msgid "Approve level" msgstr "审批级别" @@ -5107,50 +5187,54 @@ msgstr "工单批准信息" msgid "Ticket flow" msgstr "工单流程" -#: tickets/models/ticket.py:34 +#: tickets/models/ticket.py:35 msgid "Ticket step" msgstr "工单步骤" -#: tickets/models/ticket.py:45 +#: tickets/models/ticket.py:46 msgid "Ticket assignee" msgstr "工单受理人" -#: tickets/models/ticket.py:52 +#: tickets/models/ticket.py:128 msgid "Title" msgstr "标题" -#: tickets/models/ticket.py:60 +#: tickets/models/ticket.py:136 msgid "State" msgstr "状态" -#: tickets/models/ticket.py:68 +#: tickets/models/ticket.py:144 msgid "Approval step" msgstr "审批步骤" -#: tickets/models/ticket.py:73 +#: tickets/models/ticket.py:149 msgid "Applicant" msgstr "申请人" -#: tickets/models/ticket.py:75 +#: tickets/models/ticket.py:151 msgid "Applicant display" msgstr "申请人名称" -#: tickets/models/ticket.py:76 +#: tickets/models/ticket.py:152 msgid "Process" msgstr "流程" -#: tickets/models/ticket.py:81 +#: tickets/models/ticket.py:157 msgid "TicketFlow" msgstr "工单流程" -#: tickets/models/ticket.py:87 +#: tickets/models/ticket.py:163 msgid "Ticket" msgstr "工单管理" -#: tickets/models/ticket.py:301 +#: tickets/models/ticket.py:311 msgid "Please try again" msgstr "请再次尝试" +#: tickets/models/ticket.py:319 +msgid "Super ticket" +msgstr "超级工单" + #: tickets/notifications.py:57 msgid "Your has a new ticket, applicant - {}" msgstr "你有一个新的工单, 申请人 - {}" @@ -5167,6 +5251,10 @@ msgstr "你的工单已被处理, 处理人 - {}" msgid "Ticket has processed - {} ({})" msgstr "你的工单已被处理, 处理人 - {} ({})" +#: tickets/serializers/super_ticket.py:11 +msgid "Processor" +msgstr "处理人" + #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:19 msgid "Apply name" @@ -5298,7 +5386,7 @@ msgstr "当前组织已存在该类型" msgid "Click here to review" msgstr "点击查看" -#: users/api/user.py:179 +#: users/api/user.py:175 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置" @@ -5353,7 +5441,7 @@ msgid "" "and key sensitive information properly. (for example: setting complex " "password, enabling MFA)" msgstr "" -"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" +"为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息;(如:" "设置复杂密码,并启用 MFA 多因子认证)" #: users/forms/profile.py:76 @@ -5405,7 +5493,7 @@ msgstr "不能和原来的密钥相同" msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/profile.py:160 users/models/user.py:568 +#: users/forms/profile.py:160 users/models/user.py:581 #: users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "SSH公钥" @@ -5418,55 +5506,55 @@ msgstr "强制启用" msgid "Local" msgstr "数据库" -#: users/models/user.py:549 users/serializers/user.py:135 +#: users/models/user.py:562 users/serializers/user.py:134 msgid "Is service account" msgstr "服务账号" -#: users/models/user.py:551 +#: users/models/user.py:564 msgid "Avatar" msgstr "头像" -#: users/models/user.py:554 +#: users/models/user.py:567 msgid "Wechat" msgstr "微信" -#: users/models/user.py:565 +#: users/models/user.py:578 msgid "Private key" msgstr "ssh私钥" -#: users/models/user.py:587 +#: users/models/user.py:600 msgid "Source" msgstr "来源" -#: users/models/user.py:591 +#: users/models/user.py:604 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:594 +#: users/models/user.py:607 msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:759 +#: users/models/user.py:771 msgid "Can invite user" msgstr "可以邀请用户" -#: users/models/user.py:760 +#: users/models/user.py:772 msgid "Can remove user" msgstr "可以移除用户" -#: users/models/user.py:761 +#: users/models/user.py:773 msgid "Can match user" msgstr "可以匹配用户" -#: users/models/user.py:770 +#: users/models/user.py:782 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:773 +#: users/models/user.py:785 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models/user.py:798 +#: users/models/user.py:810 msgid "User password history" msgstr "用户密码历史" @@ -5517,97 +5605,97 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:141 users/serializers/user.py:133 +#: users/serializers/profile.py:141 users/serializers/user.py:132 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:25 users/serializers/user.py:31 +#: users/serializers/user.py:24 users/serializers/user.py:30 msgid "System roles" msgstr "系统角色" -#: users/serializers/user.py:29 users/serializers/user.py:32 +#: users/serializers/user.py:28 users/serializers/user.py:31 msgid "Org roles" msgstr "组织角色" -#: users/serializers/user.py:75 +#: users/serializers/user.py:74 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:22 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:77 +#: users/serializers/user.py:76 msgid "MFA enabled" msgstr "MFA" -#: users/serializers/user.py:78 +#: users/serializers/user.py:77 msgid "MFA force enabled" msgstr "强制 MFA" -#: users/serializers/user.py:80 +#: users/serializers/user.py:79 msgid "MFA level display" msgstr "MFA 等级名称" -#: users/serializers/user.py:82 +#: users/serializers/user.py:81 msgid "Login blocked" msgstr "登录被阻塞" -#: users/serializers/user.py:85 +#: users/serializers/user.py:84 msgid "Can public key authentication" msgstr "能否公钥认证" -#: users/serializers/user.py:137 +#: users/serializers/user.py:136 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:139 +#: users/serializers/user.py:138 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:140 +#: users/serializers/user.py:139 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:141 +#: users/serializers/user.py:140 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:142 +#: users/serializers/user.py:141 msgid "Super role name" msgstr "超级角色名称" -#: users/serializers/user.py:143 +#: users/serializers/user.py:142 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:145 +#: users/serializers/user.py:144 msgid "Is wecom bound" msgstr "是否绑定了企业微信" -#: users/serializers/user.py:146 +#: users/serializers/user.py:145 msgid "Is dingtalk bound" msgstr "是否绑定了钉钉" -#: users/serializers/user.py:147 +#: users/serializers/user.py:146 msgid "Is feishu bound" msgstr "是否绑定了飞书" -#: users/serializers/user.py:148 +#: users/serializers/user.py:147 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" -#: users/serializers/user.py:150 +#: users/serializers/user.py:149 msgid "System role name" msgstr "系统角色名称" -#: users/serializers/user.py:246 +#: users/serializers/user.py:235 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:247 +#: users/serializers/user.py:236 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:280 +#: users/serializers/user.py:269 msgid "name not unique" msgstr "名称重复" @@ -5976,7 +6064,7 @@ msgstr "应用" #: xpack/plugins/change_auth_plan/models/app.py:156 msgid "Application change auth plan task" -msgstr "用用改密计划任务" +msgstr "应用改密计划任务" #: xpack/plugins/change_auth_plan/models/app.py:180 #: xpack/plugins/change_auth_plan/models/asset.py:263 @@ -6220,13 +6308,10 @@ msgstr "云管中心" msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:39 -msgid "Cloud account" -msgstr "云账号" - -#: xpack/plugins/cloud/models.py:82 xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/models.py:39 xpack/plugins/cloud/models.py:82 +#: xpack/plugins/cloud/serializers/task.py:66 msgid "Account" -msgstr "账户" +msgstr "账号" #: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" @@ -6483,7 +6568,7 @@ msgstr "用户域" #: xpack/plugins/cloud/serializers/account_attrs.py:113 msgid "Service account key" -msgstr "账户密钥" +msgstr "服务账号密钥" #: xpack/plugins/cloud/serializers/account_attrs.py:114 msgid "The file is in JSON format" @@ -6517,7 +6602,7 @@ msgstr "定时执行" #: xpack/plugins/cloud/utils.py:68 msgid "Account unavailable" -msgstr "账户无效" +msgstr "账号无效" #: xpack/plugins/gathered_user/meta.py:11 msgid "Gathered user" @@ -6603,6 +6688,9 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "AppAsset" +#~ msgstr "资产管理" + #~ msgid "User and Organization" #~ msgstr "用户与组织" @@ -6660,7 +6748,7 @@ msgstr "社区版" #~ msgstr "XPack" #~ msgid "Account list" -#~ msgstr "账户列表" +#~ msgstr "账号列表" #~ msgid "Sync instance" #~ msgstr "同步实例" diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py index 1c64497a1..306f2b55b 100644 --- a/apps/notifications/apps.py +++ b/apps/notifications/apps.py @@ -7,6 +7,6 @@ class NotificationsConfig(AppConfig): verbose_name = _('Notifications') def ready(self): - from . import signals_handler + from . import signal_handlers from . import notifications super().ready() diff --git a/apps/notifications/signals_handler.py b/apps/notifications/signal_handlers.py similarity index 100% rename from apps/notifications/signals_handler.py rename to apps/notifications/signal_handlers.py diff --git a/apps/notifications/ws.py b/apps/notifications/ws.py index e3d48f79d..391bb659d 100644 --- a/apps/notifications/ws.py +++ b/apps/notifications/ws.py @@ -5,7 +5,7 @@ from channels.generic.websocket import JsonWebsocketConsumer from common.utils import get_logger from common.db.utils import safe_db_connection from .site_msg import SiteMessageUtil -from .signals_handler import new_site_msg_chan +from .signal_handlers import new_site_msg_chan logger = get_logger(__name__) diff --git a/apps/ops/apps.py b/apps/ops/apps.py index 43496ebf2..819a23002 100644 --- a/apps/ops/apps.py +++ b/apps/ops/apps.py @@ -6,13 +6,13 @@ from django.apps import AppConfig class OpsConfig(AppConfig): name = 'ops' - verbose_name = _('Operations') + verbose_name = _('App ops') def ready(self): from orgs.models import Organization from orgs.utils import set_current_org set_current_org(Organization.root()) from .celery import signal_handler - from . import signals_handler + from . import signal_handlers from . import notifications super().ready() diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py index e76c2037d..86c7ab188 100644 --- a/apps/ops/notifications.py +++ b/apps/ops/notifications.py @@ -13,7 +13,7 @@ __all__ = ('ServerPerformanceMessage', 'ServerPerformanceCheckUtil') class ServerPerformanceMessage(SystemMessage): category = 'Operations' - category_label = _('Operations') + category_label = _('App ops') message_type_label = _('Server performance') def __init__(self, terms_with_errors): diff --git a/apps/ops/signals_handler.py b/apps/ops/signal_handlers.py similarity index 100% rename from apps/ops/signals_handler.py rename to apps/ops/signal_handlers.py diff --git a/apps/orgs/apps.py b/apps/orgs/apps.py index 872fb8eb4..14a48203c 100644 --- a/apps/orgs/apps.py +++ b/apps/orgs/apps.py @@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _ class OrgsConfig(AppConfig): name = 'orgs' - verbose_name = _('Organizations') + verbose_name = _('App organizations') def ready(self): - from . import signals_handler + from . import signal_handlers diff --git a/apps/orgs/signals_handler/__init__.py b/apps/orgs/signal_handlers/__init__.py similarity index 100% rename from apps/orgs/signals_handler/__init__.py rename to apps/orgs/signal_handlers/__init__.py diff --git a/apps/orgs/signals_handler/cache.py b/apps/orgs/signal_handlers/cache.py similarity index 100% rename from apps/orgs/signals_handler/cache.py rename to apps/orgs/signal_handlers/cache.py diff --git a/apps/orgs/signals_handler/common.py b/apps/orgs/signal_handlers/common.py similarity index 100% rename from apps/orgs/signals_handler/common.py rename to apps/orgs/signal_handlers/common.py diff --git a/apps/perms/apps.py b/apps/perms/apps.py index 664765029..432c194cd 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -6,9 +6,9 @@ from django.utils.translation import ugettext_lazy as _ class PermsConfig(AppConfig): name = 'perms' - verbose_name = _('Permissions') + verbose_name = _('App permissions') def ready(self): super().ready() - from . import signals_handler + from . import signal_handlers from . import notifications diff --git a/apps/perms/serializers/application/permission.py b/apps/perms/serializers/application/permission.py index f3e6443a0..c7ebdf769 100644 --- a/apps/perms/serializers/application/permission.py +++ b/apps/perms/serializers/application/permission.py @@ -43,7 +43,7 @@ class ApplicationPermissionSerializer(BasePermissionSerializer): 'users_amount': {'label': _('Users amount')}, 'user_groups_amount': {'label': _('User groups amount')}, 'system_users_amount': {'label': _('System users amount')}, - 'applications_amount': {'label': _('Applications amount')}, + 'applications_amount': {'label': _('Apps amount')}, } def _filter_actions_choices(self, choices): diff --git a/apps/perms/signals_handler/__init__.py b/apps/perms/signal_handlers/__init__.py similarity index 100% rename from apps/perms/signals_handler/__init__.py rename to apps/perms/signal_handlers/__init__.py diff --git a/apps/perms/signals_handler/app_permission.py b/apps/perms/signal_handlers/app_permission.py similarity index 100% rename from apps/perms/signals_handler/app_permission.py rename to apps/perms/signal_handlers/app_permission.py diff --git a/apps/perms/signals_handler/asset_permission.py b/apps/perms/signal_handlers/asset_permission.py similarity index 100% rename from apps/perms/signals_handler/asset_permission.py rename to apps/perms/signal_handlers/asset_permission.py diff --git a/apps/perms/signals_handler/refresh_perms.py b/apps/perms/signal_handlers/refresh_perms.py similarity index 100% rename from apps/perms/signals_handler/refresh_perms.py rename to apps/perms/signal_handlers/refresh_perms.py diff --git a/apps/rbac/api/rolebinding.py b/apps/rbac/api/rolebinding.py index 7d31d6415..3e9165323 100644 --- a/apps/rbac/api/rolebinding.py +++ b/apps/rbac/api/rolebinding.py @@ -1,9 +1,10 @@ - +from django.utils.translation import ugettext as _ from django.db.models import F, Value from django.db.models.functions import Concat from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org +from common.exceptions import JMSException from .. import serializers from ..models import RoleBinding, SystemRoleBinding, OrgRoleBinding @@ -22,7 +23,7 @@ class RoleBindingViewSet(OrgBulkModelViewSet): ] def get_queryset(self): - queryset = super().get_queryset()\ + queryset = super().get_queryset() \ .prefetch_related('user', 'role') \ .annotate( user_display=Concat( @@ -38,6 +39,17 @@ class SystemRoleBindingViewSet(RoleBindingViewSet): model = SystemRoleBinding serializer_class = serializers.SystemRoleBindingSerializer + def perform_destroy(self, instance): + user = instance.user + role_qs = self.model.objects.filter(user=user) + if role_qs.count() == 1: + msg = _('{} at least one system role').format(user) + raise JMSException( + code='system_role_delete_error', + detail=msg + ) + super().perform_destroy(instance) + class OrgRoleBindingViewSet(RoleBindingViewSet): model = OrgRoleBinding diff --git a/apps/rbac/const.py b/apps/rbac/const.py index b7c76a261..084b82cf1 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -16,10 +16,12 @@ exclude_permissions = ( ('contenttypes', '*', '*', '*'), ('django_cas_ng', '*', '*', '*'), ('django_celery_beat', '*', '*', '*'), + ('jms_oidc_rp', '*', '*', '*'), ('admin', '*', '*', '*'), ('sessions', '*', '*', '*'), ('notifications', '*', '*', '*'), + ('applications', 'applicationuser', '*', '*'), ('applications', 'historicalaccount', '*', '*'), ('applications', 'databaseapp', '*', '*'), ('applications', 'k8sapp', '*', '*'), @@ -51,9 +53,15 @@ exclude_permissions = ( ('audits', 'userloginlog', 'change,delete,change', 'userloginlog'), ('audits', 'ftplog', 'change,delete', 'ftplog'), ('terminal', 'session', 'delete', 'session'), + ('terminal', 'session', 'delete,change', 'command'), ('tickets', 'ticket', '*', '*'), ('users', 'userpasswordhistory', '*', '*'), - ('xpack', 'interface', 'add,delete', 'interface'), + ('xpack', 'interface', '*', '*'), + ('xpack', 'license', '*', '*'), + ('common', 'permission', 'add,delete,view,change', 'permission'), + ('terminal', 'command', 'delete,change', 'command'), + ('terminal', 'sessionjoinrecord', 'delete', 'sessionjoinrecord'), + ('terminal', 'sessionreplay', 'delete', 'sessionreplay'), ) diff --git a/apps/rbac/models/permission.py b/apps/rbac/models/permission.py index 0ba84f315..4b506168b 100644 --- a/apps/rbac/models/permission.py +++ b/apps/rbac/models/permission.py @@ -1,12 +1,8 @@ -from typing import Callable - -from django.db.models import F, Count, Q -from django.apps import apps -from django.utils.translation import ugettext_lazy as _, ugettext +from django.db.models import Q +from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import Permission as DjangoPermission from django.contrib.auth.models import ContentType as DjangoContentType -from common.tree import TreeNode from .. import const Scope = const.Scope @@ -19,190 +15,6 @@ class ContentType(DjangoContentType): proxy = True -class PermissionTreeUtil: - get_permissions: Callable - - def __init__(self, permissions, scope, check_disabled=False): - self.permissions = self.prefetch_permissions(permissions) - self.all_permissions = self.prefetch_permissions( - Permission.get_permissions(scope) - ) - self.check_disabled = check_disabled - - @staticmethod - def prefetch_permissions(perms): - return perms.select_related('content_type') \ - .annotate(app=F('content_type__app_label')) \ - .annotate(model=F('content_type__model')) - - def _create_apps_tree_nodes(self): - app_counts = self.all_permissions.values('app')\ - .order_by('app').annotate(count=Count('app')) - app_checked_counts = self.permissions.values('app')\ - .order_by('app').annotate(count=Count('app')) - app_checked_counts_mapper = { - i['app']: i['count'] - for i in app_checked_counts - } - all_apps = apps.get_app_configs() - apps_name_mapper = { - app.name: app.verbose_name - for app in all_apps if hasattr(app, 'verbose_name') - } - nodes = [] - - for i in app_counts: - app = i['app'] - total_counts = i['count'] - check_counts = app_checked_counts_mapper.get(app, 0) - name = apps_name_mapper.get(app, app) - full_name = f'{name}({check_counts}/{total_counts})' - - node = TreeNode(**{ - 'id': app, - 'name': full_name, - 'title': name, - 'pId': '$ROOT$', - 'isParent': True, - 'open': False, - 'chkDisabled': self.check_disabled, - 'checked': total_counts == check_counts, - 'iconSkin': '', - 'meta': { - 'type': 'app', - } - }) - nodes.append(node) - return nodes - - def _create_models_tree_nodes(self): - content_types = ContentType.objects.all() - - model_counts = self.all_permissions \ - .values('model', 'app', 'content_type') \ - .order_by('content_type') \ - .annotate(count=Count('content_type')) - model_check_counts = self.permissions \ - .values('content_type', 'model') \ - .order_by('content_type') \ - .annotate(count=Count('content_type')) - model_counts_mapper = { - i['content_type']: i['count'] - for i in model_counts - } - model_check_counts_mapper = { - i['content_type']: i['count'] - for i in model_check_counts - } - - nodes = [] - for ct in content_types: - total_counts = model_counts_mapper.get(ct.id, 0) - if total_counts == 0: - continue - check_counts = model_check_counts_mapper.get(ct.id, 0) - model_id = f'{ct.app_label}_{ct.model}' - name = f'{ct.name}({check_counts}/{total_counts})' - node = TreeNode(**{ - 'id': model_id, - 'name': name, - 'title': name, - 'pId': ct.app_label, - 'chkDisabled': self.check_disabled, - 'isParent': True, - 'open': False, - 'checked': total_counts == check_counts, - 'meta': { - 'type': 'model', - } - }) - nodes.append(node) - return nodes - - @staticmethod - def _get_permission_name(p, content_types_name_mapper): - code_name = p.codename - action_mapper = { - 'add': ugettext('Create'), - 'view': ugettext('View'), - 'change': ugettext('Update'), - 'delete': ugettext('Delete') - } - name = '' - ct = '' - if 'add_' in p.codename: - name = action_mapper['add'] - ct = code_name.replace('add_', '') - elif 'view_' in p.codename: - name = action_mapper['view'] - ct = code_name.replace('view_', '') - elif 'change_' in p.codename: - name = action_mapper['change'] - ct = code_name.replace('change_', '') - elif 'delete' in code_name: - name = action_mapper['delete'] - ct = code_name.replace('delete_', '') - - if ct in content_types_name_mapper: - name += content_types_name_mapper[ct] - else: - name = p.name - return name - - def _create_perms_tree_nodes(self): - permissions_id = self.permissions.values_list('id', flat=True) - nodes = [] - content_types = ContentType.objects.all() - content_types_name_mapper = {ct.model: ct.name for ct in content_types} - for p in self.all_permissions: - model_id = f'{p.app}_{p.model}' - name = self._get_permission_name(p, content_types_name_mapper) - - node = TreeNode(**{ - 'id': p.id, - 'name': name + '({})'.format(p.app_label_codename), - 'title': p.name, - 'pId': model_id, - 'isParent': False, - 'chkDisabled': self.check_disabled, - 'iconSkin': 'file', - 'checked': p.id in permissions_id, - 'open': False, - 'meta': { - 'type': 'perm', - } - }) - nodes.append(node) - return nodes - - def _create_root_tree_node(self): - total_counts = self.all_permissions.count() - check_counts = self.permissions.count() - node = TreeNode(**{ - 'id': '$ROOT$', - 'name': f'所有权限({check_counts}/{total_counts})', - 'title': '所有权限', - 'pId': '', - 'chkDisabled': self.check_disabled, - 'isParent': True, - 'checked': total_counts == check_counts, - 'open': True, - 'meta': { - 'type': 'root', - } - }) - return node - - def create_tree_nodes(self): - nodes = [self._create_root_tree_node()] - apps_nodes = self._create_apps_tree_nodes() - models_nodes = self._create_models_tree_nodes() - perms_nodes = self._create_perms_tree_nodes() - - nodes += apps_nodes + models_nodes + perms_nodes - return nodes - - class Permission(DjangoPermission): """ 权限类 """ class Meta: @@ -265,6 +77,7 @@ class Permission(DjangoPermission): @staticmethod def create_tree_nodes(permissions, scope, check_disabled=False): + from ..tree import PermissionTreeUtil util = PermissionTreeUtil(permissions, scope, check_disabled) return util.create_tree_nodes() diff --git a/apps/rbac/permissions.py b/apps/rbac/permissions.py index 717f79848..43210818f 100644 --- a/apps/rbac/permissions.py +++ b/apps/rbac/permissions.py @@ -16,6 +16,7 @@ class RBACPermission(permissions.DjangoModelPermissions): ('bulk_update', '%(app_label)s.change_%(model_name)s'), ('partial_bulk_update', '%(app_label)s.change_%(model_name)s'), ('bulk_destroy', '%(app_label)s.delete_%(model_name)s'), + ('render_to_json', '%(app_label)s.add_%(model_name)s'), ('metadata', ''), ('GET', '%(app_label)s.view_%(model_name)s'), ('OPTIONS', ''), diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py new file mode 100644 index 000000000..c5cdfde32 --- /dev/null +++ b/apps/rbac/tree.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +from collections import defaultdict +from typing import Callable + +from django.utils.translation import gettext_lazy as _, gettext +from django.conf import settings +from django.apps import apps +from django.db.models import F, Count +from django.utils.translation import ugettext + +from .models import Permission, ContentType +from common.tree import TreeNode + +root_node_data = { + 'id': '$ROOT$', + 'name': _('All permissions'), + 'title': _('All permissions'), + 'pId': '', +} + +view_nodes_data = [ + { + 'id': 'view_console', + 'name': _('Console view'), + }, + { + 'id': 'view_workspace', + 'name': _('Workspace view'), + }, + { + 'id': 'view_audit', + 'name': _('Audit view'), + }, + { + 'id': 'view_setting', + 'name': _('System setting'), + }, + { + 'id': 'view_other', + 'name': _('Other'), + } +] + +app_nodes_data = [ + { + 'id': 'users', + 'view': 'view_console', + }, + { + 'id': 'assets', + 'view': 'view_console', + }, + { + 'id': 'applications', + 'view': 'view_console', + }, + { + 'id': 'accounts', + 'name': _('Accounts'), + 'view': 'view_console', + }, + { + 'id': 'perms', + 'view': 'view_console', + }, + { + 'id': 'acls', + 'view': 'view_console', + }, + { + 'id': 'ops', + 'view': 'view_console', + }, + { + 'id': 'terminal', + 'name': _('Session audits'), + 'view': 'view_audit', + }, + { + 'id': 'audits', + 'view': 'view_audit', + }, + { + 'id': 'rbac', + 'view': 'view_console' + }, + { + 'id': 'settings', + 'view': 'view_setting' + }, + { + 'id': 'tickets', + 'view': 'view_other', + }, + { + 'id': 'authentication', + 'view': 'view_other' + } +] + +extra_nodes_data = [ + { + "id": "cloud_import", + "name": _("Cloud import"), + "pId": "assets", + }, + { + "id": "backup_account_node", + "name": _("Backup account"), + "pId": "accounts" + }, + { + "id": "gather_account_node", + "name": _("Gather account"), + "pId": "accounts", + }, + { + "id": "app_change_plan_node", + "name": _("App change auth"), + "pId": "accounts" + }, + { + "id": "asset_change_plan_node", + "name": _("Asset change auth"), + "pId": "accounts" + }, + { + "id": "terminal_node", + "name": _("Terminal"), + "pId": "view_setting" + } +] + +special_pid_mapper = { + 'common.permission': 'view_other', + "assets.authbook": "accounts", + "applications.account": "accounts", + 'xpack.account': 'cloud_import', + 'xpack.syncinstancedetail': 'cloud_import', + 'xpack.syncinstancetask': 'cloud_import', + 'xpack.syncinstancetaskexecution': 'cloud_import', + 'assets.accountbackupplan': "backup_account_node", + 'assets.accountbackupplanexecution': "backup_account_node", + 'xpack.applicationchangeauthplan': 'app_change_plan_node', + 'xpack.applicationchangeauthplanexecution': 'app_change_plan_node', + 'xpack.applicationchangeauthplantask': 'app_change_plan_node', + 'xpack.changeauthplan': 'asset_change_plan_node', + 'xpack.changeauthplanexecution': 'asset_change_plan_node', + 'xpack.changeauthplantask': 'asset_change_plan_node', + "assets.gathereduser": "gather_account_node", + 'xpack.gatherusertask': 'gather_account_node', + 'xpack.gatherusertaskexecution': 'gather_account_node', + 'orgs.organization': 'view_setting', + 'settings.setting': 'view_setting', + 'terminal.terminal': 'terminal_node', + 'terminal.commandstorage': 'terminal_node', + 'terminal.replaystorage': 'terminal_node', + 'terminal.status': 'terminal_node', + 'terminal.task': 'terminal_node', +} + + +class PermissionTreeUtil: + get_permissions: Callable + + def __init__(self, permissions, scope, check_disabled=False): + self.permissions = self.prefetch_permissions(permissions) + self.all_permissions = self.prefetch_permissions( + Permission.get_permissions(scope) + ) + self.check_disabled = check_disabled + self.total_counts = defaultdict(int) + self.checked_counts = defaultdict(int) + + @staticmethod + def prefetch_permissions(perms): + return perms.select_related('content_type') \ + .annotate(app=F('content_type__app_label')) \ + .annotate(model=F('content_type__model')) + + def create_apps_nodes(self): + all_apps = apps.get_app_configs() + apps_name_mapper = { + app.name: app.verbose_name + for app in all_apps if hasattr(app, 'verbose_name') + } + nodes = [] + + for i in app_nodes_data: + app = i['id'] + name = i.get('name') or apps_name_mapper.get(app, app) + view = i.get('view', 'other') + + app_data = { + 'id': app, + 'name': name, + 'pId': view, + } + total_count = self.total_counts[app] + checked_count = self.checked_counts[app] + self.total_counts[view] += total_count + self.checked_counts[view] += checked_count + node = self._create_node( + app_data, total_count, checked_count, + 'app', is_open=False + ) + nodes.append(node) + return nodes + + def _get_model_counts_mapper(self): + model_counts = self.all_permissions \ + .values('model', 'app', 'content_type') \ + .order_by('content_type') \ + .annotate(count=Count('content_type')) + model_check_counts = self.permissions \ + .values('content_type', 'model') \ + .order_by('content_type') \ + .annotate(count=Count('content_type')) + model_counts_mapper = { + i['content_type']: i['count'] + for i in model_counts + } + model_check_counts_mapper = { + i['content_type']: i['count'] + for i in model_check_counts + } + return model_counts_mapper, model_check_counts_mapper + + def _create_models_nodes(self): + content_types = ContentType.objects.all() + total_counts_mapper, checked_counts_mapper = self._get_model_counts_mapper() + + nodes = [] + for ct in content_types: + total_count = total_counts_mapper.get(ct.id, 0) + checked_count = checked_counts_mapper.get(ct.id, 0) + if total_count == 0: + continue + + model_id = '{}.{}'.format(ct.app_label, ct.model) + app = ct.app_label + if special_pid_mapper.get(model_id): + app = special_pid_mapper[model_id] + + self.total_counts[app] += total_count + self.checked_counts[app] += checked_count + + name = f'{ct.name}' + node = self._create_node({ + 'id': model_id, + 'name': name, + 'pId': app, + }, total_count, checked_count, 'model', is_open=False) + nodes.append(node) + return nodes + + @staticmethod + def _get_permission_name(p, content_types_name_mapper): + code_name = p.codename + action_mapper = { + 'add': ugettext('Create'), + 'view': ugettext('View'), + 'change': ugettext('Update'), + 'delete': ugettext('Delete') + } + name = '' + ct = '' + if 'add_' in p.codename: + name = action_mapper['add'] + ct = code_name.replace('add_', '') + elif 'view_' in p.codename: + name = action_mapper['view'] + ct = code_name.replace('view_', '') + elif 'change_' in p.codename: + name = action_mapper['change'] + ct = code_name.replace('change_', '') + elif 'delete' in code_name: + name = action_mapper['delete'] + ct = code_name.replace('delete_', '') + + if ct in content_types_name_mapper: + name += content_types_name_mapper[ct] + else: + name = gettext(p.name) + name = name.replace('Can ', '').replace('可以', '') + return name + + def _create_perms_nodes(self): + permissions_id = self.permissions.values_list('id', flat=True) + nodes = [] + content_types = ContentType.objects.all() + content_types_name_mapper = {ct.model: ct.name for ct in content_types} + + for p in self.all_permissions: + model_id = f'{p.app}.{p.model}' + name = self._get_permission_name(p, content_types_name_mapper) + if settings.DEBUG: + name += '({})'.format(p.app_label_codename) + + node = TreeNode(**{ + 'id': p.id, + 'name': name, + 'title': p.name, + 'pId': model_id, + 'isParent': False, + 'chkDisabled': self.check_disabled, + 'iconSkin': 'file', + 'checked': p.id in permissions_id, + 'open': False, + 'meta': { + 'type': 'perm', + } + }) + nodes.append(node) + return nodes + + def _create_node(self, data, total_count, checked_count, tp, + is_parent=True, is_open=True, icon='', checked=None): + assert data.get('id') + assert data.get('name') + assert data.get('pId') is not None + if checked is None: + checked = total_count == checked_count + node_data = { + 'isParent': is_parent, + 'iconSkin': icon, + 'open': is_open, + 'chkDisabled': self.check_disabled, + 'checked': checked, + 'meta': { + 'type': tp, + }, + **data + } + if not node_data.get('title'): + node_data['title'] = node_data['name'] + node = TreeNode(**node_data) + node.name += f'({checked_count}/{total_count})' + return node + + def _create_root_tree_node(self): + total_count = self.all_permissions.count() + checked_count = self.permissions.count() + node = self._create_node(root_node_data, total_count, checked_count, 'root') + return node + + def _create_views_node(self): + nodes = [] + for view_data in view_nodes_data: + view = view_data['id'] + data = { + **view_data, + 'pId': '$ROOT$', + } + total_count = self.total_counts[view] + checked_count = self.checked_counts[view] + node = self._create_node(data, total_count, checked_count, 'view') + nodes.append(node) + return nodes + + def _create_extra_nodes(self): + nodes = [] + for data in extra_nodes_data: + i = data['id'] + pid = data['pId'] + checked_count = self.checked_counts[i] + total_count = self.total_counts[i] + self.total_counts[pid] += total_count + self.checked_counts[pid] += checked_count + node = self._create_node( + data, total_count, checked_count, + 'extra', is_open=False + ) + nodes.append(node) + + return nodes + + def create_tree_nodes(self): + nodes = [self._create_root_tree_node()] + perms_nodes = self._create_perms_nodes() + models_nodes = self._create_models_nodes() + apps_nodes = self.create_apps_nodes() + views_nodes = self._create_views_node() + extra_nodes = self._create_extra_nodes() + + nodes += views_nodes + apps_nodes + models_nodes + perms_nodes + extra_nodes + return nodes diff --git a/apps/settings/apps.py b/apps/settings/apps.py index bcb1b7693..8bef81db3 100644 --- a/apps/settings/apps.py +++ b/apps/settings/apps.py @@ -7,4 +7,4 @@ class SettingsConfig(AppConfig): verbose_name = _('Settings') def ready(self): - from . import signals_handler + from . import signal_handlers diff --git a/apps/settings/signals_handler.py b/apps/settings/signal_handlers.py similarity index 100% rename from apps/settings/signals_handler.py rename to apps/settings/signal_handlers.py diff --git a/apps/terminal/apps.py b/apps/terminal/apps.py index ca67ab296..fa46ca9e2 100644 --- a/apps/terminal/apps.py +++ b/apps/terminal/apps.py @@ -9,6 +9,6 @@ class TerminalConfig(AppConfig): verbose_name = _('Terminals') def ready(self): - from . import signals_handler + from . import signal_handlers from . import notifications return super().ready() diff --git a/apps/terminal/migrations/0047_auto_20220302_1951.py b/apps/terminal/migrations/0047_auto_20220302_1951.py new file mode 100644 index 000000000..a67b260b8 --- /dev/null +++ b/apps/terminal/migrations/0047_auto_20220302_1951.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.14 on 2022-03-02 11:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0046_auto_20220228_1744'), + ] + + operations = [ + migrations.AlterModelOptions( + name='sessionreplay', + options={'permissions': [('upload_sessionreplay', 'Can upload session replay'), ('download_sessionreplay', 'Can download session replay')], 'verbose_name': 'Session replay'}, + ), + ] diff --git a/apps/terminal/models/replay.py b/apps/terminal/models/replay.py index adba14948..5f3e372af 100644 --- a/apps/terminal/models/replay.py +++ b/apps/terminal/models/replay.py @@ -9,6 +9,7 @@ class SessionReplay(CommonModelMixin): session = models.ForeignKey(Session, on_delete=models.CASCADE, verbose_name=_("Session")) class Meta: + verbose_name = _("Session replay") permissions = [ ('upload_sessionreplay', _("Can upload session replay")), ('download_sessionreplay', _("Can download session replay")), diff --git a/apps/terminal/signals_handler.py b/apps/terminal/signal_handlers.py similarity index 100% rename from apps/terminal/signals_handler.py rename to apps/terminal/signal_handlers.py diff --git a/apps/tickets/api/super_ticket.py b/apps/tickets/api/super_ticket.py index bf96a6cc9..ea186bd1b 100644 --- a/apps/tickets/api/super_ticket.py +++ b/apps/tickets/api/super_ticket.py @@ -2,7 +2,7 @@ from rest_framework.generics import RetrieveDestroyAPIView from orgs.utils import tmp_to_root_org from ..serializers import SuperTicketSerializer -from ..models import SuperTicket +from ..models import Ticket __all__ = ['SuperTicketStatusAPI'] @@ -10,11 +10,14 @@ __all__ = ['SuperTicketStatusAPI'] class SuperTicketStatusAPI(RetrieveDestroyAPIView): serializer_class = SuperTicketSerializer + rbac_perms = { + 'GET': 'tickets.view_superticket', + 'DELETE': 'tickets.change_superticket' + } def get_queryset(self): with tmp_to_root_org(): - return SuperTicket.objects.all() + return Ticket.objects.all() def perform_destroy(self, instance): - ticket = self.get_object() - ticket.close(processor=ticket.applicant) + instance.close(processor=instance.applicant) diff --git a/apps/tickets/apps.py b/apps/tickets/apps.py index 8dd08d109..317ac592e 100644 --- a/apps/tickets/apps.py +++ b/apps/tickets/apps.py @@ -7,6 +7,6 @@ class TicketsConfig(AppConfig): verbose_name = _('Tickets') def ready(self): - from . import signals_handler + from . import signal_handlers from . import notifications return super().ready() diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py index d3ee162bd..aa536584d 100644 --- a/apps/tickets/models/ticket.py +++ b/apps/tickets/models/ticket.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- # +from typing import Callable + from django.db import models from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from datetime import timedelta from django.db.utils import IntegrityError from common.exceptions import JMSException @@ -53,6 +54,7 @@ class StatusMixin: status: str applicant: models.ForeignKey current_node: models.Manager + save: Callable def set_state_approve(self): self.state = TicketState.approved @@ -182,7 +184,8 @@ class Ticket(CommonModelMixin, StatusMixin, OrgModelMixin): @property def processor(self): - processor = self.current_node.first().ticket_assignees.exclude(state=ProcessStatus.notified).first() + processor = self.current_node.first().ticket_assignees\ + .exclude(state=ProcessStatus.notified).first() return processor.assignee if processor else None def create_related_node(self): diff --git a/apps/tickets/serializers/super_ticket.py b/apps/tickets/serializers/super_ticket.py index a59bc51fb..9d84728e4 100644 --- a/apps/tickets/serializers/super_ticket.py +++ b/apps/tickets/serializers/super_ticket.py @@ -1,4 +1,5 @@ -from rest_framework.serializers import ModelSerializer +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ from ..models import SuperTicket @@ -6,7 +7,15 @@ from ..models import SuperTicket __all__ = ['SuperTicketSerializer'] -class SuperTicketSerializer(ModelSerializer): +class SuperTicketSerializer(serializers.ModelSerializer): + processor = serializers.SerializerMethodField(label=_("Processor")) + class Meta: model = SuperTicket fields = ['id', 'status', 'state', 'processor'] + + @staticmethod + def get_processor(ticket): + if not ticket.processor: + return '' + return str(ticket.processor) diff --git a/apps/tickets/signals_handler/__init__.py b/apps/tickets/signal_handlers/__init__.py similarity index 100% rename from apps/tickets/signals_handler/__init__.py rename to apps/tickets/signal_handlers/__init__.py diff --git a/apps/tickets/signals_handler/comment.py b/apps/tickets/signal_handlers/comment.py similarity index 100% rename from apps/tickets/signals_handler/comment.py rename to apps/tickets/signal_handlers/comment.py diff --git a/apps/tickets/signals_handler/ticket.py b/apps/tickets/signal_handlers/ticket.py similarity index 100% rename from apps/tickets/signals_handler/ticket.py rename to apps/tickets/signal_handlers/ticket.py diff --git a/apps/users/apps.py b/apps/users/apps.py index 2c37f4e42..bd569c9fe 100644 --- a/apps/users/apps.py +++ b/apps/users/apps.py @@ -9,6 +9,6 @@ class UsersConfig(AppConfig): verbose_name = _('Users') def ready(self): - from . import signals_handler + from . import signal_handlers from . import notifications super().ready() diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 293e3c89a..c89a01d21 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -131,7 +131,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer 'public_key': {'write_only': True}, 'is_first_login': {'label': _('Is first login'), 'read_only': True}, 'is_valid': {'label': _('Is valid')}, - 'is_service_account': {'label': _('Is service account')}, + 'is_service_account': {'label': _('Is service account')}, 'is_expired': {'label': _('Is expired')}, 'avatar_url': {'label': _('Avatar url')}, 'created_by': {'read_only': True, 'allow_blank': True}, @@ -243,7 +243,7 @@ class InviteSerializer(RolesSerializerMixin, serializers.Serializer): class ServiceAccountSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', 'name', 'access_key'] + fields = ['id', 'name', 'access_key', 'comment'] read_only_fields = ['access_key'] def __init__(self, *args, **kwargs): diff --git a/apps/users/signals_handler.py b/apps/users/signal_handlers.py similarity index 100% rename from apps/users/signals_handler.py rename to apps/users/signal_handlers.py diff --git a/jms b/jms index b4eeb7e8a..517fb0602 100755 --- a/jms +++ b/jms @@ -63,8 +63,8 @@ def check_database_connection(): return except OperationalError: logging.info('Database not setup, retry') - except Exception as e: - logging.error('Unexpect error occur: {}'.format(str(e))) + except Exception as exc: + logging.error('Unexpect error occur: {}'.format(str(exc))) time.sleep(1) logging.error("Connection database failed, exit") sys.exit(10) @@ -96,7 +96,7 @@ def collect_static(): pass -def compile_i81n_file(): +def compile_i18n_file(): django_mo_file = os.path.join(BASE_DIR, 'apps', 'locale', 'zh', 'LC_MESSAGES', 'django.mo') if os.path.exists(django_mo_file): return @@ -134,8 +134,8 @@ def start_services(): except KeyboardInterrupt: logging.info('Cancel ...') time.sleep(2) - except Exception as e: - logging.error("Start service error {}: {}".format(services, e)) + except Exception as exc: + logging.error("Start service error {}: {}".format(services, exc)) time.sleep(2) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index aea4e3184..a29be5520 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -17,7 +17,7 @@ decorator==4.1.2 Django==3.1.14 django-auth-ldap==2.2.0 django-bootstrap3==14.2.0 -django-celery-beat==2.0 +django-celery-beat==2.2.1 django-filter==2.4.0 django-formtools==2.2 django-ranged-response==0.2.0 @@ -84,7 +84,7 @@ python-daemon==2.2.3 httpsig==1.3.0 treelib==1.5.3 django-proxy==1.2.1 -flower==0.9.3 +flower==1.0.0 channels-redis==3.2.0 channels==2.4.0 daphne==2.4.1 diff --git a/utils/start_celery_beat.py b/utils/start_celery_beat.py index 34887fb4d..236a61ba8 100644 --- a/utils/start_celery_beat.py +++ b/utils/start_celery_beat.py @@ -22,8 +22,9 @@ redis = Redis(host=CONFIG.REDIS_HOST, port=CONFIG.REDIS_PORT, password=CONFIG.RE scheduler = "django_celery_beat.schedulers:DatabaseScheduler" cmd = [ - 'celery', 'beat', + 'celery', '-A', 'ops', + 'beat', '-l', 'INFO', '--scheduler', scheduler, '--max-interval', '60'