From 3de881fa19d05acd63bf43cdf8ac9777b307a54d Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 2 Apr 2022 18:35:46 +0800 Subject: [PATCH 001/488] =?UTF-8?q?perf:=20=E6=89=93=E7=AE=97=E9=87=8D?= =?UTF-8?q?=E6=9E=84=20asset=20application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/models/account.py | 7 + apps/applications/models/application.py | 212 +----------------- apps/applications/models/database.py | 13 ++ apps/applications/models/remote_app.py | 0 apps/applications/models/tree.py | 208 +++++++++++++++++ apps/assets/api/__init__.py | 1 + apps/assets/api/asset.py | 32 +-- apps/assets/api/platform.py | 21 ++ .../migrations/0003_auto_20180109_2331.py | 2 +- .../migrations/0007_auto_20180225_1815.py | 2 +- .../migrations/0045_auto_20191206_1607.py | 2 +- apps/assets/migrations/0090_add_host.py | 20 ++ .../migrations/0091_auto_20220401_1558.py | 40 ++++ apps/assets/migrations/0092_hardware.py | 42 ++++ .../migrations/0093_auto_20220403_1627.py | 51 +++++ .../migrations/0094_auto_20220402_1736.py | 69 ++++++ apps/assets/models/__init__.py | 1 + apps/assets/models/asset/__init__.py | 2 + apps/assets/models/asset/cloud.py | 0 .../models/{asset.py => asset/common.py} | 120 ++-------- apps/assets/models/asset/database.py | 9 + apps/assets/models/asset/host.py | 56 +++++ apps/assets/models/asset/network.py | 0 apps/assets/models/asset/remote_app.py | 0 apps/assets/models/platform.py | 56 +++++ apps/assets/serializers/asset/__init__.py | 1 + .../serializers/{asset.py => asset/common.py} | 15 +- apps/assets/serializers/asset/host.py | 19 ++ apps/common/drf/api.py | 14 +- apps/common/mixins/api/common.py | 13 +- apps/xpack.bak | 1 + 31 files changed, 667 insertions(+), 362 deletions(-) create mode 100644 apps/applications/models/database.py create mode 100644 apps/applications/models/remote_app.py create mode 100644 apps/applications/models/tree.py create mode 100644 apps/assets/api/platform.py create mode 100644 apps/assets/migrations/0090_add_host.py create mode 100644 apps/assets/migrations/0091_auto_20220401_1558.py create mode 100644 apps/assets/migrations/0092_hardware.py create mode 100644 apps/assets/migrations/0093_auto_20220403_1627.py create mode 100644 apps/assets/migrations/0094_auto_20220402_1736.py create mode 100644 apps/assets/models/asset/__init__.py create mode 100644 apps/assets/models/asset/cloud.py rename apps/assets/models/{asset.py => asset/common.py} (69%) create mode 100644 apps/assets/models/asset/database.py create mode 100644 apps/assets/models/asset/host.py create mode 100644 apps/assets/models/asset/network.py create mode 100644 apps/assets/models/asset/remote_app.py create mode 100644 apps/assets/models/platform.py create mode 100644 apps/assets/serializers/asset/__init__.py rename apps/assets/serializers/{asset.py => asset/common.py} (91%) create mode 100644 apps/assets/serializers/asset/host.py create mode 160000 apps/xpack.bak diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py index 627dec91a..5d82db36b 100644 --- a/apps/applications/models/account.py +++ b/apps/applications/models/account.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty from assets.models.base import BaseUser +from assets.models import SystemUser class Account(BaseUser): @@ -108,3 +109,9 @@ class Account(BaseUser): def __str__(self): return self.smart_name + + +class ApplicationUser(SystemUser): + class Meta: + proxy = True + verbose_name = _('Application user') diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index aa2d56f70..d1859747c 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -1,216 +1,13 @@ -from collections import defaultdict -from urllib.parse import urlencode, parse_qsl - from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.conf import settings from orgs.mixins.models import OrgModelMixin from common.mixins import CommonModelMixin -from common.tree import TreeNode from common.utils import is_uuid -from assets.models import Asset, SystemUser +from assets.models import Asset -from ..utils import KubernetesTree from .. import const - - -class ApplicationTreeNodeMixin: - id: str - name: str - type: str - category: str - attrs: dict - - @staticmethod - def create_tree_id(pid, type, v): - i = dict(parse_qsl(pid)) - i[type] = v - tree_id = urlencode(i) - return tree_id - - @classmethod - def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None, - show_empty=True, show_count=True): - count = counts.get(c.value, 0) - if count == 0 and not show_empty: - return None - label = c.label - if count is not None and show_count: - label = '{} ({})'.format(label, count) - data = { - 'id': id_, - 'name': label, - 'title': label, - 'pId': pid, - 'isParent': bool(count), - 'open': opened, - 'iconSkin': '', - 'meta': { - 'type': tp, - 'data': { - 'name': c.name, - 'value': c.value - } - } - } - return TreeNode(**data) - - @classmethod - def create_root_tree_node(cls, queryset, show_count=True): - count = queryset.count() if show_count else None - root_id = 'applications' - root_name = _('Applications') - if count is not None and show_count: - root_name = '{} ({})'.format(root_name, count) - node = TreeNode(**{ - 'id': root_id, - 'name': root_name, - 'title': root_name, - 'pId': '', - 'isParent': True, - 'open': True, - 'iconSkin': '', - 'meta': { - 'type': 'applications_root', - } - }) - return node - - @classmethod - def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True): - nodes = [] - categories = const.AppType.category_types_mapper().keys() - for category in categories: - if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category): - continue - i = cls.create_tree_id(pid, 'category', category.value) - node = cls.create_choice_node( - category, i, pid=pid, tp='category', - counts=counts, opened=False, show_empty=show_empty, - show_count=show_count - ) - if not node: - continue - nodes.append(node) - return nodes - - @classmethod - def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True): - nodes = [] - temp_pid = pid - type_category_mapper = const.AppType.type_category_mapper() - types = const.AppType.type_category_mapper().keys() - - for tp in types: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp): - continue - category = type_category_mapper.get(tp) - pid = cls.create_tree_id(pid, 'category', category.value) - i = cls.create_tree_id(pid, 'type', tp.value) - node = cls.create_choice_node( - tp, i, pid, tp='type', counts=counts, opened=False, - show_empty=show_empty, show_count=show_count - ) - pid = temp_pid - if not node: - continue - nodes.append(node) - return nodes - - @staticmethod - def get_tree_node_counts(queryset): - counts = defaultdict(int) - values = queryset.values_list('type', 'category') - for i in values: - tp = i[0] - category = i[1] - counts[tp] += 1 - counts[category] += 1 - return counts - - @classmethod - def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True): - counts = cls.get_tree_node_counts(queryset) - tree_nodes = [] - - # 类别的节点 - tree_nodes += cls.create_category_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - - # 类型的节点 - tree_nodes += cls.create_types_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - return tree_nodes - - @classmethod - def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): - tree_nodes = [] - - # 根节点有可能是组织名称 - if root_node is None: - root_node = cls.create_root_tree_node(queryset, show_count=show_count) - tree_nodes.append(root_node) - - tree_nodes += cls.create_category_type_tree_nodes( - queryset, root_node.id, show_empty=show_empty, show_count=show_count - ) - - # 应用的节点 - for app in queryset: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type): - continue - node = app.as_tree_node(root_node.id) - tree_nodes.append(node) - return tree_nodes - - def create_app_tree_pid(self, root_id): - pid = self.create_tree_id(root_id, 'category', self.category) - pid = self.create_tree_id(pid, 'type', self.type) - return pid - - def as_tree_node(self, pid, k8s_as_tree=False): - if self.type == const.AppType.k8s and k8s_as_tree: - node = KubernetesTree(pid).as_tree_node(self) - else: - node = self._as_tree_node(pid) - return node - - def _attrs_to_tree(self): - if self.category == const.AppCategory.db: - return self.attrs - return {} - - def _as_tree_node(self, pid): - icon_skin_category_mapper = { - 'remote_app': 'chrome', - 'db': 'database', - 'cloud': 'cloud' - } - icon_skin = icon_skin_category_mapper.get(self.category, 'file') - pid = self.create_app_tree_pid(pid) - node = TreeNode(**{ - 'id': str(self.id), - 'name': self.name, - 'title': self.name, - 'pId': pid, - 'isParent': False, - 'open': False, - 'iconSkin': icon_skin, - 'meta': { - 'type': 'application', - 'data': { - 'category': self.category, - 'type': self.type, - 'attrs': self._attrs_to_tree() - } - } - }) - return node +from .tree import ApplicationTreeNodeMixin class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): @@ -279,8 +76,3 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): if raise_exception: raise ValueError("Remote App not has asset attr") - -class ApplicationUser(SystemUser): - class Meta: - proxy = True - verbose_name = _('Application user') diff --git a/apps/applications/models/database.py b/apps/applications/models/database.py new file mode 100644 index 000000000..f964dd737 --- /dev/null +++ b/apps/applications/models/database.py @@ -0,0 +1,13 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from .application import Application + + +class Database(Application): + host = models.CharField(max_length=1024, verbose_name=_('Host')) + port = models.IntegerField(verbose_name=_("Port")) + database = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_("Database")) + + class Meta: + verbose_name = _("Database") diff --git a/apps/applications/models/remote_app.py b/apps/applications/models/remote_app.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/models/tree.py b/apps/applications/models/tree.py new file mode 100644 index 000000000..1b459d660 --- /dev/null +++ b/apps/applications/models/tree.py @@ -0,0 +1,208 @@ +from collections import defaultdict +from urllib.parse import urlencode, parse_qsl + +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + +from common.tree import TreeNode +from ..utils import KubernetesTree +from .. import const + + +class ApplicationTreeNodeMixin: + id: str + name: str + type: str + category: str + attrs: dict + + @staticmethod + def create_tree_id(pid, type, v): + i = dict(parse_qsl(pid)) + i[type] = v + tree_id = urlencode(i) + return tree_id + + @classmethod + def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None, + show_empty=True, show_count=True): + count = counts.get(c.value, 0) + if count == 0 and not show_empty: + return None + label = c.label + if count is not None and show_count: + label = '{} ({})'.format(label, count) + data = { + 'id': id_, + 'name': label, + 'title': label, + 'pId': pid, + 'isParent': bool(count), + 'open': opened, + 'iconSkin': '', + 'meta': { + 'type': tp, + 'data': { + 'name': c.name, + 'value': c.value + } + } + } + return TreeNode(**data) + + @classmethod + def create_root_tree_node(cls, queryset, show_count=True): + count = queryset.count() if show_count else None + root_id = 'applications' + root_name = _('Applications') + if count is not None and show_count: + root_name = '{} ({})'.format(root_name, count) + node = TreeNode(**{ + 'id': root_id, + 'name': root_name, + 'title': root_name, + 'pId': '', + 'isParent': True, + 'open': True, + 'iconSkin': '', + 'meta': { + 'type': 'applications_root', + } + }) + return node + + @classmethod + def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True): + nodes = [] + categories = const.AppType.category_types_mapper().keys() + for category in categories: + if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category): + continue + i = cls.create_tree_id(pid, 'category', category.value) + node = cls.create_choice_node( + category, i, pid=pid, tp='category', + counts=counts, opened=False, show_empty=show_empty, + show_count=show_count + ) + if not node: + continue + nodes.append(node) + return nodes + + @classmethod + def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True): + nodes = [] + temp_pid = pid + type_category_mapper = const.AppType.type_category_mapper() + types = const.AppType.type_category_mapper().keys() + + for tp in types: + if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp): + continue + category = type_category_mapper.get(tp) + pid = cls.create_tree_id(pid, 'category', category.value) + i = cls.create_tree_id(pid, 'type', tp.value) + node = cls.create_choice_node( + tp, i, pid, tp='type', counts=counts, opened=False, + show_empty=show_empty, show_count=show_count + ) + pid = temp_pid + if not node: + continue + nodes.append(node) + return nodes + + @staticmethod + def get_tree_node_counts(queryset): + counts = defaultdict(int) + values = queryset.values_list('type', 'category') + for i in values: + tp = i[0] + category = i[1] + counts[tp] += 1 + counts[category] += 1 + return counts + + @classmethod + def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True): + counts = cls.get_tree_node_counts(queryset) + tree_nodes = [] + + # 类别的节点 + tree_nodes += cls.create_category_tree_nodes( + pid, counts, show_empty=show_empty, + show_count=show_count + ) + + # 类型的节点 + tree_nodes += cls.create_types_tree_nodes( + pid, counts, show_empty=show_empty, + show_count=show_count + ) + return tree_nodes + + @classmethod + def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): + tree_nodes = [] + + # 根节点有可能是组织名称 + if root_node is None: + root_node = cls.create_root_tree_node(queryset, show_count=show_count) + tree_nodes.append(root_node) + + tree_nodes += cls.create_category_type_tree_nodes( + queryset, root_node.id, show_empty=show_empty, show_count=show_count + ) + + # 应用的节点 + for app in queryset: + if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type): + continue + node = app.as_tree_node(root_node.id) + tree_nodes.append(node) + return tree_nodes + + def create_app_tree_pid(self, root_id): + pid = self.create_tree_id(root_id, 'category', self.category) + pid = self.create_tree_id(pid, 'type', self.type) + return pid + + def as_tree_node(self, pid, k8s_as_tree=False): + if self.type == const.AppType.k8s and k8s_as_tree: + node = KubernetesTree(pid).as_tree_node(self) + else: + node = self._as_tree_node(pid) + return node + + def _attrs_to_tree(self): + if self.category == const.AppCategory.db: + return self.attrs + return {} + + def _as_tree_node(self, pid): + icon_skin_category_mapper = { + 'remote_app': 'chrome', + 'db': 'database', + 'cloud': 'cloud' + } + icon_skin = icon_skin_category_mapper.get(self.category, 'file') + pid = self.create_app_tree_pid(pid) + node = TreeNode(**{ + 'id': str(self.id), + 'name': self.name, + 'title': self.name, + 'pId': pid, + 'isParent': False, + 'open': False, + 'iconSkin': icon_skin, + 'meta': { + 'type': 'application', + 'data': { + 'category': self.category, + 'type': self.type, + 'attrs': self._attrs_to_tree() + } + } + }) + return node + diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 7a8ce9a60..0fe3f480b 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -1,4 +1,5 @@ from .mixin import * +from .platform import * from .admin_user import * from .asset import * from .label import * diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 3f4b7a209..b3bfd0047 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from rest_framework.viewsets import ModelViewSet from rest_framework.generics import RetrieveAPIView, ListAPIView +from rest_framework.decorators import action from django.shortcuts import get_object_or_404 from django.db.models import Q @@ -27,8 +27,7 @@ from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFi logger = get_logger(__file__) __all__ = [ 'AssetViewSet', 'AssetPlatformRetrieveApi', - 'AssetGatewayListApi', 'AssetPlatformViewSet', - 'AssetTaskCreateApi', 'AssetsTaskCreateApi', + 'AssetGatewayListApi', 'AssetTaskCreateApi', 'AssetsTaskCreateApi', 'AssetPermUserListApi', 'AssetPermUserPermissionsListApi', 'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi', ] @@ -52,7 +51,8 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) ordering = ('hostname', ) serializer_classes = { 'default': serializers.AssetSerializer, - 'suggestion': serializers.MiniAssetSerializer + 'suggestion': serializers.MiniAssetSerializer, + 'platform': serializers.PlatformSerializer } rbac_perms = { 'match': 'assets.match_asset' @@ -74,6 +74,10 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) assets = serializer.save() self.set_assets_node(assets) + @action(methods='GET', detail=True, url_path='platform') + def platform(self, request, *args, **kwargs): + pass + class AssetPlatformRetrieveApi(RetrieveAPIView): queryset = Platform.objects.all() @@ -88,20 +92,6 @@ class AssetPlatformRetrieveApi(RetrieveAPIView): return asset.platform -class AssetPlatformViewSet(ModelViewSet): - queryset = Platform.objects.all() - serializer_class = serializers.PlatformSerializer - filterset_fields = ['name', 'base'] - search_fields = ['name'] - - def check_object_permissions(self, request, obj): - if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: - self.permission_denied( - request, message={"detail": "Internal platform"} - ) - return super().check_object_permissions(request, obj) - - class AssetsTaskMixin: def perform_assets_task(self, serializer): @@ -246,7 +236,7 @@ class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi): return user_groups -class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView): +class BasePermedAssetListApi(generics.ListAPIView): model = AssetPermission serializer_class = AssetPermissionSerializer filterset_class = AssetPermissionFilter @@ -272,7 +262,7 @@ class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView): return queryset -class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin): +class AssetPermUserPermissionsListApi(BasePermedAssetListApi): def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) queryset = self.filter_user_related(queryset) @@ -291,7 +281,7 @@ class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsLis return user -class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin): +class AssetPermUserGroupPermissionsListApi(BasePermedAssetListApi): def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) queryset = self.filter_user_group_related(queryset) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py new file mode 100644 index 000000000..a7cb798ae --- /dev/null +++ b/apps/assets/api/platform.py @@ -0,0 +1,21 @@ +from rest_framework.viewsets import ModelViewSet + +from assets.models import Platform +from assets.serializers import PlatformSerializer + + +__all__ = ['AssetPlatformViewSet'] + + +class AssetPlatformViewSet(ModelViewSet): + queryset = Platform.objects.all() + serializer_class = PlatformSerializer + filterset_fields = ['name', 'base'] + search_fields = ['name'] + + def check_object_permissions(self, request, obj): + if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: + self.permission_denied( + request, message={"detail": "Internal platform"} + ) + return super().check_object_permissions(request, obj) diff --git a/apps/assets/migrations/0003_auto_20180109_2331.py b/apps/assets/migrations/0003_auto_20180109_2331.py index 254de6236..960032331 100644 --- a/apps/assets/migrations/0003_auto_20180109_2331.py +++ b/apps/assets/migrations/0003_auto_20180109_2331.py @@ -17,6 +17,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='asset', name='cluster', - field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'), + field=models.ForeignKey(default=assets.models.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'), ), ] diff --git a/apps/assets/migrations/0007_auto_20180225_1815.py b/apps/assets/migrations/0007_auto_20180225_1815.py index 009381bcb..4ce2b1e05 100644 --- a/apps/assets/migrations/0007_auto_20180225_1815.py +++ b/apps/assets/migrations/0007_auto_20180225_1815.py @@ -50,7 +50,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='asset', name='nodes', - field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'), + field=models.ManyToManyField(default=assets.models.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'), ), migrations.AddField( model_name='systemuser', diff --git a/apps/assets/migrations/0045_auto_20191206_1607.py b/apps/assets/migrations/0045_auto_20191206_1607.py index f51839289..bf04ad773 100644 --- a/apps/assets/migrations/0045_auto_20191206_1607.py +++ b/apps/assets/migrations/0045_auto_20191206_1607.py @@ -34,7 +34,7 @@ class Migration(migrations.Migration): model_name='asset', name='platform', field=models.ForeignKey( - default=assets.models.asset.Platform.default, + default=assets.models.Platform.default, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.Platform', verbose_name='Platform'), diff --git a/apps/assets/migrations/0090_add_host.py b/apps/assets/migrations/0090_add_host.py new file mode 100644 index 000000000..4ffeb6553 --- /dev/null +++ b/apps/assets/migrations/0090_add_host.py @@ -0,0 +1,20 @@ +# Generated by Django 3.1.14 on 2022-03-30 10:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0089_auto_20220310_0616'), + ] + + operations = [ + migrations.CreateModel( + name='Host', + fields=[ + ('asset_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assets.asset')), + ], + ), + ] diff --git a/apps/assets/migrations/0091_auto_20220401_1558.py b/apps/assets/migrations/0091_auto_20220401_1558.py new file mode 100644 index 000000000..e3275d4d3 --- /dev/null +++ b/apps/assets/migrations/0091_auto_20220401_1558.py @@ -0,0 +1,40 @@ +# Generated by Django 3.1.14 on 2022-04-01 07:58 + +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_to_host(apps, schema_editor): + asset_model = apps.get_model("assets", "Asset") + host_model = apps.get_model("assets", 'Host') + db_alias = schema_editor.connection.alias + + created = 0 + batch_size = 1000 + + while True: + start = created + end = created + batch_size + assets = asset_model.objects.using(db_alias).all()[start:end] + if not assets: + break + + hosts = [host_model(asset_ptr=asset) for asset in assets] + host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True) + created += len(hosts) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0090_add_host'), + ] + + operations = [ + migrations.AlterField( + model_name='host', + name='asset_ptr', + field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset'), + ), + migrations.RunPython(migrate_to_host) + ] diff --git a/apps/assets/migrations/0092_hardware.py b/apps/assets/migrations/0092_hardware.py new file mode 100644 index 000000000..b158363ef --- /dev/null +++ b/apps/assets/migrations/0092_hardware.py @@ -0,0 +1,42 @@ +# Generated by Django 3.1.14 on 2022-04-02 09:09 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0091_auto_20220401_1558'), + ] + + operations = [ + migrations.CreateModel( + name='HostInfo', + fields=[ + ('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')), + ('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')), + ('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')), + ('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')), + ('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')), + ('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')), + ('cpu_vcpus', models.IntegerField(null=True, verbose_name='CPU vcpus')), + ('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')), + ('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')), + ('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')), + ('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')), + ('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')), + ('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')), + ('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('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')), + ('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='info', verbose_name='Host')), + ], + options={ + 'verbose_name': 'HostInfo', + }, + ), + ] diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py new file mode 100644 index 000000000..e01febe9b --- /dev/null +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -0,0 +1,51 @@ +# Generated by Django 3.1.14 on 2022-04-02 08:27 + +from django.utils import timezone +from django.db import migrations + + +def migrate_hardware(apps, *args): + host_model = apps.get_model('assets', 'Host') + asset_model = apps.get_model('assets', 'Asset') + hardware_model = apps.get_model('assets', 'HostInfo') + + created = 0 + batch_size = 1000 + + excludes = ['id', 'host', 'date_updated'] + fields = [f.name for f in hardware_model._meta.fields] + fields = [name for name in fields if name not in excludes] + + while True: + start = created + end = created + batch_size + hosts = host_model.objects.all()[start:end] + asset_ids = [h.asset_ptr_id for h in hosts] + assets = asset_model.objects.filter(id__in=asset_ids) + asset_mapper = {a.id: a for a in assets} + if not hosts: + break + + hardware_list = [] + for host in hosts: + hardware = hardware_model() + asset = asset_mapper[host.asset_ptr_id] + hardware.host = host + hardware.date_updated = timezone.now() + for name in fields: + setattr(hardware, name, getattr(asset, name)) + hardware_list.append(hardware) + + hardware_model.objects.bulk_create(hardware_list, ignore_conflicts=True) + created += len(hardware_list) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0092_hardware'), + ] + + operations = [ + migrations.RunPython(migrate_hardware) + ] diff --git a/apps/assets/migrations/0094_auto_20220402_1736.py b/apps/assets/migrations/0094_auto_20220402_1736.py new file mode 100644 index 000000000..daf53270b --- /dev/null +++ b/apps/assets/migrations/0094_auto_20220402_1736.py @@ -0,0 +1,69 @@ +# Generated by Django 3.1.14 on 2022-04-02 09:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0093_auto_20220403_1627'), + ] + + operations = [ + migrations.RemoveField( + model_name='asset', + name='cpu_cores', + ), + migrations.RemoveField( + model_name='asset', + name='cpu_count', + ), + migrations.RemoveField( + model_name='asset', + name='cpu_model', + ), + migrations.RemoveField( + model_name='asset', + name='cpu_vcpus', + ), + migrations.RemoveField( + model_name='asset', + name='disk_info', + ), + migrations.RemoveField( + model_name='asset', + name='disk_total', + ), + migrations.RemoveField( + model_name='asset', + name='hostname_raw', + ), + migrations.RemoveField( + model_name='asset', + name='memory', + ), + migrations.RemoveField( + model_name='asset', + name='model', + ), + migrations.RemoveField( + model_name='asset', + name='os', + ), + migrations.RemoveField( + model_name='asset', + name='os_arch', + ), + migrations.RemoveField( + model_name='asset', + name='os_version', + ), + migrations.RemoveField( + model_name='asset', + name='sn', + ), + migrations.RemoveField( + model_name='asset', + name='vendor', + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index d2dd03885..b4ea60bb1 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,4 +1,5 @@ from .base import * +from .platform import * from .asset import * from .label import Label from .user import * diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py new file mode 100644 index 000000000..51640e7cf --- /dev/null +++ b/apps/assets/models/asset/__init__.py @@ -0,0 +1,2 @@ +from .common import * +from .host import * diff --git a/apps/assets/models/asset/cloud.py b/apps/assets/models/asset/cloud.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset/common.py similarity index 69% rename from apps/assets/models/asset.py rename to apps/assets/models/asset/common.py index c4ecf9cfe..349765283 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset/common.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# +# import uuid import logging @@ -11,18 +11,17 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import ValidationError -from common.fields.model import JsonDictTextField from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager +from ..platform import Platform +from ..base import AbsConnectivity -from .base import AbsConnectivity - -__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet'] +__all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster'] logger = logging.getLogger(__name__) def default_cluster(): - from .cluster import Cluster + from assets.models import Cluster name = "Default" defaults = {"name": name} cluster, created = Cluster.objects.get_or_create( @@ -33,7 +32,7 @@ def default_cluster(): def default_node(): try: - from .node import Node + from assets.models import Node root = Node.org_root() return Node.objects.filter(id=root.id) except: @@ -106,7 +105,7 @@ class NodesRelationMixin: _all_nodes_keys = None def get_nodes(self): - from .node import Node + from assets.models import Node nodes = self.nodes.all() if not nodes: nodes = Node.objects.filter(id=Node.org_root().id) @@ -122,104 +121,25 @@ class NodesRelationMixin: return nodes -class Platform(models.Model): - CHARSET_CHOICES = ( - ('utf8', 'UTF-8'), - ('gbk', 'GBK'), - ) - BASE_CHOICES = ( - ('Linux', 'Linux'), - ('Unix', 'Unix'), - ('MacOS', 'MacOS'), - ('BSD', 'BSD'), - ('Windows', 'Windows'), - ('Other', 'Other'), - ) - name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) - base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base")) - charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) - meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) - internal = models.BooleanField(default=False, verbose_name=_("Internal")) - comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) - - @classmethod - def default(cls): - linux, created = cls.objects.get_or_create( - defaults={'name': 'Linux'}, name='Linux' - ) - return linux.id - - def is_windows(self): - return self.base.lower() in ('windows',) - - def is_unixlike(self): - return self.base.lower() in ("linux", "unix", "macos", "bsd") - - def __str__(self): - return self.name - - class Meta: - verbose_name = _("Platform") - # ordering = ('name',) - - -class AbsHardwareInfo(models.Model): - # Collect - vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) - model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) - sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) - - cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) - cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) - cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) - cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) - memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) - disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) - disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) - - os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) - os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) - os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) - hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) - - class Meta: - abstract = True - - @property - def cpu_info(self): - info = "" - if self.cpu_model: - info += self.cpu_model - if self.cpu_count and self.cpu_cores: - info += "{}*{}".format(self.cpu_count, self.cpu_cores) - return info - - @property - def hardware_info(self): - if self.cpu_count: - return '{} Core {} {}'.format( - self.cpu_vcpus or self.cpu_count * self.cpu_cores, - self.memory, self.disk_total - ) - else: - return '' - - -class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): +class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) + ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh, choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols")) - platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') - domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) - nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) + platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, + verbose_name=_("Platform"), related_name='assets') + domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', + verbose_name=_("Domain"), on_delete=models.SET_NULL) + nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', + verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) # Auth - admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_("Admin user"), related_name='admin_assets') + admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, + verbose_name=_("Admin user"), related_name='admin_assets') # Some information public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP')) @@ -236,7 +156,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin return '{0.hostname}({0.ip})'.format(self) def set_admin_user_relation(self): - from .authbook import AuthBook + from assets.models import AuthBook if not self.admin_user: return if self.admin_user.type != 'admin': @@ -333,7 +253,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin return names def as_node(self): - from .node import Node + from assets.models import Node fake_node = Node() fake_node.id = self.id fake_node.key = self.id @@ -372,7 +292,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin return tree_node def get_all_system_users(self): - from .user import SystemUser + from assets.models import SystemUser system_user_ids = SystemUser.assets.through.objects.filter(asset=self)\ .values_list('systemuser_id', flat=True) system_users = SystemUser.objects.filter(id__in=system_user_ids) diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py new file mode 100644 index 000000000..ff0be89a7 --- /dev/null +++ b/apps/assets/models/asset/database.py @@ -0,0 +1,9 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from .common import Asset + + +class Database(Asset): + database = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py new file mode 100644 index 000000000..f7533d5fd --- /dev/null +++ b/apps/assets/models/asset/host.py @@ -0,0 +1,56 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from common.mixins.models import CommonModelMixin +from .common import Asset + + +class Host(Asset): + pass + + +class HostInfo(CommonModelMixin): + host = models.OneToOneField(Host, related_name='info', on_delete=models.CASCADE, + verbose_name=_("Host"), unique=True) + # Collect + vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) + model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) + sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) + + cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) + cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) + cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) + cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) + memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) + disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) + disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) + + os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) + os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) + os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) + hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) + + @property + def cpu_info(self): + info = "" + if self.cpu_model: + info += self.cpu_model + if self.cpu_count and self.cpu_cores: + info += "{}*{}".format(self.cpu_count, self.cpu_cores) + return info + + @property + def hardware_info(self): + if self.cpu_count: + return '{} Core {} {}'.format( + self.cpu_vcpus or self.cpu_count * self.cpu_cores, + self.memory, self.disk_total + ) + else: + return '' + + def __str__(self): + return '{} of {}'.format(self.hardware_info, self.host.hostname) + + class Meta: + verbose_name = _("HostInfo") diff --git a/apps/assets/models/asset/network.py b/apps/assets/models/asset/network.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/models/asset/remote_app.py b/apps/assets/models/asset/remote_app.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py new file mode 100644 index 000000000..5b8aafe7e --- /dev/null +++ b/apps/assets/models/platform.py @@ -0,0 +1,56 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from common.fields.model import JsonDictTextField + +__all__ = ['Platform'] + + +class Category(models.TextChoices): + Host = 'host', _('Host') + Network = 'network', _('Network device') + Database = 'database', _('Database') + RemoteApp = 'remote_app', _('Microsoft remote app') + Cloud = 'cloud', _("Cloud") + + +class Platform(models.Model): + CHARSET_CHOICES = ( + ('utf8', 'UTF-8'), + ('gbk', 'GBK'), + ) + BASE_CHOICES = ( + ('Linux', 'Linux'), + ('Unix', 'Unix'), + ('MacOS', 'MacOS'), + ('BSD', 'BSD'), + ('Windows', 'Windows'), + ('Other', 'Other'), + ) + name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) + base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base")) + charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) + meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) + internal = models.BooleanField(default=False, verbose_name=_("Internal")) + comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + + @classmethod + def default(cls): + linux, created = cls.objects.get_or_create( + defaults={'name': 'Linux'}, name='Linux' + ) + return linux.id + + def is_windows(self): + return self.base.lower() in ('windows',) + + def is_unixlike(self): + return self.base.lower() in ("linux", "unix", "macos", "bsd") + + def __str__(self): + return self.name + + class Meta: + verbose_name = _("Platform") + # ordering = ('name',) + diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py new file mode 100644 index 000000000..55e5f844b --- /dev/null +++ b/apps/assets/serializers/asset/__init__.py @@ -0,0 +1 @@ +from .common import * diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset/common.py similarity index 91% rename from apps/assets/serializers/asset.py rename to apps/assets/serializers/asset/common.py index 427d0e470..ead6ca1f6 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset/common.py @@ -5,7 +5,7 @@ from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from ..models import Asset, Node, Platform, SystemUser +from ...models import Asset, Node, Platform, SystemUser __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', @@ -82,12 +82,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'protocol', 'port', 'protocols', 'is_active', 'public_ip', 'number', 'comment', ] - fields_hardware = [ - 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', - 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', - 'os', 'os_version', 'os_arch', 'hostname_raw', - 'cpu_info', 'hardware_info', - ] fields_fk = [ 'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display' ] @@ -95,16 +89,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'nodes', 'nodes_display', 'labels', 'labels_display', ] read_only_fields = [ - 'connectivity', 'date_verified', 'cpu_info', 'hardware_info', - 'created_by', 'date_created', + 'connectivity', 'date_verified', 'created_by', 'date_created', ] - fields = fields_small + fields_hardware + fields_fk + fields_m2m + read_only_fields + fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { 'protocol': {'write_only': True}, 'port': {'write_only': True}, - 'hardware_info': {'label': _('Hardware info'), 'read_only': True}, 'admin_user_display': {'label': _('Admin user display'), 'read_only': True}, - 'cpu_info': {'label': _('CPU info')}, } def get_fields(self): diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py new file mode 100644 index 000000000..1b40426cc --- /dev/null +++ b/apps/assets/serializers/asset/host.py @@ -0,0 +1,19 @@ +from rest_framework import serializers + +from .common import AssetSerializer +from assets.models import HostInfo + + +class HardwareSerializer(serializers.ModelSerializer): + class Meta: + model = HostInfo + fields = [ + 'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', + 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', + 'os', 'os_version', 'os_arch', 'hostname_raw', + 'cpu_info', 'hardware_info', 'date_updated' + ] + + +class HostSerializer(AssetSerializer): + hardware_info = HardwareSerializer(read_only=True) diff --git a/apps/common/drf/api.py b/apps/common/drf/api.py index 073e9fd7e..23567aa32 100644 --- a/apps/common/drf/api.py +++ b/apps/common/drf/api.py @@ -2,31 +2,31 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelV from rest_framework_bulk import BulkModelViewSet from ..mixins.api import ( - RelationMixin, AllowBulkDestroyMixin, CommonMixin + RelationMixin, AllowBulkDestroyMixin, CommonApiMixin ) -class JMSGenericViewSet(CommonMixin, GenericViewSet): +class JMSGenericViewSet(CommonApiMixin, GenericViewSet): pass -class JMSViewSet(CommonMixin, ViewSet): +class JMSViewSet(CommonApiMixin, ViewSet): pass -class JMSModelViewSet(CommonMixin, ModelViewSet): +class JMSModelViewSet(CommonApiMixin, ModelViewSet): pass -class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet): +class JMSReadOnlyModelViewSet(CommonApiMixin, ReadOnlyModelViewSet): pass -class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet): +class JMSBulkModelViewSet(CommonApiMixin, AllowBulkDestroyMixin, BulkModelViewSet): pass -class JMSBulkRelationModelViewSet(CommonMixin, +class JMSBulkRelationModelViewSet(CommonApiMixin, RelationMixin, AllowBulkDestroyMixin, BulkModelViewSet): diff --git a/apps/common/mixins/api/common.py b/apps/common/mixins/api/common.py index 8dbf4fb1e..3c59739ad 100644 --- a/apps/common/mixins/api/common.py +++ b/apps/common/mixins/api/common.py @@ -13,7 +13,7 @@ from .queryset import QuerySetMixin __all__ = [ - 'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', 'CommonMixin' + 'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', ] @@ -82,15 +82,10 @@ class RelationMixin: self.send_m2m_changed_signal(instance, 'post_remove') -class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin): - pass - - -class CommonMixin(SerializerMixin, - QuerySetMixin, - ExtraFilterFieldsMixin, - RenderToJsonMixin): +class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, + QuerySetMixin, RenderToJsonMixin): pass + diff --git a/apps/xpack.bak b/apps/xpack.bak new file mode 160000 index 000000000..244ace5a9 --- /dev/null +++ b/apps/xpack.bak @@ -0,0 +1 @@ +Subproject commit 244ace5a95503ffaf41b73037692e1121f7c066f From 8688781e15ca75a2410bf875d454c7c952f39486 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 6 Apr 2022 10:15:06 +0800 Subject: [PATCH 002/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20asset=20?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 300 ---------------------------- apps/assets/api/asset/__init__.py | 2 + apps/assets/api/asset/common.py | 173 ++++++++++++++++ apps/assets/api/asset/permission.py | 127 ++++++++++++ apps/assets/urls/api_urls.py | 3 +- apps/common/mixins/api/common.py | 3 +- 6 files changed, 305 insertions(+), 303 deletions(-) delete mode 100644 apps/assets/api/asset.py create mode 100644 apps/assets/api/asset/__init__.py create mode 100644 apps/assets/api/asset/common.py create mode 100644 apps/assets/api/asset/permission.py diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py deleted file mode 100644 index b3bfd0047..000000000 --- a/apps/assets/api/asset.py +++ /dev/null @@ -1,300 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework.generics import RetrieveAPIView, ListAPIView -from rest_framework.decorators import action -from django.shortcuts import get_object_or_404 -from django.db.models import Q - -from common.utils import get_logger, get_object_or_none -from common.mixins.api import SuggestionMixin -from users.models import User, UserGroup -from users.serializers import UserSerializer, UserGroupSerializer -from users.filters import UserFilter -from perms.models import AssetPermission -from perms.serializers import AssetPermissionSerializer -from perms.filters import AssetPermissionFilter -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.mixins import generics -from assets.api import FilterAssetByNodeMixin -from ..models import Asset, Node, Platform, Gateway -from .. import serializers -from ..tasks import ( - update_assets_hardware_info_manual, test_assets_connectivity_manual, - test_system_users_connectivity_a_asset, push_system_users_a_asset -) -from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend - -logger = get_logger(__file__) -__all__ = [ - 'AssetViewSet', 'AssetPlatformRetrieveApi', - 'AssetGatewayListApi', 'AssetTaskCreateApi', 'AssetsTaskCreateApi', - 'AssetPermUserListApi', 'AssetPermUserPermissionsListApi', - 'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi', -] - - -class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet): - """ - API endpoint that allows Asset to be viewed or edited. - """ - model = Asset - filterset_fields = { - 'hostname': ['exact'], - 'ip': ['exact'], - 'system_users__id': ['exact'], - 'platform__base': ['exact'], - 'is_active': ['exact'], - 'protocols': ['exact', 'icontains'] - } - search_fields = ("hostname", "ip") - ordering_fields = ("hostname", "ip", "port", "cpu_cores") - ordering = ('hostname', ) - serializer_classes = { - 'default': serializers.AssetSerializer, - 'suggestion': serializers.MiniAssetSerializer, - 'platform': serializers.PlatformSerializer - } - rbac_perms = { - 'match': 'assets.match_asset' - } - extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend] - - def set_assets_node(self, assets): - if not isinstance(assets, list): - assets = [assets] - node_id = self.request.query_params.get('node_id') - if not node_id: - return - node = get_object_or_none(Node, pk=node_id) - if not node: - return - node.assets.add(*assets) - - def perform_create(self, serializer): - assets = serializer.save() - self.set_assets_node(assets) - - @action(methods='GET', detail=True, url_path='platform') - def platform(self, request, *args, **kwargs): - pass - - -class AssetPlatformRetrieveApi(RetrieveAPIView): - queryset = Platform.objects.all() - serializer_class = serializers.PlatformSerializer - rbac_perms = { - 'retrieve': 'assets.view_gateway' - } - - def get_object(self): - asset_pk = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_pk) - return asset.platform - - -class AssetsTaskMixin: - - def perform_assets_task(self, serializer): - data = serializer.validated_data - action = data['action'] - assets = data.get('assets', []) - if action == "refresh": - task = update_assets_hardware_info_manual.delay(assets) - else: - # action == 'test': - task = test_assets_connectivity_manual.delay(assets) - return task - - def perform_create(self, serializer): - task = self.perform_assets_task(serializer) - self.set_task_to_serializer_data(serializer, task) - - def set_task_to_serializer_data(self, serializer, task): - data = getattr(serializer, '_data', {}) - data["task"] = task.id - setattr(serializer, '_data', data) - - -class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): - model = Asset - serializer_class = serializers.AssetTaskSerializer - - def create(self, request, *args, **kwargs): - pk = self.kwargs.get('pk') - request.data['asset'] = pk - request.data['assets'] = [pk] - return super().create(request, *args, **kwargs) - - def check_permissions(self, request): - action = request.data.get('action') - action_perm_require = { - 'refresh': 'assets.refresh_assethardwareinfo', - 'push_system_user': 'assets.push_assetsystemuser', - 'test': 'assets.test_assetconnectivity', - 'test_system_user': 'assets.test_assetconnectivity' - } - perm_required = action_perm_require.get(action) - has = self.request.user.has_perm(perm_required) - - if not has: - self.permission_denied(request) - - def perform_asset_task(self, serializer): - data = serializer.validated_data - action = data['action'] - if action not in ['push_system_user', 'test_system_user']: - return - - asset = data['asset'] - system_users = data.get('system_users') - if not system_users: - system_users = asset.get_all_system_users() - if action == 'push_system_user': - task = push_system_users_a_asset.delay(system_users, asset=asset) - elif action == 'test_system_user': - task = test_system_users_connectivity_a_asset.delay(system_users, asset=asset) - else: - task = None - return task - - def perform_create(self, serializer): - task = self.perform_asset_task(serializer) - if not task: - task = self.perform_assets_task(serializer) - self.set_task_to_serializer_data(serializer, task) - - -class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): - model = Asset - serializer_class = serializers.AssetsTaskSerializer - - def check_permissions(self, request): - action = request.data.get('action') - action_perm_require = { - 'refresh': 'assets.refresh_assethardwareinfo', - } - perm_required = action_perm_require.get(action) - has = self.request.user.has_perm(perm_required) - if not has: - self.permission_denied(request) - - -class AssetGatewayListApi(generics.ListAPIView): - serializer_class = serializers.GatewayWithAuthSerializer - rbac_perms = { - 'list': 'assets.view_gateway' - } - - def get_queryset(self): - asset_id = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - if not asset.domain: - return Gateway.objects.none() - queryset = asset.domain.gateways.filter(protocol='ssh') - return queryset - - -class BaseAssetPermUserOrUserGroupListApi(ListAPIView): - rbac_perms = { - 'GET': 'perms.view_assetpermission' - } - - def get_object(self): - asset_id = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - return asset - - def get_asset_related_perms(self): - asset = self.get_object() - nodes = asset.get_all_nodes(flat=True) - perms = AssetPermission.objects.filter(Q(assets=asset) | Q(nodes__in=nodes)) - return perms - - -class AssetPermUserListApi(BaseAssetPermUserOrUserGroupListApi): - filterset_class = UserFilter - search_fields = ('username', 'email', 'name', 'id', 'source', 'role') - serializer_class = UserSerializer - rbac_perms = { - 'GET': 'perms.view_assetpermission' - } - - def get_queryset(self): - perms = self.get_asset_related_perms() - users = User.objects.filter( - Q(assetpermissions__in=perms) | Q(groups__assetpermissions__in=perms) - ).distinct() - return users - - -class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi): - serializer_class = UserGroupSerializer - - def get_queryset(self): - perms = self.get_asset_related_perms() - user_groups = UserGroup.objects.filter(assetpermissions__in=perms).distinct() - return user_groups - - -class BasePermedAssetListApi(generics.ListAPIView): - model = AssetPermission - serializer_class = AssetPermissionSerializer - filterset_class = AssetPermissionFilter - search_fields = ('name',) - rbac_perms = { - 'list': 'perms.view_assetpermission' - } - - def get_object(self): - asset_id = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - return asset - - def filter_asset_related(self, queryset): - asset = self.get_object() - nodes = asset.get_all_nodes(flat=True) - perms = queryset.filter(Q(assets=asset) | Q(nodes__in=nodes)) - return perms - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_asset_related(queryset) - return queryset - - -class AssetPermUserPermissionsListApi(BasePermedAssetListApi): - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_user_related(queryset) - queryset = queryset.distinct() - return queryset - - def filter_user_related(self, queryset): - user = self.get_perm_user() - user_groups = user.groups.all() - perms = queryset.filter(Q(users=user) | Q(user_groups__in=user_groups)) - return perms - - def get_perm_user(self): - user_id = self.kwargs.get('perm_user_id') - user = get_object_or_404(User, pk=user_id) - return user - - -class AssetPermUserGroupPermissionsListApi(BasePermedAssetListApi): - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_user_group_related(queryset) - queryset = queryset.distinct() - return queryset - - def filter_user_group_related(self, queryset): - user_group = self.get_perm_user_group() - perms = queryset.filter(user_groups=user_group) - return perms - - def get_perm_user_group(self): - user_group_id = self.kwargs.get('perm_user_group_id') - user_group = get_object_or_404(UserGroup, pk=user_group_id) - return user_group - diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py new file mode 100644 index 000000000..85972fb52 --- /dev/null +++ b/apps/assets/api/asset/__init__.py @@ -0,0 +1,2 @@ +from .common import * +from .permission import * diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py new file mode 100644 index 000000000..6b7fa7d2c --- /dev/null +++ b/apps/assets/api/asset/common.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +# +from rest_framework.decorators import action +from rest_framework.response import Response + +from common.utils import get_logger, get_object_or_none +from common.mixins.api import SuggestionMixin +from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics +from assets.api import FilterAssetByNodeMixin +from assets.models import Asset, Node, Gateway +from assets import serializers +from assets.tasks import ( + update_assets_hardware_info_manual, test_assets_connectivity_manual, + test_system_users_connectivity_a_asset, push_system_users_a_asset +) +from assets.filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend + +logger = get_logger(__file__) +__all__ = [ + 'AssetViewSet', 'AssetTaskCreateApi', 'AssetsTaskCreateApi', +] + + +class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet): + """ + API endpoint that allows Asset to be viewed or edited. + """ + model = Asset + filterset_fields = { + 'hostname': ['exact'], + 'ip': ['exact'], + 'system_users__id': ['exact'], + 'platform__base': ['exact'], + 'is_active': ['exact'], + 'protocols': ['exact', 'icontains'] + } + search_fields = ("hostname", "ip") + ordering_fields = ("hostname", "ip", "port", "cpu_cores") + ordering = ('hostname', ) + serializer_classes = { + 'default': serializers.AssetSerializer, + 'suggestion': serializers.MiniAssetSerializer, + 'platform': serializers.PlatformSerializer, + 'gateways': serializers.GatewayWithAuthSerializer + } + rbac_perms = { + 'match': 'assets.match_asset', + 'platform': 'assets.view_platform', + 'gateways': 'assets.view_gateway' + } + extra_filter_backends = [ + FilterAssetByNodeFilterBackend, LabelFilterBackend, + IpInFilterBackend, + ] + + def set_assets_node(self, assets): + if not isinstance(assets, list): + assets = [assets] + node_id = self.request.query_params.get('node_id') + if not node_id: + return + node = get_object_or_none(Node, pk=node_id) + if not node: + return + node.assets.add(*assets) + + def perform_create(self, serializer): + assets = serializer.save() + self.set_assets_node(assets) + + @action(methods=['GET'], detail=True, url_path='platform') + def platform(self, *args, **kwargs): + asset = self.get_object() + serializer = self.get_serializer(asset.platform) + return Response(serializer.data) + + @action(methods=['GET'], detail=True, url_path='gateways') + def gateways(self, *args, **kwargs): + asset = self.get_object() + if not asset.domain: + gateways = Gateway.objects.none() + else: + gateways = asset.domain.gateways.filter(protocol='ssh') + return self.get_paginated_response_from_queryset(gateways) + + +class AssetsTaskMixin: + def perform_assets_task(self, serializer): + data = serializer.validated_data + action = data['action'] + assets = data.get('assets', []) + if action == "refresh": + task = update_assets_hardware_info_manual.delay(assets) + else: + # action == 'test': + task = test_assets_connectivity_manual.delay(assets) + return task + + def perform_create(self, serializer): + task = self.perform_assets_task(serializer) + self.set_task_to_serializer_data(serializer, task) + + def set_task_to_serializer_data(self, serializer, task): + data = getattr(serializer, '_data', {}) + data["task"] = task.id + setattr(serializer, '_data', data) + + +class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): + model = Asset + serializer_class = serializers.AssetTaskSerializer + + def create(self, request, *args, **kwargs): + pk = self.kwargs.get('pk') + request.data['asset'] = pk + request.data['assets'] = [pk] + return super().create(request, *args, **kwargs) + + def check_permissions(self, request): + action = request.data.get('action') + action_perm_require = { + 'refresh': 'assets.refresh_assethardwareinfo', + 'push_system_user': 'assets.push_assetsystemuser', + 'test': 'assets.test_assetconnectivity', + 'test_system_user': 'assets.test_assetconnectivity' + } + perm_required = action_perm_require.get(action) + has = self.request.user.has_perm(perm_required) + + if not has: + self.permission_denied(request) + + def perform_asset_task(self, serializer): + data = serializer.validated_data + action = data['action'] + if action not in ['push_system_user', 'test_system_user']: + return + + asset = data['asset'] + system_users = data.get('system_users') + if not system_users: + system_users = asset.get_all_system_users() + if action == 'push_system_user': + task = push_system_users_a_asset.delay(system_users, asset=asset) + elif action == 'test_system_user': + task = test_system_users_connectivity_a_asset.delay(system_users, asset=asset) + else: + task = None + return task + + def perform_create(self, serializer): + task = self.perform_asset_task(serializer) + if not task: + task = self.perform_assets_task(serializer) + self.set_task_to_serializer_data(serializer, task) + + +class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): + model = Asset + serializer_class = serializers.AssetsTaskSerializer + + def check_permissions(self, request): + action = request.data.get('action') + action_perm_require = { + 'refresh': 'assets.refresh_assethardwareinfo', + } + perm_required = action_perm_require.get(action) + has = self.request.user.has_perm(perm_required) + if not has: + self.permission_denied(request) + + diff --git a/apps/assets/api/asset/permission.py b/apps/assets/api/asset/permission.py new file mode 100644 index 000000000..cab75bc17 --- /dev/null +++ b/apps/assets/api/asset/permission.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# +from rest_framework.generics import ListAPIView +from django.shortcuts import get_object_or_404 +from django.db.models import Q + +from common.utils import get_logger +from users.models import User, UserGroup +from users.serializers import UserSerializer, UserGroupSerializer +from users.filters import UserFilter +from perms.models import AssetPermission +from perms.serializers import AssetPermissionSerializer +from perms.filters import AssetPermissionFilter +from orgs.mixins import generics +from assets.models import Asset + +logger = get_logger(__file__) +__all__ = [ + 'AssetPermUserListApi', 'AssetPermUserPermissionsListApi', + 'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi', +] + + +class BaseAssetPermUserOrUserGroupListApi(ListAPIView): + rbac_perms = { + 'GET': 'perms.view_assetpermission' + } + + def get_object(self): + asset_id = self.kwargs.get('pk') + asset = get_object_or_404(Asset, pk=asset_id) + return asset + + def get_asset_related_perms(self): + asset = self.get_object() + nodes = asset.get_all_nodes(flat=True) + perms = AssetPermission.objects.filter(Q(assets=asset) | Q(nodes__in=nodes)) + return perms + + +class AssetPermUserListApi(BaseAssetPermUserOrUserGroupListApi): + filterset_class = UserFilter + search_fields = ('username', 'email', 'name', 'id', 'source', 'role') + serializer_class = UserSerializer + rbac_perms = { + 'GET': 'perms.view_assetpermission' + } + + def get_queryset(self): + perms = self.get_asset_related_perms() + users = User.objects.filter( + Q(assetpermissions__in=perms) | Q(groups__assetpermissions__in=perms) + ).distinct() + return users + + +class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi): + serializer_class = UserGroupSerializer + + def get_queryset(self): + perms = self.get_asset_related_perms() + user_groups = UserGroup.objects.filter(assetpermissions__in=perms).distinct() + return user_groups + + +class BaseAssetRelatedPermissionListApi(generics.ListAPIView): + model = AssetPermission + serializer_class = AssetPermissionSerializer + filterset_class = AssetPermissionFilter + search_fields = ('name',) + rbac_perms = { + 'list': 'perms.view_assetpermission' + } + + def get_object(self): + asset_id = self.kwargs.get('pk') + asset = get_object_or_404(Asset, pk=asset_id) + return asset + + def filter_asset_related(self, queryset): + asset = self.get_object() + nodes = asset.get_all_nodes(flat=True) + perms = queryset.filter(Q(assets=asset) | Q(nodes__in=nodes)) + return perms + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset = self.filter_asset_related(queryset) + return queryset + + +class AssetPermUserPermissionsListApi(BaseAssetRelatedPermissionListApi): + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset = self.filter_user_related(queryset) + queryset = queryset.distinct() + return queryset + + def filter_user_related(self, queryset): + user = self.get_perm_user() + user_groups = user.groups.all() + perms = queryset.filter(Q(users=user) | Q(user_groups__in=user_groups)) + return perms + + def get_perm_user(self): + user_id = self.kwargs.get('perm_user_id') + user = get_object_or_404(User, pk=user_id) + return user + + +class AssetPermUserGroupPermissionsListApi(BaseAssetRelatedPermissionListApi): + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset = self.filter_user_group_related(queryset) + queryset = queryset.distinct() + return queryset + + def filter_user_group_related(self, queryset): + user_group = self.get_perm_user_group() + perms = queryset.filter(user_groups=user_group) + return perms + + def get_perm_user_group(self): + user_group_id = self.kwargs.get('perm_user_group_id') + user_group = get_object_or_404(UserGroup, pk=user_group_id) + return user_group + diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 59d4ab171..434f09441 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -34,8 +34,7 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r urlpatterns = [ - path('assets//gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), - path('assets//platform/', api.AssetPlatformRetrieveApi.as_view(), name='asset-platform-detail'), + # path('assets//gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), path('assets//tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'), path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'), path('assets//perm-users/', api.AssetPermUserListApi.as_view(), name='asset-perm-user-list'), diff --git a/apps/common/mixins/api/common.py b/apps/common/mixins/api/common.py index 3c59739ad..20e2eabc7 100644 --- a/apps/common/mixins/api/common.py +++ b/apps/common/mixins/api/common.py @@ -83,7 +83,8 @@ class RelationMixin: class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, - QuerySetMixin, RenderToJsonMixin): + QuerySetMixin, RenderToJsonMixin, + PaginatedResponseMixin): pass From ce13b194a5e2f38c9c31e0523ac483fd645aff00 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 6 Apr 2022 11:29:16 +0800 Subject: [PATCH 003/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20asset=20in?= =?UTF-8?q?fo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/__init__.py | 1 + apps/assets/api/asset/common.py | 2 +- apps/assets/api/asset/host.py | 15 +++++++++++++++ apps/assets/migrations/0092_hardware.py | 4 ++-- apps/assets/migrations/0093_auto_20220403_1627.py | 2 +- apps/assets/models/asset/host.py | 6 +++--- apps/assets/models/asset/remote_app.py | 5 +++++ apps/assets/serializers/asset/__init__.py | 1 + apps/assets/serializers/asset/host.py | 14 ++++++++++---- apps/assets/urls/api_urls.py | 1 + apps/common/mixins/api/serializer.py | 6 +++++- 11 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 apps/assets/api/asset/host.py diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py index 85972fb52..d34ec0922 100644 --- a/apps/assets/api/asset/__init__.py +++ b/apps/assets/api/asset/__init__.py @@ -1,2 +1,3 @@ from .common import * +from .host import * from .permission import * diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index 6b7fa7d2c..017986fbf 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -36,7 +36,7 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) 'protocols': ['exact', 'icontains'] } search_fields = ("hostname", "ip") - ordering_fields = ("hostname", "ip", "port", "cpu_cores") + ordering_fields = ("hostname", "ip", "port") ordering = ('hostname', ) serializer_classes = { 'default': serializers.AssetSerializer, diff --git a/apps/assets/api/asset/host.py b/apps/assets/api/asset/host.py new file mode 100644 index 000000000..a27d731f2 --- /dev/null +++ b/apps/assets/api/asset/host.py @@ -0,0 +1,15 @@ + +from assets.models import Host +from assets.serializers import HostSerializer +from .common import AssetViewSet + +__all__ = ['HostViewSet'] + + +class HostViewSet(AssetViewSet): + model = Host + + def get_serializer_classes(self): + serializer_classes = super().get_serializer_classes() + serializer_classes['default'] = HostSerializer + return serializer_classes diff --git a/apps/assets/migrations/0092_hardware.py b/apps/assets/migrations/0092_hardware.py index b158363ef..83d456b26 100644 --- a/apps/assets/migrations/0092_hardware.py +++ b/apps/assets/migrations/0092_hardware.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='HostInfo', + name='DeviceInfo', fields=[ ('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')), ('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')), @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='info', verbose_name='Host')), ], options={ - 'verbose_name': 'HostInfo', + 'verbose_name': 'DeviceInfo', }, ), ] diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py index e01febe9b..497a56c6c 100644 --- a/apps/assets/migrations/0093_auto_20220403_1627.py +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -7,7 +7,7 @@ from django.db import migrations def migrate_hardware(apps, *args): host_model = apps.get_model('assets', 'Host') asset_model = apps.get_model('assets', 'Asset') - hardware_model = apps.get_model('assets', 'HostInfo') + hardware_model = apps.get_model('assets', 'DeviceInfo') created = 0 batch_size = 1000 diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index f7533d5fd..ddaa1766d 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -9,8 +9,8 @@ class Host(Asset): pass -class HostInfo(CommonModelMixin): - host = models.OneToOneField(Host, related_name='info', on_delete=models.CASCADE, +class DeviceInfo(CommonModelMixin): + host = models.OneToOneField(Host, related_name='device_info', on_delete=models.CASCADE, verbose_name=_("Host"), unique=True) # Collect vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) @@ -53,4 +53,4 @@ class HostInfo(CommonModelMixin): return '{} of {}'.format(self.hardware_info, self.host.hostname) class Meta: - verbose_name = _("HostInfo") + verbose_name = _("DeviceInfo") diff --git a/apps/assets/models/asset/remote_app.py b/apps/assets/models/asset/remote_app.py index e69de29bb..60e8872c8 100644 --- a/apps/assets/models/asset/remote_app.py +++ b/apps/assets/models/asset/remote_app.py @@ -0,0 +1,5 @@ +from .common import Asset + + +class RemoteApp(Asset): + pass diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py index 55e5f844b..51640e7cf 100644 --- a/apps/assets/serializers/asset/__init__.py +++ b/apps/assets/serializers/asset/__init__.py @@ -1 +1,2 @@ from .common import * +from .host import * diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py index 1b40426cc..08d159713 100644 --- a/apps/assets/serializers/asset/host.py +++ b/apps/assets/serializers/asset/host.py @@ -1,12 +1,14 @@ from rest_framework import serializers from .common import AssetSerializer -from assets.models import HostInfo +from assets.models import DeviceInfo, Host + +__all__ = ['DeviceSerializer', 'HostSerializer'] -class HardwareSerializer(serializers.ModelSerializer): +class DeviceSerializer(serializers.ModelSerializer): class Meta: - model = HostInfo + model = DeviceInfo fields = [ 'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', @@ -16,4 +18,8 @@ class HardwareSerializer(serializers.ModelSerializer): class HostSerializer(AssetSerializer): - hardware_info = HardwareSerializer(read_only=True) + device_info = DeviceSerializer(read_only=True, allow_null=True) + + class Meta(AssetSerializer.Meta): + model = Host + fields = AssetSerializer.Meta.fields + ['device_info'] diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 434f09441..6788a7329 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -11,6 +11,7 @@ app_name = 'assets' router = BulkRouter() router.register(r'assets', api.AssetViewSet, 'asset') +router.register(r'hosts', api.HostViewSet, 'asset') router.register(r'accounts', api.AccountViewSet, 'account') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') diff --git a/apps/common/mixins/api/serializer.py b/apps/common/mixins/api/serializer.py index 52b0637df..a416fa990 100644 --- a/apps/common/mixins/api/serializer.py +++ b/apps/common/mixins/api/serializer.py @@ -15,8 +15,12 @@ class SerializerMixin: serializer_classes = None single_actions = ['put', 'retrieve', 'patch'] + def get_serializer_classes(self): + return getattr(self, 'serializer_classes', None) + def get_serializer_class_by_view_action(self): - if not hasattr(self, 'serializer_classes'): + serializer_classes = self.get_serializer_classes() + if serializer_classes is None: return None if not isinstance(self.serializer_classes, dict): return None From 1b9efff6c767167df59f3f456f29e105be4b9c7c Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 6 Apr 2022 18:14:51 +0800 Subject: [PATCH 004/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0092_hardware.py | 2 +- .../migrations/0095_auto_20220406_1541.py | 26 +++++++ .../migrations/0096_auto_20220406_1546.py | 53 ++++++++++++++ apps/assets/models/asset/_category.py | 73 +++++++++++++++++++ apps/assets/models/asset/common.py | 11 +++ apps/assets/models/asset/remote_app.py | 6 +- apps/assets/serializers/asset/common.py | 7 +- apps/xpack.bak | 1 - 8 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 apps/assets/migrations/0095_auto_20220406_1541.py create mode 100644 apps/assets/migrations/0096_auto_20220406_1546.py create mode 100644 apps/assets/models/asset/_category.py delete mode 160000 apps/xpack.bak diff --git a/apps/assets/migrations/0092_hardware.py b/apps/assets/migrations/0092_hardware.py index 83d456b26..55487cc22 100644 --- a/apps/assets/migrations/0092_hardware.py +++ b/apps/assets/migrations/0092_hardware.py @@ -33,7 +33,7 @@ class Migration(migrations.Migration): ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='info', verbose_name='Host')), + ('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='device_info', verbose_name='Host')), ], options={ 'verbose_name': 'DeviceInfo', diff --git a/apps/assets/migrations/0095_auto_20220406_1541.py b/apps/assets/migrations/0095_auto_20220406_1541.py new file mode 100644 index 000000000..7ba5fb36a --- /dev/null +++ b/apps/assets/migrations/0095_auto_20220406_1541.py @@ -0,0 +1,26 @@ +# Generated by Django 3.1.14 on 2022-04-06 07:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0094_auto_20220402_1736'), + ] + + operations = [ + migrations.AddField( + model_name='asset', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'Networking'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], default='host', max_length=16, verbose_name='Category'), + preserve_default=False, + ), + migrations.AddField( + model_name='asset', + name='type', + field=models.CharField(default='linux', max_length=128, verbose_name='Type'), + preserve_default=False, + ), + ] diff --git a/apps/assets/migrations/0096_auto_20220406_1546.py b/apps/assets/migrations/0096_auto_20220406_1546.py new file mode 100644 index 000000000..b4808fad5 --- /dev/null +++ b/apps/assets/migrations/0096_auto_20220406_1546.py @@ -0,0 +1,53 @@ +# Generated by Django 3.1.14 on 2022-04-06 07:46 +from itertools import groupby + +from django.db import migrations +from django.db.models import F + + +# ('Linux', 'Linux'), +# ('Unix', 'Unix'), +# ('MacOS', 'MacOS'), +# ('BSD', 'BSD'), +# ('Windows', 'Windows'), +# ('Other', 'Other'), + +category_mapper = { + 'Linux': ('host', 'linux'), + 'Unix': ('host', 'unix'), + 'MacOS': ('host', 'macos'), + 'BSD': ('host', 'unix'), + 'Windows': ('host', 'windows'), + 'Other': ('host', 'other_host'), +} + + +def migrate_category_and_type(apps, *args): + asset_model = apps.get_model('assets', 'Asset') + assets_bases = asset_model.objects.all()\ + .annotate(base=F("platform__base"))\ + .values_list('id', 'base')\ + .order_by('base') + base_assets = groupby(assets_bases, lambda a: a[1]) + + print("") + for base, grouper in base_assets: + asset_ids = [g[0] for g in grouper] + category_type = category_mapper.get(base) + if not category_type: + continue + print("Migrate {} to => {}".format(base, category_type)) + category, tp = category_type + assets = asset_model.objects.filter(id__in=asset_ids) + assets.update(category=category, type=tp) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0095_auto_20220406_1541'), + ] + + operations = [ + migrations.RunPython(migrate_category_and_type) + ] diff --git a/apps/assets/models/asset/_category.py b/apps/assets/models/asset/_category.py new file mode 100644 index 000000000..8b7f0fa0b --- /dev/null +++ b/apps/assets/models/asset/_category.py @@ -0,0 +1,73 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class Category(models.TextChoices): + HOST = 'host', _('Host') + NETWORK = 'network', _("Networking") + DATABASE = 'database', _("Database") + REMOTE_APP = 'remote_app', _("Remote app") + CLOUD = 'cloud', _("Clouding") + + +class HostTypes(models.TextChoices): + LINUX = 'linux', 'Linux' + UNIX = 'unix', 'Unix' + WINDOWS = 'windows', 'Windows' + MACOS = 'macos', 'MacOS' + MAINFRAME = 'mainframe', _("Mainframe") + OTHER_HOST = 'other_host', _("Other host") + + def __new__(cls, value): + """ + 添加 Category + :param value: + """ + obj = str.__new__(cls) + obj.category = Category.HOST + return obj + + +class NetworkTypes(models.TextChoices): + SWITCH = 'switch', _("Switch") + ROUTER = 'router', _("Router") + FIREWALL = 'firewall', _("Firewall") + OTHER_NETWORK = 'other_network', _("Other device") + + +class DatabaseTypes(models.TextChoices): + MYSQL = 'mysql', 'MySQL' + MARIADB = 'mariadb', 'MariaDB' + POSTGRESQL = 'postgresql', 'PostgreSQL' + ORACLE = 'oracle', 'Oracle' + SQLSERVER = 'sqlserver', 'SQLServer' + MONGODB = 'mongodb', 'MongoDB' + REDIS = 'redis', 'Redis' + + +class RemoteAppTypes(models.TextChoices): + CHROME = 'chrome', 'Chrome' + VSPHERE = 'vsphere', 'vSphere client' + MYSQL_WORKBENCH = 'mysql_workbench', 'MySQL workbench' + CUSTOM_REMOTE_APP = 'custom_remote_app', _("Custom") + + +class CloudTypes(models.TextChoices): + K8S = 'k8s', 'Kubernetes' + + +class AllTypes: + includes = [ + HostTypes, NetworkTypes, DatabaseTypes, + RemoteAppTypes, CloudTypes + ] + + @classmethod + def choices(cls): + choices = [] + for tp in cls.includes: + choices.extend(tp.choices) + return choices + + + diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 349765283..c8fc0ce23 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -15,6 +15,7 @@ from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager from ..platform import Platform from ..base import AbsConnectivity +from ._category import Category, AllTypes __all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster'] logger = logging.getLogger(__name__) @@ -125,6 +126,8 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) + category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category")) + type = models.CharField(max_length=128, choices=AllTypes.choices(), verbose_name=_("Type")) protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh, choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) @@ -180,6 +183,14 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): return False, warning return True, warning + @property + def category_display(self): + return self.get_category_display() + + @property + def type_display(self): + pass + @lazyproperty def platform_base(self): return self.platform.base diff --git a/apps/assets/models/asset/remote_app.py b/apps/assets/models/asset/remote_app.py index 60e8872c8..7eb7872c8 100644 --- a/apps/assets/models/asset/remote_app.py +++ b/apps/assets/models/asset/remote_app.py @@ -1,5 +1,9 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + from .common import Asset class RemoteApp(Asset): - pass + app_path = models.CharField(max_length=1024, verbose_name=_("App path")) + attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index ead6ca1f6..94283e1eb 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -77,9 +77,12 @@ class AssetSerializer(BulkOrgResourceModelSerializer): class Meta: model = Asset - fields_mini = ['id', 'hostname', 'ip', 'platform', 'protocols'] + fields_mini = [ + 'id', 'category', 'category_display', 'type', + 'hostname', 'ip', 'platform', 'protocols' + ] fields_small = fields_mini + [ - 'protocol', 'port', 'protocols', 'is_active', + 'protocol', 'port', 'is_active', 'public_ip', 'number', 'comment', ] fields_fk = [ diff --git a/apps/xpack.bak b/apps/xpack.bak deleted file mode 160000 index 244ace5a9..000000000 --- a/apps/xpack.bak +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 244ace5a95503ffaf41b73037692e1121f7c066f From d418c28e988a418613f6e95774fe491e42ffc9b5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 7 Apr 2022 18:51:35 +0800 Subject: [PATCH 005/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/serializers/login_asset_acl.py | 8 +- apps/assets/api/system_user.py | 3 +- apps/assets/const.py | 94 ++++++++++++++++++- .../migrations/0097_auto_20220407_1726.py | 34 +++++++ .../migrations/0098_auto_20220407_1730.py | 23 +++++ apps/assets/models/asset/_category.py | 73 -------------- apps/assets/models/asset/common.py | 13 +-- apps/assets/models/asset/database.py | 1 - apps/assets/models/asset/host.py | 5 +- apps/assets/models/asset/network.py | 5 + apps/assets/models/platform.py | 12 +-- apps/assets/models/user.py | 18 +--- apps/assets/serializers/asset/common.py | 4 +- apps/assets/serializers/system_user.py | 17 ++-- apps/authentication/models.py | 7 +- apps/common/db/models.py | 79 ++++++---------- apps/notifications/models/notification.py | 6 +- apps/notifications/models/site_msg.py | 6 +- apps/perms/models/asset_permission.py | 5 +- apps/rbac/models/role.py | 4 +- apps/rbac/models/rolebinding.py | 4 +- .../migrations/0048_auto_20220407_1726.py | 18 ++++ apps/terminal/models/session.py | 32 +------ utils/generate_fake_data/resources/assets.py | 3 +- 24 files changed, 256 insertions(+), 218 deletions(-) create mode 100644 apps/assets/migrations/0097_auto_20220407_1726.py create mode 100644 apps/assets/migrations/0098_auto_20220407_1730.py delete mode 100644 apps/assets/models/asset/_category.py create mode 100644 apps/terminal/migrations/0048_auto_20220407_1726.py diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index df62a04f8..34d6711c8 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -1,9 +1,11 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ + from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from assets.models import SystemUser -from acls import models from orgs.models import Organization +from assets.models import SystemUser +from assets.const import Protocol +from acls import models __all__ = ['LoginAssetACLSerializer'] @@ -54,7 +56,7 @@ class LoginAssetACLSystemUsersSerializer(serializers.Serializer): protocol_group = serializers.ListField( default=['*'], child=serializers.CharField(max_length=16), label=_('Protocol'), help_text=protocol_group_help_text.format( - ', '.join([SystemUser.Protocol.ssh, SystemUser.Protocol.telnet]) + ', '.join([Protocol.ssh, Protocol.telnet]) ) ) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index f679b4e3f..2739044de 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -10,6 +10,7 @@ from common.mixins.api import SuggestionMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics from orgs.utils import tmp_to_root_org +from assets.const import Protocol from ..models import SystemUser, CommandFilterRule from .. import serializers from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer @@ -56,7 +57,7 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet): """ API 获取可选的 su_from 系统用户""" queryset = self.filter_queryset(self.get_queryset()) queryset = queryset.filter( - protocol=SystemUser.Protocol.ssh, login_mode=SystemUser.LOGIN_AUTO + protocol=Protocol.ssh, login_mode=SystemUser.LOGIN_AUTO ) return self.get_paginate_response_if_need(queryset) diff --git a/apps/assets/const.py b/apps/assets/const.py index ec51c5a2b..ccd48205b 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -1,2 +1,92 @@ -# -*- coding: utf-8 -*- -# +from django.db import models +from django.utils.translation import gettext_lazy as _ +from common.db.models import IncludesTextChoicesMeta + + +__all__ = [ + 'Category', 'HostTypes', 'NetworkTypes', 'DatabaseTypes', + 'RemoteAppTypes', 'CloudTypes', 'Protocol', 'AllTypes', +] + + +class Category(models.TextChoices): + HOST = 'host', _('Host') + NETWORK = 'network', _("Networking") + DATABASE = 'database', _("Database") + REMOTE_APP = 'remote_app', _("Remote app") + CLOUD = 'cloud', _("Clouding") + + +class HostTypes(models.TextChoices): + LINUX = 'linux', 'Linux' + WINDOWS = 'windows', 'Windows' + UNIX = 'unix', 'Unix' + BSD = 'bsd', 'BSD' + MACOS = 'macos', 'MacOS' + MAINFRAME = 'mainframe', _("Mainframe") + OTHER_HOST = 'other_host', _("Other host") + + +class NetworkTypes(models.TextChoices): + SWITCH = 'switch', _("Switch") + ROUTER = 'router', _("Router") + FIREWALL = 'firewall', _("Firewall") + OTHER_NETWORK = 'other_network', _("Other device") + + +class DatabaseTypes(models.TextChoices): + MYSQL = 'mysql', 'MySQL' + MARIADB = 'mariadb', 'MariaDB' + POSTGRESQL = 'postgresql', 'PostgreSQL' + ORACLE = 'oracle', 'Oracle' + SQLSERVER = 'sqlserver', 'SQLServer' + MONGODB = 'mongodb', 'MongoDB' + REDIS = 'redis', 'Redis' + + +class RemoteAppTypes(models.TextChoices): + CHROME = 'chrome', 'Chrome' + VSPHERE = 'vsphere', 'vSphere client' + MYSQL_WORKBENCH = 'mysql_workbench', 'MySQL workbench' + CUSTOM_REMOTE_APP = 'custom_remote_app', _("Custom") + + +class CloudTypes(models.TextChoices): + K8S = 'k8s', 'Kubernetes' + + +class AllTypes(metaclass=IncludesTextChoicesMeta): + choices: list + includes = [ + HostTypes, NetworkTypes, DatabaseTypes, + RemoteAppTypes, CloudTypes + ] + + +class Protocol(models.TextChoices): + ssh = 'ssh', 'SSH' + rdp = 'rdp', 'RDP' + telnet = 'telnet', 'Telnet' + vnc = 'vnc', 'VNC' + + mysql = 'mysql', 'MySQL' + mariadb = 'mariadb', 'MariaDB' + oracle = 'oracle', 'Oracle' + postgresql = 'postgresql', 'PostgreSQL' + sqlserver = 'sqlserver', 'SQLServer' + redis = 'redis', 'Redis' + mongodb = 'mongodb', 'MongoDB' + + k8s = 'k8s', 'K8S' + + @classmethod + def host_protocols(cls): + return [cls.ssh, cls.rdp, cls.telnet, cls.vnc] + + @classmethod + def db_protocols(cls): + return [ + cls.mysql, cls.mariadb, cls.postgresql, cls.oracle, + cls.sqlserver, cls.redis, cls.mongodb, + ] + diff --git a/apps/assets/migrations/0097_auto_20220407_1726.py b/apps/assets/migrations/0097_auto_20220407_1726.py new file mode 100644 index 000000000..5459d8cee --- /dev/null +++ b/apps/assets/migrations/0097_auto_20220407_1726.py @@ -0,0 +1,34 @@ +# Generated by Django 3.1.14 on 2022-04-07 09:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0096_auto_20220406_1546'), + ] + + operations = [ + migrations.RenameField( + model_name='platform', + old_name='base', + new_name='type', + ), + migrations.AddField( + model_name='platform', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'Networking'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], default='host', max_length=16, verbose_name='Category'), + preserve_default=False, + ), + migrations.AlterField( + model_name='asset', + name='type', + field=models.CharField(choices=[('linux', 'Linux'), ('unix', 'Unix'), ('windows', 'Windows'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), + ), + migrations.AlterField( + model_name='systemuser', + name='protocol', + field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol'), + ), + ] diff --git a/apps/assets/migrations/0098_auto_20220407_1730.py b/apps/assets/migrations/0098_auto_20220407_1730.py new file mode 100644 index 000000000..a50986cbc --- /dev/null +++ b/apps/assets/migrations/0098_auto_20220407_1730.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.14 on 2022-04-07 09:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0097_auto_20220407_1726'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='type', + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), + ), + migrations.AlterField( + model_name='platform', + name='type', + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Base'), + ), + ] diff --git a/apps/assets/models/asset/_category.py b/apps/assets/models/asset/_category.py deleted file mode 100644 index 8b7f0fa0b..000000000 --- a/apps/assets/models/asset/_category.py +++ /dev/null @@ -1,73 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - - -class Category(models.TextChoices): - HOST = 'host', _('Host') - NETWORK = 'network', _("Networking") - DATABASE = 'database', _("Database") - REMOTE_APP = 'remote_app', _("Remote app") - CLOUD = 'cloud', _("Clouding") - - -class HostTypes(models.TextChoices): - LINUX = 'linux', 'Linux' - UNIX = 'unix', 'Unix' - WINDOWS = 'windows', 'Windows' - MACOS = 'macos', 'MacOS' - MAINFRAME = 'mainframe', _("Mainframe") - OTHER_HOST = 'other_host', _("Other host") - - def __new__(cls, value): - """ - 添加 Category - :param value: - """ - obj = str.__new__(cls) - obj.category = Category.HOST - return obj - - -class NetworkTypes(models.TextChoices): - SWITCH = 'switch', _("Switch") - ROUTER = 'router', _("Router") - FIREWALL = 'firewall', _("Firewall") - OTHER_NETWORK = 'other_network', _("Other device") - - -class DatabaseTypes(models.TextChoices): - MYSQL = 'mysql', 'MySQL' - MARIADB = 'mariadb', 'MariaDB' - POSTGRESQL = 'postgresql', 'PostgreSQL' - ORACLE = 'oracle', 'Oracle' - SQLSERVER = 'sqlserver', 'SQLServer' - MONGODB = 'mongodb', 'MongoDB' - REDIS = 'redis', 'Redis' - - -class RemoteAppTypes(models.TextChoices): - CHROME = 'chrome', 'Chrome' - VSPHERE = 'vsphere', 'vSphere client' - MYSQL_WORKBENCH = 'mysql_workbench', 'MySQL workbench' - CUSTOM_REMOTE_APP = 'custom_remote_app', _("Custom") - - -class CloudTypes(models.TextChoices): - K8S = 'k8s', 'Kubernetes' - - -class AllTypes: - includes = [ - HostTypes, NetworkTypes, DatabaseTypes, - RemoteAppTypes, CloudTypes - ] - - @classmethod - def choices(cls): - choices = [] - for tp in cls.includes: - choices.extend(tp.choices) - return choices - - - diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index c8fc0ce23..05404ff94 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -13,9 +13,9 @@ from rest_framework.exceptions import ValidationError from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager +from assets.const import Category, AllTypes from ..platform import Platform from ..base import AbsConnectivity -from ._category import Category, AllTypes __all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster'] logger = logging.getLogger(__name__) @@ -123,11 +123,12 @@ class NodesRelationMixin: class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): + Category = Category id = models.UUIDField(default=uuid.uuid4, primary_key=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category")) - type = models.CharField(max_length=128, choices=AllTypes.choices(), verbose_name=_("Type")) + type = models.CharField(max_length=128, choices=AllTypes.choices, verbose_name=_("Type")) protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh, choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) @@ -183,14 +184,6 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): return False, warning return True, warning - @property - def category_display(self): - return self.get_category_display() - - @property - def type_display(self): - pass - @lazyproperty def platform_base(self): return self.platform.base diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index ff0be89a7..84d174277 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -6,4 +6,3 @@ from .common import Asset class Database(Asset): database = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) - diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index ddaa1766d..a729c2da6 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,12 +1,15 @@ from django.utils.translation import gettext_lazy as _ from django.db import models +from assets.const import Category from common.mixins.models import CommonModelMixin from .common import Asset class Host(Asset): - pass + def save(self, *args, **kwargs): + self.category = Category.HOST + return super().save(*args, **kwargs) class DeviceInfo(CommonModelMixin): diff --git a/apps/assets/models/asset/network.py b/apps/assets/models/asset/network.py index e69de29bb..42d35e06b 100644 --- a/apps/assets/models/asset/network.py +++ b/apps/assets/models/asset/network.py @@ -0,0 +1,5 @@ +from .common import Asset + + +class Network(Asset): + pass diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 5b8aafe7e..63ea42ff1 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -1,19 +1,12 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from assets.const import Category, AllTypes from common.fields.model import JsonDictTextField __all__ = ['Platform'] -class Category(models.TextChoices): - Host = 'host', _('Host') - Network = 'network', _('Network device') - Database = 'database', _('Database') - RemoteApp = 'remote_app', _('Microsoft remote app') - Cloud = 'cloud', _("Cloud") - - class Platform(models.Model): CHARSET_CHOICES = ( ('utf8', 'UTF-8'), @@ -28,7 +21,8 @@ class Platform(models.Model): ('Other', 'Other'), ) name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) - base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base")) + category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category")) + type = models.CharField(choices=AllTypes.choices, max_length=32, default='Linux', verbose_name=_("Base")) charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) internal = models.BooleanField(default=False, verbose_name=_("Internal")) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index ce0029768..e179c9b22 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -10,6 +10,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator from django.core.cache import cache from common.utils import signer, get_object_or_none +from assets.const import Protocol from .base import BaseUser from .asset import Asset from .authbook import AuthBook @@ -21,20 +22,7 @@ logger = logging.getLogger(__name__) class ProtocolMixin: protocol: str - - class Protocol(models.TextChoices): - ssh = 'ssh', 'SSH' - rdp = 'rdp', 'RDP' - telnet = 'telnet', 'Telnet' - vnc = 'vnc', 'VNC' - mysql = 'mysql', 'MySQL' - oracle = 'oracle', 'Oracle' - mariadb = 'mariadb', 'MariaDB' - postgresql = 'postgresql', 'PostgreSQL' - sqlserver = 'sqlserver', 'SQLServer' - redis = 'redis', 'Redis' - mongodb = 'mongodb', 'MongoDB' - k8s = 'k8s', 'K8S' + Protocol = Protocol SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp] @@ -245,7 +233,7 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser): groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) - protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) + protocol = models.CharField(max_length=16, choices=Protocol.choices, default='ssh', verbose_name=_('Protocol')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 94283e1eb..94e5be260 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -70,6 +70,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer): labels_display = serializers.ListField( child=serializers.CharField(), label=_('Labels name'), required=False, read_only=True ) + category_display = serializers.ReadOnlyField(source='get_category_display', label=_("Category display")) + type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) """ 资产的数据结构 @@ -78,7 +80,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): class Meta: model = Asset fields_mini = [ - 'id', 'category', 'category_display', 'type', + 'id', 'category', 'category_display', 'type', 'type_display', 'hostname', 'ip', 'platform', 'protocols' ] fields_small = fields_mini + [ diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 850870762..4ede6ac65 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -6,6 +6,7 @@ from common.mixins.serializers import BulkSerializerMixin from common.utils import ssh_pubkey_gen from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from assets.const import Protocol from ..models import SystemUser, Asset from .utils import validate_password_contains_left_double_curly_bracket from .base import AuthSerializerMixin @@ -107,9 +108,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): def validate_username(self, username): protocol = self.get_initial_value("protocol") if username: - if protocol == SystemUser.Protocol.telnet: + if protocol == Protocol.telnet: regx = alphanumeric_cn_re - elif protocol == SystemUser.Protocol.rdp: + elif protocol == Protocol.rdp: regx = alphanumeric_win_re else: regx = alphanumeric_re @@ -122,8 +123,8 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): return '' login_mode = self.get_initial_value("login_mode") - if login_mode == SystemUser.LOGIN_AUTO and protocol != SystemUser.Protocol.vnc \ - and protocol != SystemUser.Protocol.redis: + if login_mode == SystemUser.LOGIN_AUTO and protocol != Protocol.vnc \ + and protocol != Protocol.redis: msg = _('* Automatic login mode must fill in the username.') raise serializers.ValidationError(msg) return username @@ -163,8 +164,8 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): error = _('This field is required.') raise serializers.ValidationError(error) # self: protocol ssh - protocol = self.get_initial_value('protocol', default=SystemUser.Protocol.ssh.value) - if protocol not in [SystemUser.Protocol.ssh.value]: + protocol = self.get_initial_value('protocol', default=Protocol.ssh.value) + if protocol not in [Protocol.ssh.value]: error = _('Only ssh protocol system users are allowed') raise serializers.ValidationError(error) # su_from: protocol same @@ -184,7 +185,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): tp = attrs.get('type') if tp != SystemUser.Type.admin: return attrs - attrs['protocol'] = SystemUser.Protocol.ssh + attrs['protocol'] = Protocol.ssh attrs['login_mode'] = SystemUser.LOGIN_AUTO attrs['username_same_with_user'] = False attrs['auto_push'] = False @@ -202,7 +203,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): if auto_gen_key and not self.instance: password = SystemUser.gen_password() attrs['password'] = password - if protocol == SystemUser.Protocol.ssh: + if protocol == Protocol.ssh: private_key, public_key = SystemUser.gen_key(username) attrs['private_key'] = private_key attrs['public_key'] = public_key diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 1b353f737..3b469eb95 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -3,8 +3,9 @@ import uuid from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from django.conf import settings +from django.db import models -from common.db import models +from common.db.models import BaseCreateUpdateModel class AccessKey(models.Model): @@ -40,7 +41,7 @@ class PrivateToken(Token): verbose_name = _('Private Token') -class SSOToken(models.JMSBaseModel): +class SSOToken(BaseCreateUpdateModel): """ 类似腾讯企业邮的 [单点登录](https://exmail.qq.com/qy_mng_logic/doc#10036) 出于安全考虑,这里的 `token` 使用一次随即过期。但我们保留每一个生成过的 `token`。 @@ -53,7 +54,7 @@ class SSOToken(models.JMSBaseModel): verbose_name = _('SSO token') -class ConnectionToken(models.JMSBaseModel): +class ConnectionToken(BaseCreateUpdateModel): # Todo: 未来可能放到这里,不记录到 redis 了,虽然方便,但是不易于审计 # Todo: add connection token 可能要授权给 普通用户, 或者放开就行 diff --git a/apps/common/db/models.py b/apps/common/db/models.py index 2989f734e..a871de31c 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -13,57 +13,34 @@ import uuid from functools import reduce, partial import inspect -from django.db.models import * +from django.db import models +from django.db.models import F, Value, ExpressionWrapper +from enum import _EnumDict from django.db.models import QuerySet from django.db.models.functions import Concat from django.utils.translation import ugettext_lazy as _ -class Choice(str): - def __new__(cls, value, label=''): # `deepcopy` 的时候不会传 `label` - self = super().__new__(cls, value) - self.label = label - return self +class IncludesTextChoicesMeta(type): + def __new__(metacls, classname, bases, classdict): + includes = classdict.pop('includes', None) + assert includes + attrs = _EnumDict() + for k, v in classdict.items(): + attrs[k] = v -class ChoiceSetType(type): - def __new__(cls, name, bases, attrs): - _choices = [] - collected = set() - new_attrs = {} - for k, v in attrs.items(): - if isinstance(v, tuple): - v = Choice(*v) - assert v not in collected, 'Cannot be defined repeatedly' - _choices.append(v) - collected.add(v) - new_attrs[k] = v - for base in bases: - if hasattr(base, '_choices'): - for c in base._choices: - if c not in collected: - _choices.append(c) - collected.add(c) - new_attrs['_choices'] = _choices - new_attrs['_choices_dict'] = {c: c.label for c in _choices} - return type.__new__(cls, name, bases, new_attrs) + for cls in includes: + _member_names_ = cls._member_names_ + _member_map_ = cls._member_map_ + _value2label_map_ = cls._value2label_map_ - def __contains__(self, item): - return self._choices_dict.__contains__(item) - - def __getitem__(self, item): - return self._choices_dict.__getitem__(item) - - def get(self, item, default=None): - return self._choices_dict.get(item, default) - - @property - def choices(self): - return [(c, c.label) for c in self._choices] - - -class ChoiceSet(metaclass=ChoiceSetType): - choices = None # 用于 Django Model 中的 choices 配置, 为了代码提示在此声明 + for name in _member_names_: + value = str(_member_map_[name]) + label = _value2label_map_[value] + attrs[name] = value, label + bases = (models.TextChoices,) + return type(classname, bases, attrs) class BitOperationChoice: @@ -107,18 +84,18 @@ class BitOperationChoice: return [(cls.NAME_MAP[i], j) for i, j in cls.DB_CHOICES] -class JMSBaseModel(Model): - created_by = CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) - updated_by = CharField(max_length=32, null=True, blank=True, verbose_name=_('Updated by')) - date_created = DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) - date_updated = DateTimeField(auto_now=True, verbose_name=_('Date updated')) +class BaseCreateUpdateModel(models.Model): + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) + updated_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Updated by')) + date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) + date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) class Meta: abstract = True -class JMSModel(JMSBaseModel): - id = UUIDField(default=uuid.uuid4, primary_key=True) +class JMSBaseModel(BaseCreateUpdateModel): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) class Meta: abstract = True @@ -129,7 +106,7 @@ def concated_display(name1, name2): def output_as_string(field_name): - return ExpressionWrapper(F(field_name), output_field=CharField()) + return ExpressionWrapper(F(field_name), output_field=models.CharField()) class UnionQuerySet(QuerySet): diff --git a/apps/notifications/models/notification.py b/apps/notifications/models/notification.py index d50168576..a3095fbe5 100644 --- a/apps/notifications/models/notification.py +++ b/apps/notifications/models/notification.py @@ -1,11 +1,11 @@ from django.db import models -from common.db.models import JMSModel +from common.db.models import JMSBaseModel __all__ = ('SystemMsgSubscription', 'UserMsgSubscription') -class UserMsgSubscription(JMSModel): +class UserMsgSubscription(JMSBaseModel): user = models.OneToOneField('users.User', related_name='user_msg_subscription', on_delete=models.CASCADE) receive_backends = models.JSONField(default=list) @@ -13,7 +13,7 @@ class UserMsgSubscription(JMSModel): return f'{self.user} subscription: {self.receive_backends}' -class SystemMsgSubscription(JMSModel): +class SystemMsgSubscription(JMSBaseModel): message_type = models.CharField(max_length=128, unique=True) users = models.ManyToManyField('users.User', related_name='system_msg_subscriptions') groups = models.ManyToManyField('users.UserGroup', related_name='system_msg_subscriptions') diff --git a/apps/notifications/models/site_msg.py b/apps/notifications/models/site_msg.py index 3e3c09baa..e08cd5c71 100644 --- a/apps/notifications/models/site_msg.py +++ b/apps/notifications/models/site_msg.py @@ -1,18 +1,18 @@ from django.db import models -from common.db.models import JMSModel +from common.db.models import JMSBaseModel __all__ = ('SiteMessageUsers', 'SiteMessage') -class SiteMessageUsers(JMSModel): +class SiteMessageUsers(JMSBaseModel): sitemessage = models.ForeignKey('notifications.SiteMessage', on_delete=models.CASCADE, db_constraint=False, related_name='m2m_sitemessageusers') user = models.ForeignKey('users.User', on_delete=models.CASCADE, db_constraint=False, related_name='m2m_sitemessageusers') has_read = models.BooleanField(default=False) read_at = models.DateTimeField(default=None, null=True) -class SiteMessage(JMSModel): +class SiteMessage(JMSBaseModel): subject = models.CharField(max_length=1024) message = models.TextField() users = models.ManyToManyField( diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index ea795d889..b258e6742 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -2,10 +2,11 @@ import logging from django.utils.translation import ugettext_lazy as _ from django.db.models import F, TextChoices +from django.db import models from orgs.mixins.models import OrgModelMixin -from common.db import models from common.utils import lazyproperty +from common.db.models import BaseCreateUpdateModel from assets.models import Asset, SystemUser, Node, FamilyMixin from .base import BasePermission @@ -89,7 +90,7 @@ class AssetPermission(BasePermission): return names -class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, models.JMSBaseModel): +class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): class NodeFrom(TextChoices): granted = 'granted', 'Direct node granted' child = 'child', 'Have children node' diff --git a/apps/rbac/models/role.py b/apps/rbac/models/role.py index b47bccb5b..631d979b7 100644 --- a/apps/rbac/models/role.py +++ b/apps/rbac/models/role.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _, gettext from django.db import models -from common.db.models import JMSModel +from common.db.models import JMSBaseModel from common.utils import lazyproperty from .permission import Permission from ..builtin import BuiltinRole @@ -22,7 +22,7 @@ class OrgRoleManager(models.Manager): return queryset.filter(scope=const.Scope.org) -class Role(JMSModel): +class Role(JMSBaseModel): """ 定义 角色 | 角色-权限 关系 """ Scope = const.Scope diff --git a/apps/rbac/models/rolebinding.py b/apps/rbac/models/rolebinding.py index a2ee06022..a871b4afe 100644 --- a/apps/rbac/models/rolebinding.py +++ b/apps/rbac/models/rolebinding.py @@ -4,7 +4,7 @@ from django.db.models import Q from django.core.exceptions import ValidationError from rest_framework.serializers import ValidationError -from common.db.models import JMSModel +from common.db.models import JMSBaseModel from common.utils import lazyproperty from orgs.utils import current_org from .role import Role @@ -29,7 +29,7 @@ class RoleBindingManager(models.Manager): return self.get_queryset() -class RoleBinding(JMSModel): +class RoleBinding(JMSBaseModel): Scope = Scope """ 定义 用户-角色 关系 """ scope = models.CharField( diff --git a/apps/terminal/migrations/0048_auto_20220407_1726.py b/apps/terminal/migrations/0048_auto_20220407_1726.py new file mode 100644 index 000000000..1eb432efc --- /dev/null +++ b/apps/terminal/migrations/0048_auto_20220407_1726.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2022-04-07 09:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0047_auto_20220302_1951'), + ] + + operations = [ + migrations.AlterField( + model_name='session', + name='protocol', + field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], db_index=True, default='ssh', max_length=16), + ), + ] diff --git a/apps/terminal/models/session.py b/apps/terminal/models/session.py index 09f1c9e91..e1a65ceaa 100644 --- a/apps/terminal/models/session.py +++ b/apps/terminal/models/session.py @@ -11,6 +11,7 @@ from django.core.files.storage import default_storage from django.core.cache import cache from assets.models import Asset +from assets.const import Protocol from users.models import User from orgs.mixins.models import OrgModelMixin from django.db.models import TextChoices @@ -24,20 +25,6 @@ class Session(OrgModelMixin): WT = 'WT', 'Web Terminal' DT = 'DT', 'DB Terminal' - class PROTOCOL(TextChoices): - SSH = 'ssh', 'ssh' - RDP = 'rdp', 'rdp' - VNC = 'vnc', 'vnc' - TELNET = 'telnet', 'telnet' - MYSQL = 'mysql', 'mysql' - ORACLE = 'oracle', 'oracle' - MARIADB = 'mariadb', 'mariadb' - SQLSERVER = 'sqlserver', 'sqlserver' - POSTGRESQL = 'postgresql', 'postgresql' - REDIS = 'redis', 'redis' - MONGODB = 'mongodb', 'MongoDB' - K8S = 'k8s', 'kubernetes' - id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=128, verbose_name=_("User"), db_index=True) user_id = models.CharField(blank=True, default='', max_length=36, db_index=True) @@ -52,7 +39,7 @@ class Session(OrgModelMixin): has_replay = models.BooleanField(default=False, verbose_name=_("Replay")) has_command = models.BooleanField(default=False, verbose_name=_("Command")) terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.DO_NOTHING, db_constraint=False) - protocol = models.CharField(choices=PROTOCOL.choices, default='ssh', max_length=16, db_index=True) + protocol = models.CharField(choices=Protocol.choices, default='ssh', max_length=16, db_index=True) date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) @@ -131,29 +118,20 @@ class Session(OrgModelMixin): @property def can_join(self): - _PROTOCOL = self.PROTOCOL if self.is_finished: return False if self.login_from == self.LOGIN_FROM.RT: return False - if self.protocol in [ - _PROTOCOL.SSH, _PROTOCOL.VNC, _PROTOCOL.RDP, - _PROTOCOL.TELNET, _PROTOCOL.K8S + if Protocol in [ + Protocol.SSH, Protocol.VNC, Protocol.RDP, + Protocol.TELNET, Protocol.K8S ]: return True else: return False - @property - def db_protocols(self): - _PROTOCOL = self.PROTOCOL - return [_PROTOCOL.MYSQL, _PROTOCOL.MARIADB, _PROTOCOL.ORACLE, - _PROTOCOL.POSTGRESQL, _PROTOCOL.SQLSERVER, - _PROTOCOL.REDIS, _PROTOCOL.MONGODB] - @property def can_terminate(self): - _PROTOCOL = self.PROTOCOL if self.is_finished: return False else: diff --git a/utils/generate_fake_data/resources/assets.py b/utils/generate_fake_data/resources/assets.py index 3aa11cc99..de97a27a9 100644 --- a/utils/generate_fake_data/resources/assets.py +++ b/utils/generate_fake_data/resources/assets.py @@ -5,6 +5,7 @@ import forgery_py from .base import FakeDataGenerator from assets.models import * +from assets.const import Protocol class AdminUsersGenerator(FakeDataGenerator): @@ -28,7 +29,7 @@ class AdminUsersGenerator(FakeDataGenerator): class SystemUsersGenerator(FakeDataGenerator): def do_generate(self, batch, batch_size): system_users = [] - protocols = list(dict(SystemUser.Protocol.choices).keys()) + protocols = list(dict(Protocol.choices).keys()) for i in batch: username = forgery_py.internet.user_name(True) protocol = random.choice(protocols) From 9f927f97034e4663f151f836dd9c1cefbad137b9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 Apr 2022 17:53:56 +0800 Subject: [PATCH 006/488] stash --- apps/assets/api/asset/common.py | 1 - apps/assets/api/platform.py | 2 +- .../migrations/0095_auto_20220406_1541.py | 2 +- .../migrations/0097_auto_20220407_1726.py | 4 +- .../migrations/0098_auto_20220407_1730.py | 23 -------- apps/assets/models/asset/__init__.py | 1 + apps/assets/models/asset/device_info.py | 51 ++++++++++++++++++ apps/assets/models/asset/host.py | 52 ++----------------- apps/assets/models/asset/network.py | 7 ++- apps/assets/models/platform.py | 14 ++--- apps/assets/serializers/asset/common.py | 2 +- 11 files changed, 71 insertions(+), 88 deletions(-) delete mode 100644 apps/assets/migrations/0098_auto_20220407_1730.py create mode 100644 apps/assets/models/asset/device_info.py diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index 017986fbf..4cec7495a 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -31,7 +31,6 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) 'hostname': ['exact'], 'ip': ['exact'], 'system_users__id': ['exact'], - 'platform__base': ['exact'], 'is_active': ['exact'], 'protocols': ['exact', 'icontains'] } diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index a7cb798ae..030f90905 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -10,7 +10,7 @@ __all__ = ['AssetPlatformViewSet'] class AssetPlatformViewSet(ModelViewSet): queryset = Platform.objects.all() serializer_class = PlatformSerializer - filterset_fields = ['name', 'base'] + filterset_fields = ['name'] search_fields = ['name'] def check_object_permissions(self, request, obj): diff --git a/apps/assets/migrations/0095_auto_20220406_1541.py b/apps/assets/migrations/0095_auto_20220406_1541.py index 7ba5fb36a..9647aed54 100644 --- a/apps/assets/migrations/0095_auto_20220406_1541.py +++ b/apps/assets/migrations/0095_auto_20220406_1541.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='asset', name='type', - field=models.CharField(default='linux', max_length=128, verbose_name='Type'), + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), preserve_default=False, ), ] diff --git a/apps/assets/migrations/0097_auto_20220407_1726.py b/apps/assets/migrations/0097_auto_20220407_1726.py index 5459d8cee..e16a6ed92 100644 --- a/apps/assets/migrations/0097_auto_20220407_1726.py +++ b/apps/assets/migrations/0097_auto_20220407_1726.py @@ -22,9 +22,9 @@ class Migration(migrations.Migration): preserve_default=False, ), migrations.AlterField( - model_name='asset', + model_name='platform', name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('unix', 'Unix'), ('windows', 'Windows'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), ), migrations.AlterField( model_name='systemuser', diff --git a/apps/assets/migrations/0098_auto_20220407_1730.py b/apps/assets/migrations/0098_auto_20220407_1730.py deleted file mode 100644 index a50986cbc..000000000 --- a/apps/assets/migrations/0098_auto_20220407_1730.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1.14 on 2022-04-07 09:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0097_auto_20220407_1726'), - ] - - operations = [ - migrations.AlterField( - model_name='asset', - name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), - ), - migrations.AlterField( - model_name='platform', - name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Base'), - ), - ] diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py index 51640e7cf..8f2ad65ee 100644 --- a/apps/assets/models/asset/__init__.py +++ b/apps/assets/models/asset/__init__.py @@ -1,2 +1,3 @@ from .common import * +from .device_info import * from .host import * diff --git a/apps/assets/models/asset/device_info.py b/apps/assets/models/asset/device_info.py new file mode 100644 index 000000000..7410af04f --- /dev/null +++ b/apps/assets/models/asset/device_info.py @@ -0,0 +1,51 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from common.mixins.models import CommonModelMixin + +__all__ = ['DeviceInfo'] + + +class DeviceInfo(CommonModelMixin): + # Collect + vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) + model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) + sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) + + cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) + cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) + cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) + cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) + memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) + disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) + disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) + + os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) + os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) + os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) + hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) + + @property + def cpu_info(self): + info = "" + if self.cpu_model: + info += self.cpu_model + if self.cpu_count and self.cpu_cores: + info += "{}*{}".format(self.cpu_count, self.cpu_cores) + return info + + @property + def hardware_info(self): + if self.cpu_count: + return '{} Core {} {}'.format( + self.cpu_vcpus or self.cpu_count * self.cpu_cores, + self.memory, self.disk_total + ) + else: + return '' + + def __str__(self): + return '{} of {}'.format(self.hardware_info, self.host.hostname) + + class Meta: + verbose_name = _("DeviceInfo") diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index a729c2da6..3dbe4ca3d 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,59 +1,17 @@ -from django.utils.translation import gettext_lazy as _ from django.db import models +from django.utils.translation import gettext_lazy as _ from assets.const import Category -from common.mixins.models import CommonModelMixin from .common import Asset +from .device_info import DeviceInfo class Host(Asset): + device_info = models.ForeignKey(DeviceInfo, on_delete=models.SET_NULL, + null=True, verbose_name=_("Device info")) + def save(self, *args, **kwargs): self.category = Category.HOST return super().save(*args, **kwargs) -class DeviceInfo(CommonModelMixin): - host = models.OneToOneField(Host, related_name='device_info', on_delete=models.CASCADE, - verbose_name=_("Host"), unique=True) - # Collect - vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) - model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) - sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) - - cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) - cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) - cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) - cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) - memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) - disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) - disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) - - os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) - os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) - os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) - hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) - - @property - def cpu_info(self): - info = "" - if self.cpu_model: - info += self.cpu_model - if self.cpu_count and self.cpu_cores: - info += "{}*{}".format(self.cpu_count, self.cpu_cores) - return info - - @property - def hardware_info(self): - if self.cpu_count: - return '{} Core {} {}'.format( - self.cpu_vcpus or self.cpu_count * self.cpu_cores, - self.memory, self.disk_total - ) - else: - return '' - - def __str__(self): - return '{} of {}'.format(self.hardware_info, self.host.hostname) - - class Meta: - verbose_name = _("DeviceInfo") diff --git a/apps/assets/models/asset/network.py b/apps/assets/models/asset/network.py index 42d35e06b..a17d99a2e 100644 --- a/apps/assets/models/asset/network.py +++ b/apps/assets/models/asset/network.py @@ -1,5 +1,10 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + from .common import Asset +from .device_info import DeviceInfo class Network(Asset): - pass + device_info = models.ForeignKey(DeviceInfo, on_delete=models.SET_NULL, + null=True, verbose_name=_("Device info")) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 63ea42ff1..675d3542f 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -12,17 +12,9 @@ class Platform(models.Model): ('utf8', 'UTF-8'), ('gbk', 'GBK'), ) - BASE_CHOICES = ( - ('Linux', 'Linux'), - ('Unix', 'Unix'), - ('MacOS', 'MacOS'), - ('BSD', 'BSD'), - ('Windows', 'Windows'), - ('Other', 'Other'), - ) name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category")) - type = models.CharField(choices=AllTypes.choices, max_length=32, default='Linux', verbose_name=_("Base")) + type = models.CharField(choices=AllTypes.choices, max_length=32, default='Linux', verbose_name=_("Type")) charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) internal = models.BooleanField(default=False, verbose_name=_("Internal")) @@ -36,10 +28,10 @@ class Platform(models.Model): return linux.id def is_windows(self): - return self.base.lower() in ('windows',) + return self.type.lower() in ('windows',) def is_unixlike(self): - return self.base.lower() in ("linux", "unix", "macos", "bsd") + return self.type.lower() in ("linux", "unix", "macos", "bsd") def __str__(self): return self.name diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 94e5be260..4070c6fc7 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -182,7 +182,7 @@ class PlatformSerializer(serializers.ModelSerializer): class Meta: model = Platform fields = [ - 'id', 'name', 'base', 'charset', + 'id', 'name', 'category', 'type', 'charset', 'internal', 'meta', 'comment' ] From 5101aae5ae9eaab6f989e3e5ca8b13baa483c553 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 Apr 2022 19:24:59 +0800 Subject: [PATCH 007/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0092_hardware.py | 1 - .../migrations/0093_auto_20220403_1627.py | 16 ++++++++----- .../migrations/0098_auto_20220412_1907.py | 23 +++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 apps/assets/migrations/0098_auto_20220412_1907.py diff --git a/apps/assets/migrations/0092_hardware.py b/apps/assets/migrations/0092_hardware.py index 55487cc22..26d026640 100644 --- a/apps/assets/migrations/0092_hardware.py +++ b/apps/assets/migrations/0092_hardware.py @@ -33,7 +33,6 @@ class Migration(migrations.Migration): ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='device_info', verbose_name='Host')), ], options={ 'verbose_name': 'DeviceInfo', diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py index 497a56c6c..9aa3be14d 100644 --- a/apps/assets/migrations/0093_auto_20220403_1627.py +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -1,7 +1,7 @@ # Generated by Django 3.1.14 on 2022-04-02 08:27 from django.utils import timezone -from django.db import migrations +from django.db import migrations, models def migrate_hardware(apps, *args): @@ -23,10 +23,10 @@ def migrate_hardware(apps, *args): asset_ids = [h.asset_ptr_id for h in hosts] assets = asset_model.objects.filter(id__in=asset_ids) asset_mapper = {a.id: a for a in assets} + if not hosts: break - hardware_list = [] for host in hosts: hardware = hardware_model() asset = asset_mapper[host.asset_ptr_id] @@ -34,10 +34,9 @@ def migrate_hardware(apps, *args): hardware.date_updated = timezone.now() for name in fields: setattr(hardware, name, getattr(asset, name)) - hardware_list.append(hardware) - - hardware_model.objects.bulk_create(hardware_list, ignore_conflicts=True) - created += len(hardware_list) + hardware.save() + host.device_info = hardware + host.save() class Migration(migrations.Migration): @@ -47,5 +46,10 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='host', + name='device_info', + field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to='assets.deviceinfo', verbose_name='Device info'), + ), migrations.RunPython(migrate_hardware) ] diff --git a/apps/assets/migrations/0098_auto_20220412_1907.py b/apps/assets/migrations/0098_auto_20220412_1907.py new file mode 100644 index 000000000..c12bbd644 --- /dev/null +++ b/apps/assets/migrations/0098_auto_20220412_1907.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.14 on 2022-04-12 11:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0097_auto_20220407_1726'), + ] + + operations = [ + migrations.RemoveField( + model_name='deviceinfo', + name='host', + ), + migrations.AddField( + model_name='host', + name='device_info', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.deviceinfo', verbose_name='Device info'), + ), + ] From 832228e1849aef41c51572f1e75dba844d94040b Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 19 Apr 2022 15:30:56 +0800 Subject: [PATCH 008/488] stash --- apps/assets/models/asset/host.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 3dbe4ca3d..5b6ade8bd 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,15 +1,12 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ +# from django.db import models +# from django.utils.translation import gettext_lazy as _ from assets.const import Category from .common import Asset -from .device_info import DeviceInfo +# from .device_info import DeviceInfo class Host(Asset): - device_info = models.ForeignKey(DeviceInfo, on_delete=models.SET_NULL, - null=True, verbose_name=_("Device info")) - def save(self, *args, **kwargs): self.category = Category.HOST return super().save(*args, **kwargs) From 54e772741baa6b5dfab7558f6e4719cd3b7db698 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 20 Apr 2022 10:15:20 +0800 Subject: [PATCH 009/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0092_hardware.py | 9 ++-- .../migrations/0093_auto_20220403_1627.py | 13 ++--- .../migrations/0096_auto_20220406_1546.py | 8 --- .../migrations/0098_auto_20220412_1907.py | 23 --------- apps/assets/models/asset/__init__.py | 1 - apps/assets/models/asset/device_info.py | 51 ------------------- apps/assets/models/asset/host.py | 51 +++++++++++++++++-- apps/assets/models/platform.py | 1 + apps/assets/models/protocol.py | 5 ++ 9 files changed, 64 insertions(+), 98 deletions(-) delete mode 100644 apps/assets/migrations/0098_auto_20220412_1907.py delete mode 100644 apps/assets/models/asset/device_info.py create mode 100644 apps/assets/models/protocol.py diff --git a/apps/assets/migrations/0092_hardware.py b/apps/assets/migrations/0092_hardware.py index 26d026640..a5e860a6d 100644 --- a/apps/assets/migrations/0092_hardware.py +++ b/apps/assets/migrations/0092_hardware.py @@ -15,6 +15,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='DeviceInfo', fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('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')), ('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')), ('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')), ('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')), @@ -29,10 +33,7 @@ class Migration(migrations.Migration): ('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')), ('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')), ('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('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')), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.host', verbose_name='Host')), ], options={ 'verbose_name': 'DeviceInfo', diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py index 9aa3be14d..27e5f11ee 100644 --- a/apps/assets/migrations/0093_auto_20220403_1627.py +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -27,6 +27,7 @@ def migrate_hardware(apps, *args): if not hosts: break + hardware_infos = [] for host in hosts: hardware = hardware_model() asset = asset_mapper[host.asset_ptr_id] @@ -34,9 +35,10 @@ def migrate_hardware(apps, *args): hardware.date_updated = timezone.now() for name in fields: setattr(hardware, name, getattr(asset, name)) - hardware.save() - host.device_info = hardware - host.save() + hardware_infos.append(hardware) + + hardware_model.objects.bulk_create(hardware_infos, ignore_conflicts=True) + created += len(hardware_infos) class Migration(migrations.Migration): @@ -46,10 +48,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='host', - name='device_info', - field=models.ForeignKey(null=True, on_delete=models.deletion.SET_NULL, to='assets.deviceinfo', verbose_name='Device info'), - ), migrations.RunPython(migrate_hardware) ] diff --git a/apps/assets/migrations/0096_auto_20220406_1546.py b/apps/assets/migrations/0096_auto_20220406_1546.py index b4808fad5..e2c9400e4 100644 --- a/apps/assets/migrations/0096_auto_20220406_1546.py +++ b/apps/assets/migrations/0096_auto_20220406_1546.py @@ -4,14 +4,6 @@ from itertools import groupby from django.db import migrations from django.db.models import F - -# ('Linux', 'Linux'), -# ('Unix', 'Unix'), -# ('MacOS', 'MacOS'), -# ('BSD', 'BSD'), -# ('Windows', 'Windows'), -# ('Other', 'Other'), - category_mapper = { 'Linux': ('host', 'linux'), 'Unix': ('host', 'unix'), diff --git a/apps/assets/migrations/0098_auto_20220412_1907.py b/apps/assets/migrations/0098_auto_20220412_1907.py deleted file mode 100644 index c12bbd644..000000000 --- a/apps/assets/migrations/0098_auto_20220412_1907.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1.14 on 2022-04-12 11:07 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0097_auto_20220407_1726'), - ] - - operations = [ - migrations.RemoveField( - model_name='deviceinfo', - name='host', - ), - migrations.AddField( - model_name='host', - name='device_info', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.deviceinfo', verbose_name='Device info'), - ), - ] diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py index 8f2ad65ee..51640e7cf 100644 --- a/apps/assets/models/asset/__init__.py +++ b/apps/assets/models/asset/__init__.py @@ -1,3 +1,2 @@ from .common import * -from .device_info import * from .host import * diff --git a/apps/assets/models/asset/device_info.py b/apps/assets/models/asset/device_info.py deleted file mode 100644 index 7410af04f..000000000 --- a/apps/assets/models/asset/device_info.py +++ /dev/null @@ -1,51 +0,0 @@ -from django.utils.translation import gettext_lazy as _ -from django.db import models - -from common.mixins.models import CommonModelMixin - -__all__ = ['DeviceInfo'] - - -class DeviceInfo(CommonModelMixin): - # Collect - vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) - model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) - sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) - - cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) - cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) - cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) - cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) - memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) - disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) - disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) - - os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) - os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) - os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) - hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) - - @property - def cpu_info(self): - info = "" - if self.cpu_model: - info += self.cpu_model - if self.cpu_count and self.cpu_cores: - info += "{}*{}".format(self.cpu_count, self.cpu_cores) - return info - - @property - def hardware_info(self): - if self.cpu_count: - return '{} Core {} {}'.format( - self.cpu_vcpus or self.cpu_count * self.cpu_cores, - self.memory, self.disk_total - ) - else: - return '' - - def __str__(self): - return '{} of {}'.format(self.hardware_info, self.host.hostname) - - class Meta: - verbose_name = _("DeviceInfo") diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 5b6ade8bd..ac61ea052 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,9 +1,9 @@ -# from django.db import models -# from django.utils.translation import gettext_lazy as _ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from common.mixins.models import CommonModelMixin from assets.const import Category from .common import Asset -# from .device_info import DeviceInfo class Host(Asset): @@ -12,3 +12,48 @@ class Host(Asset): return super().save(*args, **kwargs) +class DeviceInfo(CommonModelMixin): + host = models.ForeignKey(Host, on_delete=models.CASCADE, verbose_name=_("Host")) + # Collect + vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) + model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) + sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) + + cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) + cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) + cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) + cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) + memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) + disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) + disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) + + os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) + os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) + os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) + hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) + + @property + def cpu_info(self): + info = "" + if self.cpu_model: + info += self.cpu_model + if self.cpu_count and self.cpu_cores: + info += "{}*{}".format(self.cpu_count, self.cpu_cores) + return info + + @property + def hardware_info(self): + if self.cpu_count: + return '{} Core {} {}'.format( + self.cpu_vcpus or self.cpu_count * self.cpu_cores, + self.memory, self.disk_total + ) + else: + return '' + + def __str__(self): + return '{} of {}'.format(self.hardware_info, self.host.hostname) + + class Meta: + verbose_name = _("DeviceInfo") + diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 675d3542f..b3c9ec76e 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _ from assets.const import Category, AllTypes from common.fields.model import JsonDictTextField + __all__ = ['Platform'] diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py new file mode 100644 index 000000000..41c3d2c76 --- /dev/null +++ b/apps/assets/models/protocol.py @@ -0,0 +1,5 @@ +from common.db.models import JMSBaseModel + + +class Protocol(JMSBaseModel): + pass From 44d192cbe792770a2a99cdb417b5d052db103e95 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 26 Apr 2022 11:24:08 +0800 Subject: [PATCH 010/488] perf: stash --- apps/assets/models/protocol.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py index 41c3d2c76..b2e1b750b 100644 --- a/apps/assets/models/protocol.py +++ b/apps/assets/models/protocol.py @@ -1,5 +1,9 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + from common.db.models import JMSBaseModel class Protocol(JMSBaseModel): - pass + name = models.CharField(max_length=32, verbose_name=_("Name")) + port = models.IntegerField(verbose_name=_("Port")) From 0a2b2ad127ee508b5eed56f700d397dc3b72e86d Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 26 Apr 2022 21:30:01 +0800 Subject: [PATCH 011/488] =?UTF-8?q?perf:=20=E5=85=B6=E4=BB=96=20asset=20mo?= =?UTF-8?q?del?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset/__init__.py | 4 ++++ apps/assets/models/asset/common.py | 7 +++++- apps/assets/models/asset/database.py | 7 ++++-- apps/assets/models/asset/network.py | 6 +---- apps/assets/models/asset/remote_app.py | 7 ++++++ apps/assets/serializers/__init__.py | 1 + apps/assets/serializers/asset/common.py | 29 +++++++------------------ apps/assets/serializers/platform.py | 29 +++++++++++++++++++++++++ 8 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 apps/assets/serializers/platform.py diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py index 51640e7cf..28f5e30c5 100644 --- a/apps/assets/models/asset/__init__.py +++ b/apps/assets/models/asset/__init__.py @@ -1,2 +1,6 @@ from .common import * from .host import * +from .database import * +from .network import * +from .remote_app import * +from .cloud import * diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 05404ff94..04abf1f25 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -186,7 +186,7 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): @lazyproperty def platform_base(self): - return self.platform.base + return self.platform.type @lazyproperty def admin_user_username(self): @@ -302,6 +302,11 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): system_users = SystemUser.objects.filter(id__in=system_user_ids) return system_users + def save(self, *args, **kwargs): + self.type = self.platform.type + self.category = self.platform.category + return super().save(*args, **kwargs) + class Meta: unique_together = [('org_id', 'hostname')] verbose_name = _("Asset") diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index 84d174277..7c688e6d6 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -1,8 +1,11 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from .common import Asset class Database(Asset): - database = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + + class Meta: + verbose_name = _("Database") diff --git a/apps/assets/models/asset/network.py b/apps/assets/models/asset/network.py index a17d99a2e..c6506bf6d 100644 --- a/apps/assets/models/asset/network.py +++ b/apps/assets/models/asset/network.py @@ -1,10 +1,6 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ from .common import Asset -from .device_info import DeviceInfo class Network(Asset): - device_info = models.ForeignKey(DeviceInfo, on_delete=models.SET_NULL, - null=True, verbose_name=_("Device info")) + pass diff --git a/apps/assets/models/asset/remote_app.py b/apps/assets/models/asset/remote_app.py index 7eb7872c8..c7528baae 100644 --- a/apps/assets/models/asset/remote_app.py +++ b/apps/assets/models/asset/remote_app.py @@ -1,9 +1,16 @@ from django.utils.translation import gettext_lazy as _ from django.db import models +from orgs.mixins.models import OrgModelMixin +from common.mixins.models import CommonModelMixin from .common import Asset +class RemoteAppHost(CommonModelMixin, OrgModelMixin): + host = models.ForeignKey('assets.Host', verbose_name=_("Host")) + system_user = models.ForeignKey('assets.SystemUser', verbose_name=_("System user")) + + class RemoteApp(Asset): app_path = models.CharField(max_length=1024, verbose_name=_("App path")) attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index f48552c9d..f6d166db9 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -11,4 +11,5 @@ from .cmd_filter import * from .gathered_user import * from .favorite_asset import * from .account import * +from .platform import * from .backup import * diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 4070c6fc7..5028ba7c6 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers -from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer @@ -9,7 +8,6 @@ from ...models import Asset, Node, Platform, SystemUser __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', - 'ProtocolsField', 'PlatformSerializer', 'AssetTaskSerializer', 'AssetsTaskSerializer', 'ProtocolsField', ] @@ -95,6 +93,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ] read_only_fields = [ 'connectivity', 'date_verified', 'created_by', 'date_created', + 'category', 'type' ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { @@ -112,6 +111,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer): admin_user_field.queryset = SystemUser.objects.filter(type=SystemUser.Type.admin) return fields + def validate_type(self, value): + print(self.initial_data) + return value + + def validate_category(self, value): + return value + @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ @@ -168,25 +174,6 @@ class MiniAssetSerializer(serializers.ModelSerializer): fields = AssetSerializer.Meta.fields_mini -class PlatformSerializer(serializers.ModelSerializer): - meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # TODO 修复 drf SlugField RegexValidator bug,之后记得删除 - validators = self.fields['name'].validators - if isinstance(validators[-1], RegexValidator): - validators.pop() - - class Meta: - model = Platform - fields = [ - 'id', 'name', 'category', 'type', 'charset', - 'internal', 'meta', 'comment' - ] - - class AssetSimpleSerializer(serializers.ModelSerializer): class Meta: model = Asset diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py new file mode 100644 index 000000000..0a8cfd265 --- /dev/null +++ b/apps/assets/serializers/platform.py @@ -0,0 +1,29 @@ +from rest_framework import serializers +from django.core.validators import RegexValidator +from django.utils.translation import gettext_lazy as _ + +from assets.models import Platform + +__all__ = ['PlatformSerializer'] + + +class PlatformSerializer(serializers.ModelSerializer): + category_display = serializers.ReadOnlyField(source='get_category_display', label=_("Category display")) + type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) + meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # TODO 修复 drf SlugField RegexValidator bug,之后记得删除 + validators = self.fields['name'].validators + if isinstance(validators[-1], RegexValidator): + validators.pop() + + class Meta: + model = Platform + fields = [ + 'id', 'name', 'category', 'category_display', + 'type', 'type_display', 'charset', + 'internal', 'meta', 'comment' + ] From 770d2508d7c954863a653386a4c356ed785688f8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 26 Apr 2022 21:30:11 +0800 Subject: [PATCH 012/488] =?UTF-8?q?perf:=20=E5=85=B6=E4=BB=96=20asset=20mo?= =?UTF-8?q?del?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0098_auto_20220426_1550.py | 47 ++++++++++++ .../migrations/0099_auto_20220426_1558.py | 75 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 apps/assets/migrations/0098_auto_20220426_1550.py create mode 100644 apps/assets/migrations/0099_auto_20220426_1558.py diff --git a/apps/assets/migrations/0098_auto_20220426_1550.py b/apps/assets/migrations/0098_auto_20220426_1550.py new file mode 100644 index 000000000..54aeb7970 --- /dev/null +++ b/apps/assets/migrations/0098_auto_20220426_1550.py @@ -0,0 +1,47 @@ +# Generated by Django 3.1.14 on 2022-04-26 07:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0097_auto_20220407_1726'), + ] + + operations = [ + migrations.CreateModel( + name='Database', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('db_name', models.CharField(blank=True, max_length=1024, verbose_name='Database')), + ], + options={ + 'verbose_name': 'Database', + }, + bases=('assets.asset',), + ), + migrations.CreateModel( + name='Network', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ], + options={ + 'abstract': False, + }, + bases=('assets.asset',), + ), + migrations.CreateModel( + name='RemoteApp', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('app_path', models.CharField(max_length=1024, verbose_name='App path')), + ('attrs', models.JSONField(default=dict, verbose_name='Attrs')), + ], + options={ + 'abstract': False, + }, + bases=('assets.asset',), + ), + ] diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py new file mode 100644 index 000000000..b3d2c0c4e --- /dev/null +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -0,0 +1,75 @@ +# Generated by Django 3.1.14 on 2022-04-26 07:58 + +from django.db import migrations +from django.db.utils import IntegrityError + + +def create_app_platform(apps, *args): + platform_model = apps.get_model('assets', 'Platform') + apps = [ + # DB + {'name': 'MySQL', 'category': 'database', 'type': 'mysql'}, + {'name': 'MariaDB', 'category': 'database', 'type': 'mariadb'}, + {'name': 'PostgreSQL', 'category': 'database', 'type': 'postgresql'}, + {'name': 'Oracle', 'category': 'database', 'type': 'oracle'}, + {'name': 'SQLServer', 'category': 'database', 'type': 'sqlserver'}, + {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, + {'name': 'Redis', 'category': 'database', 'type': 'redis'}, + {'name': 'RemoteApp', 'category': 'remote_app', 'type': 'remote_app'}, + {'name': 'Kubernetes', 'category': 'cloud', 'type': 'kubernetes'}, + ] + + for app in apps: + app['internal'] = True + platform_model.objects.update_or_create(defaults=app, name=app['name']) + + +def migrate_database_to_asset(apps, *args): + app_model = apps.get_model('applications', 'Application') + db_model = apps.get_model('assets', 'Database') + platform_model = apps.get_model('assets', 'Platform') + + applications = app_model.objects.filter(category='db') + platforms = platform_model.objects.all() + platforms_map = {p.type: p for p in platforms} + + dbs = [] + for app in applications: + attrs = {'host': '', 'port': 0, 'database': ''} + _attrs = app.attrs or {} + attrs.update(_attrs) + print("Create: ", app.name) + db = db_model( + id=app.id, hostname=app.name, ip=attrs['host'], + protocols='{}/{}'.format(app.type, attrs['port']), + category='database', type=app.type, + db_name=attrs['database'] or '', + platform=platforms_map[app.type], + org_id=app.org_id + ) + for i in range(3): + try: + db.save() + break + except IntegrityError: + db.name = 'DB-' + db.name + + +def migrate_remote_app_to_asset(apps, *args): + pass + + +def migrate_cloud_to_asset(apps, *args): + pass + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0098_auto_20220426_1550'), + ('applications', '0020_auto_20220316_2028') + ] + + operations = [ + migrations.RunPython(create_app_platform), + migrations.RunPython(migrate_database_to_asset), + ] From ba0a017aa49a92fea26f39a50a0fe6b675a99437 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 28 Apr 2022 12:47:39 +0800 Subject: [PATCH 013/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=BF=81?= =?UTF-8?q?=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/common.py | 3 +- apps/assets/const.py | 4 +- apps/assets/filters.py | 1 + .../migrations/0095_auto_20220406_1541.py | 2 +- .../migrations/0097_auto_20220407_1726.py | 2 +- .../migrations/0098_auto_20220426_1550.py | 12 ++ .../migrations/0099_auto_20220426_1558.py | 168 ++++++++++++++++-- apps/assets/models/asset/cloud.py | 11 ++ apps/assets/models/asset/database.py | 3 + apps/assets/models/asset/remote_app.py | 8 +- apps/assets/pagination.py | 16 +- 11 files changed, 191 insertions(+), 39 deletions(-) diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index 4cec7495a..007d7fbd0 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -49,7 +49,8 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) 'gateways': 'assets.view_gateway' } extra_filter_backends = [ - FilterAssetByNodeFilterBackend, LabelFilterBackend, + FilterAssetByNodeFilterBackend, + LabelFilterBackend, IpInFilterBackend, ] diff --git a/apps/assets/const.py b/apps/assets/const.py index ccd48205b..0697c2112 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -46,9 +46,9 @@ class DatabaseTypes(models.TextChoices): class RemoteAppTypes(models.TextChoices): CHROME = 'chrome', 'Chrome' - VSPHERE = 'vsphere', 'vSphere client' + VSPHERE = 'vmware_client', 'vSphere client' MYSQL_WORKBENCH = 'mysql_workbench', 'MySQL workbench' - CUSTOM_REMOTE_APP = 'custom_remote_app', _("Custom") + GENERAL_REMOTE_APP = 'general_remote_app', _("Custom") class CloudTypes(models.TextChoices): diff --git a/apps/assets/filters.py b/apps/assets/filters.py index b807396e0..346c507b1 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -68,6 +68,7 @@ class FilterAssetByNodeFilterBackend(filters.BaseFilterBackend): Q(nodes__key=node.key) ).distinct() else: + print("Query query origin: ", queryset.count()) return queryset.filter(nodes__key=node.key).distinct() diff --git a/apps/assets/migrations/0095_auto_20220406_1541.py b/apps/assets/migrations/0095_auto_20220406_1541.py index 9647aed54..7b43c92a1 100644 --- a/apps/assets/migrations/0095_auto_20220406_1541.py +++ b/apps/assets/migrations/0095_auto_20220406_1541.py @@ -20,7 +20,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='asset', name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vmware_client', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('general_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), preserve_default=False, ), ] diff --git a/apps/assets/migrations/0097_auto_20220407_1726.py b/apps/assets/migrations/0097_auto_20220407_1726.py index e16a6ed92..61d260458 100644 --- a/apps/assets/migrations/0097_auto_20220407_1726.py +++ b/apps/assets/migrations/0097_auto_20220407_1726.py @@ -24,7 +24,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='platform', name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vsphere', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('custom_remote_app', 'Custom'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vmware_client', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('general_remote_app', 'Custom'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), ), migrations.AlterField( model_name='systemuser', diff --git a/apps/assets/migrations/0098_auto_20220426_1550.py b/apps/assets/migrations/0098_auto_20220426_1550.py index 54aeb7970..bbce73dec 100644 --- a/apps/assets/migrations/0098_auto_20220426_1550.py +++ b/apps/assets/migrations/0098_auto_20220426_1550.py @@ -36,6 +36,7 @@ class Migration(migrations.Migration): name='RemoteApp', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('connect_host', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.host')), ('app_path', models.CharField(max_length=1024, verbose_name='App path')), ('attrs', models.JSONField(default=dict, verbose_name='Attrs')), ], @@ -44,4 +45,15 @@ class Migration(migrations.Migration): }, bases=('assets.asset',), ), + migrations.CreateModel( + name='Cloud', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('cluster', models.CharField(max_length=4096, verbose_name='Cluster')), + ], + options={ + 'abstract': False, + }, + bases=('assets.asset',), + ), ] diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index b3d2c0c4e..2a7c34e3b 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -1,12 +1,14 @@ # Generated by Django 3.1.14 on 2022-04-26 07:58 +import uuid from django.db import migrations -from django.db.utils import IntegrityError + +failed_apps = [] def create_app_platform(apps, *args): platform_model = apps.get_model('assets', 'Platform') - apps = [ + platforms = [ # DB {'name': 'MySQL', 'category': 'database', 'type': 'mysql'}, {'name': 'MariaDB', 'category': 'database', 'type': 'mariadb'}, @@ -15,13 +17,30 @@ def create_app_platform(apps, *args): {'name': 'SQLServer', 'category': 'database', 'type': 'sqlserver'}, {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, {'name': 'Redis', 'category': 'database', 'type': 'redis'}, - {'name': 'RemoteApp', 'category': 'remote_app', 'type': 'remote_app'}, - {'name': 'Kubernetes', 'category': 'cloud', 'type': 'kubernetes'}, + {'name': 'Chrome', 'category': 'remote_app', 'type': 'chrome'}, + {'name': 'vSphereClient', 'category': 'remote_app', 'type': 'vmware_client'}, + {'name': 'MySQLWorkbench', 'category': 'remote_app', 'type': 'mysql_workbench'}, + {'name': 'GeneralRemoteApp', 'category': 'remote_app', 'type': 'general_remote_app'}, + {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, ] - for app in apps: - app['internal'] = True - platform_model.objects.update_or_create(defaults=app, name=app['name']) + for platform in platforms: + platform['internal'] = True + print("Create platform: {}".format(platform['name'])) + platform_model.objects.update_or_create(defaults=platform, name=platform['name']) + + +def get_prop_name_id(apps, app, category): + asset_model = apps.get_model('assets', 'Asset') + _id = app.id + id_exists = asset_model.objects.filter(id=_id).exists() + if (id_exists): + _id = uuid.uuid4() + name = app.name + name_exists = asset_model.objects.filter(hostname=name).exists() + if name_exists: + name = category + '-' + app.name + return _id, name def migrate_database_to_asset(apps, *args): @@ -33,12 +52,11 @@ def migrate_database_to_asset(apps, *args): platforms = platform_model.objects.all() platforms_map = {p.type: p for p in platforms} - dbs = [] for app in applications: attrs = {'host': '', 'port': 0, 'database': ''} _attrs = app.attrs or {} attrs.update(_attrs) - print("Create: ", app.name) + db = db_model( id=app.id, hostname=app.name, ip=attrs['host'], protocols='{}/{}'.format(app.type, attrs['port']), @@ -47,20 +65,129 @@ def migrate_database_to_asset(apps, *args): platform=platforms_map[app.type], org_id=app.org_id ) - for i in range(3): - try: - db.save() - break - except IntegrityError: - db.name = 'DB-' + db.name + try: + print("Create database: ", app.name) + db.save() + except: + failed_apps.append(app) + pass + # db.hostname = 'DB-' + db.hostname def migrate_remote_app_to_asset(apps, *args): - pass + app_model = apps.get_model('applications', 'Application') + remote_app_model = apps.get_model('assets', 'RemoteApp') + host_model = apps.get_model('assets', 'Host') + platform_model = apps.get_model('assets', 'Platform') + applications = app_model.objects.filter(category='remote_app') + platforms = platform_model.objects.filter(category='remote_app') + platforms_map = {p.type: p for p in platforms} + + connect_host_map = {} + + for app in applications: + attrs = app.attrs + connect_host = attrs.pop('asset') + if connect_host: + connect_host = host_model.objects.filter(asset_ptr_id=connect_host).first() + connect_host_map[app.id] = connect_host + + for app in applications: + tp = app.type + app_path = attrs.pop('path', '') + if tp == 'custom': + tp = 'general_remote_app' + + print("Create remote app: {}".format(app.name)) + remote_app = remote_app_model( + id=app.id, hostname=app.name, ip='', + category='remote_app', type=tp, + platform=platforms_map[tp], + org_id=app.org_id, + + app_path=app_path, + connect_host=connect_host_map.get(app.id), + attrs=attrs, + ) + try: + remote_app.save() + except Exception as e: + print("Error: ", e) + # remote_app.hostname = 'RemoteApp-' + remote_app.hostname def migrate_cloud_to_asset(apps, *args): - pass + app_model = apps.get_model('applications', 'Application') + cloud_model = apps.get_model('assets', 'Cloud') + platform_model = apps.get_model('assets', 'Platform') + + applications = app_model.objects.filter(category='cloud') + platform = platform_model.objects.filter(type='k8s').first() + + for app in applications: + attrs = app.attrs + print("Create cloud: {}".format(app.name)) + cloud = cloud_model( + id=app.id, hostname=app.name, ip='', + category='remote_app', type='k8s', + platform=platform, + org_id=app.org_id, + cluster=attrs.get('cluster', '') + ) + + try: + cloud.save() + except Exception as e: + failed_apps.append(cloud) + print("Error: ", e) + + +def create_app_nodes(apps, org_id): + node_model = apps.get_model('assets', 'Node') + + child_pattern = r'^[0-9]+:[0-9]+$' + node_keys = node_model.objects.filter(org_id=org_id) \ + .filter(key__regex=child_pattern) \ + .values_list('key', flat=True) + if not node_keys: + return + node_key_split = [key.split(':') for key in node_keys] + next_value = int(max([k[1] for k in node_key_split])) + 1 + parent_key = node_key_split[0][0] + parent = node_model.objects.get(key=parent_key) + next_key = '{}:{}'.format(node_key_split[0][0], next_value) + name = 'Apps' + full_value = parent.full_value + '/' + name + defaults = { + 'key': next_key, 'value': name, 'parent_key': parent_key, + 'full_value': full_value, 'org_id': org_id + } + node, created = node_model.objects.get_or_create( + defaults=defaults, value=name, org_id=org_id, + ) + node.parent = parent + return node + + +def migrate_to_nodes(apps, *args): + org_model = apps.get_model('orgs', 'Organization') + asset_model = apps.get_model('assets', 'Asset') + orgs = org_model.objects.all() + + for org in orgs: + node = create_app_nodes(apps, org.id) + assets = asset_model.objects.filter( + category__in=['remote_app', 'database', 'cloud'], + org_id=org.id + ) + print("Set node asset: ", node) + if node: + node.assets_amount = len(assets) + node.save() + node.assets.set(assets) + parent = node.parent + parent.assets_amount += len(assets) + parent.save() class Migration(migrations.Migration): @@ -70,6 +197,9 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(create_app_platform), - migrations.RunPython(migrate_database_to_asset), + # migrations.RunPython(create_app_platform), + # migrations.RunPython(migrate_database_to_asset), + # migrations.RunPython(migrate_remote_app_to_asset), + # migrations.RunPython(migrate_cloud_to_asset), + migrations.RunPython(migrate_to_nodes) ] diff --git a/apps/assets/models/asset/cloud.py b/apps/assets/models/asset/cloud.py index e69de29bb..b8eed03b0 100644 --- a/apps/assets/models/asset/cloud.py +++ b/apps/assets/models/asset/cloud.py @@ -0,0 +1,11 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from .common import Asset + + +class Cloud(Asset): + cluster = models.CharField(max_length=4096, verbose_name=_("Cluster")) + + def __str__(self): + return self.hostname diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index 7c688e6d6..4947b79ca 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -7,5 +7,8 @@ from .common import Asset class Database(Asset): db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + def __str__(self): + return '{}({}://{}/{})'.format(self.hostname, self.type, self.ip, self.db_name) + class Meta: verbose_name = _("Database") diff --git a/apps/assets/models/asset/remote_app.py b/apps/assets/models/asset/remote_app.py index c7528baae..caa4c89a0 100644 --- a/apps/assets/models/asset/remote_app.py +++ b/apps/assets/models/asset/remote_app.py @@ -1,16 +1,10 @@ from django.utils.translation import gettext_lazy as _ from django.db import models -from orgs.mixins.models import OrgModelMixin -from common.mixins.models import CommonModelMixin from .common import Asset -class RemoteAppHost(CommonModelMixin, OrgModelMixin): - host = models.ForeignKey('assets.Host', verbose_name=_("Host")) - system_user = models.ForeignKey('assets.SystemUser', verbose_name=_("System user")) - - class RemoteApp(Asset): app_path = models.CharField(max_length=1024, verbose_name=_("App path")) + connect_host = models.ForeignKey('assets.Host', null=True, on_delete=models.SET_NULL) attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) diff --git a/apps/assets/pagination.py b/apps/assets/pagination.py index f75ff023c..f913e9eed 100644 --- a/apps/assets/pagination.py +++ b/apps/assets/pagination.py @@ -42,11 +42,11 @@ class AssetPaginationBase(LimitOffsetPagination): class NodeAssetTreePagination(AssetPaginationBase): def get_count_from_nodes(self, queryset): is_query_all = self._view.is_query_node_all_assets - if is_query_all: - node = self._view.node - if not node: - node = Node.org_root() - if node: - logger.debug(f'Hit node.assets_amount[{node.assets_amount}] -> {self._request.get_full_path()}') - return node.assets_amount - return None + if not is_query_all: + return None + node = self._view.node + if not node: + node = Node.org_root() + if node: + logger.debug(f'Hit node assets_amount cache: [{node.assets_amount}]') + return node.assets_amount From e3f2878b0f48ac8fee64b37734132234ceab097c Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 28 Apr 2022 12:50:41 +0800 Subject: [PATCH 014/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=BF=81?= =?UTF-8?q?=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0099_auto_20220426_1558.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index 2a7c34e3b..d073bfaa6 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -197,9 +197,9 @@ class Migration(migrations.Migration): ] operations = [ - # migrations.RunPython(create_app_platform), - # migrations.RunPython(migrate_database_to_asset), - # migrations.RunPython(migrate_remote_app_to_asset), - # migrations.RunPython(migrate_cloud_to_asset), + migrations.RunPython(create_app_platform), + migrations.RunPython(migrate_database_to_asset), + migrations.RunPython(migrate_remote_app_to_asset), + migrations.RunPython(migrate_cloud_to_asset), migrations.RunPython(migrate_to_nodes) ] From 69f3c8519502c758613fa2bb2b051afda8588b9b Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 28 Apr 2022 22:54:18 +0800 Subject: [PATCH 015/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20category?= =?UTF-8?q?=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 22 +++++++++++++-- apps/assets/const.py | 28 ++++++++++++++++++- .../migrations/0099_auto_20220426_1558.py | 2 ++ apps/assets/serializers/platform.py | 2 ++ apps/common/drf/serializers.py | 13 +++++++-- apps/common/fields/model.py | 1 - 6 files changed, 61 insertions(+), 7 deletions(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 030f90905..dfa63f8f6 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -1,17 +1,33 @@ -from rest_framework.viewsets import ModelViewSet +from rest_framework.decorators import action +from rest_framework.response import Response +from common.drf.api import JMSModelViewSet +from common.drf.serializers import GroupedChoiceSerailizer from assets.models import Platform from assets.serializers import PlatformSerializer +from assets.const import AllTypes, Category __all__ = ['AssetPlatformViewSet'] -class AssetPlatformViewSet(ModelViewSet): +class AssetPlatformViewSet(JMSModelViewSet): queryset = Platform.objects.all() - serializer_class = PlatformSerializer + serializer_classes = { + 'default': PlatformSerializer, + 'categories': GroupedChoiceSerailizer + } filterset_fields = ['name'] search_fields = ['name'] + rbac_perms = { + 'categories': 'assets.view_platform' + } + + @action(methods=['GET'], detail=False) + def categories(self, request, *args, **kwargs): + data = AllTypes.grouped_choices_to_objs() + serializer = self.get_serializer(data, many=True) + return Response(serializer.data) def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: diff --git a/apps/assets/const.py b/apps/assets/const.py index 0697c2112..218657d22 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -11,7 +11,7 @@ __all__ = [ class Category(models.TextChoices): HOST = 'host', _('Host') - NETWORK = 'network', _("Networking") + NETWORK = 'network', _("NetworkDevice") DATABASE = 'database', _("Database") REMOTE_APP = 'remote_app', _("Remote app") CLOUD = 'cloud', _("Clouding") @@ -62,6 +62,32 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): RemoteAppTypes, CloudTypes ] + @classmethod + def grouped_choices(cls): + grouped_types= [ + (Category.HOST.value, HostTypes.choices), + (Category.NETWORK.value, NetworkTypes.choices), + (Category.DATABASE.value, DatabaseTypes.choices), + (Category.REMOTE_APP.value, RemoteAppTypes.choices), + (Category.CLOUD.value, CloudTypes.choices), + ] + return grouped_types + + @classmethod + def grouped_choices_to_objs(cls): + choices = cls.serialize_to_objs(Category.choices) + mapper = dict(cls.grouped_choices()) + for choice in choices: + children = cls.serialize_to_objs(mapper[choice['value']]) + choice['children'] = children + return choices + + @staticmethod + def serialize_to_objs(choices): + title = ['value', 'display_name'] + return [dict(zip(title, choice)) for choice in choices] + + class Protocol(models.TextChoices): ssh = 'ssh', 'SSH' diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index d073bfaa6..69ee76277 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -101,6 +101,7 @@ def migrate_remote_app_to_asset(apps, *args): print("Create remote app: {}".format(app.name)) remote_app = remote_app_model( id=app.id, hostname=app.name, ip='', + protocols='', category='remote_app', type=tp, platform=platforms_map[tp], org_id=app.org_id, @@ -130,6 +131,7 @@ def migrate_cloud_to_asset(apps, *args): cloud = cloud_model( id=app.id, hostname=app.name, ip='', category='remote_app', type='k8s', + protocols='', platform=platform, org_id=app.org_id, cluster=attrs.get('cluster', '') diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 0a8cfd265..b20348c14 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -3,6 +3,7 @@ from django.core.validators import RegexValidator from django.utils.translation import gettext_lazy as _ from assets.models import Platform +from assets.const import AllTypes __all__ = ['PlatformSerializer'] @@ -11,6 +12,7 @@ class PlatformSerializer(serializers.ModelSerializer): category_display = serializers.ReadOnlyField(source='get_category_display', label=_("Category display")) type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) + type = serializers.ChoiceField(choices=AllTypes.grouped_choices(), label=_("Type")) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index 4beb59fe7..94ba9a238 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -3,10 +3,11 @@ from rest_framework import serializers from rest_framework.serializers import Serializer from rest_framework.serializers import ModelSerializer from rest_framework_bulk.serializers import BulkListSerializer - -from common.mixins import BulkListSerializerMixin +from django.utils.translation import gettext_lazy as _ from django.utils.functional import cached_property from rest_framework.utils.serializer_helpers import BindingDict + +from common.mixins import BulkListSerializerMixin from common.mixins.serializers import BulkSerializerMixin __all__ = [ @@ -83,3 +84,11 @@ class CeleryTaskSerializer(serializers.Serializer): task = serializers.CharField(read_only=True) +class ChoiceSerializer(serializers.Serializer): + display_name = serializers.CharField(label=_("Display name")) + value = serializers.CharField(label=_("Value")) + + +class GroupedChoiceSerailizer(ChoiceSerializer): + children = ChoiceSerializer(many=True, label=_("Children")) + diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index 4a4f3525d..3b9622411 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -143,7 +143,6 @@ class EncryptMixin: value = sp.get_prep_value(value) value = force_text(value) # 替换新的加密方式 - return crypto.encrypt(value) class EncryptTextField(EncryptMixin, models.TextField): From 246710128e89e142f575ee04b2d0362e9c7ab6dc Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 29 Apr 2022 18:28:12 +0800 Subject: [PATCH 016/488] perf: stash --- apps/assets/api/asset/__init__.py | 1 + apps/assets/api/asset/database.py | 20 + apps/assets/serializers/asset/common.py | 10 +- apps/assets/serializers/asset/host.py | 10 +- apps/assets/urls/api_urls.py | 3 +- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 663 +++++++++++++----------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 655 ++++++++++++----------- 9 files changed, 775 insertions(+), 595 deletions(-) create mode 100644 apps/assets/api/asset/database.py diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py index d34ec0922..a2d88408d 100644 --- a/apps/assets/api/asset/__init__.py +++ b/apps/assets/api/asset/__init__.py @@ -1,3 +1,4 @@ from .common import * from .host import * +from .database import * from .permission import * diff --git a/apps/assets/api/asset/database.py b/apps/assets/api/asset/database.py new file mode 100644 index 000000000..786ff06a3 --- /dev/null +++ b/apps/assets/api/asset/database.py @@ -0,0 +1,20 @@ + +from assets.models import Database +from assets.serializers import DatabaseSerializer +from .common import AssetViewSet + +__all__ = ['DatabaseViewSet'] + + +class DatabaseViewSet(AssetViewSet): + model = Database + + def get_queryset(self): + queryset = super().get_queryset() + print("Datbase is: ", queryset) + return queryset + + def get_serializer_classes(self): + serializer_classes = super().get_serializer_classes() + serializer_classes['default'] = DatabaseSerializer + return serializer_classes diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 5028ba7c6..5092e53c2 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -57,9 +57,6 @@ class ProtocolsField(serializers.ListField): class AssetSerializer(BulkOrgResourceModelSerializer): - platform = serializers.SlugRelatedField( - slug_field='name', queryset=Platform.objects.all(), label=_("Platform") - ) protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22']) domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name')) nodes_display = serializers.ListField( @@ -97,6 +94,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { + 'hostname': {'label': _("Name")}, + 'ip': {'label': 'Address'}, 'protocol': {'write_only': True}, 'port': {'write_only': True}, 'admin_user_display': {'label': _('Admin user display'), 'read_only': True}, @@ -177,7 +176,10 @@ class MiniAssetSerializer(serializers.ModelSerializer): class AssetSimpleSerializer(serializers.ModelSerializer): class Meta: model = Asset - fields = ['id', 'hostname', 'ip', 'port', 'connectivity', 'date_verified'] + fields = [ + 'id', 'hostname', 'ip', 'port', + 'connectivity', 'date_verified' + ] class AssetsTaskSerializer(serializers.Serializer): diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py index 08d159713..95606a866 100644 --- a/apps/assets/serializers/asset/host.py +++ b/apps/assets/serializers/asset/host.py @@ -1,9 +1,9 @@ from rest_framework import serializers from .common import AssetSerializer -from assets.models import DeviceInfo, Host +from assets.models import DeviceInfo, Host, Database -__all__ = ['DeviceSerializer', 'HostSerializer'] +__all__ = ['DeviceSerializer', 'HostSerializer', 'DatabaseSerializer'] class DeviceSerializer(serializers.ModelSerializer): @@ -23,3 +23,9 @@ class HostSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Host fields = AssetSerializer.Meta.fields + ['device_info'] + + +class DatabaseSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Database + fields = AssetSerializer.Meta.fields + ['db_name'] diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 6788a7329..826e16d4e 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -11,7 +11,8 @@ app_name = 'assets' router = BulkRouter() router.register(r'assets', api.AssetViewSet, 'asset') -router.register(r'hosts', api.HostViewSet, 'asset') +router.register(r'hosts', api.HostViewSet, 'host') +router.register(r'databases', api.DatabaseViewSet, 'database') router.register(r'accounts', api.AccountViewSet, 'account') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 22c57dcbe..5625428c7 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:050a3fd63c1cf9b3dc60c8f138d58f029f2e8a32a71abd99fff6899b68c0f6d9 -size 129742 +oid sha256:7b79695fe8cb323097c12171db8f6ae58b8e016b317f08562183b677f537e8b3 +size 129597 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a6c716d25..94187fd38 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-29 18:26+0800\n" +"POT-Creation-Date: 2022-04-29 10:04+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,16 +22,16 @@ msgstr "" msgid "Acls" msgstr "Acls" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 -#: applications/models/application.py:217 assets/models/asset.py:138 -#: assets/models/base.py:175 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 -#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 -#: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 -#: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/storage.py:23 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:32 -#: users/models/group.py:15 users/models/user.py:659 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:49 +#: applications/models/application.py:14 assets/models/base.py:175 +#: assets/models/cluster.py:18 assets/models/cmd_filter.py:27 +#: assets/models/domain.py:23 assets/models/group.py:20 +#: assets/models/label.py:18 assets/models/platform.py:16 +#: assets/models/protocol.py:8 ops/mixin.py:24 orgs/models.py:65 +#: perms/models/base.py:83 rbac/models/role.py:29 settings/models.py:29 +#: settings/serializers/sms.py:6 terminal/models/storage.py:23 +#: terminal/models/task.py:16 terminal/models/terminal.py:100 +#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:659 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -41,29 +41,29 @@ msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:235 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:235 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" -#: acls/models/base.py:31 authentication/models.py:17 +#: acls/models/base.py:31 authentication/models.py:18 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:26 #: users/templates/users/_select_user_modal.html:18 msgid "Active" msgstr "アクティブ" -#: acls/models/base.py:32 applications/models/application.py:230 -#: assets/models/asset.py:143 assets/models/asset.py:231 -#: assets/models/backup.py:54 assets/models/base.py:180 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 -#: assets/models/cmd_filter.py:96 assets/models/domain.py:24 -#: assets/models/domain.py:64 assets/models/group.py:23 -#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 +#: acls/models/base.py:32 applications/models/application.py:27 +#: assets/models/asset/common.py:155 assets/models/backup.py:54 +#: assets/models/base.py:180 assets/models/cluster.py:29 +#: assets/models/cmd_filter.py:48 assets/models/cmd_filter.py:96 +#: assets/models/domain.py:24 assets/models/domain.py:64 +#: assets/models/group.py:23 assets/models/label.py:23 +#: assets/models/platform.py:22 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 @@ -90,10 +90,10 @@ msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 -#: authentication/models.py:50 orgs/models.py:214 perms/models/base.py:84 +#: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 #: rbac/builtin.py:101 rbac/models/rolebinding.py:40 templates/index.html:78 #: terminal/backends/command/models.py:19 -#: terminal/backends/command/serializers.py:12 terminal/models/session.py:42 +#: terminal/backends/command/serializers.py:12 terminal/models/session.py:29 #: terminal/notifications.py:91 terminal/notifications.py:139 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:884 #: users/models/user.py:915 users/serializers/group.py:19 @@ -109,7 +109,7 @@ msgid "Rule" msgstr "ルール" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 +#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:77 #: assets/models/cmd_filter.py:89 audits/models.py:61 audits/serializers.py:51 #: authentication/templates/authentication/_access_key_modal.html:34 #: users/templates/users/_granted_assets.html:29 @@ -136,13 +136,13 @@ msgstr "システムユーザー" #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:36 -#: assets/models/asset.py:383 assets/models/authbook.py:19 +#: assets/models/asset/common.py:312 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:264 audits/models.py:39 -#: perms/models/asset_permission.py:23 templates/index.html:82 +#: assets/serializers/system_user.py:265 audits/models.py:39 +#: perms/models/asset_permission.py:24 templates/index.html:82 #: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:31 #: terminal/notifications.py:90 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 @@ -160,12 +160,12 @@ msgstr "ログインasset acl" msgid "Login asset confirm" msgstr "ログイン資産の確認" -#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:12 +#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:14 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" -#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 -#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 +#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:19 +#: acls/serializers/login_asset_acl.py:53 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:119 #: authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -179,7 +179,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること msgid "Username" msgstr "ユーザー名" -#: acls/serializers/login_asset_acl.py:24 +#: acls/serializers/login_asset_acl.py:26 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -189,9 +189,9 @@ msgstr "" "192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" "db8:1a:1110:::/64 (ドメイン名サポート)" -#: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 +#: acls/serializers/login_asset_acl.py:33 acls/serializers/rules/rules.py:33 #: applications/serializers/attrs/application_type/mysql_workbench.py:17 -#: assets/models/asset.py:210 assets/models/domain.py:60 +#: assets/models/asset/common.py:129 assets/models/domain.py:60 #: assets/serializers/account.py:13 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 @@ -202,7 +202,7 @@ msgstr "" msgid "IP" msgstr "IP" -#: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 +#: acls/serializers/login_asset_acl.py:37 assets/models/asset/common.py:128 #: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 #: users/templates/users/_granted_assets.html:25 @@ -210,7 +210,7 @@ msgstr "IP" msgid "Hostname" msgstr "ホスト名" -#: acls/serializers/login_asset_acl.py:42 +#: acls/serializers/login_asset_acl.py:44 msgid "" "Format for comma-delimited string, with * indicating a match all. Protocol " "options: {}" @@ -218,22 +218,22 @@ msgstr "" "コンマ区切り文字列の形式。* はすべて一致することを示します。プロトコルオプ" "ション: {}" -#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:62 assets/models/user.py:248 +#: acls/serializers/login_asset_acl.py:57 assets/models/asset/common.py:133 +#: assets/models/domain.py:62 assets/models/user.py:236 #: terminal/serializers/session.py:30 terminal/serializers/storage.py:69 msgid "Protocol" msgstr "プロトコル" -#: acls/serializers/login_asset_acl.py:65 +#: acls/serializers/login_asset_acl.py:67 msgid "Unsupported protocols: {}" msgstr "サポートされていないプロトコル: {}" -#: acls/serializers/login_asset_acl.py:98 +#: acls/serializers/login_asset_acl.py:100 #: tickets/serializers/ticket/ticket.py:105 msgid "The organization `{}` does not exist" msgstr "組織 '{}'は存在しません" -#: acls/serializers/login_asset_acl.py:103 +#: acls/serializers/login_asset_acl.py:105 msgid "None of the reviewers belong to Organization `{}`" msgstr "いずれのレビューアも組織 '{}' に属していません" @@ -256,36 +256,39 @@ msgstr "" msgid "Time Period" msgstr "期間" -#: applications/apps.py:9 applications/models/application.py:63 +#: applications/apps.py:9 applications/models/tree.py:57 msgid "Applications" msgstr "アプリケーション" -#: applications/const.py:8 +#: applications/const.py:8 applications/models/database.py:10 +#: applications/models/database.py:13 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:25 +#: assets/const.py:15 assets/models/asset/database.py:8 +#: assets/models/asset/database.py:14 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "データベース" -#: applications/const.py:9 +#: applications/const.py:9 assets/const.py:16 msgid "Remote app" msgstr "リモートアプリ" -#: applications/const.py:35 +#: applications/const.py:35 assets/const.py:51 msgid "Custom" msgstr "カスタム" -#: applications/models/account.py:12 applications/models/application.py:234 +#: applications/models/account.py:13 applications/models/application.py:31 #: assets/models/backup.py:32 assets/models/cmd_filter.py:45 #: perms/models/application_permission.py:28 msgid "Application" msgstr "アプリケーション" -#: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 +#: applications/models/account.py:16 assets/models/authbook.py:20 +#: assets/models/cmd_filter.py:42 assets/models/user.py:326 audits/models.py:40 #: perms/models/application_permission.py:33 -#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 +#: perms/models/asset_permission.py:26 terminal/backends/command/models.py:21 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:33 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -298,25 +301,30 @@ msgstr "アプリケーション" msgid "System user" msgstr "システムユーザー" -#: applications/models/account.py:17 assets/models/authbook.py:21 +#: applications/models/account.py:18 assets/models/authbook.py:21 #: settings/serializers/auth/cas.py:18 msgid "Version" msgstr "バージョン" -#: applications/models/account.py:23 +#: applications/models/account.py:24 msgid "Application account" msgstr "アプリケーションアカウント" -#: applications/models/account.py:26 +#: applications/models/account.py:27 msgid "Can view application account secret" msgstr "アプリケーションアカウントの秘密を表示できます" -#: applications/models/account.py:27 +#: applications/models/account.py:28 msgid "Can change application account secret" msgstr "アプリケーションアカウントの秘密を変更できます" -#: applications/models/application.py:219 -#: applications/serializers/application.py:99 assets/models/label.py:21 +#: applications/models/account.py:117 +msgid "Application user" +msgstr "アプリケーションユーザー" + +#: applications/models/application.py:16 +#: applications/serializers/application.py:99 assets/models/asset/common.py:130 +#: assets/models/label.py:21 assets/models/platform.py:17 #: perms/models/application_permission.py:21 #: perms/serializers/application/user_permission.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22 @@ -324,9 +332,11 @@ msgstr "アプリケーションアカウントの秘密を変更できます" msgid "Category" msgstr "カテゴリ" -#: applications/models/application.py:222 -#: applications/serializers/application.py:101 assets/models/backup.py:49 -#: assets/models/cmd_filter.py:82 assets/models/user.py:246 +#: applications/models/application.py:19 +#: applications/serializers/application.py:101 +#: assets/models/asset/common.py:131 assets/models/backup.py:49 +#: assets/models/cmd_filter.py:82 assets/models/platform.py:18 +#: assets/models/user.py:234 assets/serializers/platform.py:15 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:55 terminal/models/storage.py:119 @@ -337,26 +347,48 @@ msgstr "カテゴリ" msgid "Type" msgstr "タイプ" -#: applications/models/application.py:226 assets/models/asset.py:217 +#: applications/models/application.py:23 assets/models/asset/common.py:139 #: assets/models/domain.py:29 assets/models/domain.py:63 msgid "Domain" msgstr "ドメイン" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:58 +#: applications/models/application.py:25 assets/models/asset/remote_app.py:10 +#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "ツールバーの" -#: applications/models/application.py:238 +#: applications/models/application.py:35 msgid "Can match application" msgstr "アプリケーションを一致させることができます" -#: applications/models/application.py:286 -msgid "Application user" -msgstr "アプリケーションユーザー" +#: applications/models/database.py:8 +#: applications/serializers/attrs/application_category/db.py:11 +#: assets/const.py:13 assets/models/asset/host.py:16 ops/models/adhoc.py:157 +#: settings/serializers/auth/radius.py:14 +#: xpack/plugins/cloud/serializers/account_attrs.py:68 +msgid "Host" +msgstr "ホスト" + +#: applications/models/database.py:9 +#: applications/serializers/attrs/application_category/db.py:12 +#: applications/serializers/attrs/application_type/mongodb.py:10 +#: applications/serializers/attrs/application_type/mysql.py:10 +#: applications/serializers/attrs/application_type/mysql_workbench.py:21 +#: applications/serializers/attrs/application_type/oracle.py:10 +#: applications/serializers/attrs/application_type/pgsql.py:10 +#: applications/serializers/attrs/application_type/redis.py:10 +#: applications/serializers/attrs/application_type/sqlserver.py:10 +#: assets/models/asset/common.py:134 assets/models/domain.py:61 +#: assets/models/protocol.py:9 settings/serializers/auth/radius.py:15 +#: xpack/plugins/cloud/serializers/account_attrs.py:69 +msgid "Port" +msgstr "ポート" #: applications/serializers/application.py:70 -#: applications/serializers/application.py:100 assets/serializers/label.py:13 +#: applications/serializers/application.py:100 +#: assets/serializers/asset/common.py:71 assets/serializers/label.py:13 +#: assets/serializers/platform.py:12 #: perms/serializers/application/permission.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26 msgid "Category display" @@ -364,7 +396,8 @@ msgstr "カテゴリ表示" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/system_user.py:27 audits/serializers.py:29 +#: assets/serializers/asset/common.py:72 assets/serializers/platform.py:13 +#: assets/serializers/system_user.py:28 audits/serializers.py:29 #: perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 @@ -372,15 +405,15 @@ msgstr "カテゴリ表示" msgid "Type display" msgstr "タイプ表示" -#: applications/serializers/application.py:103 assets/models/asset.py:230 -#: assets/models/base.py:181 assets/models/cluster.py:26 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 -#: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 -#: assets/serializers/cmd_filter.py:49 common/db/models.py:113 -#: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 -#: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:916 +#: applications/serializers/application.py:103 +#: assets/models/asset/common.py:154 assets/models/base.py:181 +#: assets/models/cluster.py:26 assets/models/domain.py:26 +#: assets/models/gathered_user.py:19 assets/models/group.py:22 +#: assets/models/label.py:25 assets/serializers/account.py:18 +#: assets/serializers/cmd_filter.py:28 assets/serializers/cmd_filter.py:49 +#: common/db/models.py:90 common/mixins/models.py:50 ops/models/adhoc.py:39 +#: ops/models/command.py:30 orgs/models.py:67 orgs/models.py:217 +#: perms/models/base.py:92 users/models/group.py:18 users/models/user.py:916 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "作成された日付" @@ -388,7 +421,7 @@ msgstr "作成された日付" #: applications/serializers/application.py:104 assets/models/base.py:182 #: assets/models/gathered_user.py:20 assets/serializers/account.py:21 #: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:50 -#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 +#: common/db/models.py:91 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 msgid "Date updated" msgstr "更新日" @@ -403,30 +436,10 @@ msgid "account" msgstr "アカウント" #: applications/serializers/attrs/application_category/cloud.py:8 -#: assets/models/cluster.py:40 +#: assets/models/asset/cloud.py:8 assets/models/cluster.py:40 msgid "Cluster" msgstr "クラスター" -#: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: xpack/plugins/cloud/serializers/account_attrs.py:68 -msgid "Host" -msgstr "ホスト" - -#: applications/serializers/attrs/application_category/db.py:12 -#: applications/serializers/attrs/application_type/mongodb.py:10 -#: applications/serializers/attrs/application_type/mysql.py:10 -#: applications/serializers/attrs/application_type/mysql_workbench.py:21 -#: applications/serializers/attrs/application_type/oracle.py:10 -#: applications/serializers/attrs/application_type/pgsql.py:10 -#: applications/serializers/attrs/application_type/redis.py:10 -#: applications/serializers/attrs/application_type/sqlserver.py:10 -#: assets/models/asset.py:214 assets/models/domain.py:61 -#: settings/serializers/auth/radius.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:69 -msgid "Port" -msgstr "ポート" - #: applications/serializers/attrs/application_category/remote_app.py:39 #: applications/serializers/attrs/application_type/chrome.py:13 #: applications/serializers/attrs/application_type/mysql_workbench.py:13 @@ -435,7 +448,7 @@ msgid "Application path" msgstr "アプリケーションパス" #: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:163 +#: assets/serializers/system_user.py:164 #: xpack/plugins/change_auth_plan/serializers/asset.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:69 #: xpack/plugins/change_auth_plan/serializers/asset.py:72 @@ -514,124 +527,90 @@ msgstr "削除に失敗し、ノードにアセットが含まれています。 msgid "App assets" msgstr "アプリ資産" -#: assets/models/asset.py:139 -msgid "Base" -msgstr "ベース" +#: assets/const.py:14 +msgid "NetworkDevice" +msgstr "" -#: assets/models/asset.py:140 -msgid "Charset" -msgstr "シャーセット" +#: assets/const.py:17 +#, fuzzy +#| msgid "Loading" +msgid "Clouding" +msgstr "読み込み中" -#: assets/models/asset.py:141 assets/serializers/asset.py:176 -#: tickets/models/ticket.py:133 -msgid "Meta" -msgstr "メタ" +#: assets/const.py:26 +msgid "Mainframe" +msgstr "" -#: assets/models/asset.py:142 -msgid "Internal" -msgstr "内部" +#: assets/const.py:27 +#, fuzzy +#| msgid "Other" +msgid "Other host" +msgstr "その他" -#: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:15 assets/serializers/asset.py:63 -#: perms/serializers/asset/user_permission.py:43 -msgid "Platform" -msgstr "プラットフォーム" +#: assets/const.py:31 +#, fuzzy +#| msgid "Switch from" +msgid "Switch" +msgstr "から切り替え" -#: assets/models/asset.py:168 -msgid "Vendor" -msgstr "ベンダー" +#: assets/const.py:32 +msgid "Router" +msgstr "" -#: assets/models/asset.py:169 -msgid "Model" -msgstr "モデル" +#: assets/const.py:33 +msgid "Firewall" +msgstr "" -#: assets/models/asset.py:170 tickets/models/ticket.py:159 -msgid "Serial number" -msgstr "シリアル番号" +#: assets/const.py:34 +msgid "Other device" +msgstr "" -#: assets/models/asset.py:172 -msgid "CPU model" -msgstr "CPU モデル" - -#: assets/models/asset.py:173 -msgid "CPU count" -msgstr "CPU カウント" - -#: assets/models/asset.py:174 -msgid "CPU cores" -msgstr "CPU カラー" - -#: assets/models/asset.py:175 -msgid "CPU vcpus" -msgstr "CPU 合計" - -#: assets/models/asset.py:176 -msgid "Memory" -msgstr "メモリ" - -#: assets/models/asset.py:177 -msgid "Disk total" -msgstr "ディスクの合計" - -#: assets/models/asset.py:178 -msgid "Disk info" -msgstr "ディスク情報" - -#: assets/models/asset.py:180 -msgid "OS" -msgstr "OS" - -#: assets/models/asset.py:181 -msgid "OS version" -msgstr "システムバージョン" - -#: assets/models/asset.py:182 -msgid "OS arch" -msgstr "システムアーキテクチャ" - -#: assets/models/asset.py:183 -msgid "Hostname raw" -msgstr "ホスト名生" - -#: assets/models/asset.py:215 assets/serializers/account.py:16 -#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 +#: assets/models/asset/common.py:135 assets/serializers/account.py:16 +#: assets/serializers/asset/common.py:63 +#: perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" -#: assets/models/asset.py:218 assets/models/user.py:238 -#: perms/models/asset_permission.py:24 +#: assets/models/asset/common.py:137 assets/models/platform.py:41 +#: assets/serializers/account.py:15 assets/serializers/asset/common.py:61 +#: perms/serializers/asset/user_permission.py:43 +msgid "Platform" +msgstr "プラットフォーム" + +#: assets/models/asset/common.py:141 assets/models/user.py:226 +#: perms/models/asset_permission.py:25 #: xpack/plugins/change_auth_plan/models/asset.py:43 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "ノード" -#: assets/models/asset.py:219 assets/models/cmd_filter.py:47 +#: assets/models/asset/common.py:142 assets/models/cmd_filter.py:47 #: assets/models/domain.py:65 assets/models/label.py:22 msgid "Is active" msgstr "アクティブです。" -#: assets/models/asset.py:222 assets/models/cluster.py:19 -#: assets/models/user.py:235 assets/models/user.py:390 +#: assets/models/asset/common.py:146 assets/models/cluster.py:19 +#: assets/models/user.py:223 assets/models/user.py:378 msgid "Admin user" msgstr "管理ユーザー" -#: assets/models/asset.py:225 +#: assets/models/asset/common.py:149 msgid "Public IP" msgstr "パブリックIP" -#: assets/models/asset.py:226 +#: assets/models/asset/common.py:150 msgid "Asset number" msgstr "資産番号" -#: assets/models/asset.py:228 +#: assets/models/asset/common.py:152 msgid "Labels" msgstr "ラベル" -#: assets/models/asset.py:229 assets/models/base.py:183 +#: assets/models/asset/common.py:153 assets/models/base.py:183 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:52 #: assets/models/cmd_filter.py:99 assets/models/group.py:21 -#: common/db/models.py:111 common/mixins/models.py:49 orgs/models.py:66 +#: common/db/models.py:88 common/mixins/models.py:49 orgs/models.py:66 #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:704 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 @@ -639,30 +618,96 @@ msgstr "ラベル" msgid "Created by" msgstr "によって作成された" -#: assets/models/asset.py:386 +#: assets/models/asset/common.py:315 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" -#: assets/models/asset.py:387 +#: assets/models/asset/common.py:316 msgid "Can test asset connectivity" msgstr "資産接続をテストできます" -#: assets/models/asset.py:388 +#: assets/models/asset/common.py:317 msgid "Can push system user to asset" msgstr "システムユーザーを資産にプッシュできます" -#: assets/models/asset.py:389 +#: assets/models/asset/common.py:318 msgid "Can match asset" msgstr "アセットを一致させることができます" -#: assets/models/asset.py:390 +#: assets/models/asset/common.py:319 msgid "Add asset to node" msgstr "ノードにアセットを追加する" -#: assets/models/asset.py:391 +#: assets/models/asset/common.py:320 msgid "Move asset to node" msgstr "アセットをノードに移動する" +#: assets/models/asset/host.py:18 +msgid "Vendor" +msgstr "ベンダー" + +#: assets/models/asset/host.py:19 +msgid "Model" +msgstr "モデル" + +#: assets/models/asset/host.py:20 tickets/models/ticket.py:159 +msgid "Serial number" +msgstr "シリアル番号" + +#: assets/models/asset/host.py:22 +msgid "CPU model" +msgstr "CPU モデル" + +#: assets/models/asset/host.py:23 +msgid "CPU count" +msgstr "CPU カウント" + +#: assets/models/asset/host.py:24 +msgid "CPU cores" +msgstr "CPU カラー" + +#: assets/models/asset/host.py:25 +msgid "CPU vcpus" +msgstr "CPU 合計" + +#: assets/models/asset/host.py:26 +msgid "Memory" +msgstr "メモリ" + +#: assets/models/asset/host.py:27 +msgid "Disk total" +msgstr "ディスクの合計" + +#: assets/models/asset/host.py:28 +msgid "Disk info" +msgstr "ディスク情報" + +#: assets/models/asset/host.py:30 +msgid "OS" +msgstr "OS" + +#: assets/models/asset/host.py:31 +msgid "OS version" +msgstr "システムバージョン" + +#: assets/models/asset/host.py:32 +msgid "OS arch" +msgstr "システムアーキテクチャ" + +#: assets/models/asset/host.py:33 +msgid "Hostname raw" +msgstr "ホスト名生" + +#: assets/models/asset/host.py:58 +msgid "DeviceInfo" +msgstr "" + +#: assets/models/asset/remote_app.py:8 +#, fuzzy +#| msgid "Application path" +msgid "App path" +msgstr "アプリケーションパス" + #: assets/models/authbook.py:27 msgid "AuthBook" msgstr "資産アカウント" @@ -706,7 +751,7 @@ msgid "Timing trigger" msgstr "タイミングトリガー" #: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31 -#: perms/models/base.py:89 terminal/models/session.py:56 +#: perms/models/base.py:89 terminal/models/session.py:43 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 #: xpack/plugins/change_auth_plan/models/base.py:112 @@ -767,7 +812,7 @@ msgstr "OK" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失敗しました" @@ -857,7 +902,7 @@ msgstr "デフォルトクラスター" msgid "User group" msgstr "ユーザーグループ" -#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:54 +#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:55 msgid "Command filter" msgstr "コマンドフィルター" @@ -866,7 +911,7 @@ msgid "Regex" msgstr "正規情報" #: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:53 +#: terminal/backends/command/serializers.py:15 terminal/models/session.py:40 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -954,7 +999,8 @@ msgstr "資産グループ" msgid "Default asset group" msgstr "デフォルトアセットグループ" -#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:30 +#: assets/models/label.py:19 assets/models/node.py:546 +#: common/drf/serializers.py:89 settings/models.py:30 msgid "Value" msgstr "値" @@ -970,7 +1016,7 @@ msgstr "新しいノード" msgid "empty" msgstr "空" -#: assets/models/node.py:545 perms/models/asset_permission.py:101 +#: assets/models/node.py:545 perms/models/asset_permission.py:102 msgid "Key" msgstr "キー" @@ -978,11 +1024,11 @@ msgstr "キー" msgid "Full value" msgstr "フルバリュー" -#: assets/models/node.py:550 perms/models/asset_permission.py:102 +#: assets/models/node.py:550 perms/models/asset_permission.py:103 msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:559 assets/serializers/system_user.py:263 +#: assets/models/node.py:559 assets/serializers/system_user.py:264 #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 @@ -994,77 +1040,90 @@ msgstr "ノード" msgid "Can match node" msgstr "ノードを一致させることができます" -#: assets/models/user.py:229 +#: assets/models/platform.py:19 +msgid "Charset" +msgstr "シャーセット" + +#: assets/models/platform.py:20 assets/serializers/platform.py:14 +#: tickets/models/ticket.py:133 +msgid "Meta" +msgstr "メタ" + +#: assets/models/platform.py:21 +msgid "Internal" +msgstr "内部" + +#: assets/models/user.py:217 msgid "Automatic managed" msgstr "自動管理" -#: assets/models/user.py:230 +#: assets/models/user.py:218 msgid "Manually input" msgstr "手動入力" -#: assets/models/user.py:234 +#: assets/models/user.py:222 msgid "Common user" msgstr "共通ユーザー" -#: assets/models/user.py:237 +#: assets/models/user.py:225 msgid "Username same with user" msgstr "ユーザーと同じユーザー名" -#: assets/models/user.py:240 assets/serializers/domain.py:29 +#: assets/models/user.py:228 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:244 users/apps.py:9 +#: assets/models/user.py:232 users/apps.py:9 msgid "Users" msgstr "ユーザー" -#: assets/models/user.py:245 +#: assets/models/user.py:233 msgid "User groups" msgstr "ユーザーグループ" -#: assets/models/user.py:249 +#: assets/models/user.py:237 msgid "Auto push" msgstr "オートプッシュ" -#: assets/models/user.py:250 +#: assets/models/user.py:238 msgid "Sudo" msgstr "すど" -#: assets/models/user.py:251 +#: assets/models/user.py:239 msgid "Shell" msgstr "シェル" -#: assets/models/user.py:252 +#: assets/models/user.py:240 msgid "Login mode" msgstr "ログインモード" -#: assets/models/user.py:253 +#: assets/models/user.py:241 msgid "SFTP Root" msgstr "SFTPルート" -#: assets/models/user.py:254 authentication/models.py:48 +#: assets/models/user.py:242 authentication/models.py:49 msgid "Token" msgstr "トークン" -#: assets/models/user.py:255 +#: assets/models/user.py:243 msgid "Home" msgstr "ホーム" -#: assets/models/user.py:256 +#: assets/models/user.py:244 msgid "System groups" msgstr "システムグループ" -#: assets/models/user.py:259 +#: assets/models/user.py:247 msgid "User switch" msgstr "ユーザースイッチ" -#: assets/models/user.py:260 +#: assets/models/user.py:248 msgid "Switch from" msgstr "から切り替え" -#: assets/models/user.py:340 +#: assets/models/user.py:328 msgid "Can match system user" msgstr "システムユーザーに一致できます" @@ -1099,38 +1158,30 @@ msgstr "" msgid "System user display" msgstr "システムユーザー表示" -#: assets/serializers/asset.py:20 +#: assets/serializers/asset/common.py:18 msgid "Protocol format should {}/{}" msgstr "プロトコル形式は {}/{}" -#: assets/serializers/asset.py:37 +#: assets/serializers/asset/common.py:35 msgid "Protocol duplicate: {}" msgstr "プロトコル重複: {}" -#: assets/serializers/asset.py:66 +#: assets/serializers/asset/common.py:64 msgid "Domain name" msgstr "ドメイン名" -#: assets/serializers/asset.py:68 +#: assets/serializers/asset/common.py:66 msgid "Nodes name" msgstr "ノード名" -#: assets/serializers/asset.py:71 +#: assets/serializers/asset/common.py:69 msgid "Labels name" msgstr "ラベル名" -#: assets/serializers/asset.py:105 -msgid "Hardware info" -msgstr "ハードウェア情報" - -#: assets/serializers/asset.py:106 +#: assets/serializers/asset/common.py:102 msgid "Admin user display" msgstr "管理者ユーザー表示" -#: assets/serializers/asset.py:107 -msgid "CPU info" -msgstr "CPU情報" - #: assets/serializers/backup.py:20 perms/models/base.py:87 #: perms/serializers/application/permission.py:17 #: perms/serializers/application/permission.py:42 @@ -1163,7 +1214,7 @@ msgid "Action display" msgstr "アクション表示" #: assets/serializers/domain.py:13 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:59 +#: assets/serializers/system_user.py:60 #: perms/serializers/asset/permission.py:49 msgid "Assets amount" msgstr "資産額" @@ -1188,78 +1239,78 @@ msgstr "含まれない:/" msgid "The same level node name cannot be the same" msgstr "同じレベルのノード名を同じにすることはできません。" -#: assets/serializers/system_user.py:28 +#: assets/serializers/system_user.py:29 msgid "SSH key fingerprint" msgstr "SSHキー指紋" -#: assets/serializers/system_user.py:30 +#: assets/serializers/system_user.py:31 #: perms/serializers/application/permission.py:46 msgid "Apps amount" msgstr "アプリの量" -#: assets/serializers/system_user.py:58 +#: assets/serializers/system_user.py:59 #: perms/serializers/asset/permission.py:50 msgid "Nodes amount" msgstr "ノード量" -#: assets/serializers/system_user.py:60 assets/serializers/system_user.py:265 +#: assets/serializers/system_user.py:61 assets/serializers/system_user.py:266 msgid "Login mode display" msgstr "ログインモード表示" -#: assets/serializers/system_user.py:62 +#: assets/serializers/system_user.py:63 msgid "Ad domain" msgstr "広告ドメイン" -#: assets/serializers/system_user.py:63 +#: assets/serializers/system_user.py:64 msgid "Is asset protocol" msgstr "資産プロトコルです" -#: assets/serializers/system_user.py:64 +#: assets/serializers/system_user.py:65 msgid "Only ssh and automatic login system users are supported" msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" -#: assets/serializers/system_user.py:104 +#: assets/serializers/system_user.py:105 msgid "Username same with user with protocol {} only allow 1" msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" -#: assets/serializers/system_user.py:117 common/validators.py:14 +#: assets/serializers/system_user.py:118 common/validators.py:14 msgid "Special char not allowed" msgstr "特別なcharは許可されていません" -#: assets/serializers/system_user.py:127 +#: assets/serializers/system_user.py:128 msgid "* Automatic login mode must fill in the username." msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" -#: assets/serializers/system_user.py:142 +#: assets/serializers/system_user.py:143 msgid "Path should starts with /" msgstr "パスは/で始まる必要があります" -#: assets/serializers/system_user.py:154 +#: assets/serializers/system_user.py:155 msgid "Password or private key required" msgstr "パスワードまたは秘密鍵が必要" -#: assets/serializers/system_user.py:168 +#: assets/serializers/system_user.py:169 msgid "Only ssh protocol system users are allowed" msgstr "Sshプロトコルシステムユーザーのみが許可されています" -#: assets/serializers/system_user.py:172 +#: assets/serializers/system_user.py:173 msgid "The protocol must be consistent with the current user: {}" msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" -#: assets/serializers/system_user.py:176 +#: assets/serializers/system_user.py:177 msgid "Only system users with automatic login are allowed" msgstr "自動ログインを持つシステムユーザーのみが許可されます" -#: assets/serializers/system_user.py:281 +#: assets/serializers/system_user.py:282 msgid "System user name" msgstr "システムユーザー名" -#: assets/serializers/system_user.py:282 orgs/mixins/serializers.py:26 +#: assets/serializers/system_user.py:283 orgs/mixins/serializers.py:26 #: rbac/serializers/rolebinding.py:23 msgid "Org name" msgstr "組織名" -#: assets/serializers/system_user.py:291 +#: assets/serializers/system_user.py:292 msgid "Asset hostname" msgstr "資産ホスト名" @@ -1418,7 +1469,7 @@ msgid "Symlink" msgstr "Symlink" #: audits/models.py:38 audits/models.py:64 audits/models.py:87 -#: terminal/models/session.py:49 terminal/models/sharing.py:82 +#: terminal/models/session.py:36 terminal/models/sharing.py:82 msgid "Remote addr" msgstr "リモートaddr" @@ -1668,7 +1719,7 @@ msgstr "{AssetPermission} 追加 {UserGroup}" msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:131 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:131 perms/models/asset_permission.py:30 #: users/templates/users/_user_detail_nav_header.html:31 msgid "Asset permission" msgstr "資産権限" @@ -2065,31 +2116,31 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:33 terminal/serializers/storage.py:30 +#: authentication/models.py:34 terminal/serializers/storage.py:30 msgid "Access key" msgstr "アクセスキー" -#: authentication/models.py:40 +#: authentication/models.py:41 msgid "Private Token" msgstr "プライベートトークン" -#: authentication/models.py:49 +#: authentication/models.py:50 msgid "Expired" msgstr "期限切れ" -#: authentication/models.py:53 +#: authentication/models.py:54 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:61 +#: authentication/models.py:62 msgid "Connection token" msgstr "接続トークン" -#: authentication/models.py:63 +#: authentication/models.py:64 msgid "Can view connection token secret" msgstr "接続トークンの秘密を表示できます" -#: authentication/models.py:70 +#: authentication/models.py:71 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2501,7 +2552,7 @@ msgstr "%(name)s は正常に更新されました" msgid "ugettext_lazy" msgstr "ugettext_lazy" -#: common/db/models.py:112 +#: common/db/models.py:89 msgid "Updated by" msgstr "によって更新" @@ -2517,6 +2568,14 @@ msgstr "ファイルの内容がオーバーフローしました (最大長 '{} msgid "Parse file error: {}" msgstr "解析ファイルエラー: {}" +#: common/drf/serializers.py:88 rbac/serializers/role.py:27 +msgid "Display name" +msgstr "表示名" + +#: common/drf/serializers.py:93 +msgid "Children" +msgstr "" + #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." @@ -2570,7 +2629,7 @@ msgstr "チャーフィールドへのマーシャルデータ" msgid "Marshal data to text field" msgstr "テキストフィールドへのマーシャルデータ" -#: common/fields/model.py:150 +#: common/fields/model.py:149 msgid "Encrypt field using Secret Key" msgstr "Secret Keyを使用したフィールドの暗号化" @@ -2957,27 +3016,27 @@ msgstr "ユーザーアプリを表示できます" msgid "Can view usergroup apps" msgstr "ユーザー・グループ認可の適用を表示できます" -#: perms/models/asset_permission.py:134 +#: perms/models/asset_permission.py:135 msgid "Ungrouped" msgstr "グループ化されていません" -#: perms/models/asset_permission.py:136 +#: perms/models/asset_permission.py:137 msgid "Favorite" msgstr "お気に入り" -#: perms/models/asset_permission.py:183 +#: perms/models/asset_permission.py:184 msgid "Permed asset" msgstr "許可された資産" -#: perms/models/asset_permission.py:185 +#: perms/models/asset_permission.py:186 msgid "Can view my assets" msgstr "私の資産を見ることができます" -#: perms/models/asset_permission.py:186 +#: perms/models/asset_permission.py:187 msgid "Can view user assets" msgstr "ユーザー資産を表示できます" -#: perms/models/asset_permission.py:187 +#: perms/models/asset_permission.py:188 msgid "Can view usergroup assets" msgstr "ユーザーグループの資産を表示できます" @@ -3250,10 +3309,6 @@ msgstr "パーマ" msgid "Scope display" msgstr "スコープ表示" -#: rbac/serializers/role.py:27 -msgid "Display name" -msgstr "表示名" - #: rbac/serializers/rolebinding.py:22 msgid "Role display" msgstr "ロール表示" @@ -4931,35 +4986,35 @@ msgstr "セッションのリプレイをアップロードできます" msgid "Can download session replay" msgstr "セッション再生をダウンロードできます" -#: terminal/models/session.py:48 terminal/models/sharing.py:87 +#: terminal/models/session.py:35 terminal/models/sharing.py:87 msgid "Login from" msgstr "ログイン元" -#: terminal/models/session.py:52 +#: terminal/models/session.py:39 msgid "Replay" msgstr "リプレイ" -#: terminal/models/session.py:57 +#: terminal/models/session.py:44 msgid "Date end" msgstr "終了日" -#: terminal/models/session.py:242 +#: terminal/models/session.py:220 msgid "Session record" msgstr "セッション記録" -#: terminal/models/session.py:244 +#: terminal/models/session.py:222 msgid "Can monitor session" msgstr "セッションを監視できます" -#: terminal/models/session.py:245 +#: terminal/models/session.py:223 msgid "Can share session" msgstr "セッションを共有できます" -#: terminal/models/session.py:246 +#: terminal/models/session.py:224 msgid "Can terminate session" msgstr "セッションを終了できます" -#: terminal/models/session.py:247 +#: terminal/models/session.py:225 msgid "Can validate session action perm" msgstr "セッションアクションのパーマを検証できます" @@ -6516,58 +6571,64 @@ msgid "Baidu Cloud" msgstr "百度雲" #: xpack/plugins/cloud/const.py:15 +#, fuzzy +#| msgid "Baidu Cloud" +msgid "JD Cloud" +msgstr "百度雲" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "テンセント雲" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "華為私有雲" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青雲私有雲" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌雲" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "インスタンス名" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "インスタンス名と部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "同期していません" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新しい同期" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "同期済み" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "リリース済み" @@ -6740,11 +6801,13 @@ msgid "South America (São Paulo)" msgstr "南米 (サンパウロ)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "華北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "華南-広州" @@ -6766,6 +6829,7 @@ msgid "CN North-Baoding" msgstr "華北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "華東-上海" @@ -6830,11 +6894,17 @@ msgstr "華北-ウランチャブ一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "華南-広州-友好ユーザー環境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +#, fuzzy +#| msgid "CN East-Shanghai" +msgid "CN East-Suqian" +msgstr "華東-上海" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有効表示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "プロバイダ表示" @@ -6997,3 +7067,12 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" + +#~ msgid "Base" +#~ msgstr "ベース" + +#~ msgid "Hardware info" +#~ msgstr "ハードウェア情報" + +#~ msgid "CPU info" +#~ msgstr "CPU情報" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 860a45eaa..43655b79e 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1baa8c35aa2493c03c1fe7383a13ca4cfd9b18b44150770fb51f39433c18c74c -size 107492 +oid sha256:7326f6af4efae2abb098218faabe97aceed9a8f61dd5fcd56b16d5d07164556a +size 107769 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index ee4727335..9b235fcc3 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-29 18:26+0800\n" +"POT-Creation-Date: 2022-04-29 10:04+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -21,16 +21,16 @@ msgstr "" msgid "Acls" msgstr "访问控制" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 -#: applications/models/application.py:217 assets/models/asset.py:138 -#: assets/models/base.py:175 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 -#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 -#: orgs/models.py:65 perms/models/base.py:83 rbac/models/role.py:29 -#: settings/models.py:29 settings/serializers/sms.py:6 -#: terminal/models/storage.py:23 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:32 -#: users/models/group.py:15 users/models/user.py:659 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:49 +#: applications/models/application.py:14 assets/models/base.py:175 +#: assets/models/cluster.py:18 assets/models/cmd_filter.py:27 +#: assets/models/domain.py:23 assets/models/group.py:20 +#: assets/models/label.py:18 assets/models/platform.py:16 +#: assets/models/protocol.py:8 ops/mixin.py:24 orgs/models.py:65 +#: perms/models/base.py:83 rbac/models/role.py:29 settings/models.py:29 +#: settings/serializers/sms.py:6 terminal/models/storage.py:23 +#: terminal/models/task.py:16 terminal/models/terminal.py:100 +#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:659 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -40,29 +40,29 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:235 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:247 +#: assets/models/user.py:235 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" -#: acls/models/base.py:31 authentication/models.py:17 +#: acls/models/base.py:31 authentication/models.py:18 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:88 terminal/models/sharing.py:26 #: users/templates/users/_select_user_modal.html:18 msgid "Active" msgstr "激活中" -#: acls/models/base.py:32 applications/models/application.py:230 -#: assets/models/asset.py:143 assets/models/asset.py:231 -#: assets/models/backup.py:54 assets/models/base.py:180 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 -#: assets/models/cmd_filter.py:96 assets/models/domain.py:24 -#: assets/models/domain.py:64 assets/models/group.py:23 -#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:68 +#: acls/models/base.py:32 applications/models/application.py:27 +#: assets/models/asset/common.py:155 assets/models/backup.py:54 +#: assets/models/base.py:180 assets/models/cluster.py:29 +#: assets/models/cmd_filter.py:48 assets/models/cmd_filter.py:96 +#: assets/models/domain.py:24 assets/models/domain.py:64 +#: assets/models/group.py:23 assets/models/label.py:23 +#: assets/models/platform.py:22 ops/models/adhoc.py:38 orgs/models.py:68 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/comment.py:24 tickets/models/ticket.py:154 @@ -89,10 +89,10 @@ msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100 -#: authentication/models.py:50 orgs/models.py:214 perms/models/base.py:84 +#: authentication/models.py:51 orgs/models.py:214 perms/models/base.py:84 #: rbac/builtin.py:101 rbac/models/rolebinding.py:40 templates/index.html:78 #: terminal/backends/command/models.py:19 -#: terminal/backends/command/serializers.py:12 terminal/models/session.py:42 +#: terminal/backends/command/serializers.py:12 terminal/models/session.py:29 #: terminal/notifications.py:91 terminal/notifications.py:139 #: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:884 #: users/models/user.py:915 users/serializers/group.py:19 @@ -108,7 +108,7 @@ msgid "Rule" msgstr "规则" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 +#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:77 #: assets/models/cmd_filter.py:89 audits/models.py:61 audits/serializers.py:51 #: authentication/templates/authentication/_access_key_modal.html:34 #: users/templates/users/_granted_assets.html:29 @@ -135,13 +135,13 @@ msgstr "系统用户" #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:36 -#: assets/models/asset.py:383 assets/models/authbook.py:19 +#: assets/models/asset/common.py:312 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:38 #: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:264 audits/models.py:39 -#: perms/models/asset_permission.py:23 templates/index.html:82 +#: assets/serializers/system_user.py:265 audits/models.py:39 +#: perms/models/asset_permission.py:24 templates/index.html:82 #: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:31 #: terminal/notifications.py:90 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 @@ -159,12 +159,12 @@ msgstr "登录资产访问控制" msgid "Login asset confirm" msgstr "登录资产复核" -#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:12 +#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:14 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " -#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 -#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 +#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:19 +#: acls/serializers/login_asset_acl.py:53 assets/models/base.py:176 #: assets/models/gathered_user.py:15 audits/models.py:119 #: authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -178,7 +178,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " msgid "Username" msgstr "用户名" -#: acls/serializers/login_asset_acl.py:24 +#: acls/serializers/login_asset_acl.py:26 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -187,9 +187,9 @@ msgstr "" "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" -#: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 +#: acls/serializers/login_asset_acl.py:33 acls/serializers/rules/rules.py:33 #: applications/serializers/attrs/application_type/mysql_workbench.py:17 -#: assets/models/asset.py:210 assets/models/domain.py:60 +#: assets/models/asset/common.py:129 assets/models/domain.py:60 #: assets/serializers/account.py:13 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 @@ -200,7 +200,7 @@ msgstr "" msgid "IP" msgstr "IP" -#: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 +#: acls/serializers/login_asset_acl.py:37 assets/models/asset/common.py:128 #: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 #: settings/serializers/terminal.py:7 #: users/templates/users/_granted_assets.html:25 @@ -208,28 +208,28 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: acls/serializers/login_asset_acl.py:42 +#: acls/serializers/login_asset_acl.py:44 msgid "" "Format for comma-delimited string, with * indicating a match all. Protocol " "options: {}" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" -#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:62 assets/models/user.py:248 +#: acls/serializers/login_asset_acl.py:57 assets/models/asset/common.py:133 +#: assets/models/domain.py:62 assets/models/user.py:236 #: terminal/serializers/session.py:30 terminal/serializers/storage.py:69 msgid "Protocol" msgstr "协议" -#: acls/serializers/login_asset_acl.py:65 +#: acls/serializers/login_asset_acl.py:67 msgid "Unsupported protocols: {}" msgstr "不支持的协议: {}" -#: acls/serializers/login_asset_acl.py:98 +#: acls/serializers/login_asset_acl.py:100 #: tickets/serializers/ticket/ticket.py:105 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" -#: acls/serializers/login_asset_acl.py:103 +#: acls/serializers/login_asset_acl.py:105 msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" @@ -251,36 +251,39 @@ msgstr "" msgid "Time Period" msgstr "时段" -#: applications/apps.py:9 applications/models/application.py:63 +#: applications/apps.py:9 applications/models/tree.py:57 msgid "Applications" msgstr "应用管理" -#: applications/const.py:8 +#: applications/const.py:8 applications/models/database.py:10 +#: applications/models/database.py:13 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:25 +#: assets/const.py:15 assets/models/asset/database.py:8 +#: assets/models/asset/database.py:14 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "数据库" -#: applications/const.py:9 +#: applications/const.py:9 assets/const.py:16 msgid "Remote app" msgstr "远程应用" -#: applications/const.py:35 +#: applications/const.py:35 assets/const.py:51 msgid "Custom" msgstr "自定义" -#: applications/models/account.py:12 applications/models/application.py:234 +#: applications/models/account.py:13 applications/models/application.py:31 #: assets/models/backup.py:32 assets/models/cmd_filter.py:45 #: perms/models/application_permission.py:28 msgid "Application" msgstr "应用程序" -#: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:42 assets/models/user.py:338 audits/models.py:40 +#: applications/models/account.py:16 assets/models/authbook.py:20 +#: assets/models/cmd_filter.py:42 assets/models/user.py:326 audits/models.py:40 #: perms/models/application_permission.py:33 -#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 +#: perms/models/asset_permission.py:26 terminal/backends/command/models.py:21 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:33 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -293,25 +296,30 @@ msgstr "应用程序" msgid "System user" msgstr "系统用户" -#: applications/models/account.py:17 assets/models/authbook.py:21 +#: applications/models/account.py:18 assets/models/authbook.py:21 #: settings/serializers/auth/cas.py:18 msgid "Version" msgstr "版本" -#: applications/models/account.py:23 +#: applications/models/account.py:24 msgid "Application account" msgstr "应用账号" -#: applications/models/account.py:26 +#: applications/models/account.py:27 msgid "Can view application account secret" msgstr "可以查看应用账号密码" -#: applications/models/account.py:27 +#: applications/models/account.py:28 msgid "Can change application account secret" msgstr "可以查看应用账号密码" -#: applications/models/application.py:219 -#: applications/serializers/application.py:99 assets/models/label.py:21 +#: applications/models/account.py:117 +msgid "Application user" +msgstr "应用用户" + +#: applications/models/application.py:16 +#: applications/serializers/application.py:99 assets/models/asset/common.py:130 +#: assets/models/label.py:21 assets/models/platform.py:17 #: perms/models/application_permission.py:21 #: perms/serializers/application/user_permission.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22 @@ -319,9 +327,11 @@ msgstr "可以查看应用账号密码" msgid "Category" msgstr "类别" -#: applications/models/application.py:222 -#: applications/serializers/application.py:101 assets/models/backup.py:49 -#: assets/models/cmd_filter.py:82 assets/models/user.py:246 +#: applications/models/application.py:19 +#: applications/serializers/application.py:101 +#: assets/models/asset/common.py:131 assets/models/backup.py:49 +#: assets/models/cmd_filter.py:82 assets/models/platform.py:18 +#: assets/models/user.py:234 assets/serializers/platform.py:15 #: perms/models/application_permission.py:24 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:55 terminal/models/storage.py:119 @@ -332,26 +342,48 @@ msgstr "类别" msgid "Type" msgstr "类型" -#: applications/models/application.py:226 assets/models/asset.py:217 +#: applications/models/application.py:23 assets/models/asset/common.py:139 #: assets/models/domain.py:29 assets/models/domain.py:63 msgid "Domain" msgstr "网域" -#: applications/models/application.py:228 xpack/plugins/cloud/models.py:33 -#: xpack/plugins/cloud/serializers/account.py:58 +#: applications/models/application.py:25 assets/models/asset/remote_app.py:10 +#: xpack/plugins/cloud/models.py:33 +#: xpack/plugins/cloud/serializers/account.py:59 msgid "Attrs" msgstr "属性" -#: applications/models/application.py:238 +#: applications/models/application.py:35 msgid "Can match application" msgstr "匹配应用" -#: applications/models/application.py:286 -msgid "Application user" -msgstr "应用用户" +#: applications/models/database.py:8 +#: applications/serializers/attrs/application_category/db.py:11 +#: assets/const.py:13 assets/models/asset/host.py:16 ops/models/adhoc.py:157 +#: settings/serializers/auth/radius.py:14 +#: xpack/plugins/cloud/serializers/account_attrs.py:68 +msgid "Host" +msgstr "主机" + +#: applications/models/database.py:9 +#: applications/serializers/attrs/application_category/db.py:12 +#: applications/serializers/attrs/application_type/mongodb.py:10 +#: applications/serializers/attrs/application_type/mysql.py:10 +#: applications/serializers/attrs/application_type/mysql_workbench.py:21 +#: applications/serializers/attrs/application_type/oracle.py:10 +#: applications/serializers/attrs/application_type/pgsql.py:10 +#: applications/serializers/attrs/application_type/redis.py:10 +#: applications/serializers/attrs/application_type/sqlserver.py:10 +#: assets/models/asset/common.py:134 assets/models/domain.py:61 +#: assets/models/protocol.py:9 settings/serializers/auth/radius.py:15 +#: xpack/plugins/cloud/serializers/account_attrs.py:69 +msgid "Port" +msgstr "端口" #: applications/serializers/application.py:70 -#: applications/serializers/application.py:100 assets/serializers/label.py:13 +#: applications/serializers/application.py:100 +#: assets/serializers/asset/common.py:71 assets/serializers/label.py:13 +#: assets/serializers/platform.py:12 #: perms/serializers/application/permission.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26 msgid "Category display" @@ -359,7 +391,8 @@ msgstr "类别名称" #: applications/serializers/application.py:71 #: applications/serializers/application.py:102 -#: assets/serializers/system_user.py:27 audits/serializers.py:29 +#: assets/serializers/asset/common.py:72 assets/serializers/platform.py:13 +#: assets/serializers/system_user.py:28 audits/serializers.py:29 #: perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/ticket.py:21 @@ -367,15 +400,15 @@ msgstr "类别名称" msgid "Type display" msgstr "类型名称" -#: applications/serializers/application.py:103 assets/models/asset.py:230 -#: assets/models/base.py:181 assets/models/cluster.py:26 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 -#: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 -#: assets/serializers/cmd_filter.py:49 common/db/models.py:113 -#: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 -#: orgs/models.py:67 orgs/models.py:217 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:916 +#: applications/serializers/application.py:103 +#: assets/models/asset/common.py:154 assets/models/base.py:181 +#: assets/models/cluster.py:26 assets/models/domain.py:26 +#: assets/models/gathered_user.py:19 assets/models/group.py:22 +#: assets/models/label.py:25 assets/serializers/account.py:18 +#: assets/serializers/cmd_filter.py:28 assets/serializers/cmd_filter.py:49 +#: common/db/models.py:90 common/mixins/models.py:50 ops/models/adhoc.py:39 +#: ops/models/command.py:30 orgs/models.py:67 orgs/models.py:217 +#: perms/models/base.py:92 users/models/group.py:18 users/models/user.py:916 #: xpack/plugins/cloud/models.py:125 msgid "Date created" msgstr "创建日期" @@ -383,7 +416,7 @@ msgstr "创建日期" #: applications/serializers/application.py:104 assets/models/base.py:182 #: assets/models/gathered_user.py:20 assets/serializers/account.py:21 #: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:50 -#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:40 +#: common/db/models.py:91 common/mixins/models.py:51 ops/models/adhoc.py:40 #: orgs/models.py:218 msgid "Date updated" msgstr "更新日期" @@ -398,30 +431,10 @@ msgid "account" msgstr "账号" #: applications/serializers/attrs/application_category/cloud.py:8 -#: assets/models/cluster.py:40 +#: assets/models/asset/cloud.py:8 assets/models/cluster.py:40 msgid "Cluster" msgstr "集群" -#: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: xpack/plugins/cloud/serializers/account_attrs.py:68 -msgid "Host" -msgstr "主机" - -#: applications/serializers/attrs/application_category/db.py:12 -#: applications/serializers/attrs/application_type/mongodb.py:10 -#: applications/serializers/attrs/application_type/mysql.py:10 -#: applications/serializers/attrs/application_type/mysql_workbench.py:21 -#: applications/serializers/attrs/application_type/oracle.py:10 -#: applications/serializers/attrs/application_type/pgsql.py:10 -#: applications/serializers/attrs/application_type/redis.py:10 -#: applications/serializers/attrs/application_type/sqlserver.py:10 -#: assets/models/asset.py:214 assets/models/domain.py:61 -#: settings/serializers/auth/radius.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:69 -msgid "Port" -msgstr "端口" - #: applications/serializers/attrs/application_category/remote_app.py:39 #: applications/serializers/attrs/application_type/chrome.py:13 #: applications/serializers/attrs/application_type/mysql_workbench.py:13 @@ -430,7 +443,7 @@ msgid "Application path" msgstr "应用路径" #: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:163 +#: assets/serializers/system_user.py:164 #: xpack/plugins/change_auth_plan/serializers/asset.py:66 #: xpack/plugins/change_auth_plan/serializers/asset.py:69 #: xpack/plugins/change_auth_plan/serializers/asset.py:72 @@ -509,124 +522,84 @@ msgstr "删除失败,节点包含资产" msgid "App assets" msgstr "资产管理" -#: assets/models/asset.py:139 -msgid "Base" -msgstr "基础" +#: assets/const.py:14 +msgid "NetworkDevice" +msgstr "网络设备" -#: assets/models/asset.py:140 -msgid "Charset" -msgstr "编码" +#: assets/const.py:17 +msgid "Clouding" +msgstr "云设施" -#: assets/models/asset.py:141 assets/serializers/asset.py:176 -#: tickets/models/ticket.py:133 -msgid "Meta" -msgstr "元数据" +#: assets/const.py:26 +msgid "Mainframe" +msgstr "大型机" -#: assets/models/asset.py:142 -msgid "Internal" -msgstr "内部的" +#: assets/const.py:27 +msgid "Other host" +msgstr "其它主机" -#: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:15 assets/serializers/asset.py:63 -#: perms/serializers/asset/user_permission.py:43 -msgid "Platform" -msgstr "系统平台" +#: assets/const.py:31 +msgid "Switch" +msgstr "交换机" -#: assets/models/asset.py:168 -msgid "Vendor" -msgstr "制造商" +#: assets/const.py:32 +msgid "Router" +msgstr "路由器" -#: assets/models/asset.py:169 -msgid "Model" -msgstr "型号" +#: assets/const.py:33 +msgid "Firewall" +msgstr "防火墙" -#: assets/models/asset.py:170 tickets/models/ticket.py:159 -msgid "Serial number" -msgstr "序列号" +#: assets/const.py:34 +msgid "Other device" +msgstr "其它设备" -#: assets/models/asset.py:172 -msgid "CPU model" -msgstr "CPU型号" - -#: assets/models/asset.py:173 -msgid "CPU count" -msgstr "CPU数量" - -#: assets/models/asset.py:174 -msgid "CPU cores" -msgstr "CPU核数" - -#: assets/models/asset.py:175 -msgid "CPU vcpus" -msgstr "CPU总数" - -#: assets/models/asset.py:176 -msgid "Memory" -msgstr "内存" - -#: assets/models/asset.py:177 -msgid "Disk total" -msgstr "硬盘大小" - -#: assets/models/asset.py:178 -msgid "Disk info" -msgstr "硬盘信息" - -#: assets/models/asset.py:180 -msgid "OS" -msgstr "操作系统" - -#: assets/models/asset.py:181 -msgid "OS version" -msgstr "系统版本" - -#: assets/models/asset.py:182 -msgid "OS arch" -msgstr "系统架构" - -#: assets/models/asset.py:183 -msgid "Hostname raw" -msgstr "主机名原始" - -#: assets/models/asset.py:215 assets/serializers/account.py:16 -#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 +#: assets/models/asset/common.py:135 assets/serializers/account.py:16 +#: assets/serializers/asset/common.py:63 +#: perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:218 assets/models/user.py:238 -#: perms/models/asset_permission.py:24 +#: assets/models/asset/common.py:137 assets/models/platform.py:41 +#: assets/serializers/account.py:15 assets/serializers/asset/common.py:61 +#: perms/serializers/asset/user_permission.py:43 +msgid "Platform" +msgstr "系统平台" + +#: assets/models/asset/common.py:141 assets/models/user.py:226 +#: perms/models/asset_permission.py:25 #: xpack/plugins/change_auth_plan/models/asset.py:43 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "节点" -#: assets/models/asset.py:219 assets/models/cmd_filter.py:47 +#: assets/models/asset/common.py:142 assets/models/cmd_filter.py:47 #: assets/models/domain.py:65 assets/models/label.py:22 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:222 assets/models/cluster.py:19 -#: assets/models/user.py:235 assets/models/user.py:390 +#: assets/models/asset/common.py:146 assets/models/cluster.py:19 +#: assets/models/user.py:223 assets/models/user.py:378 msgid "Admin user" msgstr "特权用户" -#: assets/models/asset.py:225 +#: assets/models/asset/common.py:149 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:226 +#: assets/models/asset/common.py:150 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:228 +#: assets/models/asset/common.py:152 msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:229 assets/models/base.py:183 +#: assets/models/asset/common.py:153 assets/models/base.py:183 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:52 #: assets/models/cmd_filter.py:99 assets/models/group.py:21 -#: common/db/models.py:111 common/mixins/models.py:49 orgs/models.py:66 +#: common/db/models.py:88 common/mixins/models.py:49 orgs/models.py:66 #: orgs/models.py:219 perms/models/base.py:91 users/models/user.py:704 #: users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 @@ -634,30 +607,94 @@ msgstr "标签管理" msgid "Created by" msgstr "创建者" -#: assets/models/asset.py:386 +#: assets/models/asset/common.py:315 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" -#: assets/models/asset.py:387 +#: assets/models/asset/common.py:316 msgid "Can test asset connectivity" msgstr "可以测试资产连接性" -#: assets/models/asset.py:388 +#: assets/models/asset/common.py:317 msgid "Can push system user to asset" msgstr "可以推送系统用户到资产" -#: assets/models/asset.py:389 +#: assets/models/asset/common.py:318 msgid "Can match asset" msgstr "可以匹配资产" -#: assets/models/asset.py:390 +#: assets/models/asset/common.py:319 msgid "Add asset to node" msgstr "添加资产到节点" -#: assets/models/asset.py:391 +#: assets/models/asset/common.py:320 msgid "Move asset to node" msgstr "移动资产到节点" +#: assets/models/asset/host.py:18 +msgid "Vendor" +msgstr "制造商" + +#: assets/models/asset/host.py:19 +msgid "Model" +msgstr "型号" + +#: assets/models/asset/host.py:20 tickets/models/ticket.py:159 +msgid "Serial number" +msgstr "序列号" + +#: assets/models/asset/host.py:22 +msgid "CPU model" +msgstr "CPU型号" + +#: assets/models/asset/host.py:23 +msgid "CPU count" +msgstr "CPU数量" + +#: assets/models/asset/host.py:24 +msgid "CPU cores" +msgstr "CPU核数" + +#: assets/models/asset/host.py:25 +msgid "CPU vcpus" +msgstr "CPU总数" + +#: assets/models/asset/host.py:26 +msgid "Memory" +msgstr "内存" + +#: assets/models/asset/host.py:27 +msgid "Disk total" +msgstr "硬盘大小" + +#: assets/models/asset/host.py:28 +msgid "Disk info" +msgstr "硬盘信息" + +#: assets/models/asset/host.py:30 +msgid "OS" +msgstr "操作系统" + +#: assets/models/asset/host.py:31 +msgid "OS version" +msgstr "系统版本" + +#: assets/models/asset/host.py:32 +msgid "OS arch" +msgstr "系统架构" + +#: assets/models/asset/host.py:33 +msgid "Hostname raw" +msgstr "主机名原始" + +#: assets/models/asset/host.py:58 +msgid "DeviceInfo" +msgstr "" + +#: assets/models/asset/remote_app.py:8 +msgid "App path" +msgstr "应用路径" + #: assets/models/authbook.py:27 msgid "AuthBook" msgstr "资产账号" @@ -701,7 +738,7 @@ msgid "Timing trigger" msgstr "定时触发" #: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31 -#: perms/models/base.py:89 terminal/models/session.py:56 +#: perms/models/base.py:89 terminal/models/session.py:43 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 #: xpack/plugins/change_auth_plan/models/base.py:112 @@ -762,7 +799,7 @@ msgstr "成功" #: assets/models/base.py:32 audits/models.py:116 #: xpack/plugins/change_auth_plan/serializers/app.py:88 #: xpack/plugins/change_auth_plan/serializers/asset.py:198 -#: xpack/plugins/cloud/const.py:30 +#: xpack/plugins/cloud/const.py:31 msgid "Failed" msgstr "失败" @@ -852,7 +889,7 @@ msgstr "默认Cluster" msgid "User group" msgstr "用户组" -#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:54 +#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:55 msgid "Command filter" msgstr "命令过滤器" @@ -861,7 +898,7 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:53 +#: terminal/backends/command/serializers.py:15 terminal/models/session.py:40 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -949,7 +986,8 @@ msgstr "资产组" msgid "Default asset group" msgstr "默认资产组" -#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:30 +#: assets/models/label.py:19 assets/models/node.py:546 +#: common/drf/serializers.py:89 settings/models.py:30 msgid "Value" msgstr "值" @@ -965,7 +1003,7 @@ msgstr "新节点" msgid "empty" msgstr "空" -#: assets/models/node.py:545 perms/models/asset_permission.py:101 +#: assets/models/node.py:545 perms/models/asset_permission.py:102 msgid "Key" msgstr "键" @@ -973,11 +1011,11 @@ msgstr "键" msgid "Full value" msgstr "全称" -#: assets/models/node.py:550 perms/models/asset_permission.py:102 +#: assets/models/node.py:550 perms/models/asset_permission.py:103 msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 assets/serializers/system_user.py:263 +#: assets/models/node.py:559 assets/serializers/system_user.py:264 #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 @@ -989,77 +1027,90 @@ msgstr "节点" msgid "Can match node" msgstr "可以匹配节点" -#: assets/models/user.py:229 +#: assets/models/platform.py:19 +msgid "Charset" +msgstr "编码" + +#: assets/models/platform.py:20 assets/serializers/platform.py:14 +#: tickets/models/ticket.py:133 +msgid "Meta" +msgstr "元数据" + +#: assets/models/platform.py:21 +msgid "Internal" +msgstr "内部的" + +#: assets/models/user.py:217 msgid "Automatic managed" msgstr "托管密码" -#: assets/models/user.py:230 +#: assets/models/user.py:218 msgid "Manually input" msgstr "手动输入" -#: assets/models/user.py:234 +#: assets/models/user.py:222 msgid "Common user" msgstr "普通用户" -#: assets/models/user.py:237 +#: assets/models/user.py:225 msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:240 assets/serializers/domain.py:29 +#: assets/models/user.py:228 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:244 users/apps.py:9 +#: assets/models/user.py:232 users/apps.py:9 msgid "Users" msgstr "用户管理" -#: assets/models/user.py:245 +#: assets/models/user.py:233 msgid "User groups" msgstr "用户组" -#: assets/models/user.py:249 +#: assets/models/user.py:237 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:250 +#: assets/models/user.py:238 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:251 +#: assets/models/user.py:239 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:252 +#: assets/models/user.py:240 msgid "Login mode" msgstr "认证方式" -#: assets/models/user.py:253 +#: assets/models/user.py:241 msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:254 authentication/models.py:48 +#: assets/models/user.py:242 authentication/models.py:49 msgid "Token" msgstr "Token" -#: assets/models/user.py:255 +#: assets/models/user.py:243 msgid "Home" msgstr "家目录" -#: assets/models/user.py:256 +#: assets/models/user.py:244 msgid "System groups" msgstr "用户组" -#: assets/models/user.py:259 +#: assets/models/user.py:247 msgid "User switch" msgstr "用户切换" -#: assets/models/user.py:260 +#: assets/models/user.py:248 msgid "Switch from" msgstr "切换自" -#: assets/models/user.py:340 +#: assets/models/user.py:328 msgid "Can match system user" msgstr "可以匹配系统用户" @@ -1091,38 +1142,30 @@ msgstr "" msgid "System user display" msgstr "系统用户名称" -#: assets/serializers/asset.py:20 +#: assets/serializers/asset/common.py:18 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" -#: assets/serializers/asset.py:37 +#: assets/serializers/asset/common.py:35 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:66 +#: assets/serializers/asset/common.py:64 msgid "Domain name" msgstr "网域名称" -#: assets/serializers/asset.py:68 +#: assets/serializers/asset/common.py:66 msgid "Nodes name" msgstr "节点名称" -#: assets/serializers/asset.py:71 +#: assets/serializers/asset/common.py:69 msgid "Labels name" msgstr "标签名称" -#: assets/serializers/asset.py:105 -msgid "Hardware info" -msgstr "硬件信息" - -#: assets/serializers/asset.py:106 +#: assets/serializers/asset/common.py:102 msgid "Admin user display" msgstr "特权用户名称" -#: assets/serializers/asset.py:107 -msgid "CPU info" -msgstr "CPU信息" - #: assets/serializers/backup.py:20 perms/models/base.py:87 #: perms/serializers/application/permission.py:17 #: perms/serializers/application/permission.py:42 @@ -1155,7 +1198,7 @@ msgid "Action display" msgstr "动作" #: assets/serializers/domain.py:13 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:59 +#: assets/serializers/system_user.py:60 #: perms/serializers/asset/permission.py:49 msgid "Assets amount" msgstr "资产数量" @@ -1180,78 +1223,78 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:28 +#: assets/serializers/system_user.py:29 msgid "SSH key fingerprint" msgstr "密钥指纹" -#: assets/serializers/system_user.py:30 +#: assets/serializers/system_user.py:31 #: perms/serializers/application/permission.py:46 msgid "Apps amount" msgstr "应用数量" -#: assets/serializers/system_user.py:58 +#: assets/serializers/system_user.py:59 #: perms/serializers/asset/permission.py:50 msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:60 assets/serializers/system_user.py:265 +#: assets/serializers/system_user.py:61 assets/serializers/system_user.py:266 msgid "Login mode display" msgstr "认证方式名称" -#: assets/serializers/system_user.py:62 +#: assets/serializers/system_user.py:63 msgid "Ad domain" msgstr "Ad 网域" -#: assets/serializers/system_user.py:63 +#: assets/serializers/system_user.py:64 msgid "Is asset protocol" msgstr "资产协议" -#: assets/serializers/system_user.py:64 +#: assets/serializers/system_user.py:65 msgid "Only ssh and automatic login system users are supported" msgstr "仅支持ssh协议和自动登录的系统用户" -#: assets/serializers/system_user.py:104 +#: assets/serializers/system_user.py:105 msgid "Username same with user with protocol {} only allow 1" msgstr "用户名和用户相同的一种协议只允许存在一个" -#: assets/serializers/system_user.py:117 common/validators.py:14 +#: assets/serializers/system_user.py:118 common/validators.py:14 msgid "Special char not allowed" msgstr "不能包含特殊字符" -#: assets/serializers/system_user.py:127 +#: assets/serializers/system_user.py:128 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:142 +#: assets/serializers/system_user.py:143 msgid "Path should starts with /" msgstr "路径应该以 / 开头" -#: assets/serializers/system_user.py:154 +#: assets/serializers/system_user.py:155 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" -#: assets/serializers/system_user.py:168 +#: assets/serializers/system_user.py:169 msgid "Only ssh protocol system users are allowed" msgstr "仅允许ssh协议的系统用户" -#: assets/serializers/system_user.py:172 +#: assets/serializers/system_user.py:173 msgid "The protocol must be consistent with the current user: {}" msgstr "协议必须和当前用户保持一致: {}" -#: assets/serializers/system_user.py:176 +#: assets/serializers/system_user.py:177 msgid "Only system users with automatic login are allowed" msgstr "仅允许自动登录的系统用户" -#: assets/serializers/system_user.py:281 +#: assets/serializers/system_user.py:282 msgid "System user name" msgstr "系统用户名称" -#: assets/serializers/system_user.py:282 orgs/mixins/serializers.py:26 +#: assets/serializers/system_user.py:283 orgs/mixins/serializers.py:26 #: rbac/serializers/rolebinding.py:23 msgid "Org name" msgstr "组织名称" -#: assets/serializers/system_user.py:291 +#: assets/serializers/system_user.py:292 msgid "Asset hostname" msgstr "资产主机名" @@ -1406,7 +1449,7 @@ msgid "Symlink" msgstr "建立软链接" #: audits/models.py:38 audits/models.py:64 audits/models.py:87 -#: terminal/models/session.py:49 terminal/models/sharing.py:82 +#: terminal/models/session.py:36 terminal/models/sharing.py:82 msgid "Remote addr" msgstr "远端地址" @@ -1656,7 +1699,7 @@ msgstr "{AssetPermission} 添加 {UserGroup}" msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:131 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:131 perms/models/asset_permission.py:30 #: users/templates/users/_user_detail_nav_header.html:31 msgid "Asset permission" msgstr "资产授权" @@ -2044,31 +2087,31 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:33 terminal/serializers/storage.py:30 +#: authentication/models.py:34 terminal/serializers/storage.py:30 msgid "Access key" msgstr "API key" -#: authentication/models.py:40 +#: authentication/models.py:41 msgid "Private Token" msgstr "SSH密钥" -#: authentication/models.py:49 +#: authentication/models.py:50 msgid "Expired" msgstr "过期时间" -#: authentication/models.py:53 +#: authentication/models.py:54 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:61 +#: authentication/models.py:62 msgid "Connection token" msgstr "连接令牌" -#: authentication/models.py:63 +#: authentication/models.py:64 msgid "Can view connection token secret" msgstr "可以查看连接令牌密文" -#: authentication/models.py:70 +#: authentication/models.py:71 msgid "Super connection token" msgstr "超级连接令牌" @@ -2471,7 +2514,7 @@ msgstr "%(name)s 更新成功" msgid "ugettext_lazy" msgstr "ugettext_lazy" -#: common/db/models.py:112 +#: common/db/models.py:89 msgid "Updated by" msgstr "更新人" @@ -2487,6 +2530,14 @@ msgstr "文件内容太大 (最大长度 `{}` 字节)" msgid "Parse file error: {}" msgstr "解析文件错误: {}" +#: common/drf/serializers.py:88 rbac/serializers/role.py:27 +msgid "Display name" +msgstr "显示名称" + +#: common/drf/serializers.py:93 +msgid "Children" +msgstr "" + #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." @@ -2540,7 +2591,7 @@ msgstr "编码数据为 char" msgid "Marshal data to text field" msgstr "编码数据为 text" -#: common/fields/model.py:150 +#: common/fields/model.py:149 msgid "Encrypt field using Secret Key" msgstr "加密的字段" @@ -2922,27 +2973,27 @@ msgstr "可以查看用户授权的应用" msgid "Can view usergroup apps" msgstr "可以查看用户组授权的应用" -#: perms/models/asset_permission.py:134 +#: perms/models/asset_permission.py:135 msgid "Ungrouped" msgstr "未分组" -#: perms/models/asset_permission.py:136 +#: perms/models/asset_permission.py:137 msgid "Favorite" msgstr "收藏夹" -#: perms/models/asset_permission.py:183 +#: perms/models/asset_permission.py:184 msgid "Permed asset" msgstr "授权的资产" -#: perms/models/asset_permission.py:185 +#: perms/models/asset_permission.py:186 msgid "Can view my assets" msgstr "可以查看我的资产" -#: perms/models/asset_permission.py:186 +#: perms/models/asset_permission.py:187 msgid "Can view user assets" msgstr "可以查看用户授权的资产" -#: perms/models/asset_permission.py:187 +#: perms/models/asset_permission.py:188 msgid "Can view usergroup assets" msgstr "可以查看用户组授权的资产" @@ -3212,10 +3263,6 @@ msgstr "权限" msgid "Scope display" msgstr "范围名称" -#: rbac/serializers/role.py:27 -msgid "Display name" -msgstr "显示名称" - #: rbac/serializers/rolebinding.py:22 msgid "Role display" msgstr "角色显示" @@ -4858,35 +4905,35 @@ msgstr "可以上传会话录像" msgid "Can download session replay" msgstr "可以下载会话录像" -#: terminal/models/session.py:48 terminal/models/sharing.py:87 +#: terminal/models/session.py:35 terminal/models/sharing.py:87 msgid "Login from" msgstr "登录来源" -#: terminal/models/session.py:52 +#: terminal/models/session.py:39 msgid "Replay" msgstr "回放" -#: terminal/models/session.py:57 +#: terminal/models/session.py:44 msgid "Date end" msgstr "结束日期" -#: terminal/models/session.py:242 +#: terminal/models/session.py:220 msgid "Session record" msgstr "会话记录" -#: terminal/models/session.py:244 +#: terminal/models/session.py:222 msgid "Can monitor session" msgstr "可以监控会话" -#: terminal/models/session.py:245 +#: terminal/models/session.py:223 msgid "Can share session" msgstr "可以分享会话" -#: terminal/models/session.py:246 +#: terminal/models/session.py:224 msgid "Can terminate session" msgstr "可以终断会话" -#: terminal/models/session.py:247 +#: terminal/models/session.py:225 msgid "Can validate session action perm" msgstr "可以验证会话动作权限" @@ -6426,58 +6473,64 @@ msgid "Baidu Cloud" msgstr "百度云" #: xpack/plugins/cloud/const.py:15 +#, fuzzy +#| msgid "Baidu Cloud" +msgid "JD Cloud" +msgstr "百度云" + +#: xpack/plugins/cloud/const.py:16 msgid "Tencent Cloud" msgstr "腾讯云" -#: xpack/plugins/cloud/const.py:16 +#: xpack/plugins/cloud/const.py:17 msgid "VMware" msgstr "VMware" -#: xpack/plugins/cloud/const.py:17 xpack/plugins/cloud/providers/nutanix.py:13 +#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 msgid "Nutanix" msgstr "Nutanix" -#: xpack/plugins/cloud/const.py:18 +#: xpack/plugins/cloud/const.py:19 msgid "Huawei Private Cloud" msgstr "华为私有云" -#: xpack/plugins/cloud/const.py:19 +#: xpack/plugins/cloud/const.py:20 msgid "Qingyun Private Cloud" msgstr "青云私有云" -#: xpack/plugins/cloud/const.py:20 +#: xpack/plugins/cloud/const.py:21 msgid "OpenStack" msgstr "OpenStack" -#: xpack/plugins/cloud/const.py:21 +#: xpack/plugins/cloud/const.py:22 msgid "Google Cloud Platform" msgstr "谷歌云" -#: xpack/plugins/cloud/const.py:25 +#: xpack/plugins/cloud/const.py:26 msgid "Instance name" msgstr "实例名称" -#: xpack/plugins/cloud/const.py:26 +#: xpack/plugins/cloud/const.py:27 msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/const.py:31 +#: xpack/plugins/cloud/const.py:32 msgid "Succeed" msgstr "成功" -#: xpack/plugins/cloud/const.py:35 +#: xpack/plugins/cloud/const.py:36 msgid "Unsync" msgstr "未同步" -#: xpack/plugins/cloud/const.py:36 +#: xpack/plugins/cloud/const.py:37 msgid "New Sync" msgstr "新同步" -#: xpack/plugins/cloud/const.py:37 +#: xpack/plugins/cloud/const.py:38 msgid "Synced" msgstr "已同步" -#: xpack/plugins/cloud/const.py:38 +#: xpack/plugins/cloud/const.py:39 msgid "Released" msgstr "已释放" @@ -6650,11 +6703,13 @@ msgid "South America (São Paulo)" msgstr "南美洲(圣保罗)" #: xpack/plugins/cloud/providers/baiducloud.py:54 +#: xpack/plugins/cloud/providers/jdcloud.py:127 msgid "CN North-Beijing" msgstr "华北-北京" #: xpack/plugins/cloud/providers/baiducloud.py:55 #: xpack/plugins/cloud/providers/huaweicloud.py:40 +#: xpack/plugins/cloud/providers/jdcloud.py:130 msgid "CN South-Guangzhou" msgstr "华南-广州" @@ -6676,6 +6731,7 @@ msgid "CN North-Baoding" msgstr "华北-保定" #: xpack/plugins/cloud/providers/baiducloud.py:60 +#: xpack/plugins/cloud/providers/jdcloud.py:129 msgid "CN East-Shanghai" msgstr "华东-上海" @@ -6740,11 +6796,17 @@ msgstr "华北-乌兰察布一" msgid "CN South-Guangzhou-InvitationOnly" msgstr "华南-广州-友好用户环境" -#: xpack/plugins/cloud/serializers/account.py:59 +#: xpack/plugins/cloud/providers/jdcloud.py:128 +#, fuzzy +#| msgid "CN East-Shanghai" +msgid "CN East-Suqian" +msgstr "华东-上海" + +#: xpack/plugins/cloud/serializers/account.py:60 msgid "Validity display" msgstr "有效性显示" -#: xpack/plugins/cloud/serializers/account.py:60 +#: xpack/plugins/cloud/serializers/account.py:61 msgid "Provider display" msgstr "服务商显示" @@ -6907,6 +6969,15 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Base" +#~ msgstr "基础" + +#~ msgid "Hardware info" +#~ msgstr "硬件信息" + +#~ msgid "CPU info" +#~ msgstr "CPU信息" + #~ msgid "Database proxy MySQL protocol listen port" #~ msgstr "MySQL 协议监听的端口" From 7e6964e0fcb29be64f4fba102a0c1a8e74f34184 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 30 Apr 2022 23:19:43 +0800 Subject: [PATCH 017/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 101 ++++++++++++--- .../migrations/0100_auto_20220430_2126.py | 54 ++++++++ apps/assets/models/platform.py | 21 ++++ apps/assets/serializers/asset/__init__.py | 2 +- apps/assets/serializers/asset/category.py | 68 ++++++++++ apps/assets/serializers/asset/common.py | 24 ++-- apps/assets/serializers/asset/host.py | 31 ----- apps/assets/serializers/mixin.py | 11 ++ apps/assets/serializers/platform.py | 25 ++-- apps/locale/ja/LC_MESSAGES/django.po | 117 ++++++++++++------ apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 103 +++++++++------ 12 files changed, 422 insertions(+), 139 deletions(-) create mode 100644 apps/assets/migrations/0100_auto_20220430_2126.py create mode 100644 apps/assets/serializers/asset/category.py delete mode 100644 apps/assets/serializers/asset/host.py create mode 100644 apps/assets/serializers/mixin.py diff --git a/apps/assets/const.py b/apps/assets/const.py index 218657d22..64a928f16 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -9,15 +9,43 @@ __all__ = [ ] -class Category(models.TextChoices): +class PlatformMixin: + @classmethod + def platform_meta(cls): + return {} + + +class Category(PlatformMixin, models.TextChoices): HOST = 'host', _('Host') NETWORK = 'network', _("NetworkDevice") DATABASE = 'database', _("Database") REMOTE_APP = 'remote_app', _("Remote app") CLOUD = 'cloud', _("Clouding") + @classmethod + def platform_meta(cls): + return { + cls.HOST: { + 'has_domain': True, + 'protocols_limit': ['ssh', 'rdp', 'vnc', 'telnet'] + }, + cls.NETWORK: { + 'has_domain': True, + 'protocols_limit': ['ssh', 'telnet'] + }, + cls.DATABASE: { + 'has_domain': True + }, + cls.REMOTE_APP: { + 'has_domain': True + }, + cls.CLOUD: { + 'has_domain': False + } + } -class HostTypes(models.TextChoices): + +class HostTypes(PlatformMixin, models.TextChoices): LINUX = 'linux', 'Linux' WINDOWS = 'windows', 'Windows' UNIX = 'unix', 'Unix' @@ -26,15 +54,30 @@ class HostTypes(models.TextChoices): MAINFRAME = 'mainframe', _("Mainframe") OTHER_HOST = 'other_host', _("Other host") + @classmethod + def platform_meta(cls): + return {} -class NetworkTypes(models.TextChoices): + @classmethod + def get_default_port(cls): + defaults = { + cls.LINUX: 22, + cls.WINDOWS: 3389, + cls.UNIX: 22, + cls.BSD: 22, + cls.MACOS: 22, + cls.MAINFRAME: 22, + } + + +class NetworkTypes(PlatformMixin, models.TextChoices): SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") FIREWALL = 'firewall', _("Firewall") OTHER_NETWORK = 'other_network', _("Other device") -class DatabaseTypes(models.TextChoices): +class DatabaseTypes(PlatformMixin, models.TextChoices): MYSQL = 'mysql', 'MySQL' MARIADB = 'mariadb', 'MariaDB' POSTGRESQL = 'postgresql', 'PostgreSQL' @@ -43,15 +86,23 @@ class DatabaseTypes(models.TextChoices): MONGODB = 'mongodb', 'MongoDB' REDIS = 'redis', 'Redis' + @classmethod + def platform_meta(cls): + meta = {} + for name, labal in cls.choices: + meta[name] = { + 'protocols_limit': [name] + } -class RemoteAppTypes(models.TextChoices): + +class RemoteAppTypes(PlatformMixin, models.TextChoices): CHROME = 'chrome', 'Chrome' VSPHERE = 'vmware_client', 'vSphere client' MYSQL_WORKBENCH = 'mysql_workbench', 'MySQL workbench' GENERAL_REMOTE_APP = 'general_remote_app', _("Custom") -class CloudTypes(models.TextChoices): +class CloudTypes(PlatformMixin, models.TextChoices): K8S = 'k8s', 'Kubernetes' @@ -62,15 +113,19 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): RemoteAppTypes, CloudTypes ] + @classmethod + def category_types(cls): + return ( + (Category.HOST, HostTypes), + (Category.NETWORK, NetworkTypes), + (Category.DATABASE, DatabaseTypes), + (Category.REMOTE_APP, RemoteAppTypes), + (Category.CLOUD, CloudTypes) + ) + @classmethod def grouped_choices(cls): - grouped_types= [ - (Category.HOST.value, HostTypes.choices), - (Category.NETWORK.value, NetworkTypes.choices), - (Category.DATABASE.value, DatabaseTypes.choices), - (Category.REMOTE_APP.value, RemoteAppTypes.choices), - (Category.CLOUD.value, CloudTypes.choices), - ] + grouped_types = [(str(ca), tp.choices) for ca, tp in cls.category_types()] return grouped_types @classmethod @@ -88,7 +143,6 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): return [dict(zip(title, choice)) for choice in choices] - class Protocol(models.TextChoices): ssh = 'ssh', 'SSH' rdp = 'rdp', 'RDP' @@ -116,3 +170,22 @@ class Protocol(models.TextChoices): cls.sqlserver, cls.redis, cls.mongodb, ] + @classmethod + def default_ports(cls): + return { + cls.ssh: 22, + cls.rdp: 3389, + cls.vnc: 5900, + cls.telnet: 21, + + cls.mysql: 3306, + cls.mariadb: 3306, + cls.postgresql: 5432, + cls.oracle: 1521, + cls.sqlserver: 1433, + cls.mongodb: 27017, + cls.redis: 6379, + + cls.k8s: 0 + } + diff --git a/apps/assets/migrations/0100_auto_20220430_2126.py b/apps/assets/migrations/0100_auto_20220430_2126.py new file mode 100644 index 000000000..bb2fc3b82 --- /dev/null +++ b/apps/assets/migrations/0100_auto_20220430_2126.py @@ -0,0 +1,54 @@ +# Generated by Django 3.1.14 on 2022-04-30 14:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0099_auto_20220426_1558'), + ] + + operations = [ + migrations.AddField( + model_name='platform', + name='admin_user_default', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.systemuser', verbose_name='Admin user default'), + ), + migrations.AddField( + model_name='platform', + name='admin_user_enabled', + field=models.BooleanField(default=True, verbose_name='Admin user enabled'), + ), + migrations.AddField( + model_name='platform', + name='domain_default', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.domain', verbose_name='Domain default'), + ), + migrations.AddField( + model_name='platform', + name='domain_enabled', + field=models.BooleanField(default=True, verbose_name='Domain enabled'), + ), + migrations.AddField( + model_name='platform', + name='protocols_default', + field=models.CharField(blank=True, default='', max_length=128, verbose_name='Protocols default'), + ), + migrations.AddField( + model_name='platform', + name='protocols_enabled', + field=models.BooleanField(default=True, verbose_name='Protocols enabled'), + ), + migrations.AlterField( + model_name='asset', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], max_length=16, verbose_name='Category'), + ), + migrations.AlterField( + model_name='platform', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], max_length=16, verbose_name='Category'), + ), + ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index b3c9ec76e..04fcd3f68 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -20,6 +20,27 @@ class Platform(models.Model): meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) internal = models.BooleanField(default=False, verbose_name=_("Internal")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) + domain_default = models.ForeignKey( + 'assets.Domain', null=True, on_delete=models.SET_NULL, + verbose_name=_("Domain default") + ) + protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) + protocols_default = models.CharField( + max_length=128, default='', blank=True, verbose_name=_("Protocols default") + ) + admin_user_enabled = models.BooleanField(default=True, verbose_name=_("Admin user enabled")) + admin_user_default = models.ForeignKey( + 'assets.SystemUser', null=True, on_delete=models.SET_NULL, + verbose_name=_("Admin user default") + ) + + def get_type_meta(self): + meta = Category.platform_meta().get(self.category, {}) + types = dict(AllTypes.category_types())[self.category] + type_meta = types.platform_meta().get(self.type, {}) + meta.update(type_meta) + return meta @classmethod def default(cls): diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py index 51640e7cf..c6e5732be 100644 --- a/apps/assets/serializers/asset/__init__.py +++ b/apps/assets/serializers/asset/__init__.py @@ -1,2 +1,2 @@ from .common import * -from .host import * +from .category import * diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py new file mode 100644 index 000000000..6d9ab1bf0 --- /dev/null +++ b/apps/assets/serializers/asset/category.py @@ -0,0 +1,68 @@ +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ + +from .common import AssetSerializer +from assets.models import DeviceInfo, Host, Database + +__all__ = [ + 'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer' +] + + +class DeviceSerializer(serializers.ModelSerializer): + class Meta: + model = DeviceInfo + fields = [ + 'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', + 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', + 'os', 'os_version', 'os_arch', 'hostname_raw', + 'cpu_info', 'hardware_info', 'date_updated' + ] + + +class HostSerializer(AssetSerializer): + device_info = DeviceSerializer(read_only=True, allow_null=True) + + class Meta(AssetSerializer.Meta): + model = Host + fields = AssetSerializer.Meta.fields + ['device_info'] + + +class DatabaseSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Database + fields_mini = [ + 'id', 'hostname', 'ip', 'port', 'db_name', + ] + fields_small = fields_mini + [ + 'is_active', 'comment', + ] + fields_fk = [ + 'domain', 'domain_display', 'platform', + ] + fields_m2m = [ + 'nodes', 'nodes_display', 'labels', 'labels_display', + ] + read_only_fields = [ + 'category', 'category_display', 'type', 'type_display', + 'created_by', 'date_created', + ] + fields = fields_small + fields_fk + fields_m2m + read_only_fields + extra_kwargs = { + **AssetSerializer.Meta.extra_kwargs, + 'db_name': {'required': True} + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if not self.instance: + self.set_port_default() + + def set_port_default(self): + port = self.fields['port'] + type_port_mapper = { + 'mysql': 3306, + 'postgresql': 5432, + 'oracle': 22 + } + port.default = '' diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 5092e53c2..2e4dd3fb9 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -3,8 +3,9 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from orgs.mixins.serializers import OrgResourceModelSerializerMixin from ...models import Asset, Node, Platform, SystemUser +from ..mixin import CategoryDisplayMixin __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', @@ -56,17 +57,17 @@ class ProtocolsField(serializers.ListField): return value.split(' ') -class AssetSerializer(BulkOrgResourceModelSerializer): +class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22']) domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name')) nodes_display = serializers.ListField( child=serializers.CharField(), label=_('Nodes name'), required=False ) labels_display = serializers.ListField( - child=serializers.CharField(), label=_('Labels name'), required=False, read_only=True + child=serializers.CharField(), label=_('Labels name'), + required=False, read_only=True ) - category_display = serializers.ReadOnlyField(source='get_category_display', label=_("Category display")) - type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) + platform_display = serializers.SlugField(source='platform.name', label=_("Platform display"), read_only=True) """ 资产的数据结构 @@ -75,27 +76,28 @@ class AssetSerializer(BulkOrgResourceModelSerializer): class Meta: model = Asset fields_mini = [ - 'id', 'category', 'category_display', 'type', 'type_display', - 'hostname', 'ip', 'platform', 'protocols' + 'id', 'hostname', 'ip', 'platform', 'protocols' ] fields_small = fields_mini + [ 'protocol', 'port', 'is_active', 'public_ip', 'number', 'comment', ] fields_fk = [ - 'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display' + 'domain', 'domain_display', 'platform', 'platform_display', + 'admin_user', 'admin_user_display' ] fields_m2m = [ 'nodes', 'nodes_display', 'labels', 'labels_display', ] read_only_fields = [ - 'connectivity', 'date_verified', 'created_by', 'date_created', - 'category', 'type' + 'category', 'category_display', 'type', 'type_display', + 'connectivity', 'date_verified', + 'created_by', 'date_created', ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { 'hostname': {'label': _("Name")}, - 'ip': {'label': 'Address'}, + 'ip': {'label': _('IP/Host')}, 'protocol': {'write_only': True}, 'port': {'write_only': True}, 'admin_user_display': {'label': _('Admin user display'), 'read_only': True}, diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py deleted file mode 100644 index 95606a866..000000000 --- a/apps/assets/serializers/asset/host.py +++ /dev/null @@ -1,31 +0,0 @@ -from rest_framework import serializers - -from .common import AssetSerializer -from assets.models import DeviceInfo, Host, Database - -__all__ = ['DeviceSerializer', 'HostSerializer', 'DatabaseSerializer'] - - -class DeviceSerializer(serializers.ModelSerializer): - class Meta: - model = DeviceInfo - fields = [ - 'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', - 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', - 'os', 'os_version', 'os_arch', 'hostname_raw', - 'cpu_info', 'hardware_info', 'date_updated' - ] - - -class HostSerializer(AssetSerializer): - device_info = DeviceSerializer(read_only=True, allow_null=True) - - class Meta(AssetSerializer.Meta): - model = Host - fields = AssetSerializer.Meta.fields + ['device_info'] - - -class DatabaseSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Database - fields = AssetSerializer.Meta.fields + ['db_name'] diff --git a/apps/assets/serializers/mixin.py b/apps/assets/serializers/mixin.py new file mode 100644 index 000000000..45943dc2a --- /dev/null +++ b/apps/assets/serializers/mixin.py @@ -0,0 +1,11 @@ +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ + + +class CategoryDisplayMixin(serializers.Serializer): + category_display = serializers.ReadOnlyField( + source='get_category_display', label=_("Category display") + ) + type_display = serializers.ReadOnlyField( + source='get_type_display', label=_("Type display") + ) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index b20348c14..078edee03 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -4,15 +4,13 @@ from django.utils.translation import gettext_lazy as _ from assets.models import Platform from assets.const import AllTypes +from .mixin import CategoryDisplayMixin __all__ = ['PlatformSerializer'] -class PlatformSerializer(serializers.ModelSerializer): - category_display = serializers.ReadOnlyField(source='get_category_display', label=_("Category display")) - type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) +class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - type = serializers.ChoiceField(choices=AllTypes.grouped_choices(), label=_("Type")) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -24,8 +22,19 @@ class PlatformSerializer(serializers.ModelSerializer): class Meta: model = Platform - fields = [ - 'id', 'name', 'category', 'category_display', - 'type', 'type_display', 'charset', - 'internal', 'meta', 'comment' + fields_mini = ['id', 'name', 'internal'] + fields_small = fields_mini + [ + 'meta', 'comment', 'charset', + 'category', 'category_display', 'type', 'type_display', ] + fields_fk = [ + 'domain_enabled', 'domain_default', + 'protocols_enabled', 'protocols_default', + 'admin_user_enabled', 'admin_user_default', + ] + fields = fields_small + fields_fk + read_only_fields = [ + 'category_display', 'type_display', + ] + + diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 94187fd38..52b5027ef 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-29 10:04+0800\n" +"POT-Creation-Date: 2022-04-30 22:42+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,11 +27,12 @@ msgstr "Acls" #: assets/models/cluster.py:18 assets/models/cmd_filter.py:27 #: assets/models/domain.py:23 assets/models/group.py:20 #: assets/models/label.py:18 assets/models/platform.py:16 -#: assets/models/protocol.py:8 ops/mixin.py:24 orgs/models.py:65 -#: perms/models/base.py:83 rbac/models/role.py:29 settings/models.py:29 -#: settings/serializers/sms.py:6 terminal/models/storage.py:23 -#: terminal/models/task.py:16 terminal/models/terminal.py:100 -#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:659 +#: assets/models/protocol.py:8 assets/serializers/asset/common.py:99 +#: ops/mixin.py:24 orgs/models.py:65 perms/models/base.py:83 +#: rbac/models/role.py:29 settings/models.py:29 settings/serializers/sms.py:6 +#: terminal/models/storage.py:23 terminal/models/task.py:16 +#: terminal/models/terminal.py:100 users/forms/profile.py:32 +#: users/models/group.py:15 users/models/user.py:659 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -264,17 +265,17 @@ msgstr "アプリケーション" #: applications/models/database.py:13 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:25 -#: assets/const.py:15 assets/models/asset/database.py:8 +#: assets/const.py:21 assets/models/asset/database.py:8 #: assets/models/asset/database.py:14 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "データベース" -#: applications/const.py:9 assets/const.py:16 +#: applications/const.py:9 assets/const.py:22 msgid "Remote app" msgstr "リモートアプリ" -#: applications/const.py:35 assets/const.py:51 +#: applications/const.py:35 assets/const.py:102 msgid "Custom" msgstr "カスタム" @@ -336,8 +337,7 @@ msgstr "カテゴリ" #: applications/serializers/application.py:101 #: assets/models/asset/common.py:131 assets/models/backup.py:49 #: assets/models/cmd_filter.py:82 assets/models/platform.py:18 -#: assets/models/user.py:234 assets/serializers/platform.py:15 -#: perms/models/application_permission.py:24 +#: assets/models/user.py:234 perms/models/application_permission.py:24 #: 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:131 @@ -364,7 +364,7 @@ msgstr "アプリケーションを一致させることができます" #: applications/models/database.py:8 #: applications/serializers/attrs/application_category/db.py:11 -#: assets/const.py:13 assets/models/asset/host.py:16 ops/models/adhoc.py:157 +#: assets/const.py:19 assets/models/asset/host.py:16 ops/models/adhoc.py:157 #: settings/serializers/auth/radius.py:14 #: xpack/plugins/cloud/serializers/account_attrs.py:68 msgid "Host" @@ -386,17 +386,14 @@ msgid "Port" msgstr "ポート" #: applications/serializers/application.py:70 -#: applications/serializers/application.py:100 -#: assets/serializers/asset/common.py:71 assets/serializers/label.py:13 -#: assets/serializers/platform.py:12 -#: perms/serializers/application/permission.py:18 +#: applications/serializers/application.py:100 assets/serializers/label.py:13 +#: assets/serializers/mixin.py:7 perms/serializers/application/permission.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26 msgid "Category display" msgstr "カテゴリ表示" #: applications/serializers/application.py:71 -#: applications/serializers/application.py:102 -#: assets/serializers/asset/common.py:72 assets/serializers/platform.py:13 +#: applications/serializers/application.py:102 assets/serializers/mixin.py:10 #: assets/serializers/system_user.py:28 audits/serializers.py:29 #: perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 @@ -527,53 +524,53 @@ msgstr "削除に失敗し、ノードにアセットが含まれています。 msgid "App assets" msgstr "アプリ資産" -#: assets/const.py:14 +#: assets/const.py:20 msgid "NetworkDevice" msgstr "" -#: assets/const.py:17 +#: assets/const.py:23 #, fuzzy #| msgid "Loading" msgid "Clouding" msgstr "読み込み中" -#: assets/const.py:26 +#: assets/const.py:54 msgid "Mainframe" msgstr "" -#: assets/const.py:27 +#: assets/const.py:55 #, fuzzy #| msgid "Other" msgid "Other host" msgstr "その他" -#: assets/const.py:31 +#: assets/const.py:74 #, fuzzy #| msgid "Switch from" msgid "Switch" msgstr "から切り替え" -#: assets/const.py:32 +#: assets/const.py:75 msgid "Router" msgstr "" -#: assets/const.py:33 +#: assets/const.py:76 msgid "Firewall" msgstr "" -#: assets/const.py:34 +#: assets/const.py:77 msgid "Other device" msgstr "" #: assets/models/asset/common.py:135 assets/serializers/account.py:16 -#: assets/serializers/asset/common.py:63 +#: assets/serializers/asset/common.py:61 #: perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "プロトコル" -#: assets/models/asset/common.py:137 assets/models/platform.py:41 -#: assets/serializers/account.py:15 assets/serializers/asset/common.py:61 +#: assets/models/asset/common.py:137 assets/models/platform.py:62 +#: assets/serializers/account.py:15 #: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "プラットフォーム" @@ -1044,7 +1041,7 @@ msgstr "ノードを一致させることができます" msgid "Charset" msgstr "シャーセット" -#: assets/models/platform.py:20 assets/serializers/platform.py:14 +#: assets/models/platform.py:20 assets/serializers/platform.py:13 #: tickets/models/ticket.py:133 msgid "Meta" msgstr "メタ" @@ -1053,6 +1050,42 @@ msgstr "メタ" msgid "Internal" msgstr "内部" +#: assets/models/platform.py:23 +#, fuzzy +#| msgid "Domain name" +msgid "Domain enabled" +msgstr "ドメイン名" + +#: assets/models/platform.py:26 +#, fuzzy +#| msgid "Default" +msgid "Domain default" +msgstr "デフォルト" + +#: assets/models/platform.py:28 +#, fuzzy +#| msgid "Protocols" +msgid "Protocols enabled" +msgstr "プロトコル" + +#: assets/models/platform.py:30 +#, fuzzy +#| msgid "Protocols" +msgid "Protocols default" +msgstr "プロトコル" + +#: assets/models/platform.py:32 +#, fuzzy +#| msgid "Admin user" +msgid "Admin user enabled" +msgstr "管理ユーザー" + +#: assets/models/platform.py:35 +#, fuzzy +#| msgid "Admin user display" +msgid "Admin user default" +msgstr "管理者ユーザー表示" + #: assets/models/user.py:217 msgid "Automatic managed" msgstr "自動管理" @@ -1158,27 +1191,39 @@ msgstr "" msgid "System user display" msgstr "システムユーザー表示" -#: assets/serializers/asset/common.py:18 +#: assets/serializers/asset/common.py:19 msgid "Protocol format should {}/{}" msgstr "プロトコル形式は {}/{}" -#: assets/serializers/asset/common.py:35 +#: assets/serializers/asset/common.py:36 msgid "Protocol duplicate: {}" msgstr "プロトコル重複: {}" -#: assets/serializers/asset/common.py:64 +#: assets/serializers/asset/common.py:62 msgid "Domain name" msgstr "ドメイン名" -#: assets/serializers/asset/common.py:66 +#: assets/serializers/asset/common.py:64 msgid "Nodes name" msgstr "ノード名" -#: assets/serializers/asset/common.py:69 +#: assets/serializers/asset/common.py:67 msgid "Labels name" msgstr "ラベル名" -#: assets/serializers/asset/common.py:102 +#: assets/serializers/asset/common.py:70 +#, fuzzy +#| msgid "Category display" +msgid "Platform display" +msgstr "カテゴリ表示" + +#: assets/serializers/asset/common.py:100 +#, fuzzy +#| msgid "Host" +msgid "IP/Host" +msgstr "ホスト" + +#: assets/serializers/asset/common.py:103 msgid "Admin user display" msgstr "管理者ユーザー表示" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 43655b79e..6d69faf53 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:7326f6af4efae2abb098218faabe97aceed9a8f61dd5fcd56b16d5d07164556a -size 107769 +oid sha256:54c9c54a2e5ae5d27eb79f8ce0d19e7f362c016efb8c6011cace7bd2cb7eec1c +size 108123 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 9b235fcc3..da8f978d8 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-29 10:04+0800\n" +"POT-Creation-Date: 2022-04-30 22:42+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -26,11 +26,12 @@ msgstr "访问控制" #: assets/models/cluster.py:18 assets/models/cmd_filter.py:27 #: assets/models/domain.py:23 assets/models/group.py:20 #: assets/models/label.py:18 assets/models/platform.py:16 -#: assets/models/protocol.py:8 ops/mixin.py:24 orgs/models.py:65 -#: perms/models/base.py:83 rbac/models/role.py:29 settings/models.py:29 -#: settings/serializers/sms.py:6 terminal/models/storage.py:23 -#: terminal/models/task.py:16 terminal/models/terminal.py:100 -#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:659 +#: assets/models/protocol.py:8 assets/serializers/asset/common.py:99 +#: ops/mixin.py:24 orgs/models.py:65 perms/models/base.py:83 +#: rbac/models/role.py:29 settings/models.py:29 settings/serializers/sms.py:6 +#: terminal/models/storage.py:23 terminal/models/task.py:16 +#: terminal/models/terminal.py:100 users/forms/profile.py:32 +#: users/models/group.py:15 users/models/user.py:659 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -259,17 +260,17 @@ msgstr "应用管理" #: applications/models/database.py:13 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:25 -#: assets/const.py:15 assets/models/asset/database.py:8 +#: assets/const.py:21 assets/models/asset/database.py:8 #: assets/models/asset/database.py:14 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "数据库" -#: applications/const.py:9 assets/const.py:16 +#: applications/const.py:9 assets/const.py:22 msgid "Remote app" msgstr "远程应用" -#: applications/const.py:35 assets/const.py:51 +#: applications/const.py:35 assets/const.py:102 msgid "Custom" msgstr "自定义" @@ -331,8 +332,7 @@ msgstr "类别" #: applications/serializers/application.py:101 #: assets/models/asset/common.py:131 assets/models/backup.py:49 #: assets/models/cmd_filter.py:82 assets/models/platform.py:18 -#: assets/models/user.py:234 assets/serializers/platform.py:15 -#: perms/models/application_permission.py:24 +#: assets/models/user.py:234 perms/models/application_permission.py:24 #: 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:131 @@ -359,7 +359,7 @@ msgstr "匹配应用" #: applications/models/database.py:8 #: applications/serializers/attrs/application_category/db.py:11 -#: assets/const.py:13 assets/models/asset/host.py:16 ops/models/adhoc.py:157 +#: assets/const.py:19 assets/models/asset/host.py:16 ops/models/adhoc.py:157 #: settings/serializers/auth/radius.py:14 #: xpack/plugins/cloud/serializers/account_attrs.py:68 msgid "Host" @@ -381,17 +381,14 @@ msgid "Port" msgstr "端口" #: applications/serializers/application.py:70 -#: applications/serializers/application.py:100 -#: assets/serializers/asset/common.py:71 assets/serializers/label.py:13 -#: assets/serializers/platform.py:12 -#: perms/serializers/application/permission.py:18 +#: applications/serializers/application.py:100 assets/serializers/label.py:13 +#: assets/serializers/mixin.py:7 perms/serializers/application/permission.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26 msgid "Category display" msgstr "类别名称" #: applications/serializers/application.py:71 -#: applications/serializers/application.py:102 -#: assets/serializers/asset/common.py:72 assets/serializers/platform.py:13 +#: applications/serializers/application.py:102 assets/serializers/mixin.py:10 #: assets/serializers/system_user.py:28 audits/serializers.py:29 #: perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 @@ -522,47 +519,47 @@ msgstr "删除失败,节点包含资产" msgid "App assets" msgstr "资产管理" -#: assets/const.py:14 +#: assets/const.py:20 msgid "NetworkDevice" msgstr "网络设备" -#: assets/const.py:17 +#: assets/const.py:23 msgid "Clouding" msgstr "云设施" -#: assets/const.py:26 +#: assets/const.py:54 msgid "Mainframe" msgstr "大型机" -#: assets/const.py:27 +#: assets/const.py:55 msgid "Other host" msgstr "其它主机" -#: assets/const.py:31 +#: assets/const.py:74 msgid "Switch" msgstr "交换机" -#: assets/const.py:32 +#: assets/const.py:75 msgid "Router" msgstr "路由器" -#: assets/const.py:33 +#: assets/const.py:76 msgid "Firewall" msgstr "防火墙" -#: assets/const.py:34 +#: assets/const.py:77 msgid "Other device" msgstr "其它设备" #: assets/models/asset/common.py:135 assets/serializers/account.py:16 -#: assets/serializers/asset/common.py:63 +#: assets/serializers/asset/common.py:61 #: perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" -#: assets/models/asset/common.py:137 assets/models/platform.py:41 -#: assets/serializers/account.py:15 assets/serializers/asset/common.py:61 +#: assets/models/asset/common.py:137 assets/models/platform.py:62 +#: assets/serializers/account.py:15 #: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "系统平台" @@ -1031,7 +1028,7 @@ msgstr "可以匹配节点" msgid "Charset" msgstr "编码" -#: assets/models/platform.py:20 assets/serializers/platform.py:14 +#: assets/models/platform.py:20 assets/serializers/platform.py:13 #: tickets/models/ticket.py:133 msgid "Meta" msgstr "元数据" @@ -1040,6 +1037,30 @@ msgstr "元数据" msgid "Internal" msgstr "内部的" +#: assets/models/platform.py:23 +msgid "Domain enabled" +msgstr "启用网域" + +#: assets/models/platform.py:26 +msgid "Domain default" +msgstr "默认网域" + +#: assets/models/platform.py:28 +msgid "Protocols enabled" +msgstr "启用协议组" + +#: assets/models/platform.py:30 +msgid "Protocols default" +msgstr "默认协议组" + +#: assets/models/platform.py:32 +msgid "Admin user enabled" +msgstr "启用特权用户" + +#: assets/models/platform.py:35 +msgid "Admin user default" +msgstr "默认特权用户" + #: assets/models/user.py:217 msgid "Automatic managed" msgstr "托管密码" @@ -1142,27 +1163,37 @@ msgstr "" msgid "System user display" msgstr "系统用户名称" -#: assets/serializers/asset/common.py:18 +#: assets/serializers/asset/common.py:19 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" -#: assets/serializers/asset/common.py:35 +#: assets/serializers/asset/common.py:36 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset/common.py:64 +#: assets/serializers/asset/common.py:62 msgid "Domain name" msgstr "网域名称" -#: assets/serializers/asset/common.py:66 +#: assets/serializers/asset/common.py:64 msgid "Nodes name" msgstr "节点名称" -#: assets/serializers/asset/common.py:69 +#: assets/serializers/asset/common.py:67 msgid "Labels name" msgstr "标签名称" -#: assets/serializers/asset/common.py:102 +#: assets/serializers/asset/common.py:70 +#, fuzzy +#| msgid "Category display" +msgid "Platform display" +msgstr "类别名称" + +#: assets/serializers/asset/common.py:100 +msgid "IP/Host" +msgstr "IP/主机" + +#: assets/serializers/asset/common.py:103 msgid "Admin user display" msgstr "特权用户名称" From 7025d4607010cbd9ac8b7452217d3ffb22cb6519 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 2 May 2022 21:37:42 +0800 Subject: [PATCH 018/488] =?UTF-8?q?perf:=20=E6=8E=A7=E5=88=B6=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 1 + .../migrations/0097_auto_20220407_1726.py | 9 +++++ apps/assets/models/platform.py | 15 +++++--- apps/assets/serializers/asset/common.py | 23 ++++++++---- apps/assets/serializers/platform.py | 36 +++++++++++++++++-- apps/common/drf/metadata.py | 7 ++-- 6 files changed, 75 insertions(+), 16 deletions(-) diff --git a/apps/assets/const.py b/apps/assets/const.py index 64a928f16..87f17b1af 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -93,6 +93,7 @@ class DatabaseTypes(PlatformMixin, models.TextChoices): meta[name] = { 'protocols_limit': [name] } + return meta class RemoteAppTypes(PlatformMixin, models.TextChoices): diff --git a/apps/assets/migrations/0097_auto_20220407_1726.py b/apps/assets/migrations/0097_auto_20220407_1726.py index 61d260458..bd08c3ad3 100644 --- a/apps/assets/migrations/0097_auto_20220407_1726.py +++ b/apps/assets/migrations/0097_auto_20220407_1726.py @@ -3,6 +3,14 @@ from django.db import migrations, models +def migrate_platform_type_to_lower(apps, *args): + platform_model = apps.get_model('assets', 'Platform') + platforms = platform_model.objects.all() + for p in platforms: + p.type = p.type.lower() + p.save() + + class Migration(migrations.Migration): dependencies = [ @@ -31,4 +39,5 @@ class Migration(migrations.Migration): name='protocol', field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol'), ), + migrations.RunPython(migrate_platform_type_to_lower) ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 04fcd3f68..186da3dd3 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -35,13 +35,20 @@ class Platform(models.Model): verbose_name=_("Admin user default") ) - def get_type_meta(self): - meta = Category.platform_meta().get(self.category, {}) - types = dict(AllTypes.category_types())[self.category] - type_meta = types.platform_meta().get(self.type, {}) + @classmethod + def get_type_meta(cls, category, tp): + meta = Category.platform_meta().get(category, {}) + types = dict(AllTypes.category_types()).get(category) + # if not types: + # return {} + types_meta = types.platform_meta() or {} + type_meta = types_meta.get(tp, {}) meta.update(type_meta) return meta + def get_meta(self): + return self.__class__.get_type_meta(self.category, self.type) + @classmethod def default(cls): linux, created = cls.objects.get_or_create( diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 2e4dd3fb9..883a1d0b8 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -14,11 +14,10 @@ __all__ = [ class ProtocolField(serializers.RegexField): - protocols = '|'.join(dict(Asset.Protocol.choices).keys()) default_error_messages = { - 'invalid': _('Protocol format should {}/{}').format(protocols, '1-65535') + 'invalid': _('Protocol format should {}/{}').format('protocol', '1-65535') } - regex = r'^(%s)/(\d{1,5})$' % protocols + regex = r'^(\w+)/(\d{1,5})$' def __init__(self, *args, **kwargs): super().__init__(self.regex, **kwargs) @@ -43,18 +42,28 @@ def validate_duplicate_protocols(values): class ProtocolsField(serializers.ListField): default_validators = [validate_duplicate_protocols] - def __init__(self, *args, **kwargs): + def __init__(self, protocols=None, *args, **kwargs): + self.choices = [] + self.set_protocols(protocols) kwargs['child'] = ProtocolField() kwargs['allow_null'] = True kwargs['allow_empty'] = True kwargs['min_length'] = 1 - kwargs['max_length'] = 4 + kwargs['max_length'] = 32 super().__init__(*args, **kwargs) + def set_protocols(self, protocols): + if protocols is None: + protocols = [] + self.choices = [(c, c) for c in protocols] + print("Chocies: ", self.choices) + def to_representation(self, value): if not value: return [] - return value.split(' ') + if isinstance(value, str): + return value.split(' ') + return value class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): @@ -139,7 +148,7 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): validated_data["protocol"] = protocol[0] validated_data["port"] = int(protocol[1]) if protocols_data: - validated_data["protocols"] = ' '.join(protocols_data) + validated_data["protocols"] = protocols_data def perform_nodes_display_create(self, instance, nodes_display): if not nodes_display: diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 078edee03..44e8ebb88 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -3,7 +3,8 @@ from django.core.validators import RegexValidator from django.utils.translation import gettext_lazy as _ from assets.models import Platform -from assets.const import AllTypes +from assets.serializers.asset import ProtocolsField +from assets.const import Protocol from .mixin import CategoryDisplayMixin __all__ = ['PlatformSerializer'] @@ -11,6 +12,7 @@ __all__ = ['PlatformSerializer'] class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) + protocols_default = ProtocolsField(label=_('Protocols'), required=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -19,13 +21,43 @@ class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): validators = self.fields['name'].validators if isinstance(validators[-1], RegexValidator): validators.pop() + self.set_platform_meta() + + def set_platform_meta(self): + view = self.context.get('view') + if not view: + return + request = view.request + + if isinstance(self.instance, Platform): + category = self.instance.category + tp = self.instance.type + else: + tp = request.query_params.get('type') + category = request.query_params.get('category') + + print("Request: {}".format(self.context.get('request').method), category, tp) + if not all([tp, category]): + return + meta = Platform.get_type_meta(category, tp) + print("Platform meta: {}".format(meta)) + protocols_default = self.fields['protocols_default'] + limits = meta.get('protocols_limit', []) + default_ports = Protocol.default_ports() + protocols = [] + for protocol in limits: + port = default_ports.get(protocol, 22) + protocols.append(f'{protocol}/{port}') + print("set ptocols: ", protocols) + protocols_default.set_protocols(protocols) class Meta: model = Platform fields_mini = ['id', 'name', 'internal'] fields_small = fields_mini + [ 'meta', 'comment', 'charset', - 'category', 'category_display', 'type', 'type_display', + 'category', 'category_display', + 'type', 'type_display', ] fields_fk = [ 'domain_enabled', 'domain_default', diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index 569854722..7e4d32527 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -33,6 +33,7 @@ class SimpleMetadataWithFilters(SimpleMetadata): """ actions = {} view.raw_action = getattr(view, 'action', None) + print("Request in metadata: ", request.path, request.GET) for method in self.methods & set(view.allowed_methods): if hasattr(view, 'action_map'): view.action = view.action_map.get(method.lower(), view.action) @@ -80,14 +81,14 @@ class SimpleMetadataWithFilters(SimpleMetadata): elif getattr(field, 'fields', None): field_info['children'] = self.get_serializer_info(field) - if not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) \ - and hasattr(field, 'choices'): + is_related_field = isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) + if not is_related_field and hasattr(field, 'choices'): field_info['choices'] = [ { 'value': choice_value, 'display_name': force_text(choice_name, strings_only=True) } - for choice_value, choice_name in field.choices.items() + for choice_value, choice_name in dict(field.choices).items() ] return field_info From 8de57773aa2e1a23e98d9f78d7f0ea39a02e58db Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 4 May 2022 09:57:45 +0800 Subject: [PATCH 019/488] stash it --- apps/assets/api/asset/common.py | 22 +++++++++++----------- apps/assets/serializers/asset/category.py | 14 -------------- apps/common/mixins/api/serializer.py | 17 +++++++++-------- apps/rbac/permissions.py | 3 ++- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index 007d7fbd0..fb949f7ef 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -37,17 +37,17 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port") ordering = ('hostname', ) - serializer_classes = { - 'default': serializers.AssetSerializer, - 'suggestion': serializers.MiniAssetSerializer, - 'platform': serializers.PlatformSerializer, - 'gateways': serializers.GatewayWithAuthSerializer - } - rbac_perms = { - 'match': 'assets.match_asset', - 'platform': 'assets.view_platform', - 'gateways': 'assets.view_gateway' - } + serializer_classes = ( + ('default', serializers.AssetSerializer), + ('suggestion', serializers.MiniAssetSerializer), + ('platform', serializers.PlatformSerializer), + ('gateways', serializers.GatewayWithAuthSerializer) + ) + rbac_perms = ( + ('match', 'assets.match_asset'), + ('platform', 'assets.view_platform'), + ('gateways', 'assets.view_gateway') + ) extra_filter_backends = [ FilterAssetByNodeFilterBackend, LabelFilterBackend, diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 6d9ab1bf0..0b01bb0a2 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -52,17 +52,3 @@ class DatabaseSerializer(AssetSerializer): **AssetSerializer.Meta.extra_kwargs, 'db_name': {'required': True} } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if not self.instance: - self.set_port_default() - - def set_port_default(self): - port = self.fields['port'] - type_port_mapper = { - 'mysql': 3306, - 'postgresql': 5432, - 'oracle': 22 - } - port.default = '' diff --git a/apps/common/mixins/api/serializer.py b/apps/common/mixins/api/serializer.py index a416fa990..84cad19ff 100644 --- a/apps/common/mixins/api/serializer.py +++ b/apps/common/mixins/api/serializer.py @@ -16,28 +16,29 @@ class SerializerMixin: single_actions = ['put', 'retrieve', 'patch'] def get_serializer_classes(self): - return getattr(self, 'serializer_classes', None) + classes = getattr(self, 'serializer_classes', None) or {} + return dict(classes) def get_serializer_class_by_view_action(self): serializer_classes = self.get_serializer_classes() if serializer_classes is None: return None - if not isinstance(self.serializer_classes, dict): + if not isinstance(serializer_classes, dict): return None - + serializer_classes = dict(serializer_classes) view_action = self.request.query_params.get('action') or self.action or 'list' - serializer_class = self.serializer_classes.get(view_action) + serializer_class = serializer_classes.get(view_action) if serializer_class is None: view_method = self.request.method.lower() - serializer_class = self.serializer_classes.get(view_method) + serializer_class = serializer_classes.get(view_method) if serializer_class is None and view_action in self.single_actions: - serializer_class = self.serializer_classes.get('single') + serializer_class = serializer_classes.get('single') if serializer_class is None: - serializer_class = self.serializer_classes.get('display') + serializer_class = serializer_classes.get('display') if serializer_class is None: - serializer_class = self.serializer_classes.get('default') + serializer_class = serializer_classes.get('default') return serializer_class def get_serializer_class(self): diff --git a/apps/rbac/permissions.py b/apps/rbac/permissions.py index 3538246b7..286e5b935 100644 --- a/apps/rbac/permissions.py +++ b/apps/rbac/permissions.py @@ -93,7 +93,8 @@ class RBACPermission(permissions.DjangoModelPermissions): try: queryset = self._queryset(view) model_cls = queryset.model - except: + except Exception as e: + raise e model_cls = None return model_cls From e53aa9696bbec1246022380082c94b0756d461f0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 5 May 2022 16:18:05 +0800 Subject: [PATCH 020/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 11 ++++++++- apps/assets/const.py | 32 ++++++++++++++++++++----- apps/assets/models/platform.py | 7 +++--- apps/assets/serializers/asset/common.py | 24 +++++++++---------- apps/assets/serializers/platform.py | 32 +++---------------------- 5 files changed, 53 insertions(+), 53 deletions(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index dfa63f8f6..6bc1c4681 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -20,7 +20,8 @@ class AssetPlatformViewSet(JMSModelViewSet): filterset_fields = ['name'] search_fields = ['name'] rbac_perms = { - 'categories': 'assets.view_platform' + 'categories': 'assets.view_platform', + 'type_limits': 'assets-view_platform' } @action(methods=['GET'], detail=False) @@ -29,6 +30,14 @@ class AssetPlatformViewSet(JMSModelViewSet): serializer = self.get_serializer(data, many=True) return Response(serializer.data) + @action(methods=['GET'], detail=False, url_path='type-limits') + def type_limits(self, request, *args, **kwargs): + category = request.query_params.get('category') + tp = request.query_params.get('type') + limits = AllTypes.get_type_limits(category, tp) + return Response(limits) + + def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( diff --git a/apps/assets/const.py b/apps/assets/const.py index 87f17b1af..08269cf4d 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -11,7 +11,7 @@ __all__ = [ class PlatformMixin: @classmethod - def platform_meta(cls): + def platform_limits(cls): return {} @@ -23,7 +23,7 @@ class Category(PlatformMixin, models.TextChoices): CLOUD = 'cloud', _("Clouding") @classmethod - def platform_meta(cls): + def platform_limits(cls): return { cls.HOST: { 'has_domain': True, @@ -40,7 +40,8 @@ class Category(PlatformMixin, models.TextChoices): 'has_domain': True }, cls.CLOUD: { - 'has_domain': False + 'has_domain': False, + 'protocol_limit': [] } } @@ -55,7 +56,7 @@ class HostTypes(PlatformMixin, models.TextChoices): OTHER_HOST = 'other_host', _("Other host") @classmethod - def platform_meta(cls): + def platform_limits(cls): return {} @classmethod @@ -87,9 +88,9 @@ class DatabaseTypes(PlatformMixin, models.TextChoices): REDIS = 'redis', 'Redis' @classmethod - def platform_meta(cls): + def platform_limits(cls): meta = {} - for name, labal in cls.choices: + for name, label in cls.choices: meta[name] = { 'protocols_limit': [name] } @@ -114,6 +115,25 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): RemoteAppTypes, CloudTypes ] + @classmethod + def get_type_limits(cls, category, tp): + limits = Category.platform_limits().get(category, {}) + types_cls = dict(cls.category_types()).get(category) + if not types_cls: + return {} + types_limits = types_cls.platform_limits() or {} + type_limits = types_limits.get(tp, {}) + limits.update(type_limits) + + _protocols_limit = limits.get('protocols_limit', []) + default_ports = Protocol.default_ports() + protocols_limit = [] + for p in _protocols_limit: + port = default_ports.get(p, 0) + protocols_limit.append(f'{p}/{port}') + limits['protocols_limit'] = protocols_limit + return limits + @classmethod def category_types(cls): return ( diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 186da3dd3..4a7ed2ce4 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -39,15 +39,14 @@ class Platform(models.Model): def get_type_meta(cls, category, tp): meta = Category.platform_meta().get(category, {}) types = dict(AllTypes.category_types()).get(category) - # if not types: - # return {} types_meta = types.platform_meta() or {} type_meta = types_meta.get(tp, {}) meta.update(type_meta) return meta - def get_meta(self): - return self.__class__.get_type_meta(self.category, self.type) + @property + def type_limits(self): + return AllTypes.get_type_limits(self.category, self.type) @classmethod def default(cls): diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 883a1d0b8..74f54ed07 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -27,7 +27,9 @@ def validate_duplicate_protocols(values): errors = [] names = [] - for value in values: + print("Value is: ", values) + + for value in values.split(' '): if not value or '/' not in value: continue name = value.split('/')[0] @@ -42,9 +44,7 @@ def validate_duplicate_protocols(values): class ProtocolsField(serializers.ListField): default_validators = [validate_duplicate_protocols] - def __init__(self, protocols=None, *args, **kwargs): - self.choices = [] - self.set_protocols(protocols) + def __init__(self, *args, **kwargs): kwargs['child'] = ProtocolField() kwargs['allow_null'] = True kwargs['allow_empty'] = True @@ -52,12 +52,6 @@ class ProtocolsField(serializers.ListField): kwargs['max_length'] = 32 super().__init__(*args, **kwargs) - def set_protocols(self, protocols): - if protocols is None: - protocols = [] - self.choices = [(c, c) for c in protocols] - print("Chocies: ", self.choices) - def to_representation(self, value): if not value: return [] @@ -65,6 +59,9 @@ class ProtocolsField(serializers.ListField): return value.split(' ') return value + def to_internal_value(self, data): + return ' '.join(data) + class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22']) @@ -76,7 +73,9 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): child=serializers.CharField(), label=_('Labels name'), required=False, read_only=True ) - platform_display = serializers.SlugField(source='platform.name', label=_("Platform display"), read_only=True) + platform_display = serializers.SlugField( + source='platform.name', label=_("Platform display"), read_only=True + ) """ 资产的数据结构 @@ -100,8 +99,7 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): ] read_only_fields = [ 'category', 'category_display', 'type', 'type_display', - 'connectivity', 'date_verified', - 'created_by', 'date_created', + 'connectivity', 'date_verified', 'created_by', 'date_created', ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 44e8ebb88..7a68e045d 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -13,6 +13,7 @@ __all__ = ['PlatformSerializer'] class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) protocols_default = ProtocolsField(label=_('Protocols'), required=False) + type_limits = serializers.ReadOnlyField(required=False, read_only=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -21,35 +22,7 @@ class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): validators = self.fields['name'].validators if isinstance(validators[-1], RegexValidator): validators.pop() - self.set_platform_meta() - - def set_platform_meta(self): - view = self.context.get('view') - if not view: - return - request = view.request - - if isinstance(self.instance, Platform): - category = self.instance.category - tp = self.instance.type - else: - tp = request.query_params.get('type') - category = request.query_params.get('category') - - print("Request: {}".format(self.context.get('request').method), category, tp) - if not all([tp, category]): - return - meta = Platform.get_type_meta(category, tp) - print("Platform meta: {}".format(meta)) - protocols_default = self.fields['protocols_default'] - limits = meta.get('protocols_limit', []) - default_ports = Protocol.default_ports() - protocols = [] - for protocol in limits: - port = default_ports.get(protocol, 22) - protocols.append(f'{protocol}/{port}') - print("set ptocols: ", protocols) - protocols_default.set_protocols(protocols) + # self.set_platform_meta() class Meta: model = Platform @@ -58,6 +31,7 @@ class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): 'meta', 'comment', 'charset', 'category', 'category_display', 'type', 'type_display', + 'type_limits', ] fields_fk = [ 'domain_enabled', 'domain_default', From cc167f1b49f0c76643c266ebce326fffaa8553ac Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 14 Jun 2022 19:49:07 +0800 Subject: [PATCH 021/488] xxx --- apps/assets/migrations/0099_auto_20220426_1558.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index 69ee76277..a42837899 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -182,14 +182,15 @@ def migrate_to_nodes(apps, *args): category__in=['remote_app', 'database', 'cloud'], org_id=org.id ) + if not node: + continue print("Set node asset: ", node) - if node: - node.assets_amount = len(assets) - node.save() - node.assets.set(assets) - parent = node.parent - parent.assets_amount += len(assets) - parent.save() + node.assets_amount = len(assets) + node.save() + node.assets.set(assets) + parent = node.parent + parent.assets_amount += len(assets) + parent.save() class Migration(migrations.Migration): From e2f199606e64122392fa6224661b585a2eae13c5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 16 Jun 2022 11:32:36 +0800 Subject: [PATCH 022/488] stash it --- .gitignore | 1 + apps/assets/migrations/0099_auto_20220426_1558.py | 8 ++++---- apps/locale/zh/LC_MESSAGES/django.po | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ecbb47960..70ca32392 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ logs/* release/* releashe /apps/script.py +xpack.bak diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index a42837899..a3418aea8 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -71,7 +71,6 @@ def migrate_database_to_asset(apps, *args): except: failed_apps.append(app) pass - # db.hostname = 'DB-' + db.hostname def migrate_remote_app_to_asset(apps, *args): @@ -154,11 +153,11 @@ def create_app_nodes(apps, org_id): if not node_keys: return node_key_split = [key.split(':') for key in node_keys] - next_value = int(max([k[1] for k in node_key_split])) + 1 + next_value = max([int(k[1]) for k in node_key_split]) + 1 parent_key = node_key_split[0][0] - parent = node_model.objects.get(key=parent_key) - next_key = '{}:{}'.format(node_key_split[0][0], next_value) + next_key = '{}:{}'.format(parent_key, next_value) name = 'Apps' + parent = node_model.objects.get(key=parent_key) full_value = parent.full_value + '/' + name defaults = { 'key': next_key, 'value': name, 'parent_key': parent_key, @@ -176,6 +175,7 @@ def migrate_to_nodes(apps, *args): asset_model = apps.get_model('assets', 'Asset') orgs = org_model.objects.all() + # Todo: 优化一些 for org in orgs: node = create_app_nodes(apps, org.id) assets = asset_model.objects.filter( diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index da8f978d8..bed4517ce 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -6157,7 +6157,7 @@ msgstr "启用 MFA(OTP)" #: users/templates/users/user_otp_enable_bind.html:6 msgid "Bind one-time password authenticator" -msgstr "绑定MFA验证器" +msgstr "绑定 MFA 验证器" #: users/templates/users/user_otp_enable_bind.html:13 msgid "" From e89765a9adec9a630c31bc48ec772529afa5d09b Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 Jul 2022 10:54:23 +0800 Subject: [PATCH 023/488] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0092_auto_20220711_1409.py | 77 +++++++++++++++++++ .../migrations/0093_auto_20220711_1413.py | 18 +++++ apps/assets/models/__init__.py | 1 + apps/assets/models/account.py | 27 +++++++ apps/assets/models/authbook.py | 1 + apps/assets/models/user.py | 2 +- 6 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 apps/assets/migrations/0092_auto_20220711_1409.py create mode 100644 apps/assets/migrations/0093_auto_20220711_1413.py create mode 100644 apps/assets/models/account.py diff --git a/apps/assets/migrations/0092_auto_20220711_1409.py b/apps/assets/migrations/0092_auto_20220711_1409.py new file mode 100644 index 000000000..5fb8704bd --- /dev/null +++ b/apps/assets/migrations/0092_auto_20220711_1409.py @@ -0,0 +1,77 @@ +# Generated by Django 3.2.12 on 2022-07-11 06:08 + +import assets.models.base +import assets.models.user +import common.db.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0091_auto_20220629_1826'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalAccount', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), + ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), + ('version', models.IntegerField(default=1, verbose_name='Version')), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('asset', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.asset', verbose_name='Asset')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical Account', + 'verbose_name_plural': 'historical Accounts', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='Account', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), + ('version', models.IntegerField(default=1, verbose_name='Version')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')), + ], + options={ + 'verbose_name': 'Account', + 'permissions': [('view_assetaccountsecret', 'Can view asset account secret'), ('change_assetaccountsecret', 'Can change asset account secret'), ('view_assethistoryaccount', 'Can view asset history account'), ('view_assethistoryaccountsecret', 'Can view asset history account secret')], + 'unique_together': {('username', 'asset')}, + }, + bases=(models.Model, assets.models.base.AuthMixin, assets.models.user.ProtocolMixin), + ), + ] diff --git a/apps/assets/migrations/0093_auto_20220711_1413.py b/apps/assets/migrations/0093_auto_20220711_1413.py new file mode 100644 index 000000000..c8cb17160 --- /dev/null +++ b/apps/assets/migrations/0093_auto_20220711_1413.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-07-11 06:13 + +from django.db import migrations + + +def migrate_accounts(apps, schema_editor): + auth_book_model = apps.get_model('assets', 'AuthBook') + account_model = apps.get_model('assets', 'Account') + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0092_auto_20220711_1409'), + ] + + operations = [ + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index d2dd03885..9d1df04a1 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -13,3 +13,4 @@ from .authbook import * from .gathered_user import * from .favorite_asset import * from .backup import * +from .account import * diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py new file mode 100644 index 000000000..0d6e999cc --- /dev/null +++ b/apps/assets/models/account.py @@ -0,0 +1,27 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from simple_history.models import HistoricalRecords + +from .user import ProtocolMixin +from .base import BaseUser + + +__all__ = ['Account'] + + +class Account(BaseUser, ProtocolMixin): + protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, + default='ssh', verbose_name=_('Protocol')) + asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) + version = models.IntegerField(default=1, verbose_name=_('Version')) + history = HistoricalRecords() + + class Meta: + verbose_name = _('Account') + unique_together = [('username', 'asset')] + permissions = [ + ('view_assetaccountsecret', _('Can view asset account secret')), + ('change_assetaccountsecret', _('Can change asset account secret')), + ('view_assethistoryaccount', _('Can view asset history account')), + ('view_assethistoryaccountsecret', _('Can view asset history account secret')), + ] diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index 338c65a3e..f5d9e457d 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -137,3 +137,4 @@ class AuthBook(BaseUser, AbsConnectivity): def __str__(self): return self.smart_name + diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index e20664071..0eb27e912 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -15,7 +15,7 @@ from .asset import Asset from .authbook import AuthBook -__all__ = ['AdminUser', 'SystemUser'] +__all__ = ['AdminUser', 'SystemUser', 'ProtocolMixin'] logger = logging.getLogger(__name__) From dac0b44b9948588036c2b02962befbf1a03d73ff Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 13 Jul 2022 16:36:49 +0800 Subject: [PATCH 024/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E9=87=8D?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0093_auto_20220711_1413.py | 45 +++++++++++++++++++ apps/assets/models/account.py | 3 ++ 2 files changed, 48 insertions(+) diff --git a/apps/assets/migrations/0093_auto_20220711_1413.py b/apps/assets/migrations/0093_auto_20220711_1413.py index c8cb17160..2a9e1b856 100644 --- a/apps/assets/migrations/0093_auto_20220711_1413.py +++ b/apps/assets/migrations/0093_auto_20220711_1413.py @@ -1,5 +1,6 @@ # Generated by Django 3.2.12 on 2022-07-11 06:13 +import time from django.db import migrations @@ -7,6 +8,49 @@ def migrate_accounts(apps, schema_editor): auth_book_model = apps.get_model('assets', 'AuthBook') account_model = apps.get_model('assets', 'Account') + count = 0 + bulk_size = 1000 + while True: + auth_books = auth_book_model.objects \ + .prefetch_related('systemuser') \ + .all()[count:count+bulk_size] + if not auth_books: + break + + accounts = [] + start = time.time() + # authbook 和 account 相同的属性 + same_attrs = [ + 'id', 'comment', 'date_created', 'date_updated', + 'created_by', 'asset_id', 'org_id', + ] + # 认证的属性,可能是 authbook 的,可能是 systemuser 的 + auth_attrs = ['username', 'password', 'private_key', 'public_key'] + + for auth_book in auth_books: + values = {attr: getattr(auth_book, attr) for attr in same_attrs} + values['protocol'] = 'ssh' + values['version'] = 1 + + system_user = auth_book.systemuser + if auth_book.systemuser: + values.update({attr: getattr(system_user, attr) for attr in auth_attrs}) + values['protocol'] = system_user.protocol + values['created_by'] = str(system_user.id) + + auth_book_auth = {attr: getattr(auth_book, attr) for attr in auth_attrs} + auth_book_auth = {attr: value for attr, value in auth_book_auth.items() if value} + values.update(auth_book_auth) + + account = account_model(**values) + accounts.append(account) + + account_model.objects.bulk_create(accounts, ignore_conflicts=True) + print("Create accounts: {}-{} using: {:.2f}s".format( + count, count + len(accounts), time.time()-start + )) + count += len(auth_books) + class Migration(migrations.Migration): @@ -15,4 +59,5 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RunPython(migrate_accounts) ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 0d6e999cc..7732c50a2 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -25,3 +25,6 @@ class Account(BaseUser, ProtocolMixin): ('view_assethistoryaccount', _('Can view asset history account')), ('view_assethistoryaccountsecret', _('Can view asset history account secret')), ] + + def __str__(self): + return '{}//{}@{}'.format(self.protocol, self.username, self.asset.hostname) From d3c67d2f04fab687e0ae24f718e3ccc6ad8468ef Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 14 Jul 2022 10:56:09 +0800 Subject: [PATCH 025/488] =?UTF-8?q?perf:=20=E6=9A=82=E5=AD=98=E4=B8=80?= =?UTF-8?q?=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account_history.py | 13 ++---- apps/assets/api/accounts.py | 18 +++----- apps/assets/api/system_user_relation.py | 1 - .../migrations/0092_auto_20220711_1409.py | 8 +++- .../migrations/0093_auto_20220711_1413.py | 13 +++--- .../0094_alter_systemuser_assets.py | 22 +++++++++ .../migrations/0095_auto_20220713_1746.py | 45 +++++++++++++++++++ apps/assets/models/account.py | 9 +++- apps/assets/models/user.py | 1 - apps/assets/serializers/system_user.py | 2 +- apps/assets/tasks/asset_connectivity.py | 11 ++--- apps/assets/tasks/common.py | 3 +- apps/assets/tasks/system_user_connectivity.py | 10 ++--- 13 files changed, 112 insertions(+), 44 deletions(-) create mode 100644 apps/assets/migrations/0094_alter_systemuser_assets.py create mode 100644 apps/assets/migrations/0095_auto_20220713_1746.py diff --git a/apps/assets/api/account_history.py b/apps/assets/api/account_history.py index 3258dfd48..6ca4fd349 100644 --- a/apps/assets/api/account_history.py +++ b/apps/assets/api/account_history.py @@ -1,23 +1,21 @@ -from django.db.models import F - from assets.api.accounts import ( AccountFilterSet, AccountViewSet, AccountSecretsViewSet ) from common.mixins import RecordViewLogMixin from .. import serializers -from ..models import AuthBook +from ..models import Account __all__ = ['AccountHistoryViewSet', 'AccountHistorySecretsViewSet'] class AccountHistoryFilterSet(AccountFilterSet): class Meta: - model = AuthBook.history.model + model = Account.history.model fields = AccountFilterSet.Meta.fields class AccountHistoryViewSet(AccountViewSet): - model = AuthBook.history.model + model = Account.history.model filterset_class = AccountHistoryFilterSet serializer_classes = { 'default': serializers.AccountHistorySerializer, @@ -26,13 +24,8 @@ class AccountHistoryViewSet(AccountViewSet): 'list': 'assets.view_assethistoryaccount', 'retrieve': 'assets.view_assethistoryaccount', } - http_method_names = ['get', 'options'] - def get_queryset(self): - queryset = AuthBook.get_queryset(is_history_model=True) - return queryset - class AccountHistorySecretsViewSet(RecordViewLogMixin, AccountHistoryViewSet): serializer_classes = { diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index 6ad59ed88..be6833e7b 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -12,7 +12,7 @@ from common.mixins import RecordViewLogMixin from common.permissions import UserConfirmation from authentication.const import ConfirmType from ..tasks.account_connectivity import test_accounts_connectivity_manual -from ..models import AuthBook, Node +from ..models import Node, Account from .. import serializers __all__ = ['AccountFilterSet', 'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] @@ -50,16 +50,16 @@ class AccountFilterSet(BaseFilterSet): return qs class Meta: - model = AuthBook + model = Account fields = [ - 'asset', 'systemuser', 'id', + 'asset', 'id', ] class AccountViewSet(OrgBulkModelViewSet): - model = AuthBook - filterset_fields = ("username", "asset", "systemuser", 'ip', 'hostname') - search_fields = ('username', 'ip', 'hostname', 'systemuser__username') + model = Account + filterset_fields = ("username", "asset", 'ip', 'hostname') + search_fields = ('username', 'ip', 'hostname') filterset_class = AccountFilterSet serializer_classes = { 'default': serializers.AccountSerializer, @@ -70,10 +70,6 @@ class AccountViewSet(OrgBulkModelViewSet): 'partial_update': 'assets.change_assetaccountsecret', } - def get_queryset(self): - queryset = AuthBook.get_queryset() - return queryset - @action(methods=['post'], detail=True, url_path='verify') def verify_account(self, request, *args, **kwargs): account = super().get_object() @@ -106,7 +102,7 @@ class AccountTaskCreateAPI(CreateAPIView): return request.user.has_perm('assets.test_assetconnectivity') def get_accounts(self): - queryset = AuthBook.objects.all() + queryset = Account.objects.all() queryset = self.filter_queryset(queryset) return queryset diff --git a/apps/assets/api/system_user_relation.py b/apps/assets/api/system_user_relation.py index 2d8018e4d..36c16a09b 100644 --- a/apps/assets/api/system_user_relation.py +++ b/apps/assets/api/system_user_relation.py @@ -68,7 +68,6 @@ class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet): class SystemUserAssetRelationViewSet(BaseRelationViewSet): - perm_model = models.AuthBook serializer_class = serializers.SystemUserAssetRelationSerializer model = models.SystemUser.assets.through filterset_fields = [ diff --git a/apps/assets/migrations/0092_auto_20220711_1409.py b/apps/assets/migrations/0092_auto_20220711_1409.py index 5fb8704bd..d6076b6c3 100644 --- a/apps/assets/migrations/0092_auto_20220711_1409.py +++ b/apps/assets/migrations/0092_auto_20220711_1409.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.12 on 2022-07-11 06:08 +# Generated by Django 3.2.12 on 2022-07-11 08:59 import assets.models.base import assets.models.user @@ -22,6 +22,8 @@ class Migration(migrations.Migration): name='HistoricalAccount', fields=[ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), + ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), @@ -33,6 +35,7 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), + ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), ('version', models.IntegerField(default=1, verbose_name='Version')), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), @@ -53,6 +56,8 @@ class Migration(migrations.Migration): name='Account', fields=[ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), + ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), @@ -64,6 +69,7 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), + ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), ('version', models.IntegerField(default=1, verbose_name='Version')), ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')), ], diff --git a/apps/assets/migrations/0093_auto_20220711_1413.py b/apps/assets/migrations/0093_auto_20220711_1413.py index 2a9e1b856..ecb4b47e6 100644 --- a/apps/assets/migrations/0093_auto_20220711_1413.py +++ b/apps/assets/migrations/0093_auto_20220711_1413.py @@ -10,16 +10,18 @@ def migrate_accounts(apps, schema_editor): count = 0 bulk_size = 1000 + print("\nStart migrate accounts") while True: + start = time.time() auth_books = auth_book_model.objects \ - .prefetch_related('systemuser') \ - .all()[count:count+bulk_size] + .prefetch_related('systemuser') \ + .all()[count:count+bulk_size] + count += len(auth_books) if not auth_books: break accounts = [] - start = time.time() - # authbook 和 account 相同的属性 + # auth book 和 account 相同的属性 same_attrs = [ 'id', 'comment', 'date_created', 'date_updated', 'created_by', 'asset_id', 'org_id', @@ -47,9 +49,8 @@ def migrate_accounts(apps, schema_editor): account_model.objects.bulk_create(accounts, ignore_conflicts=True) print("Create accounts: {}-{} using: {:.2f}s".format( - count, count + len(accounts), time.time()-start + count - bulk_size, count, time.time()-start )) - count += len(auth_books) class Migration(migrations.Migration): diff --git a/apps/assets/migrations/0094_alter_systemuser_assets.py b/apps/assets/migrations/0094_alter_systemuser_assets.py new file mode 100644 index 000000000..c3f2f2cfc --- /dev/null +++ b/apps/assets/migrations/0094_alter_systemuser_assets.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.12 on 2022-07-13 09:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0093_auto_20220711_1413'), + ] + + operations = [ + migrations.RemoveField( + model_name='systemuser', + name='assets', + ), + migrations.AddField( + model_name='systemuser', + name='assets', + field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.Asset', verbose_name='Assets'), + ), + ] diff --git a/apps/assets/migrations/0095_auto_20220713_1746.py b/apps/assets/migrations/0095_auto_20220713_1746.py new file mode 100644 index 000000000..b803dae51 --- /dev/null +++ b/apps/assets/migrations/0095_auto_20220713_1746.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2.12 on 2022-07-13 09:46 + +import time +from django.db import migrations + + +def migrate_asset_system_user_relations(apps, schema_editor): + system_user_model = apps.get_model('assets', 'SystemUser') + old_model = apps.get_model('assets', 'AuthBook') + new_model = system_user_model.assets.through + + count = 0 + bulk_size = 1000 + print("\nStart migrate asset system user relations") + while True: + start = time.time() + auth_books = old_model.objects.only('asset_id', 'systemuser_id')[count:count+bulk_size] + auth_books = list(auth_books) + count += len(auth_books) + if not auth_books: + break + asset_system_users = [] + for auth_book in auth_books: + if not auth_book.asset_id or not auth_book.systemuser_id: + continue + asset_system_user = new_model( + asset_id=auth_book.asset_id, + systemuser_id=auth_book.systemuser_id + ) + asset_system_users.append(asset_system_user) + new_model.objects.bulk_create(asset_system_users, ignore_conflicts=True) + print("Create asset system user relations: {}-{} using: {:.2f}s".format( + count - bulk_size, count, time.time()-start + )) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0094_alter_systemuser_assets'), + ] + + operations = [ + migrations.RunPython(migrate_asset_system_user_relations) + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 7732c50a2..d060ec40d 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,15 +3,20 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from .user import ProtocolMixin -from .base import BaseUser +from .base import BaseUser, AbsConnectivity __all__ = ['Account'] -class Account(BaseUser, ProtocolMixin): +class Account(BaseUser, AbsConnectivity, ProtocolMixin): + class Type(models.TextChoices): + common = 'common', _('Common user') + admin = 'admin', _('Admin user') + protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) + type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_("Type")) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=1, verbose_name=_('Version')) history = HistoricalRecords() diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 0eb27e912..29f7b5972 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -242,7 +242,6 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser): nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) assets = models.ManyToManyField( 'assets.Asset', blank=True, verbose_name=_("Assets"), - through='assets.AuthBook', through_fields=['systemuser', 'asset'], related_name='system_users' ) users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users")) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 54aff3e82..aa17b0585 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -298,7 +298,7 @@ class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializ asset_display = serializers.ReadOnlyField(label=_('Asset hostname')) class Meta: - model = SystemUser.assets.through + model = SystemUser fields = [ "id", "asset", "asset_display", 'systemuser', 'systemuser_display', "connectivity", 'date_verified', 'org_id' diff --git a/apps/assets/tasks/asset_connectivity.py b/apps/assets/tasks/asset_connectivity.py index 0038c38d3..491792095 100644 --- a/apps/assets/tasks/asset_connectivity.py +++ b/apps/assets/tasks/asset_connectivity.py @@ -6,7 +6,7 @@ from django.utils.translation import gettext_noop from common.utils import get_logger from orgs.utils import org_aware_func -from ..models import Asset, Connectivity, AuthBook +from ..models import Asset, Connectivity, Account from . import const from .utils import clean_ansible_task_hosts, group_asset_by_platform @@ -18,6 +18,7 @@ __all__ = [ ] +# Todo: 这里可能有问题了 def set_assets_accounts_connectivity(assets, results_summary): asset_ids_ok = set() asset_ids_failed = set() @@ -33,11 +34,11 @@ def set_assets_accounts_connectivity(assets, results_summary): Asset.bulk_set_connectivity(asset_ids_ok, Connectivity.ok) Asset.bulk_set_connectivity(asset_ids_failed, Connectivity.failed) - accounts_ok = AuthBook.objects.filter(asset_id__in=asset_ids_ok, systemuser__type='admin') - accounts_failed = AuthBook.objects.filter(asset_id__in=asset_ids_failed, systemuser__type='admin') + accounts_ok = Account.objects.filter(asset_id__in=asset_ids_ok,) + accounts_failed = Account.objects.filter(asset_id__in=asset_ids_faile) - AuthBook.bulk_set_connectivity(accounts_ok, Connectivity.ok) - AuthBook.bulk_set_connectivity(accounts_failed, Connectivity.failed) + Account.bulk_set_connectivity(accounts_ok, Connectivity.ok) + Account.bulk_set_connectivity(accounts_failed, Connectivity.failed) @shared_task(queue="ansible") diff --git a/apps/assets/tasks/common.py b/apps/assets/tasks/common.py index b6c2326e9..a1bd79e11 100644 --- a/apps/assets/tasks/common.py +++ b/apps/assets/tasks/common.py @@ -17,6 +17,7 @@ def add_nodes_assets_to_system_users(nodes_keys, system_users): nodes = Node.objects.filter(key__in=nodes_keys) assets = Node.get_nodes_all_assets(*nodes) + for system_user in system_users: """ 解决资产和节点进行关联时,已经关联过的节点不会触发 authbook post_save 信号, 无法更新节点下所有资产的管理用户的问题 """ @@ -28,7 +29,7 @@ def add_nodes_assets_to_system_users(nodes_keys, system_users): ) if created: need_push_asset_ids.append(asset.id) - # # 不再自动更新资产管理用户,只允许用户手动指定。 + # 不再自动更新资产管理用户,只允许用户手动指定。 # 只要关联都需要更新资产的管理用户 # instance.update_asset_admin_user_if_need() diff --git a/apps/assets/tasks/system_user_connectivity.py b/apps/assets/tasks/system_user_connectivity.py index 893081163..2213bfa26 100644 --- a/apps/assets/tasks/system_user_connectivity.py +++ b/apps/assets/tasks/system_user_connectivity.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _, gettext_noop from assets.models import Asset from common.utils import get_logger from orgs.utils import tmp_to_org, org_aware_func -from ..models import SystemUser, Connectivity, AuthBook +from ..models import SystemUser, Connectivity, Account from . import const from .utils import ( clean_ansible_task_hosts, group_asset_by_platform @@ -34,11 +34,11 @@ def set_assets_accounts_connectivity(system_user, assets, results_summary): else: asset_ids_failed.add(asset.id) - accounts_ok = AuthBook.objects.filter(asset_id__in=asset_ids_ok, systemuser=system_user) - accounts_failed = AuthBook.objects.filter(asset_id__in=asset_ids_failed, systemuser=system_user) + accounts_ok = Account.objects.filter(asset_id__in=asset_ids_ok, systemuser=system_user) + accounts_failed = Account.objects.filter(asset_id__in=asset_ids_failed, systemuser=system_user) - AuthBook.bulk_set_connectivity(accounts_ok, Connectivity.ok) - AuthBook.bulk_set_connectivity(accounts_failed, Connectivity.failed) + Account.bulk_set_connectivity(accounts_ok, Connectivity.ok) + Account.bulk_set_connectivity(accounts_failed, Connectivity.failed) @org_aware_func("system_user") From 29c9c6d68029e695df3fb43fd25e4aacf62d8c09 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 15 Jul 2022 18:03:32 +0800 Subject: [PATCH 026/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0093_auto_20220711_1413.py | 1 + .../migrations/0096_auto_20220714_1627.py | 23 +++ apps/assets/models/account.py | 2 +- apps/assets/models/asset.py | 10 -- apps/assets/models/user.py | 168 ++---------------- apps/assets/serializers/account.py | 35 ++-- apps/assets/serializers/account_history.py | 19 +- apps/assets/serializers/asset.py | 23 ++- apps/assets/serializers/system_user.py | 8 +- apps/assets/signal_handlers/asset.py | 34 ++-- apps/assets/signal_handlers/authbook.py | 44 +---- apps/assets/signal_handlers/system_user.py | 33 +--- apps/assets/task_handlers/backup/handlers.py | 6 +- apps/assets/tasks/common.py | 1 + apps/assets/tasks/utils.py | 2 +- apps/audits/const.py | 2 +- apps/jumpserver/settings/custom.py | 1 - apps/locale/zh/LC_MESSAGES/django.po | 12 +- .../serializers/system_user_permission.py | 2 +- utils/create_assets_user/bulk_create_user.py | 2 +- 20 files changed, 118 insertions(+), 310 deletions(-) create mode 100644 apps/assets/migrations/0096_auto_20220714_1627.py diff --git a/apps/assets/migrations/0093_auto_20220711_1413.py b/apps/assets/migrations/0093_auto_20220711_1413.py index ecb4b47e6..ba7d7e0c0 100644 --- a/apps/assets/migrations/0093_auto_20220711_1413.py +++ b/apps/assets/migrations/0093_auto_20220711_1413.py @@ -39,6 +39,7 @@ def migrate_accounts(apps, schema_editor): values.update({attr: getattr(system_user, attr) for attr in auth_attrs}) values['protocol'] = system_user.protocol values['created_by'] = str(system_user.id) + values['type'] = system_user.type auth_book_auth = {attr: getattr(auth_book, attr) for attr in auth_attrs} auth_book_auth = {attr: value for attr, value in auth_book_auth.items() if value} diff --git a/apps/assets/migrations/0096_auto_20220714_1627.py b/apps/assets/migrations/0096_auto_20220714_1627.py new file mode 100644 index 000000000..4637128ff --- /dev/null +++ b/apps/assets/migrations/0096_auto_20220714_1627.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.12 on 2022-07-14 08:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0095_auto_20220713_1746'), + ] + + operations = [ + migrations.RenameField( + model_name='systemuser', + old_name='auto_push', + new_name='auto_push_account', + ), + migrations.AddField( + model_name='systemuser', + name='auto_create_account', + field=models.BooleanField(default=False, verbose_name='Auto account if not exist'), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index d060ec40d..9d503c99b 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -32,4 +32,4 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin): ] def __str__(self): - return '{}//{}@{}'.format(self.protocol, self.username, self.asset.hostname) + return '{}://{}@{}'.format(self.protocol, self.username, self.asset.hostname) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 7dde0862f..157895dae 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -238,16 +238,6 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin def get_target_ip(self): return self.ip - def set_admin_user_relation(self): - from .authbook import AuthBook - if not self.admin_user: - return - if self.admin_user.type != 'admin': - raise ValidationError('System user should be type admin') - - defaults = {'asset': self, 'systemuser': self.admin_user, 'org_id': self.org_id} - AuthBook.objects.get_or_create(defaults=defaults, asset=self, systemuser=self.admin_user) - @property def admin_user_display(self): if not self.admin_user: diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 29f7b5972..bfa4dd3af 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -7,12 +7,10 @@ import logging from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator -from django.core.cache import cache -from common.utils import signer, get_object_or_none +from common.utils import signer from .base import BaseUser from .asset import Asset -from .authbook import AuthBook __all__ = ['AdminUser', 'SystemUser', 'ProtocolMixin'] @@ -82,155 +80,11 @@ class ProtocolMixin: return self.protocol in self.ASSET_CATEGORY_PROTOCOLS -class AuthMixin: - username_same_with_user: bool - protocol: str - ASSET_CATEGORY_PROTOCOLS: list - login_mode: str - LOGIN_MANUAL: str - id: str - username: str - password: str - private_key: str - public_key: str - - def set_temp_auth(self, asset_or_app_id, user_id, auth, ttl=300): - if not auth: - raise ValueError('Auth not set') - key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id) - logger.debug(f'Set system user temp auth: {key}') - cache.set(key, auth, ttl) - - def get_temp_auth(self, asset_or_app_id, user_id): - key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id) - logger.debug(f'Get system user temp auth: {key}') - password = cache.get(key) - return password - - def _clean_auth_info_if_manual_login_mode(self): - if self.login_mode == self.LOGIN_MANUAL: - self.password = '' - self.private_key = '' - self.public_key = '' - - def _load_tmp_auth_if_has(self, asset_or_app_id, user_id): - if self.login_mode != self.LOGIN_MANUAL: - return - - if not asset_or_app_id or not user_id: - return - - auth = self.get_temp_auth(asset_or_app_id, user_id) - if not auth: - return - - username = auth.get('username') - password = auth.get('password') - - if username: - self.username = username - if password: - self.password = password - - def load_app_more_auth(self, app_id=None, username=None, user_id=None): - # 清除认证信息 - self._clean_auth_info_if_manual_login_mode() - - # 先加载临时认证信息 - if self.login_mode == self.LOGIN_MANUAL: - self._load_tmp_auth_if_has(app_id, user_id) - return - - # Remote app - from applications.models import Application - app = get_object_or_none(Application, pk=app_id) - if app and app.category_remote_app: - # Remote app - self._load_remoteapp_more_auth(app, username, user_id) - return - - # Other app - # 更新用户名 - from users.models import User - user = get_object_or_none(User, pk=user_id) if user_id else None - if self.username_same_with_user: - if user and not username: - _username = user.username - else: - _username = username - self.username = _username - - def _load_remoteapp_more_auth(self, app, username, user_id): - asset = app.get_remote_app_asset(raise_exception=False) - if asset: - self.load_asset_more_auth(asset_id=asset.id, username=username, user_id=user_id) - - def load_asset_special_auth(self, asset, username=''): - """ - AuthBook 的数据状态 - | asset | systemuser | username | - 1 | * | * | x | - 2 | * | x | * | - - 当前 AuthBook 只有以上两种状态,systemuser 与 username 不会并存。 - 正常的资产与系统用户关联产生的是第1种状态,改密则产生第2种状态。改密之后 - 只有 username 而没有 systemuser 。 - - Freq: 关联同一资产的多个系统用户指定同一用户名时,修改用户密码会影响所有系统用户 - - 这里有一个不对称的行为,同名系统用户密码覆盖 - 当有相同 username 的多个系统用户时,有改密动作之后,所有的同名系统用户都使用最后 - 一次改动,但如果没有发生过改密,同名系统用户使用的密码还是各自的。 - - """ - if username == '': - username = self.username - - authbook = AuthBook.objects.filter( - asset=asset, username=username, systemuser__isnull=True - ).order_by('-date_created').first() - - if not authbook: - authbook = AuthBook.objects.filter( - asset=asset, systemuser=self - ).order_by('-date_created').first() - - if not authbook: - return None - - authbook.load_auth() - self.password = authbook.password - self.private_key = authbook.private_key - self.public_key = authbook.public_key - - def load_asset_more_auth(self, asset_id=None, username=None, user_id=None): - from users.models import User - self._clean_auth_info_if_manual_login_mode() - # 加载临时认证信息 - if self.login_mode == self.LOGIN_MANUAL: - self._load_tmp_auth_if_has(asset_id, user_id) - return - # 更新用户名 - user = get_object_or_none(User, pk=user_id) if user_id else None - if self.username_same_with_user: - if user and not username: - _username = user.username - else: - _username = username - self.username = _username - # 加载某个资产的特殊配置认证信息 - asset = get_object_or_none(Asset, pk=asset_id) if asset_id else None - if not asset: - logger.debug('Asset not found, pass') - return - self.load_asset_special_auth(asset, self.username) - - -class SystemUser(ProtocolMixin, AuthMixin, BaseUser): +class SystemUser(ProtocolMixin, BaseUser): LOGIN_AUTO = 'auto' LOGIN_MANUAL = 'manual' LOGIN_MODE_CHOICES = ( - (LOGIN_AUTO, _('Automatic managed')), + (LOGIN_AUTO, _('使用账号')), (LOGIN_MANUAL, _('Manually input')) ) @@ -246,13 +100,19 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser): ) users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users")) groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) - type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) - priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) + priority = models.IntegerField( + default=81, verbose_name=_("Priority"), + help_text=_("1-100, the lower the value will be match first"), + validators=[MinValueValidator(1), MaxValueValidator(100)] + ) protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) - auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) + + login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) + auto_create_account = models.BooleanField(default=False, verbose_name=_("自动创建账号")) + auto_push_account = models.BooleanField(default=True, verbose_name=_('推送账号到资产')) + type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) - login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) token = models.TextField(default='', verbose_name=_('Token')) home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) @@ -277,7 +137,7 @@ class SystemUser(ProtocolMixin, AuthMixin, BaseUser): return self.get_login_mode_display() def is_need_push(self): - if self.auto_push and self.is_protocol_support_push: + if self.auto_push_account and self.is_protocol_support_push: return True else: return False diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index e4e320f33..33632d0c9 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -1,7 +1,8 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from django.db.models import F -from assets.models import AuthBook +from assets.models import Account from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .base import AuthSerializerMixin @@ -13,7 +14,6 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ip = serializers.ReadOnlyField(label=_("IP")) hostname = serializers.ReadOnlyField(label=_("Hostname")) platform = serializers.ReadOnlyField(label=_("Platform")) - protocols = serializers.SerializerMethodField(label=_("Protocols")) date_created = serializers.DateTimeField( label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True ) @@ -22,18 +22,20 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ) class Meta: - model = AuthBook - fields_mini = ['id', 'username', 'ip', 'hostname', 'platform', 'protocols', 'version'] - fields_write_only = ['password', 'private_key', "public_key", 'passphrase'] + model = Account + fields_mini = [ + 'id', 'type', 'username', 'ip', 'hostname', + 'platform', 'protocol', 'version' + ] + fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment'] fields_small = fields_mini + fields_write_only + fields_other - fields_fk = ['asset', 'systemuser', 'systemuser_display'] + fields_fk = ['asset'] fields = fields_small + fields_fk extra_kwargs = { 'username': {'required': True}, 'private_key': {'write_only': True}, 'public_key': {'write_only': True}, - 'systemuser_display': {'label': _('System user display')} } ref_name = 'AssetAccountSerializer' @@ -52,27 +54,14 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): attrs = self._validate_gen_key(attrs) return attrs - def get_protocols(self, v): - """ protocols 是 queryset 中返回的,Post 创建成功后返回序列化时没有这个字段 """ - if hasattr(v, 'protocols'): - protocols = v.protocols - elif hasattr(v, 'asset') and v.asset: - protocols = v.asset.protocols - else: - protocols = '' - protocols = protocols.replace(' ', ', ') - return protocols - @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('systemuser', 'asset') + queryset = queryset.prefetch_related('asset')\ + .annotate(ip=F('asset__ip')) \ + .annotate(hostname=F('asset__hostname')) return queryset - def to_representation(self, instance): - instance.load_auth() - return super().to_representation(instance) - class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): diff --git a/apps/assets/serializers/account_history.py b/apps/assets/serializers/account_history.py index fb2844182..b0846c04d 100644 --- a/apps/assets/serializers/account_history.py +++ b/apps/assets/serializers/account_history.py @@ -1,29 +1,20 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ -from assets.models import AuthBook +from assets.models import Account from common.drf.serializers import SecretReadableMixin from .account import AccountSerializer, AccountSecretSerializer class AccountHistorySerializer(AccountSerializer): - systemuser_display = serializers.SerializerMethodField(label=_('System user display')) class Meta: - model = AuthBook.history.model + model = Account.history.model fields = AccountSerializer.Meta.fields_mini + \ - AccountSerializer.Meta.fields_write_only + \ - AccountSerializer.Meta.fields_fk + \ - ['history_id', 'date_created', 'date_updated'] + AccountSerializer.Meta.fields_write_only + \ + AccountSerializer.Meta.fields_fk + \ + ['history_id', 'date_created', 'date_updated'] read_only_fields = fields ref_name = 'AccountHistorySerializer' - @staticmethod - def get_systemuser_display(instance): - if not instance.systemuser: - return '' - return str(instance.systemuser) - def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) fields = list(set(fields) - {'org_name'}) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 427d0e470..da1a0a0fc 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import Asset, Node, Platform, SystemUser +from .account import AccountSerializer __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', @@ -70,6 +71,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): labels_display = serializers.ListField( child=serializers.CharField(), label=_('Labels name'), required=False, read_only=True ) + accounts = AccountSerializer(many=True, write_only=True, required=False) """ 资产的数据结构 @@ -92,7 +94,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display' ] fields_m2m = [ - 'nodes', 'nodes_display', 'labels', 'labels_display', + 'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts' ] read_only_fields = [ 'connectivity', 'date_verified', 'cpu_info', 'hardware_info', @@ -107,6 +109,11 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'cpu_info': {'label': _('CPU info')}, } + def __init__(self, *args, **kwargs): + data = kwargs.get('data', {}) + self.accounts_data = data.pop('accounts', []) + super().__init__(*args, **kwargs) + def get_fields(self): fields = super().get_fields() @@ -151,10 +158,24 @@ class AssetSerializer(BulkOrgResourceModelSerializer): nodes_to_set.append(node) instance.nodes.set(nodes_to_set) + @staticmethod + def add_accounts(instance, accounts_data): + for data in accounts_data: + data['asset'] = instance.id + print("Data: ", accounts_data) + serializer = AccountSerializer(data=accounts_data, many=True) + try: + serializer.is_valid(raise_exception=True) + except Exception as e: + raise serializers.ValidationError({'accounts': e}) + serializer.save() + def create(self, validated_data): self.compatible_with_old_protocol(validated_data) nodes_display = validated_data.pop('nodes_display', '') instance = super().create(validated_data) + if self.accounts_data: + self.add_accounts(instance, self.accounts_data) self.perform_nodes_display_create(instance, nodes_display) return instance diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index aa17b0585..a572a1736 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -47,9 +47,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): fields_small = fields_mini + fields_write_only + [ 'token', 'ssh_key_fingerprint', 'type', 'type_display', 'protocol', 'is_asset_protocol', - 'login_mode', 'login_mode_display', 'priority', + 'auto_create_account', 'login_mode', 'login_mode_display', 'priority', 'sudo', 'shell', 'sftp_root', 'home', 'system_groups', 'ad_domain', - 'username_same_with_user', 'auto_push', 'auto_generate_key', + 'username_same_with_user', 'auto_push_account', 'auto_generate_key', 'su_enabled', 'su_from', 'date_created', 'date_updated', 'comment', 'created_by', ] @@ -191,7 +191,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): attrs['protocol'] = SystemUser.Protocol.ssh attrs['login_mode'] = SystemUser.LOGIN_AUTO attrs['username_same_with_user'] = False - attrs['auto_push'] = False + attrs['auto_push_account'] = False return attrs def _validate_gen_key(self, attrs): @@ -259,7 +259,7 @@ class SystemUserWithAuthInfoSerializer(SecretReadableMixin, SystemUserSerializer fields_small = fields_mini + fields_write_only + [ 'protocol', 'login_mode', 'login_mode_display', 'priority', 'sudo', 'shell', 'ad_domain', 'sftp_root', 'token', - "username_same_with_user", 'auto_push', 'auto_generate_key', + "username_same_with_user", 'auto_push_account', 'auto_generate_key', 'comment', ] fields = fields_small diff --git a/apps/assets/signal_handlers/asset.py b/apps/assets/signal_handlers/asset.py index 97f727a46..a7e466c9b 100644 --- a/apps/assets/signal_handlers/asset.py +++ b/apps/assets/signal_handlers/asset.py @@ -52,8 +52,6 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs): if not has_node: instance.nodes.add(Node.org_root()) - instance.set_admin_user_relation() - @receiver(m2m_changed, sender=Asset.nodes.through) def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs): @@ -89,22 +87,22 @@ def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs): systemuser_id__in=system_user_ids, asset_id__in=asset_ids ).values_list('systemuser_id', 'asset_id')) # TODO 优化 - to_create = [] - for system_user_id in system_user_ids: - asset_ids_to_push = [] - for asset_id in asset_ids: - if (system_user_id, asset_id) in exist: - continue - asset_ids_to_push.append(asset_id) - to_create.append(m2m_model( - systemuser_id=system_user_id, - asset_id=asset_id, - org_id=instance.org_id - )) - if asset_ids_to_push: - push_system_user_to_assets.delay(system_user_id, asset_ids_to_push) - m2m_model.objects.bulk_create(to_create) - + # to_create = [] + # for system_user_id in system_user_ids: + # asset_ids_to_push = [] + # for asset_id in asset_ids: + # if (system_user_id, asset_id) in exist: + # continue + # asset_ids_to_push.append(asset_id) + # to_create.append(m2m_model( + # systemuser_id=system_user_id, + # asset_id=asset_id, + # org_id=instance.org_id + # )) + # if asset_ids_to_push: + # push_system_user_to_assets.delay(system_user_id, asset_ids_to_push) + # m2m_model.objects.bulk_create(to_create) + # RELATED_NODE_IDS = '_related_node_ids' diff --git a/apps/assets/signal_handlers/authbook.py b/apps/assets/signal_handlers/authbook.py index 513488763..8020e4087 100644 --- a/apps/assets/signal_handlers/authbook.py +++ b/apps/assets/signal_handlers/authbook.py @@ -1,50 +1,14 @@ from django.dispatch import receiver -from django.apps import apps -from simple_history.signals import pre_create_historical_record -from django.db.models.signals import post_save, pre_save, pre_delete +from django.db.models.signals import pre_save from common.utils import get_logger -from ..models import AuthBook, SystemUser +from ..models import Account -AuthBookHistory = apps.get_model('assets', 'HistoricalAuthBook') logger = get_logger(__name__) -@receiver(pre_create_historical_record, sender=AuthBookHistory) -def pre_create_historical_record_callback(sender, history_instance=None, **kwargs): - attrs_to_copy = ['username', 'password', 'private_key'] - - for attr in attrs_to_copy: - if getattr(history_instance, attr): - continue - try: - system_user = history_instance.systemuser - except SystemUser.DoesNotExist: - continue - if not system_user: - continue - system_user_attr_value = getattr(history_instance.systemuser, attr) - if system_user_attr_value: - setattr(history_instance, attr, system_user_attr_value) - - -@receiver(pre_delete, sender=AuthBook) -def on_authbook_post_delete(sender, instance, **kwargs): - instance.remove_asset_admin_user_if_need() - - -@receiver(post_save, sender=AuthBook) -def on_authbook_post_create(sender, instance, created, **kwargs): - instance.sync_to_system_user_account() - if created: - pass - # # 不再自动更新资产管理用户,只允许用户手动指定。 - # 只在创建时进行更新资产的管理用户 - # instance.update_asset_admin_user_if_need() - - -@receiver(pre_save, sender=AuthBook) -def on_authbook_pre_create(sender, instance, **kwargs): +@receiver(pre_save, sender=Account) +def on_account_pre_create(sender, instance, **kwargs): # 升级版本号 instance.version += 1 # 即使在 root 组织也不怕 diff --git a/apps/assets/signal_handlers/system_user.py b/apps/assets/signal_handlers/system_user.py index 00b19e110..64a656c29 100644 --- a/apps/assets/signal_handlers/system_user.py +++ b/apps/assets/signal_handlers/system_user.py @@ -9,9 +9,8 @@ from common.exceptions import M2MReverseNotAllowed from common.const.signals import POST_ADD from common.utils import get_logger from common.decorator import on_transaction_commit -from assets.models import Asset, SystemUser, Node, AuthBook +from assets.models import Asset, SystemUser, Node from users.models import User -from orgs.utils import tmp_to_root_org from assets.tasks import ( push_system_user_to_assets_manual, push_system_user_to_assets, @@ -39,35 +38,7 @@ def on_system_user_assets_change(instance, action, model, pk_set, **kwargs): else: system_user_ids = pk_set asset_ids = [instance.id] - - org_id = instance.org_id - - # 关联创建的 authbook 没有系统用户id - with tmp_to_root_org(): - authbooks = AuthBook.objects.filter( - asset_id__in=asset_ids, - systemuser_id__in=system_user_ids - ) - if action == POST_ADD: - authbooks.update(org_id=org_id) - - save_action_mapper = { - 'pre_add': pre_save, - 'post_add': post_save, - 'pre_remove': pre_delete, - 'post_remove': post_delete - } - - for ab in authbooks: - ab.org_id = org_id - - save_action = save_action_mapper[action] - logger.debug('Send AuthBook post save signal: {} -> {}'.format(action, ab.id)) - save_action.send(sender=AuthBook, instance=ab, created=True) - - if action == POST_ADD: - for system_user_id in system_user_ids: - push_system_user_to_assets.delay(system_user_id, asset_ids) + # todo: Auto create account if need @receiver(m2m_changed, sender=SystemUser.users.through) diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index 311d8e395..969ed5433 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -7,7 +7,7 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from assets.models import AuthBook +from assets.models import Account from assets.serializers import AccountSecretSerializer from assets.notifications import AccountBackupExecutionTaskMsg from applications.models import Account @@ -74,9 +74,9 @@ class AssetAccountHandler(BaseAccountHandler): @classmethod def create_data_map(cls): data_map = defaultdict(list) - sheet_name = AuthBook._meta.verbose_name + sheet_name = Account._meta.verbose_name - accounts = AuthBook.get_queryset().select_related('systemuser') + accounts = Account.get_queryset() if not accounts.first(): return data_map diff --git a/apps/assets/tasks/common.py b/apps/assets/tasks/common.py index a1bd79e11..6ad17b4c3 100644 --- a/apps/assets/tasks/common.py +++ b/apps/assets/tasks/common.py @@ -9,6 +9,7 @@ from assets.models import AuthBook __all__ = ['add_nodes_assets_to_system_users'] +# Todo: 等待优化 @shared_task @tmp_to_root_org() def add_nodes_assets_to_system_users(nodes_keys, system_users): diff --git a/apps/assets/tasks/utils.py b/apps/assets/tasks/utils.py index 93aaa4bfc..a60f1b494 100644 --- a/apps/assets/tasks/utils.py +++ b/apps/assets/tasks/utils.py @@ -25,7 +25,7 @@ def check_asset_can_run_ansible(asset): def check_system_user_can_run_ansible(system_user): - if not system_user.auto_push: + if not system_user.auto_push_account: logger.warn(f'Push system user task skip, auto push not enable: system_user={system_user.name}') return False if not system_user.is_protocol_support_push: diff --git a/apps/audits/const.py b/apps/audits/const.py index 9ff993556..eaed75fc0 100644 --- a/apps/audits/const.py +++ b/apps/audits/const.py @@ -11,7 +11,7 @@ MODELS_NEED_RECORD = ( 'LoginACL', 'LoginAssetACL', 'LoginConfirmSetting', # assets 'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', - 'CommandFilter', 'Platform', 'AuthBook', + 'CommandFilter', 'Platform', 'Account', # applications 'Application', # orgs diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index b6f6e9863..28c740189 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -81,7 +81,6 @@ TERMINAL_HOST_KEY = CONFIG.TERMINAL_HOST_KEY TERMINAL_HEADER_TITLE = CONFIG.TERMINAL_HEADER_TITLE TERMINAL_TELNET_REGEX = CONFIG.TERMINAL_TELNET_REGEX -# Asset user auth external backend, default AuthBook backend BACKEND_ASSET_USER_AUTH_VAULT = False PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index cf215a3fb..c66a34a44 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -785,13 +785,13 @@ msgstr "密码" #: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:206 msgid "SSH private key" -msgstr "SSH密钥" +msgstr "SSH 密钥" #: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56 #: xpack/plugins/change_auth_plan/models/asset.py:126 #: xpack/plugins/change_auth_plan/models/asset.py:202 msgid "SSH public key" -msgstr "SSH公钥" +msgstr "SSH 公钥" #: assets/models/cluster.py:20 msgid "Bandwidth" @@ -2067,7 +2067,7 @@ msgstr "Access key" #: authentication/models.py:41 msgid "Private Token" -msgstr "SSH密钥" +msgstr "SSH 密钥" #: authentication/models.py:50 msgid "Expired" @@ -5468,7 +5468,7 @@ msgstr "原来密码错误" #: users/forms/profile.py:129 msgid "Automatically configure and download the SSH key" -msgstr "自动配置并下载SSH密钥" +msgstr "自动配置并下载 SSH 密钥" #: users/forms/profile.py:131 msgid "ssh public key" @@ -5489,11 +5489,11 @@ msgstr "不能和原来的密钥相同" #: users/forms/profile.py:150 users/serializers/profile.py:100 #: users/serializers/profile.py:183 users/serializers/profile.py:210 msgid "Not a valid ssh public key" -msgstr "SSH密钥不合法" +msgstr "SSH 密钥不合法" #: users/forms/profile.py:161 users/models/user.py:692 msgid "Public key" -msgstr "SSH公钥" +msgstr "SSH 公钥" #: users/models/user.py:558 msgid "Force enable" diff --git a/apps/perms/serializers/system_user_permission.py b/apps/perms/serializers/system_user_permission.py index 3452d7af3..def2516d6 100644 --- a/apps/perms/serializers/system_user_permission.py +++ b/apps/perms/serializers/system_user_permission.py @@ -13,7 +13,7 @@ class SystemUserSerializer(serializers.ModelSerializer): 'id', 'name', 'username', 'protocol', 'login_mode', 'login_mode_display', 'priority', 'username_same_with_user', - 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', + 'auto_push_account', 'cmd_filters', 'sudo', 'shell', 'comment', 'sftp_root', 'date_created', 'created_by' ] ref_name = 'PermedSystemUserSerializer' diff --git a/utils/create_assets_user/bulk_create_user.py b/utils/create_assets_user/bulk_create_user.py index 14cbca820..2897d4f0c 100644 --- a/utils/create_assets_user/bulk_create_user.py +++ b/utils/create_assets_user/bulk_create_user.py @@ -113,7 +113,7 @@ class UserCreation: "username": username, "password": password, "protocol": protocol, - "auto_push": bool(int(auto_push)), + "auto_push_account": bool(int(auto_push)), "login_mode": "auto" } users.append(info) From 0d46834fbf3e235f4bce4d96ae6a4742e2ddb0f5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 15 Jul 2022 18:57:52 +0800 Subject: [PATCH 027/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=9A=82?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0092_auto_20220711_1409.py | 2 +- apps/assets/models/account.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/migrations/0092_auto_20220711_1409.py b/apps/assets/migrations/0092_auto_20220711_1409.py index d6076b6c3..8036cc09f 100644 --- a/apps/assets/migrations/0092_auto_20220711_1409.py +++ b/apps/assets/migrations/0092_auto_20220711_1409.py @@ -70,7 +70,7 @@ class Migration(migrations.Migration): ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), - ('version', models.IntegerField(default=1, verbose_name='Version')), + ('version', models.IntegerField(default=0, verbose_name='Version')), ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')), ], options={ diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 9d503c99b..7660ff682 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -18,7 +18,7 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin): default='ssh', verbose_name=_('Protocol')) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_("Type")) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) - version = models.IntegerField(default=1, verbose_name=_('Version')) + version = models.IntegerField(default=0, verbose_name=_('Version')) history = HistoricalRecords() class Meta: From 008b18eced879182ad05bef9dcfbe0fb4869fd5e Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 17 Jul 2022 13:57:13 +0800 Subject: [PATCH 028/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0096_auto_20220714_1627.py | 2 +- apps/assets/models/user.py | 4 ++-- apps/assets/serializers/system_user.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/assets/migrations/0096_auto_20220714_1627.py b/apps/assets/migrations/0096_auto_20220714_1627.py index 4637128ff..274b2d8ef 100644 --- a/apps/assets/migrations/0096_auto_20220714_1627.py +++ b/apps/assets/migrations/0096_auto_20220714_1627.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='systemuser', - name='auto_create_account', + name='account_template_enabled', field=models.BooleanField(default=False, verbose_name='Auto account if not exist'), ), ] diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index bfa4dd3af..418840e6a 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -108,8 +108,8 @@ class SystemUser(ProtocolMixin, BaseUser): protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) - auto_create_account = models.BooleanField(default=False, verbose_name=_("自动创建账号")) - auto_push_account = models.BooleanField(default=True, verbose_name=_('推送账号到资产')) + account_template_enabled = models.BooleanField(default=False, verbose_name=_("启用账号模版")) + auto_push_account = models.BooleanField(default=True, verbose_name=_('自动推送账号')) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index a572a1736..7bdd13fe1 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -47,7 +47,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): fields_small = fields_mini + fields_write_only + [ 'token', 'ssh_key_fingerprint', 'type', 'type_display', 'protocol', 'is_asset_protocol', - 'auto_create_account', 'login_mode', 'login_mode_display', 'priority', + 'account_template_enabled', 'login_mode', 'login_mode_display', 'priority', 'sudo', 'shell', 'sftp_root', 'home', 'system_groups', 'ad_domain', 'username_same_with_user', 'auto_push_account', 'auto_generate_key', 'su_enabled', 'su_from', From 8b188f020dffebe5d0deac90fff29c9604909a9c Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 17 Jul 2022 14:17:16 +0800 Subject: [PATCH 029/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=A4=87?= =?UTF-8?q?=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/user.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 418840e6a..661f89480 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -106,8 +106,10 @@ class SystemUser(ProtocolMixin, BaseUser): validators=[MinValueValidator(1), MaxValueValidator(100)] ) protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) - login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) + + # Todo: 重构平台后或许这里也得变化 + # 账号模版 account_template_enabled = models.BooleanField(default=False, verbose_name=_("启用账号模版")) auto_push_account = models.BooleanField(default=True, verbose_name=_('自动推送账号')) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) @@ -118,7 +120,9 @@ class SystemUser(ProtocolMixin, BaseUser): home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True) ad_domain = models.CharField(default='', max_length=256) + # linux su 命令 (switch user) + # Todo: 修改为 username, 不必系统用户了 su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) From b961d1f9ee261b2158bfb3835d771725284c2320 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 18 Jul 2022 11:12:21 +0800 Subject: [PATCH 030/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 6 ++++-- apps/assets/models/user.py | 10 ++++++++++ apps/assets/signal_handlers/system_user.py | 6 +++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 7660ff682..3c0588cdf 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -14,8 +14,10 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin): common = 'common', _('Common user') admin = 'admin', _('Admin user') - protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, - default='ssh', verbose_name=_('Protocol')) + protocol = models.CharField( + max_length=16, choices=ProtocolMixin.Protocol.choices, + default='ssh', verbose_name=_('Protocol') + ) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_("Type")) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 661f89480..5affa7367 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -199,6 +199,10 @@ class SystemUser(ProtocolMixin, BaseUser): return self.su_from.assets.add(*tuple(assets_or_ids)) + @classmethod + def create_accounts_with_assets(cls, asset_ids, system_user_ids): + pass + class Meta: ordering = ['name'] unique_together = [('name', 'org_id')] @@ -208,6 +212,12 @@ class SystemUser(ProtocolMixin, BaseUser): ] +class SystemUserAccount(models.Model): + system_user = models.ForeignKey('SystemUser', on_delete=models.CASCADE, related_name='accounts') + account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, related_name='system_users') + date_created = models.DateTimeField(auto_now_add=True) + + # Deprecated: 准备废弃 class AdminUser(BaseUser): """ diff --git a/apps/assets/signal_handlers/system_user.py b/apps/assets/signal_handlers/system_user.py index 64a656c29..e0dfe3ebf 100644 --- a/apps/assets/signal_handlers/system_user.py +++ b/apps/assets/signal_handlers/system_user.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.db.models.signals import ( - post_save, m2m_changed, pre_save, pre_delete, post_delete + post_save, m2m_changed ) from django.dispatch import receiver @@ -32,6 +32,9 @@ def on_system_user_assets_change(instance, action, model, pk_set, **kwargs): logger.debug('No system user found') return + if action != POST_ADD: + return + if model == Asset: system_user_ids = [instance.id] asset_ids = pk_set @@ -39,6 +42,7 @@ def on_system_user_assets_change(instance, action, model, pk_set, **kwargs): system_user_ids = pk_set asset_ids = [instance.id] # todo: Auto create account if need + SystemUser.create_accounts_with_assets(asset_ids, system_user_ids) @receiver(m2m_changed, sender=SystemUser.users.through) From c9becca63394950ff976ea30e140da76c3040f1a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 20 Jul 2022 12:56:41 +0800 Subject: [PATCH 031/488] stash --- apps/assets/api/system_user.py | 24 +++++++++++++++++------- apps/assets/models/user.py | 16 ++++++++++++++++ apps/assets/urls/api_urls.py | 1 + 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index f95303a5e..5113cc30a 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -9,9 +9,8 @@ from common.mixins.api import SuggestionMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics from orgs.utils import tmp_to_root_org -from ..models import SystemUser, CommandFilterRule +from ..models import SystemUser, CommandFilterRule, Account from .. import serializers -from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer from ..tasks import ( push_system_user_to_assets_manual, test_system_user_connectivity_manual, push_system_user_to_assets @@ -21,7 +20,7 @@ logger = get_logger(__file__) __all__ = [ 'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi', 'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView', - 'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi', + 'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi', 'SystemUserAssetAccountApi' ] @@ -77,12 +76,23 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet): return Response(serializer.data) +class SystemUserAssetAccountApi(generics.RetrieveUpdateDestroyAPIView): + model = Account + serializer_class = serializers.AccountSerializer + + def get_object(self): + asset_id = self.kwargs.get('asset_id') + user_id = self.request.query_params.get("user_id") + system_user = super().get_object() + return system_user.get_account(user_id, asset_id) + + class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): """ Get system user auth info """ model = SystemUser - serializer_class = SystemUserWithAuthInfoSerializer + serializer_class = serializers.SystemUserWithAuthInfoSerializer rbac_perms = { 'retrieve': 'assets.view_systemusersecret', 'list': 'assets.view_systemusersecret', @@ -99,7 +109,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): class SystemUserTempAuthInfoApi(generics.CreateAPIView): model = SystemUser permission_classes = (IsValidUser,) - serializer_class = SystemUserTempAuthSerializer + serializer_class = serializers.SystemUserTempAuthSerializer def create(self, request, *args, **kwargs): serializer = super().get_serializer(data=request.data) @@ -120,7 +130,7 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): Get system user with asset auth info """ model = SystemUser - serializer_class = SystemUserWithAuthInfoSerializer + serializer_class = serializers.SystemUserWithAuthInfoSerializer def get_object(self): instance = super().get_object() @@ -136,7 +146,7 @@ class SystemUserAppAuthInfoApi(generics.RetrieveAPIView): Get system user with asset auth info """ model = SystemUser - serializer_class = SystemUserWithAuthInfoSerializer + serializer_class = serializers.SystemUserWithAuthInfoSerializer rbac_perms = { 'retrieve': 'assets.view_systemusersecret', } diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 5affa7367..0f6b51e0f 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator from common.utils import signer +from users.models import User from .base import BaseUser from .asset import Asset @@ -203,6 +204,21 @@ class SystemUser(ProtocolMixin, BaseUser): def create_accounts_with_assets(cls, asset_ids, system_user_ids): pass + def get_manual_account(self, user_id, asset_id): + pass + + def get_auto_account(self, user_id, asset_id): + username = self.username + if self.username_same_with_user: + user = get_object_or_404(User, id=user_id) + username = user.username + + def get_account(self, user_id, asset_id): + if self.login_mode == self.LOGIN_AUTO: + return self.get_manual_account(user_id, asset_id) + else: + return self.get_auto_account(user_id, asset_id) + class Meta: ordering = ['name'] unique_together = [('name', 'org_id')] diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index e8658ec0f..5876c5030 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -49,6 +49,7 @@ urlpatterns = [ path('system-users//assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'), path('system-users//assets//auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), path('system-users//applications//auth-info/', api.SystemUserAppAuthInfoApi.as_view(), name='system-user-app-auth-info'), + path('system-users//assets//account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'), path('system-users//temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'), path('system-users//tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'), path('system-users//cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), From d176ccde4bc596f07624737a5aefd4869d264d7b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 20 Jul 2022 16:52:01 +0800 Subject: [PATCH 032/488] perf: stash --- apps/assets/api/system_user.py | 5 +++-- apps/assets/models/user.py | 17 ++++++++++------- apps/assets/urls/api_urls.py | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 5113cc30a..89d5d909b 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -82,9 +82,10 @@ class SystemUserAssetAccountApi(generics.RetrieveUpdateDestroyAPIView): def get_object(self): asset_id = self.kwargs.get('asset_id') - user_id = self.request.query_params.get("user_id") + user_id = self.kwargs.get("user_id") system_user = super().get_object() - return system_user.get_account(user_id, asset_id) + account = system_user.get_account(user_id, asset_id) + return account class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 0f6b51e0f..9e37b2fee 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -7,6 +7,8 @@ import logging from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator +from django.shortcuts import get_object_or_404 +from django.core.cache import cache from common.utils import signer from users.models import User @@ -205,13 +207,20 @@ class SystemUser(ProtocolMixin, BaseUser): pass def get_manual_account(self, user_id, asset_id): - pass + cache_key = 'manual_account_{}_{}_{}'.format(self.id, user_id, asset_id) + return cache.get(cache_key) + + def create_manual_account(self, user_id, asset_id, account, ttl=300): + cache_key = 'manual_account_{}_{}_{}'.format(self.id, user_id, asset_id) + cache.set(cache_key, account, ttl) def get_auto_account(self, user_id, asset_id): + from .account import Account username = self.username if self.username_same_with_user: user = get_object_or_404(User, id=user_id) username = user.username + return get_object_or_404(Account, asset_id=asset_id, username=username) def get_account(self, user_id, asset_id): if self.login_mode == self.LOGIN_AUTO: @@ -228,12 +237,6 @@ class SystemUser(ProtocolMixin, BaseUser): ] -class SystemUserAccount(models.Model): - system_user = models.ForeignKey('SystemUser', on_delete=models.CASCADE, related_name='accounts') - account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, related_name='system_users') - date_created = models.DateTimeField(auto_now_add=True) - - # Deprecated: 准备废弃 class AdminUser(BaseUser): """ diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 5876c5030..8f2d4c8cf 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -49,7 +49,7 @@ urlpatterns = [ path('system-users//assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'), path('system-users//assets//auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), path('system-users//applications//auth-info/', api.SystemUserAppAuthInfoApi.as_view(), name='system-user-app-auth-info'), - path('system-users//assets//account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'), + path('system-users//assets//users//account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'), path('system-users//temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'), path('system-users//tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'), path('system-users//cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), From 43d3791ddca1f0d447cbea045b1c0d76d0ec2ccf Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 27 Jul 2022 16:51:39 +0800 Subject: [PATCH 033/488] stash --- apps/assets/api/system_user.py | 55 +++++++++++++++++-- .../migrations/0092_auto_20220711_1409.py | 2 +- apps/assets/models/account.py | 8 +-- apps/assets/models/user.py | 2 +- apps/assets/serializers/system_user.py | 6 +- apps/assets/urls/api_urls.py | 4 +- 6 files changed, 61 insertions(+), 16 deletions(-) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 89d5d909b..14c02aa8d 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -2,6 +2,7 @@ from django.shortcuts import get_object_or_404 from rest_framework.response import Response from rest_framework.decorators import action +from rest_framework.viewsets import GenericViewSet from common.utils import get_logger, get_object_or_none from common.permissions import IsValidUser @@ -20,7 +21,8 @@ logger = get_logger(__file__) __all__ = [ 'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi', 'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView', - 'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi', 'SystemUserAssetAccountApi' + 'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi', 'SystemUserAssetAccountApi', + 'SystemUserAssetAccountSecretApi', ] @@ -76,24 +78,61 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet): return Response(serializer.data) -class SystemUserAssetAccountApi(generics.RetrieveUpdateDestroyAPIView): +class SystemUserAccountViewSet(GenericViewSet): + model = Account + serializer_classes = { + 'default': serializers.AccountSerializer, + 'account_secret': serializers.AccountSecretSerializer, + } + + def get_object(self): + system_user_id = self.kwargs.get('pk') + asset_id = self.kwargs.get('asset_id') + user_id = self.kwargs.get("user_id") + system_user = SystemUser.objects.get(id=system_user_id) + account = system_user.get_account(user_id, asset_id) + return account + + @action(methods=['get'], detail=False, url_path='account') + def account(self, request, *args, **kwargs): + pass + + @action(methods=['get'], detail=False, url_path='account-secret') + def account_secret(self): + pass + + @action(methods=['put'], detail=False, url_path='manual-account') + def manual_account(self, request, *args, **kwargs): + pass + + +class SystemUserAssetAccountApi(generics.RetrieveAPIView): model = Account serializer_class = serializers.AccountSerializer def get_object(self): + system_user_id = self.kwargs.get('pk') asset_id = self.kwargs.get('asset_id') user_id = self.kwargs.get("user_id") - system_user = super().get_object() + system_user = SystemUser.objects.get(id=system_user_id) account = system_user.get_account(user_id, asset_id) return account +class SystemUserAssetAccountSecretApi(SystemUserAssetAccountApi): + model = Account + serializer_class = serializers.AccountSecretSerializer + rbac_perms = { + 'retrieve': 'assets.view_accountsecret' + } + + class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): """ Get system user auth info """ model = SystemUser - serializer_class = serializers.SystemUserWithAuthInfoSerializer + serializer_class = serializers.AccountSerializer rbac_perms = { 'retrieve': 'assets.view_systemusersecret', 'list': 'assets.view_systemusersecret', @@ -101,6 +140,14 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): 'destroy': 'assets.change_systemuser', } + def get_object(self): + system_user_id = self.kwargs.get('pk') + asset_id = self.kwargs.get('asset_id') + user_id = self.kwargs.get("user_id") + system_user = SystemUser.objects.get(id=system_user_id) + account = system_user.get_account(user_id, asset_id) + return account + def destroy(self, request, *args, **kwargs): instance = self.get_object() instance.clear_auth() diff --git a/apps/assets/migrations/0092_auto_20220711_1409.py b/apps/assets/migrations/0092_auto_20220711_1409.py index 8036cc09f..efcb59e9d 100644 --- a/apps/assets/migrations/0092_auto_20220711_1409.py +++ b/apps/assets/migrations/0092_auto_20220711_1409.py @@ -75,7 +75,7 @@ class Migration(migrations.Migration): ], options={ 'verbose_name': 'Account', - 'permissions': [('view_assetaccountsecret', 'Can view asset account secret'), ('change_assetaccountsecret', 'Can change asset account secret'), ('view_assethistoryaccount', 'Can view asset history account'), ('view_assethistoryaccountsecret', 'Can view asset history account secret')], + 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], 'unique_together': {('username', 'asset')}, }, bases=(models.Model, assets.models.base.AuthMixin, assets.models.user.ProtocolMixin), diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 3c0588cdf..eb651965b 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -27,10 +27,10 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin): verbose_name = _('Account') unique_together = [('username', 'asset')] permissions = [ - ('view_assetaccountsecret', _('Can view asset account secret')), - ('change_assetaccountsecret', _('Can change asset account secret')), - ('view_assethistoryaccount', _('Can view asset history account')), - ('view_assethistoryaccountsecret', _('Can view asset history account secret')), + ('view_accountsecret', _('Can view asset account secret')), + ('change_accountsecret', _('Can change asset account secret')), + ('view_historyaccount', _('Can view asset history account')), + ('view_historyaccountsecret', _('Can view asset history account secret')), ] def __str__(self): diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 9e37b2fee..9770b2266 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -223,7 +223,7 @@ class SystemUser(ProtocolMixin, BaseUser): return get_object_or_404(Account, asset_id=asset_id, username=username) def get_account(self, user_id, asset_id): - if self.login_mode == self.LOGIN_AUTO: + if self.login_mode == self.LOGIN_MANUAL: return self.get_manual_account(user_id, asset_id) else: return self.get_auto_account(user_id, asset_id) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 7bdd13fe1..7d15041ec 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -298,10 +298,10 @@ class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializ asset_display = serializers.ReadOnlyField(label=_('Asset hostname')) class Meta: - model = SystemUser + model = SystemUser.assets.through fields = [ - "id", "asset", "asset_display", 'systemuser', 'systemuser_display', - "connectivity", 'date_verified', 'org_id' + "id", "asset", "asset_display", + "systemuser", "systemuser_display", ] use_model_bulk_create = True model_bulk_create_kwargs = { diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 8f2d4c8cf..1c405a82a 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -45,12 +45,10 @@ urlpatterns = [ path('assets//perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'), path('assets//perm-user-groups//permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'), - path('system-users//auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'), path('system-users//assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'), - path('system-users//assets//auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), path('system-users//applications//auth-info/', api.SystemUserAppAuthInfoApi.as_view(), name='system-user-app-auth-info'), path('system-users//assets//users//account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'), - path('system-users//temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'), + path('system-users//assets//users//account-secret/', api.SystemUserAssetAccountSecretApi.as_view(), name='system-user-asset-account-secret'), path('system-users//tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'), path('system-users//cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), path('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'), From fb0fb71ea3f5bad16f4471ba38d79c245d69a0c2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 28 Jul 2022 18:50:58 +0800 Subject: [PATCH 034/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/__init__.py | 2 - apps/assets/api/admin_user.py | 30 --- apps/assets/api/asset.py | 1 - apps/assets/api/system_user.py | 139 +--------- apps/assets/api/system_user_relation.py | 138 ---------- ...py => 0094_alter_systemuser_assets.py.bak} | 0 .../migrations/0094_auto_20220728_1125.py | 89 +++++++ ...1746.py => 0095_auto_20220713_1746.py.bak} | 0 ...1627.py => 0096_auto_20220714_1627.py.bak} | 0 apps/assets/models/__init__.py | 3 +- apps/assets/models/cluster.py | 40 --- apps/assets/models/protocol.py | 65 +++++ apps/assets/models/user.py | 199 +------------- apps/assets/serializers/__init__.py | 1 - apps/assets/serializers/admin_user.py | 26 -- apps/assets/serializers/asset.py | 14 +- apps/assets/serializers/system_user.py | 244 +----------------- apps/assets/signal_handlers/__init__.py | 3 +- .../{authbook.py => account.py} | 0 apps/assets/signal_handlers/common.py | 0 .../{system_user.py => system_user.py.bak} | 0 apps/assets/urls/api_urls.py | 7 - apps/audits/signal_handlers.py | 10 - .../api/application/user_permission/common.py | 7 +- .../api/asset/asset_permission_relation.py | 24 +- .../migrations/0029_auto_20220728_1728.py | 28 ++ apps/perms/models/asset_permission.py | 13 +- apps/perms/serializers/asset/permission.py | 21 +- .../serializers/asset/permission_relation.py | 11 - .../perms/signal_handlers/asset_permission.py | 138 +--------- apps/perms/urls/asset_permission.py | 1 - apps/perms/utils/asset/permission.py | 23 +- .../migrations/0018_auto_20220728_1125.py | 23 ++ utils/generate_fake_data/resources/perms.py | 7 - 34 files changed, 244 insertions(+), 1063 deletions(-) delete mode 100644 apps/assets/api/admin_user.py delete mode 100644 apps/assets/api/system_user_relation.py rename apps/assets/migrations/{0094_alter_systemuser_assets.py => 0094_alter_systemuser_assets.py.bak} (100%) create mode 100644 apps/assets/migrations/0094_auto_20220728_1125.py rename apps/assets/migrations/{0095_auto_20220713_1746.py => 0095_auto_20220713_1746.py.bak} (100%) rename apps/assets/migrations/{0096_auto_20220714_1627.py => 0096_auto_20220714_1627.py.bak} (100%) delete mode 100644 apps/assets/models/cluster.py create mode 100644 apps/assets/models/protocol.py delete mode 100644 apps/assets/serializers/admin_user.py rename apps/assets/signal_handlers/{authbook.py => account.py} (100%) delete mode 100644 apps/assets/signal_handlers/common.py rename apps/assets/signal_handlers/{system_user.py => system_user.py.bak} (100%) create mode 100644 apps/perms/migrations/0029_auto_20220728_1728.py create mode 100644 apps/tickets/migrations/0018_auto_20220728_1125.py diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 3e95f59ab..e8b06b537 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -1,9 +1,7 @@ from .mixin import * -from .admin_user import * from .asset import * from .label import * from .system_user import * -from .system_user_relation import * from .accounts import * from .node import * from .domain import * diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py deleted file mode 100644 index 1192599b9..000000000 --- a/apps/assets/api/admin_user.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.db.models import Count - -from orgs.mixins.api import OrgBulkModelViewSet -from common.utils import get_logger -from ..models import SystemUser -from .. import serializers -from rbac.permissions import RBACPermission - - -logger = get_logger(__file__) -__all__ = ['AdminUserViewSet'] - - -# 兼容一下老的 api -class AdminUserViewSet(OrgBulkModelViewSet): - """ - Admin user api set, for add,delete,update,list,retrieve resource - """ - model = SystemUser - filterset_fields = ("name", "username") - search_fields = filterset_fields - serializer_class = serializers.AdminUserSerializer - permission_classes = (RBACPermission,) - ordering_fields = ('name',) - ordering = ('name', ) - - def get_queryset(self): - queryset = super().get_queryset().filter(type=SystemUser.Type.admin) - queryset = queryset.annotate(assets_amount=Count('assets')) - return queryset diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 3f4b7a209..a930bfd41 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -42,7 +42,6 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) filterset_fields = { 'hostname': ['exact'], 'ip': ['exact'], - 'system_users__id': ['exact'], 'platform__base': ['exact'], 'is_active': ['exact'], 'protocols': ['exact', 'icontains'] diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 14c02aa8d..7ae682839 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -5,23 +5,17 @@ from rest_framework.decorators import action from rest_framework.viewsets import GenericViewSet from common.utils import get_logger, get_object_or_none -from common.permissions import IsValidUser from common.mixins.api import SuggestionMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics -from orgs.utils import tmp_to_root_org from ..models import SystemUser, CommandFilterRule, Account from .. import serializers -from ..tasks import ( - push_system_user_to_assets_manual, test_system_user_connectivity_manual, - push_system_user_to_assets -) logger = get_logger(__file__) __all__ = [ - 'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi', - 'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView', - 'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi', 'SystemUserAssetAccountApi', + 'SystemUserViewSet', 'SystemUserAuthInfoApi', + 'SystemUserCommandFilterRuleListApi', + 'SystemUserAssetAccountApi', 'SystemUserAssetAccountSecretApi', ] @@ -35,7 +29,6 @@ class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet): 'name': ['exact'], 'username': ['exact'], 'protocol': ['exact', 'in'], - 'type': ['exact', 'in'], } search_fields = filterset_fields serializer_class = serializers.SystemUserSerializer @@ -154,116 +147,6 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): return Response(status=204) -class SystemUserTempAuthInfoApi(generics.CreateAPIView): - model = SystemUser - permission_classes = (IsValidUser,) - serializer_class = serializers.SystemUserTempAuthSerializer - - def create(self, request, *args, **kwargs): - serializer = super().get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - - pk = kwargs.get('pk') - data = serializer.validated_data - asset_or_app_id = data.get('instance_id') - - with tmp_to_root_org(): - instance = get_object_or_404(SystemUser, pk=pk) - instance.set_temp_auth(asset_or_app_id, self.request.user.id, data) - return Response(serializer.data, status=201) - - -class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): - """ - Get system user with asset auth info - """ - model = SystemUser - serializer_class = serializers.SystemUserWithAuthInfoSerializer - - def get_object(self): - instance = super().get_object() - asset_id = self.kwargs.get('asset_id') - user_id = self.request.query_params.get("user_id") - username = self.request.query_params.get("username") - instance.load_asset_more_auth(asset_id, username, user_id) - return instance - - -class SystemUserAppAuthInfoApi(generics.RetrieveAPIView): - """ - Get system user with asset auth info - """ - model = SystemUser - serializer_class = serializers.SystemUserWithAuthInfoSerializer - rbac_perms = { - 'retrieve': 'assets.view_systemusersecret', - } - - def get_object(self): - instance = super().get_object() - app_id = self.kwargs.get('app_id') - user_id = self.request.query_params.get("user_id") - username = self.request.query_params.get("username") - instance.load_app_more_auth(app_id, username, user_id) - return instance - - -class SystemUserTaskApi(generics.CreateAPIView): - serializer_class = serializers.SystemUserTaskSerializer - - def do_push(self, system_user, asset_ids=None): - if asset_ids is None: - task = push_system_user_to_assets_manual.delay(system_user) - else: - username = self.request.query_params.get('username') - task = push_system_user_to_assets.delay( - system_user.id, asset_ids, username=username - ) - return task - - @staticmethod - def do_test(system_user, asset_ids): - task = test_system_user_connectivity_manual.delay(system_user, asset_ids) - return task - - def get_object(self): - pk = self.kwargs.get('pk') - return get_object_or_404(SystemUser, pk=pk) - - def check_permissions(self, request): - action = request.data.get('action') - action_perm_require = { - 'push': 'assets.push_assetsystemuser', - 'test': 'assets.test_assetconnectivity' - } - perm_required = action_perm_require.get(action) - has = self.request.user.has_perm(perm_required) - - if not has: - self.permission_denied(request) - - def perform_create(self, serializer): - action = serializer.validated_data["action"] - asset = serializer.validated_data.get('asset') - - if asset: - assets = [asset] - else: - assets = serializer.validated_data.get('assets') or [] - - asset_ids = [asset.id for asset in assets] - asset_ids = asset_ids if asset_ids else None - - system_user = self.get_object() - if action == 'push': - task = self.do_push(system_user, asset_ids) - else: - task = self.do_test(system_user, asset_ids) - data = getattr(serializer, '_data', {}) - data["task"] = task.id - setattr(serializer, '_data', data) - - class SystemUserCommandFilterRuleListApi(generics.ListAPIView): rbac_perms = { 'list': 'assets.view_commandfilterule' @@ -291,19 +174,3 @@ class SystemUserCommandFilterRuleListApi(generics.ListAPIView): ) return rules - -class SystemUserAssetsListView(generics.ListAPIView): - serializer_class = serializers.AssetSimpleSerializer - filterset_fields = ("hostname", "ip") - search_fields = filterset_fields - rbac_perms = { - 'list': 'assets.view_asset' - } - - def get_object(self): - pk = self.kwargs.get('pk') - return get_object_or_404(SystemUser, pk=pk) - - def get_queryset(self): - system_user = self.get_object() - return system_user.get_all_assets() diff --git a/apps/assets/api/system_user_relation.py b/apps/assets/api/system_user_relation.py deleted file mode 100644 index 36c16a09b..000000000 --- a/apps/assets/api/system_user_relation.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -# -from collections import defaultdict -from django.db.models import F, Value, Model -from django.db.models.signals import m2m_changed -from django.db.models.functions import Concat - -from common.utils import get_logger -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.utils import current_org -from .. import models, serializers - -__all__ = [ - 'SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet', - 'SystemUserUserRelationViewSet', 'BaseRelationViewSet', -] - -logger = get_logger(__name__) - - -class RelationMixin: - model: Model - - def get_queryset(self): - queryset = self.model.objects.all() - if not current_org.is_root(): - org_id = current_org.org_id() - queryset = queryset.filter(systemuser__org_id=org_id) - - queryset = queryset.annotate(systemuser_display=Concat( - F('systemuser__name'), Value('('), - F('systemuser__username'), Value(')') - )) - return queryset - - def send_post_add_signal(self, instance): - if not isinstance(instance, list): - instance = [instance] - - system_users_objects_map = defaultdict(list) - model, object_field = self.get_objects_attr() - - for i in instance: - _id = getattr(i, object_field).id - system_users_objects_map[i.systemuser].append(_id) - - sender = self.get_sender() - for system_user, object_ids in system_users_objects_map.items(): - logger.debug('System user relation changed, send m2m_changed signals') - m2m_changed.send( - sender=sender, instance=system_user, action='post_add', - reverse=False, model=model, pk_set=set(object_ids) - ) - - def get_sender(self): - return self.model - - def get_objects_attr(self): - return models.Asset, 'asset' - - def perform_create(self, serializer): - instance = serializer.save() - self.send_post_add_signal(instance) - - -class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet): - perm_model = models.SystemUser - - -class SystemUserAssetRelationViewSet(BaseRelationViewSet): - serializer_class = serializers.SystemUserAssetRelationSerializer - model = models.SystemUser.assets.through - filterset_fields = [ - 'id', 'asset', 'systemuser', - ] - search_fields = [ - "id", "asset__hostname", "asset__ip", - "systemuser__name", "systemuser__username", - ] - - def get_objects_attr(self): - return models.Asset, 'asset' - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate( - asset_display=Concat( - F('asset__hostname'), Value('('), - F('asset__ip'), Value(')') - ) - ) - return queryset - - -class SystemUserNodeRelationViewSet(BaseRelationViewSet): - serializer_class = serializers.SystemUserNodeRelationSerializer - model = models.SystemUser.nodes.through - filterset_fields = [ - 'id', 'node', 'systemuser', - ] - search_fields = [ - "node__value", "systemuser__name", "systemuser__username" - ] - - def get_objects_attr(self): - return models.Node, 'node' - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset \ - .annotate(node_key=F('node__key')) - return queryset - - -class SystemUserUserRelationViewSet(BaseRelationViewSet): - serializer_class = serializers.SystemUserUserRelationSerializer - model = models.SystemUser.users.through - filterset_fields = [ - 'id', 'user', 'systemuser', - ] - search_fields = [ - "user__username", "user__name", - "systemuser__name", "systemuser__username", - ] - - def get_objects_attr(self): - from users.models import User - return User, 'user' - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate( - user_display=Concat( - F('user__name'), Value('('), - F('user__username'), Value(')') - ) - ) - return queryset diff --git a/apps/assets/migrations/0094_alter_systemuser_assets.py b/apps/assets/migrations/0094_alter_systemuser_assets.py.bak similarity index 100% rename from apps/assets/migrations/0094_alter_systemuser_assets.py rename to apps/assets/migrations/0094_alter_systemuser_assets.py.bak diff --git a/apps/assets/migrations/0094_auto_20220728_1125.py b/apps/assets/migrations/0094_auto_20220728_1125.py new file mode 100644 index 000000000..adc477c1c --- /dev/null +++ b/apps/assets/migrations/0094_auto_20220728_1125.py @@ -0,0 +1,89 @@ +# Generated by Django 3.2.14 on 2022-07-28 03:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0093_auto_20220711_1413'), + ] + + operations = [ + migrations.RemoveField( + model_name='cluster', + name='admin_user', + ), + migrations.RemoveField( + model_name='systemuser', + name='ad_domain', + ), + migrations.RemoveField( + model_name='systemuser', + name='assets', + ), + migrations.RemoveField( + model_name='systemuser', + name='auto_push', + ), + migrations.RemoveField( + model_name='systemuser', + name='groups', + ), + migrations.RemoveField( + model_name='systemuser', + name='home', + ), + migrations.RemoveField( + model_name='systemuser', + name='nodes', + ), + migrations.RemoveField( + model_name='systemuser', + name='priority', + ), + migrations.RemoveField( + model_name='systemuser', + name='sftp_root', + ), + migrations.RemoveField( + model_name='systemuser', + name='shell', + ), + migrations.RemoveField( + model_name='systemuser', + name='sudo', + ), + migrations.RemoveField( + model_name='systemuser', + name='system_groups', + ), + migrations.RemoveField( + model_name='systemuser', + name='token', + ), + migrations.RemoveField( + model_name='systemuser', + name='type', + ), + migrations.RemoveField( + model_name='systemuser', + name='users', + ), + migrations.AlterField( + model_name='historicalaccount', + name='version', + field=models.IntegerField(default=0, verbose_name='Version'), + ), + migrations.AlterField( + model_name='systemuser', + name='login_mode', + field=models.CharField(choices=[('auto', '使用账号'), ('manual', 'Manually input')], default='auto', max_length=10, verbose_name='Login mode'), + ), + migrations.DeleteModel( + name='AdminUser', + ), + migrations.DeleteModel( + name='Cluster', + ), + ] diff --git a/apps/assets/migrations/0095_auto_20220713_1746.py b/apps/assets/migrations/0095_auto_20220713_1746.py.bak similarity index 100% rename from apps/assets/migrations/0095_auto_20220713_1746.py rename to apps/assets/migrations/0095_auto_20220713_1746.py.bak diff --git a/apps/assets/migrations/0096_auto_20220714_1627.py b/apps/assets/migrations/0096_auto_20220714_1627.py.bak similarity index 100% rename from apps/assets/migrations/0096_auto_20220714_1627.py rename to apps/assets/migrations/0096_auto_20220714_1627.py.bak diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 9d1df04a1..da35178d1 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -2,7 +2,6 @@ from .base import * from .asset import * from .label import Label from .user import * -from .cluster import * from .group import * from .domain import * from .node import * @@ -12,5 +11,5 @@ from .utils import * from .authbook import * from .gathered_user import * from .favorite_asset import * -from .backup import * from .account import * +from .backup import * diff --git a/apps/assets/models/cluster.py b/apps/assets/models/cluster.py deleted file mode 100644 index 6c0692ab9..000000000 --- a/apps/assets/models/cluster.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# - -import logging -import uuid - -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -__all__ = ['Cluster'] -logger = logging.getLogger(__name__) - - -class Cluster(models.Model): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=32, verbose_name=_('Name')) - admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user")) - bandwidth = models.CharField(max_length=32, blank=True, verbose_name=_('Bandwidth')) - contact = models.CharField(max_length=128, blank=True, verbose_name=_('Contact')) - phone = models.CharField(max_length=32, blank=True, verbose_name=_('Phone')) - address = models.CharField(max_length=128, blank=True, verbose_name=_("Address")) - intranet = models.TextField(blank=True, verbose_name=_('Intranet')) - extranet = models.TextField(blank=True, verbose_name=_('Extranet')) - date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) - operator = models.CharField(max_length=32, blank=True, verbose_name=_('Operator')) - created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by')) - comment = models.TextField(blank=True, verbose_name=_('Comment')) - - def __str__(self): - return self.name - - @classmethod - def initial(cls): - return cls.objects.get_or_create(name=_('Default'), created_by=_('System'), comment=_('Default Cluster'))[0] - - class Meta: - ordering = ['name'] - verbose_name = _("Cluster") diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py new file mode 100644 index 000000000..cd27e7b19 --- /dev/null +++ b/apps/assets/models/protocol.py @@ -0,0 +1,65 @@ +from django.db import models + + +class ProtocolMixin: + protocol: str + + class Protocol(models.TextChoices): + ssh = 'ssh', 'SSH' + rdp = 'rdp', 'RDP' + telnet = 'telnet', 'Telnet' + vnc = 'vnc', 'VNC' + mysql = 'mysql', 'MySQL' + oracle = 'oracle', 'Oracle' + mariadb = 'mariadb', 'MariaDB' + postgresql = 'postgresql', 'PostgreSQL' + sqlserver = 'sqlserver', 'SQLServer' + redis = 'redis', 'Redis' + mongodb = 'mongodb', 'MongoDB' + k8s = 'k8s', 'K8S' + + SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp] + + ASSET_CATEGORY_PROTOCOLS = [ + Protocol.ssh, Protocol.rdp, Protocol.telnet, Protocol.vnc + ] + APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [ + Protocol.rdp + ] + APPLICATION_CATEGORY_DB_PROTOCOLS = [ + Protocol.mysql, Protocol.mariadb, Protocol.oracle, + Protocol.postgresql, Protocol.sqlserver, + Protocol.redis, Protocol.mongodb + ] + APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [ + Protocol.k8s + ] + APPLICATION_CATEGORY_PROTOCOLS = [ + *APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS, + *APPLICATION_CATEGORY_DB_PROTOCOLS, + *APPLICATION_CATEGORY_CLOUD_PROTOCOLS + ] + + @property + def is_protocol_support_push(self): + return self.protocol in self.SUPPORT_PUSH_PROTOCOLS + + @classmethod + def get_protocol_by_application_type(cls, app_type): + from applications.const import AppType + if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS: + protocol = app_type + elif app_type in AppType.remote_app_types(): + protocol = cls.Protocol.rdp + else: + protocol = None + return protocol + + @property + def can_perm_to_asset(self): + return self.protocol in self.ASSET_CATEGORY_PROTOCOLS + + @property + def is_asset_protocol(self): + return self.protocol in self.ASSET_CATEGORY_PROTOCOLS + diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 9770b2266..a5be417a2 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -6,83 +6,17 @@ import logging from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.core.validators import MinValueValidator, MaxValueValidator from django.shortcuts import get_object_or_404 from django.core.cache import cache -from common.utils import signer -from users.models import User from .base import BaseUser -from .asset import Asset +from .protocol import ProtocolMixin -__all__ = ['AdminUser', 'SystemUser', 'ProtocolMixin'] +__all__ = ['SystemUser'] logger = logging.getLogger(__name__) -class ProtocolMixin: - protocol: str - - class Protocol(models.TextChoices): - ssh = 'ssh', 'SSH' - rdp = 'rdp', 'RDP' - telnet = 'telnet', 'Telnet' - vnc = 'vnc', 'VNC' - mysql = 'mysql', 'MySQL' - oracle = 'oracle', 'Oracle' - mariadb = 'mariadb', 'MariaDB' - postgresql = 'postgresql', 'PostgreSQL' - sqlserver = 'sqlserver', 'SQLServer' - redis = 'redis', 'Redis' - mongodb = 'mongodb', 'MongoDB' - k8s = 'k8s', 'K8S' - - SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp] - - ASSET_CATEGORY_PROTOCOLS = [ - Protocol.ssh, Protocol.rdp, Protocol.telnet, Protocol.vnc - ] - APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [ - Protocol.rdp - ] - APPLICATION_CATEGORY_DB_PROTOCOLS = [ - Protocol.mysql, Protocol.mariadb, Protocol.oracle, - Protocol.postgresql, Protocol.sqlserver, - Protocol.redis, Protocol.mongodb - ] - APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [ - Protocol.k8s - ] - APPLICATION_CATEGORY_PROTOCOLS = [ - *APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS, - *APPLICATION_CATEGORY_DB_PROTOCOLS, - *APPLICATION_CATEGORY_CLOUD_PROTOCOLS - ] - - @property - def is_protocol_support_push(self): - return self.protocol in self.SUPPORT_PUSH_PROTOCOLS - - @classmethod - def get_protocol_by_application_type(cls, app_type): - from applications.const import AppType - if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS: - protocol = app_type - elif app_type in AppType.remote_app_types(): - protocol = cls.Protocol.rdp - else: - protocol = None - return protocol - - @property - def can_perm_to_asset(self): - return self.protocol in self.ASSET_CATEGORY_PROTOCOLS - - @property - def is_asset_protocol(self): - return self.protocol in self.ASSET_CATEGORY_PROTOCOLS - - class SystemUser(ProtocolMixin, BaseUser): LOGIN_AUTO = 'auto' LOGIN_MANUAL = 'manual' @@ -91,39 +25,10 @@ class SystemUser(ProtocolMixin, BaseUser): (LOGIN_MANUAL, _('Manually input')) ) - class Type(models.TextChoices): - common = 'common', _('Common user') - admin = 'admin', _('Admin user') - username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user")) - nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) - assets = models.ManyToManyField( - 'assets.Asset', blank=True, verbose_name=_("Assets"), - related_name='system_users' - ) - users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users")) - groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) - priority = models.IntegerField( - default=81, verbose_name=_("Priority"), - help_text=_("1-100, the lower the value will be match first"), - validators=[MinValueValidator(1), MaxValueValidator(100)] - ) protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) - # Todo: 重构平台后或许这里也得变化 - # 账号模版 - account_template_enabled = models.BooleanField(default=False, verbose_name=_("启用账号模版")) - auto_push_account = models.BooleanField(default=True, verbose_name=_('自动推送账号')) - type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) - sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) - shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) - sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) - token = models.TextField(default='', verbose_name=_('Token')) - home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) - system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True) - ad_domain = models.CharField(default='', max_length=256) - # linux su 命令 (switch user) # Todo: 修改为 username, 不必系统用户了 su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) @@ -135,32 +40,6 @@ class SystemUser(ProtocolMixin, BaseUser): username = '*' return '{0.name}({1})'.format(self, username) - @property - def nodes_amount(self): - return self.nodes.all().count() - - @property - def login_mode_display(self): - return self.get_login_mode_display() - - def is_need_push(self): - if self.auto_push_account and self.is_protocol_support_push: - return True - else: - return False - - @property - def is_admin_user(self): - return self.type == self.Type.admin - - @property - def is_need_cmd_filter(self): - return self.protocol not in [self.Protocol.rdp, self.Protocol.vnc] - - @property - def is_need_test_asset_connective(self): - return self.protocol in self.ASSET_CATEGORY_PROTOCOLS - @property def cmd_filter_rules(self): from .cmd_filter import CommandFilterRule @@ -178,30 +57,6 @@ class SystemUser(ProtocolMixin, BaseUser): return False, matched_cmd return True, None - def get_all_assets(self): - from assets.models import Node - nodes_keys = self.nodes.all().values_list('key', flat=True) - asset_ids = set(self.assets.all().values_list('id', flat=True)) - nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys) - asset_ids.update(nodes_asset_ids) - assets = Asset.objects.filter(id__in=asset_ids) - return assets - - def add_related_assets(self, assets_or_ids): - self.assets.add(*tuple(assets_or_ids)) - self.add_related_assets_to_su_from_if_need(assets_or_ids) - - def add_related_assets_to_su_from_if_need(self, assets_or_ids): - if self.protocol not in [self.Protocol.ssh.value]: - return - if not self.su_enabled: - return - if not self.su_from: - return - if self.su_from.protocol != self.protocol: - return - self.su_from.assets.add(*tuple(assets_or_ids)) - @classmethod def create_accounts_with_assets(cls, asset_ids, system_user_ids): pass @@ -216,6 +71,7 @@ class SystemUser(ProtocolMixin, BaseUser): def get_auto_account(self, user_id, asset_id): from .account import Account + from users.models import User username = self.username if self.username_same_with_user: user = get_object_or_404(User, id=user_id) @@ -235,52 +91,3 @@ class SystemUser(ProtocolMixin, BaseUser): permissions = [ ('match_systemuser', _('Can match system user')), ] - - -# Deprecated: 准备废弃 -class AdminUser(BaseUser): - """ - A privileged user that ansible can use it to push system user and so on - """ - BECOME_METHOD_CHOICES = ( - ('sudo', 'sudo'), - ('su', 'su'), - ) - become = models.BooleanField(default=True) - become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) - become_user = models.CharField(default='root', max_length=64) - _become_pass = models.CharField(default='', blank=True, max_length=128) - CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}' - _prefer = "admin_user" - - def __str__(self): - return self.name - - @property - def become_pass(self): - password = signer.unsign(self._become_pass) - if password: - return password - else: - return "" - - @become_pass.setter - def become_pass(self, password): - self._become_pass = signer.sign(password) - - @property - def become_info(self): - if self.become: - info = { - "method": self.become_method, - "user": self.become_user, - "pass": self.become_pass, - } - else: - info = None - return info - - class Meta: - ordering = ['name'] - unique_together = [('name', 'org_id')] - verbose_name = _("Admin user") diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 3f1222fc5..5c684652c 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -2,7 +2,6 @@ # from .asset import * -from .admin_user import * from .label import * from .system_user import * from .node import * diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py deleted file mode 100644 index b6ab18af3..000000000 --- a/apps/assets/serializers/admin_user.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -from ..models import SystemUser -from .system_user import SystemUserSerializer as SuS - - -class AdminUserSerializer(SuS): - """ - 管理用户 - """ - - class Meta(SuS.Meta): - fields = SuS.Meta.fields_mini + \ - SuS.Meta.fields_write_only + \ - SuS.Meta.fields_m2m + \ - [ - 'type', 'protocol', "priority", 'sftp_root', 'ssh_key_fingerprint', - 'su_enabled', 'su_from', - 'date_created', 'date_updated', 'comment', 'created_by', - ] - - def validate_type(self, val): - return SystemUser.Type.admin - - def validate_protocol(self, val): - return 'ssh' diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index da1a0a0fc..40e845e15 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -91,7 +91,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'cpu_info', 'hardware_info', ] fields_fk = [ - 'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display' + 'domain', 'domain_display', 'platform', ] fields_m2m = [ 'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts' @@ -114,19 +114,10 @@ class AssetSerializer(BulkOrgResourceModelSerializer): self.accounts_data = data.pop('accounts', []) super().__init__(*args, **kwargs) - def get_fields(self): - fields = super().get_fields() - - admin_user_field = fields.get('admin_user') - # 因为 mixin 中对 fields 有处理,可能不需要返回 admin_user - if admin_user_field: - admin_user_field.queryset = SystemUser.objects.filter(type=SystemUser.Type.admin) - return fields - @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('domain', 'platform', 'admin_user') + queryset = queryset.prefetch_related('domain', 'platform') queryset = queryset.prefetch_related('nodes', 'labels') return queryset @@ -162,7 +153,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer): def add_accounts(instance, accounts_data): for data in accounts_data: data['asset'] = instance.id - print("Data: ", accounts_data) serializer = AccountSerializer(data=accounts_data, many=True) try: serializer.is_valid(raise_exception=True) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 7d15041ec..dc1133e73 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -1,97 +1,38 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from django.db.models import Count -from common.mixins.serializers import BulkSerializerMixin -from common.utils import ssh_pubkey_gen -from common.drf.fields import EncryptedField -from common.drf.serializers import SecretReadableMixin from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from ..models import SystemUser, Asset -from .utils import validate_password_for_ansible -from .base import AuthSerializerMixin +from ..models import SystemUser __all__ = [ 'SystemUserSerializer', 'MiniSystemUserSerializer', - 'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer', - 'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer', - 'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer', - 'SystemUserTempAuthSerializer', 'RelationMixin', + 'SystemUserSimpleSerializer', ] -class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +class SystemUserSerializer(BulkOrgResourceModelSerializer): """ 系统用户 """ - password = EncryptedField( - label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, - trim_whitespace=False, validators=[validate_password_for_ansible], - write_only=True - ) - auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True) - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) - ssh_key_fingerprint = serializers.ReadOnlyField(label=_('SSH key fingerprint')) - token = EncryptedField( - label=_('Token'), required=False, write_only=True, style={'base_template': 'textarea.html'} - ) - applications_amount = serializers.IntegerField( - source='apps_amount', read_only=True, label=_('Apps amount') - ) class Meta: model = SystemUser - fields_mini = ['id', 'name', 'username'] - fields_write_only = ['password', 'public_key', 'private_key', 'passphrase'] - fields_small = fields_mini + fields_write_only + [ - 'token', 'ssh_key_fingerprint', - 'type', 'type_display', 'protocol', 'is_asset_protocol', - 'account_template_enabled', 'login_mode', 'login_mode_display', 'priority', - 'sudo', 'shell', 'sftp_root', 'home', 'system_groups', 'ad_domain', - 'username_same_with_user', 'auto_push_account', 'auto_generate_key', - 'su_enabled', 'su_from', - 'date_created', 'date_updated', 'comment', 'created_by', + fields_mini = ['id', 'name', 'username', 'protocol'] + fields_small = fields_mini + [ + 'login_mode', 'su_enabled', 'su_from', + 'date_created', 'date_updated', 'comment', + 'created_by', ] - fields_m2m = ['cmd_filters', 'assets_amount', 'applications_amount', 'nodes'] - fields = fields_small + fields_m2m + fields = fields_small extra_kwargs = { 'cmd_filters': {"required": False, 'label': _('Command filter')}, - 'public_key': {"write_only": True}, - 'private_key': {"write_only": True}, - 'nodes_amount': {'label': _('Nodes amount')}, - 'assets_amount': {'label': _('Assets amount')}, 'login_mode_display': {'label': _('Login mode display')}, 'created_by': {'read_only': True}, 'ad_domain': {'required': False, 'allow_blank': True, 'label': _('Ad domain')}, - 'is_asset_protocol': {'label': _('Is asset protocol')}, 'su_from': {'help_text': _('Only ssh and automatic login system users are supported')} } - def validate_auto_push(self, value): - login_mode = self.get_initial_value("login_mode") - protocol = self.get_initial_value("protocol") - - if login_mode == SystemUser.LOGIN_MANUAL: - value = False - elif protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS: - value = False - return value - - def validate_auto_generate_key(self, value): - login_mode = self.get_initial_value("login_mode") - protocol = self.get_initial_value("protocol") - - if self.context["request"].method.lower() != "post": - value = False - elif self.instance: - value = False - elif login_mode == SystemUser.LOGIN_MANUAL: - value = False - elif protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS: - value = False - return value - def validate_username_same_with_user(self, username_same_with_user): if not username_same_with_user: return username_same_with_user @@ -132,12 +73,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): raise serializers.ValidationError(msg) return username - def validate_home(self, home): - username_same_with_user = self.get_initial_value("username_same_with_user") - if username_same_with_user: - return '' - return home - @staticmethod def validate_sftp_root(value): if value in ['home', 'tmp']: @@ -147,17 +82,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): raise serializers.ValidationError(error) return value - def validate_password(self, password): - super().validate_password(password) - auto_gen_key = self.get_initial_value('auto_generate_key', False) - private_key = self.get_initial_value('private_key') - login_mode = self.get_initial_value('login_mode') - - if not self.instance and not auto_gen_key and not password and \ - not private_key and login_mode == SystemUser.LOGIN_AUTO: - raise serializers.ValidationError(_("Password or private key required")) - return password - def validate_su_from(self, su_from: SystemUser): # self: su enabled su_enabled = self.get_initial_value('su_enabled', default=False) @@ -181,70 +105,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): raise serializers.ValidationError(error) return su_from - def _validate_admin_user(self, attrs): - if self.instance: - tp = self.instance.type - else: - tp = attrs.get('type') - if tp != SystemUser.Type.admin: - return attrs - attrs['protocol'] = SystemUser.Protocol.ssh - attrs['login_mode'] = SystemUser.LOGIN_AUTO - attrs['username_same_with_user'] = False - attrs['auto_push_account'] = False - return attrs - - def _validate_gen_key(self, attrs): - username = attrs.get('username', 'manual') - auto_gen_key = attrs.pop('auto_generate_key', False) - protocol = attrs.get('protocol') - - if protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS: - return attrs - - # 自动生成 - if auto_gen_key and not self.instance: - password = SystemUser.gen_password() - attrs['password'] = password - if protocol == SystemUser.Protocol.ssh: - private_key, public_key = SystemUser.gen_key(username) - attrs['private_key'] = private_key - attrs['public_key'] = public_key - # 如果设置了private key,没有设置public key则生成 - elif attrs.get('private_key'): - private_key = attrs['private_key'] - password = attrs.get('password') - public_key = ssh_pubkey_gen(private_key, password=password, username=username) - attrs['public_key'] = public_key - return attrs - - def _validate_login_mode(self, attrs): - if 'login_mode' in attrs: - login_mode = attrs['login_mode'] - else: - login_mode = self.instance.login_mode if self.instance else SystemUser.LOGIN_AUTO - - if login_mode == SystemUser.LOGIN_MANUAL: - attrs['password'] = '' - attrs['private_key'] = '' - attrs['public_key'] = '' - - return attrs - - def validate(self, attrs): - attrs = self._validate_admin_user(attrs) - attrs = self._validate_gen_key(attrs) - attrs = self._validate_login_mode(attrs) - return attrs - - @classmethod - def setup_eager_loading(cls, queryset): - """ Perform necessary eager loading of data. """ - queryset = queryset \ - .annotate(assets_amount=Count("assets")) \ - .prefetch_related('nodes', 'cmd_filters') - return queryset - class MiniSystemUserSerializer(serializers.ModelSerializer): class Meta: @@ -252,28 +112,6 @@ class MiniSystemUserSerializer(serializers.ModelSerializer): fields = SystemUserSerializer.Meta.fields_mini -class SystemUserWithAuthInfoSerializer(SecretReadableMixin, SystemUserSerializer): - class Meta(SystemUserSerializer.Meta): - fields_mini = ['id', 'name', 'username'] - fields_write_only = ['password', 'public_key', 'private_key'] - fields_small = fields_mini + fields_write_only + [ - 'protocol', 'login_mode', 'login_mode_display', 'priority', - 'sudo', 'shell', 'ad_domain', 'sftp_root', 'token', - "username_same_with_user", 'auto_push_account', 'auto_generate_key', - 'comment', - ] - fields = fields_small - extra_kwargs = { - 'nodes_amount': {'label': _('Node')}, - 'assets_amount': {'label': _('Asset')}, - 'login_mode_display': {'label': _('Login mode display')}, - 'created_by': {'read_only': True}, - 'password': {'write_only': False}, - 'private_key': {'write_only': False}, - 'token': {'write_only': False} - } - - class SystemUserSimpleSerializer(serializers.ModelSerializer): """ 系统用户最基本信息的数据结构 @@ -284,70 +122,6 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'username') -class RelationMixin(BulkSerializerMixin, serializers.Serializer): - systemuser_display = serializers.ReadOnlyField(label=_("System user name")) - org_name = serializers.ReadOnlyField(label=_("Org name")) - - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend(['systemuser', "systemuser_display", "org_name"]) - return fields - - -class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializer): - asset_display = serializers.ReadOnlyField(label=_('Asset hostname')) - - class Meta: - model = SystemUser.assets.through - fields = [ - "id", "asset", "asset_display", - "systemuser", "systemuser_display", - ] - use_model_bulk_create = True - model_bulk_create_kwargs = { - 'ignore_conflicts': True - } - - -class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerializer): - node_display = serializers.SerializerMethodField() - - class Meta: - model = SystemUser.nodes.through - fields = [ - 'id', 'node', "node_display", - ] - - def get_node_display(self, obj): - return obj.node.full_value - - -class SystemUserUserRelationSerializer(RelationMixin, serializers.ModelSerializer): - user_display = serializers.ReadOnlyField() - - class Meta: - model = SystemUser.users.through - fields = [ - 'id', "user", "user_display", - ] - - -class SystemUserTaskSerializer(serializers.Serializer): - ACTION_CHOICES = ( - ("test", "test"), - ("push", "push"), - ) - action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True) - asset = serializers.PrimaryKeyRelatedField( - queryset=Asset.objects, allow_null=True, required=False, write_only=True - ) - assets = serializers.PrimaryKeyRelatedField( - queryset=Asset.objects, allow_null=True, required=False, write_only=True, - many=True - ) - task = serializers.CharField(read_only=True) - - class SystemUserTempAuthSerializer(SystemUserSerializer): instance_id = serializers.CharField() diff --git a/apps/assets/signal_handlers/__init__.py b/apps/assets/signal_handlers/__init__.py index 8a895544f..b337df001 100644 --- a/apps/assets/signal_handlers/__init__.py +++ b/apps/assets/signal_handlers/__init__.py @@ -1,5 +1,4 @@ from .asset import * -from .system_user import * -from .authbook import * +from .account import * from .node_assets_amount import * from .node_assets_mapping import * diff --git a/apps/assets/signal_handlers/authbook.py b/apps/assets/signal_handlers/account.py similarity index 100% rename from apps/assets/signal_handlers/authbook.py rename to apps/assets/signal_handlers/account.py diff --git a/apps/assets/signal_handlers/common.py b/apps/assets/signal_handlers/common.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/assets/signal_handlers/system_user.py b/apps/assets/signal_handlers/system_user.py.bak similarity index 100% rename from apps/assets/signal_handlers/system_user.py rename to apps/assets/signal_handlers/system_user.py.bak diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 1c405a82a..f3f192a3d 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -17,7 +17,6 @@ router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') router.register(r'system-users', api.SystemUserViewSet, 'system-user') -router.register(r'admin-users', api.AdminUserViewSet, 'admin-user') router.register(r'labels', api.LabelViewSet, 'label') router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'domains', api.DomainViewSet, 'domain') @@ -25,9 +24,6 @@ router.register(r'gateways', api.GatewayViewSet, 'gateway') router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter') router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user') router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') -router.register(r'system-users-assets-relations', api.SystemUserAssetRelationViewSet, 'system-users-assets-relation') -router.register(r'system-users-nodes-relations', api.SystemUserNodeRelationViewSet, 'system-users-nodes-relation') -router.register(r'system-users-users-relations', api.SystemUserUserRelationViewSet, 'system-users-users-relation') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') @@ -45,11 +41,8 @@ urlpatterns = [ path('assets//perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'), path('assets//perm-user-groups//permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'), - path('system-users//assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'), - path('system-users//applications//auth-info/', api.SystemUserAppAuthInfoApi.as_view(), name='system-user-app-auth-info'), path('system-users//assets//users//account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'), path('system-users//assets//users//account-secret/', api.SystemUserAssetAccountSecretApi.as_view(), name='system-user-asset-account-secret'), - path('system-users//tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'), path('system-users//cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), path('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'), diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 9ac565bc6..af6959f48 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -69,11 +69,6 @@ M2M_NEED_RECORD = { _('{User} JOINED {UserGroup}'), _('{User} LEFT {UserGroup}') ), - SystemUser.assets.through._meta.object_name: ( - _('Asset and SystemUser'), - _('{Asset} ADD {SystemUser}'), - _('{Asset} REMOVE {SystemUser}') - ), Asset.nodes.through._meta.object_name: ( _('Node and Asset'), _('{Node} ADD {Asset}'), @@ -99,11 +94,6 @@ M2M_NEED_RECORD = { _('{AssetPermission} ADD {Node}'), _('{AssetPermission} REMOVE {Node}'), ), - AssetPermission.system_users.through._meta.object_name: ( - _('Asset permission and SystemUser'), - _('{AssetPermission} ADD {SystemUser}'), - _('{AssetPermission} REMOVE {SystemUser}'), - ), ApplicationPermission.users.through._meta.object_name: ( _('User application permissions'), _('{ApplicationPermission} ADD {User}'), diff --git a/apps/perms/api/application/user_permission/common.py b/apps/perms/api/application/user_permission/common.py index 34fcc4dd1..d4c68662a 100644 --- a/apps/perms/api/application/user_permission/common.py +++ b/apps/perms/api/application/user_permission/common.py @@ -62,20 +62,19 @@ class ValidateUserApplicationPermissionApi(APIView): def get(self, request, *args, **kwargs): user_id = request.query_params.get('user_id', '') application_id = request.query_params.get('application_id', '') - system_user_id = request.query_params.get('system_user_id', '') + account = system_user_id = request.query_params.get('account', '') data = { 'has_permission': False, 'expire_at': int(time.time()), 'actions': [] } - if not all((user_id, application_id, system_user_id)): + if not all((user_id, application_id, account)): return Response(data) user = User.objects.get(id=user_id) application = Application.objects.get(id=application_id) - system_user = SystemUser.objects.get(id=system_user_id) - has_perm, actions, expire_at = validate_permission(user, application, system_user) + has_perm, actions, expire_at = validate_permission(user, application, account) status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN data = { 'has_permission': has_perm, diff --git a/apps/perms/api/asset/asset_permission_relation.py b/apps/perms/api/asset/asset_permission_relation.py index b0a67f858..16469695b 100644 --- a/apps/perms/api/asset/asset_permission_relation.py +++ b/apps/perms/api/asset/asset_permission_relation.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- # from rest_framework import generics -from django.db.models import F, Value -from django.db.models.functions import Concat +from django.db.models import F from django.shortcuts import get_object_or_404 from orgs.mixins.api import OrgRelationMixin @@ -15,8 +14,7 @@ from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils __all__ = [ 'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet', 'AssetPermissionAssetRelationViewSet', 'AssetPermissionNodeRelationViewSet', - 'AssetPermissionSystemUserRelationViewSet', 'AssetPermissionAllAssetListApi', - 'AssetPermissionAllUserListApi', + 'AssetPermissionAllAssetListApi', 'AssetPermissionAllUserListApi', ] @@ -117,21 +115,3 @@ class AssetPermissionNodeRelationViewSet(RelationMixin): .annotate(node_key=F('node__key')) return queryset - -class AssetPermissionSystemUserRelationViewSet(RelationMixin): - serializer_class = serializers.AssetPermissionSystemUserRelationSerializer - m2m_field = models.AssetPermission.system_users.field - filterset_fields = [ - 'id', 'systemuser', 'assetpermission', - ] - search_fields = [ - "assetpermission__name", "systemuser__name", "systemuser__username" - ] - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate( - systemuser_display=Concat( - F('systemuser__name'), Value('('), F('systemuser__username'), Value(')') - )) - return queryset diff --git a/apps/perms/migrations/0029_auto_20220728_1728.py b/apps/perms/migrations/0029_auto_20220728_1728.py new file mode 100644 index 000000000..6f64dfbbd --- /dev/null +++ b/apps/perms/migrations/0029_auto_20220728_1728.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.14 on 2022-07-28 09:10 + +from django.db import migrations, models + + +def migrate_system_user_to_accounts(apps, schema_editor): + # Todo: 迁移 系统用户为账号 + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0028_auto_20220316_2028'), + ] + + operations = [ + migrations.AddField( + model_name='assetpermission', + name='accounts', + field=models.JSONField(default=list, verbose_name='Accounts'), + ), + migrations.RunPython(migrate_system_user_to_accounts), + migrations.RemoveField( + model_name='assetpermission', + name='system_users', + ), + ] diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index ea795d889..b944e84e3 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -22,14 +22,13 @@ logger = logging.getLogger(__name__) class AssetPermission(BasePermission): assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) - system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', blank=True, verbose_name=_("System user")) + accounts = models.JSONField(default=list, verbose_name=_("Accounts")) class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset permission") ordering = ('name',) - permissions = [ - ] + permissions = [] @lazyproperty def users_amount(self): @@ -47,10 +46,6 @@ class AssetPermission(BasePermission): def nodes_amount(self): return self.nodes.count() - @lazyproperty - def system_users_amount(self): - return self.system_users.count() - @classmethod def get_queryset_with_prefetch(cls): return cls.objects.all().valid().prefetch_related( @@ -80,10 +75,6 @@ class AssetPermission(BasePermission): names = [asset.hostname for asset in self.assets.all()] return names - def system_users_display(self): - names = [system_user.name for system_user in self.system_users.all()] - return names - def nodes_display(self): names = [node.full_value for node in self.nodes.all()] return names diff --git a/apps/perms/serializers/asset/permission.py b/apps/perms/serializers/asset/permission.py index cd6c24723..9476dfaa3 100644 --- a/apps/perms/serializers/asset/permission.py +++ b/apps/perms/serializers/asset/permission.py @@ -22,7 +22,6 @@ class AssetPermissionSerializer(BasePermissionSerializer): user_groups_display = serializers.ListField(child=serializers.CharField(), label=_('User groups display'), required=False) assets_display = serializers.ListField(child=serializers.CharField(), label=_('Assets display'), required=False) nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes display'), required=False) - system_users_display = serializers.ListField(child=serializers.CharField(), label=_('System users display'), required=False) class Meta: model = AssetPermission @@ -34,9 +33,9 @@ class AssetPermissionSerializer(BasePermissionSerializer): ] fields_m2m = [ 'users', 'users_display', 'user_groups', 'user_groups_display', 'assets', - 'assets_display', 'nodes', 'nodes_display', 'system_users', 'system_users_display', + 'assets_display', 'nodes', 'nodes_display', 'accounts', 'users_amount', 'user_groups_amount', 'assets_amount', - 'nodes_amount', 'system_users_amount', + 'nodes_amount', ] fields = fields_small + fields_m2m read_only_fields = ['created_by', 'date_created', 'from_ticket'] @@ -48,30 +47,16 @@ class AssetPermissionSerializer(BasePermissionSerializer): 'user_groups_amount': {'label': _('User groups amount')}, 'assets_amount': {'label': _('Assets amount')}, 'nodes_amount': {'label': _('Nodes amount')}, - 'system_users_amount': {'label': _('System users amount')}, } @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ queryset = queryset.prefetch_related( - 'users', 'user_groups', 'assets', 'nodes', 'system_users' + 'users', 'user_groups', 'assets', 'nodes', ) return queryset - def to_internal_value(self, data): - if 'system_users_display' in data: - # system_users_display 转化为 system_users - system_users = data.get('system_users', []) - system_users_display = data.pop('system_users_display') - - for name in system_users_display: - system_user = SystemUser.objects.filter(name=name).first() - if system_user and system_user.id not in system_users: - system_users.append(system_user.id) - data['system_users'] = system_users - return super().to_internal_value(data) - @staticmethod def perform_display_create(instance, **kwargs): # 用户 diff --git a/apps/perms/serializers/asset/permission_relation.py b/apps/perms/serializers/asset/permission_relation.py index ee1e05112..3bd5e8b1d 100644 --- a/apps/perms/serializers/asset/permission_relation.py +++ b/apps/perms/serializers/asset/permission_relation.py @@ -12,7 +12,6 @@ __all__ = [ 'AssetPermissionUserGroupRelationSerializer', "AssetPermissionAssetRelationSerializer", 'AssetPermissionNodeRelationSerializer', - 'AssetPermissionSystemUserRelationSerializer', 'AssetPermissionAllAssetSerializer', 'AssetPermissionAllUserSerializer', ] @@ -99,13 +98,3 @@ class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSeri fields = [ 'id', 'node', "node_display", ] - - -class AssetPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer): - systemuser_display = serializers.ReadOnlyField() - - class Meta: - model = AssetPermission.system_users.through - fields = [ - 'id', 'systemuser', 'systemuser_display' - ] diff --git a/apps/perms/signal_handlers/asset_permission.py b/apps/perms/signal_handlers/asset_permission.py index e889c318a..68503b6df 100644 --- a/apps/perms/signal_handlers/asset_permission.py +++ b/apps/perms/signal_handlers/asset_permission.py @@ -1,138 +1,10 @@ # -*- coding: utf-8 -*- # -from django.db.models.signals import m2m_changed -from django.dispatch import receiver - -from users.models import User -from assets.models import SystemUser -from common.utils import get_logger -from common.decorator import on_transaction_commit -from common.exceptions import M2MReverseNotAllowed -from common.const.signals import POST_ADD -from perms.models import AssetPermission - - -logger = get_logger(__file__) - - -@receiver(m2m_changed, sender=User.groups.through) -@on_transaction_commit -def on_user_groups_change(sender, instance, action, reverse, pk_set, **kwargs): - """ - UserGroup 增加 User 时,增加的 User 需要与 UserGroup 关联的动态系统用户相关联 - """ - user: User - - if action != POST_ADD: - return - - if not reverse: - # 一个用户添加了多个用户组 - user_ids = [instance.id] - system_users = SystemUser.objects.filter(groups__id__in=pk_set).distinct() - else: - # 一个用户组添加了多个用户 - user_ids = pk_set - system_users = SystemUser.objects.filter(groups__id=instance.pk).distinct() - - for system_user in system_users: - system_user.users.add(*user_ids) - - -@receiver(m2m_changed, sender=AssetPermission.nodes.through) -@on_transaction_commit -def on_permission_nodes_changed(instance, action, reverse, pk_set, model, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Asset permission nodes change signal received") - nodes = model.objects.filter(pk__in=pk_set) - system_users = instance.system_users.all() - - # TODO 待优化 - for system_user in system_users: - system_user.nodes.add(*nodes) - - -@receiver(m2m_changed, sender=AssetPermission.assets.through) -@on_transaction_commit -def on_permission_assets_changed(instance, action, reverse, pk_set, model, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Asset permission assets change signal received") - assets = model.objects.filter(pk__in=pk_set) - - # TODO 待优化 - system_users = instance.system_users.all() - for system_user in system_users: - system_user: SystemUser - system_user.add_related_assets(assets) - - -@receiver(m2m_changed, sender=AssetPermission.system_users.through) -@on_transaction_commit -def on_asset_permission_system_users_changed(instance, action, reverse, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Asset permission system_users change signal received") - system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) - assets = instance.assets.all().values_list('id', flat=True) - nodes = instance.nodes.all().values_list('id', flat=True) - - for system_user in system_users: - system_user.nodes.add(*tuple(nodes)) - system_user.add_related_assets(assets) - - # 动态系统用户,需要关联用户和用户组了 - if system_user.username_same_with_user: - users = instance.users.all().values_list('id', flat=True) - groups = instance.user_groups.all().values_list('id', flat=True) - system_user.groups.add(*tuple(groups)) - system_user.users.add(*tuple(users)) - - -@receiver(m2m_changed, sender=AssetPermission.users.through) -@on_transaction_commit -def on_asset_permission_users_changed(instance, action, reverse, pk_set, model, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Asset permission users change signal received") - users = model.objects.filter(pk__in=pk_set) - system_users = instance.system_users.all() - - # TODO 待优化 - for system_user in system_users: - if system_user.username_same_with_user: - system_user.users.add(*tuple(users)) - - -@receiver(m2m_changed, sender=AssetPermission.user_groups.through) -@on_transaction_commit -def on_asset_permission_user_groups_changed(instance, action, pk_set, model, reverse, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Asset permission user groups change signal received") - groups = model.objects.filter(pk__in=pk_set) - system_users = instance.system_users.all() - - # TODO 待优化 - for system_user in system_users: - if system_user.username_same_with_user: - system_user.groups.add(*tuple(groups)) + + + + + diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index f24b1b8ba..17fb22990 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -11,7 +11,6 @@ router.register('asset-permissions-users-relations', api.AssetPermissionUserRela router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation') router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation') router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation') -router.register('asset-permissions-system-users-relations', api.AssetPermissionSystemUserRelationViewSet, 'asset-permissions-system-users-relation') user_permission_urlpatterns = [ # 统一说明: diff --git a/apps/perms/utils/asset/permission.py b/apps/perms/utils/asset/permission.py index e749e630b..8b9991715 100644 --- a/apps/perms/utils/asset/permission.py +++ b/apps/perms/utils/asset/permission.py @@ -11,11 +11,7 @@ from perms.utils.asset.user_permission import get_user_all_asset_perm_ids logger = get_logger(__file__) -def validate_permission(user, asset, system_user, action='connect'): - - if not system_user.protocol in asset.protocols_as_dict.keys(): - return False, time.time() - +def validate_permission(user, asset, account, action='connect'): asset_perm_ids = get_user_all_asset_perm_ids(user) asset_perm_ids_from_asset = AssetPermission.assets.through.objects.filter( @@ -28,9 +24,7 @@ def validate_permission(user, asset, system_user, action='connect'): for node in nodes: ancestor_keys = node.get_ancestor_keys(with_self=True) node_keys.update(ancestor_keys) - node_ids = Node.objects.filter(key__in=node_keys).values_list('id', flat=True) - - node_ids = set(node_ids) + node_ids = set(Node.objects.filter(key__in=node_keys).values_list('id', flat=True)) asset_perm_ids_from_node = AssetPermission.nodes.through.objects.filter( assetpermission_id__in=asset_perm_ids, @@ -39,16 +33,9 @@ def validate_permission(user, asset, system_user, action='connect'): asset_perm_ids = {*asset_perm_ids_from_asset, *asset_perm_ids_from_node} - asset_perm_ids = AssetPermission.system_users.through.objects.filter( - assetpermission_id__in=asset_perm_ids, - systemuser_id=system_user.id - ).values_list('assetpermission_id', flat=True) - - asset_perm_ids = set(asset_perm_ids) - - asset_perms = AssetPermission.objects.filter( - id__in=asset_perm_ids - ).order_by('-date_expired') + asset_perms = AssetPermission.objects\ + .filter(id__in=asset_perm_ids, accounts__contains=account)\ + .order_by('-date_expired') if asset_perms: actions = set() diff --git a/apps/tickets/migrations/0018_auto_20220728_1125.py b/apps/tickets/migrations/0018_auto_20220728_1125.py new file mode 100644 index 000000000..70bb7c6bd --- /dev/null +++ b/apps/tickets/migrations/0018_auto_20220728_1125.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-07-28 03:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0017_auto_20220623_1027'), + ] + + operations = [ + migrations.AlterField( + model_name='applyapplicationticket', + name='apply_permission_name', + field=models.CharField(max_length=128, verbose_name='Permission name'), + ), + migrations.AlterField( + model_name='applyassetticket', + name='apply_permission_name', + field=models.CharField(max_length=128, verbose_name='Permission name'), + ), + ] diff --git a/utils/generate_fake_data/resources/perms.py b/utils/generate_fake_data/resources/perms.py index 953712a5d..e3e866feb 100644 --- a/utils/generate_fake_data/resources/perms.py +++ b/utils/generate_fake_data/resources/perms.py @@ -47,12 +47,6 @@ class AssetPermissionGenerator(FakeDataGenerator): relation_name = 'node_id' self.set_relations(perms, through, relation_name, choices) - def set_system_users(self, perms): - through = AssetPermission.system_users.through - choices = self.system_user_ids - relation_name = 'systemuser_id' - self.set_relations(perms, through, relation_name, choices) - def set_relations(self, perms, through, relation_name, choices, choice_count=None): relations = [] @@ -79,4 +73,3 @@ class AssetPermissionGenerator(FakeDataGenerator): self.set_user_groups(created) self.set_assets(created) self.set_nodes(created) - self.set_system_users(created) From 0dc3d43ee53aa0de7ed49015996fb6402e018529 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 28 Jul 2022 19:12:27 +0800 Subject: [PATCH 035/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/models/application.py | 2 +- apps/assets/api/system_user.py | 176 ---------- apps/assets/models/__init__.py | 5 +- apps/assets/models/account.py | 2 +- apps/assets/models/authbook.py | 140 -------- apps/assets/models/cmd_filter.py | 223 ------------- apps/assets/models/user.py | 17 - .../assets/signal_handlers/system_user.py.bak | 119 ------- apps/assets/tasks/push_system_user.py | 307 ------------------ apps/assets/tasks/system_user_connectivity.py | 151 --------- apps/assets/urls/api_urls.py | 12 +- apps/perms/models/asset_permission.py | 2 +- 12 files changed, 5 insertions(+), 1151 deletions(-) delete mode 100644 apps/assets/api/system_user.py delete mode 100644 apps/assets/models/authbook.py delete mode 100644 apps/assets/models/cmd_filter.py delete mode 100644 apps/assets/signal_handlers/system_user.py.bak delete mode 100644 apps/assets/tasks/push_system_user.py delete mode 100644 apps/assets/tasks/system_user_connectivity.py diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index af1e27c2d..000094b4f 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -9,7 +9,7 @@ from orgs.mixins.models import OrgModelMixin from common.mixins import CommonModelMixin from common.tree import TreeNode from common.utils import is_uuid -from assets.models import Asset, SystemUser +from assets.models import Asset from ..utils import KubernetesTree from .. import const diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py deleted file mode 100644 index 7ae682839..000000000 --- a/apps/assets/api/system_user.py +++ /dev/null @@ -1,176 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -from django.shortcuts import get_object_or_404 -from rest_framework.response import Response -from rest_framework.decorators import action -from rest_framework.viewsets import GenericViewSet - -from common.utils import get_logger, get_object_or_none -from common.mixins.api import SuggestionMixin -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.mixins import generics -from ..models import SystemUser, CommandFilterRule, Account -from .. import serializers - -logger = get_logger(__file__) -__all__ = [ - 'SystemUserViewSet', 'SystemUserAuthInfoApi', - 'SystemUserCommandFilterRuleListApi', - 'SystemUserAssetAccountApi', - 'SystemUserAssetAccountSecretApi', -] - - -class SystemUserViewSet(SuggestionMixin, OrgBulkModelViewSet): - """ - System user api set, for add,delete,update,list,retrieve resource - """ - model = SystemUser - filterset_fields = { - 'name': ['exact'], - 'username': ['exact'], - 'protocol': ['exact', 'in'], - } - search_fields = filterset_fields - serializer_class = serializers.SystemUserSerializer - serializer_classes = { - 'default': serializers.SystemUserSerializer, - 'suggestion': serializers.MiniSystemUserSerializer - } - ordering_fields = ('name', 'protocol', 'login_mode') - ordering = ('name', ) - rbac_perms = { - 'su_from': 'assets.view_systemuser', - 'su_to': 'assets.view_systemuser', - 'match': 'assets.match_systemuser' - } - - @action(methods=['get'], detail=False, url_path='su-from') - def su_from(self, request, *args, **kwargs): - """ API 获取可选的 su_from 系统用户""" - queryset = self.filter_queryset(self.get_queryset()) - queryset = queryset.filter( - protocol=SystemUser.Protocol.ssh, login_mode=SystemUser.LOGIN_AUTO - ) - return self.get_paginate_response_if_need(queryset) - - @action(methods=['get'], detail=True, url_path='su-to') - def su_to(self, request, *args, **kwargs): - """ 获取系统用户的所有 su_to 系统用户 """ - pk = kwargs.get('pk') - system_user = get_object_or_404(SystemUser, pk=pk) - queryset = system_user.su_to.all() - queryset = self.filter_queryset(queryset) - return self.get_paginate_response_if_need(queryset) - - def get_paginate_response_if_need(self, queryset): - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) - - -class SystemUserAccountViewSet(GenericViewSet): - model = Account - serializer_classes = { - 'default': serializers.AccountSerializer, - 'account_secret': serializers.AccountSecretSerializer, - } - - def get_object(self): - system_user_id = self.kwargs.get('pk') - asset_id = self.kwargs.get('asset_id') - user_id = self.kwargs.get("user_id") - system_user = SystemUser.objects.get(id=system_user_id) - account = system_user.get_account(user_id, asset_id) - return account - - @action(methods=['get'], detail=False, url_path='account') - def account(self, request, *args, **kwargs): - pass - - @action(methods=['get'], detail=False, url_path='account-secret') - def account_secret(self): - pass - - @action(methods=['put'], detail=False, url_path='manual-account') - def manual_account(self, request, *args, **kwargs): - pass - - -class SystemUserAssetAccountApi(generics.RetrieveAPIView): - model = Account - serializer_class = serializers.AccountSerializer - - def get_object(self): - system_user_id = self.kwargs.get('pk') - asset_id = self.kwargs.get('asset_id') - user_id = self.kwargs.get("user_id") - system_user = SystemUser.objects.get(id=system_user_id) - account = system_user.get_account(user_id, asset_id) - return account - - -class SystemUserAssetAccountSecretApi(SystemUserAssetAccountApi): - model = Account - serializer_class = serializers.AccountSecretSerializer - rbac_perms = { - 'retrieve': 'assets.view_accountsecret' - } - - -class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): - """ - Get system user auth info - """ - model = SystemUser - serializer_class = serializers.AccountSerializer - rbac_perms = { - 'retrieve': 'assets.view_systemusersecret', - 'list': 'assets.view_systemusersecret', - 'change': 'assets.change_systemuser', - 'destroy': 'assets.change_systemuser', - } - - def get_object(self): - system_user_id = self.kwargs.get('pk') - asset_id = self.kwargs.get('asset_id') - user_id = self.kwargs.get("user_id") - system_user = SystemUser.objects.get(id=system_user_id) - account = system_user.get_account(user_id, asset_id) - return account - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - instance.clear_auth() - return Response(status=204) - - -class SystemUserCommandFilterRuleListApi(generics.ListAPIView): - rbac_perms = { - 'list': 'assets.view_commandfilterule' - } - - def get_serializer_class(self): - from ..serializers import CommandFilterRuleSerializer - return CommandFilterRuleSerializer - - def get_queryset(self): - user_id = self.request.query_params.get('user_id') - user_group_id = self.request.query_params.get('user_group_id') - system_user_id = self.kwargs.get('pk', None) - system_user = get_object_or_none(SystemUser, pk=system_user_id) - if not system_user: - system_user_id = self.request.query_params.get('system_user_id') - asset_id = self.request.query_params.get('asset_id') - application_id = self.request.query_params.get('application_id') - rules = CommandFilterRule.get_queryset( - user_id=user_id, - user_group_id=user_group_id, - system_user_id=system_user_id, - asset_id=asset_id, - application_id=application_id - ) - return rules - diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index da35178d1..4eeab7b0b 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,15 +1,12 @@ from .base import * from .asset import * from .label import Label -from .user import * from .group import * from .domain import * from .node import * -from .cmd_filter import * -from .authbook import * from .utils import * -from .authbook import * from .gathered_user import * from .favorite_asset import * from .account import * from .backup import * +from .user import * diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index eb651965b..641ba1a76 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -2,7 +2,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords -from .user import ProtocolMixin +from .protocol import ProtocolMixin from .base import BaseUser, AbsConnectivity diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py deleted file mode 100644 index f5d9e457d..000000000 --- a/apps/assets/models/authbook.py +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.db import models -from django.db.models import F -from django.utils.translation import ugettext_lazy as _ -from simple_history.models import HistoricalRecords - -from common.utils import lazyproperty, get_logger -from .base import BaseUser, AbsConnectivity - -logger = get_logger(__name__) - - -__all__ = ['AuthBook'] - - -class AuthBook(BaseUser, AbsConnectivity): - asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) - systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) - version = models.IntegerField(default=1, verbose_name=_('Version')) - history = HistoricalRecords() - - auth_attrs = ['username', 'password', 'private_key', 'public_key'] - - class Meta: - verbose_name = _('AuthBook') - unique_together = [('username', 'asset', 'systemuser')] - permissions = [ - ('test_authbook', _('Can test asset account connectivity')), - ('view_assetaccountsecret', _('Can view asset account secret')), - ('change_assetaccountsecret', _('Can change asset account secret')), - ('view_assethistoryaccount', _('Can view asset history account')), - ('view_assethistoryaccountsecret', _('Can view asset history account secret')), - ] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.auth_snapshot = {} - - def get_or_systemuser_attr(self, attr): - val = getattr(self, attr, None) - if val: - return val - if self.systemuser: - return getattr(self.systemuser, attr, '') - return '' - - def load_auth(self): - for attr in self.auth_attrs: - value = self.get_or_systemuser_attr(attr) - self.auth_snapshot[attr] = [getattr(self, attr), value] - setattr(self, attr, value) - - def unload_auth(self): - if not self.systemuser: - return - - for attr, values in self.auth_snapshot.items(): - origin_value, loaded_value = values - current_value = getattr(self, attr, '') - if current_value == loaded_value: - setattr(self, attr, origin_value) - - def save(self, *args, **kwargs): - self.unload_auth() - instance = super().save(*args, **kwargs) - self.load_auth() - return instance - - @property - def username_display(self): - return self.get_or_systemuser_attr('username') or '*' - - @lazyproperty - def systemuser_display(self): - if not self.systemuser: - return '' - return str(self.systemuser) - - @property - def smart_name(self): - username = self.username_display - - if self.asset: - asset = str(self.asset) - else: - asset = '*' - return '{}@{}'.format(username, asset) - - def sync_to_system_user_account(self): - if self.systemuser: - return - matched = AuthBook.objects.filter( - asset=self.asset, systemuser__username=self.username - ) - if not matched: - return - - for i in matched: - i.password = self.password - i.private_key = self.private_key - i.public_key = self.public_key - i.comment = 'Update triggered by account {}'.format(self.id) - - # 不触发post_save信号 - self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key']) - - def remove_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser: - return - self.asset.admin_user = None - self.asset.save() - logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser)) - - def update_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser: - return - self.asset.admin_user = self.systemuser - self.asset.save() - logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) - - @classmethod - def get_queryset(cls, is_history_model=False): - model = cls.history.model if is_history_model else cls - queryset = model.objects.all() \ - .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) \ - .annotate(platform=F('asset__platform__name')) \ - .annotate(protocols=F('asset__protocols')) - return queryset - - def __str__(self): - return self.smart_name - - diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py deleted file mode 100644 index b17a4263d..000000000 --- a/apps/assets/models/cmd_filter.py +++ /dev/null @@ -1,223 +0,0 @@ -# -*- coding: utf-8 -*- -# -import uuid -import re - -from django.db import models -from django.db.models import Q -from django.core.validators import MinValueValidator, MaxValueValidator -from django.utils.translation import ugettext_lazy as _ - -from users.models import User, UserGroup -from applications.models import Application -from ..models import SystemUser, Asset - -from common.utils import lazyproperty, get_logger, get_object_or_none -from orgs.mixins.models import OrgModelMixin - -logger = get_logger(__file__) - -__all__ = [ - 'CommandFilter', 'CommandFilterRule' -] - - -class CommandFilter(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=64, verbose_name=_("Name")) - users = models.ManyToManyField( - 'users.User', related_name='cmd_filters', blank=True, - verbose_name=_("User") - ) - user_groups = models.ManyToManyField( - 'users.UserGroup', related_name='cmd_filters', blank=True, - verbose_name=_("User group"), - ) - assets = models.ManyToManyField( - 'assets.Asset', related_name='cmd_filters', blank=True, - verbose_name=_("Asset") - ) - system_users = models.ManyToManyField( - 'assets.SystemUser', related_name='cmd_filters', blank=True, - verbose_name=_("System user")) - applications = models.ManyToManyField( - 'applications.Application', related_name='cmd_filters', blank=True, - verbose_name=_("Application") - ) - is_active = models.BooleanField(default=True, verbose_name=_('Is active')) - comment = models.TextField(blank=True, default='', verbose_name=_("Comment")) - date_created = models.DateTimeField(auto_now_add=True) - date_updated = models.DateTimeField(auto_now=True) - created_by = models.CharField( - max_length=128, blank=True, default='', verbose_name=_('Created by') - ) - - def __str__(self): - return self.name - - class Meta: - unique_together = [('org_id', 'name')] - verbose_name = _("Command filter") - - -class CommandFilterRule(OrgModelMixin): - TYPE_REGEX = 'regex' - TYPE_COMMAND = 'command' - TYPE_CHOICES = ( - (TYPE_REGEX, _('Regex')), - (TYPE_COMMAND, _('Command')), - ) - - ACTION_UNKNOWN = 10 - - class ActionChoices(models.IntegerChoices): - deny = 0, _('Deny') - allow = 9, _('Allow') - confirm = 2, _('Reconfirm') - - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - filter = models.ForeignKey( - 'CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules' - ) - type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type")) - priority = models.IntegerField( - default=50, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), - validators=[MinValueValidator(1), MaxValueValidator(100)] - ) - content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) - ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case')) - action = models.IntegerField(default=ActionChoices.deny, choices=ActionChoices.choices, verbose_name=_("Action")) - # 动作: 附加字段 - # - confirm: 命令复核人 - reviewers = models.ManyToManyField( - 'users.User', related_name='review_cmd_filter_rules', blank=True, - verbose_name=_("Reviewers") - ) - comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment")) - date_created = models.DateTimeField(auto_now_add=True) - date_updated = models.DateTimeField(auto_now=True) - created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by')) - - class Meta: - ordering = ('priority', 'action') - verbose_name = _("Command filter rule") - - @lazyproperty - def pattern(self): - if self.type == 'command': - s = self.construct_command_regex(content=self.content) - else: - s = r'{0}'.format(self.content) - - return s - - @classmethod - def construct_command_regex(cls, content): - regex = [] - content = content.replace('\r\n', '\n') - for _cmd in content.split('\n'): - cmd = re.sub(r'\s+', ' ', _cmd) - cmd = re.escape(cmd) - cmd = cmd.replace('\\ ', '\s+') - - # 有空格就不能 铆钉单词了 - if ' ' in _cmd: - regex.append(cmd) - continue - - # 如果是单个字符 - if cmd[-1].isalpha(): - regex.append(r'\b{0}\b'.format(cmd)) - else: - regex.append(r'\b{0}'.format(cmd)) - s = r'{}'.format('|'.join(regex)) - return s - - @staticmethod - def compile_regex(regex, ignore_case): - try: - if ignore_case: - pattern = re.compile(regex, re.IGNORECASE) - else: - pattern = re.compile(regex) - except Exception as e: - error = _('The generated regular expression is incorrect: {}').format(str(e)) - logger.error(error) - return False, error, None - return True, '', pattern - - def match(self, data): - succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case) - if not succeed: - return self.ACTION_UNKNOWN, '' - - found = pattern.search(data) - if not found: - return self.ACTION_UNKNOWN, '' - - if self.action == self.ActionChoices.allow: - return self.ActionChoices.allow, found.group() - else: - return self.ActionChoices.deny, found.group() - - def __str__(self): - return '{} % {}'.format(self.type, self.content) - - def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): - from tickets.const import TicketType - from tickets.models import ApplyCommandTicket - data = { - 'title': _('Command confirm') + ' ({})'.format(session.user), - 'type': TicketType.command_confirm, - 'applicant': session.user_obj, - 'apply_run_user_id': session.user_id, - 'apply_run_asset': str(session.asset), - 'apply_run_system_user_id': session.system_user_id, - 'apply_run_command': run_command[:4090], - 'apply_from_session_id': str(session.id), - 'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id), - 'apply_from_cmd_filter_id': str(cmd_filter_rule.filter.id), - 'org_id': org_id, - } - ticket = ApplyCommandTicket.objects.create(**data) - assignees = self.reviewers.all() - ticket.open_by_system(assignees) - return ticket - - @classmethod - def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None, - asset_id=None, application_id=None, org_id=None): - user_groups = [] - user = get_object_or_none(User, pk=user_id) - if user: - user_groups.extend(list(user.groups.all())) - user_group = get_object_or_none(UserGroup, pk=user_group_id) - if user_group: - org_id = user_group.org_id - user_groups.append(user_group) - system_user = get_object_or_none(SystemUser, pk=system_user_id) - asset = get_object_or_none(Asset, pk=asset_id) - application = get_object_or_none(Application, pk=application_id) - q = Q() - if user: - q |= Q(users=user) - if user_groups: - q |= Q(user_groups__in=set(user_groups)) - if system_user: - org_id = system_user.org_id - q |= Q(system_users=system_user) - if asset: - org_id = asset.org_id - q |= Q(assets=asset) - if application: - org_id = application.org_id - q |= Q(applications=application) - if q: - cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True) - if org_id: - cmd_filters = cmd_filters.filter(org_id=org_id) - rule_ids = cmd_filters.values_list('rules', flat=True) - rules = cls.objects.filter(id__in=rule_ids) - else: - rules = cls.objects.none() - return rules diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index a5be417a2..79d6e96e2 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -40,23 +40,6 @@ class SystemUser(ProtocolMixin, BaseUser): username = '*' return '{0.name}({1})'.format(self, username) - @property - def cmd_filter_rules(self): - from .cmd_filter import CommandFilterRule - rules = CommandFilterRule.objects.filter( - filter__in=self.cmd_filters.all() - ).distinct() - return rules - - def is_command_can_run(self, command): - for rule in self.cmd_filter_rules: - action, matched_cmd = rule.match(command) - if action == rule.ActionChoices.allow: - return True, None - elif action == rule.ActionChoices.deny: - return False, matched_cmd - return True, None - @classmethod def create_accounts_with_assets(cls, asset_ids, system_user_ids): pass diff --git a/apps/assets/signal_handlers/system_user.py.bak b/apps/assets/signal_handlers/system_user.py.bak deleted file mode 100644 index e0dfe3ebf..000000000 --- a/apps/assets/signal_handlers/system_user.py.bak +++ /dev/null @@ -1,119 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.db.models.signals import ( - post_save, m2m_changed -) -from django.dispatch import receiver - -from common.exceptions import M2MReverseNotAllowed -from common.const.signals import POST_ADD -from common.utils import get_logger -from common.decorator import on_transaction_commit -from assets.models import Asset, SystemUser, Node -from users.models import User -from assets.tasks import ( - push_system_user_to_assets_manual, - push_system_user_to_assets, - add_nodes_assets_to_system_users -) - -logger = get_logger(__file__) - - -@receiver(m2m_changed, sender=SystemUser.assets.through) -@on_transaction_commit -def on_system_user_assets_change(instance, action, model, pk_set, **kwargs): - """ - 当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中 - """ - logger.debug("System user assets change signal recv: {}".format(instance)) - - if not instance: - logger.debug('No system user found') - return - - if action != POST_ADD: - return - - if model == Asset: - system_user_ids = [instance.id] - asset_ids = pk_set - else: - system_user_ids = pk_set - asset_ids = [instance.id] - # todo: Auto create account if need - SystemUser.create_accounts_with_assets(asset_ids, system_user_ids) - - -@receiver(m2m_changed, sender=SystemUser.users.through) -@on_transaction_commit -def on_system_user_users_change(sender, instance: SystemUser, action, model, pk_set, reverse, **kwargs): - """ - 当系统用户和用户关系发生变化时,应该重新推送系统用户资产中 - """ - if action != POST_ADD: - return - - if reverse: - raise M2MReverseNotAllowed - - if not instance.username_same_with_user: - return - - logger.debug("System user users change signal recv: {}".format(instance)) - usernames = model.objects.filter(pk__in=pk_set).values_list('username', flat=True) - - for username in usernames: - push_system_user_to_assets_manual.delay(instance, username) - - -@receiver(m2m_changed, sender=SystemUser.nodes.through) -@on_transaction_commit -def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs): - """ - 当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上 - """ - if action != POST_ADD: - return - logger.info("System user nodes update signal recv: {}".format(instance)) - - queryset = model.objects.filter(pk__in=pk_set) - if model == Node: - nodes_keys = queryset.values_list('key', flat=True) - system_users = [instance] - else: - nodes_keys = [instance.key] - system_users = queryset - add_nodes_assets_to_system_users.delay(nodes_keys, system_users) - - -@receiver(m2m_changed, sender=SystemUser.groups.through) -def on_system_user_groups_change(instance, action, pk_set, reverse, **kwargs): - """ - 当系统用户和用户组关系发生变化时,应该将组下用户关联到新的系统用户上 - """ - if action != POST_ADD: - return - if reverse: - raise M2MReverseNotAllowed - logger.info("System user groups update signal recv: {}".format(instance)) - - users = User.objects.filter(groups__id__in=pk_set).distinct() - instance.users.add(*users) - - -@receiver(post_save, sender=SystemUser, dispatch_uid="jms") -@on_transaction_commit -def on_system_user_update(instance: SystemUser, created, **kwargs): - """ - 当系统用户更新时,可能更新了密钥,用户名等,这时要自动推送系统用户到资产上, - 其实应该当 用户名,密码,密钥 sudo等更新时再推送,这里偷个懒, - 这里直接取了 instance.assets 因为nodes和系统用户发生变化时,会自动将nodes下的资产 - 关联到上面 - """ - if instance and not created: - logger.info("System user update signal recv: {}".format(instance)) - assets = instance.assets.all().valid() - push_system_user_to_assets.delay(instance.id, [_asset.id for _asset in assets]) - # add assets to su_from - instance.add_related_assets_to_su_from_if_need(assets) diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py deleted file mode 100644 index 8834a29e9..000000000 --- a/apps/assets/tasks/push_system_user.py +++ /dev/null @@ -1,307 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from itertools import groupby -from celery import shared_task -from common.db.utils import get_object_if_need, get_objects -from django.utils.translation import ugettext as _, gettext_noop -from django.db.models import Empty - -from common.utils import encrypt_password, get_logger -from assets.models import SystemUser, Asset -from orgs.utils import org_aware_func, tmp_to_root_org -from . import const -from .utils import clean_ansible_task_hosts, group_asset_by_platform - - -logger = get_logger(__file__) -__all__ = [ - 'push_system_user_util', 'push_system_user_to_assets', - 'push_system_user_to_assets_manual', 'push_system_user_a_asset_manual', - 'push_system_users_a_asset' -] - - -def _split_by_comma(raw: str): - try: - return [i.strip() for i in raw.split(',')] - except AttributeError: - return [] - - -def _dump_args(args: dict): - return ' '.join([f'{k}={v}' for k, v in args.items() if v is not Empty]) - - -def get_push_unixlike_system_user_tasks(system_user, username=None, **kwargs): - algorithm = kwargs.get('algorithm') - if username is None: - username = system_user.username - - comment = system_user.name - if system_user.username_same_with_user: - from users.models import User - user = User.objects.filter(username=username).only('name', 'username').first() - if user: - comment = f'{system_user.name}[{str(user)}]' - comment = comment.replace(' ', '') - - password = system_user.password - public_key = system_user.public_key - - groups = _split_by_comma(system_user.system_groups) - - if groups: - groups = '"%s"' % ','.join(groups) - - add_user_args = { - 'name': username, - 'shell': system_user.shell or Empty, - 'state': 'present', - 'home': system_user.home or Empty, - 'expires': -1, - 'groups': groups or Empty, - 'comment': comment - } - - tasks = [ - { - 'name': 'Add user {}'.format(username), - 'action': { - 'module': 'user', - 'args': _dump_args(add_user_args), - } - }, - { - 'name': 'Add group {}'.format(username), - 'action': { - 'module': 'group', - 'args': 'name={} state=present'.format(username), - } - } - ] - if not system_user.home: - tasks.extend([ - { - 'name': 'Check home dir exists', - 'action': { - 'module': 'stat', - 'args': 'path=/home/{}'.format(username) - }, - 'register': 'home_existed' - }, - { - 'name': "Set home dir permission", - 'action': { - 'module': 'file', - 'args': "path=/home/{0} owner={0} group={0} mode=700".format(username) - }, - 'when': 'home_existed.stat.exists == true' - } - ]) - if password: - tasks.append({ - 'name': 'Set {} password'.format(username), - 'action': { - 'module': 'user', - 'args': 'name={} shell={} state=present password={}'.format( - username, system_user.shell, - encrypt_password(password, salt="K3mIlKK", algorithm=algorithm), - ), - } - }) - if public_key: - tasks.append({ - 'name': 'Set {} authorized key'.format(username), - 'action': { - 'module': 'authorized_key', - 'args': "user={} state=present key='{}'".format( - username, public_key - ) - } - }) - if system_user.sudo: - sudo = system_user.sudo.replace('\r\n', '\n').replace('\r', '\n') - sudo_list = sudo.split('\n') - sudo_tmp = [] - for s in sudo_list: - sudo_tmp.append(s.strip(',')) - sudo = ','.join(sudo_tmp) - tasks.append({ - 'name': 'Set {} sudo setting'.format(username), - 'action': { - 'module': 'lineinfile', - 'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' " - "line='{0} ALL=(ALL) NOPASSWD: {1}' " - "validate='visudo -cf %s'".format(username, sudo) - } - }) - - return tasks - - -def get_push_windows_system_user_tasks(system_user: SystemUser, username=None, **kwargs): - if username is None: - username = system_user.username - password = system_user.password - groups = {'Users', 'Remote Desktop Users'} - if system_user.system_groups: - groups.update(_split_by_comma(system_user.system_groups)) - groups = ','.join(groups) - - tasks = [] - if not password: - logger.error("Error: no password found") - return tasks - - if system_user.ad_domain: - logger.error('System user with AD domain do not support push.') - return tasks - - task = { - 'name': 'Add user {}'.format(username), - 'action': { - 'module': 'win_user', - 'args': 'fullname={} ' - 'name={} ' - 'password={} ' - 'state=present ' - 'update_password=always ' - 'password_expired=no ' - 'password_never_expires=yes ' - 'groups="{}" ' - 'groups_action=add ' - ''.format(username, username, password, groups), - } - } - tasks.append(task) - return tasks - - -def get_push_system_user_tasks(system_user, platform="unixlike", username=None, algorithm=None): - """ - 获取推送系统用户的 ansible 命令,跟资产无关 - :param system_user: - :param platform: - :param username: 当动态时,近推送某个 - :return: - """ - get_task_map = { - "unixlike": get_push_unixlike_system_user_tasks, - "windows": get_push_windows_system_user_tasks, - } - get_tasks = get_task_map.get(platform, get_push_unixlike_system_user_tasks) - if not system_user.username_same_with_user: - return get_tasks(system_user, algorithm=algorithm) - tasks = [] - # 仅推送这个username - if username is not None: - tasks.extend(get_tasks(system_user, username, algorithm=algorithm)) - return tasks - users = system_user.users.all().values_list('username', flat=True) - print(_("System user is dynamic: {}").format(list(users))) - for _username in users: - tasks.extend(get_tasks(system_user, _username, algorithm=algorithm)) - return tasks - - -@org_aware_func("system_user") -def push_system_user_util(system_user, assets, task_name, username=None): - from ops.utils import update_or_create_ansible_task - assets = clean_ansible_task_hosts(assets, system_user=system_user) - if not assets: - return {} - - # 资产按平台分类 - assets_sorted = sorted(assets, key=group_asset_by_platform) - platform_hosts = groupby(assets_sorted, key=group_asset_by_platform) - - if system_user.username_same_with_user: - if username is None: - # 动态系统用户,但是没有指定 username - usernames = list(system_user.users.all().values_list('username', flat=True).distinct()) - else: - usernames = [username] - else: - # 非动态系统用户指定 username 无效 - assert username is None, 'Only Dynamic user can assign `username`' - usernames = [system_user.username] - - def run_task(_tasks, _hosts): - if not _tasks: - return - task, created = update_or_create_ansible_task( - task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all', - options=const.TASK_OPTIONS, run_as_admin=True, - ) - task.run() - - for platform, _assets in platform_hosts: - _assets = list(_assets) - if not _assets: - continue - print(_("Start push system user for platform: [{}]").format(platform)) - print(_("Hosts count: {}").format(len(_assets))) - - for u in usernames: - for a in _assets: - system_user.load_asset_special_auth(a, u) - algorithm = 'des' if a.platform.name == 'AIX' else 'sha512' - tasks = get_push_system_user_tasks( - system_user, platform, username=u, - algorithm=algorithm - ) - run_task(tasks, [a]) - - -@shared_task(queue="ansible") -@tmp_to_root_org() -def push_system_user_to_assets_manual(system_user, username=None): - """ - 将系统用户推送到与它关联的所有资产上 - """ - system_user = get_object_if_need(SystemUser, system_user) - assets = system_user.get_related_assets() - task_name = gettext_noop("Push system users to assets: ") + system_user.name - return push_system_user_util(system_user, assets, task_name=task_name, username=username) - - -@shared_task(queue="ansible") -@tmp_to_root_org() -def push_system_user_a_asset_manual(system_user, asset, username=None): - """ - 将系统用户推送到一个资产上 - """ - # if username is None: - # username = system_user.username - task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format( - system_user.name, username or system_user.username, asset - ) - return push_system_user_util(system_user, [asset], task_name=task_name, username=username) - - -@shared_task(queue="ansible") -@tmp_to_root_org() -def push_system_users_a_asset(system_users, asset): - for system_user in system_users: - push_system_user_a_asset_manual(system_user, asset) - - -@shared_task(queue="ansible") -@tmp_to_root_org() -def push_system_user_to_assets(system_user_id, asset_ids, username=None): - """ - 推送系统用户到指定的若干资产上 - """ - system_user = SystemUser.objects.get(id=system_user_id) - assets = get_objects(Asset, asset_ids) - task_name = gettext_noop("Push system users to assets: ") + system_user.name - - return push_system_user_util(system_user, assets, task_name, username=username) - -# @shared_task -# @register_as_period_task(interval=3600) -# @after_app_ready_start -# @after_app_shutdown_clean_periodic -# def push_system_user_period(): -# for system_user in SystemUser.objects.all(): -# push_system_user_related_nodes(system_user) diff --git a/apps/assets/tasks/system_user_connectivity.py b/apps/assets/tasks/system_user_connectivity.py deleted file mode 100644 index 2213bfa26..000000000 --- a/apps/assets/tasks/system_user_connectivity.py +++ /dev/null @@ -1,151 +0,0 @@ - -from itertools import groupby -from collections import defaultdict - -from celery import shared_task -from django.utils.translation import ugettext as _, gettext_noop - -from assets.models import Asset -from common.utils import get_logger -from orgs.utils import tmp_to_org, org_aware_func -from ..models import SystemUser, Connectivity, Account -from . import const -from .utils import ( - clean_ansible_task_hosts, group_asset_by_platform -) - -logger = get_logger(__name__) -__all__ = [ - 'test_system_user_connectivity_util', 'test_system_user_connectivity_manual', - 'test_system_user_connectivity_period', 'test_system_user_connectivity_a_asset', - 'test_system_users_connectivity_a_asset' -] - - -def set_assets_accounts_connectivity(system_user, assets, results_summary): - asset_ids_ok = set() - asset_ids_failed = set() - - asset_hostnames_ok = results_summary.get('contacted', {}).keys() - - for asset in assets: - if asset.hostname in asset_hostnames_ok: - asset_ids_ok.add(asset.id) - else: - asset_ids_failed.add(asset.id) - - accounts_ok = Account.objects.filter(asset_id__in=asset_ids_ok, systemuser=system_user) - accounts_failed = Account.objects.filter(asset_id__in=asset_ids_failed, systemuser=system_user) - - Account.bulk_set_connectivity(accounts_ok, Connectivity.ok) - Account.bulk_set_connectivity(accounts_failed, Connectivity.failed) - - -@org_aware_func("system_user") -def test_system_user_connectivity_util(system_user, assets, task_name): - """ - Test system cant connect his assets or not. - :param system_user: - :param assets: - :param task_name: - :return: - """ - from ops.utils import update_or_create_ansible_task - - if system_user.username_same_with_user: - logger.error(_("Dynamic system user not support test")) - return - - # hosts = clean_ansible_task_hosts(assets, system_user=system_user) - # TODO: 这里不传递系统用户,因为clean_ansible_task_hosts会通过system_user来判断是否可以推送, - # 不符合测试可连接性逻辑, 后面需要优化此逻辑 - hosts = clean_ansible_task_hosts(assets) - if not hosts: - return {} - platform_hosts_map = {} - hosts_sorted = sorted(hosts, key=group_asset_by_platform) - platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform) - for i in platform_hosts: - platform_hosts_map[i[0]] = list(i[1]) - - platform_tasks_map = { - "unixlike": const.PING_UNIXLIKE_TASKS, - "windows": const.PING_WINDOWS_TASKS - } - - results_summary = dict( - contacted=defaultdict(dict), dark=defaultdict(dict), success=True - ) - - def run_task(_tasks, _hosts, _username): - old_name = "{}".format(system_user) - new_name = "{}({})".format(system_user.name, _username) - _task_name = task_name.replace(old_name, new_name) - _task, created = update_or_create_ansible_task( - task_name=_task_name, hosts=_hosts, tasks=_tasks, - pattern='all', options=const.TASK_OPTIONS, - run_as=_username, system_user=system_user - ) - raw, summary = _task.run() - success = summary.get('success', False) - contacted = summary.get('contacted', {}) - dark = summary.get('dark', {}) - - results_summary['success'] &= success - results_summary['contacted'].update(contacted) - results_summary['dark'].update(dark) - - for platform, _hosts in platform_hosts_map.items(): - if not _hosts: - continue - if platform not in ["unixlike", "windows"]: - continue - - tasks = platform_tasks_map[platform] - print(_("Start test system user connectivity for platform: [{}]").format(platform)) - print(_("Hosts count: {}").format(len(_hosts))) - # 用户名不是动态的,用户名则是一个 - logger.debug("System user not has special auth") - run_task(tasks, _hosts, system_user.username) - - set_assets_accounts_connectivity(system_user, hosts, results_summary) - return results_summary - - -@shared_task(queue="ansible") -@org_aware_func("system_user") -def test_system_user_connectivity_manual(system_user, asset_ids=None): - task_name = gettext_noop("Test system user connectivity: ") + str(system_user) - if asset_ids: - assets = Asset.objects.filter(id__in=asset_ids) - else: - assets = system_user.get_related_assets() - test_system_user_connectivity_util(system_user, assets, task_name) - - -@shared_task(queue="ansible") -@org_aware_func("system_user") -def test_system_user_connectivity_a_asset(system_user, asset): - task_name = gettext_noop("Test system user connectivity: ") + "{} => {}".format( - system_user, asset - ) - test_system_user_connectivity_util(system_user, [asset], task_name) - - -@shared_task(queue="ansible") -def test_system_users_connectivity_a_asset(system_users, asset): - for system_user in system_users: - test_system_user_connectivity_a_asset(system_user, asset) - - -@shared_task(queue="ansible") -def test_system_user_connectivity_period(): - if not const.PERIOD_TASK_ENABLED: - logger.debug("Period task disabled, test system user connectivity pass") - return - queryset_map = SystemUser.objects.all_group_by_org() - for org, system_user in queryset_map.items(): - task_name = gettext_noop("Test system user connectivity period: ") + str(system_user) - with tmp_to_org(org): - assets = system_user.get_related_assets() - test_system_user_connectivity_util(system_user, assets, task_name) diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index f3f192a3d..b312a41b7 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -16,7 +16,6 @@ router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history') router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') -router.register(r'system-users', api.SystemUserViewSet, 'system-user') router.register(r'labels', api.LabelViewSet, 'label') router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'domains', api.DomainViewSet, 'domain') @@ -41,11 +40,6 @@ urlpatterns = [ path('assets//perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'), path('assets//perm-user-groups//permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'), - path('system-users//assets//users//account/', api.SystemUserAssetAccountApi.as_view(), name='system-user-asset-account'), - path('system-users//assets//users//account-secret/', api.SystemUserAssetAccountSecretApi.as_view(), name='system-user-asset-account-secret'), - path('system-users//cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), - path('cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='cmd-filter-rules'), - path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'), path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), @@ -65,9 +59,5 @@ urlpatterns = [ ] -old_version_urlpatterns = [ - re_path('(?Padmin-user|system-user|domain|gateway|cmd-filter|asset-user)/.*', capi.redirect_plural_name_api) -] - -urlpatterns += router.urls + cmd_filter_router.urls + old_version_urlpatterns +urlpatterns += router.urls + cmd_filter_router.urls diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index b944e84e3..d0b25d769 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -6,7 +6,7 @@ from django.db.models import F, TextChoices from orgs.mixins.models import OrgModelMixin from common.db import models from common.utils import lazyproperty -from assets.models import Asset, SystemUser, Node, FamilyMixin +from assets.models import Asset, Node, FamilyMixin from .base import BasePermission From 109db8886b20d40df9ab546b5dfad76376ffc8fb Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 28 Jul 2022 19:27:42 +0800 Subject: [PATCH 036/488] =?UTF-8?q?perf:=20=E8=BF=98=E5=8E=9F=E5=9B=9E=20m?= =?UTF-8?q?odel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/models/application.py | 2 +- apps/assets/api/__init__.py | 2 - apps/assets/api/asset.py | 4 +- apps/assets/api/cmd_filter.py | 85 --------- apps/assets/models/__init__.py | 1 + apps/assets/models/cmd_filter.py | 226 ++++++++++++++++++++++++ apps/assets/serializers/__init__.py | 1 - apps/assets/serializers/cmd_filter.py | 110 ------------ apps/assets/signal_handlers/asset.py | 17 +- apps/assets/tasks/__init__.py | 2 - apps/assets/tasks/common.py | 36 ---- apps/assets/urls/api_urls.py | 9 +- apps/orgs/signal_handlers/common.py | 3 +- 13 files changed, 240 insertions(+), 258 deletions(-) delete mode 100644 apps/assets/api/cmd_filter.py create mode 100644 apps/assets/models/cmd_filter.py delete mode 100644 apps/assets/serializers/cmd_filter.py diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index 000094b4f..af1e27c2d 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -9,7 +9,7 @@ from orgs.mixins.models import OrgModelMixin from common.mixins import CommonModelMixin from common.tree import TreeNode from common.utils import is_uuid -from assets.models import Asset +from assets.models import Asset, SystemUser from ..utils import KubernetesTree from .. import const diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index e8b06b537..b8cc9e7d8 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -1,11 +1,9 @@ from .mixin import * from .asset import * from .label import * -from .system_user import * from .accounts import * from .node import * from .domain import * -from .cmd_filter import * from .gathered_user import * from .favorite_asset import * from .account_backup import * diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index a930bfd41..d8ff1547f 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -19,8 +19,8 @@ from assets.api import FilterAssetByNodeMixin from ..models import Asset, Node, Platform, Gateway from .. import serializers from ..tasks import ( - update_assets_hardware_info_manual, test_assets_connectivity_manual, - test_system_users_connectivity_a_asset, push_system_users_a_asset + update_assets_hardware_info_manual, + test_assets_connectivity_manual, ) from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py deleted file mode 100644 index 0e09d5c73..000000000 --- a/apps/assets/api/cmd_filter.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from rest_framework.response import Response -from rest_framework.generics import CreateAPIView -from django.shortcuts import get_object_or_404 - -from common.utils import reverse -from common.utils import lazyproperty -from orgs.mixins.api import OrgBulkModelViewSet -from ..models import CommandFilter, CommandFilterRule -from .. import serializers - -__all__ = [ - 'CommandFilterViewSet', 'CommandFilterRuleViewSet', 'CommandConfirmAPI', -] - - -class CommandFilterViewSet(OrgBulkModelViewSet): - model = CommandFilter - filterset_fields = ("name",) - search_fields = filterset_fields - serializer_class = serializers.CommandFilterSerializer - - -class CommandFilterRuleViewSet(OrgBulkModelViewSet): - model = CommandFilterRule - filterset_fields = ('content',) - search_fields = filterset_fields - serializer_class = serializers.CommandFilterRuleSerializer - - def get_queryset(self): - fpk = self.kwargs.get('filter_pk') - if not fpk: - return CommandFilterRule.objects.none() - cmd_filter = get_object_or_404(CommandFilter, pk=fpk) - return cmd_filter.rules.all() - - -class CommandConfirmAPI(CreateAPIView): - serializer_class = serializers.CommandConfirmSerializer - rbac_perms = { - 'POST': 'tickets.add_superticket' - } - - def create(self, request, *args, **kwargs): - ticket = self.create_command_confirm_ticket() - response_data = self.get_response_data(ticket) - return Response(data=response_data, status=200) - - def create_command_confirm_ticket(self): - ticket = self.serializer.cmd_filter_rule.create_command_confirm_ticket( - 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, - ) - return ticket - - @staticmethod - def get_response_data(ticket): - confirm_status_url = reverse( - view_name='api-tickets:super-ticket-status', - kwargs={'pk': str(ticket.id)} - ) - ticket_detail_url = reverse( - view_name='api-tickets:ticket-detail', - kwargs={'pk': str(ticket.id)}, - external=True, api_to_ui=True - ) - ticket_detail_url = '{url}?type={type}'.format(url=ticket_detail_url, type=ticket.type) - ticket_assignees = ticket.current_step.ticket_assignees.all() - return { - 'check_confirm_status': {'method': 'GET', 'url': confirm_status_url}, - 'close_confirm': {'method': 'DELETE', 'url': confirm_status_url}, - 'ticket_detail_url': ticket_detail_url, - 'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees] - } - - @lazyproperty - def serializer(self): - serializer = self.get_serializer(data=self.request.data) - serializer.is_valid(raise_exception=True) - return serializer - diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 4eeab7b0b..7719ca63a 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -10,3 +10,4 @@ from .favorite_asset import * from .account import * from .backup import * from .user import * +from .cmd_filter import * diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py new file mode 100644 index 000000000..c7fa33aae --- /dev/null +++ b/apps/assets/models/cmd_filter.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +# +import uuid +import re + +from django.db import models +from django.db.models import Q +from django.core.validators import MinValueValidator, MaxValueValidator +from django.utils.translation import ugettext_lazy as _ + +from users.models import User, UserGroup +from applications.models import Application +from ..models import SystemUser, Asset + +from common.utils import lazyproperty, get_logger, get_object_or_none +from orgs.mixins.models import OrgModelMixin + +logger = get_logger(__file__) + +__all__ = [ + 'CommandFilter', 'CommandFilterRule' +] + + +class CommandFilter(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=64, verbose_name=_("Name")) + users = models.ManyToManyField( + 'users.User', related_name='cmd_filters', blank=True, + verbose_name=_("User") + ) + user_groups = models.ManyToManyField( + 'users.UserGroup', related_name='cmd_filters', blank=True, + verbose_name=_("User group"), + ) + assets = models.ManyToManyField( + 'assets.Asset', related_name='cmd_filters', blank=True, + verbose_name=_("Asset") + ) + system_users = models.ManyToManyField( + 'assets.SystemUser', related_name='cmd_filters', blank=True, + verbose_name=_("System user")) + applications = models.ManyToManyField( + 'applications.Application', related_name='cmd_filters', blank=True, + verbose_name=_("Application") + ) + is_active = models.BooleanField(default=True, verbose_name=_('Is active')) + comment = models.TextField(blank=True, default='', verbose_name=_("Comment")) + date_created = models.DateTimeField(auto_now_add=True) + date_updated = models.DateTimeField(auto_now=True) + created_by = models.CharField( + max_length=128, blank=True, default='', verbose_name=_('Created by') + ) + + def __str__(self): + return self.name + + class Meta: + unique_together = [('org_id', 'name')] + verbose_name = _("Command filter") + + +class CommandFilterRule(OrgModelMixin): + TYPE_REGEX = 'regex' + TYPE_COMMAND = 'command' + TYPE_CHOICES = ( + (TYPE_REGEX, _('Regex')), + (TYPE_COMMAND, _('Command')), + ) + + ACTION_UNKNOWN = 10 + + class ActionChoices(models.IntegerChoices): + deny = 0, _('Deny') + allow = 9, _('Allow') + confirm = 2, _('Reconfirm') + + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + filter = models.ForeignKey( + 'CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules' + ) + type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type")) + priority = models.IntegerField( + default=50, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), + validators=[MinValueValidator(1), MaxValueValidator(100)] + ) + content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) + ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case')) + action = models.IntegerField(default=ActionChoices.deny, choices=ActionChoices.choices, verbose_name=_("Action")) + # 动作: 附加字段 + # - confirm: 命令复核人 + reviewers = models.ManyToManyField( + 'users.User', related_name='review_cmd_filter_rules', blank=True, + verbose_name=_("Reviewers") + ) + comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment")) + date_created = models.DateTimeField(auto_now_add=True) + date_updated = models.DateTimeField(auto_now=True) + created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by')) + + class Meta: + ordering = ('priority', 'action') + verbose_name = _("Command filter rule") + + @lazyproperty + def pattern(self): + if self.type == 'command': + s = self.construct_command_regex(content=self.content) + else: + s = r'{0}'.format(self.content) + + return s + + @classmethod + def construct_command_regex(cls, content): + regex = [] + content = content.replace('\r\n', '\n') + for _cmd in content.split('\n'): + cmd = re.sub(r'\s+', ' ', _cmd) + cmd = re.escape(cmd) + cmd = cmd.replace('\\ ', '\s+') + + # 有空格就不能 铆钉单词了 + if ' ' in _cmd: + regex.append(cmd) + continue + + if not cmd: + continue + + # 如果是单个字符 + if cmd[-1].isalpha(): + regex.append(r'\b{0}\b'.format(cmd)) + else: + regex.append(r'\b{0}'.format(cmd)) + s = r'{}'.format('|'.join(regex)) + return s + + @staticmethod + def compile_regex(regex, ignore_case): + try: + if ignore_case: + pattern = re.compile(regex, re.IGNORECASE) + else: + pattern = re.compile(regex) + except Exception as e: + error = _('The generated regular expression is incorrect: {}').format(str(e)) + logger.error(error) + return False, error, None + return True, '', pattern + + def match(self, data): + succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case) + if not succeed: + return self.ACTION_UNKNOWN, '' + + found = pattern.search(data) + if not found: + return self.ACTION_UNKNOWN, '' + + if self.action == self.ActionChoices.allow: + return self.ActionChoices.allow, found.group() + else: + return self.ActionChoices.deny, found.group() + + def __str__(self): + return '{} % {}'.format(self.type, self.content) + + def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): + from tickets.const import TicketType + from tickets.models import ApplyCommandTicket + data = { + 'title': _('Command confirm') + ' ({})'.format(session.user), + 'type': TicketType.command_confirm, + 'applicant': session.user_obj, + 'apply_run_user_id': session.user_id, + 'apply_run_asset': str(session.asset), + 'apply_run_system_user_id': session.system_user_id, + 'apply_run_command': run_command[:4090], + 'apply_from_session_id': str(session.id), + 'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id), + 'apply_from_cmd_filter_id': str(cmd_filter_rule.filter.id), + 'org_id': org_id, + } + ticket = ApplyCommandTicket.objects.create(**data) + assignees = self.reviewers.all() + ticket.open_by_system(assignees) + return ticket + + @classmethod + def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None, + asset_id=None, application_id=None, org_id=None): + user_groups = [] + user = get_object_or_none(User, pk=user_id) + if user: + user_groups.extend(list(user.groups.all())) + user_group = get_object_or_none(UserGroup, pk=user_group_id) + if user_group: + org_id = user_group.org_id + user_groups.append(user_group) + system_user = get_object_or_none(SystemUser, pk=system_user_id) + asset = get_object_or_none(Asset, pk=asset_id) + application = get_object_or_none(Application, pk=application_id) + q = Q() + if user: + q |= Q(users=user) + if user_groups: + q |= Q(user_groups__in=set(user_groups)) + if system_user: + org_id = system_user.org_id + q |= Q(system_users=system_user) + if asset: + org_id = asset.org_id + q |= Q(assets=asset) + if application: + org_id = application.org_id + q |= Q(applications=application) + if q: + cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True) + if org_id: + cmd_filters = cmd_filters.filter(org_id=org_id) + rule_ids = cmd_filters.values_list('rules', flat=True) + rules = cls.objects.filter(id__in=rule_ids) + else: + rules = cls.objects.none() + return rules diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 5c684652c..9f41d1020 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -6,7 +6,6 @@ from .label import * from .system_user import * from .node import * from .domain import * -from .cmd_filter import * from .gathered_user import * from .favorite_asset import * from .account import * diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py deleted file mode 100644 index 9a33dd6fa..000000000 --- a/apps/assets/serializers/cmd_filter.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8 -*- -# -import re -from rest_framework import serializers - -from django.utils.translation import ugettext_lazy as _ -from ..models import CommandFilter, CommandFilterRule -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from orgs.utils import tmp_to_root_org -from common.utils import get_object_or_none, lazyproperty -from terminal.models import Session - - -class CommandFilterSerializer(BulkOrgResourceModelSerializer): - class Meta: - model = CommandFilter - fields_mini = ['id', 'name'] - fields_small = fields_mini + [ - 'org_id', 'org_name', 'is_active', - 'date_created', 'date_updated', - 'comment', 'created_by', - ] - fields_fk = ['rules'] - fields_m2m = ['users', 'user_groups', 'system_users', 'assets', 'applications'] - fields = fields_small + fields_fk + fields_m2m - extra_kwargs = { - 'rules': {'read_only': True}, - 'date_created': {'label': _("Date created")}, - 'date_updated': {'label': _("Date updated")}, - } - - -class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) - action_display = serializers.ReadOnlyField(source='get_action_display', label=_("Action display")) - - class Meta: - model = CommandFilterRule - fields_mini = ['id'] - fields_small = fields_mini + [ - 'type', 'type_display', 'content', 'ignore_case', 'pattern', - 'priority', 'action', 'action_display', 'reviewers', - 'date_created', 'date_updated', 'comment', 'created_by', - ] - fields_fk = ['filter'] - fields = fields_small + fields_fk - extra_kwargs = { - 'date_created': {'label': _("Date created")}, - 'date_updated': {'label': _("Date updated")}, - 'action_display': {'label': _("Action display")}, - 'pattern': {'label': _("Pattern")} - } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_action_choices() - - def set_action_choices(self): - from django.conf import settings - action = self.fields.get('action') - if not action: - return - choices = action._choices - if not settings.XPACK_ENABLED: - choices.pop(CommandFilterRule.ActionChoices.confirm, None) - action._choices = choices - - def validate_content(self, content): - tp = self.initial_data.get("type") - if tp == CommandFilterRule.TYPE_COMMAND: - regex = CommandFilterRule.construct_command_regex(content) - else: - regex = content - ignore_case = self.initial_data.get('ignore_case') - succeed, error, pattern = CommandFilterRule.compile_regex(regex, ignore_case) - if not succeed: - raise serializers.ValidationError(error) - return content - - -class CommandConfirmSerializer(serializers.Serializer): - session_id = serializers.UUIDField(required=True, allow_null=False) - cmd_filter_rule_id = serializers.UUIDField(required=True, allow_null=False) - run_command = serializers.CharField(required=True, allow_null=False) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.session = None - self.cmd_filter_rule = None - - def validate_session_id(self, session_id): - self.session = self.validate_object_exist(Session, session_id) - return session_id - - def validate_cmd_filter_rule_id(self, cmd_filter_rule_id): - self.cmd_filter_rule = self.validate_object_exist(CommandFilterRule, cmd_filter_rule_id) - return cmd_filter_rule_id - - @staticmethod - def validate_object_exist(model, field_id): - with tmp_to_root_org(): - obj = get_object_or_none(model, id=field_id) - if not obj: - error = '{} Model object does not exist'.format(model.__name__) - raise serializers.ValidationError(error) - return obj - - @lazyproperty - def org(self): - return self.session.org diff --git a/apps/assets/signal_handlers/asset.py b/apps/assets/signal_handlers/asset.py index a7e466c9b..5aac26319 100644 --- a/apps/assets/signal_handlers/asset.py +++ b/apps/assets/signal_handlers/asset.py @@ -8,11 +8,10 @@ from django.dispatch import receiver from common.const.signals import POST_ADD, POST_REMOVE, PRE_REMOVE from common.utils import get_logger from common.decorator import on_transaction_commit -from assets.models import Asset, SystemUser, Node +from assets.models import Asset, Node from assets.tasks import ( update_assets_hardware_info_util, test_asset_connectivity_util, - push_system_user_to_assets, ) logger = get_logger(__file__) @@ -77,15 +76,15 @@ def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs): nodes_ancestors_keys.update(Node.get_node_ancestor_keys(node, with_self=True)) # 查询所有祖先节点关联的系统用户,都是要跟资产建立关系的 - system_user_ids = SystemUser.objects.filter( - nodes__key__in=nodes_ancestors_keys - ).distinct().values_list('id', flat=True) + # system_user_ids = SystemUser.objects.filter( + # nodes__key__in=nodes_ancestors_keys + # ).distinct().values_list('id', flat=True) # 查询所有已存在的关系 - m2m_model = SystemUser.assets.through - exist = set(m2m_model.objects.filter( - systemuser_id__in=system_user_ids, asset_id__in=asset_ids - ).values_list('systemuser_id', 'asset_id')) + # m2m_model = SystemUser.assets.through + # exist = set(m2m_model.objects.filter( + # systemuser_id__in=system_user_ids, asset_id__in=asset_ids + # ).values_list('systemuser_id', 'asset_id')) # TODO 优化 # to_create = [] # for system_user_id in system_user_ids: diff --git a/apps/assets/tasks/__init__.py b/apps/assets/tasks/__init__.py index 22ccbf503..fcd5fbe46 100644 --- a/apps/assets/tasks/__init__.py +++ b/apps/assets/tasks/__init__.py @@ -6,7 +6,5 @@ from .asset_connectivity import * from .account_connectivity import * from .gather_asset_users import * from .gather_asset_hardware_info import * -from .push_system_user import * -from .system_user_connectivity import * from .nodes_amount import * from .backup import * diff --git a/apps/assets/tasks/common.py b/apps/assets/tasks/common.py index 6ad17b4c3..ec51c5a2b 100644 --- a/apps/assets/tasks/common.py +++ b/apps/assets/tasks/common.py @@ -1,38 +1,2 @@ # -*- coding: utf-8 -*- # - -from celery import shared_task - -from orgs.utils import tmp_to_root_org -from assets.models import AuthBook - -__all__ = ['add_nodes_assets_to_system_users'] - - -# Todo: 等待优化 -@shared_task -@tmp_to_root_org() -def add_nodes_assets_to_system_users(nodes_keys, system_users): - from ..models import Node - from assets.tasks import push_system_user_to_assets - - nodes = Node.objects.filter(key__in=nodes_keys) - assets = Node.get_nodes_all_assets(*nodes) - - for system_user in system_users: - """ 解决资产和节点进行关联时,已经关联过的节点不会触发 authbook post_save 信号, - 无法更新节点下所有资产的管理用户的问题 """ - need_push_asset_ids = [] - for asset in assets: - defaults = {'asset': asset, 'systemuser': system_user, 'org_id': asset.org_id} - instance, created = AuthBook.objects.update_or_create( - defaults=defaults, asset=asset, systemuser=system_user - ) - if created: - need_push_asset_ids.append(asset.id) - # 不再自动更新资产管理用户,只允许用户手动指定。 - # 只要关联都需要更新资产的管理用户 - # instance.update_asset_admin_user_if_need() - - if need_push_asset_ids: - push_system_user_to_assets.delay(system_user.id, need_push_asset_ids) diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index b312a41b7..d3262bd96 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -20,15 +20,11 @@ router.register(r'labels', api.LabelViewSet, 'label') router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'domains', api.DomainViewSet, 'domain') router.register(r'gateways', api.GatewayViewSet, 'gateway') -router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter') router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user') router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') -cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter') -cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') - urlpatterns = [ path('assets//gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), @@ -54,10 +50,7 @@ urlpatterns = [ path('nodes//tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'), path('gateways//test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), - - path('cmd-filters/command-confirm/', api.CommandConfirmAPI.as_view(), name='command-confirm'), - ] -urlpatterns += router.urls + cmd_filter_router.urls +urlpatterns += router.urls diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index 48576a828..fc8b4af12 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -20,7 +20,6 @@ from common.decorator import on_transaction_commit from common.signals import django_ready from common.utils import get_logger from common.utils.connection import RedisPubSub -from assets.models import CommandFilterRule from users.signals import post_user_leave_org @@ -141,7 +140,7 @@ def _clear_users_from_org(org, users): for m in models: _remove_users(m, users, org) - _remove_users(CommandFilterRule, users, org, user_field_name='reviewers') + # _remove_users(CommandFilterRule, users, org, user_field_name='reviewers') @receiver(post_save, sender=User) From 65423ea893b0b8cd4364355a0ef4ddd0ce1984c1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Aug 2022 15:58:06 +0800 Subject: [PATCH 037/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/models/application.py | 3 +- apps/assets/migrations/0001_initial.py | 9 - .../migrations/0003_auto_20180109_2331.py | 5 - .../migrations/0092_auto_20220711_1409.py | 5 +- .../migrations/0094_auto_20220728_1125.py | 89 --------- .../migrations/0094_auto_20220803_1448.py | 16 ++ apps/assets/models/__init__.py | 5 +- apps/assets/models/_authbook.py | 137 +++++++++++++ apps/assets/models/_user.py | 184 ++++++++++++++++++ apps/assets/models/asset.py | 11 -- apps/assets/models/cmd_filter.py | 2 +- apps/assets/models/user.py | 76 -------- 12 files changed, 346 insertions(+), 196 deletions(-) delete mode 100644 apps/assets/migrations/0094_auto_20220728_1125.py create mode 100644 apps/assets/migrations/0094_auto_20220803_1448.py create mode 100644 apps/assets/models/_authbook.py create mode 100644 apps/assets/models/_user.py delete mode 100644 apps/assets/models/user.py diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py index af1e27c2d..258f2fe6e 100644 --- a/apps/applications/models/application.py +++ b/apps/applications/models/application.py @@ -9,7 +9,8 @@ from orgs.mixins.models import OrgModelMixin from common.mixins import CommonModelMixin from common.tree import TreeNode from common.utils import is_uuid -from assets.models import Asset, SystemUser +from assets.models import Asset +from assets.models._user import SystemUser from ..utils import KubernetesTree from .. import const diff --git a/apps/assets/migrations/0001_initial.py b/apps/assets/migrations/0001_initial.py index 7c0a9e95a..03c945c57 100644 --- a/apps/assets/migrations/0001_initial.py +++ b/apps/assets/migrations/0001_initial.py @@ -16,14 +16,6 @@ def add_default_group(apps, schema_editor): ) -def add_default_cluster(apps, schema_editor): - cluster_model = apps.get_model("assets", "Cluster") - db_alias = schema_editor.connection.alias - cluster_model.objects.using(db_alias).create( - name="Default" - ) - - class Migration(migrations.Migration): initial = True @@ -163,6 +155,5 @@ class Migration(migrations.Migration): unique_together=set([('ip', 'port')]), ), - migrations.RunPython(add_default_cluster), migrations.RunPython(add_default_group), ] diff --git a/apps/assets/migrations/0003_auto_20180109_2331.py b/apps/assets/migrations/0003_auto_20180109_2331.py index 254de6236..097bc607a 100644 --- a/apps/assets/migrations/0003_auto_20180109_2331.py +++ b/apps/assets/migrations/0003_auto_20180109_2331.py @@ -14,9 +14,4 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name='asset', - name='cluster', - field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'), - ), ] diff --git a/apps/assets/migrations/0092_auto_20220711_1409.py b/apps/assets/migrations/0092_auto_20220711_1409.py index efcb59e9d..6515392a0 100644 --- a/apps/assets/migrations/0092_auto_20220711_1409.py +++ b/apps/assets/migrations/0092_auto_20220711_1409.py @@ -1,7 +1,6 @@ # Generated by Django 3.2.12 on 2022-07-11 08:59 import assets.models.base -import assets.models.user import common.db.fields from django.conf import settings from django.db import migrations, models @@ -36,7 +35,7 @@ class Migration(migrations.Migration): ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), - ('version', models.IntegerField(default=1, verbose_name='Version')), + ('version', models.IntegerField(default=0, verbose_name='Version')), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), ('history_change_reason', models.CharField(max_length=100, null=True)), @@ -78,6 +77,6 @@ class Migration(migrations.Migration): 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], 'unique_together': {('username', 'asset')}, }, - bases=(models.Model, assets.models.base.AuthMixin, assets.models.user.ProtocolMixin), + bases=(models.Model, assets.models.base.AuthMixin, assets.models.protocol.ProtocolMixin), ), ] diff --git a/apps/assets/migrations/0094_auto_20220728_1125.py b/apps/assets/migrations/0094_auto_20220728_1125.py deleted file mode 100644 index adc477c1c..000000000 --- a/apps/assets/migrations/0094_auto_20220728_1125.py +++ /dev/null @@ -1,89 +0,0 @@ -# Generated by Django 3.2.14 on 2022-07-28 03:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0093_auto_20220711_1413'), - ] - - operations = [ - migrations.RemoveField( - model_name='cluster', - name='admin_user', - ), - migrations.RemoveField( - model_name='systemuser', - name='ad_domain', - ), - migrations.RemoveField( - model_name='systemuser', - name='assets', - ), - migrations.RemoveField( - model_name='systemuser', - name='auto_push', - ), - migrations.RemoveField( - model_name='systemuser', - name='groups', - ), - migrations.RemoveField( - model_name='systemuser', - name='home', - ), - migrations.RemoveField( - model_name='systemuser', - name='nodes', - ), - migrations.RemoveField( - model_name='systemuser', - name='priority', - ), - migrations.RemoveField( - model_name='systemuser', - name='sftp_root', - ), - migrations.RemoveField( - model_name='systemuser', - name='shell', - ), - migrations.RemoveField( - model_name='systemuser', - name='sudo', - ), - migrations.RemoveField( - model_name='systemuser', - name='system_groups', - ), - migrations.RemoveField( - model_name='systemuser', - name='token', - ), - migrations.RemoveField( - model_name='systemuser', - name='type', - ), - migrations.RemoveField( - model_name='systemuser', - name='users', - ), - migrations.AlterField( - model_name='historicalaccount', - name='version', - field=models.IntegerField(default=0, verbose_name='Version'), - ), - migrations.AlterField( - model_name='systemuser', - name='login_mode', - field=models.CharField(choices=[('auto', '使用账号'), ('manual', 'Manually input')], default='auto', max_length=10, verbose_name='Login mode'), - ), - migrations.DeleteModel( - name='AdminUser', - ), - migrations.DeleteModel( - name='Cluster', - ), - ] diff --git a/apps/assets/migrations/0094_auto_20220803_1448.py b/apps/assets/migrations/0094_auto_20220803_1448.py new file mode 100644 index 000000000..eafb9caca --- /dev/null +++ b/apps/assets/migrations/0094_auto_20220803_1448.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.14 on 2022-08-03 06:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0093_auto_20220711_1413'), + ] + + operations = [ + migrations.DeleteModel( + name='Cluster', + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 7719ca63a..e433a95ef 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,4 +1,5 @@ from .base import * +from ._user import * from .asset import * from .label import Label from .group import * @@ -9,5 +10,7 @@ from .gathered_user import * from .favorite_asset import * from .account import * from .backup import * -from .user import * +# 废弃以下 +from ._authbook import * +from .protocol import * from .cmd_filter import * diff --git a/apps/assets/models/_authbook.py b/apps/assets/models/_authbook.py new file mode 100644 index 000000000..e96196d22 --- /dev/null +++ b/apps/assets/models/_authbook.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# + +from django.db import models +from django.db.models import F +from django.utils.translation import ugettext_lazy as _ +from simple_history.models import HistoricalRecords + +from common.utils import lazyproperty, get_logger +from .base import BaseUser, AbsConnectivity + +logger = get_logger(__name__) + + +__all__ = ['AuthBook'] + + +class AuthBook(BaseUser, AbsConnectivity): + asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) + systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) + version = models.IntegerField(default=1, verbose_name=_('Version')) + history = HistoricalRecords() + + auth_attrs = ['username', 'password', 'private_key', 'public_key'] + + class Meta: + verbose_name = _('AuthBook') + unique_together = [('username', 'asset', 'systemuser')] + permissions = [ + ('test_authbook', _('Can test asset account connectivity')), + ('view_assetaccountsecret', _('Can view asset account secret')), + ('change_assetaccountsecret', _('Can change asset account secret')), + ('view_assethistoryaccount', _('Can view asset history account')), + ('view_assethistoryaccountsecret', _('Can view asset history account secret')), + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.auth_snapshot = {} + + def get_or_systemuser_attr(self, attr): + val = getattr(self, attr, None) + if val: + return val + if self.systemuser: + return getattr(self.systemuser, attr, '') + return '' + + def load_auth(self): + for attr in self.auth_attrs: + value = self.get_or_systemuser_attr(attr) + self.auth_snapshot[attr] = [getattr(self, attr), value] + setattr(self, attr, value) + + def unload_auth(self): + if not self.systemuser: + return + + for attr, values in self.auth_snapshot.items(): + origin_value, loaded_value = values + current_value = getattr(self, attr, '') + if current_value == loaded_value: + setattr(self, attr, origin_value) + + def save(self, *args, **kwargs): + self.unload_auth() + instance = super().save(*args, **kwargs) + self.load_auth() + return instance + + @property + def username_display(self): + return self.get_or_systemuser_attr('username') or '*' + + @lazyproperty + def systemuser_display(self): + if not self.systemuser: + return '' + return str(self.systemuser) + + @property + def smart_name(self): + username = self.username_display + + if self.asset: + asset = str(self.asset) + else: + asset = '*' + return '{}@{}'.format(username, asset) + + def sync_to_system_user_account(self): + if self.systemuser: + return + matched = AuthBook.objects.filter( + asset=self.asset, systemuser__username=self.username + ) + if not matched: + return + + for i in matched: + i.password = self.password + i.private_key = self.private_key + i.public_key = self.public_key + i.comment = 'Update triggered by account {}'.format(self.id) + + # 不触发post_save信号 + self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key']) + + def remove_asset_admin_user_if_need(self): + if not self.asset or not self.systemuser: + return + if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser: + return + self.asset.admin_user = None + self.asset.save() + logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser)) + + def update_asset_admin_user_if_need(self): + if not self.asset or not self.systemuser: + return + if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser: + return + self.asset.admin_user = self.systemuser + self.asset.save() + logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) + + @classmethod + def get_queryset(cls): + queryset = cls.objects.all() \ + .annotate(ip=F('asset__ip')) \ + .annotate(hostname=F('asset__hostname')) \ + .annotate(platform=F('asset__platform__name')) \ + .annotate(protocols=F('asset__protocols')) + return queryset + + def __str__(self): + return self.smart_name diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py new file mode 100644 index 000000000..565513c11 --- /dev/null +++ b/apps/assets/models/_user.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# + +import logging + +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.core.validators import MinValueValidator, MaxValueValidator + +from .base import BaseUser +from .protocol import ProtocolMixin + + +__all__ = ['SystemUser'] +logger = logging.getLogger(__name__) + + +class SystemUser(ProtocolMixin, BaseUser): + LOGIN_AUTO = 'auto' + LOGIN_MANUAL = 'manual' + LOGIN_MODE_CHOICES = ( + (LOGIN_AUTO, _('Automatic managed')), + (LOGIN_MANUAL, _('Manually input')) + ) + + class Type(models.TextChoices): + common = 'common', _('Common user') + admin = 'admin', _('Admin user') + + username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user")) + nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) + assets = models.ManyToManyField( + 'assets.Asset', blank=True, verbose_name=_("Assets"), + through='assets.AuthBook', through_fields=['systemuser', 'asset'], + related_name='system_users' + ) + users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users")) + groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) + type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) + priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) + protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) + auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) + sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) + shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) + login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) + sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) + token = models.TextField(default='', verbose_name=_('Token')) + home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) + system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True) + ad_domain = models.CharField(default='', max_length=256) + # linux su 命令 (switch user) + su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) + su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) + + def __str__(self): + username = self.username + if self.username_same_with_user: + username = '*' + return '{0.name}({1})'.format(self, username) + + @property + def nodes_amount(self): + return self.nodes.all().count() + + @property + def login_mode_display(self): + return self.get_login_mode_display() + + def is_need_push(self): + if self.auto_push and self.is_protocol_support_push: + return True + else: + return False + + @property + def is_admin_user(self): + return self.type == self.Type.admin + + @property + def is_need_cmd_filter(self): + return self.protocol not in [self.Protocol.rdp, self.Protocol.vnc] + + @property + def is_need_test_asset_connective(self): + return self.protocol in self.ASSET_CATEGORY_PROTOCOLS + + @property + def cmd_filter_rules(self): + from .cmd_filter import CommandFilterRule + rules = CommandFilterRule.objects.filter( + filter__in=self.cmd_filters.all() + ).distinct() + return rules + + def is_command_can_run(self, command): + for rule in self.cmd_filter_rules: + action, matched_cmd = rule.match(command) + if action == rule.ActionChoices.allow: + return True, None + elif action == rule.ActionChoices.deny: + return False, matched_cmd + return True, None + + def get_all_assets(self): + from assets.models import Node, Asset + nodes_keys = self.nodes.all().values_list('key', flat=True) + asset_ids = set(self.assets.all().values_list('id', flat=True)) + nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys) + asset_ids.update(nodes_asset_ids) + assets = Asset.objects.filter(id__in=asset_ids) + return assets + + def add_related_assets(self, assets_or_ids): + self.assets.add(*tuple(assets_or_ids)) + self.add_related_assets_to_su_from_if_need(assets_or_ids) + + def add_related_assets_to_su_from_if_need(self, assets_or_ids): + if self.protocol not in [self.Protocol.ssh.value]: + return + if not self.su_enabled: + return + if not self.su_from: + return + if self.su_from.protocol != self.protocol: + return + self.su_from.assets.add(*tuple(assets_or_ids)) + + class Meta: + ordering = ['name'] + unique_together = [('name', 'org_id')] + verbose_name = _("System user") + permissions = [ + ('match_systemuser', _('Can match system user')), + ] + + +# Deprecated: 准备废弃 +class AdminUser(BaseUser): + """ + A privileged user that ansible can use it to push system user and so on + """ + BECOME_METHOD_CHOICES = ( + ('sudo', 'sudo'), + ('su', 'su'), + ) + become = models.BooleanField(default=True) + become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) + become_user = models.CharField(default='root', max_length=64) + _become_pass = models.CharField(default='', blank=True, max_length=128) + CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}' + _prefer = "admin_user" + + def __str__(self): + return self.name + + @property + def become_pass(self): + password = signer.unsign(self._become_pass) + if password: + return password + else: + return "" + + @become_pass.setter + def become_pass(self, password): + self._become_pass = signer.sign(password) + + @property + def become_info(self): + if self.become: + info = { + "method": self.become_method, + "user": self.become_user, + "pass": self.become_pass, + } + else: + info = None + return info + + class Meta: + ordering = ['name'] + unique_together = [('name', 'org_id')] + verbose_name = _("Admin user") diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 157895dae..1eedc21dd 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -9,7 +9,6 @@ from collections import OrderedDict from django.db import models from django.utils.translation import ugettext_lazy as _ -from rest_framework.exceptions import ValidationError from common.db.fields import JsonDictTextField from common.utils import lazyproperty @@ -21,16 +20,6 @@ __all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet'] logger = logging.getLogger(__name__) -def default_cluster(): - from .cluster import Cluster - name = "Default" - defaults = {"name": name} - cluster, created = Cluster.objects.get_or_create( - defaults=defaults, name=name - ) - return cluster.id - - def default_node(): try: from .node import Node diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index c7fa33aae..86e613d3d 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -9,7 +9,6 @@ from django.core.validators import MinValueValidator, MaxValueValidator from django.utils.translation import ugettext_lazy as _ from users.models import User, UserGroup -from applications.models import Application from ..models import SystemUser, Asset from common.utils import lazyproperty, get_logger, get_object_or_none @@ -190,6 +189,7 @@ class CommandFilterRule(OrgModelMixin): @classmethod def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None, asset_id=None, application_id=None, org_id=None): + from applications.models import Application user_groups = [] user = get_object_or_none(User, pk=user_id) if user: diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py deleted file mode 100644 index 79d6e96e2..000000000 --- a/apps/assets/models/user.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# - -import logging - -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.shortcuts import get_object_or_404 -from django.core.cache import cache - -from .base import BaseUser -from .protocol import ProtocolMixin - - -__all__ = ['SystemUser'] -logger = logging.getLogger(__name__) - - -class SystemUser(ProtocolMixin, BaseUser): - LOGIN_AUTO = 'auto' - LOGIN_MANUAL = 'manual' - LOGIN_MODE_CHOICES = ( - (LOGIN_AUTO, _('使用账号')), - (LOGIN_MANUAL, _('Manually input')) - ) - - username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user")) - protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) - login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) - - # linux su 命令 (switch user) - # Todo: 修改为 username, 不必系统用户了 - su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) - su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) - - def __str__(self): - username = self.username - if self.username_same_with_user: - username = '*' - return '{0.name}({1})'.format(self, username) - - @classmethod - def create_accounts_with_assets(cls, asset_ids, system_user_ids): - pass - - def get_manual_account(self, user_id, asset_id): - cache_key = 'manual_account_{}_{}_{}'.format(self.id, user_id, asset_id) - return cache.get(cache_key) - - def create_manual_account(self, user_id, asset_id, account, ttl=300): - cache_key = 'manual_account_{}_{}_{}'.format(self.id, user_id, asset_id) - cache.set(cache_key, account, ttl) - - def get_auto_account(self, user_id, asset_id): - from .account import Account - from users.models import User - username = self.username - if self.username_same_with_user: - user = get_object_or_404(User, id=user_id) - username = user.username - return get_object_or_404(Account, asset_id=asset_id, username=username) - - def get_account(self, user_id, asset_id): - if self.login_mode == self.LOGIN_MANUAL: - return self.get_manual_account(user_id, asset_id) - else: - return self.get_auto_account(user_id, asset_id) - - class Meta: - ordering = ['name'] - unique_together = [('name', 'org_id')] - verbose_name = _("System user") - permissions = [ - ('match_systemuser', _('Can match system user')), - ] From 6c57db0897e5945d8fe26020938a6cee4286bf7f Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 4 Aug 2022 10:44:11 +0800 Subject: [PATCH 038/488] stash it --- apps/assets/api/asset/common.py | 1 - apps/assets/migrations/0090_add_host.py | 20 --- .../migrations/0091_auto_20220401_1558.py | 40 ------ .../{0092_hardware.py => 0092_add_host.py} | 12 +- .../migrations/0093_auto_20220403_1627.py | 25 +++- .../0094_alter_systemuser_assets.py.bak | 22 --- .../migrations/0094_auto_20220803_1448.py | 16 --- .../migrations/0095_auto_20220713_1746.py.bak | 45 ------ .../migrations/0096_auto_20220714_1627.py.bak | 23 ---- ...711_1409.py => 0101_auto_20220711_1409.py} | 2 +- ...711_1413.py => 0102_auto_20220711_1413.py} | 2 +- .../migrations/0103_auto_20220803_1448.py | 51 +++++++ .../migrations/0104_auto_20220803_1859.py | 42 ++++++ apps/assets/models/_user.py | 2 +- apps/assets/models/asset/common.py | 107 +-------------- apps/assets/models/protocol.py | 4 +- apps/assets/serializers/__init__.py | 1 - apps/assets/serializers/system_user.py | 128 ------------------ apps/rbac/api/role.py | 4 +- ...407_1726.py => 0052_auto_20220707_1726.py} | 2 +- 20 files changed, 137 insertions(+), 412 deletions(-) delete mode 100644 apps/assets/migrations/0090_add_host.py delete mode 100644 apps/assets/migrations/0091_auto_20220401_1558.py rename apps/assets/migrations/{0092_hardware.py => 0092_add_host.py} (86%) delete mode 100644 apps/assets/migrations/0094_alter_systemuser_assets.py.bak delete mode 100644 apps/assets/migrations/0094_auto_20220803_1448.py delete mode 100644 apps/assets/migrations/0095_auto_20220713_1746.py.bak delete mode 100644 apps/assets/migrations/0096_auto_20220714_1627.py.bak rename apps/assets/migrations/{0092_auto_20220711_1409.py => 0101_auto_20220711_1409.py} (99%) rename apps/assets/migrations/{0093_auto_20220711_1413.py => 0102_auto_20220711_1413.py} (97%) create mode 100644 apps/assets/migrations/0103_auto_20220803_1448.py create mode 100644 apps/assets/migrations/0104_auto_20220803_1859.py delete mode 100644 apps/assets/serializers/system_user.py rename apps/terminal/migrations/{0048_auto_20220407_1726.py => 0052_auto_20220707_1726.py} (92%) diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index fb949f7ef..9b7b39a25 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -12,7 +12,6 @@ from assets.models import Asset, Node, Gateway from assets import serializers from assets.tasks import ( update_assets_hardware_info_manual, test_assets_connectivity_manual, - test_system_users_connectivity_a_asset, push_system_users_a_asset ) from assets.filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend diff --git a/apps/assets/migrations/0090_add_host.py b/apps/assets/migrations/0090_add_host.py deleted file mode 100644 index 4ffeb6553..000000000 --- a/apps/assets/migrations/0090_add_host.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 3.1.14 on 2022-03-30 10:35 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0089_auto_20220310_0616'), - ] - - operations = [ - migrations.CreateModel( - name='Host', - fields=[ - ('asset_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assets.asset')), - ], - ), - ] diff --git a/apps/assets/migrations/0091_auto_20220401_1558.py b/apps/assets/migrations/0091_auto_20220401_1558.py deleted file mode 100644 index e3275d4d3..000000000 --- a/apps/assets/migrations/0091_auto_20220401_1558.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 3.1.14 on 2022-04-01 07:58 - -from django.db import migrations, models -import django.db.models.deletion - - -def migrate_to_host(apps, schema_editor): - asset_model = apps.get_model("assets", "Asset") - host_model = apps.get_model("assets", 'Host') - db_alias = schema_editor.connection.alias - - created = 0 - batch_size = 1000 - - while True: - start = created - end = created + batch_size - assets = asset_model.objects.using(db_alias).all()[start:end] - if not assets: - break - - hosts = [host_model(asset_ptr=asset) for asset in assets] - host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True) - created += len(hosts) - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0090_add_host'), - ] - - operations = [ - migrations.AlterField( - model_name='host', - name='asset_ptr', - field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset'), - ), - migrations.RunPython(migrate_to_host) - ] diff --git a/apps/assets/migrations/0092_hardware.py b/apps/assets/migrations/0092_add_host.py similarity index 86% rename from apps/assets/migrations/0092_hardware.py rename to apps/assets/migrations/0092_add_host.py index a5e860a6d..9e2936e3f 100644 --- a/apps/assets/migrations/0092_hardware.py +++ b/apps/assets/migrations/0092_add_host.py @@ -1,17 +1,23 @@ -# Generated by Django 3.1.14 on 2022-04-02 09:09 +# Generated by Django 3.1.14 on 2022-03-30 10:35 +import uuid from django.db import migrations, models import django.db.models.deletion -import uuid class Migration(migrations.Migration): dependencies = [ - ('assets', '0091_auto_20220401_1558'), + ('assets', '0091_auto_20220629_1826'), ] operations = [ + migrations.CreateModel( + name='Host', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ], + ), migrations.CreateModel( name='DeviceInfo', fields=[ diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py index 27e5f11ee..64e79ccda 100644 --- a/apps/assets/migrations/0093_auto_20220403_1627.py +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -41,12 +41,33 @@ def migrate_hardware(apps, *args): created += len(hardware_infos) +def migrate_to_host(apps, schema_editor): + asset_model = apps.get_model("assets", "Asset") + host_model = apps.get_model("assets", 'Host') + db_alias = schema_editor.connection.alias + + created = 0 + batch_size = 1000 + + while True: + start = created + end = created + batch_size + assets = asset_model.objects.using(db_alias).all()[start:end] + if not assets: + break + + hosts = [host_model(asset_ptr=asset) for asset in assets] + host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True) + created += len(hosts) + + class Migration(migrations.Migration): dependencies = [ - ('assets', '0092_hardware'), + ('assets', '0092_add_host'), ] operations = [ - migrations.RunPython(migrate_hardware) + migrations.RunPython(migrate_to_host), + migrations.RunPython(migrate_hardware), ] diff --git a/apps/assets/migrations/0094_alter_systemuser_assets.py.bak b/apps/assets/migrations/0094_alter_systemuser_assets.py.bak deleted file mode 100644 index c3f2f2cfc..000000000 --- a/apps/assets/migrations/0094_alter_systemuser_assets.py.bak +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.12 on 2022-07-13 09:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0093_auto_20220711_1413'), - ] - - operations = [ - migrations.RemoveField( - model_name='systemuser', - name='assets', - ), - migrations.AddField( - model_name='systemuser', - name='assets', - field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.Asset', verbose_name='Assets'), - ), - ] diff --git a/apps/assets/migrations/0094_auto_20220803_1448.py b/apps/assets/migrations/0094_auto_20220803_1448.py deleted file mode 100644 index eafb9caca..000000000 --- a/apps/assets/migrations/0094_auto_20220803_1448.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-03 06:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0093_auto_20220711_1413'), - ] - - operations = [ - migrations.DeleteModel( - name='Cluster', - ), - ] diff --git a/apps/assets/migrations/0095_auto_20220713_1746.py.bak b/apps/assets/migrations/0095_auto_20220713_1746.py.bak deleted file mode 100644 index b803dae51..000000000 --- a/apps/assets/migrations/0095_auto_20220713_1746.py.bak +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 3.2.12 on 2022-07-13 09:46 - -import time -from django.db import migrations - - -def migrate_asset_system_user_relations(apps, schema_editor): - system_user_model = apps.get_model('assets', 'SystemUser') - old_model = apps.get_model('assets', 'AuthBook') - new_model = system_user_model.assets.through - - count = 0 - bulk_size = 1000 - print("\nStart migrate asset system user relations") - while True: - start = time.time() - auth_books = old_model.objects.only('asset_id', 'systemuser_id')[count:count+bulk_size] - auth_books = list(auth_books) - count += len(auth_books) - if not auth_books: - break - asset_system_users = [] - for auth_book in auth_books: - if not auth_book.asset_id or not auth_book.systemuser_id: - continue - asset_system_user = new_model( - asset_id=auth_book.asset_id, - systemuser_id=auth_book.systemuser_id - ) - asset_system_users.append(asset_system_user) - new_model.objects.bulk_create(asset_system_users, ignore_conflicts=True) - print("Create asset system user relations: {}-{} using: {:.2f}s".format( - count - bulk_size, count, time.time()-start - )) - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0094_alter_systemuser_assets'), - ] - - operations = [ - migrations.RunPython(migrate_asset_system_user_relations) - ] diff --git a/apps/assets/migrations/0096_auto_20220714_1627.py.bak b/apps/assets/migrations/0096_auto_20220714_1627.py.bak deleted file mode 100644 index 274b2d8ef..000000000 --- a/apps/assets/migrations/0096_auto_20220714_1627.py.bak +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.12 on 2022-07-14 08:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0095_auto_20220713_1746'), - ] - - operations = [ - migrations.RenameField( - model_name='systemuser', - old_name='auto_push', - new_name='auto_push_account', - ), - migrations.AddField( - model_name='systemuser', - name='account_template_enabled', - field=models.BooleanField(default=False, verbose_name='Auto account if not exist'), - ), - ] diff --git a/apps/assets/migrations/0092_auto_20220711_1409.py b/apps/assets/migrations/0101_auto_20220711_1409.py similarity index 99% rename from apps/assets/migrations/0092_auto_20220711_1409.py rename to apps/assets/migrations/0101_auto_20220711_1409.py index 6515392a0..ff3b1a1c6 100644 --- a/apps/assets/migrations/0092_auto_20220711_1409.py +++ b/apps/assets/migrations/0101_auto_20220711_1409.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0091_auto_20220629_1826'), + ('assets', '0100_auto_20220430_2126'), ] operations = [ diff --git a/apps/assets/migrations/0093_auto_20220711_1413.py b/apps/assets/migrations/0102_auto_20220711_1413.py similarity index 97% rename from apps/assets/migrations/0093_auto_20220711_1413.py rename to apps/assets/migrations/0102_auto_20220711_1413.py index ba7d7e0c0..bc74f336c 100644 --- a/apps/assets/migrations/0093_auto_20220711_1413.py +++ b/apps/assets/migrations/0102_auto_20220711_1413.py @@ -57,7 +57,7 @@ def migrate_accounts(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('assets', '0092_auto_20220711_1409'), + ('assets', '0101_auto_20220711_1409'), ] operations = [ diff --git a/apps/assets/migrations/0103_auto_20220803_1448.py b/apps/assets/migrations/0103_auto_20220803_1448.py new file mode 100644 index 000000000..7af3514e2 --- /dev/null +++ b/apps/assets/migrations/0103_auto_20220803_1448.py @@ -0,0 +1,51 @@ +# Generated by Django 3.2.14 on 2022-08-03 10:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0102_auto_20220711_1413'), + ] + + operations = [ + migrations.CreateModel( + name='Protocol', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32, verbose_name='Name')), + ('port', models.IntegerField(verbose_name='Port')), + ], + ), + migrations.RemoveField( + model_name='asset', + name='port', + ), + migrations.RemoveField( + model_name='asset', + name='protocol', + ), + migrations.AddField( + model_name='asset', + name='_protocols', + field=models.CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'), + ), + migrations.RemoveField( + model_name='asset', + name='protocols', + ), + migrations.AlterField( + model_name='systemuser', + name='protocol', + field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'), + ), + migrations.AddField( + model_name='asset', + name='protocols', + field=models.ManyToManyField(blank=True, to='assets.Protocol', verbose_name='Protocols'), + ), + migrations.DeleteModel( + name='Cluster', + ), + ] diff --git a/apps/assets/migrations/0104_auto_20220803_1859.py b/apps/assets/migrations/0104_auto_20220803_1859.py new file mode 100644 index 000000000..ecd75743b --- /dev/null +++ b/apps/assets/migrations/0104_auto_20220803_1859.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.14 on 2022-08-03 10:59 +import time +from django.db import migrations + + +def migrate_asset_protocols(apps, schema_editor): + asset_model = apps.get_model('assets', 'Asset') + protocol_model = apps.get_model('assets', 'Protocol') + + count = 0 + bulk_size = 1000 + print("\nStart migrate asset protocols") + while True: + start = time.time() + assets = asset_model.objects.all()[count:count+bulk_size] + count += len(assets) + if not assets: + break + + protocols = [] + for asset in assets: + for protocol in asset.protocols.all(): + protocols.append(protocol_model( + asset_id=asset.id, + protocol=protocol.protocol, + port=protocol.port, + )) + protocol_model.objects.bulk_create(protocols, ignore_conflicts=True) + print("Create asset protocols: {}-{} using: {:.2f}s".format( + count - bulk_size, count, time.time()-start + )) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0103_auto_20220803_1448'), + ] + + operations = [ + migrations.RunPython(migrate_asset_protocols) + ] diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index 6baffa3d0..94e38b1e4 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -41,7 +41,7 @@ class SystemUser(ProtocolMixin, BaseUser): groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) - protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol')) + protocol = models.CharField(max_length=16, default='ssh', verbose_name=_('Protocol')) auto_push = models.BooleanField(default=True, verbose_name=_('Auto push')) sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 56348f4b2..e24c7e80d 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -5,19 +5,17 @@ import uuid import logging from functools import reduce -from collections import OrderedDict from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.db.fields import JsonDictTextField from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager from assets.const import Category, AllTypes from ..platform import Platform from ..base import AbsConnectivity -__all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster'] +__all__ = ['Asset', 'AssetQuerySet', 'default_node'] logger = logging.getLogger(__name__) @@ -45,49 +43,6 @@ class AssetQuerySet(models.QuerySet): return self.filter(protocols__contains=name) -class ProtocolsMixin: - protocols = '' - - class Protocol(models.TextChoices): - ssh = 'ssh', 'SSH' - rdp = 'rdp', 'RDP' - telnet = 'telnet', 'Telnet' - vnc = 'vnc', 'VNC' - - @property - def protocols_as_list(self): - if not self.protocols: - return [] - return self.protocols.split(' ') - - @property - def protocols_as_dict(self): - d = OrderedDict() - protocols = self.protocols_as_list - for i in protocols: - if '/' not in i: - continue - name, port = i.split('/')[:2] - if not all([name, port]): - continue - d[name] = int(port) - return d - - @property - def protocols_as_json(self): - return [ - {"name": name, "port": port} - for name, port in self.protocols_as_dict.items() - ] - - def has_protocol(self, name): - return name in self.protocols_as_dict - - @property - def ssh_port(self): - return self.protocols_as_dict.get("ssh", 22) - - class NodesRelationMixin: NODES_CACHE_KEY = 'ASSET_NODES_{}' ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES' @@ -112,17 +67,15 @@ class NodesRelationMixin: return nodes -class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): +class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): Category = Category id = models.UUIDField(default=uuid.uuid4, primary_key=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category")) type = models.CharField(max_length=128, choices=AllTypes.choices, verbose_name=_("Type")) - protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh, - choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol')) - port = models.IntegerField(default=22, verbose_name=_('Port')) - protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols")) + _protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols")) + protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocols"), blank=True) platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', @@ -134,7 +87,6 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): # Auth admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_("Admin user"), related_name='admin_assets') - # Some information public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP')) number = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Asset number')) @@ -178,55 +130,6 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): """ return self.admin_user.username - def is_windows(self): - return self.platform.is_windows() - - def is_unixlike(self): - return self.platform.is_unixlike() - - def is_support_ansible(self): - return self.has_protocol('ssh') and self.platform_base not in ("Other",) - - def get_auth_info(self, with_become=False): - if not self.admin_user: - return {} - - if self.is_unixlike() and self.admin_user.su_enabled and self.admin_user.su_from: - auth_user = self.admin_user.su_from - become_user = self.admin_user - else: - auth_user = self.admin_user - become_user = None - - auth_user.load_asset_special_auth(self) - info = { - 'username': auth_user.username, - 'password': auth_user.password, - 'private_key': auth_user.private_key_file - } - - if not with_become or self.is_windows(): - return info - - if become_user: - become_user.load_asset_special_auth(self) - become_method = 'su' - become_username = become_user.username - become_pass = become_user.password - else: - become_method = 'sudo' - become_username = 'root' - become_pass = auth_user.password - become_info = { - 'become': { - 'method': become_method, - 'username': become_username, - 'pass': become_pass - } - } - info.update(become_info) - return info - def nodes_display(self): names = [] for n in self.nodes.all(): @@ -270,7 +173,7 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin): 'id': self.id, 'hostname': self.hostname, 'ip': self.ip, - 'protocols': self.protocols_as_list, + 'protocols': self.protocols, 'platform': self.platform_base, } } diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py index b6bf4cf6a..22d330339 100644 --- a/apps/assets/models/protocol.py +++ b/apps/assets/models/protocol.py @@ -1,10 +1,8 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from common.db.models import JMSBaseModel - -class Protocol(JMSBaseModel): +class Protocol(models.Model): name = models.CharField(max_length=32, verbose_name=_("Name")) port = models.IntegerField(verbose_name=_("Port")) diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index f29c81d21..ded671746 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -3,7 +3,6 @@ from .asset import * from .label import * -from .system_user import * from .node import * from .domain import * from .gathered_user import * diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py deleted file mode 100644 index f4ca404f3..000000000 --- a/apps/assets/serializers/system_user.py +++ /dev/null @@ -1,128 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re -from orgs.mixins.serializers import BulkOrgResourceModelSerializer - -__all__ = [ - 'SystemUserSerializer', 'MiniSystemUserSerializer', - 'SystemUserSimpleSerializer', -] - - -class SystemUserSerializer(BulkOrgResourceModelSerializer): - """ - 系统用户 - """ - - class Meta: - model = SystemUser - fields_mini = ['id', 'name', 'username', 'protocol'] - fields_small = fields_mini + [ - 'login_mode', 'su_enabled', 'su_from', - 'date_created', 'date_updated', 'comment', - 'created_by', - ] - fields = fields_small - extra_kwargs = { - 'cmd_filters': {"required": False, 'label': _('Command filter')}, - 'login_mode_display': {'label': _('Login mode display')}, - 'created_by': {'read_only': True}, - 'ad_domain': {'required': False, 'allow_blank': True, 'label': _('Ad domain')}, - 'su_from': {'help_text': _('Only ssh and automatic login system users are supported')} - } - - def validate_username_same_with_user(self, username_same_with_user): - if not username_same_with_user: - return username_same_with_user - protocol = self.get_initial_value("protocol", "ssh") - queryset = SystemUser.objects.filter( - protocol=protocol, - username_same_with_user=True - ) - if self.instance: - queryset = queryset.exclude(id=self.instance.id) - exists = queryset.exists() - if not exists: - return username_same_with_user - error = _("Username same with user with protocol {} only allow 1").format(protocol) - raise serializers.ValidationError(error) - - def validate_username(self, username): - protocol = self.get_initial_value("protocol") - if username: - if protocol == Protocol.telnet: - regx = alphanumeric_cn_re - elif protocol == Protocol.rdp: - regx = alphanumeric_win_re - else: - regx = alphanumeric_re - if not regx.match(username): - raise serializers.ValidationError(_('Special char not allowed')) - return username - - username_same_with_user = self.get_initial_value("username_same_with_user") - if username_same_with_user: - return '' - - login_mode = self.get_initial_value("login_mode") - if login_mode == SystemUser.LOGIN_AUTO and protocol != Protocol.vnc \ - and protocol != Protocol.redis: - msg = _('* Automatic login mode must fill in the username.') - raise serializers.ValidationError(msg) - return username - - @staticmethod - def validate_sftp_root(value): - if value in ['home', 'tmp']: - return value - if not value.startswith('/'): - error = _("Path should starts with /") - raise serializers.ValidationError(error) - return value - - def validate_su_from(self, su_from: SystemUser): - # self: su enabled - su_enabled = self.get_initial_value('su_enabled', default=False) - if not su_enabled: - return - if not su_from: - error = _('This field is required.') - raise serializers.ValidationError(error) - # self: protocol ssh - protocol = self.get_initial_value('protocol', default=Protocol.ssh.value) - if protocol not in [Protocol.ssh.value]: - error = _('Only ssh protocol system users are allowed') - raise serializers.ValidationError(error) - # su_from: protocol same - if su_from.protocol != protocol: - error = _('The protocol must be consistent with the current user: {}').format(protocol) - raise serializers.ValidationError(error) - # su_from: login model auto - if su_from.login_mode != su_from.LOGIN_AUTO: - error = _('Only system users with automatic login are allowed') - raise serializers.ValidationError(error) - return su_from - - -class MiniSystemUserSerializer(serializers.ModelSerializer): - class Meta: - model = SystemUser - fields = SystemUserSerializer.Meta.fields_mini - - -class SystemUserSimpleSerializer(serializers.ModelSerializer): - """ - 系统用户最基本信息的数据结构 - """ - - class Meta: - model = SystemUser - fields = ('id', 'name', 'username') - - -class SystemUserTempAuthSerializer(SystemUserSerializer): - instance_id = serializers.CharField() - - class Meta(SystemUserSerializer.Meta): - fields = ['instance_id', 'username', 'password'] diff --git a/apps/rbac/api/role.py b/apps/rbac/api/role.py index 44cb899db..be8274e91 100644 --- a/apps/rbac/api/role.py +++ b/apps/rbac/api/role.py @@ -4,11 +4,11 @@ from rest_framework.exceptions import PermissionDenied from rest_framework.decorators import action from common.drf.api import JMSModelViewSet +from common.mixins.api import PaginatedResponseMixin from ..filters import RoleFilter from ..serializers import RoleSerializer, RoleUserSerializer from ..models import Role, SystemRole, OrgRole from .permission import PermissionViewSet -from common.mixins.api import PaginatedResponseMixin __all__ = [ 'RoleViewSet', 'SystemRoleViewSet', 'OrgRoleViewSet', @@ -16,7 +16,7 @@ __all__ = [ ] -class RoleViewSet(PaginatedResponseMixin, JMSModelViewSet): +class RoleViewSet(JMSModelViewSet): queryset = Role.objects.all() serializer_classes = { 'default': RoleSerializer, diff --git a/apps/terminal/migrations/0048_auto_20220407_1726.py b/apps/terminal/migrations/0052_auto_20220707_1726.py similarity index 92% rename from apps/terminal/migrations/0048_auto_20220407_1726.py rename to apps/terminal/migrations/0052_auto_20220707_1726.py index 1eb432efc..22eab3a1a 100644 --- a/apps/terminal/migrations/0048_auto_20220407_1726.py +++ b/apps/terminal/migrations/0052_auto_20220707_1726.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('terminal', '0047_auto_20220302_1951'), + ('terminal', '0051_sessionsharing_users'), ] operations = [ From 196e38897f31eecf2519351a11abfd8a850e781e Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Aug 2022 15:46:36 +0800 Subject: [PATCH 039/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20protcols?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0104_auto_20220803_1859.py | 27 ++++--- apps/assets/serializers/asset/common.py | 71 +++++-------------- apps/assets/serializers/platform.py | 4 +- .../serializers/connection_token.py | 2 - .../serializers/asset/user_permission.py | 2 - 5 files changed, 36 insertions(+), 70 deletions(-) diff --git a/apps/assets/migrations/0104_auto_20220803_1859.py b/apps/assets/migrations/0104_auto_20220803_1859.py index ecd75743b..f1836f713 100644 --- a/apps/assets/migrations/0104_auto_20220803_1859.py +++ b/apps/assets/migrations/0104_auto_20220803_1859.py @@ -6,10 +6,12 @@ from django.db import migrations def migrate_asset_protocols(apps, schema_editor): asset_model = apps.get_model('assets', 'Asset') protocol_model = apps.get_model('assets', 'Protocol') + asset_protocol_through = asset_model.protocols.through count = 0 bulk_size = 1000 print("\nStart migrate asset protocols") + protocol_map = {} while True: start = time.time() assets = asset_model.objects.all()[count:count+bulk_size] @@ -17,15 +19,24 @@ def migrate_asset_protocols(apps, schema_editor): if not assets: break - protocols = [] + assets_protocols = [] for asset in assets: - for protocol in asset.protocols.all(): - protocols.append(protocol_model( - asset_id=asset.id, - protocol=protocol.protocol, - port=protocol.port, - )) - protocol_model.objects.bulk_create(protocols, ignore_conflicts=True) + old_protocols = asset._protocols + + for name_port in old_protocols.split(','): + name_port_list = name_port.split('/') + if len(name_port_list) != 2: + continue + + name, port = name_port_list + protocol = protocol_map.get(name_port) + if not protocol: + protocol = protocol_model.objects.get_or_create( + defaults={'name': name, 'port': port}, + name=name, port=port + )[0] + assets_protocols.append(asset_protocol_through(asset_id=asset.id, protocol_id=protocol.id)) + asset_model.protocols.through.objects.bulk_create(assets_protocols, ignore_conflicts=True) print("Create asset protocols: {}-{} using: {:.2f}s".format( count - bulk_size, count, time.time()-start )) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index d3ca2b257..0e8acfe12 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -3,70 +3,30 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from ...models import Asset, Node, Platform, SystemUser +from ...models import Asset, Node, Platform, SystemUser, Protocol, Label from ..mixin import CategoryDisplayMixin from ..account import AccountSerializer __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', - 'AssetTaskSerializer', 'AssetsTaskSerializer', 'ProtocolsField', + 'AssetTaskSerializer', 'AssetsTaskSerializer', ] -class ProtocolField(serializers.RegexField): - default_error_messages = { - 'invalid': _('Protocol format should {}/{}').format('protocol', '1-65535') - } - regex = r'^(\w+)/(\d{1,5})$' - - def __init__(self, *args, **kwargs): - super().__init__(self.regex, **kwargs) +class AssetProtocolsSerializer(serializers.ModelSerializer): + class Meta: + model = Protocol + fields = ['id', 'name', 'port'] -def validate_duplicate_protocols(values): - errors = [] - names = [] - - print("Value is: ", values) - - for value in values.split(' '): - if not value or '/' not in value: - continue - name = value.split('/')[0] - if name in names: - errors.append(_("Protocol duplicate: {}").format(name)) - names.append(name) - errors.append('') - if any(errors): - raise serializers.ValidationError(errors) - - -class ProtocolsField(serializers.ListField): - default_validators = [validate_duplicate_protocols] - - def __init__(self, *args, **kwargs): - kwargs['child'] = ProtocolField() - kwargs['allow_null'] = True - kwargs['allow_empty'] = True - kwargs['min_length'] = 1 - kwargs['max_length'] = 32 - super().__init__(*args, **kwargs) - - def to_representation(self, value): - if not value: - return [] - if isinstance(value, str): - return value.split(' ') - return value - - def to_internal_value(self, data): - return ' '.join(data) +class AssetLabelSerializer(serializers.ModelSerializer): + class Meta: + model = Label + fields = ['id', 'name', 'value'] class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): - protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22']) domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name')) nodes_display = serializers.ListField( child=serializers.CharField(), label=_('Nodes name'), required=False @@ -75,10 +35,12 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): child=serializers.CharField(), label=_('Labels name'), required=False, read_only=True ) + labels = AssetLabelSerializer(many=True, required=False) platform_display = serializers.SlugField( source='platform.name', label=_("Platform display"), read_only=True ) accounts = AccountSerializer(many=True, write_only=True, required=False) + protocols = AssetProtocolsSerializer(many=True) """ 资产的数据结构 @@ -87,17 +49,16 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): class Meta: model = Asset fields_mini = [ - 'id', 'hostname', 'ip', 'platform', 'protocols' + 'id', 'hostname', 'ip', ] fields_small = fields_mini + [ - 'protocol', 'port', 'is_active', - 'public_ip', 'number', 'comment', + 'is_active', 'number', 'comment', ] fields_fk = [ - 'domain', 'domain_display', 'platform', + 'domain', 'domain_display', 'platform', 'platform', 'platform_display', ] fields_m2m = [ - 'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts' + 'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts', 'protocols', ] read_only_fields = [ 'category', 'category_display', 'type', 'type_display', diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 7a68e045d..b7277741d 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -3,8 +3,6 @@ from django.core.validators import RegexValidator from django.utils.translation import gettext_lazy as _ from assets.models import Platform -from assets.serializers.asset import ProtocolsField -from assets.const import Protocol from .mixin import CategoryDisplayMixin __all__ = ['PlatformSerializer'] @@ -12,7 +10,7 @@ __all__ = ['PlatformSerializer'] class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - protocols_default = ProtocolsField(label=_('Protocols'), required=False) + protocols_default = serializers.ListField(label=_('Protocols'), required=False) type_limits = serializers.ReadOnlyField(required=False, read_only=True) def __init__(self, *args, **kwargs): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 1b639bec6..a05d8c0e0 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -8,7 +8,6 @@ from common.utils.random import random_string from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule from users.models import User from applications.models import Application -from assets.serializers import ProtocolsField from perms.serializers.base import ActionsField @@ -122,7 +121,6 @@ class ConnectionTokenUserSerializer(serializers.ModelSerializer): class ConnectionTokenAssetSerializer(serializers.ModelSerializer): - protocols = ProtocolsField(label='Protocols', read_only=True) class Meta: model = Asset diff --git a/apps/perms/serializers/asset/user_permission.py b/apps/perms/serializers/asset/user_permission.py index d2ad97894..26a21d7f0 100644 --- a/apps/perms/serializers/asset/user_permission.py +++ b/apps/perms/serializers/asset/user_permission.py @@ -5,7 +5,6 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from assets.models import Node, SystemUser, Asset, Platform -from assets.serializers import ProtocolsField from perms.serializers.base import ActionsField __all__ = [ @@ -38,7 +37,6 @@ class AssetGrantedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 """ - protocols = ProtocolsField(label=_('Protocols'), required=False, read_only=True) platform = serializers.SlugRelatedField( slug_field='name', queryset=Platform.objects.all(), label=_("Platform") ) From 88d4bf932c3c8b17a49cecffad02fcf2beff115a Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Aug 2022 16:17:45 +0800 Subject: [PATCH 040/488] perf: change asset --- apps/assets/serializers/asset/common.py | 17 +---------------- run_server.py | 4 ++-- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 0e8acfe12..9c0871b87 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -97,25 +97,10 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('domain', 'platform') + queryset = queryset.prefetch_related('domain', 'platform', 'protocols') queryset = queryset.prefetch_related('nodes', 'labels') return queryset - def compatible_with_old_protocol(self, validated_data): - protocols_data = validated_data.pop("protocols", []) - - # 兼容老的api - name = validated_data.get("protocol") - port = validated_data.get("port") - if not protocols_data and name and port: - protocols_data.insert(0, '/'.join([name, str(port)])) - elif not name and not port and protocols_data: - protocol = protocols_data[0].split('/') - validated_data["protocol"] = protocol[0] - validated_data["port"] = int(protocol[1]) - if protocols_data: - validated_data["protocols"] = protocols_data - def perform_nodes_display_create(self, instance, nodes_display): if not nodes_display: return diff --git a/run_server.py b/run_server.py index c7ec7bccb..4f4d3dbaa 100644 --- a/run_server.py +++ b/run_server.py @@ -6,6 +6,6 @@ import subprocess if __name__ == '__main__': - subprocess.call('python3 jms start all', shell=True, - stdin=sys.stdin, stdout=sys.stdout) + kwargs = dict(shell=True, stdin=sys.stdin, stdout=sys.stdout) + subprocess.call('python3 jms start all', **kwargs) From 8dfb8eeb75af15fc284be3693c337b3587463947 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Aug 2022 18:31:57 +0800 Subject: [PATCH 041/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=9D=83?= =?UTF-8?q?=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 5 +++++ apps/jumpserver/api.py | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 641ba1a76..31e43fddc 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -2,6 +2,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords +from common.db.models import JMSBaseModel from .protocol import ProtocolMixin from .base import BaseUser, AbsConnectivity @@ -35,3 +36,7 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin): def __str__(self): return '{}://{}@{}'.format(self.protocol, self.username, self.asset.hostname) + + +class AccountTemplate(JMSBaseModel): + pass diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index d5ff38593..cee761132 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -213,9 +213,9 @@ class DatesLoginMetricMixin: class IndexApi(DatesLoginMetricMixin, APIView): http_method_names = ['get'] - rbac_perms = { - 'GET': 'rbac.view_audit | rbac.view_console' - } + + def check_permissions(self, request): + return request.user.has_perm(['rbac.view_audit', 'rbac.view_console']) def get(self, request, *args, **kwargs): data = {} From 698ea3f2ea435e2e338047ef588e9fed89e2b923 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Aug 2022 19:11:17 +0800 Subject: [PATCH 042/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20category?= =?UTF-8?q?=20node=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/node.py | 9 +++++++++ apps/assets/const.py | 19 ++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 3d5d22178..8ddb79e71 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -27,6 +27,7 @@ from ..tasks import ( check_node_assets_amount_task ) from .. import serializers +from ..const import AllTypes from .mixin import SerializeToTreeNodeMixin from assets.locks import NodeAddChildrenLock @@ -197,6 +198,14 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): return self.serialize_assets(assets, self.instance.key) +class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView): + def list(self, request, *args, **kwargs): + category_types = AllTypes.category_types() + nodes = self.get_queryset().order_by('value') + nodes = self.serialize_nodes(nodes, with_asset_amount=True) + return Response(data=nodes) + + class NodeAssetsApi(generics.ListAPIView): serializer_class = serializers.AssetSerializer diff --git a/apps/assets/const.py b/apps/assets/const.py index 08269cf4d..9069a6c57 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -5,7 +5,7 @@ from common.db.models import IncludesTextChoicesMeta __all__ = [ 'Category', 'HostTypes', 'NetworkTypes', 'DatabaseTypes', - 'RemoteAppTypes', 'CloudTypes', 'Protocol', 'AllTypes', + 'WebTypes', 'CloudTypes', 'Protocol', 'AllTypes', ] @@ -19,8 +19,8 @@ class Category(PlatformMixin, models.TextChoices): HOST = 'host', _('Host') NETWORK = 'network', _("NetworkDevice") DATABASE = 'database', _("Database") - REMOTE_APP = 'remote_app', _("Remote app") CLOUD = 'cloud', _("Clouding") + WEB = 'web', _("Web") @classmethod def platform_limits(cls): @@ -36,8 +36,8 @@ class Category(PlatformMixin, models.TextChoices): cls.DATABASE: { 'has_domain': True }, - cls.REMOTE_APP: { - 'has_domain': True + cls.WEB: { + 'has_domain': False, }, cls.CLOUD: { 'has_domain': False, @@ -97,11 +97,8 @@ class DatabaseTypes(PlatformMixin, models.TextChoices): return meta -class RemoteAppTypes(PlatformMixin, models.TextChoices): - CHROME = 'chrome', 'Chrome' - VSPHERE = 'vmware_client', 'vSphere client' - MYSQL_WORKBENCH = 'mysql_workbench', 'MySQL workbench' - GENERAL_REMOTE_APP = 'general_remote_app', _("Custom") +class WebTypes(PlatformMixin, models.TextChoices): + General = 'general', 'General' class CloudTypes(PlatformMixin, models.TextChoices): @@ -112,7 +109,7 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): choices: list includes = [ HostTypes, NetworkTypes, DatabaseTypes, - RemoteAppTypes, CloudTypes + WebTypes, CloudTypes ] @classmethod @@ -140,7 +137,7 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): (Category.HOST, HostTypes), (Category.NETWORK, NetworkTypes), (Category.DATABASE, DatabaseTypes), - (Category.REMOTE_APP, RemoteAppTypes), + (Category.WEB, WebTypes), (Category.CLOUD, CloudTypes) ) From c0cb58c0019f2a744c0ebb907ebfa083e98db3b2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 8 Aug 2022 10:41:37 +0800 Subject: [PATCH 043/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20tree=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/node.py | 20 +++++++++++++------- apps/assets/const.py | 27 +++++++++++++++++++++++++++ apps/assets/urls/api_urls.py | 1 + apps/jumpserver/urls.py | 12 ------------ 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 8ddb79e71..59a1da793 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ from functools import partial from collections import namedtuple, defaultdict +from django.core.exceptions import PermissionDenied from rest_framework import status from rest_framework.serializers import ValidationError @@ -35,9 +36,8 @@ logger = get_logger(__file__) __all__ = [ 'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'MoveAssetsToNodeApi', - 'NodeAddChildrenApi', 'NodeListAsTreeApi', - 'NodeChildrenAsTreeApi', - 'NodeTaskCreateApi', + 'NodeAddChildrenApi', 'NodeListAsTreeApi', 'NodeChildrenAsTreeApi', + 'NodeTaskCreateApi', 'CategoryTreeApi', ] @@ -199,11 +199,17 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): class CategoryTreeApi(SerializeToTreeNodeMixin, generics.ListAPIView): + serializer_class = TreeNodeSerializer + + def check_permissions(self, request): + if not request.user.has_perm('assets.view_asset'): + raise PermissionDenied + return True + def list(self, request, *args, **kwargs): - category_types = AllTypes.category_types() - nodes = self.get_queryset().order_by('value') - nodes = self.serialize_nodes(nodes, with_asset_amount=True) - return Response(data=nodes) + nodes = AllTypes.to_tree_nodes() + serializer = self.get_serializer(nodes, many=True) + return Response(data=serializer.data) class NodeAssetsApi(generics.ListAPIView): diff --git a/apps/assets/const.py b/apps/assets/const.py index 9069a6c57..4da356115 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from common.db.models import IncludesTextChoicesMeta +from common.tree import TreeNode __all__ = [ @@ -160,6 +161,32 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): title = ['value', 'display_name'] return [dict(zip(title, choice)) for choice in choices] + @staticmethod + def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): + node = TreeNode(**{ + 'id': choice.name, + 'name': choice.label, + 'title': choice.label, + 'pId': pid, + 'open': opened, + 'isParent': is_parent, + }) + if meta: + node.meta = meta + return node + + @classmethod + def to_tree_nodes(cls): + root = TreeNode(id='ROOT', name='类型节点', title='类型节点') + nodes = [root] + for category, types in cls.category_types(): + category_node = cls.choice_to_node(category, 'ROOT', meta={'type': 'category'}) + nodes.append(category_node) + for tp in types: + tp_node = cls.choice_to_node(tp, category_node.id, meta={'type': 'type'}) + nodes.append(tp_node) + return nodes + class Protocol(models.TextChoices): ssh = 'ssh', 'SSH' diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 5070cd44d..3e6b1309b 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -39,6 +39,7 @@ urlpatterns = [ path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'), + path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'), path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'), path('nodes//children/', api.NodeChildrenApi.as_view(), name='node-children'), diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index ef654fddf..cd4d2311d 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -45,12 +45,6 @@ if settings.XPACK_ENABLED: ) -apps = [ - 'users', 'assets', 'perms', 'terminal', 'ops', 'audits', - 'orgs', 'auth', 'applications', 'tickets', 'settings', 'xpack', - 'flower', 'luna', 'koko', 'ws', 'docs', 'redocs', -] - urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('api/v1/', include(api_v1)), @@ -86,11 +80,5 @@ if os.environ.get('DEBUG_TOOLBAR', False): ] -# 兼容之前的 -old_app_pattern = '|'.join(apps) -old_app_pattern = r'^{}'.format(old_app_pattern) -urlpatterns += [re_path(old_app_pattern, views.redirect_old_apps_view)] - - handler404 = 'jumpserver.views.handler404' handler500 = 'jumpserver.views.handler500' From 11d9a0e9ccca6ea64f050f3079c39e109c23a897 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 8 Aug 2022 11:39:55 +0800 Subject: [PATCH 044/488] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E5=B5=8C?= =?UTF-8?q?=E5=A5=97=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 80 +++++++++++++++---------- requirements/requirements.txt | 1 + 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 9c0871b87..ecb9fea29 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -2,9 +2,11 @@ # from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from drf_writable_nested.serializers import WritableNestedModelSerializer + from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from ...models import Asset, Node, Platform, SystemUser, Protocol, Label +from ...models import Asset, Node, Platform, Protocol, Label, Domain from ..mixin import CategoryDisplayMixin from ..account import AccountSerializer @@ -24,22 +26,52 @@ class AssetLabelSerializer(serializers.ModelSerializer): class Meta: model = Label fields = ['id', 'name', 'value'] + extra_kwargs = { + 'value': {'required': False} + } -class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): - domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name')) - nodes_display = serializers.ListField( - child=serializers.CharField(), label=_('Nodes name'), required=False - ) - labels_display = serializers.ListField( - child=serializers.CharField(), label=_('Labels name'), - required=False, read_only=True - ) +class AssetPlatformSerializer(serializers.ModelSerializer): + class Meta: + model = Platform + fields = ['id', 'name'] + extra_kwargs = { + 'name': {'required': False} + } + + +class AssetDomainSerializer(serializers.ModelSerializer): + class Meta: + model = Domain + fields = ['id', 'name'] + + +class AssetNodesSerializer(serializers.ModelSerializer): + class Meta: + model = Node + fields = ['id', 'value'] + extra_kwargs = { + 'value': {'required': False} + } + + +class AssetSerializer(CategoryDisplayMixin, WritableNestedModelSerializer, OrgResourceModelSerializerMixin): + domain = AssetDomainSerializer(required=False) + # nodes_display = serializers.ListField( + # child=serializers.CharField(), label=_('Nodes name'), required=False + # ) labels = AssetLabelSerializer(many=True, required=False) - platform_display = serializers.SlugField( - source='platform.name', label=_("Platform display"), read_only=True - ) - accounts = AccountSerializer(many=True, write_only=True, required=False) + # labels_display = serializers.ListField( + # child=serializers.CharField(), label=_('Labels name'), + # required=False, read_only=True + # ) + # labels = AssetLabelSerializer(many=True, required=False) + # platform_display = serializers.SlugField( + # source='platform.name', label=_("Platform display"), read_only=True + # ) + nodes = AssetNodesSerializer(many=True, required=False) + platform = AssetPlatformSerializer(required=False) + accounts = AccountSerializer(many=True, required=False) protocols = AssetProtocolsSerializer(many=True) """ @@ -55,10 +87,12 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): 'is_active', 'number', 'comment', ] fields_fk = [ - 'domain', 'domain_display', 'platform', 'platform', 'platform_display', + 'domain', 'platform', 'platform', + # 'domain_display', 'platform_display', ] fields_m2m = [ - 'nodes', 'nodes_display', 'labels', 'labels_display', 'accounts', 'protocols', + 'nodes', 'labels', 'accounts', 'protocols', + # 'labels_display','nodes_display', ] read_only_fields = [ 'category', 'category_display', 'type', 'type_display', @@ -78,15 +112,6 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): self.accounts_data = data.pop('accounts', []) super().__init__(*args, **kwargs) - def get_fields(self): - fields = super().get_fields() - - admin_user_field = fields.get('admin_user') - # 因为 mixin 中对 fields 有处理,可能不需要返回 admin_user - if admin_user_field: - admin_user_field.queryset = SystemUser.objects.filter(type=SystemUser.Type.admin) - return fields - def validate_type(self, value): print(self.initial_data) return value @@ -126,7 +151,6 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): serializer.save() def create(self, validated_data): - self.compatible_with_old_protocol(validated_data) nodes_display = validated_data.pop('nodes_display', '') instance = super().create(validated_data) if self.accounts_data: @@ -136,7 +160,6 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): def update(self, instance, validated_data): nodes_display = validated_data.pop('nodes_display', '') - self.compatible_with_old_protocol(validated_data) instance = super().update(instance, validated_data) self.perform_nodes_display_create(instance, nodes_display) return instance @@ -178,6 +201,3 @@ class AssetTaskSerializer(AssetsTaskSerializer): asset = serializers.PrimaryKeyRelatedField( queryset=Asset.objects, required=False, allow_empty=True, many=False ) - system_users = serializers.PrimaryKeyRelatedField( - queryset=SystemUser.objects, required=False, allow_empty=True, many=True - ) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index afe9b7179..fcb42d5c5 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -74,6 +74,7 @@ djangorestframework==3.13.1 djangorestframework-bulk==0.2.1 django-simple-history==3.1.1 drf-nested-routers==0.93.4 +drf-writable-nested==0.6.4 rest_condition==1.0.3 drf-yasg==1.20.0 coreapi==2.3.3 From 87df92ea924719e4522e85fe603443e60f70d70e Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 8 Aug 2022 14:34:57 +0800 Subject: [PATCH 045/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 34 ++++++++++--------------- apps/common/drf/serializers.py | 18 +++++++++---- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index ecb9fea29..e355a83bb 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -2,9 +2,8 @@ # from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from drf_writable_nested.serializers import WritableNestedModelSerializer - +from common.drf.serializers import JMSWritableNestedModelSerializer from orgs.mixins.serializers import OrgResourceModelSerializerMixin from ...models import Asset, Node, Platform, Protocol, Label, Domain from ..mixin import CategoryDisplayMixin @@ -19,13 +18,13 @@ __all__ = [ class AssetProtocolsSerializer(serializers.ModelSerializer): class Meta: model = Protocol - fields = ['id', 'name', 'port'] + fields = ['pk', 'name', 'port'] class AssetLabelSerializer(serializers.ModelSerializer): class Meta: model = Label - fields = ['id', 'name', 'value'] + fields = ['pk', 'name', 'value'] extra_kwargs = { 'value': {'required': False} } @@ -34,7 +33,7 @@ class AssetLabelSerializer(serializers.ModelSerializer): class AssetPlatformSerializer(serializers.ModelSerializer): class Meta: model = Platform - fields = ['id', 'name'] + fields = ['pk', 'name'] extra_kwargs = { 'name': {'required': False} } @@ -43,32 +42,26 @@ class AssetPlatformSerializer(serializers.ModelSerializer): class AssetDomainSerializer(serializers.ModelSerializer): class Meta: model = Domain - fields = ['id', 'name'] + fields = ['pk', 'name'] class AssetNodesSerializer(serializers.ModelSerializer): class Meta: model = Node - fields = ['id', 'value'] + fields = ['pk', 'value'] extra_kwargs = { 'value': {'required': False} } -class AssetSerializer(CategoryDisplayMixin, WritableNestedModelSerializer, OrgResourceModelSerializerMixin): +class AssetSerializer(CategoryDisplayMixin, + JMSWritableNestedModelSerializer, + OrgResourceModelSerializerMixin): domain = AssetDomainSerializer(required=False) - # nodes_display = serializers.ListField( - # child=serializers.CharField(), label=_('Nodes name'), required=False - # ) + nodes_display = serializers.ListField( + child=serializers.CharField(), label=_('Nodes name'), required=False + ) labels = AssetLabelSerializer(many=True, required=False) - # labels_display = serializers.ListField( - # child=serializers.CharField(), label=_('Labels name'), - # required=False, read_only=True - # ) - # labels = AssetLabelSerializer(many=True, required=False) - # platform_display = serializers.SlugField( - # source='platform.name', label=_("Platform display"), read_only=True - # ) nodes = AssetNodesSerializer(many=True, required=False) platform = AssetPlatformSerializer(required=False) accounts = AccountSerializer(many=True, required=False) @@ -88,11 +81,10 @@ class AssetSerializer(CategoryDisplayMixin, WritableNestedModelSerializer, OrgRe ] fields_fk = [ 'domain', 'platform', 'platform', - # 'domain_display', 'platform_display', ] fields_m2m = [ 'nodes', 'labels', 'accounts', 'protocols', - # 'labels_display','nodes_display', + 'nodes_display', ] read_only_fields = [ 'category', 'category_display', 'type', 'type_display', diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index 21d315236..ece410d19 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -1,20 +1,19 @@ -import copy from rest_framework import serializers from rest_framework.serializers import Serializer from rest_framework.serializers import ModelSerializer from rest_framework_bulk.serializers import BulkListSerializer from django.utils.translation import gettext_lazy as _ from django.utils.functional import cached_property -from rest_framework.utils.serializer_helpers import BindingDict +from drf_writable_nested.serializers import WritableNestedModelSerializer from common.mixins import BulkListSerializerMixin from common.mixins.serializers import BulkSerializerMixin from common.drf.fields import EncryptedField __all__ = [ - 'MethodSerializer', - 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer', - 'SecretReadableMixin' + 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', + 'AdaptedBulkListSerializer', 'CeleryTaskSerializer', + 'SecretReadableMixin', 'JMSWritableNestedModelSerializer', ] @@ -112,3 +111,12 @@ class SecretReadableMixin(serializers.Serializer): if 'write_only' not in field_extra_kwargs: continue serializer_field.write_only = field_extra_kwargs['write_only'] + + +class JMSWritableNestedModelSerializer(WritableNestedModelSerializer): + + def _get_related_pk(self, data, model_class): + pk = data.get('pk') or data.get('id') or data.get(model_class._meta.pk.attname) + if pk: + return str(pk) + return None From a7c82f94cc52c52d57811af7fe977bb3ce93230d Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 8 Aug 2022 19:18:45 +0800 Subject: [PATCH 046/488] =?UTF-8?q?perf:=20=E4=BF=AE=E5=A4=8D=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=96=87=E6=A1=A3=20=E4=BF=AE=E5=A4=8D=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0045_auto_20191206_1607.py | 2 +- apps/assets/models/_user.py | 5 +++++ apps/assets/models/asset/common.py | 7 ++++++- apps/assets/models/platform.py | 2 +- apps/audits/api.py | 2 ++ apps/perms/api/application/user_permission/common.py | 5 ++--- apps/rbac/permissions.py | 2 +- 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/apps/assets/migrations/0045_auto_20191206_1607.py b/apps/assets/migrations/0045_auto_20191206_1607.py index bf04ad773..a2a136c3b 100644 --- a/apps/assets/migrations/0045_auto_20191206_1607.py +++ b/apps/assets/migrations/0045_auto_20191206_1607.py @@ -34,7 +34,7 @@ class Migration(migrations.Migration): model_name='asset', name='platform', field=models.ForeignKey( - default=assets.models.Platform.default, + default='', on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.Platform', verbose_name='Platform'), diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index 94e38b1e4..2ecb39094 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -128,6 +128,11 @@ class SystemUser(ProtocolMixin, BaseUser): return self.su_from.assets.add(*tuple(assets_or_ids)) + # TODO 暂时为了接口文档添加 + @property + def auto_push_account(self): + return + class Meta: ordering = ['name'] unique_together = [('name', 'org_id')] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index e24c7e80d..e2544f2b8 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -183,7 +183,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): def get_all_system_users(self): from assets.models import SystemUser - system_user_ids = SystemUser.assets.through.objects.filter(asset=self)\ + system_user_ids = SystemUser.assets.through.objects.filter(asset=self) \ .values_list('systemuser_id', flat=True) system_users = SystemUser.objects.filter(id__in=system_user_ids) return system_users @@ -193,6 +193,11 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): self.category = self.platform.category return super().save(*args, **kwargs) + # TODO 暂时为了接口文档添加 + @property + def os(self): + return + class Meta: unique_together = [('org_id', 'hostname')] verbose_name = _("Asset") diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 878940e9e..d50afd27e 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -14,7 +14,7 @@ class Platform(models.Model): ('gbk', 'GBK'), ) name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) - category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category")) + category = models.CharField(max_length=16, choices=Category.choices, default=Category.HOST, verbose_name=_("Category")) type = models.CharField(choices=AllTypes.choices, max_length=32, default='Linux', verbose_name=_("Type")) charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) diff --git a/apps/audits/api.py b/apps/audits/api.py index fb1efe989..2abc52a57 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -117,6 +117,8 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): def get_queryset(self): queryset = super().get_queryset() + if getattr(self, 'swagger_fake_view', False): + return queryset.model.objects.none() if current_org.is_root(): return queryset queryset = queryset.filter(run_as__org_id=current_org.org_id()) diff --git a/apps/perms/api/application/user_permission/common.py b/apps/perms/api/application/user_permission/common.py index d4c68662a..934748a18 100644 --- a/apps/perms/api/application/user_permission/common.py +++ b/apps/perms/api/application/user_permission/common.py @@ -10,7 +10,7 @@ from rest_framework.generics import ( ListAPIView, get_object_or_404 ) -from orgs.utils import tmp_to_root_org, get_current_org +from orgs.utils import tmp_to_root_org from applications.models import Application from perms.utils.application.permission import ( get_application_system_user_ids, @@ -20,7 +20,6 @@ from .mixin import AppRoleAdminMixin, AppRoleUserMixin from perms.hands import User, SystemUser from perms import serializers - __all__ = [ 'UserGrantedApplicationSystemUsersApi', 'MyGrantedApplicationSystemUsersApi', @@ -40,7 +39,7 @@ class BaseGrantedApplicationSystemUsersApi(ListAPIView): application_id = self.kwargs.get('application_id') application = get_object_or_404(Application, id=application_id) system_user_ids = self.get_application_system_user_ids(application) - system_users = SystemUser.objects.filter(id__in=system_user_ids)\ + system_users = SystemUser.objects.filter(id__in=system_user_ids) \ .only(*self.only_fields).order_by('priority') return system_users diff --git a/apps/rbac/permissions.py b/apps/rbac/permissions.py index 286e5b935..b25fa1baf 100644 --- a/apps/rbac/permissions.py +++ b/apps/rbac/permissions.py @@ -94,7 +94,7 @@ class RBACPermission(permissions.DjangoModelPermissions): queryset = self._queryset(view) model_cls = queryset.model except Exception as e: - raise e + logger.error(e) model_cls = None return model_cls From 05e2f8aaf67832a2b808009b19c8290e776609b3 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 9 Aug 2022 10:42:35 +0800 Subject: [PATCH 047/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20EncryptMixi?= =?UTF-8?q?n=20get=5Fprep=5Fvalue=20=E5=92=8C=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=85=B3=E9=97=AD=20XPACK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/db/fields.py | 1 + apps/jumpserver/settings/_xpack.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 8d46c9174..0757bc068 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -143,6 +143,7 @@ class EncryptMixin: value = sp.get_prep_value(value) value = force_text(value) # 替换新的加密方式 + return crypto.encrypt(value) class EncryptTextField(EncryptMixin, models.TextField): diff --git a/apps/jumpserver/settings/_xpack.py b/apps/jumpserver/settings/_xpack.py index 322740201..7a2f34c8d 100644 --- a/apps/jumpserver/settings/_xpack.py +++ b/apps/jumpserver/settings/_xpack.py @@ -6,8 +6,8 @@ from .. import const from .base import INSTALLED_APPS, TEMPLATES XPACK_DIR = os.path.join(const.BASE_DIR, 'xpack') -# XPACK_ENABLED = False -XPACK_ENABLED = os.path.isdir(XPACK_DIR) +XPACK_ENABLED = False +# XPACK_ENABLED = os.path.isdir(XPACK_DIR) XPACK_TEMPLATES_DIR = [] XPACK_CONTEXT_PROCESSOR = [] From 3011b18eaac575ae7f29d3f0140017e13d39a5a4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Aug 2022 15:42:06 +0800 Subject: [PATCH 048/488] =?UTF-8?q?perf:=20=E5=B9=B2=E6=8E=89=20applicatio?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/__init__.py | 0 apps/applications/admin.py | 3 - apps/applications/api/__init__.py | 3 - apps/applications/api/account.py | 66 ------- apps/applications/api/application.py | 39 ---- apps/applications/api/remote_app.py | 16 -- apps/applications/hands.py | 14 -- apps/applications/models/tree.py | 1 - apps/applications/permissions.py | 9 - apps/applications/serializers/__init__.py | 2 - apps/applications/serializers/application.py | 168 ---------------- .../serializers/attrs/__init__.py | 1 - .../attrs/application_category/__init__.py | 3 - .../attrs/application_category/cloud.py | 8 - .../attrs/application_category/db.py | 15 -- .../attrs/application_category/remote_app.py | 60 ------ .../attrs/application_type/__init__.py | 15 -- .../attrs/application_type/chrome.py | 34 ---- .../attrs/application_type/custom.py | 33 ---- .../serializers/attrs/application_type/k8s.py | 7 - .../attrs/application_type/mariadb.py | 7 - .../attrs/application_type/mongodb.py | 11 -- .../attrs/application_type/mysql.py | 11 -- .../attrs/application_type/mysql_workbench.py | 43 ---- .../attrs/application_type/oracle.py | 10 - .../attrs/application_type/pgsql.py | 10 - .../attrs/application_type/redis.py | 11 -- .../attrs/application_type/sqlserver.py | 11 -- .../attrs/application_type/vmware_client.py | 39 ---- apps/applications/serializers/attrs/attrs.py | 62 ------ apps/applications/serializers/remote_app.py | 31 --- apps/applications/tests.py | 3 - apps/applications/urls/__init__.py | 7 - apps/applications/urls/api_urls.py | 25 --- apps/applications/utils/__init__.py | 4 - apps/applications/utils/kubernetes_util.py | 186 ------------------ apps/assets/serializers/asset/common.py | 5 +- apps/jumpserver/urls.py | 1 - apps/perms/api/__init__.py | 1 - apps/perms/api/application/__init__.py | 4 - .../api/application/application_permission.py | 55 ------ .../application_permission_relation.py | 123 ------------ .../api/application/user_group_permission.py | 36 ---- .../application/user_permission/__init__.py | 2 - .../api/application/user_permission/common.py | 83 -------- .../api/application/user_permission/mixin.py | 24 --- .../user_permission_applications.py | 81 -------- apps/perms/serializers/__init__.py | 1 - .../perms/serializers/application/__init__.py | 3 - .../serializers/application/permission.py | 83 -------- .../application/permission_relation.py | 88 --------- .../application/user_permission.py | 42 ---- apps/perms/signal_handlers/__init__.py | 1 - apps/perms/signal_handlers/app_permission.py | 106 ---------- apps/perms/tree/__init__.py | 0 apps/perms/tree/app.py | 103 ---------- apps/perms/urls/api_urls.py | 11 -- apps/perms/urls/application_permission.py | 48 ----- apps/perms/urls/system_user_permission.py | 6 - 59 files changed, 2 insertions(+), 1873 deletions(-) delete mode 100644 apps/applications/__init__.py delete mode 100644 apps/applications/admin.py delete mode 100644 apps/applications/api/__init__.py delete mode 100644 apps/applications/api/account.py delete mode 100644 apps/applications/api/application.py delete mode 100644 apps/applications/api/remote_app.py delete mode 100644 apps/applications/hands.py delete mode 100644 apps/applications/permissions.py delete mode 100644 apps/applications/serializers/__init__.py delete mode 100644 apps/applications/serializers/application.py delete mode 100644 apps/applications/serializers/attrs/__init__.py delete mode 100644 apps/applications/serializers/attrs/application_category/__init__.py delete mode 100644 apps/applications/serializers/attrs/application_category/cloud.py delete mode 100644 apps/applications/serializers/attrs/application_category/db.py delete mode 100644 apps/applications/serializers/attrs/application_category/remote_app.py delete mode 100644 apps/applications/serializers/attrs/application_type/__init__.py delete mode 100644 apps/applications/serializers/attrs/application_type/chrome.py delete mode 100644 apps/applications/serializers/attrs/application_type/custom.py delete mode 100644 apps/applications/serializers/attrs/application_type/k8s.py delete mode 100644 apps/applications/serializers/attrs/application_type/mariadb.py delete mode 100644 apps/applications/serializers/attrs/application_type/mongodb.py delete mode 100644 apps/applications/serializers/attrs/application_type/mysql.py delete mode 100644 apps/applications/serializers/attrs/application_type/mysql_workbench.py delete mode 100644 apps/applications/serializers/attrs/application_type/oracle.py delete mode 100644 apps/applications/serializers/attrs/application_type/pgsql.py delete mode 100644 apps/applications/serializers/attrs/application_type/redis.py delete mode 100644 apps/applications/serializers/attrs/application_type/sqlserver.py delete mode 100644 apps/applications/serializers/attrs/application_type/vmware_client.py delete mode 100644 apps/applications/serializers/attrs/attrs.py delete mode 100644 apps/applications/serializers/remote_app.py delete mode 100644 apps/applications/tests.py delete mode 100644 apps/applications/urls/__init__.py delete mode 100644 apps/applications/urls/api_urls.py delete mode 100644 apps/applications/utils/__init__.py delete mode 100644 apps/applications/utils/kubernetes_util.py delete mode 100644 apps/perms/api/application/__init__.py delete mode 100644 apps/perms/api/application/application_permission.py delete mode 100644 apps/perms/api/application/application_permission_relation.py delete mode 100644 apps/perms/api/application/user_group_permission.py delete mode 100644 apps/perms/api/application/user_permission/__init__.py delete mode 100644 apps/perms/api/application/user_permission/common.py delete mode 100644 apps/perms/api/application/user_permission/mixin.py delete mode 100644 apps/perms/api/application/user_permission/user_permission_applications.py delete mode 100644 apps/perms/serializers/application/__init__.py delete mode 100644 apps/perms/serializers/application/permission.py delete mode 100644 apps/perms/serializers/application/permission_relation.py delete mode 100644 apps/perms/serializers/application/user_permission.py delete mode 100644 apps/perms/signal_handlers/app_permission.py delete mode 100644 apps/perms/tree/__init__.py delete mode 100644 apps/perms/tree/app.py delete mode 100644 apps/perms/urls/application_permission.py delete mode 100644 apps/perms/urls/system_user_permission.py diff --git a/apps/applications/__init__.py b/apps/applications/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/applications/admin.py b/apps/applications/admin.py deleted file mode 100644 index 8c38f3f3d..000000000 --- a/apps/applications/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/apps/applications/api/__init__.py b/apps/applications/api/__init__.py deleted file mode 100644 index 7a32ac0a0..000000000 --- a/apps/applications/api/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .application import * -from .account import * -from .remote_app import * diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py deleted file mode 100644 index bad9a02bd..000000000 --- a/apps/applications/api/account.py +++ /dev/null @@ -1,66 +0,0 @@ -# coding: utf-8 -# - -from django_filters import rest_framework as filters -from django.db.models import Q - -from common.drf.filters import BaseFilterSet -from common.drf.api import JMSBulkModelViewSet -from common.mixins import RecordViewLogMixin -from common.permissions import UserConfirmation -from authentication.const import ConfirmType -from rbac.permissions import RBACPermission -from assets.models import SystemUser -from ..models import Account -from .. import serializers - - -class AccountFilterSet(BaseFilterSet): - username = filters.CharFilter(method='do_nothing') - type = filters.CharFilter(field_name='type', lookup_expr='exact') - category = filters.CharFilter(field_name='category', lookup_expr='exact') - app_display = filters.CharFilter(field_name='app_display', lookup_expr='exact') - - class Meta: - model = Account - fields = ['app', 'systemuser'] - - @property - def qs(self): - qs = super().qs - qs = self.filter_username(qs) - return qs - - def filter_username(self, qs): - username = self.get_query_param('username') - if not username: - return qs - q = Q(username=username) | Q(systemuser__username=username) - qs = qs.filter(q).distinct() - return qs - - -class ApplicationAccountViewSet(JMSBulkModelViewSet): - model = Account - search_fields = ['username', 'app_display'] - filterset_class = AccountFilterSet - filterset_fields = ['username', 'app_display', 'type', 'category', 'app'] - serializer_class = serializers.AppAccountSerializer - - def get_queryset(self): - queryset = Account.get_queryset() - return queryset - - -class SystemUserAppRelationViewSet(ApplicationAccountViewSet): - perm_model = SystemUser - - -class ApplicationAccountSecretViewSet(RecordViewLogMixin, ApplicationAccountViewSet): - serializer_class = serializers.AppAccountSecretSerializer - permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] - http_method_names = ['get', 'options'] - rbac_perms = { - 'retrieve': 'applications.view_applicationaccountsecret', - 'list': 'applications.view_applicationaccountsecret', - } diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py deleted file mode 100644 index 71315cdcf..000000000 --- a/apps/applications/api/application.py +++ /dev/null @@ -1,39 +0,0 @@ -# coding: utf-8 -# -from orgs.mixins.api import OrgBulkModelViewSet -from rest_framework.decorators import action -from rest_framework.response import Response - -from common.tree import TreeNodeSerializer -from common.mixins.api import SuggestionMixin -from .. import serializers -from ..models import Application - -__all__ = ['ApplicationViewSet'] - - -class ApplicationViewSet(SuggestionMixin, OrgBulkModelViewSet): - model = Application - filterset_fields = { - 'name': ['exact'], - 'category': ['exact', 'in'], - 'type': ['exact', 'in'], - } - search_fields = ('name', 'type', 'category') - serializer_classes = { - 'default': serializers.AppSerializer, - 'get_tree': TreeNodeSerializer, - 'suggestion': serializers.MiniAppSerializer - } - rbac_perms = { - 'get_tree': 'applications.view_application', - 'match': 'applications.match_application' - } - - @action(methods=['GET'], detail=False, url_path='tree') - def get_tree(self, request, *args, **kwargs): - show_count = request.query_params.get('show_count', '1') == '1' - queryset = self.filter_queryset(self.get_queryset()) - tree_nodes = Application.create_tree_nodes(queryset, show_count=show_count) - serializer = self.get_serializer(tree_nodes, many=True) - return Response(serializer.data) diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py deleted file mode 100644 index 4aea7d93b..000000000 --- a/apps/applications/api/remote_app.py +++ /dev/null @@ -1,16 +0,0 @@ -# coding: utf-8 -# - -from orgs.mixins import generics -from .. import models -from ..serializers import RemoteAppConnectionInfoSerializer - - -__all__ = [ - 'RemoteAppConnectionInfoApi', -] - - -class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): - model = models.Application - serializer_class = RemoteAppConnectionInfoSerializer diff --git a/apps/applications/hands.py b/apps/applications/hands.py deleted file mode 100644 index c2bf4fd20..000000000 --- a/apps/applications/hands.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - jumpserver.__app__.hands.py - ~~~~~~~~~~~~~~~~~ - - This app depends other apps api, function .. should be import or write mack here. - - Other module of this app shouldn't connect with other app. - - :copyright: (c) 2014-2018 by JumpServer Team. - :license: GPL v2, see LICENSE for more details. -""" - - -from users.models import User, UserGroup diff --git a/apps/applications/models/tree.py b/apps/applications/models/tree.py index 1b459d660..cd3c43717 100644 --- a/apps/applications/models/tree.py +++ b/apps/applications/models/tree.py @@ -5,7 +5,6 @@ from django.utils.translation import ugettext_lazy as _ from django.conf import settings from common.tree import TreeNode -from ..utils import KubernetesTree from .. import const diff --git a/apps/applications/permissions.py b/apps/applications/permissions.py deleted file mode 100644 index e56fd3652..000000000 --- a/apps/applications/permissions.py +++ /dev/null @@ -1,9 +0,0 @@ -from rest_framework import permissions - - -__all__ = ['IsRemoteApp'] - - -class IsRemoteApp(permissions.BasePermission): - def has_object_permission(self, request, view, obj): - return obj.category_remote_app diff --git a/apps/applications/serializers/__init__.py b/apps/applications/serializers/__init__.py deleted file mode 100644 index 3785f035d..000000000 --- a/apps/applications/serializers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .application import * -from .remote_app import * diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py deleted file mode 100644 index 35a07e262..000000000 --- a/apps/applications/serializers/application.py +++ /dev/null @@ -1,168 +0,0 @@ -# coding: utf-8 -# -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from assets.serializers.base import AuthSerializerMixin -from common.drf.serializers import MethodSerializer, SecretReadableMixin -from .attrs import ( - category_serializer_classes_mapping, - type_serializer_classes_mapping, - type_secret_serializer_classes_mapping -) -from .. import models -from .. import const - -__all__ = [ - 'AppSerializer', 'MiniAppSerializer', 'AppSerializerMixin', - 'AppAccountSerializer', 'AppAccountSecretSerializer' -] - - -class AppSerializerMixin(serializers.Serializer): - attrs = MethodSerializer() - - @property - def app(self): - if isinstance(self.instance, models.Application): - instance = self.instance - else: - instance = None - return instance - - def get_attrs_serializer(self): - default_serializer = serializers.Serializer(read_only=True) - instance = self.app - if instance: - _type = instance.type - _category = instance.category - else: - _type = self.context['request'].query_params.get('type') - _category = self.context['request'].query_params.get('category') - if _type: - if isinstance(self, AppAccountSecretSerializer): - serializer_class = type_secret_serializer_classes_mapping.get(_type) - else: - serializer_class = type_serializer_classes_mapping.get(_type) - elif _category: - serializer_class = category_serializer_classes_mapping.get(_category) - else: - serializer_class = default_serializer - - if not serializer_class: - serializer_class = default_serializer - - if isinstance(serializer_class, type): - serializer = serializer_class() - else: - serializer = serializer_class - return serializer - - def create(self, validated_data): - return super().create(validated_data) - - def update(self, instance, validated_data): - return super().update(instance, validated_data) - - -class AppSerializer(AppSerializerMixin, BulkOrgResourceModelSerializer): - category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display')) - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) - - class Meta: - model = models.Application - fields_mini = ['id', 'name'] - fields_small = fields_mini + [ - 'category', 'category_display', 'type', 'type_display', - 'attrs', 'date_created', 'date_updated', 'created_by', 'comment' - ] - fields_fk = ['domain'] - fields = fields_small + fields_fk - read_only_fields = [ - 'created_by', 'date_created', 'date_updated', 'get_type_display', - ] - - def validate_attrs(self, attrs): - _attrs = self.instance.attrs if self.instance else {} - _attrs.update(attrs) - return _attrs - - -class MiniAppSerializer(serializers.ModelSerializer): - class Meta: - model = models.Application - fields = AppSerializer.Meta.fields_mini - - -class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResourceModelSerializer): - category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True) - category_display = serializers.SerializerMethodField(label=_('Category display')) - type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True) - type_display = serializers.SerializerMethodField(label=_('Type display')) - date_created = serializers.DateTimeField(label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True) - date_updated = serializers.DateTimeField(label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True) - - category_mapper = dict(const.AppCategory.choices) - type_mapper = dict(const.AppType.choices) - - class Meta: - model = models.Account - fields_mini = ['id', 'username', 'version'] - fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] - fields_other = ['date_created', 'date_updated'] - fields_fk = ['systemuser', 'systemuser_display', 'app', 'app_display'] - fields = fields_mini + fields_fk + fields_write_only + fields_other + [ - 'type', 'type_display', 'category', 'category_display', 'attrs' - ] - extra_kwargs = { - 'username': {'default': '', 'required': False}, - 'password': {'write_only': True}, - 'app_display': {'label': _('Application display')}, - 'systemuser_display': {'label': _('System User')}, - 'account': {'label': _('account')} - } - use_model_bulk_create = True - model_bulk_create_kwargs = { - 'ignore_conflicts': True - } - - @property - def app(self): - if isinstance(self.instance, models.Account): - instance = self.instance.app - else: - instance = None - return instance - - def get_category_display(self, obj): - return self.category_mapper.get(obj.category) - - def get_type_display(self, obj): - return self.type_mapper.get(obj.type) - - @classmethod - def setup_eager_loading(cls, queryset): - """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('systemuser', 'app') - return queryset - - def to_representation(self, instance): - instance.load_auth() - return super().to_representation(instance) - - -class AppAccountSecretSerializer(SecretReadableMixin, AppAccountSerializer): - class Meta(AppAccountSerializer.Meta): - fields_backup = [ - 'id', 'app_display', 'attrs', 'username', 'password', 'private_key', - 'public_key', 'date_created', 'date_updated', 'version' - ] - - extra_kwargs = { - 'password': {'write_only': False}, - 'private_key': {'write_only': False}, - 'public_key': {'write_only': False}, - 'app_display': {'label': _('Application display')}, - 'systemuser_display': {'label': _('System User')} - } diff --git a/apps/applications/serializers/attrs/__init__.py b/apps/applications/serializers/attrs/__init__.py deleted file mode 100644 index bbbffd064..000000000 --- a/apps/applications/serializers/attrs/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .attrs import * diff --git a/apps/applications/serializers/attrs/application_category/__init__.py b/apps/applications/serializers/attrs/application_category/__init__.py deleted file mode 100644 index 1fd0beb3d..000000000 --- a/apps/applications/serializers/attrs/application_category/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .remote_app import * -from .db import * -from .cloud import * diff --git a/apps/applications/serializers/attrs/application_category/cloud.py b/apps/applications/serializers/attrs/application_category/cloud.py deleted file mode 100644 index 64306b24e..000000000 --- a/apps/applications/serializers/attrs/application_category/cloud.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -__all__ = ['CloudSerializer'] - - -class CloudSerializer(serializers.Serializer): - cluster = serializers.CharField(max_length=1024, label=_('Cluster'), allow_null=True) diff --git a/apps/applications/serializers/attrs/application_category/db.py b/apps/applications/serializers/attrs/application_category/db.py deleted file mode 100644 index f2967963e..000000000 --- a/apps/applications/serializers/attrs/application_category/db.py +++ /dev/null @@ -1,15 +0,0 @@ -# coding: utf-8 -# -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - - -__all__ = ['DBSerializer'] - - -class DBSerializer(serializers.Serializer): - host = serializers.CharField(max_length=128, label=_('Host'), allow_null=True) - port = serializers.IntegerField(label=_('Port'), allow_null=True) - database = serializers.CharField( - max_length=128, required=True, allow_null=True, label=_('Database') - ) diff --git a/apps/applications/serializers/attrs/application_category/remote_app.py b/apps/applications/serializers/attrs/application_category/remote_app.py deleted file mode 100644 index ad2610791..000000000 --- a/apps/applications/serializers/attrs/application_category/remote_app.py +++ /dev/null @@ -1,60 +0,0 @@ -# coding: utf-8 -# - -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ -from django.core.exceptions import ObjectDoesNotExist - -from common.utils import get_logger, is_uuid, get_object_or_none -from assets.models import Asset - -logger = get_logger(__file__) - -__all__ = ['RemoteAppSerializer'] - - -class ExistAssetPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): - - def to_internal_value(self, data): - instance = super().to_internal_value(data) - return str(instance.id) - - def to_representation(self, _id): - # _id 是 instance.id - if self.pk_field is not None: - return self.pk_field.to_representation(_id) - # 解决删除资产后,远程应用更新页面会显示资产ID的问题 - asset = get_object_or_none(Asset, id=_id) - if not asset: - return None - return _id - - -class RemoteAppSerializer(serializers.Serializer): - asset_info = serializers.SerializerMethodField() - asset = ExistAssetPrimaryKeyRelatedField( - queryset=Asset.objects, required=True, label=_("Asset"), allow_null=True - ) - path = serializers.CharField( - max_length=128, label=_('Application path'), allow_null=True - ) - - def validate_asset(self, asset): - if not asset: - raise serializers.ValidationError(_('This field is required.')) - return asset - - @staticmethod - def get_asset_info(obj): - asset_id = obj.get('asset') - if not asset_id or not is_uuid(asset_id): - return {} - try: - asset = Asset.objects.get(id=str(asset_id)) - except ObjectDoesNotExist as e: - logger.error(e) - return {} - if not asset: - return {} - asset_info = {'id': str(asset.id), 'hostname': asset.hostname} - return asset_info diff --git a/apps/applications/serializers/attrs/application_type/__init__.py b/apps/applications/serializers/attrs/application_type/__init__.py deleted file mode 100644 index 603dd89a4..000000000 --- a/apps/applications/serializers/attrs/application_type/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ - -from .mysql import * -from .mariadb import * -from .oracle import * -from .pgsql import * -from .sqlserver import * -from .redis import * -from .mongodb import * - -from .chrome import * -from .mysql_workbench import * -from .vmware_client import * -from .custom import * - -from .k8s import * diff --git a/apps/applications/serializers/attrs/application_type/chrome.py b/apps/applications/serializers/attrs/application_type/chrome.py deleted file mode 100644 index 08035bc31..000000000 --- a/apps/applications/serializers/attrs/application_type/chrome.py +++ /dev/null @@ -1,34 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers - -from common.drf.fields import EncryptedField -from ..application_category import RemoteAppSerializer - -__all__ = ['ChromeSerializer', 'ChromeSecretSerializer'] - - -class ChromeSerializer(RemoteAppSerializer): - CHROME_PATH = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe' - - path = serializers.CharField( - max_length=128, label=_('Application path'), default=CHROME_PATH, allow_null=True, - ) - chrome_target = serializers.CharField( - max_length=128, allow_blank=True, required=False, - label=_('Target URL'), allow_null=True, - ) - chrome_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, - label=_('Chrome username'), allow_null=True, - ) - chrome_password = EncryptedField( - max_length=128, allow_blank=True, required=False, - label=_('Chrome password'), allow_null=True - ) - - -class ChromeSecretSerializer(ChromeSerializer): - chrome_password = EncryptedField( - max_length=128, allow_blank=True, required=False, - label=_('Chrome password'), allow_null=True, write_only=False - ) diff --git a/apps/applications/serializers/attrs/application_type/custom.py b/apps/applications/serializers/attrs/application_type/custom.py deleted file mode 100644 index cfef59d5f..000000000 --- a/apps/applications/serializers/attrs/application_type/custom.py +++ /dev/null @@ -1,33 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers - -from common.drf.fields import EncryptedField -from ..application_category import RemoteAppSerializer - -__all__ = ['CustomSerializer', 'CustomSecretSerializer'] - - -class CustomSerializer(RemoteAppSerializer): - custom_cmdline = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Operating parameter'), - allow_null=True, - ) - custom_target = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Target url'), - allow_null=True, - ) - custom_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Custom Username'), - allow_null=True, - ) - custom_password = EncryptedField( - max_length=128, allow_blank=True, required=False, - label=_('Custom password'), allow_null=True, - ) - - -class CustomSecretSerializer(RemoteAppSerializer): - custom_password = EncryptedField( - max_length=128, allow_blank=True, required=False, write_only=False, - label=_('Custom password'), allow_null=True, - ) diff --git a/apps/applications/serializers/attrs/application_type/k8s.py b/apps/applications/serializers/attrs/application_type/k8s.py deleted file mode 100644 index cccfd67a0..000000000 --- a/apps/applications/serializers/attrs/application_type/k8s.py +++ /dev/null @@ -1,7 +0,0 @@ -from ..application_category import CloudSerializer - -__all__ = ['K8SSerializer'] - - -class K8SSerializer(CloudSerializer): - pass diff --git a/apps/applications/serializers/attrs/application_type/mariadb.py b/apps/applications/serializers/attrs/application_type/mariadb.py deleted file mode 100644 index e5693e429..000000000 --- a/apps/applications/serializers/attrs/application_type/mariadb.py +++ /dev/null @@ -1,7 +0,0 @@ -from .mysql import MySQLSerializer - -__all__ = ['MariaDBSerializer'] - - -class MariaDBSerializer(MySQLSerializer): - pass diff --git a/apps/applications/serializers/attrs/application_type/mongodb.py b/apps/applications/serializers/attrs/application_type/mongodb.py deleted file mode 100644 index 3824e9c8a..000000000 --- a/apps/applications/serializers/attrs/application_type/mongodb.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from ..application_category import DBSerializer - -__all__ = ['MongoDBSerializer'] - - -class MongoDBSerializer(DBSerializer): - port = serializers.IntegerField(default=27017, label=_('Port'), allow_null=True) - diff --git a/apps/applications/serializers/attrs/application_type/mysql.py b/apps/applications/serializers/attrs/application_type/mysql.py deleted file mode 100644 index 78f312ebe..000000000 --- a/apps/applications/serializers/attrs/application_type/mysql.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from ..application_category import DBSerializer - -__all__ = ['MySQLSerializer'] - - -class MySQLSerializer(DBSerializer): - port = serializers.IntegerField(default=3306, label=_('Port'), allow_null=True) - diff --git a/apps/applications/serializers/attrs/application_type/mysql_workbench.py b/apps/applications/serializers/attrs/application_type/mysql_workbench.py deleted file mode 100644 index 6092b2ed1..000000000 --- a/apps/applications/serializers/attrs/application_type/mysql_workbench.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers - -from common.drf.fields import EncryptedField -from ..application_category import RemoteAppSerializer - -__all__ = ['MySQLWorkbenchSerializer', 'MySQLWorkbenchSecretSerializer'] - - -class MySQLWorkbenchSerializer(RemoteAppSerializer): - MYSQL_WORKBENCH_PATH = 'C:\Program Files\MySQL\MySQL Workbench 8.0 CE\MySQLWorkbench.exe' - - path = serializers.CharField( - max_length=128, label=_('Application path'), default=MYSQL_WORKBENCH_PATH, - allow_null=True, - ) - mysql_workbench_ip = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('IP'), - allow_null=True, - ) - mysql_workbench_port = serializers.IntegerField( - required=False, label=_('Port'), - allow_null=True, - ) - mysql_workbench_name = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Database'), - allow_null=True, - ) - mysql_workbench_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Mysql workbench username'), - allow_null=True, - ) - mysql_workbench_password = EncryptedField( - max_length=128, allow_blank=True, required=False, - label=_('Mysql workbench password'), allow_null=True, - ) - - -class MySQLWorkbenchSecretSerializer(RemoteAppSerializer): - mysql_workbench_password = EncryptedField( - max_length=128, allow_blank=True, required=False, write_only=False, - label=_('Mysql workbench password'), allow_null=True, - ) diff --git a/apps/applications/serializers/attrs/application_type/oracle.py b/apps/applications/serializers/attrs/application_type/oracle.py deleted file mode 100644 index c87c4904d..000000000 --- a/apps/applications/serializers/attrs/application_type/oracle.py +++ /dev/null @@ -1,10 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from ..application_category import DBSerializer - -__all__ = ['OracleSerializer'] - - -class OracleSerializer(DBSerializer): - port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True) diff --git a/apps/applications/serializers/attrs/application_type/pgsql.py b/apps/applications/serializers/attrs/application_type/pgsql.py deleted file mode 100644 index 0434a0423..000000000 --- a/apps/applications/serializers/attrs/application_type/pgsql.py +++ /dev/null @@ -1,10 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from ..application_category import DBSerializer - -__all__ = ['PostgreSerializer'] - - -class PostgreSerializer(DBSerializer): - port = serializers.IntegerField(default=5432, label=_('Port'), allow_null=True) diff --git a/apps/applications/serializers/attrs/application_type/redis.py b/apps/applications/serializers/attrs/application_type/redis.py deleted file mode 100644 index 06cd1ae3b..000000000 --- a/apps/applications/serializers/attrs/application_type/redis.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from ..application_category import DBSerializer - -__all__ = ['RedisSerializer'] - - -class RedisSerializer(DBSerializer): - port = serializers.IntegerField(default=6379, label=_('Port'), allow_null=True) - diff --git a/apps/applications/serializers/attrs/application_type/sqlserver.py b/apps/applications/serializers/attrs/application_type/sqlserver.py deleted file mode 100644 index 5f9b5d2bf..000000000 --- a/apps/applications/serializers/attrs/application_type/sqlserver.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from ..application_category import DBSerializer - -__all__ = ['SQLServerSerializer'] - - -class SQLServerSerializer(DBSerializer): - port = serializers.IntegerField(default=1433, label=_('Port'), allow_null=True) - diff --git a/apps/applications/serializers/attrs/application_type/vmware_client.py b/apps/applications/serializers/attrs/application_type/vmware_client.py deleted file mode 100644 index d6b9cef0b..000000000 --- a/apps/applications/serializers/attrs/application_type/vmware_client.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers - -from common.drf.fields import EncryptedField -from ..application_category import RemoteAppSerializer - -__all__ = ['VMwareClientSerializer', 'VMwareClientSecretSerializer'] - - -class VMwareClientSerializer(RemoteAppSerializer): - PATH = r''' - C:\Program Files (x86)\VMware\Infrastructure\Virtual Infrastructure Client\Launcher\VpxClient - .exe - ''' - VMWARE_CLIENT_PATH = ''.join(PATH.split()) - - path = serializers.CharField( - max_length=128, label=_('Application path'), default=VMWARE_CLIENT_PATH, - allow_null=True - ) - vmware_target = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Target URL'), - allow_null=True - ) - vmware_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Vmware username'), - allow_null=True - ) - vmware_password = EncryptedField( - max_length=128, allow_blank=True, required=False, - label=_('Vmware password'), allow_null=True - ) - - -class VMwareClientSecretSerializer(RemoteAppSerializer): - vmware_password = EncryptedField( - max_length=128, allow_blank=True, required=False, write_only=False, - label=_('Vmware password'), allow_null=True - ) diff --git a/apps/applications/serializers/attrs/attrs.py b/apps/applications/serializers/attrs/attrs.py deleted file mode 100644 index 1f3fb7944..000000000 --- a/apps/applications/serializers/attrs/attrs.py +++ /dev/null @@ -1,62 +0,0 @@ -import copy - -from applications import const -from . import application_category, application_type - -__all__ = [ - 'category_serializer_classes_mapping', - 'type_serializer_classes_mapping', - 'get_serializer_class_by_application_type', - 'type_secret_serializer_classes_mapping' -] - -# define `attrs` field `category serializers mapping` -# --------------------------------------------------- - -category_serializer_classes_mapping = { - const.AppCategory.db.value: application_category.DBSerializer, - const.AppCategory.remote_app.value: application_category.RemoteAppSerializer, - const.AppCategory.cloud.value: application_category.CloudSerializer, -} - -# define `attrs` field `type serializers mapping` -# ----------------------------------------------- - -type_serializer_classes_mapping = { - # db - const.AppType.mysql.value: application_type.MySQLSerializer, - const.AppType.mariadb.value: application_type.MariaDBSerializer, - const.AppType.oracle.value: application_type.OracleSerializer, - const.AppType.pgsql.value: application_type.PostgreSerializer, - const.AppType.sqlserver.value: application_type.SQLServerSerializer, - const.AppType.redis.value: application_type.RedisSerializer, - const.AppType.mongodb.value: application_type.MongoDBSerializer, - # cloud - const.AppType.k8s.value: application_type.K8SSerializer -} - -remote_app_serializer_classes_mapping = { - # remote-app - const.AppType.chrome.value: application_type.ChromeSerializer, - const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer, - const.AppType.vmware_client.value: application_type.VMwareClientSerializer, - const.AppType.custom.value: application_type.CustomSerializer -} - -type_serializer_classes_mapping.update(remote_app_serializer_classes_mapping) - -remote_app_secret_serializer_classes_mapping = { - # remote-app - const.AppType.chrome.value: application_type.ChromeSecretSerializer, - const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSecretSerializer, - const.AppType.vmware_client.value: application_type.VMwareClientSecretSerializer, - const.AppType.custom.value: application_type.CustomSecretSerializer -} - -type_secret_serializer_classes_mapping = copy.deepcopy(type_serializer_classes_mapping) - -type_secret_serializer_classes_mapping.update(remote_app_secret_serializer_classes_mapping) - - -def get_serializer_class_by_application_type(_application_type): - return type_serializer_classes_mapping.get(_application_type) diff --git a/apps/applications/serializers/remote_app.py b/apps/applications/serializers/remote_app.py deleted file mode 100644 index d036a8c65..000000000 --- a/apps/applications/serializers/remote_app.py +++ /dev/null @@ -1,31 +0,0 @@ -# coding: utf-8 -# -from rest_framework import serializers -from common.utils import get_logger -from ..models import Application - - -logger = get_logger(__file__) - - -__all__ = ['RemoteAppConnectionInfoSerializer'] - - -class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer): - parameter_remote_app = serializers.SerializerMethodField() - asset = serializers.SerializerMethodField() - - class Meta: - model = Application - fields = [ - 'id', 'name', 'asset', 'parameter_remote_app', - ] - read_only_fields = ['parameter_remote_app'] - - @staticmethod - def get_asset(obj): - return obj.attrs.get('asset') - - @staticmethod - def get_parameter_remote_app(obj): - return obj.get_rdp_remote_app_setting() diff --git a/apps/applications/tests.py b/apps/applications/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/apps/applications/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/applications/urls/__init__.py b/apps/applications/urls/__init__.py deleted file mode 100644 index 3aab4972f..000000000 --- a/apps/applications/urls/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# coding: utf-8 -# - - -__all__ = [ - -] diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py deleted file mode 100644 index 4fdf006b0..000000000 --- a/apps/applications/urls/api_urls.py +++ /dev/null @@ -1,25 +0,0 @@ -# coding:utf-8 -# -from django.urls import path -from rest_framework_bulk.routes import BulkRouter -from .. import api - - -app_name = 'applications' - - -router = BulkRouter() -router.register(r'applications', api.ApplicationViewSet, 'application') -router.register(r'accounts', api.ApplicationAccountViewSet, 'application-account') -router.register(r'system-users-apps-relations', api.SystemUserAppRelationViewSet, 'system-users-apps-relation') -router.register(r'account-secrets', api.ApplicationAccountSecretViewSet, 'application-account-secret') - - -urlpatterns = [ - path('remote-apps//connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'), - # path('accounts/', api.ApplicationAccountViewSet.as_view(), name='application-account'), - # path('account-secrets/', api.ApplicationAccountSecretViewSet.as_view(), name='application-account-secret') -] - - -urlpatterns += router.urls diff --git a/apps/applications/utils/__init__.py b/apps/applications/utils/__init__.py deleted file mode 100644 index 5efec40b2..000000000 --- a/apps/applications/utils/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from .kubernetes_util import * diff --git a/apps/applications/utils/kubernetes_util.py b/apps/applications/utils/kubernetes_util.py deleted file mode 100644 index e95922d48..000000000 --- a/apps/applications/utils/kubernetes_util.py +++ /dev/null @@ -1,186 +0,0 @@ -# -*- coding: utf-8 -*- -from urllib3.exceptions import MaxRetryError -from urllib.parse import urlencode - -from kubernetes.client import api_client -from kubernetes.client.api import core_v1_api -from kubernetes import client -from kubernetes.client.exceptions import ApiException - -from rest_framework.generics import get_object_or_404 - -from common.utils import get_logger -from common.tree import TreeNode -from assets.models import SystemUser - -from .. import const - -logger = get_logger(__file__) - - -class KubernetesClient: - def __init__(self, url, token): - self.url = url - self.token = token - - def get_api(self): - configuration = client.Configuration() - configuration.host = self.url - configuration.verify_ssl = False - configuration.api_key = {"authorization": "Bearer " + self.token} - c = api_client.ApiClient(configuration=configuration) - api = core_v1_api.CoreV1Api(c) - return api - - def get_namespace_list(self): - api = self.get_api() - namespace_list = [] - for ns in api.list_namespace().items: - namespace_list.append(ns.metadata.name) - return namespace_list - - def get_services(self): - api = self.get_api() - ret = api.list_service_for_all_namespaces(watch=False) - for i in ret.items: - print("%s \t%s \t%s \t%s \t%s \n" % ( - i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports)) - - def get_pod_info(self, namespace, pod): - api = self.get_api() - resp = api.read_namespaced_pod(namespace=namespace, name=pod) - return resp - - def get_pod_logs(self, namespace, pod): - api = self.get_api() - log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200) - return log_content - - def get_pods(self): - api = self.get_api() - try: - ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3)) - except MaxRetryError: - logger.warning('Kubernetes connection timed out') - return - except ApiException as e: - if e.status == 401: - logger.warning('Kubernetes User not authenticated') - else: - logger.warning(e) - return - data = {} - for i in ret.items: - namespace = i.metadata.namespace - pod_info = { - 'pod_name': i.metadata.name, - 'containers': [j.name for j in i.spec.containers] - } - if namespace in data: - data[namespace].append(pod_info) - else: - data[namespace] = [pod_info, ] - return data - - @staticmethod - def get_kubernetes_data(app_id, system_user_id): - from ..models import Application - app = get_object_or_404(Application, id=app_id) - system_user = get_object_or_404(SystemUser, id=system_user_id) - k8s = KubernetesClient(app.attrs['cluster'], system_user.token) - return k8s.get_pods() - - -class KubernetesTree: - def __init__(self, tree_id): - self.tree_id = tree_id - - def as_tree_node(self, app): - pid = app.create_app_tree_pid(self.tree_id) - app_id = str(app.id) - parent_info = {'app_id': app_id} - node = self.create_tree_node( - app_id, pid, app.name, 'k8s', parent_info - ) - return node - - def as_system_user_tree_node(self, system_user, parent_info): - from ..models import ApplicationTreeNodeMixin - system_user_id = str(system_user.id) - username = system_user.username - username = username if username else '*' - name = f'{system_user.name}({username})' - pid = urlencode({'app_id': self.tree_id}) - i = ApplicationTreeNodeMixin.create_tree_id(pid, 'system_user_id', system_user_id) - parent_info.update({'system_user_id': system_user_id}) - node = self.create_tree_node( - i, pid, name, 'system_user', parent_info, icon='user-tie' - ) - return node - - def as_namespace_pod_tree_node(self, name, meta, type, counts=0, is_container=False): - from ..models import ApplicationTreeNodeMixin - i = ApplicationTreeNodeMixin.create_tree_id(self.tree_id, type, name) - meta.update({type: name}) - name = name if is_container else f'{name}({counts})' - node = self.create_tree_node( - i, self.tree_id, name, type, meta, icon='cloud', is_container=is_container - ) - return node - - @staticmethod - def create_tree_node(id_, pid, name, identity, parent_info, icon='', is_container=False): - node = TreeNode(**{ - 'id': id_, - 'name': name, - 'title': name, - 'pId': pid, - 'isParent': not is_container, - 'open': False, - 'iconSkin': icon, - 'parentInfo': urlencode(parent_info), - 'meta': { - 'type': 'application', - 'data': { - 'category': const.AppCategory.cloud, - 'type': const.AppType.k8s, - 'identity': identity - } - } - }) - return node - - def async_tree_node(self, parent_info): - pod_name = parent_info.get('pod') - app_id = parent_info.get('app_id') - namespace = parent_info.get('namespace') - system_user_id = parent_info.get('system_user_id') - - tree_nodes = [] - data = KubernetesClient.get_kubernetes_data(app_id, system_user_id) - if not data: - return tree_nodes - - if pod_name: - for container in next( - filter( - lambda x: x['pod_name'] == pod_name, data[namespace] - ) - )['containers']: - container_node = self.as_namespace_pod_tree_node( - container, parent_info, 'container', is_container=True - ) - tree_nodes.append(container_node) - elif namespace: - for pod in data[namespace]: - pod_nodes = self.as_namespace_pod_tree_node( - pod['pod_name'], parent_info, 'pod', len(pod['containers']) - ) - tree_nodes.append(pod_nodes) - elif system_user_id: - for namespace, pods in data.items(): - namespace_node = self.as_namespace_pod_tree_node( - namespace, parent_info, 'namespace', len(pods) - ) - tree_nodes.append(namespace_node) - return tree_nodes diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index e355a83bb..16cc25264 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import JMSWritableNestedModelSerializer from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from ...models import Asset, Node, Platform, Protocol, Label, Domain +from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account from ..mixin import CategoryDisplayMixin from ..account import AccountSerializer @@ -59,7 +59,7 @@ class AssetSerializer(CategoryDisplayMixin, OrgResourceModelSerializerMixin): domain = AssetDomainSerializer(required=False) nodes_display = serializers.ListField( - child=serializers.CharField(), label=_('Nodes name'), required=False + child=serializers.CharField(), label=_('Nodes name'), required=False, ) labels = AssetLabelSerializer(many=True, required=False) nodes = AssetNodesSerializer(many=True, required=False) @@ -105,7 +105,6 @@ class AssetSerializer(CategoryDisplayMixin, super().__init__(*args, **kwargs) def validate_type(self, value): - print(self.initial_data) return value def validate_category(self, value): diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index cd4d2311d..730796b1d 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -21,7 +21,6 @@ api_v1 = [ path('settings/', include('settings.urls.api_urls', namespace='api-settings')), path('authentication/', include('authentication.urls.api_urls', namespace='api-auth')), path('common/', include('common.urls.api_urls', namespace='api-common')), - path('applications/', include('applications.urls.api_urls', namespace='api-applications')), path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')), path('acls/', include('acls.urls.api_urls', namespace='api-acls')), path('notifications/', include('notifications.urls.api_urls', namespace='api-notifications')), diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index d9a7afe0f..e4983cd95 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -2,5 +2,4 @@ # from .asset import * -from .application import * from .system_user_permission import * diff --git a/apps/perms/api/application/__init__.py b/apps/perms/api/application/__init__.py deleted file mode 100644 index 05e4ff6d2..000000000 --- a/apps/perms/api/application/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .user_permission import * -from .application_permission import * -from .application_permission_relation import * -from .user_group_permission import * diff --git a/apps/perms/api/application/application_permission.py b/apps/perms/api/application/application_permission.py deleted file mode 100644 index 798455053..000000000 --- a/apps/perms/api/application/application_permission.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# -from applications.models import Application -from perms.models import ApplicationPermission -from perms import serializers -from ..base import BasePermissionViewSet - - -class ApplicationPermissionViewSet(BasePermissionViewSet): - """ - 应用授权列表的增删改查API - """ - model = ApplicationPermission - serializer_class = serializers.ApplicationPermissionSerializer - filterset_fields = { - 'name': ['exact'], - 'category': ['exact'], - 'type': ['exact', 'in'], - 'from_ticket': ['exact'] - } - search_fields = ['name', 'category', 'type'] - custom_filter_fields = BasePermissionViewSet.custom_filter_fields + [ - 'application_id', 'application', 'app', 'app_name' - ] - ordering_fields = ('name',) - ordering = ('name', ) - - def get_queryset(self): - queryset = super().get_queryset().prefetch_related( - "applications", "users", "user_groups", "system_users" - ) - return queryset - - def filter_application(self, queryset): - app_id = self.request.query_params.get('application_id') or \ - self.request.query_params.get('app') - app_name = self.request.query_params.get('application') or \ - self.request.query_params.get('app_name') - - if app_id: - applications = Application.objects.filter(pk=app_id) - elif app_name: - applications = Application.objects.filter(name=app_name) - else: - return queryset - if not applications: - return queryset.none() - queryset = queryset.filter(applications__in=applications) - return queryset - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_application(queryset) - return queryset - diff --git a/apps/perms/api/application/application_permission_relation.py b/apps/perms/api/application/application_permission_relation.py deleted file mode 100644 index 611d0930e..000000000 --- a/apps/perms/api/application/application_permission_relation.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import generics -from django.db.models import F, Value -from django.db.models.functions import Concat -from django.shortcuts import get_object_or_404 - -from applications.models import Application -from orgs.mixins.api import OrgRelationMixin -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.utils import current_org -from perms import serializers -from perms import models - -__all__ = [ - 'ApplicationPermissionUserRelationViewSet', - 'ApplicationPermissionUserGroupRelationViewSet', - 'ApplicationPermissionApplicationRelationViewSet', - 'ApplicationPermissionSystemUserRelationViewSet', - 'ApplicationPermissionAllApplicationListApi', - 'ApplicationPermissionAllUserListApi', -] - - -class RelationMixin(OrgRelationMixin, OrgBulkModelViewSet): - perm_model = models.ApplicationPermission - - def get_queryset(self): - queryset = super().get_queryset() - org_id = current_org.org_id() - if org_id is not None: - queryset = queryset.filter(applicationpermission__org_id=org_id) - queryset = queryset.annotate(applicationpermission_display=F('applicationpermission__name')) - return queryset - - -class ApplicationPermissionUserRelationViewSet(RelationMixin): - serializer_class = serializers.ApplicationPermissionUserRelationSerializer - m2m_field = models.ApplicationPermission.users.field - filterset_fields = [ - 'id', "user", "applicationpermission", - ] - search_fields = ("user__name", "user__username", "applicationpermission__name") - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate(user_display=F('user__name')) - return queryset - - -class ApplicationPermissionUserGroupRelationViewSet(RelationMixin): - serializer_class = serializers.ApplicationPermissionUserGroupRelationSerializer - m2m_field = models.ApplicationPermission.user_groups.field - filterset_fields = [ - 'id', "usergroup", "applicationpermission" - ] - search_fields = ["usergroup__name", "applicationpermission__name"] - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate(usergroup_display=F('usergroup__name')) - return queryset - - -class ApplicationPermissionApplicationRelationViewSet(RelationMixin): - serializer_class = serializers.ApplicationPermissionApplicationRelationSerializer - m2m_field = models.ApplicationPermission.applications.field - filterset_fields = [ - 'id', 'application', 'applicationpermission', - ] - search_fields = ["id", "application__name", "applicationpermission__name"] - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate(application_display=F('application__name')) - return queryset - - -class ApplicationPermissionSystemUserRelationViewSet(RelationMixin): - serializer_class = serializers.ApplicationPermissionSystemUserRelationSerializer - m2m_field = models.ApplicationPermission.system_users.field - filterset_fields = [ - 'id', 'systemuser', 'applicationpermission', - ] - search_fields = [ - "applicactionpermission__name", "systemuser__name", "systemuser__username" - ] - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate( - systemuser_display=Concat( - F('systemuser__name'), Value('('), F('systemuser__username'), - Value(')') - )) - return queryset - - -class ApplicationPermissionAllApplicationListApi(generics.ListAPIView): - serializer_class = serializers.ApplicationPermissionAllApplicationSerializer - only_fields = serializers.ApplicationPermissionAllApplicationSerializer.Meta.only_fields - filterset_fields = ('name',) - search_fields = filterset_fields - - def get_queryset(self): - pk = self.kwargs.get('pk') - perm = get_object_or_404(models.ApplicationPermission, pk=pk) - applications = Application.objects.filter(granted_by_permissions=perm) \ - .only(*self.only_fields).distinct() - return applications - - -class ApplicationPermissionAllUserListApi(generics.ListAPIView): - serializer_class = serializers.ApplicationPermissionAllUserSerializer - only_fields = serializers.ApplicationPermissionAllUserSerializer.Meta.only_fields - filterset_fields = ('username', 'name') - search_fields = filterset_fields - - def get_queryset(self): - pk = self.kwargs.get('pk') - perm = get_object_or_404(models.ApplicationPermission, pk=pk) - users = perm.get_all_users().only(*self.only_fields).distinct() - return users diff --git a/apps/perms/api/application/user_group_permission.py b/apps/perms/api/application/user_group_permission.py deleted file mode 100644 index e8061e9fc..000000000 --- a/apps/perms/api/application/user_group_permission.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.db.models import Q -from rest_framework.generics import ListAPIView - -from common.mixins.api import CommonApiMixin -from applications.models import Application -from perms import serializers - -__all__ = [ - 'UserGroupGrantedApplicationsApi' -] - - -class UserGroupGrantedApplicationsApi(CommonApiMixin, ListAPIView): - """ - 获取用户组直接授权的应用 - """ - serializer_class = serializers.AppGrantedSerializer - only_fields = serializers.AppGrantedSerializer.Meta.only_fields - filterset_fields = ['id', 'name', 'category', 'type', 'comment'] - search_fields = ['name', 'comment'] - rbac_perms = { - 'list': 'perms.view_applicationpermission' - } - - def get_queryset(self): - user_group_id = self.kwargs.get('pk') - if not user_group_id: - return Application.objects.none() - - queryset = Application.objects\ - .filter(Q(granted_by_permissions__user_groups__id=user_group_id))\ - .distinct().only(*self.only_fields) - return queryset diff --git a/apps/perms/api/application/user_permission/__init__.py b/apps/perms/api/application/user_permission/__init__.py deleted file mode 100644 index 50aed175b..000000000 --- a/apps/perms/api/application/user_permission/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .user_permission_applications import * -from .common import * diff --git a/apps/perms/api/application/user_permission/common.py b/apps/perms/api/application/user_permission/common.py deleted file mode 100644 index 934748a18..000000000 --- a/apps/perms/api/application/user_permission/common.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# -import time - -from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from rest_framework.views import APIView, Response -from rest_framework import status -from rest_framework.generics import ( - ListAPIView, get_object_or_404 -) - -from orgs.utils import tmp_to_root_org -from applications.models import Application -from perms.utils.application.permission import ( - get_application_system_user_ids, - validate_permission, -) -from .mixin import AppRoleAdminMixin, AppRoleUserMixin -from perms.hands import User, SystemUser -from perms import serializers - -__all__ = [ - 'UserGrantedApplicationSystemUsersApi', - 'MyGrantedApplicationSystemUsersApi', - 'ValidateUserApplicationPermissionApi' -] - - -class BaseGrantedApplicationSystemUsersApi(ListAPIView): - serializer_class = serializers.ApplicationSystemUserSerializer - only_fields = serializers.ApplicationSystemUserSerializer.Meta.only_fields - user: None - - def get_application_system_user_ids(self, application): - return get_application_system_user_ids(self.user, application) - - def get_queryset(self): - application_id = self.kwargs.get('application_id') - application = get_object_or_404(Application, id=application_id) - system_user_ids = self.get_application_system_user_ids(application) - system_users = SystemUser.objects.filter(id__in=system_user_ids) \ - .only(*self.only_fields).order_by('priority') - return system_users - - -class UserGrantedApplicationSystemUsersApi(AppRoleAdminMixin, BaseGrantedApplicationSystemUsersApi): - pass - - -class MyGrantedApplicationSystemUsersApi(AppRoleUserMixin, BaseGrantedApplicationSystemUsersApi): - pass - - -@method_decorator(tmp_to_root_org(), name='get') -class ValidateUserApplicationPermissionApi(APIView): - rbac_perms = { - 'GET': 'perms.view_applicationpermission' - } - - def get(self, request, *args, **kwargs): - user_id = request.query_params.get('user_id', '') - application_id = request.query_params.get('application_id', '') - account = system_user_id = request.query_params.get('account', '') - - data = { - 'has_permission': False, - 'expire_at': int(time.time()), - 'actions': [] - } - if not all((user_id, application_id, account)): - return Response(data) - - user = User.objects.get(id=user_id) - application = Application.objects.get(id=application_id) - has_perm, actions, expire_at = validate_permission(user, application, account) - status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN - data = { - 'has_permission': has_perm, - 'expire_at': int(expire_at), - 'actions': actions - } - return Response(data, status=status_code) diff --git a/apps/perms/api/application/user_permission/mixin.py b/apps/perms/api/application/user_permission/mixin.py deleted file mode 100644 index 6e8f91090..000000000 --- a/apps/perms/api/application/user_permission/mixin.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from common.mixins.api import RoleAdminMixin as _RoleAdminMixin -from common.mixins.api import RoleUserMixin as _RoleUserMixin -from orgs.utils import tmp_to_root_org - - -class AppRoleAdminMixin(_RoleAdminMixin): - rbac_perms = ( - ('list', 'perms.view_userapp'), - ('retrieve', 'perms.view_userapps'), - ('get_tree', 'perms.view_userapps'), - ('GET', 'perms.view_userapps'), - ) - - -class AppRoleUserMixin(_RoleUserMixin): - rbac_perms = ( - ('list', 'perms.view_myapps'), - ('retrieve', 'perms.view_myapps'), - ('get_tree', 'perms.view_myapps'), - ('GET', 'perms.view_myapps'), - ) diff --git a/apps/perms/api/application/user_permission/user_permission_applications.py b/apps/perms/api/application/user_permission/user_permission_applications.py deleted file mode 100644 index 82b0ab025..000000000 --- a/apps/perms/api/application/user_permission/user_permission_applications.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# -from typing import Callable - -from rest_framework.generics import ListAPIView -from rest_framework.response import Response - -from common.mixins.api import CommonApiMixin -from common.tree import TreeNodeSerializer -from perms import serializers -from perms.tree.app import GrantedAppTreeUtil -from perms.utils.application.user_permission import ( - get_user_granted_all_applications -) -from .mixin import AppRoleAdminMixin, AppRoleUserMixin - - -__all__ = [ - 'UserAllGrantedApplicationsApi', - 'MyAllGrantedApplicationsApi', - 'UserAllGrantedApplicationsAsTreeApi', - 'MyAllGrantedApplicationsAsTreeApi', -] - - -class AllGrantedApplicationsApi(CommonApiMixin, ListAPIView): - only_fields = serializers.AppGrantedSerializer.Meta.only_fields - serializer_class = serializers.AppGrantedSerializer - filterset_fields = { - 'id': ['exact'], - 'name': ['exact'], - 'category': ['exact'], - 'type': ['exact', 'in'], - 'comment': ['exact'], - } - search_fields = ['name', 'comment'] - user: None - - def get_queryset(self): - queryset = get_user_granted_all_applications(self.user) - return queryset.only(*self.only_fields) - - -class UserAllGrantedApplicationsApi(AppRoleAdminMixin, AllGrantedApplicationsApi): - pass - - -class MyAllGrantedApplicationsApi(AppRoleUserMixin, AllGrantedApplicationsApi): - pass - - -class ApplicationsAsTreeMixin: - """ - 将应用序列化成树的结构返回 - """ - serializer_class = TreeNodeSerializer - user: None - filter_queryset: Callable - get_queryset: Callable - get_serializer: Callable - - def list(self, request, *args, **kwargs): - tree_id = request.query_params.get('tree_id', None) - parent_info = request.query_params.get('parentInfo', None) - queryset = self.filter_queryset(self.get_queryset()) - util = GrantedAppTreeUtil() - - if not tree_id: - tree_nodes = util.create_tree_nodes(queryset) - else: - tree_nodes = util.get_children_nodes(tree_id, parent_info, self.user) - serializer = self.get_serializer(tree_nodes, many=True) - return Response(data=serializer.data) - - -class UserAllGrantedApplicationsAsTreeApi(ApplicationsAsTreeMixin, UserAllGrantedApplicationsApi): - pass - - -class MyAllGrantedApplicationsAsTreeApi(ApplicationsAsTreeMixin, MyAllGrantedApplicationsApi): - pass diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py index a4a701773..5e26adc99 100644 --- a/apps/perms/serializers/__init__.py +++ b/apps/perms/serializers/__init__.py @@ -2,5 +2,4 @@ # from .base import * from .asset import * -from .application import * from .system_user_permission import * diff --git a/apps/perms/serializers/application/__init__.py b/apps/perms/serializers/application/__init__.py deleted file mode 100644 index 5fb99849f..000000000 --- a/apps/perms/serializers/application/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .permission import * -from .permission_relation import * -from .user_permission import * diff --git a/apps/perms/serializers/application/permission.py b/apps/perms/serializers/application/permission.py deleted file mode 100644 index ae65ea869..000000000 --- a/apps/perms/serializers/application/permission.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from perms.models import ApplicationPermission, Action -from ..base import ActionsField, BasePermissionSerializer - -__all__ = [ - 'ApplicationPermissionSerializer' -] - - -class ApplicationPermissionSerializer(BasePermissionSerializer): - actions = ActionsField(required=False, allow_null=True, label=_("Actions")) - category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display')) - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) - is_valid = serializers.BooleanField(read_only=True, label=_('Is valid')) - is_expired = serializers.BooleanField(read_only=True, label=_("Is expired")) - - class Meta: - model = ApplicationPermission - fields_mini = ['id', 'name'] - fields_small = fields_mini + [ - 'category', 'category_display', 'type', 'type_display', - 'actions', - 'is_active', 'is_expired', 'is_valid', - 'created_by', 'date_created', 'date_expired', 'date_start', 'comment', 'from_ticket' - ] - fields_m2m = [ - 'users', 'user_groups', 'applications', 'system_users', - 'users_amount', 'user_groups_amount', 'applications_amount', - 'system_users_amount', - ] - fields = fields_small + fields_m2m - read_only_fields = ['created_by', 'date_created', 'from_ticket'] - extra_kwargs = { - 'is_expired': {'label': _('Is expired')}, - 'is_valid': {'label': _('Is valid')}, - 'actions': {'label': _('Actions')}, - 'users_amount': {'label': _('Users amount')}, - 'user_groups_amount': {'label': _('User groups amount')}, - 'system_users_amount': {'label': _('System users amount')}, - 'applications_amount': {'label': _('Apps amount')}, - } - - def _filter_actions_choices(self, choices): - if request := self.context.get('request'): - category = request.query_params.get('category') - else: - category = None - exclude_choices = ApplicationPermission.get_exclude_actions_choices(category=category) - for choice in exclude_choices: - choices.pop(choice, None) - return choices - - @classmethod - def setup_eager_loading(cls, queryset): - """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related( - 'users', 'user_groups', 'applications', 'system_users' - ) - return queryset - - def validate_applications(self, applications): - if self.instance: - permission_type = self.instance.type - else: - permission_type = self.initial_data['type'] - - other_type_applications = [ - application for application in applications - if application.type != permission_type - ] - if len(other_type_applications) > 0: - error = _( - 'The application list contains applications ' - 'that are different from the permission type. ({})' - ).format(', '.join([application.name for application in other_type_applications])) - raise serializers.ValidationError(error) - return applications diff --git a/apps/perms/serializers/application/permission_relation.py b/apps/perms/serializers/application/permission_relation.py deleted file mode 100644 index 941b03127..000000000 --- a/apps/perms/serializers/application/permission_relation.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import serializers - -from common.mixins import BulkSerializerMixin -from perms.models import ApplicationPermission - -__all__ = [ - 'ApplicationPermissionUserRelationSerializer', - 'ApplicationPermissionUserGroupRelationSerializer', - 'ApplicationPermissionApplicationRelationSerializer', - 'ApplicationPermissionSystemUserRelationSerializer', - 'ApplicationPermissionAllApplicationSerializer', - 'ApplicationPermissionAllUserSerializer' -] - - -class RelationMixin(BulkSerializerMixin, serializers.Serializer): - applicationpermission_display = serializers.ReadOnlyField() - - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend(['applicationpermission', "applicationpermission_display"]) - return fields - - -class ApplicationPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer): - user_display = serializers.ReadOnlyField() - - class Meta: - model = ApplicationPermission.users.through - fields = [ - 'id', 'user', 'user_display', - ] - - -class ApplicationPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer): - usergroup_display = serializers.ReadOnlyField() - - class Meta: - model = ApplicationPermission.user_groups.through - fields = [ - 'id', 'usergroup', "usergroup_display", - ] - - -class ApplicationPermissionApplicationRelationSerializer(RelationMixin, serializers.ModelSerializer): - application_display = serializers.ReadOnlyField() - - class Meta: - model = ApplicationPermission.applications.through - fields = [ - 'id', "application", "application_display", - ] - - -class ApplicationPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer): - systemuser_display = serializers.ReadOnlyField() - - class Meta: - model = ApplicationPermission.system_users.through - fields = [ - 'id', 'systemuser', 'systemuser_display' - ] - - -class ApplicationPermissionAllApplicationSerializer(serializers.Serializer): - application = serializers.UUIDField(read_only=True, source='id') - application_display = serializers.SerializerMethodField() - - class Meta: - only_fields = ['id', 'name'] - - @staticmethod - def get_application_display(obj): - return str(obj) - - -class ApplicationPermissionAllUserSerializer(serializers.Serializer): - user = serializers.UUIDField(read_only=True, source='id') - user_display = serializers.SerializerMethodField() - - class Meta: - only_fields = ['id', 'username', 'name'] - - @staticmethod - def get_user_display(obj): - return str(obj) diff --git a/apps/perms/serializers/application/user_permission.py b/apps/perms/serializers/application/user_permission.py deleted file mode 100644 index 63b681d5a..000000000 --- a/apps/perms/serializers/application/user_permission.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from assets.models import SystemUser -from applications.models import Application -from applications.serializers import AppSerializerMixin - -__all__ = [ - 'AppGrantedSerializer', 'ApplicationSystemUserSerializer' -] - - -class ApplicationSystemUserSerializer(serializers.ModelSerializer): - """ - 查看授权的应用系统用户的数据结构,这个和SystemUserSerializer不同,字段少 - """ - class Meta: - model = SystemUser - only_fields = ( - 'id', 'name', 'username', 'priority', 'protocol', 'login_mode' - ) - fields = list(only_fields) - read_only_fields = fields - - -class AppGrantedSerializer(AppSerializerMixin, serializers.ModelSerializer): - """ - 被授权应用的数据结构 - """ - category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category')) - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type')) - - class Meta: - model = Application - only_fields = [ - 'id', 'name', 'domain', 'category', 'type', 'attrs', 'comment', 'org_id' - ] - fields = only_fields + ['category_display', 'type_display', 'org_name'] - read_only_fields = fields diff --git a/apps/perms/signal_handlers/__init__.py b/apps/perms/signal_handlers/__init__.py index 68a531887..6a8ef9467 100644 --- a/apps/perms/signal_handlers/__init__.py +++ b/apps/perms/signal_handlers/__init__.py @@ -1,3 +1,2 @@ from . import asset_permission -from . import app_permission from . import refresh_perms diff --git a/apps/perms/signal_handlers/app_permission.py b/apps/perms/signal_handlers/app_permission.py deleted file mode 100644 index 104f56e9a..000000000 --- a/apps/perms/signal_handlers/app_permission.py +++ /dev/null @@ -1,106 +0,0 @@ -import itertools - -from django.db.models.signals import m2m_changed -from django.dispatch import receiver - -from users.models import User, UserGroup -from assets.models import Asset, SystemUser -from applications.models import Application -from common.utils import get_logger -from common.exceptions import M2MReverseNotAllowed -from common.decorator import on_transaction_commit -from common.const.signals import POST_ADD -from perms.models import ApplicationPermission -from applications.models import Account as AppAccount - - -logger = get_logger(__file__) - - -@receiver(m2m_changed, sender=ApplicationPermission.applications.through) -@on_transaction_commit -def on_app_permission_applications_changed(sender, instance, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Application permission applications change signal received") - system_users = instance.system_users.all() - set_remote_app_asset_system_users_if_need(instance, system_users=system_users) - - apps = Application.objects.filter(pk__in=pk_set) - set_app_accounts(apps, system_users) - - -def set_app_accounts(apps, system_users): - for app, system_user in itertools.product(apps, system_users): - AppAccount.objects.get_or_create( - defaults={'app': app, 'systemuser': system_user}, - app=app, systemuser=system_user - ) - - -def set_remote_app_asset_system_users_if_need(instance: ApplicationPermission, system_users=None, - users=None, groups=None): - if not instance.category_remote_app: - return - - attrs = instance.applications.all().values_list('attrs', flat=True) - asset_ids = [attr['asset'] for attr in attrs if attr.get('asset')] - # 远程应用中资产可能在资产表里不存在 - asset_ids = Asset.objects.filter(id__in=asset_ids).values_list('id', flat=True) - if not asset_ids: - return - - system_users = system_users or instance.system_users.all() - for system_user in system_users: - system_user.add_related_assets(asset_ids) - - if system_user.username_same_with_user: - users = users or instance.users.all() - groups = groups or instance.user_groups.all() - system_user.groups.add(*groups) - system_user.users.add(*users) - - -@receiver(m2m_changed, sender=ApplicationPermission.system_users.through) -@on_transaction_commit -def on_app_permission_system_users_changed(sender, instance, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Application permission system_users change signal received") - system_users = SystemUser.objects.filter(pk__in=pk_set) - - set_remote_app_asset_system_users_if_need(instance, system_users=system_users) - apps = instance.applications.all() - set_app_accounts(apps, system_users) - - -@receiver(m2m_changed, sender=ApplicationPermission.users.through) -@on_transaction_commit -def on_app_permission_users_changed(sender, instance, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Application permission users change signal received") - users = User.objects.filter(pk__in=pk_set) - set_remote_app_asset_system_users_if_need(instance, users=users) - - -@receiver(m2m_changed, sender=ApplicationPermission.user_groups.through) -@on_transaction_commit -def on_app_permission_user_groups_changed(sender, instance, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if action != POST_ADD: - return - - logger.debug("Application permission user groups change signal received") - groups = UserGroup.objects.filter(pk__in=pk_set) - set_remote_app_asset_system_users_if_need(instance, groups=groups) diff --git a/apps/perms/tree/__init__.py b/apps/perms/tree/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/perms/tree/app.py b/apps/perms/tree/app.py deleted file mode 100644 index 6a6d6c74f..000000000 --- a/apps/perms/tree/app.py +++ /dev/null @@ -1,103 +0,0 @@ -from urllib.parse import urlencode, parse_qsl - -from django.utils.translation import ugettext as _ -from rest_framework.generics import get_object_or_404 - -from common.tree import TreeNode -from orgs.models import Organization -from assets.models import SystemUser -from applications.utils import KubernetesTree -from applications.models import Application -from perms.utils.application.permission import get_application_system_user_ids - - -class GrantedAppTreeUtil: - @staticmethod - def filter_organizations(applications): - organization_ids = set(applications.values_list('org_id', flat=True)) - organizations = [Organization.get_instance(org_id) for org_id in organization_ids] - organizations.sort(key=lambda x: x.name) - return organizations - - @staticmethod - def create_root_node(): - name = _('My applications') - node = TreeNode(**{ - 'id': 'applications', - 'name': name, - 'title': name, - 'pId': '', - 'open': True, - 'iconSkin': 'applications', - 'isParent': True, - 'meta': { - 'type': 'root' - } - }) - return node - - @staticmethod - def create_empty_node(): - name = _("Empty") - node = TreeNode(**{ - 'id': 'empty', - 'name': name, - 'title': name, - 'pId': '', - 'isParent': True, - 'children': [], - 'meta': { - 'type': 'application' - } - }) - return node - - @staticmethod - def get_children_nodes(tree_id, parent_info, user): - tree_nodes = [] - parent_info = dict(parse_qsl(parent_info)) - pod_name = parent_info.get('pod') - app_id = parent_info.get('app_id') - namespace = parent_info.get('namespace') - system_user_id = parent_info.get('system_user_id') - - if app_id and not any([pod_name, namespace, system_user_id]): - app = get_object_or_404(Application, id=app_id) - system_user_ids = get_application_system_user_ids(user, app) - system_users = SystemUser.objects.filter(id__in=system_user_ids).order_by('priority') - for system_user in system_users: - system_user_node = KubernetesTree(tree_id).as_system_user_tree_node( - system_user, parent_info - ) - tree_nodes.append(system_user_node) - return tree_nodes - tree_nodes = KubernetesTree(tree_id).async_tree_node(parent_info) - return tree_nodes - - def create_tree_nodes(self, applications): - tree_nodes = [] - if not applications: - return [self.create_empty_node()] - - root_node = self.create_root_node() - organizations = self.filter_organizations(applications) - - for i, org in enumerate(organizations): - tree_id = urlencode({'org_id': str(org.id)}) - apps = applications.filter(org_id=org.id) - - # 组织节点 - org_node = org.as_tree_node(oid=tree_id, pid=root_node.id) - org_node.name += '({})'.format(apps.count()) - tree_nodes.append(org_node) - - # 类别节点 - category_type_nodes = Application.create_category_type_tree_nodes( - apps, tree_id, show_empty=False - ) - tree_nodes += category_type_nodes - - for app in apps: - app_node = app.as_tree_node(tree_id, k8s_as_tree=True) - tree_nodes.append(app_node) - return tree_nodes diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index c2dfe2380..568c226ee 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -1,19 +1,8 @@ # coding:utf-8 -from django.urls import re_path -from common import api as capi from .asset_permission import asset_permission_urlpatterns -from .application_permission import application_permission_urlpatterns -from .system_user_permission import system_users_permission_urlpatterns app_name = 'perms' -old_version_urlpatterns = [ - re_path('(?Puser|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api) -] - urlpatterns = [] urlpatterns += asset_permission_urlpatterns -urlpatterns += application_permission_urlpatterns -urlpatterns += system_users_permission_urlpatterns -urlpatterns += old_version_urlpatterns diff --git a/apps/perms/urls/application_permission.py b/apps/perms/urls/application_permission.py deleted file mode 100644 index 4ed9e6d37..000000000 --- a/apps/perms/urls/application_permission.py +++ /dev/null @@ -1,48 +0,0 @@ -# coding: utf-8 -# - -from django.urls import path, include -from rest_framework_bulk.routes import BulkRouter -from .. import api - - -router = BulkRouter() -router.register('application-permissions', api.ApplicationPermissionViewSet, 'application-permission') -router.register('application-permissions-users-relations', api.ApplicationPermissionUserRelationViewSet, 'application-permissions-users-relation') -router.register('application-permissions-user-groups-relations', api.ApplicationPermissionUserGroupRelationViewSet, 'application-permissions-user-groups-relation') -router.register('application-permissions-applications-relations', api.ApplicationPermissionApplicationRelationViewSet, 'application-permissions-application-relation') -router.register('application-permissions-system-users-relations', api.ApplicationPermissionSystemUserRelationViewSet, 'application-permissions-system-users-relation') - -user_permission_urlpatterns = [ - path('/applications/', api.UserAllGrantedApplicationsApi.as_view(), name='user-applications'), - path('applications/', api.MyAllGrantedApplicationsApi.as_view(), name='my-applications'), - - # Application As Tree - path('/applications/tree/', api.UserAllGrantedApplicationsAsTreeApi.as_view(), name='user-applications-as-tree'), - path('applications/tree/', api.MyAllGrantedApplicationsAsTreeApi.as_view(), name='my-applications-as-tree'), - - # Application System Users - path('/applications//system-users/', api.UserGrantedApplicationSystemUsersApi.as_view(), name='user-application-system-users'), - path('applications//system-users/', api.MyGrantedApplicationSystemUsersApi.as_view(), name='my-application-system-users'), -] - -user_group_permission_urlpatterns = [ - path('/applications/', api.UserGroupGrantedApplicationsApi.as_view(), name='user-group-applications'), -] - -permission_urlpatterns = [ - # 授权规则中授权的用户和应用 - path('/applications/all/', api.ApplicationPermissionAllApplicationListApi.as_view(), name='application-permission-all-applications'), - path('/users/all/', api.ApplicationPermissionAllUserListApi.as_view(), name='application-permission-all-users'), - - # 验证用户是否有某个应用的权限 - path('user/validate/', api.ValidateUserApplicationPermissionApi.as_view(), name='validate-user-application-permission'), -] - -application_permission_urlpatterns = [ - path('users/', include(user_permission_urlpatterns)), - path('user-groups/', include(user_group_permission_urlpatterns)), - path('application-permissions/', include(permission_urlpatterns)) -] - -application_permission_urlpatterns += router.urls diff --git a/apps/perms/urls/system_user_permission.py b/apps/perms/urls/system_user_permission.py deleted file mode 100644 index e5a5ba1e4..000000000 --- a/apps/perms/urls/system_user_permission.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.urls import path -from .. import api - -system_users_permission_urlpatterns = [ - path('system-users-permission/', api.SystemUserPermission.as_view(), name='system-users-permission'), -] From 1ca0bdf8430d38a407b2a7f3600d9792a0ac0b11 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Aug 2022 16:53:43 +0800 Subject: [PATCH 049/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20category?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/category.py | 1 - apps/assets/serializers/asset/common.py | 36 +++++++++++------------ apps/common/drf/fields.py | 20 ++++++++++++- apps/common/drf/serializers.py | 13 ++++---- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 0b01bb0a2..32d954652 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -1,5 +1,4 @@ from rest_framework import serializers -from django.utils.translation import gettext_lazy as _ from .common import AssetSerializer from assets.models import DeviceInfo, Host, Database diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 16cc25264..e25946dc0 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -4,10 +4,8 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import JMSWritableNestedModelSerializer -from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account -from ..mixin import CategoryDisplayMixin from ..account import AccountSerializer +from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', @@ -18,14 +16,15 @@ __all__ = [ class AssetProtocolsSerializer(serializers.ModelSerializer): class Meta: model = Protocol - fields = ['pk', 'name', 'port'] + fields = ['id', 'name', 'port'] class AssetLabelSerializer(serializers.ModelSerializer): class Meta: model = Label - fields = ['pk', 'name', 'value'] + fields = ['id', 'name', 'value'] extra_kwargs = { + 'name': {'required': False}, 'value': {'required': False} } @@ -33,7 +32,7 @@ class AssetLabelSerializer(serializers.ModelSerializer): class AssetPlatformSerializer(serializers.ModelSerializer): class Meta: model = Platform - fields = ['pk', 'name'] + fields = ['id', 'name'] extra_kwargs = { 'name': {'required': False} } @@ -42,35 +41,34 @@ class AssetPlatformSerializer(serializers.ModelSerializer): class AssetDomainSerializer(serializers.ModelSerializer): class Meta: model = Domain - fields = ['pk', 'name'] + fields = ['id', 'name'] + extra_kwargs = { + 'name': {'required': False} + } class AssetNodesSerializer(serializers.ModelSerializer): class Meta: model = Node - fields = ['pk', 'value'] + fields = ['id', 'value'] extra_kwargs = { 'value': {'required': False} } -class AssetSerializer(CategoryDisplayMixin, - JMSWritableNestedModelSerializer, - OrgResourceModelSerializerMixin): +class AssetSerializer(JMSWritableNestedModelSerializer): + # category = ChoiceDisplayField(choices=Category.choices, required=False) + # type = ChoiceDisplayField(choices=AllTypes.choices, required=False) domain = AssetDomainSerializer(required=False) - nodes_display = serializers.ListField( - child=serializers.CharField(), label=_('Nodes name'), required=False, - ) + platform = AssetPlatformSerializer(required=False) labels = AssetLabelSerializer(many=True, required=False) nodes = AssetNodesSerializer(many=True, required=False) - platform = AssetPlatformSerializer(required=False) accounts = AccountSerializer(many=True, required=False) - protocols = AssetProtocolsSerializer(many=True) + protocols = AssetProtocolsSerializer(many=True, required=False) """ 资产的数据结构 """ - class Meta: model = Asset fields_mini = [ @@ -87,8 +85,8 @@ class AssetSerializer(CategoryDisplayMixin, 'nodes_display', ] read_only_fields = [ - 'category', 'category_display', 'type', 'type_display', - 'connectivity', 'date_verified', 'created_by', 'date_created', + 'category', 'type', 'connectivity', 'date_verified', + 'created_by', 'date_created', ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index f97925f43..c0c214c4f 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- # +import six +from rest_framework.fields import ChoiceField from rest_framework import serializers from common.utils import decrypt_password __all__ = [ - 'ReadableHiddenField', 'EncryptedField' + 'ReadableHiddenField', 'EncryptedField', 'ChoiceDisplayField' ] @@ -36,3 +38,19 @@ class EncryptedField(serializers.CharField): def to_internal_value(self, value): value = super().to_internal_value(value) return decrypt_password(value) + + +class ChoiceDisplayField(ChoiceField): + def __init__(self, *args, **kwargs): + super(ChoiceDisplayField, self).__init__(*args, **kwargs) + self.choice_mapper = { + six.text_type(key): value for key, value in self.choices.items() + } + + def to_representation(self, value): + if value in ('', None): + return value + return { + 'name': value, + 'label': self.choice_mapper.get(six.text_type(value), value), + } diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index ece410d19..627ef728c 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -114,9 +114,10 @@ class SecretReadableMixin(serializers.Serializer): class JMSWritableNestedModelSerializer(WritableNestedModelSerializer): - - def _get_related_pk(self, data, model_class): - pk = data.get('pk') or data.get('id') or data.get(model_class._meta.pk.attname) - if pk: - return str(pk) - return None + pass + # + # def _get_related_pk(self, data, model_class): + # pk = data.get('pk') or data.get('id') or data.get(model_class._meta.pk.attname) + # if pk: + # return str(pk) + # return None From 839099c97c6e4142030b8d98470f18c7416d462a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 10 Aug 2022 17:58:30 +0800 Subject: [PATCH 050/488] =?UTF-8?q?perf:=20=E8=B5=84=E4=BA=A7=E6=8E=88?= =?UTF-8?q?=E6=9D=83Model=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/application_permission.py | 1 + apps/perms/models/asset_permission.py | 129 ++++++++++++++++++-- apps/perms/models/base.py | 3 + 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/apps/perms/models/application_permission.py b/apps/perms/models/application_permission.py index 6bea6e47f..f3e1d0c48 100644 --- a/apps/perms/models/application_permission.py +++ b/apps/perms/models/application_permission.py @@ -1,5 +1,6 @@ # coding: utf-8 # +# TODO: v3 delete 整个文件 from django.db import models from django.db.models import Q diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 2d683920d..eedca805f 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -1,15 +1,15 @@ +import uuid import logging - +from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.db.models import F, TextChoices from django.db import models +from django.db.models import F, Q, TextChoices -from orgs.mixins.models import OrgModelMixin -from common.utils import lazyproperty -from common.db.models import BaseCreateUpdateModel from assets.models import Asset, Node, FamilyMixin - -from .base import BasePermission +from orgs.mixins.models import OrgModelMixin +from orgs.mixins.models import OrgManager +from common.utils import lazyproperty, date_expired_default +from common.db.models import BaseCreateUpdateModel, BitOperationChoice, UnionQuerySet __all__ = [ @@ -20,10 +20,84 @@ __all__ = [ logger = logging.getLogger(__name__) -class AssetPermission(BasePermission): +class Action(BitOperationChoice): + ALL = 0xff + CONNECT = 0b1 + UPLOAD = 0b1 << 1 + DOWNLOAD = 0b1 << 2 + CLIPBOARD_COPY = 0b1 << 3 + CLIPBOARD_PASTE = 0b1 << 4 + UPDOWNLOAD = UPLOAD | DOWNLOAD + CLIPBOARD_COPY_PASTE = CLIPBOARD_COPY | CLIPBOARD_PASTE + + DB_CHOICES = ( + (ALL, _('All')), + (CONNECT, _('Connect')), + (UPLOAD, _('Upload file')), + (DOWNLOAD, _('Download file')), + (UPDOWNLOAD, _("Upload download")), + (CLIPBOARD_COPY, _('Clipboard copy')), + (CLIPBOARD_PASTE, _('Clipboard paste')), + (CLIPBOARD_COPY_PASTE, _('Clipboard copy paste')) + ) + + NAME_MAP = { + ALL: "all", + CONNECT: "connect", + UPLOAD: "upload_file", + DOWNLOAD: "download_file", + UPDOWNLOAD: "updownload", + CLIPBOARD_COPY: 'clipboard_copy', + CLIPBOARD_PASTE: 'clipboard_paste', + CLIPBOARD_COPY_PASTE: 'clipboard_copy_paste' + } + + NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} + CHOICES = [] + for i, j in DB_CHOICES: + CHOICES.append((NAME_MAP[i], j)) + + +class AssetPermissionQuerySet(models.QuerySet): + def active(self): + return self.filter(is_active=True) + + def valid(self): + return self.active().filter(date_start__lt=timezone.now()) \ + .filter(date_expired__gt=timezone.now()) + + def inactive(self): + return self.filter(is_active=False) + + def invalid(self): + now = timezone.now() + q = (Q(is_active=False) | Q(date_start__gt=now) | Q(date_expired__lt=now)) + return self.filter(q) + + +class AssetPermissionManager(OrgManager): + def valid(self): + return self.get_queryset().valid() + + +class AssetPermission(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), related_name='%(class)ss') + user_groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User group"), related_name='%(class)ss') assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) accounts = models.JSONField(default=list, verbose_name=_("Accounts")) + actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions")) + is_active = models.BooleanField(default=True, verbose_name=_('Active')) + date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) + date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) + created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket')) + comment = models.TextField(verbose_name=_('Comment'), blank=True) + + objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)() class Meta: unique_together = [('org_id', 'name')] @@ -31,6 +105,45 @@ class AssetPermission(BasePermission): ordering = ('name',) permissions = [] + def __str__(self): + return self.name + + @property + def id_str(self): + return str(self.id) + + @property + def is_expired(self): + if self.date_expired > timezone.now() > self.date_start: + return False + return True + + @property + def is_valid(self): + if not self.is_expired and self.is_active: + return True + return False + + @property + def all_users(self): + from users.models import User + users_query = self._meta.get_field('users').related_query_name() + user_groups_query = self._meta.get_field('user_groups').related_query_name() + users_q = Q(**{f'{users_query}': self}) + user_groups_q = Q(**{f'groups__{user_groups_query}': self}) + return User.objects.filter(users_q | user_groups_q).distinct() + + def get_all_users(self): + from users.models import User + user_ids = self.users.all().values_list('id', flat=True) + group_ids = self.user_groups.all().values_list('id', flat=True) + user_ids = list(user_ids) + group_ids = list(group_ids) + qs1 = User.objects.filter(id__in=user_ids).distinct() + qs2 = User.objects.filter(groups__id__in=group_ids).distinct() + qs = UnionQuerySet(qs1, qs2) + return qs + @lazyproperty def users_amount(self): return self.users.count() diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py index b2d388717..729ce7df1 100644 --- a/apps/perms/models/base.py +++ b/apps/perms/models/base.py @@ -1,6 +1,9 @@ # coding: utf-8 # +# TODO: v3 delete 整个文件 + + import uuid from django.utils.translation import ugettext_lazy as _ from django.db import models From 9d4a828c53fa13b56caba317c1b6ba06d7908d37 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Aug 2022 19:27:08 +0800 Subject: [PATCH 051/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 9 +-- apps/assets/const.py | 80 +++++++++++-------- .../migrations/0100_auto_20220430_2126.py | 62 +++++++++++--- .../migrations/0105_auto_20220810_1449.py | 41 ++++++++++ apps/assets/models/platform.py | 44 +++++----- apps/assets/serializers/platform.py | 26 +++--- 6 files changed, 176 insertions(+), 86 deletions(-) create mode 100644 apps/assets/migrations/0105_auto_20220810_1449.py diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 6bc1c4681..4098dc29a 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -21,7 +21,7 @@ class AssetPlatformViewSet(JMSModelViewSet): search_fields = ['name'] rbac_perms = { 'categories': 'assets.view_platform', - 'type_limits': 'assets-view_platform' + 'type_constraints': 'assets-view_platform' } @action(methods=['GET'], detail=False) @@ -30,14 +30,13 @@ class AssetPlatformViewSet(JMSModelViewSet): serializer = self.get_serializer(data, many=True) return Response(serializer.data) - @action(methods=['GET'], detail=False, url_path='type-limits') - def type_limits(self, request, *args, **kwargs): + @action(methods=['GET'], detail=False, url_path='type-constraints') + def type_constraints(self, request, *args, **kwargs): category = request.query_params.get('category') tp = request.query_params.get('type') - limits = AllTypes.get_type_limits(category, tp) + limits = AllTypes.get_constraints(category, tp) return Response(limits) - def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( diff --git a/apps/assets/const.py b/apps/assets/const.py index 4da356115..d5bf84727 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -12,8 +12,16 @@ __all__ = [ class PlatformMixin: @classmethod - def platform_limits(cls): - return {} + def platform_constraints(cls): + return { + 'has_domain': False, + 'has_su': False, + 'has_ping': False, + 'has_change_password': False, + 'has_verify_account': False, + 'has_create_account': False, + '_protocols': [] + } class Category(PlatformMixin, models.TextChoices): @@ -24,25 +32,30 @@ class Category(PlatformMixin, models.TextChoices): WEB = 'web', _("Web") @classmethod - def platform_limits(cls): + def platform_constraints(cls) -> dict: return { cls.HOST: { 'has_domain': True, - 'protocols_limit': ['ssh', 'rdp', 'vnc', 'telnet'] + 'has_ping': True, + 'has_verify_account': True, + 'has_change_password': True, + 'has_create_account': True, + '_protocols': ['ssh', 'telnet'] }, cls.NETWORK: { 'has_domain': True, - 'protocols_limit': ['ssh', 'telnet'] + '_protocols': ['ssh', 'telnet'] }, cls.DATABASE: { - 'has_domain': True + 'has_domain': True, }, cls.WEB: { 'has_domain': False, + '_protocols': [] }, cls.CLOUD: { 'has_domain': False, - 'protocol_limit': [] + '_protocols': [] } } @@ -57,18 +70,17 @@ class HostTypes(PlatformMixin, models.TextChoices): OTHER_HOST = 'other_host', _("Other host") @classmethod - def platform_limits(cls): - return {} - - @classmethod - def get_default_port(cls): - defaults = { - cls.LINUX: 22, - cls.WINDOWS: 3389, - cls.UNIX: 22, - cls.BSD: 22, - cls.MACOS: 22, - cls.MAINFRAME: 22, + def platform_constraints(cls): + return { + cls.LINUX: { + '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] + }, + cls.WINDOWS: { + '_protocols': ['ssh', 'rdp', 'vnc'] + }, + cls.MACOS: { + '_protocols': ['ssh', 'vnc'] + } } @@ -89,11 +101,11 @@ class DatabaseTypes(PlatformMixin, models.TextChoices): REDIS = 'redis', 'Redis' @classmethod - def platform_limits(cls): + def platform_constraints(cls): meta = {} for name, label in cls.choices: meta[name] = { - 'protocols_limit': [name] + 'protocols': [name] } return meta @@ -114,23 +126,25 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): ] @classmethod - def get_type_limits(cls, category, tp): - limits = Category.platform_limits().get(category, {}) + def get_constraints(cls, category, tp): + constraints = PlatformMixin.platform_constraints() + category_constraints = Category.platform_constraints().get(category) or {} + constraints.update(category_constraints) + types_cls = dict(cls.category_types()).get(category) if not types_cls: - return {} - types_limits = types_cls.platform_limits() or {} - type_limits = types_limits.get(tp, {}) - limits.update(type_limits) + return constraints + type_constraints = types_cls.platform_constraints().get(tp) or {} + constraints.update(type_constraints) - _protocols_limit = limits.get('protocols_limit', []) + _protocols = constraints.pop('_protocols', []) default_ports = Protocol.default_ports() - protocols_limit = [] - for p in _protocols_limit: + protocols = [] + for p in _protocols: port = default_ports.get(p, 0) - protocols_limit.append(f'{p}/{port}') - limits['protocols_limit'] = protocols_limit - return limits + protocols.append({'name': p, 'port': port}) + constraints['protocols'] = protocols + return constraints @classmethod def category_types(cls): diff --git a/apps/assets/migrations/0100_auto_20220430_2126.py b/apps/assets/migrations/0100_auto_20220430_2126.py index bb2fc3b82..9651a9e43 100644 --- a/apps/assets/migrations/0100_auto_20220430_2126.py +++ b/apps/assets/migrations/0100_auto_20220430_2126.py @@ -11,16 +11,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='platform', - name='admin_user_default', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.systemuser', verbose_name='Admin user default'), - ), - migrations.AddField( - model_name='platform', - name='admin_user_enabled', - field=models.BooleanField(default=True, verbose_name='Admin user enabled'), - ), migrations.AddField( model_name='platform', name='domain_default', @@ -34,13 +24,63 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='protocols_default', - field=models.CharField(blank=True, default='', max_length=128, verbose_name='Protocols default'), + field=models.JSONField(blank=True, default=list, max_length=128, verbose_name='Protocols default'), ), migrations.AddField( model_name='platform', name='protocols_enabled', field=models.BooleanField(default=True, verbose_name='Protocols enabled'), ), + migrations.AddField( + model_name='platform', + name='change_password_enabled', + field=models.BooleanField(default=False, verbose_name='Change password enabled'), + ), + migrations.AddField( + model_name='platform', + name='change_password_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method'), + ), + migrations.AddField( + model_name='platform', + name='create_account_enabled', + field=models.BooleanField(default=False, verbose_name='Create account enabled'), + ), + migrations.AddField( + model_name='platform', + name='create_account_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method'), + ), + migrations.AddField( + model_name='platform', + name='ping_enabled', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='platform', + name='ping_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Ping method'), + ), + migrations.AddField( + model_name='platform', + name='su_enabled', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='platform', + name='su_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='SU method'), + ), + migrations.AddField( + model_name='platform', + name='verify_account_enabled', + field=models.BooleanField(default=False, verbose_name='Verify account enabled'), + ), + migrations.AddField( + model_name='platform', + name='verify_account_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method'), + ), migrations.AlterField( model_name='asset', name='category', diff --git a/apps/assets/migrations/0105_auto_20220810_1449.py b/apps/assets/migrations/0105_auto_20220810_1449.py new file mode 100644 index 000000000..18b933338 --- /dev/null +++ b/apps/assets/migrations/0105_auto_20220810_1449.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.14 on 2022-08-10 06:49 + +import assets.models.platform +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0104_auto_20220803_1859'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('cloud', 'Clouding'), ('web', 'Web')], max_length=16, verbose_name='Category'), + ), + migrations.AlterField( + model_name='asset', + name='platform', + field=models.ForeignKey(default=assets.models.platform.Platform.default, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.platform', verbose_name='Platform'), + ), + migrations.AlterField( + model_name='asset', + name='type', + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('general', 'General'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), + ), + migrations.AlterField( + model_name='platform', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('cloud', 'Clouding'), ('web', 'Web')], default='host', max_length=16, verbose_name='Category'), + ), + migrations.AlterField( + model_name='platform', + name='type', + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('general', 'General'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), + ), + ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index d50afd27e..36e1b0b51 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -9,6 +9,10 @@ __all__ = ['Platform'] class Platform(models.Model): + """ + 对资产提供 约束和默认值 + 对资产进行抽象 + """ CHARSET_CHOICES = ( ('utf8', 'UTF-8'), ('gbk', 'GBK'), @@ -26,27 +30,25 @@ class Platform(models.Model): verbose_name=_("Domain default") ) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) - protocols_default = models.CharField( - max_length=128, default='', blank=True, verbose_name=_("Protocols default") + protocols_default = models.JSONField( + max_length=128, default=list, blank=True, verbose_name=_("Protocols default") ) - admin_user_enabled = models.BooleanField(default=True, verbose_name=_("Admin user enabled")) - admin_user_default = models.ForeignKey( - 'assets.SystemUser', null=True, on_delete=models.SET_NULL, - verbose_name=_("Admin user default") - ) - - @classmethod - def get_type_meta(cls, category, tp): - meta = Category.platform_meta().get(category, {}) - types = dict(AllTypes.category_types()).get(category) - types_meta = types.platform_meta() or {} - type_meta = types_meta.get(tp, {}) - meta.update(type_meta) - return meta + # Accounts + # 这应该和账号有关 + su_enabled = models.BooleanField(default=False) + su_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) + ping_enabled = models.BooleanField(default=False) + ping_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) + verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) + verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) + create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) + change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) + change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) @property - def type_limits(self): - return AllTypes.get_type_limits(self.category, self.type) + def type_constraints(self): + return AllTypes.get_constraints(self.category, self.type) @classmethod def default(cls): @@ -55,12 +57,6 @@ class Platform(models.Model): ) return linux.id - def is_windows(self): - return self.type.lower() in ('windows',) - - def is_unixlike(self): - return self.type.lower() in ("linux", "unix", "macos", "bsd") - def __str__(self): return self.name diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index b7277741d..999821abc 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -8,19 +8,15 @@ from .mixin import CategoryDisplayMixin __all__ = ['PlatformSerializer'] +class PlatformProtocolsSerializer(serializers.Serializer): + name = serializers.CharField(max_length=255, required=True) + port = serializers.IntegerField(max_value=65535, min_value=1, required=True) + + class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - protocols_default = serializers.ListField(label=_('Protocols'), required=False) - type_limits = serializers.ReadOnlyField(required=False, read_only=True) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # TODO 修复 drf SlugField RegexValidator bug,之后记得删除 - validators = self.fields['name'].validators - if isinstance(validators[-1], RegexValidator): - validators.pop() - # self.set_platform_meta() + protocols_default = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) + type_constraints = serializers.ReadOnlyField(required=False, read_only=True) class Meta: model = Platform @@ -29,12 +25,16 @@ class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): 'meta', 'comment', 'charset', 'category', 'category_display', 'type', 'type_display', - 'type_limits', + 'su_enabled', 'su_method', + 'ping_enabled', 'ping_method', + 'verify_account_enabled', 'verify_account_method', + 'create_account_enabled', 'create_account_method', + 'change_password_enabled', 'change_password_method', + 'type_constraints', ] fields_fk = [ 'domain_enabled', 'domain_default', 'protocols_enabled', 'protocols_default', - 'admin_user_enabled', 'admin_user_default', ] fields = fields_small + fields_fk read_only_fields = [ From 497204d7779b58443f7135853fe04014812f0c17 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Aug 2022 14:05:45 +0800 Subject: [PATCH 052/488] perf: account remove protocol --- .../migrations/0106_auto_20220811_1358.py | 35 +++++++++++++++++++ apps/assets/models/account.py | 9 ++--- apps/assets/serializers/account.py | 2 +- 3 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 apps/assets/migrations/0106_auto_20220811_1358.py diff --git a/apps/assets/migrations/0106_auto_20220811_1358.py b/apps/assets/migrations/0106_auto_20220811_1358.py new file mode 100644 index 000000000..640dd8adb --- /dev/null +++ b/apps/assets/migrations/0106_auto_20220811_1358.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.14 on 2022-08-11 05:58 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0105_auto_20220810_1449'), + ] + + operations = [ + migrations.CreateModel( + name='AccountTemplate', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ], + options={ + 'abstract': False, + }, + ), + migrations.RemoveField( + model_name='account', + name='protocol', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='protocol', + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 31e43fddc..654ce2cf5 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,22 +3,17 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from common.db.models import JMSBaseModel -from .protocol import ProtocolMixin from .base import BaseUser, AbsConnectivity __all__ = ['Account'] -class Account(BaseUser, AbsConnectivity, ProtocolMixin): +class Account(BaseUser, AbsConnectivity): class Type(models.TextChoices): common = 'common', _('Common user') admin = 'admin', _('Admin user') - protocol = models.CharField( - max_length=16, choices=ProtocolMixin.Protocol.choices, - default='ssh', verbose_name=_('Protocol') - ) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_("Type")) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) @@ -35,7 +30,7 @@ class Account(BaseUser, AbsConnectivity, ProtocolMixin): ] def __str__(self): - return '{}://{}@{}'.format(self.protocol, self.username, self.asset.hostname) + return '{}@{}'.format(self.username, self.asset.hostname) class AccountTemplate(JMSBaseModel): diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index 33632d0c9..2d6de8bc8 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -25,7 +25,7 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): model = Account fields_mini = [ 'id', 'type', 'username', 'ip', 'hostname', - 'platform', 'protocol', 'version' + 'platform', 'version' ] fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment'] From d402ba5d921382939a56ed6d4ee2179da27c3f89 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Aug 2022 15:45:03 +0800 Subject: [PATCH 053/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/login_asset_acl.py | 2 +- apps/assets/api/accounts.py | 6 +- apps/assets/api/asset/common.py | 8 +- apps/assets/api/mixin.py | 4 +- apps/assets/api/node.py | 2 +- apps/assets/migrations/0092_add_host.py | 1 + .../migrations/0107_auto_20220811_1449.py | 37 ++++++++++ .../migrations/0108_auto_20220811_1511.py | 41 ++++++++++ apps/assets/models/__init__.py | 1 + apps/assets/models/account.py | 2 +- apps/assets/models/asset/cloud.py | 2 +- apps/assets/models/asset/common.py | 74 +++++-------------- apps/assets/models/asset/database.py | 2 +- apps/assets/models/asset/host.py | 3 +- apps/assets/models/gathered_user.py | 4 +- apps/assets/serializers/account.py | 8 +- apps/assets/serializers/asset/category.py | 4 +- apps/assets/serializers/asset/common.py | 16 +--- apps/assets/serializers/gathered_user.py | 4 +- apps/assets/tasks/account_connectivity.py | 2 +- apps/assets/tasks/asset_connectivity.py | 4 +- .../tasks/gather_asset_hardware_info.py | 6 +- apps/assets/tasks/gather_asset_users.py | 2 +- apps/authentication/api/connection_token.py | 4 +- .../serializers/connection_token.py | 2 +- apps/jumpserver/conf.py | 2 +- apps/ops/ansible/inventory.py | 8 +- apps/ops/ansible/test_inventory.py | 4 +- apps/ops/ansible/test_runner.py | 4 +- apps/ops/inventory.py | 2 +- apps/ops/models/command.py | 4 +- .../0013_delete_organizationmember.py | 16 ++++ apps/orgs/mixins/models.py | 11 ++- apps/orgs/models.py | 27 ------- .../api/asset/asset_permission_relation.py | 2 +- apps/perms/api/asset/user_group_permission.py | 8 +- .../user_permission_assets/mixin.py | 10 +-- apps/perms/filters.py | 4 +- apps/perms/models/asset_permission.py | 2 +- .../serializers/asset/permission_relation.py | 2 +- .../serializers/asset/user_permission.py | 2 +- apps/perms/utils/asset/user_permission.py | 2 +- apps/settings/serializers/terminal.py | 2 +- utils/sync_node.py | 2 +- 44 files changed, 194 insertions(+), 161 deletions(-) create mode 100644 apps/assets/migrations/0107_auto_20220811_1449.py create mode 100644 apps/assets/migrations/0108_auto_20220811_1511.py create mode 100644 apps/orgs/migrations/0013_delete_organizationmember.py diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 9cf7989f9..5727e521b 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -61,7 +61,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): @classmethod def filter_asset(cls, asset, queryset): queryset = queryset.filter( - Q(assets__hostname_group__contains=asset.hostname) | + Q(assets__hostname_group__contains=asset.name) | Q(assets__hostname_group__contains='*') ) ids = [q.id for q in queryset if contains_ip(asset.ip, q.assets.get('ip_group', []))] diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index be6833e7b..459b1013c 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -21,7 +21,7 @@ __all__ = ['AccountFilterSet', 'AccountViewSet', 'AccountSecretsViewSet', 'Accou class AccountFilterSet(BaseFilterSet): username = filters.CharFilter(method='do_nothing') ip = filters.CharFilter(field_name='ip', lookup_expr='exact') - hostname = filters.CharFilter(field_name='hostname', lookup_expr='exact') + hostname = filters.CharFilter(field_name='name', lookup_expr='exact') node = filters.CharFilter(method='do_nothing') @property @@ -58,8 +58,8 @@ class AccountFilterSet(BaseFilterSet): class AccountViewSet(OrgBulkModelViewSet): model = Account - filterset_fields = ("username", "asset", 'ip', 'hostname') - search_fields = ('username', 'ip', 'hostname') + filterset_fields = ("username", "asset", 'ip', 'name') + search_fields = ('username', 'ip', 'name') filterset_class = AccountFilterSet serializer_classes = { 'default': serializers.AccountSerializer, diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index 9b7b39a25..6d266e1bd 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -27,15 +27,15 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) """ model = Asset filterset_fields = { - 'hostname': ['exact'], + 'name': ['exact'], 'ip': ['exact'], 'system_users__id': ['exact'], 'is_active': ['exact'], 'protocols': ['exact', 'icontains'] } - search_fields = ("hostname", "ip") - ordering_fields = ("hostname", "ip", "port") - ordering = ('hostname', ) + search_fields = ("name", "ip") + ordering_fields = ("name", "ip", "port") + ordering = ('name', ) serializer_classes = ( ('default', serializers.AssetSerializer), ('suggestion', serializers.MiniAssetSerializer), diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 7f485bf29..d814ce11f 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -56,7 +56,7 @@ class SerializeToTreeNodeMixin: data = [ { 'id': str(asset.id), - 'name': asset.hostname, + 'name': asset.name, 'title': asset.ip, 'pId': get_pid(asset), 'isParent': False, @@ -67,7 +67,7 @@ class SerializeToTreeNodeMixin: 'type': 'asset', 'data': { 'id': asset.id, - 'hostname': asset.hostname, + 'name': asset.name, 'ip': asset.ip, 'protocols': asset.protocols_as_list, 'platform': asset.platform_base, diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 59a1da793..922fd8766 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -192,7 +192,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): if not self.instance or not include_assets: return [] assets = self.instance.get_assets().only( - "id", "hostname", "ip", "os", "platform_id", + "id", "name", "ip", "os", "platform_id", "org_id", "protocols", "is_active", ).prefetch_related('platform') return self.serialize_assets(assets, self.instance.key) diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index 9e2936e3f..185842c42 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -40,6 +40,7 @@ class Migration(migrations.Migration): ('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')), ('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')), ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.host', verbose_name='Host')), + ('number', models.CharField(blank=True, max_length=128, null=True, verbose_name='Asset number')), ], options={ 'verbose_name': 'DeviceInfo', diff --git a/apps/assets/migrations/0107_auto_20220811_1449.py b/apps/assets/migrations/0107_auto_20220811_1449.py new file mode 100644 index 000000000..f5aacbecf --- /dev/null +++ b/apps/assets/migrations/0107_auto_20220811_1449.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.14 on 2022-08-11 06:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0106_auto_20220811_1358'), + ] + + operations = [ + migrations.RemoveField( + model_name='asset', + name='_protocols', + ), + migrations.RemoveField( + model_name='asset', + name='admin_user', + ), + migrations.RemoveField( + model_name='asset', + name='category', + ), + migrations.RemoveField( + model_name='asset', + name='number', + ), + migrations.RemoveField( + model_name='asset', + name='public_ip', + ), + migrations.RemoveField( + model_name='asset', + name='type', + ), + ] diff --git a/apps/assets/migrations/0108_auto_20220811_1511.py b/apps/assets/migrations/0108_auto_20220811_1511.py new file mode 100644 index 000000000..b4bca4599 --- /dev/null +++ b/apps/assets/migrations/0108_auto_20220811_1511.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.14 on 2022-08-11 07:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_auto_20220811_1449'), + ] + + operations = [ + migrations.AlterModelOptions( + name='asset', + options={'ordering': ['name'], 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), ('test_assetconnectivity', 'Can test asset connectivity'), ('push_assetsystemuser', 'Can push system user to asset'), ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'), ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'}, + ), + migrations.RenameField( + model_name='asset', + old_name='hostname', + new_name='name', + ), + migrations.AddField( + model_name='asset', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='asset', + name='updated_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='asset', + name='created_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), + ), + migrations.AlterUniqueTogether( + name='asset', + unique_together={('org_id', 'name')}, + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index a785c5410..f257d6e8d 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -15,3 +15,4 @@ from .backup import * from ._authbook import * from .protocol import * from .cmd_filter import * + diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 654ce2cf5..56b5758cf 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -30,7 +30,7 @@ class Account(BaseUser, AbsConnectivity): ] def __str__(self): - return '{}@{}'.format(self.username, self.asset.hostname) + return '{}@{}'.format(self.username, self.asset.name) class AccountTemplate(JMSBaseModel): diff --git a/apps/assets/models/asset/cloud.py b/apps/assets/models/asset/cloud.py index b8eed03b0..8810f199c 100644 --- a/apps/assets/models/asset/cloud.py +++ b/apps/assets/models/asset/cloud.py @@ -8,4 +8,4 @@ class Cloud(Asset): cluster = models.CharField(max_length=4096, verbose_name=_("Cluster")) def __str__(self): - return self.hostname + return self.name diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index e2544f2b8..4a412429c 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -2,16 +2,14 @@ # -*- coding: utf-8 -*- # -import uuid import logging +import uuid from functools import reduce from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils import lazyproperty -from orgs.mixins.models import OrgModelMixin, OrgManager -from assets.const import Category, AllTypes +from orgs.mixins.models import OrgManager, JMSOrgBaseModel from ..platform import Platform from ..base import AbsConnectivity @@ -67,14 +65,10 @@ class NodesRelationMixin: return nodes -class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): - Category = Category +class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) + name = models.CharField(max_length=128, verbose_name=_('Hostname')) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) - category = models.CharField(max_length=16, choices=Category.choices, verbose_name=_("Category")) - type = models.CharField(max_length=128, choices=AllTypes.choices, verbose_name=_("Type")) - _protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols")) protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocols"), blank=True) platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') @@ -84,16 +78,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) - # Auth - admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, - verbose_name=_("Admin user"), related_name='admin_assets') - # Some information - public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP')) - number = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Asset number')) - labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) - created_by = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) objects = AssetManager.from_queryset(AssetQuerySet)() @@ -104,12 +89,6 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): def get_target_ip(self): return self.ip - @property - def admin_user_display(self): - if not self.admin_user: - return '' - return str(self.admin_user) - @property def is_valid(self): warning = '' @@ -119,17 +98,6 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): return False, warning return True, warning - @lazyproperty - def platform_base(self): - return self.platform.type - - @lazyproperty - def admin_user_username(self): - """求可连接性时,直接用用户名去取,避免再查一次admin user - serializer 中直接通过annotate方式返回了这个 - """ - return self.admin_user.username - def nodes_display(self): names = [] for n in self.nodes.all(): @@ -142,12 +110,20 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): names.append(n.name + ':' + n.value) return names + @property + def type(self): + return self.platform.type + + @property + def category(self): + return self.platform.category + def as_node(self): from assets.models import Node fake_node = Node() fake_node.id = self.id fake_node.key = self.id - fake_node.value = self.hostname + fake_node.value = self.name fake_node.asset = self fake_node.is_node = False return fake_node @@ -155,13 +131,14 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): def as_tree_node(self, parent_node): from common.tree import TreeNode icon_skin = 'file' - if self.platform_base.lower() == 'windows': + platform_type = self.platform.type.lower() + if platform_type == 'windows': icon_skin = 'windows' - elif self.platform_base.lower() == 'linux': + elif platform_type == 'linux': icon_skin = 'linux' data = { 'id': str(self.id), - 'name': self.hostname, + 'name': self.name, 'title': self.ip, 'pId': parent_node.key, 'isParent': False, @@ -171,10 +148,9 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): 'type': 'asset', 'data': { 'id': self.id, - 'hostname': self.hostname, + 'name': self.name, 'ip': self.ip, 'protocols': self.protocols, - 'platform': self.platform_base, } } } @@ -188,20 +164,10 @@ class Asset(AbsConnectivity, NodesRelationMixin, OrgModelMixin): system_users = SystemUser.objects.filter(id__in=system_user_ids) return system_users - def save(self, *args, **kwargs): - self.type = self.platform.type - self.category = self.platform.category - return super().save(*args, **kwargs) - - # TODO 暂时为了接口文档添加 - @property - def os(self): - return - class Meta: - unique_together = [('org_id', 'hostname')] + unique_together = [('org_id', 'name')] verbose_name = _("Asset") - ordering = ["hostname", ] + ordering = ["name", ] permissions = [ ('refresh_assethardwareinfo', _('Can refresh asset hardware info')), ('test_assetconnectivity', _('Can test asset connectivity')), diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index 4947b79ca..cb97c95cd 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -8,7 +8,7 @@ class Database(Asset): db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) def __str__(self): - return '{}({}://{}/{})'.format(self.hostname, self.type, self.ip, self.db_name) + return '{}({}://{}/{})'.format(self.name, self.type, self.ip, self.db_name) class Meta: verbose_name = _("Database") diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index ac61ea052..12f35ea5a 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -31,6 +31,7 @@ class DeviceInfo(CommonModelMixin): os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) + number = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Asset number')) @property def cpu_info(self): @@ -52,7 +53,7 @@ class DeviceInfo(CommonModelMixin): return '' def __str__(self): - return '{} of {}'.format(self.hardware_info, self.host.hostname) + return '{} of {}'.format(self.hardware_info, self.host.name) class Meta: verbose_name = _("DeviceInfo") diff --git a/apps/assets/models/gathered_user.py b/apps/assets/models/gathered_user.py index d00021c56..b00ea0843 100644 --- a/apps/assets/models/gathered_user.py +++ b/apps/assets/models/gathered_user.py @@ -21,7 +21,7 @@ class GatheredUser(OrgModelMixin): @property def hostname(self): - return self.asset.hostname + return self.asset.name @property def ip(self): @@ -32,7 +32,7 @@ class GatheredUser(OrgModelMixin): ordering = ['asset'] def __str__(self): - return '{}: {}'.format(self.asset.hostname, self.username) + return '{}: {}'.format(self.asset.name, self.username) diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index 2d6de8bc8..3de132804 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -12,7 +12,7 @@ from common.drf.serializers import SecretReadableMixin class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ip = serializers.ReadOnlyField(label=_("IP")) - hostname = serializers.ReadOnlyField(label=_("Hostname")) + asset = serializers.ReadOnlyField(label=_("Asset")) platform = serializers.ReadOnlyField(label=_("Platform")) date_created = serializers.DateTimeField( label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True @@ -24,7 +24,7 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class Meta: model = Account fields_mini = [ - 'id', 'type', 'username', 'ip', 'hostname', + 'id', 'type', 'username', 'ip', 'name', 'platform', 'version' ] fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] @@ -59,14 +59,14 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): """ Perform necessary eager loading of data. """ queryset = queryset.prefetch_related('asset')\ .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) + .annotate(asset=F('asset__name')) return queryset class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): fields_backup = [ - 'hostname', 'ip', 'platform', 'protocols', 'username', 'password', + 'name', 'ip', 'platform', 'protocols', 'username', 'password', 'private_key', 'public_key', 'date_created', 'date_updated', 'version' ] extra_kwargs = { diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 32d954652..78c3180cd 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -14,7 +14,7 @@ class DeviceSerializer(serializers.ModelSerializer): fields = [ 'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', - 'os', 'os_version', 'os_arch', 'hostname_raw', + 'os', 'os_version', 'os_arch', 'hostname_raw', 'number', 'cpu_info', 'hardware_info', 'date_updated' ] @@ -31,7 +31,7 @@ class DatabaseSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Database fields_mini = [ - 'id', 'hostname', 'ip', 'port', 'db_name', + 'id', 'name', 'ip', 'port', 'db_name', ] fields_small = fields_mini + [ 'is_active', 'comment', diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index e25946dc0..91f1871e9 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -57,8 +57,6 @@ class AssetNodesSerializer(serializers.ModelSerializer): class AssetSerializer(JMSWritableNestedModelSerializer): - # category = ChoiceDisplayField(choices=Category.choices, required=False) - # type = ChoiceDisplayField(choices=AllTypes.choices, required=False) domain = AssetDomainSerializer(required=False) platform = AssetPlatformSerializer(required=False) labels = AssetLabelSerializer(many=True, required=False) @@ -72,10 +70,10 @@ class AssetSerializer(JMSWritableNestedModelSerializer): class Meta: model = Asset fields_mini = [ - 'id', 'hostname', 'ip', + 'id', 'name', 'ip', ] fields_small = fields_mini + [ - 'is_active', 'number', 'comment', + 'is_active', 'comment', ] fields_fk = [ 'domain', 'platform', 'platform', @@ -90,7 +88,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { - 'hostname': {'label': _("Name")}, + 'name': {'label': _("Name")}, 'ip': {'label': _('IP/Host')}, 'protocol': {'write_only': True}, 'port': {'write_only': True}, @@ -102,12 +100,6 @@ class AssetSerializer(JMSWritableNestedModelSerializer): self.accounts_data = data.pop('accounts', []) super().__init__(*args, **kwargs) - def validate_type(self, value): - return value - - def validate_category(self, value): - return value - @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ @@ -164,7 +156,7 @@ class AssetSimpleSerializer(serializers.ModelSerializer): class Meta: model = Asset fields = [ - 'id', 'hostname', 'ip', 'port', + 'id', 'name', 'ip', 'port', 'connectivity', 'date_verified' ] diff --git a/apps/assets/serializers/gathered_user.py b/apps/assets/serializers/gathered_user.py index 86e7dcfae..572b9f80b 100644 --- a/apps/assets/serializers/gathered_user.py +++ b/apps/assets/serializers/gathered_user.py @@ -16,10 +16,10 @@ class GatheredUserSerializer(OrgResourceModelSerializerMixin): 'present', 'date_last_login', 'date_created', 'date_updated' ] - fields_fk = ['asset', 'hostname', 'ip'] + fields_fk = ['asset', 'name', 'ip'] fields = fields_small + fields_fk read_only_fields = fields extra_kwargs = { - 'hostname': {'label': _("Hostname")}, + 'name': {'label': _("Hostname")}, 'ip': {'label': 'IP'}, } diff --git a/apps/assets/tasks/account_connectivity.py b/apps/assets/tasks/account_connectivity.py index 197979055..cec4ed3af 100644 --- a/apps/assets/tasks/account_connectivity.py +++ b/apps/assets/tasks/account_connectivity.py @@ -28,7 +28,7 @@ def get_test_account_connectivity_tasks(asset): else: msg = _( "The asset {} system platform {} does not " - "support run Ansible tasks".format(asset.hostname, asset.platform) + "support run Ansible tasks".format(asset.name, asset.platform) ) logger.info(msg) tasks = [] diff --git a/apps/assets/tasks/asset_connectivity.py b/apps/assets/tasks/asset_connectivity.py index 491792095..8c76d8e2b 100644 --- a/apps/assets/tasks/asset_connectivity.py +++ b/apps/assets/tasks/asset_connectivity.py @@ -26,7 +26,7 @@ def set_assets_accounts_connectivity(assets, results_summary): asset_hostnames_ok = results_summary.get('contacted', {}).keys() for asset in assets: - if asset.hostname in asset_hostnames_ok: + if asset.name in asset_hostnames_ok: asset_ids_ok.add(asset.id) else: asset_ids_failed.add(asset.id) @@ -100,7 +100,7 @@ def test_asset_connectivity_manual(asset): @shared_task(queue="ansible") def test_assets_connectivity_manual(assets): - task_name = gettext_noop("Test assets connectivity: ") + str([asset.hostname for asset in assets]) + task_name = gettext_noop("Test assets connectivity: ") + str([asset.name for asset in assets]) summary = test_asset_connectivity_util(assets, task_name=task_name) if summary.get('dark'): diff --git a/apps/assets/tasks/gather_asset_hardware_info.py b/apps/assets/tasks/gather_asset_hardware_info.py index ae8107cb2..7678f1115 100644 --- a/apps/assets/tasks/gather_asset_hardware_info.py +++ b/apps/assets/tasks/gather_asset_hardware_info.py @@ -39,7 +39,7 @@ def set_assets_hardware_info(assets, result, **kwargs): success_result = result_raw.get('ok', {}) for asset in assets: - hostname = asset.hostname + hostname = asset.name info = success_result.get(hostname, {}) info = info.get('setup', {}).get('ansible_facts', {}) if not info: @@ -111,13 +111,13 @@ def update_assets_hardware_info_util(assets, task_name=None): @shared_task(queue="ansible") def update_asset_hardware_info_manual(asset): - task_name = gettext_noop("Update asset hardware info: ") + str(asset.hostname) + task_name = gettext_noop("Update asset hardware info: ") + str(asset.name) update_assets_hardware_info_util([asset], task_name=task_name) @shared_task(queue="ansible") def update_assets_hardware_info_manual(assets): - task_name = gettext_noop("Update assets hardware info: ") + str([asset.hostname for asset in assets]) + task_name = gettext_noop("Update assets hardware info: ") + str([asset.name for asset in assets]) update_assets_hardware_info_util(assets, task_name=task_name) diff --git a/apps/assets/tasks/gather_asset_users.py b/apps/assets/tasks/gather_asset_users.py index abc69997a..f5a46c099 100644 --- a/apps/assets/tasks/gather_asset_users.py +++ b/apps/assets/tasks/gather_asset_users.py @@ -70,7 +70,7 @@ def parse_windows_result_to_users(result): def add_asset_users(assets, results): - assets_map = {a.hostname: a for a in assets} + assets_map = {a.name: a for a in assets} parser_map = { 'linux': parse_linux_result_to_users, 'windows': parse_windows_result_to_users diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index f0a7e0a63..7b7a17aed 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -160,7 +160,7 @@ class ConnectionTokenMixin: rdp_options['audiomode:i'] = self.parse_env_bool('JUMPSERVER_DISABLE_AUDIO', 'false', '2', '0') if token.asset: - name = token.asset.hostname + name = token.asset.name elif token.application and token.application.category_remote_app: app = '||jmservisor' name = token.application.name @@ -182,7 +182,7 @@ class ConnectionTokenMixin: def get_ssh_token(self, token: ConnectionToken): if token.asset: - name = token.asset.hostname + name = token.asset.name elif token.application: name = token.application.name else: diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index a05d8c0e0..3ae38ff18 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -124,7 +124,7 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer): class Meta: model = Asset - fields = ['id', 'hostname', 'ip', 'protocols', 'org_id'] + fields = ['id', 'name', 'ip', 'protocols', 'org_id'] class ConnectionTokenSystemUserSerializer(serializers.ModelSerializer): diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 551076192..0eaaffad9 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -315,7 +315,7 @@ class Config(dict): 'TERMINAL_PASSWORD_AUTH': True, 'TERMINAL_PUBLIC_KEY_AUTH': True, 'TERMINAL_HEARTBEAT_INTERVAL': 20, - 'TERMINAL_ASSET_LIST_SORT_BY': 'hostname', + 'TERMINAL_ASSET_LIST_SORT_BY': 'name', 'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto', 'TERMINAL_SESSION_KEEP_DURATION': 200, 'TERMINAL_HOST_KEY': '', diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index df20b06d6..01a806172 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -15,7 +15,7 @@ class BaseHost(Host): """ 初始化 :param host_data: { - "hostname": "", + "name": "", "ip": "", "port": "", # behind is not must be required @@ -32,7 +32,7 @@ class BaseHost(Host): } """ self.host_data = host_data - hostname = host_data.get('hostname') or host_data.get('ip') + hostname = host_data.get('name') or host_data.get('ip') port = host_data.get('port') or 22 super().__init__(hostname, port) self.__set_required_variables() @@ -82,7 +82,7 @@ class BaseInventory(InventoryManager): """ 用于生成动态构建Ansible Inventory. super().__init__ 会自动调用 host_list: [{ - "hostname": "", + "name": "", "ip": "", "port": "", "username": "", @@ -136,7 +136,7 @@ class BaseInventory(InventoryManager): ungrouped = self.get_or_create_group('ungrouped') for host_data in self.host_list: host = self.host_manager_class(host_data=host_data) - self.hosts[host_data['hostname']] = host + self.hosts[host_data['name']] = host groups_data = host_data.get('groups') if groups_data: for group_name in groups_data: diff --git a/apps/ops/ansible/test_inventory.py b/apps/ops/ansible/test_inventory.py index 00c7fa459..a03faeaf5 100644 --- a/apps/ops/ansible/test_inventory.py +++ b/apps/ops/ansible/test_inventory.py @@ -12,7 +12,7 @@ from ops.ansible.inventory import BaseInventory class TestJMSInventory(unittest.TestCase): def setUp(self): host_list = [{ - "hostname": "testserver1", + "name": "testserver1", "ip": "102.1.1.1", "port": 22, "username": "root", @@ -26,7 +26,7 @@ class TestJMSInventory(unittest.TestCase): "groups": ["group1", "group2"], "vars": {"sexy": "yes"}, }, { - "hostname": "testserver2", + "name": "testserver2", "ip": "8.8.8.8", "port": 2222, "username": "root", diff --git a/apps/ops/ansible/test_runner.py b/apps/ops/ansible/test_runner.py index e38168a6c..6f56985a7 100644 --- a/apps/ops/ansible/test_runner.py +++ b/apps/ops/ansible/test_runner.py @@ -14,7 +14,7 @@ class TestAdHocRunner(unittest.TestCase): def setUp(self): host_data = [ { - "hostname": "testserver", + "name": "testserver", "ip": "192.168.244.185", "port": 22, "username": "root", @@ -38,7 +38,7 @@ class TestCommandRunner(unittest.TestCase): def setUp(self): host_data = [ { - "hostname": "testserver", + "name": "testserver", "ip": "192.168.244.168", "port": 22, "username": "root", diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index b19ce8130..c2b886413 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -20,7 +20,7 @@ class JMSBaseInventory(BaseInventory): def convert_to_ansible(self, asset, run_as_admin=False): info = { 'id': asset.id, - 'hostname': asset.hostname, + 'name': asset.name, 'ip': asset.ip, 'port': asset.ssh_port, 'vars': dict(), diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index b5a1f2292..8df5005c4 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -57,7 +57,7 @@ class CommandExecution(OrgModelMixin): @lazyproperty def hosts_display(self): - return ','.join(self.hosts.all().values_list('hostname', flat=True)) + return ','.join(self.hosts.all().values_list('name', flat=True)) @property def result(self): @@ -77,7 +77,7 @@ class CommandExecution(OrgModelMixin): return True def get_hosts_names(self): - return ','.join(self.hosts.all().values_list('hostname', flat=True)) + return ','.join(self.hosts.all().values_list('name', flat=True)) def cmd_filter_rules(self, asset_id=None): from assets.models import CommandFilterRule diff --git a/apps/orgs/migrations/0013_delete_organizationmember.py b/apps/orgs/migrations/0013_delete_organizationmember.py new file mode 100644 index 000000000..78d8ad655 --- /dev/null +++ b/apps/orgs/migrations/0013_delete_organizationmember.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.14 on 2022-08-11 07:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('orgs', '0012_auto_20220118_1054'), + ] + + operations = [ + migrations.DeleteModel( + name='OrganizationMember', + ), + ] diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index 3406271b6..48ed09af7 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ValidationError from common.utils import get_logger +from common.db.models import JMSBaseModel from ..utils import ( set_current_org, get_current_org, current_org, filter_org_queryset ) @@ -14,7 +15,7 @@ from ..models import Organization logger = get_logger(__file__) __all__ = [ - 'OrgManager', 'OrgModelMixin', 'Organization' + 'OrgManager', 'OrgModelMixin', 'JMSOrgBaseModel' ] @@ -40,7 +41,6 @@ class OrgManager(models.Manager): set_current_org(org) return self - def bulk_create(self, objs, batch_size=None, ignore_conflicts=False): org = get_current_org() for obj in objs: @@ -87,7 +87,7 @@ class OrgModelMixin(models.Model): name = getattr(self, attr) elif hasattr(self, 'name'): name = self.name - elif hasattr(self, 'hostname'): + elif hasattr(self, 'name'): name = self.hostname return name + self.sep + self.org_name @@ -113,3 +113,8 @@ class OrgModelMixin(models.Model): class Meta: abstract = True + + +class JMSOrgBaseModel(JMSBaseModel, OrgModelMixin): + class Meta: + abstract = True diff --git a/apps/orgs/models.py b/apps/orgs/models.py index def83f509..430a75a5b 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -199,30 +199,3 @@ class Organization(OrgRoleMixin, models.Model): def delete(self, *args, **kwargs): self.delete_related_models() return super().delete(*args, **kwargs) - - -class OrganizationMember(models.Model): - """ - 注意:直接调用该 `Model.delete` `Model.objects.delete` 不会触发清理该用户的信号 - """ - - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - org = models.ForeignKey( - Organization, related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('Organization') - ) - user = models.ForeignKey( - 'users.User', related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('User') - ) - role = models.CharField(max_length=16, default='User', verbose_name=_("Role")) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) - date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) - created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - - # objects = OrgMemberManager() - - class Meta: - unique_together = [('org', 'user', 'role')] - db_table = 'orgs_organization_members' - - def __str__(self): - return '{} | {}'.format(self.user, self.org) diff --git a/apps/perms/api/asset/asset_permission_relation.py b/apps/perms/api/asset/asset_permission_relation.py index 16469695b..ecf0d731b 100644 --- a/apps/perms/api/asset/asset_permission_relation.py +++ b/apps/perms/api/asset/asset_permission_relation.py @@ -91,7 +91,7 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): class AssetPermissionAllAssetListApi(generics.ListAPIView): serializer_class = serializers.AssetPermissionAllAssetSerializer - filterset_fields = ("hostname", "ip") + filterset_fields = ("name", "ip") search_fields = filterset_fields def get_queryset(self): diff --git a/apps/perms/api/asset/user_group_permission.py b/apps/perms/api/asset/user_group_permission.py index 9b6499bb3..5c9e0d47e 100644 --- a/apps/perms/api/asset/user_group_permission.py +++ b/apps/perms/api/asset/user_group_permission.py @@ -33,8 +33,8 @@ class UserGroupMixin: class UserGroupGrantedAssetsApi(ListAPIView): serializer_class = serializers.AssetGrantedSerializer only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - filterset_fields = ['hostname', 'ip', 'id', 'comment'] - search_fields = ['hostname', 'ip', 'comment'] + filterset_fields = ['name', 'ip', 'id', 'comment'] + search_fields = ['name', 'ip', 'comment'] rbac_perms = { 'list': 'perms.view_usergroupassets', } @@ -70,8 +70,8 @@ class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView): serializer_class = serializers.AssetGrantedSerializer only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - filterset_fields = ['hostname', 'ip', 'id', 'comment'] - search_fields = ['hostname', 'ip', 'comment'] + filterset_fields = ['name', 'ip', 'id', 'comment'] + search_fields = ['name', 'ip', 'comment'] rbac_perms = { 'list': 'perms.view_usergroupassets', } diff --git a/apps/perms/api/asset/user_permission/user_permission_assets/mixin.py b/apps/perms/api/asset/user_permission/user_permission_assets/mixin.py index 1b6b8e00f..c2d3fa7d3 100644 --- a/apps/perms/api/asset/user_permission/user_permission_assets/mixin.py +++ b/apps/perms/api/asset/user_permission/user_permission_assets/mixin.py @@ -81,16 +81,16 @@ class UserGrantedNodeAssetsMixin: class AssetsSerializerFormatMixin: serializer_class = serializers.AssetGrantedSerializer - filterset_fields = ['hostname', 'ip', 'id', 'comment'] - search_fields = ['hostname', 'ip', 'comment'] + filterset_fields = ['name', 'ip', 'id', 'comment'] + search_fields = ['name', 'ip', 'comment'] class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): """ 将 资产 序列化成树的结构返回 """ - filterset_fields = ['hostname', 'ip', 'id', 'comment'] - search_fields = ['hostname', 'ip', 'comment'] + filterset_fields = ['name', 'ip', 'id', 'comment'] + search_fields = ['name', 'ip', 'comment'] def list(self, request: Request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) @@ -99,6 +99,6 @@ class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): # 如果用户搜索的条件不精准,会导致返回大量的无意义数据。 # 这里限制一下返回数据的最大条数 queryset = queryset[:999] - queryset = sorted(queryset, key=lambda asset: asset.hostname) + queryset = sorted(queryset, key=lambda asset: asset.name) data = self.serialize_assets(queryset, None) return Response(data=data) diff --git a/apps/perms/filters.py b/apps/perms/filters.py index da82b5090..1699ff4e7 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -117,7 +117,7 @@ class AssetPermissionFilter(PermissionBaseFilter): model = AssetPermission fields = ( 'user_id', 'username', 'system_user_id', 'system_user', 'user_group_id', - 'user_group', 'node_id', 'node', 'asset_id', 'hostname', 'ip', 'name', + 'user_group', 'node_id', 'node', 'asset_id', 'name', 'ip', 'name', 'all', 'asset_id', 'is_valid', 'is_effective', 'from_ticket' ) @@ -157,7 +157,7 @@ class AssetPermissionFilter(PermissionBaseFilter): def filter_asset(self, queryset): is_query_all = self.get_query_param('all', True) asset_id = self.get_query_param('asset_id') - hostname = self.get_query_param('hostname') + hostname = self.get_query_param('name') ip = self.get_query_param('ip') if asset_id: diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index eedca805f..6d2401885 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -185,7 +185,7 @@ class AssetPermission(OrgModelMixin): return names def assets_display(self): - names = [asset.hostname for asset in self.assets.all()] + names = [asset.name for asset in self.assets.all()] return names def nodes_display(self): diff --git a/apps/perms/serializers/asset/permission_relation.py b/apps/perms/serializers/asset/permission_relation.py index 3bd5e8b1d..983768422 100644 --- a/apps/perms/serializers/asset/permission_relation.py +++ b/apps/perms/serializers/asset/permission_relation.py @@ -83,7 +83,7 @@ class AssetPermissionAllAssetSerializer(serializers.Serializer): asset_display = serializers.SerializerMethodField() class Meta: - only_fields = ['id', 'hostname', 'ip'] + only_fields = ['id', 'name', 'ip'] @staticmethod def get_asset_display(obj): diff --git a/apps/perms/serializers/asset/user_permission.py b/apps/perms/serializers/asset/user_permission.py index 26a21d7f0..ac42b97ba 100644 --- a/apps/perms/serializers/asset/user_permission.py +++ b/apps/perms/serializers/asset/user_permission.py @@ -44,7 +44,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): class Meta: model = Asset only_fields = [ - "id", "hostname", "ip", "protocols", "os", 'domain', + "id", "name", "ip", "protocols", "os", 'domain', "platform", "comment", "org_id", "is_active" ] fields = only_fields + ['org_name'] diff --git a/apps/perms/utils/asset/user_permission.py b/apps/perms/utils/asset/user_permission.py index dd15c0b38..21fd82620 100644 --- a/apps/perms/utils/asset/user_permission.py +++ b/apps/perms/utils/asset/user_permission.py @@ -513,7 +513,7 @@ class UserGrantedAssetsQueryUtils(UserGrantedUtilsBase): assets = self._get_indirect_granted_node_assets(node.id) else: assets = Asset.objects.none() - assets = assets.order_by('hostname') + assets = assets.order_by('name') return assets def _get_indirect_granted_node_assets(self, id) -> AssetQuerySet: diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index 1b70fadf3..712a10e04 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -4,7 +4,7 @@ from rest_framework import serializers class TerminalSettingSerializer(serializers.Serializer): SORT_BY_CHOICES = ( - ('hostname', _('Hostname')), + ('name', _('Hostname')), ('ip', _('IP')) ) diff --git a/utils/sync_node.py b/utils/sync_node.py index 4563a8ab0..983f6ae7c 100644 --- a/utils/sync_node.py +++ b/utils/sync_node.py @@ -28,7 +28,7 @@ def sync_node(src, target, cut=False): asset.save() new_asset = asset else: - new_asset = get_object_or_none(Asset, hostname=asset.hostname, org_id=target.org_id) + new_asset = get_object_or_none(Asset, hostname=asset.name, org_id=target.org_id) if new_asset is None: asset.id = None asset.org_id = target.org_id From a748f5d57d2ae6e489d18c90a2eb996e84cd96fa Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 11 Aug 2022 17:39:44 +0800 Subject: [PATCH 054/488] account template model --- .../migrations/0107_delete_accounttemplate.py | 16 ++++++++ .../assets/migrations/0108_accounttemplate.py | 39 +++++++++++++++++++ apps/assets/models/account.py | 13 +++++-- 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 apps/assets/migrations/0107_delete_accounttemplate.py create mode 100644 apps/assets/migrations/0108_accounttemplate.py diff --git a/apps/assets/migrations/0107_delete_accounttemplate.py b/apps/assets/migrations/0107_delete_accounttemplate.py new file mode 100644 index 000000000..a322dc421 --- /dev/null +++ b/apps/assets/migrations/0107_delete_accounttemplate.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.13 on 2022-08-11 09:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0106_auto_20220811_1358'), + ] + + operations = [ + migrations.DeleteModel( + name='AccountTemplate', + ), + ] diff --git a/apps/assets/migrations/0108_accounttemplate.py b/apps/assets/migrations/0108_accounttemplate.py new file mode 100644 index 000000000..ede37eb1b --- /dev/null +++ b/apps/assets/migrations/0108_accounttemplate.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.13 on 2022-08-11 09:34 + +import assets.models.base +import common.db.fields +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_delete_accounttemplate'), + ] + + operations = [ + migrations.CreateModel( + name='AccountTemplate', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), + ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), + ], + options={ + 'verbose_name': 'Account Template', + }, + bases=(models.Model, assets.models.base.AuthMixin), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 654ce2cf5..ef2531fb2 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -5,7 +5,6 @@ from simple_history.models import HistoricalRecords from common.db.models import JMSBaseModel from .base import BaseUser, AbsConnectivity - __all__ = ['Account'] @@ -33,5 +32,13 @@ class Account(BaseUser, AbsConnectivity): return '{}@{}'.format(self.username, self.asset.hostname) -class AccountTemplate(JMSBaseModel): - pass +class AccountTemplate(BaseUser, AbsConnectivity): + type = models.CharField( + max_length=16, choices=Account.Type.choices, default=Account.Type.common, verbose_name=_("Type") + ) + + class Meta: + verbose_name = _('Account Template') + + def __str__(self): + return '{}'.format(self.username) From 0bf88782281eb3dce85a69405a9fa9136972414a Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Aug 2022 18:31:57 +0800 Subject: [PATCH 055/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0109_auto_20220815_1811.py | 36 +++++++++++++++++++ .../migrations/0110_auto_20220815_1831.py | 24 +++++++++++++ apps/assets/models/account.py | 8 ++--- apps/assets/models/base.py | 32 ++--------------- apps/assets/models/platform.py | 2 +- apps/assets/serializers/account.py | 12 ++----- 6 files changed, 69 insertions(+), 45 deletions(-) create mode 100644 apps/assets/migrations/0109_auto_20220815_1811.py create mode 100644 apps/assets/migrations/0110_auto_20220815_1831.py diff --git a/apps/assets/migrations/0109_auto_20220815_1811.py b/apps/assets/migrations/0109_auto_20220815_1811.py new file mode 100644 index 000000000..c79715533 --- /dev/null +++ b/apps/assets/migrations/0109_auto_20220815_1811.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.14 on 2022-08-15 10:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0108_auto_20220811_1511'), + ] + + operations = [ + migrations.RemoveField( + model_name='account', + name='type', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='type', + ), + migrations.AddField( + model_name='account', + name='privileged', + field=models.BooleanField(default=False, verbose_name='Privileged account'), + ), + migrations.AddField( + model_name='historicalaccount', + name='privileged', + field=models.BooleanField(default=False, verbose_name='Privileged account'), + ), + migrations.AlterField( + model_name='platform', + name='su_enabled', + field=models.BooleanField(default=False, verbose_name='Su enabled'), + ), + ] diff --git a/apps/assets/migrations/0110_auto_20220815_1831.py b/apps/assets/migrations/0110_auto_20220815_1831.py new file mode 100644 index 000000000..e3d55ced6 --- /dev/null +++ b/apps/assets/migrations/0110_auto_20220815_1831.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-08-15 10:31 + +import common.db.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0109_auto_20220815_1811'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='token', + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), + ), + migrations.AddField( + model_name='historicalaccount', + name='token', + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 56b5758cf..a57535587 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from common.db.models import JMSBaseModel +from common.db import fields from .base import BaseUser, AbsConnectivity @@ -10,11 +11,8 @@ __all__ = ['Account'] class Account(BaseUser, AbsConnectivity): - class Type(models.TextChoices): - common = 'common', _('Common user') - admin = 'admin', _('Admin user') - - type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_("Type")) + token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) + privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) history = HistoricalRecords() diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 493036efc..14dfcfe83 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -13,12 +13,11 @@ from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django.db.models import QuerySet -from common.utils import random_string, signer +from common.utils import random_string from common.utils import ( - ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty + ssh_key_string_to_obj, ssh_key_gen, get_logger ) from common.utils.encode import ssh_pubkey_gen -from common.validators import alphanumeric from common.db import fields from orgs.mixins.models import OrgModelMixin @@ -188,36 +187,9 @@ class BaseUser(OrgModelMixin, AuthMixin): APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT" APP_USER_CACHE_TIME = 600 - def get_related_assets(self): - assets = self.assets.filter(org_id=self.org_id) - return assets - - def get_related_apps(self): - from applications.models import Account - apps = Account.objects.filter(systemuser=self) - return apps - def get_username(self): return self.username - @lazyproperty - def assets_amount(self): - cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) - cached = cache.get(cache_key) - if not cached: - cached = self.get_related_assets().count() - cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME) - return cached - - @property - def apps_amount(self): - cache_key = self.APPS_AMOUNT_CACHE_KEY.format(self.id) - cached = cache.get(cache_key) - if not cached: - cached = self.get_related_apps().count() - cache.set(cache_key, cached, self.APP_USER_CACHE_TIME) - return cached - def expire_assets_amount(self): cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) cache.delete(cache_key) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 36e1b0b51..7dd991c1d 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -35,7 +35,7 @@ class Platform(models.Model): ) # Accounts # 这应该和账号有关 - su_enabled = models.BooleanField(default=False) + su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) ping_enabled = models.BooleanField(default=False) ping_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index 3de132804..3538676b0 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -12,19 +12,13 @@ from common.drf.serializers import SecretReadableMixin class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ip = serializers.ReadOnlyField(label=_("IP")) - asset = serializers.ReadOnlyField(label=_("Asset")) + asset_name = serializers.ReadOnlyField(label=_("Asset")) platform = serializers.ReadOnlyField(label=_("Platform")) - date_created = serializers.DateTimeField( - label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True - ) - date_updated = serializers.DateTimeField( - label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True - ) class Meta: model = Account fields_mini = [ - 'id', 'type', 'username', 'ip', 'name', + 'id', 'privileged', 'username', 'ip', 'asset_name', 'platform', 'version' ] fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] @@ -59,7 +53,7 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): """ Perform necessary eager loading of data. """ queryset = queryset.prefetch_related('asset')\ .annotate(ip=F('asset__ip')) \ - .annotate(asset=F('asset__name')) + .annotate(asset_name=F('asset__name')) return queryset From 85acd4b2ac5c0908cee5cd74ae99aacdccb4f050 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Aug 2022 11:09:30 +0800 Subject: [PATCH 056/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20command=20?= =?UTF-8?q?filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/command_acl.py | 0 .../migrations/0022_auto_20220816_1015.py | 24 +++++++ .../migrations/0023_auto_20220816_1021.py | 21 ++++++ apps/applications/models/account.py | 4 +- .../migrations/0107_delete_accounttemplate.py | 16 ----- .../assets/migrations/0108_accounttemplate.py | 39 ---------- .../migrations/0111_auto_20220816_1022.py | 71 +++++++++++++++++++ apps/assets/models/_authbook.py | 4 +- apps/assets/models/_user.py | 6 +- apps/assets/models/account.py | 17 +---- apps/assets/models/asset/common.py | 2 +- apps/assets/models/base.py | 3 +- apps/assets/models/cmd_filter.py | 10 +-- apps/assets/models/domain.py | 4 +- 14 files changed, 132 insertions(+), 89 deletions(-) create mode 100644 apps/acls/models/command_acl.py create mode 100644 apps/applications/migrations/0022_auto_20220816_1015.py create mode 100644 apps/applications/migrations/0023_auto_20220816_1021.py delete mode 100644 apps/assets/migrations/0107_delete_accounttemplate.py delete mode 100644 apps/assets/migrations/0108_accounttemplate.py create mode 100644 apps/assets/migrations/0111_auto_20220816_1022.py diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/migrations/0022_auto_20220816_1015.py b/apps/applications/migrations/0022_auto_20220816_1015.py new file mode 100644 index 000000000..58e3291c9 --- /dev/null +++ b/apps/applications/migrations/0022_auto_20220816_1015.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-08-16 02:15 + +import common.db.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0021_auto_20220629_1826'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='token', + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), + ), + migrations.AddField( + model_name='historicalaccount', + name='token', + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), + ), + ] diff --git a/apps/applications/migrations/0023_auto_20220816_1021.py b/apps/applications/migrations/0023_auto_20220816_1021.py new file mode 100644 index 000000000..34d95cbe6 --- /dev/null +++ b/apps/applications/migrations/0023_auto_20220816_1021.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.14 on 2022-08-16 02:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0022_auto_20220816_1015'), + ] + + operations = [ + migrations.RemoveField( + model_name='account', + name='token', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='token', + ), + ] diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py index 5d82db36b..3828b5995 100644 --- a/apps/applications/models/account.py +++ b/apps/applications/models/account.py @@ -4,11 +4,11 @@ from django.db.models import F from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty -from assets.models.base import BaseUser +from assets.models.base import BaseAccount from assets.models import SystemUser -class Account(BaseUser): +class Account(BaseAccount): app = models.ForeignKey( 'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Application') ) diff --git a/apps/assets/migrations/0107_delete_accounttemplate.py b/apps/assets/migrations/0107_delete_accounttemplate.py deleted file mode 100644 index a322dc421..000000000 --- a/apps/assets/migrations/0107_delete_accounttemplate.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.13 on 2022-08-11 09:34 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0106_auto_20220811_1358'), - ] - - operations = [ - migrations.DeleteModel( - name='AccountTemplate', - ), - ] diff --git a/apps/assets/migrations/0108_accounttemplate.py b/apps/assets/migrations/0108_accounttemplate.py deleted file mode 100644 index ede37eb1b..000000000 --- a/apps/assets/migrations/0108_accounttemplate.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 3.2.13 on 2022-08-11 09:34 - -import assets.models.base -import common.db.fields -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0107_delete_accounttemplate'), - ] - - operations = [ - migrations.CreateModel( - name='AccountTemplate', - fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), - ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, verbose_name='Name')), - ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), - ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), - ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), - ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), - ], - options={ - 'verbose_name': 'Account Template', - }, - bases=(models.Model, assets.models.base.AuthMixin), - ), - ] diff --git a/apps/assets/migrations/0111_auto_20220816_1022.py b/apps/assets/migrations/0111_auto_20220816_1022.py new file mode 100644 index 000000000..296aaa99a --- /dev/null +++ b/apps/assets/migrations/0111_auto_20220816_1022.py @@ -0,0 +1,71 @@ +# Generated by Django 3.2.14 on 2022-08-16 02:22 +import time +from django.db import migrations, models + + +def migrate_command_filter_to_assets(apps, schema_editor): + command_filter_model = apps.get_model('assets', 'CommandFilter') + + count = 0 + bulk_size = 1000 + print("\nStart migrate command filters to assets") + while True: + start = time.time() + command_filters = command_filter_model.objects.all() \ + .prefetch_related('system_users')[count:count + bulk_size] + count += len(command_filters) + if not command_filters: + break + + updated = [] + for command_filter in command_filters: + command_filter.accounts = [s.username for s in command_filter.system_users.all()] + updated.append(command_filter) + command_filter_model.objects.bulk_update(updated, ['accounts']) + + print("Create assets: {}-{} using: {:.2f}s".format( + count - bulk_size, count, time.time() - start + )) + + +def migrate_command_filter_apps(apps, schema_editor): + command_filter_model = apps.get_model('assets', 'CommandFilter') + command_filters = command_filter_model.objects \ + .annotate(app_count=Count('applications')) \ + .filter(app_count__gt=0) + + for command_filter in command_filters: + app_ids = command_filter.applications.all().values_list('id', flat=True) + + try: + command_filter.assets.add(*app_ids) + except: + print("Migrate command filter apps failed: {}, skip".format(command_filter.id)) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_auto_20220815_1831'), + ] + + operations = [ + migrations.DeleteModel( + name='AccountTemplate', + ), + migrations.AddField( + model_name='commandfilter', + name='accounts', + field=models.JSONField(default=list, verbose_name='Accounts'), + ), + migrations.RunPython(migrate_command_filter_to_assets), + migrations.RemoveField( + model_name='commandfilter', + name='system_users', + ), + migrations.RunPython(migrate_command_filter_apps), + migrations.RemoveField( + model_name='commandfilter', + name='applications', + ), + ] diff --git a/apps/assets/models/_authbook.py b/apps/assets/models/_authbook.py index e96196d22..74a81df61 100644 --- a/apps/assets/models/_authbook.py +++ b/apps/assets/models/_authbook.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from simple_history.models import HistoricalRecords from common.utils import lazyproperty, get_logger -from .base import BaseUser, AbsConnectivity +from .base import BaseAccount, AbsConnectivity logger = get_logger(__name__) @@ -15,7 +15,7 @@ logger = get_logger(__name__) __all__ = ['AuthBook'] -class AuthBook(BaseUser, AbsConnectivity): +class AuthBook(BaseAccount, AbsConnectivity): asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) version = models.IntegerField(default=1, verbose_name=_('Version')) diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index 2ecb39094..8f5c9c9d1 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -10,7 +10,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator from assets.const import Protocol from common.utils import signer -from .base import BaseUser +from .base import BaseAccount from .protocol import ProtocolMixin @@ -18,7 +18,7 @@ __all__ = ['SystemUser'] logger = logging.getLogger(__name__) -class SystemUser(ProtocolMixin, BaseUser): +class SystemUser(ProtocolMixin, BaseAccount): LOGIN_AUTO = 'auto' LOGIN_MANUAL = 'manual' LOGIN_MODE_CHOICES = ( @@ -143,7 +143,7 @@ class SystemUser(ProtocolMixin, BaseUser): # Deprecated: 准备废弃 -class AdminUser(BaseUser): +class AdminUser(BaseAccount): """ A privileged user that ansible can use it to push system user and so on """ diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 57c4ba693..c48fd0256 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -2,14 +2,13 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords -from common.db.models import JMSBaseModel from common.db import fields -from .base import BaseUser, AbsConnectivity +from .base import BaseAccount, AbsConnectivity __all__ = ['Account'] -class Account(BaseUser, AbsConnectivity): +class Account(BaseAccount, AbsConnectivity): token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) @@ -28,15 +27,3 @@ class Account(BaseUser, AbsConnectivity): def __str__(self): return '{}@{}'.format(self.username, self.asset.name) - - -class AccountTemplate(BaseUser, AbsConnectivity): - type = models.CharField( - max_length=16, choices=Account.Type.choices, default=Account.Type.common, verbose_name=_("Type") - ) - - class Meta: - verbose_name = _('Account Template') - - def __str__(self): - return '{}'.format(self.username) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 4a412429c..f04f1f959 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -84,7 +84,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): objects = AssetManager.from_queryset(AssetQuerySet)() def __str__(self): - return '{0.hostname}({0.ip})'.format(self) + return '{0.name}({0.ip})'.format(self) def get_target_ip(self): return self.ip diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 14dfcfe83..c32de09e1 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -169,13 +169,14 @@ class AuthMixin: ) -class BaseUser(OrgModelMixin, AuthMixin): +class BaseAccount(OrgModelMixin, AuthMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) + # token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 86e613d3d..53189018c 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -9,7 +9,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator from django.utils.translation import ugettext_lazy as _ from users.models import User, UserGroup -from ..models import SystemUser, Asset +from ..models import Asset from common.utils import lazyproperty, get_logger, get_object_or_none from orgs.mixins.models import OrgModelMixin @@ -36,13 +36,7 @@ class CommandFilter(OrgModelMixin): 'assets.Asset', related_name='cmd_filters', blank=True, verbose_name=_("Asset") ) - system_users = models.ManyToManyField( - 'assets.SystemUser', related_name='cmd_filters', blank=True, - verbose_name=_("System user")) - applications = models.ManyToManyField( - 'applications.Application', related_name='cmd_filters', blank=True, - verbose_name=_("Application") - ) + accounts = models.JSONField(default=list, verbose_name=_("Accounts")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) comment = models.TextField(blank=True, default='', verbose_name=_("Comment")) date_created = models.DateTimeField(auto_now_add=True) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index a57cbdffc..a4ab3888f 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin -from .base import BaseUser +from .base import BaseAccount logger = get_logger(__file__) @@ -50,7 +50,7 @@ class Domain(OrgModelMixin): return random.choice(self.gateways) -class Gateway(BaseUser): +class Gateway(BaseAccount): UNCONNECTIVE_KEY_TMPL = 'asset_unconnective_gateway_{}' UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}' UNCONNECTIVE_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5 From 34c8cfc20aa75f639a55647eff66d750e0565a60 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Aug 2022 15:51:19 +0800 Subject: [PATCH 057/488] =?UTF-8?q?perf:=20=E8=BF=81=E7=A7=BB=20app=20perm?= =?UTF-8?q?ission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0029_auto_20220728_1728.py | 75 +++++++++++++++---- .../migrations/0030_auto_20220816_1132.py | 45 +++++++++++ 2 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 apps/perms/migrations/0030_auto_20220816_1132.py diff --git a/apps/perms/migrations/0029_auto_20220728_1728.py b/apps/perms/migrations/0029_auto_20220728_1728.py index 6f64dfbbd..4e312a57a 100644 --- a/apps/perms/migrations/0029_auto_20220728_1728.py +++ b/apps/perms/migrations/0029_auto_20220728_1728.py @@ -1,11 +1,62 @@ -# Generated by Django 3.2.14 on 2022-07-28 09:10 - -from django.db import migrations, models +from django.db import migrations -def migrate_system_user_to_accounts(apps, schema_editor): - # Todo: 迁移 系统用户为账号 - pass +def migrate_app_perms_to_assets(apps, schema_editor): + asset_permission_model = apps.get_model("perms", "AssetPermission") + app_permission_model = apps.get_model("perms", "ApplicationPermission") + + count = 0 + bulk_size = 1000 + while True: + app_perms = app_permission_model.objects.all()[count:bulk_size] + if not app_perms: + break + count += len(app_perms) + attrs = [ + 'id', 'name', 'actions', 'is_active', 'date_start', + 'date_expired', 'created_by', 'from_ticket', 'comment', + ] + asset_permissions = [] + + for app_perm in app_perms: + asset_permission = asset_permission_model() + for attr in attrs: + setattr(asset_permission, attr, getattr(app_perm, attr)) + asset_permissions.append(asset_permission) + asset_permission_model.objects.bulk_create(asset_permissions, ignore_conflicts=True) + + +def migrate_relations(apps, schema_editor): + asset_permission_model = apps.get_model("perms", "AssetPermission") + app_permission_model = apps.get_model("perms", "ApplicationPermission") + m2m_names = [ + ('applications', 'assets', 'application_id', 'asset_id'), + ('users', 'users', 'user_id', 'user_id'), + ('user_groups', 'user_groups', 'usergroup_id', 'usergroup_id'), + ('system_users', 'system_users', 'systemuser_id', 'systemuser_id'), + ] + + for app_name, asset_name, app_attr, asset_attr in m2m_names: + app_through = getattr(app_permission_model, app_name).through + asset_through = getattr(asset_permission_model, asset_name).through + + count = 0 + bulk_size = 1000 + + while True: + app_permission_relations = app_through.objects.all()[count:bulk_size] + if not app_permission_relations: + break + count += len(app_permission_relations) + asset_through_relations = [] + + for app_relation in app_permission_relations: + asset_relation = asset_through() + asset_relation.assetpermission_id = app_relation.applicationpermission_id + setattr(asset_relation, asset_attr, getattr(app_relation, app_attr)) + asset_through_relations.append(asset_relation) + + asset_through.objects.bulk_create(asset_through_relations, ignore_conflicts=True) class Migration(migrations.Migration): @@ -15,14 +66,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='assetpermission', - name='accounts', - field=models.JSONField(default=list, verbose_name='Accounts'), - ), - migrations.RunPython(migrate_system_user_to_accounts), - migrations.RemoveField( - model_name='assetpermission', - name='system_users', - ), + migrations.RunPython(migrate_app_perms_to_assets), + migrations.RunPython(migrate_relations), ] diff --git a/apps/perms/migrations/0030_auto_20220816_1132.py b/apps/perms/migrations/0030_auto_20220816_1132.py new file mode 100644 index 000000000..d80bc0dfc --- /dev/null +++ b/apps/perms/migrations/0030_auto_20220816_1132.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2.14 on 2022-08-16 03:32 + + + +import time +from django.db import migrations, models + + +def migrate_system_user_to_accounts(apps, schema_editor): + asset_permission_model = apps.get_model("perms", "AssetPermission") + + count = 0 + bulk_size = 10000 + while True: + asset_permissions = asset_permission_model.objects \ + .prefetch_related('system_users')[count:bulk_size] + if not asset_permissions: + break + count += len(asset_permissions) + + updated = [] + for asset_permission in asset_permissions: + asset_permission.accounts = [s.username for s in asset_permission.system_users.all()] + updated.append(asset_permission) + asset_permission_model.objects.bulk_update(updated, ['accounts']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0029_auto_20220728_1728'), + ] + + operations = [ + migrations.AddField( + model_name='assetpermission', + name='accounts', + field=models.JSONField(default=list, verbose_name='Accounts'), + ), + migrations.RunPython(migrate_system_user_to_accounts), + migrations.RemoveField( + model_name='assetpermission', + name='system_users', + ), + ] From b8f8c2a2641144c64d74714db6370c3bbca7e4c0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Aug 2022 16:05:08 +0800 Subject: [PATCH 058/488] perf: remove application permission --- apps/audits/signal_handlers.py | 22 +--- apps/orgs/api.py | 4 +- apps/orgs/caches.py | 3 +- apps/orgs/signal_handlers/cache.py | 4 +- apps/orgs/signal_handlers/common.py | 4 +- .../migrations/0031_auto_20220816_1600.py | 39 ++++++ apps/perms/models/__init__.py | 2 +- apps/perms/models/application_permission.py | 118 ------------------ apps/perms/utils/__init__.py | 1 - apps/perms/utils/application/__init__.py | 2 - apps/perms/utils/application/permission.py | 82 ------------ .../utils/application/user_permission.py | 18 --- apps/tickets/api/ticket.py | 15 +-- apps/tickets/serializers/ticket/__init__.py | 1 - .../serializers/ticket/apply_application.py | 62 --------- apps/tickets/urls/api_urls.py | 1 - 16 files changed, 50 insertions(+), 328 deletions(-) create mode 100644 apps/perms/migrations/0031_auto_20220816_1600.py delete mode 100644 apps/perms/models/application_permission.py delete mode 100644 apps/perms/utils/application/__init__.py delete mode 100644 apps/perms/utils/application/permission.py delete mode 100644 apps/perms/utils/application/user_permission.py delete mode 100644 apps/tickets/serializers/ticket/apply_application.py diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index af6959f48..9173a6d9f 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -27,7 +27,7 @@ from .utils import write_login_log, create_operate_log from . import models, serializers from .models import OperateLog from orgs.utils import current_org -from perms.models import AssetPermission, ApplicationPermission +from perms.models import AssetPermission from terminal.backends.command.serializers import SessionCommandSerializer from terminal.serializers import SessionSerializer from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR @@ -94,26 +94,6 @@ M2M_NEED_RECORD = { _('{AssetPermission} ADD {Node}'), _('{AssetPermission} REMOVE {Node}'), ), - ApplicationPermission.users.through._meta.object_name: ( - _('User application permissions'), - _('{ApplicationPermission} ADD {User}'), - _('{ApplicationPermission} REMOVE {User}'), - ), - ApplicationPermission.user_groups.through._meta.object_name: ( - _('User group application permissions'), - _('{ApplicationPermission} ADD {UserGroup}'), - _('{ApplicationPermission} REMOVE {UserGroup}'), - ), - ApplicationPermission.applications.through._meta.object_name: ( - _('Application permission'), - _('{ApplicationPermission} ADD {Application}'), - _('{ApplicationPermission} REMOVE {Application}'), - ), - ApplicationPermission.system_users.through._meta.object_name: ( - _('Application permission and SystemUser'), - _('{ApplicationPermission} ADD {SystemUser}'), - _('{ApplicationPermission} REMOVE {SystemUser}'), - ), } M2M_ACTION = { diff --git a/apps/orgs/api.py b/apps/orgs/api.py index 7a3271c50..eaa615c52 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -18,7 +18,7 @@ from assets.models import ( CommandFilter, CommandFilterRule, GatheredUser ) from applications.models import Application -from perms.models import AssetPermission, ApplicationPermission +from perms.models import AssetPermission from orgs.utils import current_org, tmp_to_root_org from common.utils import get_logger @@ -30,7 +30,7 @@ logger = get_logger(__file__) org_related_models = [ User, UserGroup, Asset, Label, Domain, Gateway, Node, SystemUser, Label, CommandFilter, CommandFilterRule, GatheredUser, - AssetPermission, ApplicationPermission, + AssetPermission, Application, ] diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index e8b0bdcba..53d8ab1dc 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -9,7 +9,7 @@ from users.models import UserGroup, User from assets.models import Node, SystemUser, Domain, Gateway, Asset from terminal.models import Session from applications.models import Application -from perms.models import AssetPermission, ApplicationPermission +from perms.models import AssetPermission logger = get_logger(__file__) @@ -61,7 +61,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache): applications_amount = IntegerField(queryset=Application.objects) asset_perms_amount = IntegerField(queryset=AssetPermission.objects) - app_perms_amount = IntegerField(queryset=ApplicationPermission.objects) total_count_online_users = IntegerField() total_count_online_sessions = IntegerField() diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 8bd2ca609..4ee0b6edd 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -1,10 +1,9 @@ -from functools import wraps from django.db.models.signals import post_save, pre_delete, pre_save, post_delete from django.dispatch import receiver from orgs.models import Organization from assets.models import Node -from perms.models import AssetPermission, ApplicationPermission +from perms.models import AssetPermission from users.models import UserGroup, User from users.signals import pre_user_leave_org from applications.models import Application @@ -76,7 +75,6 @@ def on_user_delete_refresh_cache(sender, instance, **kwargs): class OrgResourceStatisticsRefreshUtil: model_cache_field_mapper = { - ApplicationPermission: ['app_perms_amount'], AssetPermission: ['asset_perms_amount'], Application: ['applications_amount'], Gateway: ['gateways_amount'], diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index fc8b4af12..bc0ac64cd 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -12,7 +12,7 @@ from django.db.models.signals import post_save, pre_delete from orgs.utils import tmp_to_org from orgs.models import Organization from orgs.hands import set_current_org, Node, get_current_org -from perms.models import (AssetPermission, ApplicationPermission) +from perms.models import AssetPermission from users.models import UserGroup, User from assets.models import SystemUser from common.const.signals import PRE_REMOVE, POST_REMOVE @@ -135,7 +135,7 @@ def _clear_users_from_org(org, users): if not users: return - models = (AssetPermission, ApplicationPermission, UserGroup, SystemUser) + models = (AssetPermission, UserGroup, SystemUser) for m in models: _remove_users(m, users, org) diff --git a/apps/perms/migrations/0031_auto_20220816_1600.py b/apps/perms/migrations/0031_auto_20220816_1600.py new file mode 100644 index 000000000..b5ccbc24c --- /dev/null +++ b/apps/perms/migrations/0031_auto_20220816_1600.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.14 on 2022-08-16 08:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0030_auto_20220816_1132'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='applicationpermission', + unique_together=None, + ), + migrations.RemoveField( + model_name='applicationpermission', + name='applications', + ), + migrations.RemoveField( + model_name='applicationpermission', + name='system_users', + ), + migrations.RemoveField( + model_name='applicationpermission', + name='user_groups', + ), + migrations.RemoveField( + model_name='applicationpermission', + name='users', + ), + migrations.DeleteModel( + name='PermedApplication', + ), + migrations.DeleteModel( + name='ApplicationPermission', + ), + ] diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py index e2ac0c1d6..37e04577d 100644 --- a/apps/perms/models/__init__.py +++ b/apps/perms/models/__init__.py @@ -2,5 +2,5 @@ # from .asset_permission import * -from .application_permission import * +# from .application_permission import * from .base import * diff --git a/apps/perms/models/application_permission.py b/apps/perms/models/application_permission.py deleted file mode 100644 index f3e1d0c48..000000000 --- a/apps/perms/models/application_permission.py +++ /dev/null @@ -1,118 +0,0 @@ -# coding: utf-8 -# -# TODO: v3 delete 整个文件 - -from django.db import models -from django.db.models import Q -from django.utils.translation import ugettext_lazy as _ - -from common.utils import lazyproperty -from .base import BasePermission, Action -from applications.models import Application -from users.models import User -from applications.const import AppCategory, AppType - -__all__ = [ - 'ApplicationPermission', -] - - -class ApplicationPermission(BasePermission): - category = models.CharField( - max_length=16, choices=AppCategory.choices, verbose_name=_('Category') - ) - type = models.CharField( - max_length=16, choices=AppType.choices, verbose_name=_('Type') - ) - applications = models.ManyToManyField( - 'applications.Application', related_name='granted_by_permissions', blank=True, - verbose_name=_("Application") - ) - system_users = models.ManyToManyField( - 'assets.SystemUser', - related_name='granted_by_application_permissions', blank=True, - verbose_name=_("System user") - ) - - class Meta: - unique_together = [('org_id', 'name')] - verbose_name = _('Application permission') - permissions = [ - ] - ordering = ('name',) - - @property - def category_remote_app(self): - return self.category == AppCategory.remote_app.value - - @property - def category_db(self): - return self.category == AppCategory.db.value - - @property - def category_cloud(self): - return self.category == AppCategory.cloud.value - - @lazyproperty - def users_amount(self): - return self.users.count() - - @lazyproperty - def user_groups_amount(self): - return self.user_groups.count() - - @lazyproperty - def applications_amount(self): - return self.applications.count() - - @lazyproperty - def system_users_amount(self): - return self.system_users.count() - - def get_all_users(self): - user_ids = self.users.all().values_list('id', flat=True) - user_group_ids = self.user_groups.all().values_list('id', flat=True) - users = User.objects.filter( - Q(id__in=user_ids) | Q(groups__id__in=user_group_ids) - ) - return users - - @classmethod - def get_include_actions_choices(cls, category=None): - actions = {Action.ALL, Action.CONNECT} - if category == AppCategory.db: - _actions = [Action.UPLOAD, Action.DOWNLOAD] - elif category == AppCategory.remote_app: - _actions = [ - Action.UPLOAD, Action.DOWNLOAD, - Action.CLIPBOARD_COPY, Action.CLIPBOARD_PASTE - ] - else: - _actions = [] - actions.update(_actions) - - if (Action.UPLOAD in actions) or (Action.DOWNLOAD in actions): - actions.update([Action.UPDOWNLOAD]) - if (Action.CLIPBOARD_COPY in actions) or (Action.CLIPBOARD_PASTE in actions): - actions.update([Action.CLIPBOARD_COPY_PASTE]) - - choices = [Action.NAME_MAP[action] for action in actions] - return choices - - @classmethod - def get_exclude_actions_choices(cls, category=None): - include_choices = cls.get_include_actions_choices(category) - exclude_choices = set(Action.NAME_MAP.values()) - set(include_choices) - return exclude_choices - - -class PermedApplication(Application): - class Meta: - proxy = True - verbose_name = _('Permed application') - default_permissions = [] - permissions = [ - ('view_myapps', _('Can view my apps')), - ('view_userapps', _('Can view user apps')), - ('view_usergroupapps', _('Can view usergroup apps')), - ] diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py index e204cd61b..15277144a 100644 --- a/apps/perms/utils/__init__.py +++ b/apps/perms/utils/__init__.py @@ -2,4 +2,3 @@ # from .asset import * -from .application import * diff --git a/apps/perms/utils/application/__init__.py b/apps/perms/utils/application/__init__.py deleted file mode 100644 index ea3cb14de..000000000 --- a/apps/perms/utils/application/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .permission import * -from .user_permission import * diff --git a/apps/perms/utils/application/permission.py b/apps/perms/utils/application/permission.py deleted file mode 100644 index 955743e25..000000000 --- a/apps/perms/utils/application/permission.py +++ /dev/null @@ -1,82 +0,0 @@ -import time -from functools import reduce - -from django.db.models import Q - -from common.utils import get_logger -from perms.models import ApplicationPermission, Action - -logger = get_logger(__file__) - - -def get_user_all_app_perm_ids(user) -> set: - app_perm_ids = set() - user_perm_id = ApplicationPermission.users.through.objects \ - .filter(user_id=user.id) \ - .values_list('applicationpermission_id', flat=True) \ - .distinct() - app_perm_ids.update(user_perm_id) - - group_ids = user.groups.through.objects \ - .filter(user_id=user.id) \ - .values_list('usergroup_id', flat=True) \ - .distinct() - group_ids = list(group_ids) - groups_perm_id = ApplicationPermission.user_groups.through.objects \ - .filter(usergroup_id__in=group_ids) \ - .values_list('applicationpermission_id', flat=True) \ - .distinct() - app_perm_ids.update(groups_perm_id) - - app_perm_ids = ApplicationPermission.objects.filter( - id__in=app_perm_ids).valid().values_list('id', flat=True) - app_perm_ids = set(app_perm_ids) - return app_perm_ids - - -def validate_permission(user, application, system_user, action='connect'): - app_perm_ids = get_user_all_app_perm_ids(user) - app_perm_ids = ApplicationPermission.applications.through.objects.filter( - applicationpermission_id__in=app_perm_ids, - application_id=application.id - ).values_list('applicationpermission_id', flat=True) - app_perm_ids = set(app_perm_ids) - app_perm_ids = ApplicationPermission.system_users.through.objects.filter( - applicationpermission_id__in=app_perm_ids, - systemuser_id=system_user.id - ).values_list('applicationpermission_id', flat=True) - app_perm_ids = set(app_perm_ids) - app_perms = ApplicationPermission.objects.filter( - id__in=app_perm_ids - ).order_by('-date_expired') - - if app_perms: - actions = set() - actions_values = app_perms.values_list('actions', flat=True) - for value in actions_values: - _actions = Action.value_to_choices(value) - actions.update(_actions) - actions = list(actions) - app_perm: ApplicationPermission = app_perms.first() - expire_at = app_perm.date_expired.timestamp() - else: - actions = [] - expire_at = time.time() - - # TODO: 组件改造API完成后统一通过actions判断has_perm - has_perm = action in actions - return has_perm, actions, expire_at - - -def get_application_system_user_ids(user, application): - queryset = ApplicationPermission.objects.valid()\ - .filter( - Q(users=user) | Q(user_groups__users=user), - Q(applications=application) - ).values_list('system_users', flat=True) - return queryset - - -def has_application_system_permission(user, application, system_user): - system_user_ids = get_application_system_user_ids(user, application) - return system_user.id in system_user_ids diff --git a/apps/perms/utils/application/user_permission.py b/apps/perms/utils/application/user_permission.py deleted file mode 100644 index 524d5cd42..000000000 --- a/apps/perms/utils/application/user_permission.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.db.models import Q -from perms.models import ApplicationPermission -from applications.models import Application - - -def get_user_all_applicationpermission_ids(user): - application_perm_ids = ApplicationPermission.objects.valid().filter( - Q(users=user) | Q(user_groups__users=user) - ).distinct().values_list('id', flat=True) - return application_perm_ids - - -def get_user_granted_all_applications(user): - application_perm_ids = get_user_all_applicationpermission_ids(user) - applications = Application.objects.filter( - granted_by_permissions__id__in=application_perm_ids - ).distinct() - return applications diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index ee6b76534..436c57f90 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -20,8 +20,9 @@ from tickets.models import ( ) __all__ = [ - 'TicketViewSet', 'ApplyAssetTicketViewSet', 'ApplyApplicationTicketViewSet', - 'ApplyLoginTicketViewSet', 'ApplyLoginAssetTicketViewSet', 'ApplyCommandTicketViewSet' + 'TicketViewSet', 'ApplyAssetTicketViewSet', + 'ApplyLoginTicketViewSet', 'ApplyLoginAssetTicketViewSet', + 'ApplyCommandTicketViewSet' ] @@ -104,16 +105,6 @@ class ApplyAssetTicketViewSet(TicketViewSet): filterset_class = filters.ApplyAssetTicketFilter -class ApplyApplicationTicketViewSet(TicketViewSet): - serializer_class = serializers.ApplyApplicationDisplaySerializer - serializer_classes = { - 'open': serializers.ApplyApplicationSerializer, - 'approve': serializers.ApproveApplicationSerializer - } - model = ApplyApplicationTicket - filterset_class = filters.ApplyApplicationTicketFilter - - class ApplyLoginTicketViewSet(TicketViewSet): serializer_class = serializers.LoginConfirmSerializer model = ApplyLoginTicket diff --git a/apps/tickets/serializers/ticket/__init__.py b/apps/tickets/serializers/ticket/__init__.py index 698906d3a..7b1bdfe13 100644 --- a/apps/tickets/serializers/ticket/__init__.py +++ b/apps/tickets/serializers/ticket/__init__.py @@ -1,6 +1,5 @@ from .ticket import * from .apply_asset import * -from .apply_application import * from .login_confirm import * from .login_asset_confirm import * from .command_confirm import * diff --git a/apps/tickets/serializers/ticket/apply_application.py b/apps/tickets/serializers/ticket/apply_application.py deleted file mode 100644 index c713f21d6..000000000 --- a/apps/tickets/serializers/ticket/apply_application.py +++ /dev/null @@ -1,62 +0,0 @@ -from django.utils.translation import ugettext as _ -from rest_framework import serializers - -from perms.models import ApplicationPermission -from orgs.utils import tmp_to_org -from applications.models import Application -from tickets.models import ApplyApplicationTicket -from .ticket import TicketApplySerializer -from .common import BaseApplyAssetApplicationSerializer - -__all__ = ['ApplyApplicationSerializer', 'ApplyApplicationDisplaySerializer', 'ApproveApplicationSerializer'] - - -class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer): - permission_model = ApplicationPermission - - class Meta: - model = ApplyApplicationTicket - writeable_fields = [ - 'id', 'title', 'type', 'apply_category', - 'apply_type', 'apply_applications', 'apply_system_users', - 'apply_date_start', 'apply_date_expired', 'org_id' - ] - fields = TicketApplySerializer.Meta.fields + writeable_fields + ['apply_permission_name'] - read_only_fields = list(set(fields) - set(writeable_fields)) - ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs - extra_kwargs = { - 'apply_applications': {'required': False, 'allow_empty': True}, - 'apply_system_users': {'required': False, 'allow_empty': True}, - } - extra_kwargs.update(ticket_extra_kwargs) - - def validate_apply_applications(self, applications): - if self.is_final_approval and not applications: - raise serializers.ValidationError(_('This field is required.')) - tp = self.initial_data.get('apply_type') - return self.filter_many_to_many_field(Application, applications, type=tp) - - -class ApproveApplicationSerializer(ApplyApplicationSerializer): - class Meta(ApplyApplicationSerializer.Meta): - read_only_fields = ApplyApplicationSerializer.Meta.read_only_fields + ['title', 'type'] - - -class ApplyApplicationDisplaySerializer(ApplyApplicationSerializer): - apply_applications = serializers.SerializerMethodField() - apply_system_users = serializers.SerializerMethodField() - - class Meta: - model = ApplyApplicationSerializer.Meta.model - fields = ApplyApplicationSerializer.Meta.fields - read_only_fields = fields - - @staticmethod - def get_apply_applications(instance): - with tmp_to_org(instance.org_id): - return instance.apply_applications.values_list('id', flat=True) - - @staticmethod - def get_apply_system_users(instance): - with tmp_to_org(instance.org_id): - return instance.apply_system_users.values_list('id', flat=True) diff --git a/apps/tickets/urls/api_urls.py b/apps/tickets/urls/api_urls.py index 22715c527..6602e207c 100644 --- a/apps/tickets/urls/api_urls.py +++ b/apps/tickets/urls/api_urls.py @@ -11,7 +11,6 @@ router = BulkRouter() router.register('tickets', api.TicketViewSet, 'ticket') router.register('apply-asset-tickets', api.ApplyAssetTicketViewSet, 'apply-asset-ticket') -router.register('apply-app-tickets', api.ApplyApplicationTicketViewSet, 'apply-app-ticket') router.register('apply-login-tickets', api.ApplyLoginTicketViewSet, 'apply-login-ticket') router.register('apply-login-asset-tickets', api.ApplyLoginAssetTicketViewSet, 'apply-login-asset-ticket') router.register('apply-command-tickets', api.ApplyCommandTicketViewSet, 'apply-command-ticket') From 2948d5af7f8d4ab25a1b4d4419a5e3ea683adbff Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Aug 2022 16:34:16 +0800 Subject: [PATCH 059/488] =?UTF-8?q?perf:=20=E5=88=A0=E9=99=A4=E4=B8=80?= =?UTF-8?q?=E9=83=A8=E5=88=86=20system=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0024_auto_20220816_1629.py | 59 +++++++++++++++ apps/applications/models/__init__.py | 4 +- apps/assets/serializers/domain.py | 7 +- apps/assets/task_handlers/backup/handlers.py | 31 +------- apps/audits/const.py | 3 +- .../migrations/0012_auto_20220816_1629.py | 53 +++++++++++++ apps/authentication/models.py | 15 +--- .../serializers/connection_token.py | 13 ---- apps/orgs/api.py | 2 - apps/orgs/caches.py | 4 - apps/orgs/serializers.py | 2 - apps/orgs/signal_handlers/cache.py | 2 - apps/perms/notifications.py | 74 ------------------- apps/terminal/api/endpoint.py | 12 +-- apps/terminal/models/session.py | 1 - apps/tickets/api/ticket.py | 2 +- apps/tickets/filters.py | 8 +- .../0019_delete_applyapplicationticket.py | 16 ++++ apps/tickets/models/ticket/__init__.py | 2 +- apps/tickets/views/approve.py | 3 +- 20 files changed, 141 insertions(+), 172 deletions(-) create mode 100644 apps/applications/migrations/0024_auto_20220816_1629.py create mode 100644 apps/authentication/migrations/0012_auto_20220816_1629.py create mode 100644 apps/tickets/migrations/0019_delete_applyapplicationticket.py diff --git a/apps/applications/migrations/0024_auto_20220816_1629.py b/apps/applications/migrations/0024_auto_20220816_1629.py new file mode 100644 index 000000000..4e8f88cff --- /dev/null +++ b/apps/applications/migrations/0024_auto_20220816_1629.py @@ -0,0 +1,59 @@ +# Generated by Django 3.2.14 on 2022-08-16 08:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0019_delete_applyapplicationticket'), + ('authentication', '0012_auto_20220816_1629'), + ('applications', '0023_auto_20220816_1021'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='account', + unique_together=None, + ), + migrations.RemoveField( + model_name='account', + name='app', + ), + migrations.RemoveField( + model_name='account', + name='systemuser', + ), + migrations.AlterUniqueTogether( + name='application', + unique_together=None, + ), + migrations.RemoveField( + model_name='application', + name='domain', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='app', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='history_user', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='systemuser', + ), + migrations.DeleteModel( + name='ApplicationUser', + ), + migrations.DeleteModel( + name='Account', + ), + migrations.DeleteModel( + name='Application', + ), + migrations.DeleteModel( + name='HistoricalAccount', + ), + ] diff --git a/apps/applications/models/__init__.py b/apps/applications/models/__init__.py index 4bd20e32f..268bbae0d 100644 --- a/apps/applications/models/__init__.py +++ b/apps/applications/models/__init__.py @@ -1,2 +1,2 @@ -from .application import * -from .account import * +# from .application import * +# from .account import * diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index fa1a39574..d67b624e4 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -12,7 +12,6 @@ from .base import AuthSerializerMixin class DomainSerializer(BulkOrgResourceModelSerializer): asset_count = serializers.SerializerMethodField(label=_('Assets amount')) - application_count = serializers.SerializerMethodField(label=_('Applications amount')) gateway_count = serializers.SerializerMethodField(label=_('Gateways count')) class Meta: @@ -22,7 +21,7 @@ class DomainSerializer(BulkOrgResourceModelSerializer): 'comment', 'date_created' ] fields_m2m = [ - 'asset_count', 'assets', 'application_count', 'gateway_count', + 'asset_count', 'assets', 'gateway_count', ] fields = fields_small + fields_m2m read_only_fields = ('asset_count', 'gateway_count', 'date_created') @@ -34,10 +33,6 @@ class DomainSerializer(BulkOrgResourceModelSerializer): def get_asset_count(obj): return obj.assets.count() - @staticmethod - def get_application_count(obj): - return obj.applications.count() - @staticmethod def get_gateway_count(obj): return obj.gateway_set.all().count() diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index 969ed5433..df7bb52da 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -10,9 +10,6 @@ from rest_framework import serializers from assets.models import Account from assets.serializers import AccountSecretSerializer from assets.notifications import AccountBackupExecutionTaskMsg -from applications.models import Account -from applications.const import AppType -from applications.serializers import AppAccountSecretSerializer from users.models import User from common.utils import get_logger from common.utils.timezone import local_now_display @@ -76,7 +73,7 @@ class AssetAccountHandler(BaseAccountHandler): data_map = defaultdict(list) sheet_name = Account._meta.verbose_name - accounts = Account.get_queryset() + accounts = Account.objects.all() if not accounts.first(): return data_map @@ -91,34 +88,8 @@ class AssetAccountHandler(BaseAccountHandler): logger.info('\n\033[33m- 共收集 {} 条资产账号\033[0m'.format(accounts.count())) return data_map - -class AppAccountHandler(BaseAccountHandler): - @staticmethod - def get_filename(plan_name): - filename = os.path.join( - PATH, f'{plan_name}-{_("Application")}-{local_now_display()}-{time.time()}.xlsx' - ) - return filename - - @classmethod - def create_data_map(cls): - data_map = defaultdict(list) - accounts = Account.get_queryset().select_related('systemuser') - for account in accounts: - account.load_auth() - app_type = account.type - sheet_name = AppType.get_label(app_type) - row = cls.create_row(account, AppAccountSecretSerializer) - if sheet_name not in data_map: - data_map[sheet_name].append(list(row.keys())) - data_map[sheet_name].append(list(row.values())) - logger.info('\n\033[33m- 共收集{}条应用账号\033[0m'.format(accounts.count())) - return data_map - - handler_map = { 'asset': AssetAccountHandler, - 'application': AppAccountHandler } diff --git a/apps/audits/const.py b/apps/audits/const.py index eaed75fc0..18033ee78 100644 --- a/apps/audits/const.py +++ b/apps/audits/const.py @@ -13,13 +13,12 @@ MODELS_NEED_RECORD = ( 'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', 'CommandFilter', 'Platform', 'Account', # applications - 'Application', # orgs 'Organization', # settings 'Setting', # perms - 'AssetPermission', 'ApplicationPermission', + 'AssetPermission', # xpack 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask', ) diff --git a/apps/authentication/migrations/0012_auto_20220816_1629.py b/apps/authentication/migrations/0012_auto_20220816_1629.py new file mode 100644 index 000000000..1f76da99c --- /dev/null +++ b/apps/authentication/migrations/0012_auto_20220816_1629.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.14 on 2022-08-16 08:29 + +from django.db import migrations, models + + +def migrate_system_user_to_accounts(apps, schema_editor): + connection_token_model = apps.get_model("perms", "ConnectionToken") + count = 0 + bulk_size = 10000 + + while True: + connection_tokens = connection_token_model.objects \ + .prefect_related('system_users')[count:bulk_size] + if not connection_tokens: + break + count += len(connection_tokens) + updated = [] + for connection_token in connection_tokens: + connection_token.account = connection_token.system_user.username + updated.append(connection_token) + connection_token_model.objects.bulk_update(updated, ['account']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0011_auto_20220705_1940'), + ] + + operations = [ + migrations.RemoveField( + model_name='connectiontoken', + name='application', + ), + migrations.RemoveField( + model_name='connectiontoken', + name='application_display', + ), + migrations.RemoveField( + model_name='connectiontoken', + name='system_user_display', + ), + migrations.AddField( + model_name='connectiontoken', + name='account', + field=models.CharField(default='', max_length=128, verbose_name='Account'), + ), + migrations.RunPython(migrate_system_user_to_accounts), + migrations.RemoveField( + model_name='connectiontoken', + name='system_user', + ) + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 4c34f1d8e..97512ff3e 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -80,25 +80,12 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): related_name='connection_tokens', null=True, blank=True ) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) - system_user = models.ForeignKey( - 'assets.SystemUser', on_delete=models.SET_NULL, verbose_name=_('System user'), - related_name='connection_tokens', null=True, blank=True - ) - system_user_display = models.CharField( - max_length=128, default='', verbose_name=_("System user display") - ) asset = models.ForeignKey( 'assets.Asset', on_delete=models.SET_NULL, verbose_name=_('Asset'), related_name='connection_tokens', null=True, blank=True ) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - application = models.ForeignKey( - 'applications.Application', on_delete=models.SET_NULL, verbose_name=_('Application'), - related_name='connection_tokens', null=True, blank=True - ) - application_display = models.CharField( - max_length=128, default='', verbose_name=_("Application display") - ) + account = models.CharField(max_length=128, default='', verbose_name=_("Account")) class Meta: ordering = ('-date_expired',) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 3ae38ff18..284c35663 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -7,7 +7,6 @@ from common.utils import pretty_string from common.utils.random import random_string from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule from users.models import User -from applications.models import Application from perms.serializers.base import ActionsField @@ -66,9 +65,6 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): if isinstance(asset, Asset): tp = ConnectionToken.Type.asset org_id = asset.org_id - elif isinstance(application, Application): - tp = ConnectionToken.Type.application - org_id = application.org_id else: raise serializers.ValidationError(_('Asset or application required')) @@ -148,14 +144,6 @@ class ConnectionTokenRemoteAppSerializer(serializers.Serializer): parameters = serializers.CharField(allow_null=True, allow_blank=True) -class ConnectionTokenApplicationSerializer(serializers.ModelSerializer): - attrs = serializers.JSONField(read_only=True) - - class Meta: - model = Application - fields = ['id', 'name', 'category', 'type', 'attrs', 'org_id'] - - class ConnectionTokenDomainSerializer(serializers.ModelSerializer): gateways = ConnectionTokenGatewaySerializer(many=True, read_only=True) @@ -176,7 +164,6 @@ class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) - application = ConnectionTokenApplicationSerializer(read_only=True) remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) system_user = ConnectionTokenSystemUserSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) diff --git a/apps/orgs/api.py b/apps/orgs/api.py index eaa615c52..d61e137bc 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -17,7 +17,6 @@ from assets.models import ( Asset, Domain, SystemUser, Label, Node, Gateway, CommandFilter, CommandFilterRule, GatheredUser ) -from applications.models import Application from perms.models import AssetPermission from orgs.utils import current_org, tmp_to_root_org from common.utils import get_logger @@ -31,7 +30,6 @@ org_related_models = [ User, UserGroup, Asset, Label, Domain, Gateway, Node, SystemUser, Label, CommandFilter, CommandFilterRule, GatheredUser, AssetPermission, - Application, ] diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index 53d8ab1dc..33fc87f3e 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -8,7 +8,6 @@ from common.utils import get_logger from users.models import UserGroup, User from assets.models import Node, SystemUser, Domain, Gateway, Asset from terminal.models import Session -from applications.models import Application from perms.models import AssetPermission logger = get_logger(__file__) @@ -57,9 +56,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache): system_users_amount = IntegerField() domains_amount = IntegerField(queryset=Domain.objects) gateways_amount = IntegerField(queryset=Gateway.objects) - - applications_amount = IntegerField(queryset=Application.objects) - asset_perms_amount = IntegerField(queryset=AssetPermission.objects) total_count_online_users = IntegerField() diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index 0ae933044..fadf1ef8d 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -16,9 +16,7 @@ class ResourceStatisticsSerializer(serializers.Serializer): domains_amount = serializers.IntegerField(required=False) gateways_amount = serializers.IntegerField(required=False) - applications_amount = serializers.IntegerField(required=False) asset_perms_amount = serializers.IntegerField(required=False) - app_perms_amount = serializers.IntegerField(required=False) class OrgSerializer(ModelSerializer): diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 4ee0b6edd..6acf1ac7a 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -6,7 +6,6 @@ from assets.models import Node from perms.models import AssetPermission from users.models import UserGroup, User from users.signals import pre_user_leave_org -from applications.models import Application from terminal.models import Session from rbac.models import OrgRoleBinding, SystemRoleBinding, RoleBinding from assets.models import Asset, SystemUser, Domain, Gateway @@ -76,7 +75,6 @@ def on_user_delete_refresh_cache(sender, instance, **kwargs): class OrgResourceStatisticsRefreshUtil: model_cache_field_mapper = { AssetPermission: ['asset_perms_amount'], - Application: ['applications_amount'], Gateway: ['gateways_amount'], Domain: ['domains_amount'], SystemUser: ['system_users_amount', 'admin_users_amount'], diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index a6316eb8a..28e315ab3 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -1,6 +1,4 @@ -from urllib.parse import urljoin -from django.conf import settings from django.utils.translation import ugettext as _ from django.template.loader import render_to_string @@ -82,75 +80,3 @@ class AssetPermsWillExpireForOrgAdminMsg(UserMessage): perms = AssetPermission.objects.all()[:10] org = Organization.objects.first() return cls(user, perms, org) - - -class PermedAppsWillExpireUserMsg(UserMessage): - def __init__(self, user, apps, day_count=0): - super().__init__(user) - self.apps = apps - self.day_count = day_count - - def get_html_msg(self) -> dict: - subject = _("Your permed applications is about to expire") - context = { - 'name': self.user.name, - 'count': self.day_count, - 'item_type': _('permed applications'), - 'items': [str(app) for app in self.apps] - } - message = render_to_string('perms/_msg_permed_items_expire.html', context) - return { - 'subject': subject, - 'message': message - } - - @classmethod - def gen_test_msg(cls): - from users.models import User - from applications.models import Application - - user = User.objects.first() - apps = Application.objects.all()[:10] - return cls(user, apps) - - -class AppPermsWillExpireForOrgAdminMsg(UserMessage): - def __init__(self, user, perms, org, day_count=0): - super().__init__(user) - self.perms = perms - self.org = org - self.day_count = day_count - - def get_items_with_url(self): - items_with_url = [] - perm_detail_url = urljoin(settings.SITE_URL, '/ui/#/perms/app-permissions/{}') - for perm in self.perms: - url = perm_detail_url.format(perm.id) + f'?oid={perm.org_id}' - items_with_url.append([perm.name, url]) - return items_with_url - - def get_html_msg(self) -> dict: - items = self.get_items_with_url() - subject = _('Application permissions is about to expire') - context = { - 'name': self.user.name, - 'count': self.day_count, - 'item_type': _('application permissions of organization {}').format(self.org), - 'items_with_url': items - } - message = render_to_string('perms/_msg_item_permissions_expire.html', context) - return { - 'subject': subject, - 'message': message - } - - @classmethod - def gen_test_msg(cls): - from users.models import User - from perms.models import ApplicationPermission - from orgs.models import Organization - - user = User.objects.first() - perms = ApplicationPermission.objects.all()[:10] - org = Organization.objects.first() - return cls(user, perms, org) diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/endpoint.py index 5e4c7542d..225b3300a 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/endpoint.py @@ -6,7 +6,6 @@ from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from assets.models import Asset from orgs.utils import tmp_to_root_org -from applications.models import Application from terminal.models import Session from common.permissions import IsValidUser from ..models import Endpoint, EndpointRule @@ -52,21 +51,16 @@ class SmartEndpointViewMixin: @staticmethod def get_target_instance(request): asset_id = request.GET.get('asset_id') - app_id = request.GET.get('app_id') session_id = request.GET.get('session_id') token_id = request.GET.get('token') if token_id: from authentication.models import ConnectionToken token = ConnectionToken.objects.filter(id=token_id).first() - if token: - if token.asset: - asset_id = token.asset.id - elif token.application: - app_id = token.application.id + if token and token.asset: + asset_id = token.asset.id + if asset_id: pk, model = asset_id, Asset - elif app_id: - pk, model = app_id, Application elif session_id: pk, model = session_id, Session else: diff --git a/apps/terminal/models/session.py b/apps/terminal/models/session.py index 0e946fba4..932f47321 100644 --- a/apps/terminal/models/session.py +++ b/apps/terminal/models/session.py @@ -12,7 +12,6 @@ from django.core.cache import cache from assets.models import Asset from assets.const import Protocol -from applications.models import Application from users.models import User from orgs.mixins.models import OrgModelMixin from django.db.models import TextChoices diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 436c57f90..9933a5401 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -15,7 +15,7 @@ from tickets import serializers from tickets import filters from tickets.permissions.ticket import IsAssignee, IsApplicant from tickets.models import ( - Ticket, ApplyAssetTicket, ApplyApplicationTicket, + Ticket, ApplyAssetTicket, ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket ) diff --git a/apps/tickets/filters.py b/apps/tickets/filters.py index bf20014cc..536f06b56 100644 --- a/apps/tickets/filters.py +++ b/apps/tickets/filters.py @@ -4,7 +4,7 @@ from django.db.models import Subquery, OuterRef from common.drf.filters import BaseFilterSet from tickets.models import ( - Ticket, TicketStep, ApplyAssetTicket, ApplyApplicationTicket, + Ticket, TicketStep, ApplyAssetTicket, ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket ) @@ -34,12 +34,6 @@ class ApplyAssetTicketFilter(BaseFilterSet): fields = ('id',) -class ApplyApplicationTicketFilter(BaseFilterSet): - class Meta: - model = ApplyApplicationTicket - fields = ('id',) - - class ApplyLoginTicketFilter(BaseFilterSet): class Meta: model = ApplyLoginTicket diff --git a/apps/tickets/migrations/0019_delete_applyapplicationticket.py b/apps/tickets/migrations/0019_delete_applyapplicationticket.py new file mode 100644 index 000000000..67586593d --- /dev/null +++ b/apps/tickets/migrations/0019_delete_applyapplicationticket.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.14 on 2022-08-16 08:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0018_auto_20220728_1125'), + ] + + operations = [ + migrations.DeleteModel( + name='ApplyApplicationTicket', + ), + ] diff --git a/apps/tickets/models/ticket/__init__.py b/apps/tickets/models/ticket/__init__.py index c13cea9b1..89592be82 100644 --- a/apps/tickets/models/ticket/__init__.py +++ b/apps/tickets/models/ticket/__init__.py @@ -1,6 +1,6 @@ from .general import * from .apply_asset import * -from .apply_application import * +# from .apply_application import * from .command_confirm import * from .login_asset_confirm import * from .login_confirm import * diff --git a/apps/tickets/views/approve.py b/apps/tickets/views/approve.py index f5f20e3d9..d742437f8 100644 --- a/apps/tickets/views/approve.py +++ b/apps/tickets/views/approve.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _ from orgs.utils import tmp_to_root_org from tickets.models import ( - Ticket, ApplyAssetTicket, ApplyApplicationTicket, + Ticket, ApplyAssetTicket, ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket ) from tickets.const import TicketType @@ -27,7 +27,6 @@ class TicketDirectApproveView(TemplateView): TICKET_SUB_MODEL_MAP = { TicketType.apply_asset: ApplyAssetTicket, - TicketType.apply_application: ApplyApplicationTicket, TicketType.login_confirm: ApplyLoginTicket, TicketType.login_asset_confirm: ApplyLoginAssetTicket, TicketType.command_confirm: ApplyCommandTicket, From 3f47e63080e3a4d5f0c9859fa1f9e8c7c674493f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 17 Aug 2022 11:54:18 +0800 Subject: [PATCH 060/488] perf: remove system user --- apps/acls/serializers/login_asset_acl.py | 9 - apps/acls/serializers/login_asset_check.py | 53 +++-- apps/applications/const.py | 85 ------- .../migrations/0024_auto_20220816_1629.py | 59 ----- apps/applications/models/__init__.py | 2 - apps/applications/models/account.py | 117 ---------- apps/applications/models/application.py | 97 -------- apps/applications/models/database.py | 13 -- apps/applications/models/remote_app.py | 0 apps/applications/models/tree.py | 207 ------------------ .../migrations/0106_auto_20220811_1358.py | 13 -- .../migrations/0111_auto_20220816_1022.py | 4 +- apps/assets/models/__init__.py | 4 +- apps/assets/models/utils.py | 16 +- apps/audits/signal_handlers.py | 2 +- apps/authentication/api/connection_token.py | 2 +- .../migrations/0012_auto_20220816_1629.py | 8 +- .../serializers/connection_token.py | 13 +- apps/ops/models/adhoc.py | 1 - apps/ops/models/command.py | 1 + apps/orgs/api.py | 4 +- apps/orgs/caches.py | 8 +- apps/orgs/signal_handlers/cache.py | 3 +- apps/orgs/signal_handlers/common.py | 3 +- apps/perms/api/__init__.py | 1 - .../perms/api/asset/user_permission/common.py | 33 +-- apps/perms/api/system_user_permission.py | 21 -- apps/perms/filters.py | 18 +- apps/perms/hands.py | 4 +- .../migrations/0030_auto_20220816_1132.py | 4 - apps/perms/models/__init__.py | 2 - apps/perms/models/asset_permission.py | 4 +- apps/perms/models/base.py | 160 -------------- apps/perms/serializers/__init__.py | 1 - apps/perms/serializers/asset/permission.py | 3 +- .../serializers/asset/user_permission.py | 54 +---- .../serializers/system_user_permission.py | 19 -- apps/perms/urls/asset_permission.py | 5 - apps/perms/utils/asset/permission.py | 4 +- .../models/ticket/apply_application.py | 34 --- apps/tickets/models/ticket/apply_asset.py | 1 + apps/tickets/models/ticket/command_confirm.py | 1 + .../models/ticket/login_asset_confirm.py | 1 + apps/tickets/serializers/ticket/common.py | 3 +- utils/generate_fake_data/resources/assets.py | 40 ---- utils/generate_fake_data/resources/perms.py | 1 - 46 files changed, 67 insertions(+), 1071 deletions(-) delete mode 100644 apps/applications/const.py delete mode 100644 apps/applications/migrations/0024_auto_20220816_1629.py delete mode 100644 apps/applications/models/__init__.py delete mode 100644 apps/applications/models/account.py delete mode 100644 apps/applications/models/application.py delete mode 100644 apps/applications/models/database.py delete mode 100644 apps/applications/models/remote_app.py delete mode 100644 apps/applications/models/tree.py delete mode 100644 apps/perms/api/system_user_permission.py delete mode 100644 apps/perms/models/base.py delete mode 100644 apps/perms/serializers/system_user_permission.py delete mode 100644 apps/tickets/models/ticket/apply_application.py diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index 34d6711c8..b30876f27 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.models import Organization -from assets.models import SystemUser from assets.const import Protocol from acls import models @@ -60,14 +59,6 @@ class LoginAssetACLSystemUsersSerializer(serializers.Serializer): ) ) - @staticmethod - def validate_protocol_group(protocol_group): - unsupported_protocols = set(protocol_group) - set(SystemUser.ASSET_CATEGORY_PROTOCOLS + ['*']) - if unsupported_protocols: - error = _('Unsupported protocols: {}').format(unsupported_protocols) - raise serializers.ValidationError(error) - return protocol_group - class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): users = LoginAssetACLUsersSerializer() diff --git a/apps/acls/serializers/login_asset_check.py b/apps/acls/serializers/login_asset_check.py index ec7a7c35c..2c52506d5 100644 --- a/apps/acls/serializers/login_asset_check.py +++ b/apps/acls/serializers/login_asset_check.py @@ -1,9 +1,8 @@ -from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from orgs.utils import tmp_to_root_org from common.utils import get_object_or_none, lazyproperty from users.models import User -from assets.models import Asset, SystemUser +from assets.models import Asset __all__ = ['LoginAssetCheckSerializer'] @@ -30,21 +29,21 @@ class LoginAssetCheckSerializer(serializers.Serializer): self.asset = self.validate_object_exist(Asset, asset_id) return asset_id - def validate_system_user_id(self, system_user_id): - self._system_user = self.validate_object_exist(SystemUser, system_user_id) - return system_user_id - - def validate_system_user_username(self, system_user_username): - system_user_id = self.initial_data.get('system_user_id') - system_user = self.validate_object_exist(SystemUser, system_user_id) - if self._system_user.login_mode == SystemUser.LOGIN_MANUAL \ - and not system_user.username \ - and not system_user.username_same_with_user \ - and not system_user_username: - error = 'Missing parameter: system_user_username' - raise serializers.ValidationError(error) - self._system_user_username = system_user_username - return system_user_username + # def validate_system_user_id(self, system_user_id): + # self._system_user = self.validate_object_exist(SystemUser, system_user_id) + # return system_user_id + # + # def validate_system_user_username(self, system_user_username): + # system_user_id = self.initial_data.get('system_user_id') + # system_user = self.validate_object_exist(SystemUser, system_user_id) + # if self._system_user.login_mode == SystemUser.LOGIN_MANUAL \ + # and not system_user.username \ + # and not system_user.username_same_with_user \ + # and not system_user_username: + # error = 'Missing parameter: system_user_username' + # raise serializers.ValidationError(error) + # self._system_user_username = system_user_username + # return system_user_username @staticmethod def validate_object_exist(model, field_id): @@ -55,16 +54,16 @@ class LoginAssetCheckSerializer(serializers.Serializer): raise serializers.ValidationError(error) return obj - @lazyproperty - def system_user(self): - if self._system_user.username_same_with_user: - username = self.user.username - elif self._system_user.login_mode == SystemUser.LOGIN_MANUAL: - username = self._system_user_username - else: - username = self._system_user.username - self._system_user.username = username - return self._system_user + # @lazyproperty + # def system_user(self): + # if self._system_user.username_same_with_user: + # username = self.user.username + # elif self._system_user.login_mode == SystemUser.LOGIN_MANUAL: + # username = self._system_user_username + # else: + # username = self._system_user.username + # self._system_user.username = username + # return self._system_user @lazyproperty def org(self): diff --git a/apps/applications/const.py b/apps/applications/const.py deleted file mode 100644 index 313477c25..000000000 --- a/apps/applications/const.py +++ /dev/null @@ -1,85 +0,0 @@ -# coding: utf-8 -# -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -class AppCategory(models.TextChoices): - db = 'db', _('Database') - remote_app = 'remote_app', _('Remote app') - cloud = 'cloud', 'Cloud' - - @classmethod - def get_label(cls, category): - return dict(cls.choices).get(category, '') - - @classmethod - def is_xpack(cls, category): - return category in ['remote_app'] - - -class AppType(models.TextChoices): - # db category - mysql = 'mysql', 'MySQL' - mariadb = 'mariadb', 'MariaDB' - oracle = 'oracle', 'Oracle' - pgsql = 'postgresql', 'PostgreSQL' - sqlserver = 'sqlserver', 'SQLServer' - redis = 'redis', 'Redis' - mongodb = 'mongodb', 'MongoDB' - - # remote-app category - chrome = 'chrome', 'Chrome' - mysql_workbench = 'mysql_workbench', 'MySQL Workbench' - vmware_client = 'vmware_client', 'vSphere Client' - custom = 'custom', _('Custom') - - # cloud category - k8s = 'k8s', 'Kubernetes' - - @classmethod - def category_types_mapper(cls): - return { - AppCategory.db: [ - cls.mysql, cls.mariadb, cls.oracle, cls.pgsql, - cls.sqlserver, cls.redis, cls.mongodb - ], - AppCategory.remote_app: [ - cls.chrome, cls.mysql_workbench, - cls.vmware_client, cls.custom - ], - AppCategory.cloud: [cls.k8s] - } - - @classmethod - def type_category_mapper(cls): - mapper = {} - for category, tps in cls.category_types_mapper().items(): - for tp in tps: - mapper[tp] = category - return mapper - - @classmethod - def get_label(cls, tp): - return dict(cls.choices).get(tp, '') - - @classmethod - def db_types(cls): - return [tp.value for tp in cls.category_types_mapper()[AppCategory.db]] - - @classmethod - def remote_app_types(cls): - return [tp.value for tp in cls.category_types_mapper()[AppCategory.remote_app]] - - @classmethod - def cloud_types(cls): - return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]] - - @classmethod - def is_xpack(cls, tp): - tp_category_mapper = cls.type_category_mapper() - category = tp_category_mapper[tp] - - if AppCategory.is_xpack(category): - return True - return tp in ['oracle', 'postgresql', 'sqlserver'] diff --git a/apps/applications/migrations/0024_auto_20220816_1629.py b/apps/applications/migrations/0024_auto_20220816_1629.py deleted file mode 100644 index 4e8f88cff..000000000 --- a/apps/applications/migrations/0024_auto_20220816_1629.py +++ /dev/null @@ -1,59 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-16 08:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tickets', '0019_delete_applyapplicationticket'), - ('authentication', '0012_auto_20220816_1629'), - ('applications', '0023_auto_20220816_1021'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='account', - unique_together=None, - ), - migrations.RemoveField( - model_name='account', - name='app', - ), - migrations.RemoveField( - model_name='account', - name='systemuser', - ), - migrations.AlterUniqueTogether( - name='application', - unique_together=None, - ), - migrations.RemoveField( - model_name='application', - name='domain', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='app', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='history_user', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='systemuser', - ), - migrations.DeleteModel( - name='ApplicationUser', - ), - migrations.DeleteModel( - name='Account', - ), - migrations.DeleteModel( - name='Application', - ), - migrations.DeleteModel( - name='HistoricalAccount', - ), - ] diff --git a/apps/applications/models/__init__.py b/apps/applications/models/__init__.py deleted file mode 100644 index 268bbae0d..000000000 --- a/apps/applications/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# from .application import * -# from .account import * diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py deleted file mode 100644 index 3828b5995..000000000 --- a/apps/applications/models/account.py +++ /dev/null @@ -1,117 +0,0 @@ -from django.db import models -from simple_history.models import HistoricalRecords -from django.db.models import F -from django.utils.translation import ugettext_lazy as _ - -from common.utils import lazyproperty -from assets.models.base import BaseAccount -from assets.models import SystemUser - - -class Account(BaseAccount): - app = models.ForeignKey( - 'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Application') - ) - systemuser = models.ForeignKey( - 'assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user") - ) - version = models.IntegerField(default=1, verbose_name=_('Version')) - history = HistoricalRecords() - - auth_attrs = ['username', 'password', 'private_key', 'public_key'] - - class Meta: - verbose_name = _('Application account') - unique_together = [('username', 'app', 'systemuser')] - permissions = [ - ('view_applicationaccountsecret', _('Can view application account secret')), - ('change_appplicationaccountsecret', _('Can change application account secret')), - ] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.auth_snapshot = {} - - def get_or_systemuser_attr(self, attr): - val = getattr(self, attr, None) - if val: - return val - if self.systemuser: - return getattr(self.systemuser, attr, '') - return '' - - def load_auth(self): - for attr in self.auth_attrs: - value = self.get_or_systemuser_attr(attr) - self.auth_snapshot[attr] = [getattr(self, attr), value] - setattr(self, attr, value) - - def unload_auth(self): - if not self.systemuser: - return - - for attr, values in self.auth_snapshot.items(): - origin_value, loaded_value = values - current_value = getattr(self, attr, '') - if current_value == loaded_value: - setattr(self, attr, origin_value) - - def save(self, *args, **kwargs): - self.unload_auth() - instance = super().save(*args, **kwargs) - self.load_auth() - return instance - - @lazyproperty - def category(self): - return self.app.category - - @lazyproperty - def type(self): - return self.app.type - - @lazyproperty - def attrs(self): - return self.app.attrs - - @lazyproperty - def app_display(self): - return self.systemuser.name - - @property - def username_display(self): - return self.get_or_systemuser_attr('username') or '' - - @lazyproperty - def systemuser_display(self): - if not self.systemuser: - return '' - return str(self.systemuser) - - @property - def smart_name(self): - username = self.username_display - - if self.app: - app = str(self.app) - else: - app = '*' - return '{}@{}'.format(username, app) - - @classmethod - def get_queryset(cls): - queryset = cls.objects.all() \ - .annotate(type=F('app__type')) \ - .annotate(app_display=F('app__name')) \ - .annotate(systemuser_display=F('systemuser__name')) \ - .annotate(category=F('app__category')) - return queryset - - def __str__(self): - return self.smart_name - - -class ApplicationUser(SystemUser): - class Meta: - proxy = True - verbose_name = _('Application user') diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py deleted file mode 100644 index ca45b043a..000000000 --- a/apps/applications/models/application.py +++ /dev/null @@ -1,97 +0,0 @@ -from django.db import models -from django.utils.translation import ugettext_lazy as _ - -from orgs.mixins.models import OrgModelMixin -from common.mixins import CommonModelMixin -from common.utils import is_uuid -from assets.models import Asset - -from .. import const -from .tree import ApplicationTreeNodeMixin - - -class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): - name = models.CharField(max_length=128, verbose_name=_('Name')) - category = models.CharField( - max_length=16, choices=const.AppCategory.choices, verbose_name=_('Category') - ) - type = models.CharField( - max_length=16, choices=const.AppType.choices, verbose_name=_('Type') - ) - domain = models.ForeignKey( - 'assets.Domain', null=True, blank=True, related_name='applications', - on_delete=models.SET_NULL, verbose_name=_("Domain"), - ) - attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) - comment = models.TextField( - max_length=128, default='', blank=True, verbose_name=_('Comment') - ) - - class Meta: - verbose_name = _('Application') - unique_together = [('org_id', 'name')] - ordering = ('name',) - permissions = [ - ('match_application', _('Can match application')), - ] - - def __str__(self): - category_display = self.get_category_display() - type_display = self.get_type_display() - return f'{self.name}({type_display})[{category_display}]' - - @property - def category_remote_app(self): - return self.category == const.AppCategory.remote_app.value - - @property - def category_cloud(self): - return self.category == const.AppCategory.cloud.value - - @property - def category_db(self): - return self.category == const.AppCategory.db.value - - def get_rdp_remote_app_setting(self): - from applications.serializers.attrs import get_serializer_class_by_application_type - if not self.category_remote_app: - raise ValueError(f"Not a remote app application: {self.name}") - serializer_class = get_serializer_class_by_application_type(self.type) - fields = serializer_class().get_fields() - - parameters = [self.type] - for field_name in list(fields.keys()): - if field_name in ['asset']: - continue - value = self.attrs.get(field_name) - if not value: - continue - if field_name == 'path': - value = '\"%s\"' % value - parameters.append(str(value)) - - parameters = ' '.join(parameters) - return { - 'program': '||jmservisor', - 'working_directory': '', - 'parameters': parameters - } - - def get_remote_app_asset(self, raise_exception=True): - asset_id = self.attrs.get('asset') - if is_uuid(asset_id): - return Asset.objects.filter(id=asset_id).first() - if raise_exception: - raise ValueError("Remote App not has asset attr") - - def get_target_ip(self): - target_ip = '' - if self.category_remote_app: - asset = self.get_remote_app_asset() - target_ip = asset.ip if asset else target_ip - elif self.category_cloud: - target_ip = self.attrs.get('cluster') - elif self.category_db: - target_ip = self.attrs.get('host') - return target_ip - diff --git a/apps/applications/models/database.py b/apps/applications/models/database.py deleted file mode 100644 index f964dd737..000000000 --- a/apps/applications/models/database.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - -from .application import Application - - -class Database(Application): - host = models.CharField(max_length=1024, verbose_name=_('Host')) - port = models.IntegerField(verbose_name=_("Port")) - database = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_("Database")) - - class Meta: - verbose_name = _("Database") diff --git a/apps/applications/models/remote_app.py b/apps/applications/models/remote_app.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/applications/models/tree.py b/apps/applications/models/tree.py deleted file mode 100644 index cd3c43717..000000000 --- a/apps/applications/models/tree.py +++ /dev/null @@ -1,207 +0,0 @@ -from collections import defaultdict -from urllib.parse import urlencode, parse_qsl - -from django.utils.translation import ugettext_lazy as _ -from django.conf import settings - -from common.tree import TreeNode -from .. import const - - -class ApplicationTreeNodeMixin: - id: str - name: str - type: str - category: str - attrs: dict - - @staticmethod - def create_tree_id(pid, type, v): - i = dict(parse_qsl(pid)) - i[type] = v - tree_id = urlencode(i) - return tree_id - - @classmethod - def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None, - show_empty=True, show_count=True): - count = counts.get(c.value, 0) - if count == 0 and not show_empty: - return None - label = c.label - if count is not None and show_count: - label = '{} ({})'.format(label, count) - data = { - 'id': id_, - 'name': label, - 'title': label, - 'pId': pid, - 'isParent': bool(count), - 'open': opened, - 'iconSkin': '', - 'meta': { - 'type': tp, - 'data': { - 'name': c.name, - 'value': c.value - } - } - } - return TreeNode(**data) - - @classmethod - def create_root_tree_node(cls, queryset, show_count=True): - count = queryset.count() if show_count else None - root_id = 'applications' - root_name = _('Applications') - if count is not None and show_count: - root_name = '{} ({})'.format(root_name, count) - node = TreeNode(**{ - 'id': root_id, - 'name': root_name, - 'title': root_name, - 'pId': '', - 'isParent': True, - 'open': True, - 'iconSkin': '', - 'meta': { - 'type': 'applications_root', - } - }) - return node - - @classmethod - def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True): - nodes = [] - categories = const.AppType.category_types_mapper().keys() - for category in categories: - if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category): - continue - i = cls.create_tree_id(pid, 'category', category.value) - node = cls.create_choice_node( - category, i, pid=pid, tp='category', - counts=counts, opened=False, show_empty=show_empty, - show_count=show_count - ) - if not node: - continue - nodes.append(node) - return nodes - - @classmethod - def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True): - nodes = [] - temp_pid = pid - type_category_mapper = const.AppType.type_category_mapper() - types = const.AppType.type_category_mapper().keys() - - for tp in types: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp): - continue - category = type_category_mapper.get(tp) - pid = cls.create_tree_id(pid, 'category', category.value) - i = cls.create_tree_id(pid, 'type', tp.value) - node = cls.create_choice_node( - tp, i, pid, tp='type', counts=counts, opened=False, - show_empty=show_empty, show_count=show_count - ) - pid = temp_pid - if not node: - continue - nodes.append(node) - return nodes - - @staticmethod - def get_tree_node_counts(queryset): - counts = defaultdict(int) - values = queryset.values_list('type', 'category') - for i in values: - tp = i[0] - category = i[1] - counts[tp] += 1 - counts[category] += 1 - return counts - - @classmethod - def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True): - counts = cls.get_tree_node_counts(queryset) - tree_nodes = [] - - # 类别的节点 - tree_nodes += cls.create_category_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - - # 类型的节点 - tree_nodes += cls.create_types_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - return tree_nodes - - @classmethod - def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): - tree_nodes = [] - - # 根节点有可能是组织名称 - if root_node is None: - root_node = cls.create_root_tree_node(queryset, show_count=show_count) - tree_nodes.append(root_node) - - tree_nodes += cls.create_category_type_tree_nodes( - queryset, root_node.id, show_empty=show_empty, show_count=show_count - ) - - # 应用的节点 - for app in queryset: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type): - continue - node = app.as_tree_node(root_node.id) - tree_nodes.append(node) - return tree_nodes - - def create_app_tree_pid(self, root_id): - pid = self.create_tree_id(root_id, 'category', self.category) - pid = self.create_tree_id(pid, 'type', self.type) - return pid - - def as_tree_node(self, pid, k8s_as_tree=False): - if self.type == const.AppType.k8s and k8s_as_tree: - node = KubernetesTree(pid).as_tree_node(self) - else: - node = self._as_tree_node(pid) - return node - - def _attrs_to_tree(self): - if self.category == const.AppCategory.db: - return self.attrs - return {} - - def _as_tree_node(self, pid): - icon_skin_category_mapper = { - 'remote_app': 'chrome', - 'db': 'database', - 'cloud': 'cloud' - } - icon_skin = icon_skin_category_mapper.get(self.category, 'file') - pid = self.create_app_tree_pid(pid) - node = TreeNode(**{ - 'id': str(self.id), - 'name': self.name, - 'title': self.name, - 'pId': pid, - 'isParent': False, - 'open': False, - 'iconSkin': icon_skin, - 'meta': { - 'type': 'application', - 'data': { - 'category': self.category, - 'type': self.type, - 'attrs': self._attrs_to_tree() - } - } - }) - return node - diff --git a/apps/assets/migrations/0106_auto_20220811_1358.py b/apps/assets/migrations/0106_auto_20220811_1358.py index 640dd8adb..63b7509dd 100644 --- a/apps/assets/migrations/0106_auto_20220811_1358.py +++ b/apps/assets/migrations/0106_auto_20220811_1358.py @@ -11,19 +11,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='AccountTemplate', - fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ], - options={ - 'abstract': False, - }, - ), migrations.RemoveField( model_name='account', name='protocol', diff --git a/apps/assets/migrations/0111_auto_20220816_1022.py b/apps/assets/migrations/0111_auto_20220816_1022.py index 296aaa99a..55c286ce5 100644 --- a/apps/assets/migrations/0111_auto_20220816_1022.py +++ b/apps/assets/migrations/0111_auto_20220816_1022.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.14 on 2022-08-16 02:22 import time from django.db import migrations, models +from django.db.models import Count def migrate_command_filter_to_assets(apps, schema_editor): @@ -50,9 +51,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.DeleteModel( - name='AccountTemplate', - ), migrations.AddField( model_name='commandfilter', name='accounts', diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index f257d6e8d..d81856923 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,6 +1,6 @@ from .base import * from .platform import * -from ._user import * +# from ._user import * from .asset import * from .label import Label from .group import * @@ -12,7 +12,7 @@ from .favorite_asset import * from .account import * from .backup import * # 废弃以下 -from ._authbook import * +# from ._authbook import * from .protocol import * from .cmd_filter import * diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index 90b0ee178..bc6bdb21f 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -11,24 +11,10 @@ from common.utils import validate_ssh_private_key __all__ = [ - 'init_model', 'generate_fake', 'private_key_validator', + 'private_key_validator', ] -def init_model(): - from . import SystemUser, AdminUser, Asset - for cls in [SystemUser, AdminUser, Asset]: - if hasattr(cls, 'initial'): - cls.initial() - - -def generate_fake(): - from . import SystemUser, AdminUser, Asset - for cls in [SystemUser, AdminUser, Asset]: - if hasattr(cls, 'generate_fake'): - cls.generate_fake() - - def private_key_validator(value): if not validate_ssh_private_key(value): raise ValidationError( diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 9173a6d9f..206b5e5f8 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -16,7 +16,7 @@ from django.utils import translation from rest_framework.renderers import JSONRenderer from rest_framework.request import Request -from assets.models import Asset, SystemUser +from assets.models import Asset from authentication.signals import post_auth_failed, post_auth_success from authentication.utils import check_different_city_login_if_need from jumpserver.utils import current_request diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 7b7a17aed..ce84cb74e 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -13,7 +13,7 @@ from rest_framework.request import Request from common.drf.api import JMSModelViewSet from common.http import is_true from orgs.mixins.api import RootOrgViewMixin -from perms.models.base import Action +from perms.models import Action from terminal.models import EndpointRule from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer, diff --git a/apps/authentication/migrations/0012_auto_20220816_1629.py b/apps/authentication/migrations/0012_auto_20220816_1629.py index 1f76da99c..dac9d1302 100644 --- a/apps/authentication/migrations/0012_auto_20220816_1629.py +++ b/apps/authentication/migrations/0012_auto_20220816_1629.py @@ -3,14 +3,14 @@ from django.db import migrations, models -def migrate_system_user_to_accounts(apps, schema_editor): - connection_token_model = apps.get_model("perms", "ConnectionToken") +def migrate_system_user_to_account(apps, schema_editor): + connection_token_model = apps.get_model("authentication", "ConnectionToken") count = 0 bulk_size = 10000 while True: connection_tokens = connection_token_model.objects \ - .prefect_related('system_users')[count:bulk_size] + .prefetch_related('system_users')[count:bulk_size] if not connection_tokens: break count += len(connection_tokens) @@ -45,7 +45,7 @@ class Migration(migrations.Migration): name='account', field=models.CharField(default='', max_length=128, verbose_name='Account'), ), - migrations.RunPython(migrate_system_user_to_accounts), + migrations.RunPython(migrate_system_user_to_account), migrations.RemoveField( model_name='connectiontoken', name='system_user', diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 284c35663..acbce801e 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -5,7 +5,7 @@ from orgs.mixins.serializers import OrgResourceModelSerializerMixin from authentication.models import ConnectionToken from common.utils import pretty_string from common.utils.random import random_string -from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule +from assets.models import Asset, Gateway, Domain, CommandFilterRule from users.models import User from perms.serializers.base import ActionsField @@ -123,15 +123,6 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer): fields = ['id', 'name', 'ip', 'protocols', 'org_id'] -class ConnectionTokenSystemUserSerializer(serializers.ModelSerializer): - class Meta: - model = SystemUser - fields = [ - 'id', 'name', 'username', 'password', 'private_key', - 'protocol', 'ad_domain', 'org_id' - ] - - class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): class Meta: model = Gateway @@ -165,7 +156,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) - system_user = ConnectionTokenSystemUserSerializer(read_only=True) + account = serializers.CharField(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) domain = ConnectionTokenDomainSerializer(read_only=True) cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 0d1b32ba5..1d2920206 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -157,7 +157,6 @@ class AdHoc(OrgModelMixin): hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host")) run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin')) run_as = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Username')) - run_system_user = models.ForeignKey('assets.SystemUser', null=True, on_delete=models.CASCADE) become = EncryptJsonDictCharField(max_length=1024, default='', blank=True, null=True, verbose_name=_("Become")) created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by')) date_created = models.DateTimeField(auto_now_add=True, db_index=True) diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 8df5005c4..0c1038f89 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -23,6 +23,7 @@ class CommandExecution(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) hosts = models.ManyToManyField('assets.Asset') run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE) + account = models.CharField(max_length=128, verbose_name=_('account')) command = models.TextField(verbose_name=_("Command")) _result = models.TextField(blank=True, null=True, verbose_name=_('Result')) user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True) diff --git a/apps/orgs/api.py b/apps/orgs/api.py index d61e137bc..16fde4d69 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -14,7 +14,7 @@ from .serializers import ( ) from users.models import User, UserGroup from assets.models import ( - Asset, Domain, SystemUser, Label, Node, Gateway, + Asset, Domain, Label, Node, Gateway, CommandFilter, CommandFilterRule, GatheredUser ) from perms.models import AssetPermission @@ -27,7 +27,7 @@ logger = get_logger(__file__) # 部分 org 相关的 model,需要清空这些数据之后才能删除该组织 org_related_models = [ - User, UserGroup, Asset, Label, Domain, Gateway, Node, SystemUser, Label, + User, UserGroup, Asset, Label, Domain, Gateway, Node, Label, CommandFilter, CommandFilterRule, GatheredUser, AssetPermission, ] diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index 33fc87f3e..e9e5bc700 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -6,7 +6,7 @@ from orgs.utils import current_org, tmp_to_org from common.cache import Cache, IntegerField from common.utils import get_logger from users.models import UserGroup, User -from assets.models import Node, SystemUser, Domain, Gateway, Asset +from assets.models import Node, Domain, Gateway, Asset from terminal.models import Session from perms.models import AssetPermission @@ -71,12 +71,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache): def get_current_org(self): return self.org - def compute_admin_users_amount(self): - return SystemUser.objects.filter(type=SystemUser.Type.admin).count() - - def compute_system_users_amount(self): - return SystemUser.objects.filter(type=SystemUser.Type.common).count() - def compute_users_amount(self): amount = User.get_org_users(self.org).count() return amount diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 6acf1ac7a..1e3343931 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -8,7 +8,7 @@ from users.models import UserGroup, User from users.signals import pre_user_leave_org from terminal.models import Session from rbac.models import OrgRoleBinding, SystemRoleBinding, RoleBinding -from assets.models import Asset, SystemUser, Domain, Gateway +from assets.models import Asset, Domain, Gateway from orgs.caches import OrgResourceStatisticsCache from orgs.utils import current_org from common.utils import get_logger @@ -77,7 +77,6 @@ class OrgResourceStatisticsRefreshUtil: AssetPermission: ['asset_perms_amount'], Gateway: ['gateways_amount'], Domain: ['domains_amount'], - SystemUser: ['system_users_amount', 'admin_users_amount'], Node: ['nodes_amount'], Asset: ['assets_amount'], UserGroup: ['groups_amount'], diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index bc0ac64cd..f2396231b 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -14,7 +14,6 @@ from orgs.models import Organization from orgs.hands import set_current_org, Node, get_current_org from perms.models import AssetPermission from users.models import UserGroup, User -from assets.models import SystemUser from common.const.signals import PRE_REMOVE, POST_REMOVE from common.decorator import on_transaction_commit from common.signals import django_ready @@ -135,7 +134,7 @@ def _clear_users_from_org(org, users): if not users: return - models = (AssetPermission, UserGroup, SystemUser) + models = (AssetPermission, UserGroup) for m in models: _remove_users(m, users, org) diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index e4983cd95..e5e3698dd 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -2,4 +2,3 @@ # from .asset import * -from .system_user_permission import * diff --git a/apps/perms/api/asset/user_permission/common.py b/apps/perms/api/asset/user_permission/common.py index 86bff2123..5e8e839e4 100644 --- a/apps/perms/api/asset/user_permission/common.py +++ b/apps/perms/api/asset/user_permission/common.py @@ -8,7 +8,7 @@ from django.utils.decorators import method_decorator from rest_framework.views import APIView, Response from rest_framework import status from rest_framework.generics import ( - ListAPIView, get_object_or_404, RetrieveAPIView, DestroyAPIView + ListAPIView, get_object_or_404, RetrieveAPIView ) from orgs.utils import tmp_to_root_org @@ -16,7 +16,7 @@ from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_ from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty -from perms.hands import User, Asset, SystemUser +from perms.hands import User, Asset from perms import serializers logger = get_logger(__name__) @@ -25,7 +25,6 @@ __all__ = [ 'UserGrantedAssetSystemUsersForAdminApi', 'ValidateUserAssetPermissionApi', 'GetUserAssetPermissionActionsApi', - 'UserAssetPermissionsCacheApi', 'MyGrantedAssetSystemUsersApi', ] @@ -45,19 +44,18 @@ class GetUserAssetPermissionActionsApi(RetrieveAPIView): def get_object(self): asset_id = self.request.query_params.get('asset_id', '') - system_id = self.request.query_params.get('system_user_id', '') + account = self.request.query_params.get('account', '') try: asset_id = uuid.UUID(asset_id) - system_id = uuid.UUID(system_id) except ValueError: return Response({'msg': False}, status=403) asset = get_object_or_404(Asset, id=asset_id) - system_user = get_object_or_404(SystemUser, id=system_id) system_users_actions = get_asset_system_user_ids_with_actions_by_user(self.get_user(), asset) - actions = system_users_actions.get(system_user.id) + # actions = system_users_actions.get(system_user.id) + actions = system_users_actions.get(account) return {"actions": actions} @@ -70,7 +68,7 @@ class ValidateUserAssetPermissionApi(APIView): def get(self, request, *args, **kwargs): user_id = self.request.query_params.get('user_id', '') asset_id = request.query_params.get('asset_id', '') - system_id = request.query_params.get('system_user_id', '') + account = request.query_params.get('account', '') action_name = request.query_params.get('action_name', '') data = { @@ -79,14 +77,13 @@ class ValidateUserAssetPermissionApi(APIView): 'actions': [] } - if not all((user_id, asset_id, system_id, action_name)): + if not all((user_id, asset_id, account, action_name)): return Response(data) user = User.objects.get(id=user_id) asset = Asset.objects.valid().get(id=asset_id) - system_user = SystemUser.objects.get(id=system_id) - has_perm, actions, expire_at = validate_permission(user, asset, system_user, action_name) + has_perm, actions, expire_at = validate_permission(user, asset, account, action_name) status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN data = { 'has_permission': has_perm, @@ -97,8 +94,6 @@ class ValidateUserAssetPermissionApi(APIView): class UserGrantedAssetSystemUsersForAdminApi(ListAPIView): - serializer_class = serializers.AssetSystemUserSerializer - only_fields = serializers.AssetSystemUserSerializer.Meta.only_fields rbac_perms = { 'list': 'perms.view_userassets' } @@ -117,13 +112,6 @@ class UserGrantedAssetSystemUsersForAdminApi(ListAPIView): def get_asset_system_user_ids_with_actions(self, asset): return get_asset_system_user_ids_with_actions_by_user(self.user, asset) - def get_queryset(self): - system_user_ids = self.system_users_with_actions.keys() - system_users = SystemUser.objects.filter(id__in=system_user_ids) \ - .only(*self.serializer_class.Meta.only_fields) \ - .order_by('name') - return system_users - def paginate_queryset(self, queryset): page = super().paginate_queryset(queryset) @@ -148,8 +136,3 @@ class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi): def user(self): return self.request.user - -# TODO 删除 -class UserAssetPermissionsCacheApi(DestroyAPIView): - def destroy(self, request, *args, **kwargs): - return Response(status=204) diff --git a/apps/perms/api/system_user_permission.py b/apps/perms/api/system_user_permission.py deleted file mode 100644 index 6d7569192..000000000 --- a/apps/perms/api/system_user_permission.py +++ /dev/null @@ -1,21 +0,0 @@ -from rest_framework import generics - -from assets.models import SystemUser -from common.permissions import IsValidUser -from perms.utils.asset.user_permission import get_user_all_asset_perm_ids -from .. import serializers - - -class SystemUserPermission(generics.ListAPIView): - permission_classes = (IsValidUser,) - serializer_class = serializers.SystemUserSerializer - - def get_queryset(self): - user = self.request.user - - asset_perm_ids = get_user_all_asset_perm_ids(user) - queryset = SystemUser.objects.filter( - granted_by_permissions__id__in=asset_perm_ids - ).distinct() - - return queryset diff --git a/apps/perms/filters.py b/apps/perms/filters.py index 1699ff4e7..ac23b691c 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -5,7 +5,7 @@ from common.db.models import UnionQuerySet from common.drf.filters import BaseFilterSet from common.utils import get_object_or_none from users.models import User, UserGroup -from assets.models import Node, Asset, SystemUser +from assets.models import Node, Asset from perms.models import AssetPermission @@ -30,7 +30,6 @@ class PermissionBaseFilter(BaseFilterSet): qs = super().qs qs = self.filter_valid(qs) qs = self.filter_user(qs) - qs = self.filter_system_user(qs) qs = self.filter_user_group(qs) return qs @@ -74,21 +73,6 @@ class PermissionBaseFilter(BaseFilterSet): ).distinct() return queryset - def filter_system_user(self, queryset): - system_user_id = self.get_query_param('system_user_id') - system_user_name = self.get_query_param('system_user') - - if system_user_id: - system_user = get_object_or_none(SystemUser, pk=system_user_id) - elif system_user_name: - system_user = get_object_or_none(SystemUser, name=system_user_name) - else: - return queryset - if not system_user: - return queryset.none() - queryset = queryset.filter(system_users=system_user) - return queryset - def filter_user_group(self, queryset): user_group_id = self.get_query_param('user_group_id') user_group_name = self.get_query_param('user_group') diff --git a/apps/perms/hands.py b/apps/perms/hands.py index 25902fddf..1537b7260 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -2,12 +2,12 @@ # from users.models import User, UserGroup -from assets.models import Asset, SystemUser, Node, Label, FavoriteAsset +from assets.models import Asset, Node, Label, FavoriteAsset from assets.serializers import NodeSerializer __all__ = [ 'User', 'UserGroup', - 'Asset', 'SystemUser', 'Node', 'Label', 'FavoriteAsset', + 'Asset', 'Node', 'Label', 'FavoriteAsset', 'NodeSerializer', ] diff --git a/apps/perms/migrations/0030_auto_20220816_1132.py b/apps/perms/migrations/0030_auto_20220816_1132.py index d80bc0dfc..348000823 100644 --- a/apps/perms/migrations/0030_auto_20220816_1132.py +++ b/apps/perms/migrations/0030_auto_20220816_1132.py @@ -1,8 +1,4 @@ # Generated by Django 3.2.14 on 2022-08-16 03:32 - - - -import time from django.db import migrations, models diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py index 37e04577d..0c7e25c70 100644 --- a/apps/perms/models/__init__.py +++ b/apps/perms/models/__init__.py @@ -2,5 +2,3 @@ # from .asset_permission import * -# from .application_permission import * -from .base import * diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 6d2401885..bf70e9b6f 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -13,7 +13,9 @@ from common.db.models import BaseCreateUpdateModel, BitOperationChoice, UnionQue __all__ = [ - 'AssetPermission', 'PermNode', 'UserAssetGrantedTreeNodeRelation', + 'AssetPermission', 'PermNode', + 'UserAssetGrantedTreeNodeRelation', + 'Action' ] # 使用场景 diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py deleted file mode 100644 index 729ce7df1..000000000 --- a/apps/perms/models/base.py +++ /dev/null @@ -1,160 +0,0 @@ -# coding: utf-8 -# - -# TODO: v3 delete 整个文件 - - -import uuid -from django.utils.translation import ugettext_lazy as _ -from django.db import models -from django.db.models import Q -from django.utils import timezone -from orgs.mixins.models import OrgModelMixin - -from common.db.models import UnionQuerySet, BitOperationChoice -from common.utils import date_expired_default, lazyproperty -from orgs.mixins.models import OrgManager - -__all__ = [ - 'BasePermission', 'BasePermissionQuerySet', 'Action' -] - - -class BasePermissionQuerySet(models.QuerySet): - def active(self): - return self.filter(is_active=True) - - def valid(self): - return self.active().filter(date_start__lt=timezone.now()) \ - .filter(date_expired__gt=timezone.now()) - - def inactive(self): - return self.filter(is_active=False) - - def invalid(self): - now = timezone.now() - q = (Q(is_active=False) | Q(date_start__gt=now) | Q(date_expired__lt=now)) - return self.filter(q) - - -class BasePermissionManager(OrgManager): - def valid(self): - return self.get_queryset().valid() - - -class Action(BitOperationChoice): - ALL = 0xff - - CONNECT = 0b1 - UPLOAD = 0b1 << 1 - DOWNLOAD = 0b1 << 2 - CLIPBOARD_COPY = 0b1 << 3 - CLIPBOARD_PASTE = 0b1 << 4 - UPDOWNLOAD = UPLOAD | DOWNLOAD - CLIPBOARD_COPY_PASTE = CLIPBOARD_COPY | CLIPBOARD_PASTE - - DB_CHOICES = ( - (ALL, _('All')), - (CONNECT, _('Connect')), - (UPLOAD, _('Upload file')), - (DOWNLOAD, _('Download file')), - (UPDOWNLOAD, _("Upload download")), - (CLIPBOARD_COPY, _('Clipboard copy')), - (CLIPBOARD_PASTE, _('Clipboard paste')), - (CLIPBOARD_COPY_PASTE, _('Clipboard copy paste')) - ) - - NAME_MAP = { - ALL: "all", - CONNECT: "connect", - UPLOAD: "upload_file", - DOWNLOAD: "download_file", - UPDOWNLOAD: "updownload", - CLIPBOARD_COPY: 'clipboard_copy', - CLIPBOARD_PASTE: 'clipboard_paste', - CLIPBOARD_COPY_PASTE: 'clipboard_copy_paste' - } - - NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} - CHOICES = [] - for i, j in DB_CHOICES: - CHOICES.append((NAME_MAP[i], j)) - - -class BasePermission(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_('Name')) - users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), related_name='%(class)ss') - user_groups = models.ManyToManyField( - 'users.UserGroup', blank=True, verbose_name=_("User group"), related_name='%(class)ss') - actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions")) - is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) - date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) - created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) - comment = models.TextField(verbose_name=_('Comment'), blank=True) - from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket')) - - objects = BasePermissionManager.from_queryset(BasePermissionQuerySet)() - - class Meta: - abstract = True - - def __str__(self): - return self.name - - @property - def id_str(self): - return str(self.id) - - @property - def is_expired(self): - if self.date_expired > timezone.now() > self.date_start: - return False - return True - - @property - def is_valid(self): - if not self.is_expired and self.is_active: - return True - return False - - @property - def all_users(self): - from users.models import User - - users_query = self._meta.get_field('users').related_query_name() - user_groups_query = self._meta.get_field('user_groups').related_query_name() - - users_q = Q(**{ - f'{users_query}': self - }) - - user_groups_q = Q(**{ - f'groups__{user_groups_query}': self - }) - - return User.objects.filter(users_q | user_groups_q).distinct() - - def get_all_users(self): - from users.models import User - user_ids = self.users.all().values_list('id', flat=True) - group_ids = self.user_groups.all().values_list('id', flat=True) - - user_ids = list(user_ids) - group_ids = list(group_ids) - - qs1 = User.objects.filter(id__in=user_ids).distinct() - qs2 = User.objects.filter(groups__id__in=group_ids).distinct() - - qs = UnionQuerySet(qs1, qs2) - return qs - - @lazyproperty - def users_amount(self): - return self.users.count() - - @lazyproperty - def user_groups_amount(self): - return self.user_groups.count() diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py index 5e26adc99..39f7912a3 100644 --- a/apps/perms/serializers/__init__.py +++ b/apps/perms/serializers/__init__.py @@ -2,4 +2,3 @@ # from .base import * from .asset import * -from .system_user_permission import * diff --git a/apps/perms/serializers/asset/permission.py b/apps/perms/serializers/asset/permission.py index 9476dfaa3..490b9e57a 100644 --- a/apps/perms/serializers/asset/permission.py +++ b/apps/perms/serializers/asset/permission.py @@ -5,9 +5,8 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from django.db.models import Q -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from perms.models import AssetPermission, Action -from assets.models import Asset, Node, SystemUser +from assets.models import Asset, Node from users.models import User, UserGroup from ..base import ActionsField, BasePermissionSerializer diff --git a/apps/perms/serializers/asset/user_permission.py b/apps/perms/serializers/asset/user_permission.py index ac42b97ba..f05834f1b 100644 --- a/apps/perms/serializers/asset/user_permission.py +++ b/apps/perms/serializers/asset/user_permission.py @@ -4,35 +4,16 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from assets.models import Node, SystemUser, Asset, Platform +from assets.models import Node, Asset, Platform from perms.serializers.base import ActionsField __all__ = [ 'NodeGrantedSerializer', 'AssetGrantedSerializer', - 'ActionsSerializer', 'AssetSystemUserSerializer', - 'RemoteAppSystemUserSerializer', - 'DatabaseAppSystemUserSerializer', - 'K8sAppSystemUserSerializer', + 'ActionsSerializer', ] -class AssetSystemUserSerializer(serializers.ModelSerializer): - """ - 查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少 - """ - actions = ActionsField(read_only=True) - - class Meta: - model = SystemUser - only_fields = ( - 'id', 'name', 'username', 'priority', 'protocol', 'login_mode', - 'sftp_root', 'username_same_with_user', 'su_enabled', 'su_from', - ) - fields = list(only_fields) + ["actions"] - read_only_fields = fields - - class AssetGrantedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 @@ -63,34 +44,3 @@ class NodeGrantedSerializer(serializers.ModelSerializer): class ActionsSerializer(serializers.Serializer): actions = ActionsField(read_only=True) - -# TODO: 删除 -class RemoteAppSystemUserSerializer(serializers.ModelSerializer): - class Meta: - model = SystemUser - only_fields = ( - 'id', 'name', 'username', 'priority', 'protocol', 'login_mode', - ) - fields = list(only_fields) - read_only_fields = fields - - -class DatabaseAppSystemUserSerializer(serializers.ModelSerializer): - class Meta: - model = SystemUser - only_fields = ( - 'id', 'name', 'username', 'priority', 'protocol', 'login_mode', - ) - fields = list(only_fields) - read_only_fields = fields - - -class K8sAppSystemUserSerializer(serializers.ModelSerializer): - class Meta: - model = SystemUser - only_fields = ( - 'id', 'name', 'username', 'priority', 'protocol', 'login_mode', - ) - fields = list(only_fields) - read_only_fields = fields - diff --git a/apps/perms/serializers/system_user_permission.py b/apps/perms/serializers/system_user_permission.py deleted file mode 100644 index def2516d6..000000000 --- a/apps/perms/serializers/system_user_permission.py +++ /dev/null @@ -1,19 +0,0 @@ -from rest_framework import serializers -from ..hands import SystemUser - -__all__ = [ - 'SystemUserSerializer', -] - - -class SystemUserSerializer(serializers.ModelSerializer): - class Meta: - model = SystemUser - fields = [ - 'id', 'name', 'username', 'protocol', - 'login_mode', 'login_mode_display', - 'priority', 'username_same_with_user', - 'auto_push_account', 'cmd_filters', 'sudo', 'shell', 'comment', - 'sftp_root', 'date_created', 'created_by' - ] - ref_name = 'PermedSystemUserSerializer' diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 17fb22990..a39f61a80 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -73,11 +73,6 @@ user_permission_urlpatterns = [ # Asset System users path('/assets//system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'), path('assets//system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), - - # TODO 要废弃 Expire user permission cache - path('/asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), - name='user-asset-permission-cache'), - path('asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), name='my-asset-permission-cache'), ] user_group_permission_urlpatterns = [ diff --git a/apps/perms/utils/asset/permission.py b/apps/perms/utils/asset/permission.py index 8b9991715..a6a68f6eb 100644 --- a/apps/perms/utils/asset/permission.py +++ b/apps/perms/utils/asset/permission.py @@ -5,7 +5,7 @@ from django.db.models import Q from common.utils import get_logger from perms.models import AssetPermission, Action -from perms.hands import Asset, User, UserGroup, SystemUser, Node +from perms.hands import Asset, User, UserGroup, Node from perms.utils.asset.user_permission import get_user_all_asset_perm_ids logger = get_logger(__file__) @@ -83,7 +83,7 @@ def get_asset_system_user_ids_with_actions_by_user(user: User, asset: Asset): return get_asset_system_user_ids_with_actions(asset_perm_ids, asset) -def has_asset_system_permission(user: User, asset: Asset, system_user: SystemUser): +def has_asset_system_permission(user: User, asset: Asset, account: str): systemuser_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset) actions = systemuser_actions_mapper.get(system_user.id, 0) if actions: diff --git a/apps/tickets/models/ticket/apply_application.py b/apps/tickets/models/ticket/apply_application.py deleted file mode 100644 index 6bd721677..000000000 --- a/apps/tickets/models/ticket/apply_application.py +++ /dev/null @@ -1,34 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - -from .general import Ticket -from applications.const import AppCategory, AppType - -__all__ = ['ApplyApplicationTicket'] - - -class ApplyApplicationTicket(Ticket): - apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name')) - # 申请信息 - apply_category = models.CharField( - max_length=16, choices=AppCategory.choices, verbose_name=_('Category') - ) - apply_type = models.CharField( - max_length=16, choices=AppType.choices, verbose_name=_('Type') - ) - apply_applications = models.ManyToManyField( - 'applications.Application', verbose_name=_('Apply applications'), - ) - apply_system_users = models.ManyToManyField( - 'assets.SystemUser', verbose_name=_('Apply system users'), - ) - apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) - apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) - - @property - def apply_category_display(self): - return AppCategory.get_label(self.apply_category) - - @property - def apply_type_display(self): - return AppType.get_label(self.apply_type) diff --git a/apps/tickets/models/ticket/apply_asset.py b/apps/tickets/models/ticket/apply_asset.py index c3759dc9a..1b358f5c1 100644 --- a/apps/tickets/models/ticket/apply_asset.py +++ b/apps/tickets/models/ticket/apply_asset.py @@ -17,6 +17,7 @@ class ApplyAssetTicket(Ticket): apply_system_users = models.ManyToManyField( 'assets.SystemUser', verbose_name=_('Apply system users') ) + apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts')) apply_actions = models.IntegerField( choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_('Actions') ) diff --git a/apps/tickets/models/ticket/command_confirm.py b/apps/tickets/models/ticket/command_confirm.py index eb6b838ea..b8c37ec54 100644 --- a/apps/tickets/models/ticket/command_confirm.py +++ b/apps/tickets/models/ticket/command_confirm.py @@ -14,6 +14,7 @@ class ApplyCommandTicket(Ticket): 'assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_('Run system user') ) + apply_run_account = models.CharField(max_length=128, verbose_name=_('Run account')) apply_run_command = models.CharField(max_length=4096, verbose_name=_('Run command')) apply_from_session = models.ForeignKey( 'terminal.Session', on_delete=models.SET_NULL, diff --git a/apps/tickets/models/ticket/login_asset_confirm.py b/apps/tickets/models/ticket/login_asset_confirm.py index 5e5c53a47..43130e052 100644 --- a/apps/tickets/models/ticket/login_asset_confirm.py +++ b/apps/tickets/models/ticket/login_asset_confirm.py @@ -19,3 +19,4 @@ class ApplyLoginAssetTicket(Ticket): 'assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_('Login system user'), ) + apply_login_account = models.CharField(max_length=128, verbose_name=_('Login account')) diff --git a/apps/tickets/serializers/ticket/common.py b/apps/tickets/serializers/ticket/common.py index 6957da3b0..8da30e458 100644 --- a/apps/tickets/serializers/ticket/common.py +++ b/apps/tickets/serializers/ticket/common.py @@ -3,7 +3,6 @@ from django.db.models import Model from django.utils.translation import ugettext as _ from rest_framework import serializers -from assets.models import SystemUser from orgs.utils import tmp_to_org from tickets.models import Ticket @@ -54,7 +53,7 @@ class BaseApplyAssetApplicationSerializer(serializers.Serializer): qs = model.objects.filter(id__in=ids, **kwargs).values_list('id', flat=True) return list(qs) - def validate_apply_system_users(self, system_users): + def validate_apply_account(self, system_users): if self.is_final_approval and not system_users: raise serializers.ValidationError(_('This field is required.')) return self.filter_many_to_many_field(SystemUser, system_users) diff --git a/utils/generate_fake_data/resources/assets.py b/utils/generate_fake_data/resources/assets.py index de97a27a9..e14df6932 100644 --- a/utils/generate_fake_data/resources/assets.py +++ b/utils/generate_fake_data/resources/assets.py @@ -5,45 +5,6 @@ import forgery_py from .base import FakeDataGenerator from assets.models import * -from assets.const import Protocol - - -class AdminUsersGenerator(FakeDataGenerator): - resource = 'admin_user' - - def do_generate(self, batch, batch_size): - admin_users = [] - for i in batch: - username = forgery_py.internet.user_name(True) - password = forgery_py.basic.password() - admin_users.append(AdminUser( - name=username.title(), - username=username, - password=password, - org_id=self.org.id, - created_by='Fake', - )) - AdminUser.objects.bulk_create(admin_users, ignore_conflicts=True) - - -class SystemUsersGenerator(FakeDataGenerator): - def do_generate(self, batch, batch_size): - system_users = [] - protocols = list(dict(Protocol.choices).keys()) - for i in batch: - username = forgery_py.internet.user_name(True) - protocol = random.choice(protocols) - name = username.title() - name = f'{name}-{protocol}' - system_users.append(SystemUser( - name=name, - username=username, - password=forgery_py.basic.password(), - protocol=protocol, - org_id=self.org.id, - created_by='Fake', - )) - SystemUser.objects.bulk_create(system_users, ignore_conflicts=True) class NodesGenerator(FakeDataGenerator): @@ -62,7 +23,6 @@ class AssetsGenerator(FakeDataGenerator): node_ids: list def pre_generate(self): - self.admin_user_ids = list(AdminUser.objects.all().values_list('id', flat=True)) self.node_ids = list(Node.objects.all().values_list('id', flat=True)) def set_assets_nodes(self, assets): diff --git a/utils/generate_fake_data/resources/perms.py b/utils/generate_fake_data/resources/perms.py index e3e866feb..d6a5248cb 100644 --- a/utils/generate_fake_data/resources/perms.py +++ b/utils/generate_fake_data/resources/perms.py @@ -19,7 +19,6 @@ class AssetPermissionGenerator(FakeDataGenerator): def pre_generate(self): self.node_ids = list(Node.objects.all().values_list('id', flat=True)) self.asset_ids = list(Asset.objects.all().values_list('id', flat=True)) - self.system_user_ids = list(SystemUser.objects.all().values_list('id', flat=True)) self.user_ids = list(User.objects.all().values_list('id', flat=True)) self.user_group_ids = list(UserGroup.objects.all().values_list('id', flat=True)) From 8282a6869a2facda24f623805d0551731c0c2828 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 17 Aug 2022 15:44:59 +0800 Subject: [PATCH 061/488] perf: remove system users --- .../migrations/0024_auto_20220817_1346.py | 57 +++++++++++++ .../migrations/0112_auto_20220817_1544.py | 61 ++++++++++++++ apps/ops/api/command.py | 1 - .../ops/migrations/0022_auto_20220817_1346.py | 47 +++++++++++ apps/ops/models/command.py | 3 +- .../migrations/0011_auto_20200721_1739.py | 2 +- apps/tickets/api/ticket.py | 4 +- .../migrations/0020_auto_20220817_1346.py | 81 +++++++++++++++++++ apps/tickets/models/ticket/apply_asset.py | 3 - apps/tickets/models/ticket/command_confirm.py | 6 +- .../models/ticket/login_asset_confirm.py | 6 +- 11 files changed, 253 insertions(+), 18 deletions(-) create mode 100644 apps/applications/migrations/0024_auto_20220817_1346.py create mode 100644 apps/assets/migrations/0112_auto_20220817_1544.py create mode 100644 apps/ops/migrations/0022_auto_20220817_1346.py create mode 100644 apps/tickets/migrations/0020_auto_20220817_1346.py diff --git a/apps/applications/migrations/0024_auto_20220817_1346.py b/apps/applications/migrations/0024_auto_20220817_1346.py new file mode 100644 index 000000000..a6d03a5ca --- /dev/null +++ b/apps/applications/migrations/0024_auto_20220817_1346.py @@ -0,0 +1,57 @@ +# Generated by Django 3.2.14 on 2022-08-17 05:46 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0023_auto_20220816_1021'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='account', + unique_together=None, + ), + migrations.RemoveField( + model_name='account', + name='app', + ), + migrations.RemoveField( + model_name='account', + name='systemuser', + ), + migrations.AlterUniqueTogether( + name='application', + unique_together=None, + ), + migrations.RemoveField( + model_name='application', + name='domain', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='app', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='history_user', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='systemuser', + ), + migrations.DeleteModel( + name='ApplicationUser', + ), + migrations.DeleteModel( + name='Account', + ), + migrations.DeleteModel( + name='Application', + ), + migrations.DeleteModel( + name='HistoricalAccount', + ), + ] diff --git a/apps/assets/migrations/0112_auto_20220817_1544.py b/apps/assets/migrations/0112_auto_20220817_1544.py new file mode 100644 index 000000000..d1ba8875b --- /dev/null +++ b/apps/assets/migrations/0112_auto_20220817_1544.py @@ -0,0 +1,61 @@ +# Generated by Django 3.2.14 on 2022-08-17 07:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0111_auto_20220816_1022'), + ] + + operations = [ + migrations.DeleteModel( + name='AdminUser', + ), + migrations.RemoveField( + model_name='historicalauthbook', + name='asset', + ), + migrations.RemoveField( + model_name='historicalauthbook', + name='history_user', + ), + migrations.RemoveField( + model_name='historicalauthbook', + name='systemuser', + ), + migrations.AlterUniqueTogether( + name='systemuser', + unique_together=None, + ), + migrations.RemoveField( + model_name='systemuser', + name='assets', + ), + migrations.RemoveField( + model_name='systemuser', + name='groups', + ), + migrations.RemoveField( + model_name='systemuser', + name='nodes', + ), + migrations.RemoveField( + model_name='systemuser', + name='su_from', + ), + migrations.RemoveField( + model_name='systemuser', + name='users', + ), + migrations.DeleteModel( + name='AuthBook', + ), + migrations.DeleteModel( + name='HistoricalAuthBook', + ), + migrations.DeleteModel( + name='SystemUser', + ), + ] diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py index 0d513cd9e..a129ec416 100644 --- a/apps/ops/api/command.py +++ b/apps/ops/api/command.py @@ -9,7 +9,6 @@ from django.conf import settings from assets.models import Asset, Node from orgs.mixins.api import RootOrgViewMixin -from common.permissions import IsValidUser from rbac.permissions import RBACPermission from ..models import CommandExecution from ..serializers import CommandExecutionSerializer diff --git a/apps/ops/migrations/0022_auto_20220817_1346.py b/apps/ops/migrations/0022_auto_20220817_1346.py new file mode 100644 index 000000000..b06c85a18 --- /dev/null +++ b/apps/ops/migrations/0022_auto_20220817_1346.py @@ -0,0 +1,47 @@ +# Generated by Django 3.2.14 on 2022-08-17 05:46 + +from django.db import migrations, models + + +def migrate_run_system_user_to_account(apps, schema_editor): + execution_model = apps.get_model('ops', 'CommandExecution') + count = 0 + bulk_size = 1000 + + while True: + executions = execution_model.objects.all().prefetch_related('run_as')[count:bulk_size] + if not executions: + break + count += len(executions) + updated = [] + for obj in executions: + run_as = obj.run_as + if not run_as: + continue + obj.account = run_as.username + updated.append(obj) + execution_model.objects.bulk_update(updated, ['account']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0021_auto_20211130_1037'), + ] + + operations = [ + migrations.RemoveField( + model_name='adhoc', + name='run_system_user', + ), + migrations.AddField( + model_name='commandexecution', + name='account', + field=models.CharField(default='', max_length=128, verbose_name='account'), + ), + migrations.RunPython(migrate_run_system_user_to_account), + migrations.RemoveField( + model_name='commandexecution', + name='run_as', + ), + ] diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 0c1038f89..14cb99081 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -22,8 +22,7 @@ from ..inventory import JMSInventory class CommandExecution(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) hosts = models.ManyToManyField('assets.Asset') - run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE) - account = models.CharField(max_length=128, verbose_name=_('account')) + account = models.CharField(max_length=128, default='', verbose_name=_('account')) command = models.TextField(verbose_name=_("Command")) _result = models.TextField(blank=True, null=True, verbose_name=_('Result')) user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True) diff --git a/apps/perms/migrations/0011_auto_20200721_1739.py b/apps/perms/migrations/0011_auto_20200721_1739.py index 352bd6b79..df8b46cde 100644 --- a/apps/perms/migrations/0011_auto_20200721_1739.py +++ b/apps/perms/migrations/0011_auto_20200721_1739.py @@ -3,7 +3,7 @@ from django.db import migrations, models from django.db.models import F -from ..models.base import Action +from perms.models import Action def migrate_asset_permission(apps, schema_editor): diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 9933a5401..369bc155d 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -15,8 +15,8 @@ from tickets import serializers from tickets import filters from tickets.permissions.ticket import IsAssignee, IsApplicant from tickets.models import ( - Ticket, ApplyAssetTicket, - ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket + Ticket, ApplyAssetTicket, ApplyLoginTicket, + ApplyLoginAssetTicket, ApplyCommandTicket ) __all__ = [ diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py new file mode 100644 index 000000000..76437f936 --- /dev/null +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -0,0 +1,81 @@ +# Generated by Django 3.2.14 on 2022-08-17 05:46 + +import time +from django.db import migrations, models + + +def migrate_system_to_account(apps, schema_editor): + apply_asset_ticket_model = apps.get_model('tickets', 'ApplyAssetTicket') + apply_command_ticket_model = apps.get_model('tickets', 'ApplyCommandTicket') + apply_login_asset_ticket_model = apps.get_model('tickets', 'ApplyLoginAssetTicket') + + model_system_user_account = ( + (apply_asset_ticket_model, 'apply_system_users', 'apply_accounts', True), + (apply_command_ticket_model, 'apply_run_system_user', 'apply_run_account', False), + (apply_login_asset_ticket_model, 'apply_login_system_user', 'apply_login_account', False), + ) + + for model, old_field, new_field, m2m in model_system_user_account: + print("Start migrate '{}' system user to account".format(model)) + count = 0 + bulk_size = 1000 + + while True: + start = time.time() + objects = model.objects.all().prefetch_related(old_field)[count:bulk_size] + if not objects: + break + count += len(objects) + + updated = [] + for obj in objects: + if m2m: + old_value = getattr(obj, old_field).all() + new_value = [s.username for s in old_value] + else: + old_value = getattr(obj, old_field) + new_value = old_value.username + setattr(obj, new_field, new_value) + updated.append(obj) + model.objects.bulk_update(updated, [new_field]) + print("Migrate account: {}-{} using: {:.2f}s".format( + count - bulk_size, count, time.time()-start + )) + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0019_delete_applyapplicationticket'), + ] + + operations = [ + migrations.AddField( + model_name='applyassetticket', + name='apply_accounts', + field=models.JSONField(default=list, verbose_name='Apply accounts'), + ), + migrations.AddField( + model_name='applycommandticket', + name='apply_run_account', + field=models.CharField(default='', max_length=128, verbose_name='Run account'), + ), + migrations.AddField( + model_name='applyloginassetticket', + name='apply_login_account', + field=models.CharField(default='', max_length=128, verbose_name='Login account'), + ), + migrations.RunPython(migrate_system_to_account), + migrations.RemoveField( + model_name='applyassetticket', + name='apply_system_users', + ), + migrations.RemoveField( + model_name='applycommandticket', + name='apply_run_system_user', + ), + migrations.RemoveField( + model_name='applyloginassetticket', + name='apply_login_system_user', + ), + ] diff --git a/apps/tickets/models/ticket/apply_asset.py b/apps/tickets/models/ticket/apply_asset.py index 1b358f5c1..c0ec46cc0 100644 --- a/apps/tickets/models/ticket/apply_asset.py +++ b/apps/tickets/models/ticket/apply_asset.py @@ -14,9 +14,6 @@ class ApplyAssetTicket(Ticket): apply_nodes = models.ManyToManyField('assets.Node', verbose_name=_('Apply nodes')) # 申请信息 apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets')) - apply_system_users = models.ManyToManyField( - 'assets.SystemUser', verbose_name=_('Apply system users') - ) apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts')) apply_actions = models.IntegerField( choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_('Actions') diff --git a/apps/tickets/models/ticket/command_confirm.py b/apps/tickets/models/ticket/command_confirm.py index b8c37ec54..601102ba3 100644 --- a/apps/tickets/models/ticket/command_confirm.py +++ b/apps/tickets/models/ticket/command_confirm.py @@ -10,11 +10,7 @@ class ApplyCommandTicket(Ticket): null=True, verbose_name=_('Run user') ) apply_run_asset = models.CharField(max_length=128, verbose_name=_('Run asset')) - apply_run_system_user = models.ForeignKey( - 'assets.SystemUser', on_delete=models.SET_NULL, - null=True, verbose_name=_('Run system user') - ) - apply_run_account = models.CharField(max_length=128, verbose_name=_('Run account')) + apply_run_account = models.CharField(max_length=128, default='', verbose_name=_('Run account')) apply_run_command = models.CharField(max_length=4096, verbose_name=_('Run command')) apply_from_session = models.ForeignKey( 'terminal.Session', on_delete=models.SET_NULL, diff --git a/apps/tickets/models/ticket/login_asset_confirm.py b/apps/tickets/models/ticket/login_asset_confirm.py index 43130e052..2b97cd7a2 100644 --- a/apps/tickets/models/ticket/login_asset_confirm.py +++ b/apps/tickets/models/ticket/login_asset_confirm.py @@ -15,8 +15,6 @@ class ApplyLoginAssetTicket(Ticket): 'assets.Asset', on_delete=models.SET_NULL, null=True, verbose_name=_('Login asset'), ) - apply_login_system_user = models.ForeignKey( - 'assets.SystemUser', on_delete=models.SET_NULL, null=True, - verbose_name=_('Login system user'), + apply_login_account = models.CharField( + max_length=128, default='', verbose_name=_('Login account') ) - apply_login_account = models.CharField(max_length=128, verbose_name=_('Login account')) From 2c3239e2383cd8da0451e79de407f7c48193d360 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 18 Aug 2022 11:15:17 +0800 Subject: [PATCH 062/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0022_auto_20220816_1015.py | 24 --- ...817_1346.py => 0022_auto_20220817_1346.py} | 18 +-- .../migrations/0023_auto_20220816_1021.py | 21 --- .../migrations/0023_auto_20220817_1716.py | 26 ++++ .../migrations/0024_auto_20220818_1057.py | 23 +++ apps/applications/models.py | 28 ++++ .../migrations/0101_auto_20220711_1409.py | 8 +- .../migrations/0102_auto_20220711_1413.py | 4 +- .../migrations/0104_auto_20220803_1859.py | 5 +- .../migrations/0105_auto_20220810_1449.py | 5 + .../migrations/0106_auto_20220811_1358.py | 22 --- ...811_1449.py => 0106_auto_20220811_1449.py} | 2 +- ...811_1511.py => 0107_auto_20220811_1511.py} | 6 +- ...816_1022.py => 0108_auto_20220816_1022.py} | 9 +- .../migrations/0109_auto_20220815_1811.py | 36 ----- ...817_1544.py => 0109_auto_20220817_1544.py} | 19 +-- .../migrations/0110_auto_20220815_1831.py | 24 --- .../migrations/0110_auto_20220817_1716.py | 32 ++++ apps/assets/models/__init__.py | 2 +- apps/assets/models/_user.py | 137 ------------------ .../migrations/0020_auto_20220817_1346.py | 4 +- 21 files changed, 133 insertions(+), 322 deletions(-) delete mode 100644 apps/applications/migrations/0022_auto_20220816_1015.py rename apps/applications/migrations/{0024_auto_20220817_1346.py => 0022_auto_20220817_1346.py} (66%) delete mode 100644 apps/applications/migrations/0023_auto_20220816_1021.py create mode 100644 apps/applications/migrations/0023_auto_20220817_1716.py create mode 100644 apps/applications/migrations/0024_auto_20220818_1057.py create mode 100644 apps/applications/models.py delete mode 100644 apps/assets/migrations/0106_auto_20220811_1358.py rename apps/assets/migrations/{0107_auto_20220811_1449.py => 0106_auto_20220811_1449.py} (94%) rename apps/assets/migrations/{0108_auto_20220811_1511.py => 0107_auto_20220811_1511.py} (88%) rename apps/assets/migrations/{0111_auto_20220816_1022.py => 0108_auto_20220816_1022.py} (91%) delete mode 100644 apps/assets/migrations/0109_auto_20220815_1811.py rename apps/assets/migrations/{0112_auto_20220817_1544.py => 0109_auto_20220817_1544.py} (67%) delete mode 100644 apps/assets/migrations/0110_auto_20220815_1831.py create mode 100644 apps/assets/migrations/0110_auto_20220817_1716.py diff --git a/apps/applications/migrations/0022_auto_20220816_1015.py b/apps/applications/migrations/0022_auto_20220816_1015.py deleted file mode 100644 index 58e3291c9..000000000 --- a/apps/applications/migrations/0022_auto_20220816_1015.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-16 02:15 - -import common.db.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('applications', '0021_auto_20220629_1826'), - ] - - operations = [ - migrations.AddField( - model_name='account', - name='token', - field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), - ), - migrations.AddField( - model_name='historicalaccount', - name='token', - field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), - ), - ] diff --git a/apps/applications/migrations/0024_auto_20220817_1346.py b/apps/applications/migrations/0022_auto_20220817_1346.py similarity index 66% rename from apps/applications/migrations/0024_auto_20220817_1346.py rename to apps/applications/migrations/0022_auto_20220817_1346.py index a6d03a5ca..eae021831 100644 --- a/apps/applications/migrations/0024_auto_20220817_1346.py +++ b/apps/applications/migrations/0022_auto_20220817_1346.py @@ -6,7 +6,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('applications', '0023_auto_20220816_1021'), + ('applications', '0021_auto_20220629_1826'), ] operations = [ @@ -22,10 +22,6 @@ class Migration(migrations.Migration): model_name='account', name='systemuser', ), - migrations.AlterUniqueTogether( - name='application', - unique_together=None, - ), migrations.RemoveField( model_name='application', name='domain', @@ -42,16 +38,4 @@ class Migration(migrations.Migration): model_name='historicalaccount', name='systemuser', ), - migrations.DeleteModel( - name='ApplicationUser', - ), - migrations.DeleteModel( - name='Account', - ), - migrations.DeleteModel( - name='Application', - ), - migrations.DeleteModel( - name='HistoricalAccount', - ), ] diff --git a/apps/applications/migrations/0023_auto_20220816_1021.py b/apps/applications/migrations/0023_auto_20220816_1021.py deleted file mode 100644 index 34d95cbe6..000000000 --- a/apps/applications/migrations/0023_auto_20220816_1021.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-16 02:21 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('applications', '0022_auto_20220816_1015'), - ] - - operations = [ - migrations.RemoveField( - model_name='account', - name='token', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='token', - ), - ] diff --git a/apps/applications/migrations/0023_auto_20220817_1716.py b/apps/applications/migrations/0023_auto_20220817_1716.py new file mode 100644 index 000000000..b8b235fe5 --- /dev/null +++ b/apps/applications/migrations/0023_auto_20220817_1716.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.14 on 2022-08-17 09:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0022_auto_20220817_1346'), + ('perms', '0031_auto_20220816_1600'), + ('ops', '0022_auto_20220817_1346'), + ('assets', '0109_auto_20220817_1544'), + ('tickets', '0020_auto_20220817_1346'), + ] + + operations = [ + migrations.DeleteModel( + name='Account', + ), + migrations.DeleteModel( + name='HistoricalAccount', + ), + migrations.DeleteModel( + name='ApplicationUser', + ), + ] diff --git a/apps/applications/migrations/0024_auto_20220818_1057.py b/apps/applications/migrations/0024_auto_20220818_1057.py new file mode 100644 index 000000000..7a0b8919a --- /dev/null +++ b/apps/applications/migrations/0024_auto_20220818_1057.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-08-18 02:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0023_auto_20220817_1716'), + ] + + operations = [ + migrations.AlterField( + model_name='application', + name='category', + field=models.CharField(max_length=16, verbose_name='Category'), + ), + migrations.AlterField( + model_name='application', + name='type', + field=models.CharField(max_length=16, verbose_name='Type'), + ), + ] diff --git a/apps/applications/models.py b/apps/applications/models.py new file mode 100644 index 000000000..f1b1bbc8a --- /dev/null +++ b/apps/applications/models.py @@ -0,0 +1,28 @@ + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from orgs.mixins.models import OrgModelMixin +from common.mixins import CommonModelMixin + + +class Application(CommonModelMixin, OrgModelMixin): + name = models.CharField(max_length=128, verbose_name=_('Name')) + category = models.CharField( + max_length=16, verbose_name=_('Category') + ) + type = models.CharField( + max_length=16, verbose_name=_('Type') + ) + attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) + comment = models.TextField( + max_length=128, default='', blank=True, verbose_name=_('Comment') + ) + + class Meta: + verbose_name = _('Application') + unique_together = [('org_id', 'name')] + ordering = ('name',) + permissions = [ + ('match_application', _('Can match application')), + ] diff --git a/apps/assets/migrations/0101_auto_20220711_1409.py b/apps/assets/migrations/0101_auto_20220711_1409.py index ff3b1a1c6..30dd8c51f 100644 --- a/apps/assets/migrations/0101_auto_20220711_1409.py +++ b/apps/assets/migrations/0101_auto_20220711_1409.py @@ -29,12 +29,12 @@ class Migration(migrations.Migration): ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), - ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), + ('privileged', models.BooleanField(default=False, verbose_name='Privileged account')), ('version', models.IntegerField(default=0, verbose_name='Version')), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), @@ -63,12 +63,12 @@ class Migration(migrations.Migration): ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('protocol', models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol')), - ('type', models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type')), + ('privileged', models.BooleanField(default=False, verbose_name='Privileged account')), ('version', models.IntegerField(default=0, verbose_name='Version')), ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')), ], diff --git a/apps/assets/migrations/0102_auto_20220711_1413.py b/apps/assets/migrations/0102_auto_20220711_1413.py index bc74f336c..e1e87d2f2 100644 --- a/apps/assets/migrations/0102_auto_20220711_1413.py +++ b/apps/assets/migrations/0102_auto_20220711_1413.py @@ -16,10 +16,10 @@ def migrate_accounts(apps, schema_editor): auth_books = auth_book_model.objects \ .prefetch_related('systemuser') \ .all()[count:count+bulk_size] - count += len(auth_books) if not auth_books: break + count += len(auth_books) accounts = [] # auth book 和 account 相同的属性 same_attrs = [ @@ -50,7 +50,7 @@ def migrate_accounts(apps, schema_editor): account_model.objects.bulk_create(accounts, ignore_conflicts=True) print("Create accounts: {}-{} using: {:.2f}s".format( - count - bulk_size, count, time.time()-start + count - len(auth_books), count, time.time()-start )) diff --git a/apps/assets/migrations/0104_auto_20220803_1859.py b/apps/assets/migrations/0104_auto_20220803_1859.py index f1836f713..ad5d99ef5 100644 --- a/apps/assets/migrations/0104_auto_20220803_1859.py +++ b/apps/assets/migrations/0104_auto_20220803_1859.py @@ -15,10 +15,9 @@ def migrate_asset_protocols(apps, schema_editor): while True: start = time.time() assets = asset_model.objects.all()[count:count+bulk_size] - count += len(assets) if not assets: break - + count += len(assets) assets_protocols = [] for asset in assets: old_protocols = asset._protocols @@ -38,7 +37,7 @@ def migrate_asset_protocols(apps, schema_editor): assets_protocols.append(asset_protocol_through(asset_id=asset.id, protocol_id=protocol.id)) asset_model.protocols.through.objects.bulk_create(assets_protocols, ignore_conflicts=True) print("Create asset protocols: {}-{} using: {:.2f}s".format( - count - bulk_size, count, time.time()-start + count - len(assets), count, time.time()-start )) diff --git a/apps/assets/migrations/0105_auto_20220810_1449.py b/apps/assets/migrations/0105_auto_20220810_1449.py index 18b933338..a305c8156 100644 --- a/apps/assets/migrations/0105_auto_20220810_1449.py +++ b/apps/assets/migrations/0105_auto_20220810_1449.py @@ -38,4 +38,9 @@ class Migration(migrations.Migration): name='type', field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('general', 'General'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), ), + migrations.AlterField( + model_name='platform', + name='su_enabled', + field=models.BooleanField(default=False, verbose_name='Su enabled'), + ), ] diff --git a/apps/assets/migrations/0106_auto_20220811_1358.py b/apps/assets/migrations/0106_auto_20220811_1358.py deleted file mode 100644 index 63b7509dd..000000000 --- a/apps/assets/migrations/0106_auto_20220811_1358.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-11 05:58 - -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0105_auto_20220810_1449'), - ] - - operations = [ - migrations.RemoveField( - model_name='account', - name='protocol', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='protocol', - ), - ] diff --git a/apps/assets/migrations/0107_auto_20220811_1449.py b/apps/assets/migrations/0106_auto_20220811_1449.py similarity index 94% rename from apps/assets/migrations/0107_auto_20220811_1449.py rename to apps/assets/migrations/0106_auto_20220811_1449.py index f5aacbecf..decab283b 100644 --- a/apps/assets/migrations/0107_auto_20220811_1449.py +++ b/apps/assets/migrations/0106_auto_20220811_1449.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('assets', '0106_auto_20220811_1358'), + ('assets', '0105_auto_20220810_1449'), ] operations = [ diff --git a/apps/assets/migrations/0108_auto_20220811_1511.py b/apps/assets/migrations/0107_auto_20220811_1511.py similarity index 88% rename from apps/assets/migrations/0108_auto_20220811_1511.py rename to apps/assets/migrations/0107_auto_20220811_1511.py index b4bca4599..c95e336e3 100644 --- a/apps/assets/migrations/0108_auto_20220811_1511.py +++ b/apps/assets/migrations/0107_auto_20220811_1511.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('assets', '0107_auto_20220811_1449'), + ('assets', '0106_auto_20220811_1449'), ] operations = [ @@ -34,8 +34,4 @@ class Migration(migrations.Migration): name='created_by', field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), ), - migrations.AlterUniqueTogether( - name='asset', - unique_together={('org_id', 'name')}, - ), ] diff --git a/apps/assets/migrations/0111_auto_20220816_1022.py b/apps/assets/migrations/0108_auto_20220816_1022.py similarity index 91% rename from apps/assets/migrations/0111_auto_20220816_1022.py rename to apps/assets/migrations/0108_auto_20220816_1022.py index 55c286ce5..90606c2bf 100644 --- a/apps/assets/migrations/0111_auto_20220816_1022.py +++ b/apps/assets/migrations/0108_auto_20220816_1022.py @@ -13,11 +13,10 @@ def migrate_command_filter_to_assets(apps, schema_editor): while True: start = time.time() command_filters = command_filter_model.objects.all() \ - .prefetch_related('system_users')[count:count + bulk_size] - count += len(command_filters) + .prefetch_related('system_users')[count:count + bulk_size] if not command_filters: break - + count += len(command_filters) updated = [] for command_filter in command_filters: command_filter.accounts = [s.username for s in command_filter.system_users.all()] @@ -25,7 +24,7 @@ def migrate_command_filter_to_assets(apps, schema_editor): command_filter_model.objects.bulk_update(updated, ['accounts']) print("Create assets: {}-{} using: {:.2f}s".format( - count - bulk_size, count, time.time() - start + count - len(command_filters), count, time.time() - start )) @@ -47,7 +46,7 @@ def migrate_command_filter_apps(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('assets', '0110_auto_20220815_1831'), + ('assets', '0107_auto_20220811_1511'), ] operations = [ diff --git a/apps/assets/migrations/0109_auto_20220815_1811.py b/apps/assets/migrations/0109_auto_20220815_1811.py deleted file mode 100644 index c79715533..000000000 --- a/apps/assets/migrations/0109_auto_20220815_1811.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-15 10:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0108_auto_20220811_1511'), - ] - - operations = [ - migrations.RemoveField( - model_name='account', - name='type', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='type', - ), - migrations.AddField( - model_name='account', - name='privileged', - field=models.BooleanField(default=False, verbose_name='Privileged account'), - ), - migrations.AddField( - model_name='historicalaccount', - name='privileged', - field=models.BooleanField(default=False, verbose_name='Privileged account'), - ), - migrations.AlterField( - model_name='platform', - name='su_enabled', - field=models.BooleanField(default=False, verbose_name='Su enabled'), - ), - ] diff --git a/apps/assets/migrations/0112_auto_20220817_1544.py b/apps/assets/migrations/0109_auto_20220817_1544.py similarity index 67% rename from apps/assets/migrations/0112_auto_20220817_1544.py rename to apps/assets/migrations/0109_auto_20220817_1544.py index d1ba8875b..d9ca483fa 100644 --- a/apps/assets/migrations/0112_auto_20220817_1544.py +++ b/apps/assets/migrations/0109_auto_20220817_1544.py @@ -6,7 +6,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('assets', '0111_auto_20220816_1022'), + ('assets', '0108_auto_20220816_1022'), ] operations = [ @@ -25,10 +25,6 @@ class Migration(migrations.Migration): model_name='historicalauthbook', name='systemuser', ), - migrations.AlterUniqueTogether( - name='systemuser', - unique_together=None, - ), migrations.RemoveField( model_name='systemuser', name='assets', @@ -41,21 +37,8 @@ class Migration(migrations.Migration): model_name='systemuser', name='nodes', ), - migrations.RemoveField( - model_name='systemuser', - name='su_from', - ), migrations.RemoveField( model_name='systemuser', name='users', ), - migrations.DeleteModel( - name='AuthBook', - ), - migrations.DeleteModel( - name='HistoricalAuthBook', - ), - migrations.DeleteModel( - name='SystemUser', - ), ] diff --git a/apps/assets/migrations/0110_auto_20220815_1831.py b/apps/assets/migrations/0110_auto_20220815_1831.py deleted file mode 100644 index e3d55ced6..000000000 --- a/apps/assets/migrations/0110_auto_20220815_1831.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-15 10:31 - -import common.db.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0109_auto_20220815_1811'), - ] - - operations = [ - migrations.AddField( - model_name='account', - name='token', - field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), - ), - migrations.AddField( - model_name='historicalaccount', - name='token', - field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token'), - ), - ] diff --git a/apps/assets/migrations/0110_auto_20220817_1716.py b/apps/assets/migrations/0110_auto_20220817_1716.py new file mode 100644 index 000000000..6fdaadd52 --- /dev/null +++ b/apps/assets/migrations/0110_auto_20220817_1716.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.14 on 2022-08-17 09:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0109_auto_20220817_1544'), + ('applications', '0022_auto_20220817_1346'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='authbook', + unique_together=None, + ), + migrations.RemoveField( + model_name='authbook', + name='asset', + ), + migrations.RemoveField( + model_name='authbook', + name='systemuser', + ), + migrations.DeleteModel( + name='HistoricalAuthBook', + ), + migrations.DeleteModel( + name='AuthBook', + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index d81856923..6b4bd31a1 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -1,6 +1,5 @@ from .base import * from .platform import * -# from ._user import * from .asset import * from .label import Label from .group import * @@ -11,6 +10,7 @@ from .gathered_user import * from .favorite_asset import * from .account import * from .backup import * +from ._user import * # 废弃以下 # from ._authbook import * from .protocol import * diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index 8f5c9c9d1..83aaec2c0 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -8,8 +8,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator -from assets.const import Protocol -from common.utils import signer from .base import BaseAccount from .protocol import ProtocolMixin @@ -31,14 +29,6 @@ class SystemUser(ProtocolMixin, BaseAccount): admin = 'admin', _('Admin user') username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user")) - nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) - assets = models.ManyToManyField( - 'assets.Asset', blank=True, verbose_name=_("Assets"), - through='assets.AuthBook', through_fields=['systemuser', 'asset'], - related_name='system_users' - ) - users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users")) - groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups")) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) protocol = models.CharField(max_length=16, default='ssh', verbose_name=_('Protocol')) @@ -55,84 +45,6 @@ class SystemUser(ProtocolMixin, BaseAccount): su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) - def __str__(self): - username = self.username - if self.username_same_with_user: - username = '*' - return '{0.name}({1})'.format(self, username) - - @property - def nodes_amount(self): - return self.nodes.all().count() - - @property - def login_mode_display(self): - return self.get_login_mode_display() - - def is_need_push(self): - if self.auto_push and self.is_protocol_support_push: - return True - else: - return False - - @property - def is_admin_user(self): - return self.type == self.Type.admin - - @property - def is_need_cmd_filter(self): - return self.protocol not in [self.Protocol.rdp, self.Protocol.vnc] - - @property - def is_need_test_asset_connective(self): - return self.protocol in self.ASSET_CATEGORY_PROTOCOLS - - @property - def cmd_filter_rules(self): - from .cmd_filter import CommandFilterRule - rules = CommandFilterRule.objects.filter( - filter__in=self.cmd_filters.all() - ).distinct() - return rules - - def is_command_can_run(self, command): - for rule in self.cmd_filter_rules: - action, matched_cmd = rule.match(command) - if action == rule.ActionChoices.allow: - return True, None - elif action == rule.ActionChoices.deny: - return False, matched_cmd - return True, None - - def get_all_assets(self): - from assets.models import Node, Asset - nodes_keys = self.nodes.all().values_list('key', flat=True) - asset_ids = set(self.assets.all().values_list('id', flat=True)) - nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys) - asset_ids.update(nodes_asset_ids) - assets = Asset.objects.filter(id__in=asset_ids) - return assets - - def add_related_assets(self, assets_or_ids): - self.assets.add(*tuple(assets_or_ids)) - self.add_related_assets_to_su_from_if_need(assets_or_ids) - - def add_related_assets_to_su_from_if_need(self, assets_or_ids): - if self.protocol not in [self.Protocol.ssh.value]: - return - if not self.su_enabled: - return - if not self.su_from: - return - if self.su_from.protocol != self.protocol: - return - self.su_from.assets.add(*tuple(assets_or_ids)) - - # TODO 暂时为了接口文档添加 - @property - def auto_push_account(self): - return - class Meta: ordering = ['name'] unique_together = [('name', 'org_id')] @@ -140,52 +52,3 @@ class SystemUser(ProtocolMixin, BaseAccount): permissions = [ ('match_systemuser', _('Can match system user')), ] - - -# Deprecated: 准备废弃 -class AdminUser(BaseAccount): - """ - A privileged user that ansible can use it to push system user and so on - """ - BECOME_METHOD_CHOICES = ( - ('sudo', 'sudo'), - ('su', 'su'), - ) - become = models.BooleanField(default=True) - become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) - become_user = models.CharField(default='root', max_length=64) - _become_pass = models.CharField(default='', blank=True, max_length=128) - CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}' - _prefer = "admin_user" - - def __str__(self): - return self.name - - @property - def become_pass(self): - password = signer.unsign(self._become_pass) - if password: - return password - else: - return "" - - @become_pass.setter - def become_pass(self, password): - self._become_pass = signer.sign(password) - - @property - def become_info(self): - if self.become: - info = { - "method": self.become_method, - "user": self.become_user, - "pass": self.become_pass, - } - else: - info = None - return info - - class Meta: - ordering = ['name'] - unique_together = [('name', 'org_id')] - verbose_name = _("Admin user") diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index 76437f936..b657e7fd3 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -34,12 +34,12 @@ def migrate_system_to_account(apps, schema_editor): new_value = [s.username for s in old_value] else: old_value = getattr(obj, old_field) - new_value = old_value.username + new_value = old_value.username if old_value else '' setattr(obj, new_field, new_value) updated.append(obj) model.objects.bulk_update(updated, [new_field]) print("Migrate account: {}-{} using: {:.2f}s".format( - count - bulk_size, count, time.time()-start + count - len(objects), count, time.time()-start )) From a9bf4eddeaff8412a0898931355ed19b129f0e11 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 18 Aug 2022 13:02:10 +0800 Subject: [PATCH 063/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0102_auto_20220711_1413.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/assets/migrations/0102_auto_20220711_1413.py b/apps/assets/migrations/0102_auto_20220711_1413.py index e1e87d2f2..d2cecc858 100644 --- a/apps/assets/migrations/0102_auto_20220711_1413.py +++ b/apps/assets/migrations/0102_auto_20220711_1413.py @@ -31,15 +31,13 @@ def migrate_accounts(apps, schema_editor): for auth_book in auth_books: values = {attr: getattr(auth_book, attr) for attr in same_attrs} - values['protocol'] = 'ssh' values['version'] = 1 system_user = auth_book.systemuser if auth_book.systemuser: values.update({attr: getattr(system_user, attr) for attr in auth_attrs}) - values['protocol'] = system_user.protocol values['created_by'] = str(system_user.id) - values['type'] = system_user.type + values['privileged'] = system_user.type == 'admin' auth_book_auth = {attr: getattr(auth_book, attr) for attr in auth_attrs} auth_book_auth = {attr: value for attr, value in auth_book_auth.items() if value} From 05f913ab1805e6a30d99a87b451c34b2bb6ea18a Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 18 Aug 2022 17:58:59 +0800 Subject: [PATCH 064/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/common.py | 1 - .../migrations/0099_auto_20220426_1558.py | 3 - .../migrations/0100_auto_20220430_2126.py | 13 +- apps/assets/models/_authbook.py | 137 ------------------ apps/assets/models/asset/common.py | 7 - apps/assets/models/platform.py | 12 +- apps/assets/serializers/asset/common.py | 4 + apps/assets/serializers/platform.py | 35 ++--- apps/common/drf/fields.py | 2 +- apps/ops/api/command.py | 4 +- 10 files changed, 45 insertions(+), 173 deletions(-) delete mode 100644 apps/assets/models/_authbook.py diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index 6d266e1bd..756ce09ee 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -29,7 +29,6 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) filterset_fields = { 'name': ['exact'], 'ip': ['exact'], - 'system_users__id': ['exact'], 'is_active': ['exact'], 'protocols': ['exact', 'icontains'] } diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index a3418aea8..8d888cf6b 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -18,9 +18,6 @@ def create_app_platform(apps, *args): {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, {'name': 'Redis', 'category': 'database', 'type': 'redis'}, {'name': 'Chrome', 'category': 'remote_app', 'type': 'chrome'}, - {'name': 'vSphereClient', 'category': 'remote_app', 'type': 'vmware_client'}, - {'name': 'MySQLWorkbench', 'category': 'remote_app', 'type': 'mysql_workbench'}, - {'name': 'GeneralRemoteApp', 'category': 'remote_app', 'type': 'general_remote_app'}, {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, ] diff --git a/apps/assets/migrations/0100_auto_20220430_2126.py b/apps/assets/migrations/0100_auto_20220430_2126.py index 9651a9e43..17f6d6da1 100644 --- a/apps/assets/migrations/0100_auto_20220430_2126.py +++ b/apps/assets/migrations/0100_auto_20220430_2126.py @@ -11,6 +11,15 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='PlatformProtocol', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32, verbose_name='Name')), + ('port', models.IntegerField(verbose_name='Port')), + ('setting', models.JSONField(default=dict, verbose_name='Setting')), + ], + ), migrations.AddField( model_name='platform', name='domain_default', @@ -28,8 +37,8 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='platform', - name='protocols_enabled', - field=models.BooleanField(default=True, verbose_name='Protocols enabled'), + name='protocols', + field=models.ManyToManyField(blank=True, to='assets.PlatformProtocol', verbose_name='Protocols'), ), migrations.AddField( model_name='platform', diff --git a/apps/assets/models/_authbook.py b/apps/assets/models/_authbook.py deleted file mode 100644 index 74a81df61..000000000 --- a/apps/assets/models/_authbook.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.db import models -from django.db.models import F -from django.utils.translation import ugettext_lazy as _ -from simple_history.models import HistoricalRecords - -from common.utils import lazyproperty, get_logger -from .base import BaseAccount, AbsConnectivity - -logger = get_logger(__name__) - - -__all__ = ['AuthBook'] - - -class AuthBook(BaseAccount, AbsConnectivity): - asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) - systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) - version = models.IntegerField(default=1, verbose_name=_('Version')) - history = HistoricalRecords() - - auth_attrs = ['username', 'password', 'private_key', 'public_key'] - - class Meta: - verbose_name = _('AuthBook') - unique_together = [('username', 'asset', 'systemuser')] - permissions = [ - ('test_authbook', _('Can test asset account connectivity')), - ('view_assetaccountsecret', _('Can view asset account secret')), - ('change_assetaccountsecret', _('Can change asset account secret')), - ('view_assethistoryaccount', _('Can view asset history account')), - ('view_assethistoryaccountsecret', _('Can view asset history account secret')), - ] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.auth_snapshot = {} - - def get_or_systemuser_attr(self, attr): - val = getattr(self, attr, None) - if val: - return val - if self.systemuser: - return getattr(self.systemuser, attr, '') - return '' - - def load_auth(self): - for attr in self.auth_attrs: - value = self.get_or_systemuser_attr(attr) - self.auth_snapshot[attr] = [getattr(self, attr), value] - setattr(self, attr, value) - - def unload_auth(self): - if not self.systemuser: - return - - for attr, values in self.auth_snapshot.items(): - origin_value, loaded_value = values - current_value = getattr(self, attr, '') - if current_value == loaded_value: - setattr(self, attr, origin_value) - - def save(self, *args, **kwargs): - self.unload_auth() - instance = super().save(*args, **kwargs) - self.load_auth() - return instance - - @property - def username_display(self): - return self.get_or_systemuser_attr('username') or '*' - - @lazyproperty - def systemuser_display(self): - if not self.systemuser: - return '' - return str(self.systemuser) - - @property - def smart_name(self): - username = self.username_display - - if self.asset: - asset = str(self.asset) - else: - asset = '*' - return '{}@{}'.format(username, asset) - - def sync_to_system_user_account(self): - if self.systemuser: - return - matched = AuthBook.objects.filter( - asset=self.asset, systemuser__username=self.username - ) - if not matched: - return - - for i in matched: - i.password = self.password - i.private_key = self.private_key - i.public_key = self.public_key - i.comment = 'Update triggered by account {}'.format(self.id) - - # 不触发post_save信号 - self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key']) - - def remove_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser: - return - self.asset.admin_user = None - self.asset.save() - logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser)) - - def update_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser: - return - self.asset.admin_user = self.systemuser - self.asset.save() - logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) - - @classmethod - def get_queryset(cls): - queryset = cls.objects.all() \ - .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) \ - .annotate(platform=F('asset__platform__name')) \ - .annotate(protocols=F('asset__protocols')) - return queryset - - def __str__(self): - return self.smart_name diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index f04f1f959..dd8d07dd1 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -157,13 +157,6 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): tree_node = TreeNode(**data) return tree_node - def get_all_system_users(self): - from assets.models import SystemUser - system_user_ids = SystemUser.assets.through.objects.filter(asset=self) \ - .values_list('systemuser_id', flat=True) - system_users = SystemUser.objects.filter(id__in=system_user_ids) - return system_users - class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset") diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 7dd991c1d..7b6dc1889 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -5,7 +5,13 @@ from assets.const import Category, AllTypes from common.db.fields import JsonDictTextField -__all__ = ['Platform'] +__all__ = ['Platform', 'PlatformProtocol'] + + +class PlatformProtocol(models.Model): + name = models.CharField(max_length=32, verbose_name=_('Name')) + port = models.IntegerField(verbose_name=_('Port')) + setting = models.JSONField(verbose_name=_('Setting'), default=dict) class Platform(models.Model): @@ -30,9 +36,7 @@ class Platform(models.Model): verbose_name=_("Domain default") ) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) - protocols_default = models.JSONField( - max_length=128, default=list, blank=True, verbose_name=_("Protocols default") - ) + protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) # Accounts # 这应该和账号有关 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 91f1871e9..9363cbe3c 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -4,8 +4,10 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import JMSWritableNestedModelSerializer +from common.drf.fields import ChoiceDisplayField from ..account import AccountSerializer from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account +from ...const import Category, AllTypes __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', @@ -57,6 +59,8 @@ class AssetNodesSerializer(serializers.ModelSerializer): class AssetSerializer(JMSWritableNestedModelSerializer): + category = ChoiceDisplayField(choices=Category.choices, read_only=True, label=_('Category')) + type = ChoiceDisplayField(choices=AllTypes.choices, read_only=True, label=_('Type')) domain = AssetDomainSerializer(required=False) platform = AssetPlatformSerializer(required=False) labels = AssetLabelSerializer(many=True, required=False) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 999821abc..6cbd5b898 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -1,42 +1,43 @@ from rest_framework import serializers -from django.core.validators import RegexValidator from django.utils.translation import gettext_lazy as _ -from assets.models import Platform -from .mixin import CategoryDisplayMixin +from common.drf.fields import ChoiceDisplayField +from common.drf.serializers import JMSWritableNestedModelSerializer +from ..models import Platform, PlatformProtocol +from ..const import Category, AllTypes __all__ = ['PlatformSerializer'] -class PlatformProtocolsSerializer(serializers.Serializer): - name = serializers.CharField(max_length=255, required=True) - port = serializers.IntegerField(max_value=65535, min_value=1, required=True) +class PlatformProtocolsSerializer(serializers.ModelSerializer): + class Meta: + model = PlatformProtocol + fields = ['id', 'name', 'port', 'setting'] -class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): - meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - protocols_default = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) +class PlatformSerializer(JMSWritableNestedModelSerializer): + type = ChoiceDisplayField(choices=AllTypes.choices, label=_("Type")) + category = ChoiceDisplayField(choices=Category.choices, label=_("Category")) + protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) type_constraints = serializers.ReadOnlyField(required=False, read_only=True) class Meta: model = Platform fields_mini = ['id', 'name', 'internal'] fields_small = fields_mini + [ - 'meta', 'comment', 'charset', - 'category', 'category_display', - 'type', 'type_display', + 'category', 'type', + ] + fields = fields_small + [ + 'domain_enabled', 'domain_default', 'su_enabled', 'su_method', + 'protocols_enabled', 'protocols', 'ping_enabled', 'ping_method', 'verify_account_enabled', 'verify_account_method', 'create_account_enabled', 'create_account_method', 'change_password_enabled', 'change_password_method', 'type_constraints', + 'comment', 'charset', ] - fields_fk = [ - 'domain_enabled', 'domain_default', - 'protocols_enabled', 'protocols_default', - ] - fields = fields_small + fields_fk read_only_fields = [ 'category_display', 'type_display', ] diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index c0c214c4f..d925164da 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -51,6 +51,6 @@ class ChoiceDisplayField(ChoiceField): if value in ('', None): return value return { - 'name': value, + 'value': value, 'label': self.choice_mapper.get(six.text_type(value), value), } diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py index a129ec416..8c716fb03 100644 --- a/apps/ops/api/command.py +++ b/apps/ops/api/command.py @@ -28,7 +28,9 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet): system_user = data["run_as"] user = self.request.user - q = Q(granted_by_permissions__system_users__id=system_user.id) & ( + # TOdo: + # Q(granted_by_permissions__system_users__id=system_user.id) & + q = ( Q(granted_by_permissions__users=user) | Q(granted_by_permissions__user_groups__users=user) ) From fe4df4b1796eb072d1fa1b8b72112fc25376c7cd Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Fri, 19 Aug 2022 18:49:00 +0800 Subject: [PATCH 065/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dswagger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/__init__.py | 1 + apps/assets/api/account_template.py | 12 ++++++ apps/assets/api/gathered_user.py | 4 +- .../migrations/0111_auto_20220819_1523.py | 40 +++++++++++++++++++ .../migrations/0112_auto_20220819_1526.py | 22 ++++++++++ apps/assets/models/account.py | 13 +++++- apps/assets/models/asset/common.py | 13 ++++++ apps/assets/serializers/__init__.py | 1 + apps/assets/serializers/account.py | 12 ------ apps/assets/serializers/account_template.py | 19 +++++++++ apps/assets/serializers/asset/category.py | 2 +- apps/assets/serializers/base.py | 14 ++++++- apps/assets/urls/api_urls.py | 1 + apps/audits/api.py | 10 ++--- apps/audits/serializers.py | 8 ++-- apps/authentication/api/connection_token.py | 9 +---- apps/ops/api/command.py | 1 - apps/ops/models/command.py | 4 -- apps/ops/serializers/adhoc.py | 3 +- .../api/asset/asset_permission_relation.py | 4 +- .../serializers/asset/user_permission.py | 2 +- 21 files changed, 150 insertions(+), 45 deletions(-) create mode 100644 apps/assets/api/account_template.py create mode 100644 apps/assets/migrations/0111_auto_20220819_1523.py create mode 100644 apps/assets/migrations/0112_auto_20220819_1526.py create mode 100644 apps/assets/serializers/account_template.py diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 805202615..14fd38e9a 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -7,5 +7,6 @@ from .node import * from .domain import * from .gathered_user import * from .favorite_asset import * +from .account_template import * from .account_backup import * from .account_history import * diff --git a/apps/assets/api/account_template.py b/apps/assets/api/account_template.py new file mode 100644 index 000000000..b88fcd0f6 --- /dev/null +++ b/apps/assets/api/account_template.py @@ -0,0 +1,12 @@ +from orgs.mixins.api import OrgBulkModelViewSet +from ..models import AccountTemplate +from .. import serializers + + +class AccountTemplateViewSet(OrgBulkModelViewSet): + model = AccountTemplate + filterset_fields = ("username", 'name') + search_fields = ('username', 'name') + serializer_classes = { + 'default': serializers.AccountTemplateSerializer + } diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py index 22be7daf7..c5c9ece64 100644 --- a/apps/assets/api/gathered_user.py +++ b/apps/assets/api/gathered_user.py @@ -16,5 +16,5 @@ class GatheredUserViewSet(OrgModelViewSet): serializer_class = GatheredUserSerializer extra_filter_backends = [AssetRelatedByNodeFilterBackend] - filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id'] - search_fields = ['username', 'asset__ip', 'asset__hostname'] + filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__name', 'asset_id'] + search_fields = ['username', 'asset__ip', 'asset__name'] diff --git a/apps/assets/migrations/0111_auto_20220819_1523.py b/apps/assets/migrations/0111_auto_20220819_1523.py new file mode 100644 index 000000000..1bed01959 --- /dev/null +++ b/apps/assets/migrations/0111_auto_20220819_1523.py @@ -0,0 +1,40 @@ +# Generated by Django 3.2.13 on 2022-08-19 07:23 + +import assets.models.base +import common.db.fields +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_auto_20220817_1716'), + ] + + operations = [ + migrations.CreateModel( + name='AccountTemplate', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), + ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')), + ('privileged', models.BooleanField(default=False, verbose_name='Privileged account')), + ], + options={ + 'verbose_name': 'Account template', + }, + bases=(models.Model, assets.models.base.AuthMixin), + ) + ] diff --git a/apps/assets/migrations/0112_auto_20220819_1526.py b/apps/assets/migrations/0112_auto_20220819_1526.py new file mode 100644 index 000000000..14e7d7765 --- /dev/null +++ b/apps/assets/migrations/0112_auto_20220819_1526.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.13 on 2022-08-19 07:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0111_auto_20220819_1523'), + ] + + operations = [ + migrations.RemoveField( + model_name='platform', + name='protocols_default', + ), + migrations.AddField( + model_name='platform', + name='protocols_enabled', + field=models.BooleanField(default=True, verbose_name='Protocols enabled'), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index c48fd0256..585307324 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -5,7 +5,7 @@ from simple_history.models import HistoricalRecords from common.db import fields from .base import BaseAccount, AbsConnectivity -__all__ = ['Account'] +__all__ = ['Account', 'AccountTemplate'] class Account(BaseAccount, AbsConnectivity): @@ -27,3 +27,14 @@ class Account(BaseAccount, AbsConnectivity): def __str__(self): return '{}@{}'.format(self.username, self.asset.name) + + +class AccountTemplate(BaseAccount, AbsConnectivity): + token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) + privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) + + class Meta: + verbose_name = _('Account template') + + def __str__(self): + return '{}@{}'.format(self.username, self.name) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index dd8d07dd1..1e4c3d71f 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -98,6 +98,11 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): return False, warning return True, warning + def domain_display(self): + if self.domain: + return self.domain.name + return '' + def nodes_display(self): names = [] for n in self.nodes.all(): @@ -114,10 +119,18 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): def type(self): return self.platform.type + @property + def type_display(self): + return self.platform.type + @property def category(self): return self.platform.category + @property + def category_display(self): + return self.platform.category + def as_node(self): from assets.models import Node fake_node = Node() diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index ded671746..a86110712 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -9,5 +9,6 @@ from .gathered_user import * from .favorite_asset import * from .account import * from .account_history import * +from .account_template import * from .backup import * from .platform import * diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index 3538676b0..6695aa348 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -6,7 +6,6 @@ from assets.models import Account from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .base import AuthSerializerMixin -from common.utils.encode import ssh_pubkey_gen from common.drf.serializers import SecretReadableMixin @@ -33,17 +32,6 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): } ref_name = 'AssetAccountSerializer' - def _validate_gen_key(self, attrs): - private_key = attrs.get('private_key') - if not private_key: - return attrs - - password = attrs.get('passphrase') - username = attrs.get('username') - public_key = ssh_pubkey_gen(private_key, password=password, username=username) - attrs['public_key'] = public_key - return attrs - def validate(self, attrs): attrs = self._validate_gen_key(attrs) return attrs diff --git a/apps/assets/serializers/account_template.py b/apps/assets/serializers/account_template.py new file mode 100644 index 000000000..ba25ac8dd --- /dev/null +++ b/apps/assets/serializers/account_template.py @@ -0,0 +1,19 @@ +from assets.models import AccountTemplate +from orgs.mixins.serializers import BulkOrgResourceModelSerializer + +from .base import AuthSerializerMixin +from .account import AccountSerializer + + +class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): + class Meta: + model = AccountTemplate + fields_mini = ['id', 'privileged', 'username'] + fields_write_only = AccountSerializer.Meta.fields_write_only + fields_other = AccountSerializer.Meta.fields_other + fields = fields_mini + fields_write_only + fields_other + extra_kwargs = AccountSerializer.Meta.extra_kwargs + + def validate(self, attrs): + attrs = self._validate_gen_key(attrs) + return attrs diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 78c3180cd..971a69471 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -31,7 +31,7 @@ class DatabaseSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Database fields_mini = [ - 'id', 'name', 'ip', 'port', 'db_name', + 'id', 'name', 'ip', 'db_name', ] fields_small = fields_mini + [ 'is_active', 'comment', diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 92249705d..aba1ddfe8 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -13,7 +13,8 @@ from .utils import validate_password_for_ansible class AuthSerializer(serializers.ModelSerializer): password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password')) - private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384, label=_('Private key')) + private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384, + label=_('Private key')) def gen_keys(self, private_key=None, password=None): if private_key is None: @@ -74,6 +75,17 @@ class AuthSerializerMixin(serializers.ModelSerializer): validated_data.pop(field, None) validated_data.pop('passphrase', None) + def _validate_gen_key(self, attrs): + private_key = attrs.get('private_key') + if not private_key: + return attrs + + password = attrs.get('passphrase') + username = attrs.get('username') + public_key = ssh_pubkey_gen(private_key, password=password, username=username) + attrs['public_key'] = public_key + return attrs + def create(self, validated_data): self.clean_auth_fields(validated_data) return super().create(validated_data) diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 3e6b1309b..9d73acfa6 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -14,6 +14,7 @@ router.register(r'assets', api.AssetViewSet, 'asset') router.register(r'hosts', api.HostViewSet, 'host') router.register(r'databases', api.DatabaseViewSet, 'database') router.register(r'accounts', api.AccountViewSet, 'account') +router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history') router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret') diff --git a/apps/audits/api.py b/apps/audits/api.py index 2abc52a57..6df4ec861 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -107,11 +107,11 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): ] filterset_fields = [ 'user__name', 'user__username', 'command', - 'run_as__name', 'run_as__username', 'is_finished' + 'account', 'is_finished' ] search_fields = [ 'command', 'user__name', 'user__username', - 'run_as__name', 'run_as__username', + 'account__username', ] ordering = ['-date_created'] @@ -121,7 +121,7 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): return queryset.model.objects.none() if current_org.is_root(): return queryset - queryset = queryset.filter(run_as__org_id=current_org.org_id()) + # queryset = queryset.filter(run_as__org_id=current_org.org_id()) return queryset @@ -131,7 +131,7 @@ class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet) filterset_fields = [ 'id', 'asset', 'commandexecution' ] - search_fields = ('asset__hostname', ) + search_fields = ('asset__name', ) http_method_names = ['options', 'get'] rbac_perms = { 'GET': 'ops.view_commandexecution', @@ -142,7 +142,7 @@ class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet) queryset = super().get_queryset() queryset = queryset.annotate( asset_display=Concat( - F('asset__hostname'), Value('('), + F('asset__name'), Value('('), F('asset__ip'), Value(')') ) ) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index bc78ef238..9895176fd 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -88,24 +88,22 @@ class CommandExecutionSerializer(serializers.ModelSerializer): model = CommandExecution fields_mini = ['id'] fields_small = fields_mini + [ - 'run_as', 'command', 'is_finished', 'user', + 'command', 'is_finished', 'user', 'date_start', 'result', 'is_success', 'org_id' ] - fields = fields_small + ['hosts', 'hosts_display', 'run_as_display', 'user_display'] + fields = fields_small + ['hosts', 'hosts_display', 'user_display'] extra_kwargs = { 'result': {'label': _('Result')}, # model 上的方法,只能在这修改 'is_success': {'label': _('Is success')}, 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改 - 'run_as': {'label': _('Run as')}, 'user': {'label': _('User')}, - 'run_as_display': {'label': _('Run as display')}, 'user_display': {'label': _('User display')}, } @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('user', 'run_as', 'hosts') + queryset = queryset.prefetch_related('user', 'hosts') return queryset diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index ce84cb74e..8ab5ecee9 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -45,18 +45,12 @@ class ConnectionTokenMixin: @staticmethod def check_user_has_resource_permission(user, asset, application, system_user): from perms.utils.asset import has_asset_system_permission - from perms.utils.application import has_application_system_permission if asset and not has_asset_system_permission(user, asset, system_user): error = f'User not has this asset and system user permission: ' \ f'user={user.id} system_user={system_user.id} asset={asset.id}' raise PermissionDenied(error) - if application and not has_application_system_permission(user, application, system_user): - error = f'User not has this application and system user permission: ' \ - f'user={user.id} system_user={system_user.id} application={application.id}' - raise PermissionDenied(error) - def get_smart_endpoint(self, protocol, asset=None, application=None): if asset: target_ip = asset.get_target_ip() @@ -204,8 +198,7 @@ class ConnectionTokenMixin: class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet): filterset_fields = ( - 'type', - 'user_display', 'system_user_display', 'application_display', 'asset_display' + 'type', 'user_display', 'asset_display' ) search_fields = filterset_fields serializer_classes = { diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py index 8c716fb03..1cf7950a6 100644 --- a/apps/ops/api/command.py +++ b/apps/ops/api/command.py @@ -25,7 +25,6 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet): def check_hosts(self, serializer): data = serializer.validated_data assets = data["hosts"] - system_user = data["run_as"] user = self.request.user # TOdo: diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 14cb99081..cb6023564 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -47,10 +47,6 @@ class CommandExecution(OrgModelMixin): inv = JMSInventory(self.allow_assets, run_as=username, system_user=self.run_as) return inv - @lazyproperty - def run_as_display(self): - return str(self.run_as) - @lazyproperty def user_display(self): return str(self.user) diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 094abf958..50b1faeba 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -138,9 +138,8 @@ class CommandExecutionSerializer(serializers.ModelSerializer): 'command', 'result', 'log_url', 'is_finished', 'date_created', 'date_finished' ] - fields_fk = ['run_as'] fields_m2m = ['hosts'] - fields = fields_small + fields_fk + fields_m2m + fields = fields_small + fields_m2m read_only_fields = [ 'result', 'is_finished', 'log_url', 'date_created', 'date_finished' diff --git a/apps/perms/api/asset/asset_permission_relation.py b/apps/perms/api/asset/asset_permission_relation.py index ecf0d731b..902d45864 100644 --- a/apps/perms/api/asset/asset_permission_relation.py +++ b/apps/perms/api/asset/asset_permission_relation.py @@ -80,12 +80,12 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): filterset_fields = [ 'id', 'asset', 'assetpermission', ] - search_fields = ["id", "asset__hostname", "asset__ip", "assetpermission__name"] + search_fields = ["id", "asset__name", "asset__ip", "assetpermission__name"] def get_queryset(self): queryset = super().get_queryset() queryset = queryset \ - .annotate(asset_display=F('asset__hostname')) + .annotate(asset_display=F('asset__name')) return queryset diff --git a/apps/perms/serializers/asset/user_permission.py b/apps/perms/serializers/asset/user_permission.py index f05834f1b..c0d9b4c74 100644 --- a/apps/perms/serializers/asset/user_permission.py +++ b/apps/perms/serializers/asset/user_permission.py @@ -25,7 +25,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): class Meta: model = Asset only_fields = [ - "id", "name", "ip", "protocols", "os", 'domain', + "id", "name", "ip", "protocols", 'domain', "platform", "comment", "org_id", "is_active" ] fields = only_fields + ['org_name'] From a7d193464e80f35728f510eabae157d16ce4b394 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 22 Aug 2022 11:47:45 +0800 Subject: [PATCH 066/488] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20choices=20?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=20label=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 18 +++++++++--------- apps/assets/models/asset/common.py | 7 +++++-- apps/common/db/models.py | 8 ++++++++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/assets/const.py b/apps/assets/const.py index d5bf84727..f2ec1a882 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -1,6 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from common.db.models import IncludesTextChoicesMeta +from common.db.models import IncludesTextChoicesMeta, ChoicesMixin from common.tree import TreeNode @@ -24,7 +24,7 @@ class PlatformMixin: } -class Category(PlatformMixin, models.TextChoices): +class Category(PlatformMixin, ChoicesMixin, models.TextChoices): HOST = 'host', _('Host') NETWORK = 'network', _("NetworkDevice") DATABASE = 'database', _("Database") @@ -60,7 +60,7 @@ class Category(PlatformMixin, models.TextChoices): } -class HostTypes(PlatformMixin, models.TextChoices): +class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): LINUX = 'linux', 'Linux' WINDOWS = 'windows', 'Windows' UNIX = 'unix', 'Unix' @@ -84,14 +84,14 @@ class HostTypes(PlatformMixin, models.TextChoices): } -class NetworkTypes(PlatformMixin, models.TextChoices): +class NetworkTypes(PlatformMixin, ChoicesMixin, models.TextChoices): SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") FIREWALL = 'firewall', _("Firewall") OTHER_NETWORK = 'other_network', _("Other device") -class DatabaseTypes(PlatformMixin, models.TextChoices): +class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): MYSQL = 'mysql', 'MySQL' MARIADB = 'mariadb', 'MariaDB' POSTGRESQL = 'postgresql', 'PostgreSQL' @@ -110,15 +110,15 @@ class DatabaseTypes(PlatformMixin, models.TextChoices): return meta -class WebTypes(PlatformMixin, models.TextChoices): +class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices): General = 'general', 'General' -class CloudTypes(PlatformMixin, models.TextChoices): +class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): K8S = 'k8s', 'Kubernetes' -class AllTypes(metaclass=IncludesTextChoicesMeta): +class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): choices: list includes = [ HostTypes, NetworkTypes, DatabaseTypes, @@ -202,7 +202,7 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): return nodes -class Protocol(models.TextChoices): +class Protocol(ChoicesMixin, models.TextChoices): ssh = 'ssh', 'SSH' rdp = 'rdp', 'RDP' telnet = 'telnet', 'Telnet' diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 1e4c3d71f..b4b4e5b81 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -10,6 +10,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from orgs.mixins.models import OrgManager, JMSOrgBaseModel +from ...const import Category from ..platform import Platform from ..base import AbsConnectivity @@ -121,7 +122,8 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): @property def type_display(self): - return self.platform.type + value = self.platform.type + return Category.get_label(value) @property def category(self): @@ -129,7 +131,8 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): @property def category_display(self): - return self.platform.category + value = self.platform.category + return Category.get_label(value) def as_node(self): from assets.models import Node diff --git a/apps/common/db/models.py b/apps/common/db/models.py index 03e95ca5d..f180b0da1 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -85,6 +85,14 @@ class BitOperationChoice: return [(cls.NAME_MAP[i], j) for i, j in cls.DB_CHOICES] +class ChoicesMixin: + _value2label_map_: dict + + @classmethod + def get_label(cls, value: (str, int)): + return cls._value2label_map_[value] + + class BaseCreateUpdateModel(models.Model): created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) updated_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Updated by')) From dd0b8e988c7d50f3bb716187c4ea3a9b8444db02 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Aug 2022 13:25:57 +0800 Subject: [PATCH 067/488] perf: stash it --- apps/assets/api/platform.py | 4 +-- apps/assets/apps.py | 2 +- apps/assets/serializers/asset/common.py | 2 +- utils/generate_fake_data/generate.py | 4 +-- utils/generate_fake_data/resources/assets.py | 28 +++++++++++++++++--- utils/generate_fake_data/resources/users.py | 1 - 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 4098dc29a..fc038ee67 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -17,11 +17,11 @@ class AssetPlatformViewSet(JMSModelViewSet): 'default': PlatformSerializer, 'categories': GroupedChoiceSerailizer } - filterset_fields = ['name'] + filterset_fields = ['name', 'category', 'type'] search_fields = ['name'] rbac_perms = { 'categories': 'assets.view_platform', - 'type_constraints': 'assets-view_platform' + 'type_constraints': 'assets.view_platform' } @action(methods=['GET'], detail=False) diff --git a/apps/assets/apps.py b/apps/assets/apps.py index e1bb43544..712033455 100644 --- a/apps/assets/apps.py +++ b/apps/assets/apps.py @@ -13,4 +13,4 @@ class AssetsConfig(AppConfig): def ready(self): super().ready() - from . import signal_handlers + # from . import signal_handlers diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 9363cbe3c..284ec2e0d 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.fields import ChoiceDisplayField from ..account import AccountSerializer -from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account +from ...models import Asset, Node, Platform, Protocol, Label, Domain from ...const import Category, AllTypes __all__ = [ diff --git a/utils/generate_fake_data/generate.py b/utils/generate_fake_data/generate.py index 97340e8e6..5fdfddc5c 100644 --- a/utils/generate_fake_data/generate.py +++ b/utils/generate_fake_data/generate.py @@ -12,7 +12,7 @@ sys.path.insert(0, APPS_DIR) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") django.setup() -from resources.assets import AssetsGenerator, NodesGenerator, SystemUsersGenerator, AdminUsersGenerator +from resources.assets import AssetsGenerator, NodesGenerator from resources.users import UserGroupGenerator, UserGenerator from resources.perms import AssetPermissionGenerator from resources.terminal import CommandGenerator, SessionGenerator @@ -21,8 +21,6 @@ from resources.terminal import CommandGenerator, SessionGenerator resource_generator_mapper = { 'asset': AssetsGenerator, 'node': NodesGenerator, - 'system_user': SystemUsersGenerator, - 'admin_user': AdminUsersGenerator, 'user': UserGenerator, 'user_group': UserGroupGenerator, 'asset_permission': AssetPermissionGenerator, diff --git a/utils/generate_fake_data/resources/assets.py b/utils/generate_fake_data/resources/assets.py index e14df6932..7144b1b5c 100644 --- a/utils/generate_fake_data/resources/assets.py +++ b/utils/generate_fake_data/resources/assets.py @@ -5,6 +5,7 @@ import forgery_py from .base import FakeDataGenerator from assets.models import * +from assets.const import AllTypes class NodesGenerator(FakeDataGenerator): @@ -17,13 +18,34 @@ class NodesGenerator(FakeDataGenerator): parent.create_child() +class PlatformGenerator(FakeDataGenerator): + resource = 'platform' + category_type: dict + categories: list + + def pre_generate(self): + self.category_type = dict(AllTypes.category_types()) + self.categories = list(self.category_type.keys()) + + def do_generate(self, batch, batch_size): + platforms = [] + for i in batch: + data = { + 'name': forgery_py.name.company_name(), + 'category': '' + } + platforms.append(Platform(**data)) + Platform.objects.bulk_create(platforms, ignore_conflicts=True) + + class AssetsGenerator(FakeDataGenerator): resource = 'asset' - admin_user_ids: list node_ids: list + platform_ids: list def pre_generate(self): self.node_ids = list(Node.objects.all().values_list('id', flat=True)) + self.platform_ids = list(Platform.objects.all().values_list('id', flat=True)) def set_assets_nodes(self, assets): for asset in assets: @@ -39,8 +61,8 @@ class AssetsGenerator(FakeDataGenerator): hostname = f'{hostname}-{ip}' data = dict( ip=ip, - hostname=hostname, - admin_user_id=choice(self.admin_user_ids), + name=hostname, + platform_id=choice(self.platform_ids), created_by='Fake', org_id=self.org.id ) diff --git a/utils/generate_fake_data/resources/users.py b/utils/generate_fake_data/resources/users.py index c973fec5e..34a9d9703 100644 --- a/utils/generate_fake_data/resources/users.py +++ b/utils/generate_fake_data/resources/users.py @@ -4,7 +4,6 @@ import forgery_py from .base import FakeDataGenerator from users.models import * -from orgs.models import OrganizationMember class UserGroupGenerator(FakeDataGenerator): From f0c9c2b1ad3afcebf5c2ce402539d498e7889d12 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Aug 2022 15:23:28 +0800 Subject: [PATCH 068/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=E7=94=9F?= =?UTF-8?q?=E6=88=90=20platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/generate_fake_data/generate.py | 3 ++- utils/generate_fake_data/resources/assets.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/utils/generate_fake_data/generate.py b/utils/generate_fake_data/generate.py index 5fdfddc5c..a1e82cf7a 100644 --- a/utils/generate_fake_data/generate.py +++ b/utils/generate_fake_data/generate.py @@ -12,7 +12,7 @@ sys.path.insert(0, APPS_DIR) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") django.setup() -from resources.assets import AssetsGenerator, NodesGenerator +from resources.assets import AssetsGenerator, NodesGenerator, PlatformGenerator from resources.users import UserGroupGenerator, UserGenerator from resources.perms import AssetPermissionGenerator from resources.terminal import CommandGenerator, SessionGenerator @@ -20,6 +20,7 @@ from resources.terminal import CommandGenerator, SessionGenerator resource_generator_mapper = { 'asset': AssetsGenerator, + 'platform': PlatformGenerator, 'node': NodesGenerator, 'user': UserGenerator, 'user_group': UserGroupGenerator, diff --git a/utils/generate_fake_data/resources/assets.py b/utils/generate_fake_data/resources/assets.py index 7144b1b5c..6e1ad8cab 100644 --- a/utils/generate_fake_data/resources/assets.py +++ b/utils/generate_fake_data/resources/assets.py @@ -30,9 +30,12 @@ class PlatformGenerator(FakeDataGenerator): def do_generate(self, batch, batch_size): platforms = [] for i in batch: + category = choice(self.categories) + tp = choice(self.category_type[category].choices) data = { 'name': forgery_py.name.company_name(), - 'category': '' + 'category': choice(self.categories), + 'type': tp } platforms.append(Platform(**data)) Platform.objects.bulk_create(platforms, ignore_conflicts=True) From 09607a18852930bf4a9b15100d30bd046b8afbf2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Aug 2022 18:32:33 +0800 Subject: [PATCH 069/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20perms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/__init__.py | 2 +- apps/assets/api/asset/{common.py => asset.py} | 26 +++-- apps/assets/api/asset/database.py | 2 +- apps/assets/api/asset/host.py | 2 +- apps/assets/api/mixin.py | 11 ++- apps/assets/filters.py | 16 ++-- apps/assets/models/asset/common.py | 5 +- apps/assets/pagination.py | 8 +- apps/assets/serializers/asset/common.py | 5 +- apps/assets/utils.py | 2 +- apps/perms/api/__init__.py | 5 +- apps/perms/api/asset/__init__.py | 4 - .../api/asset/user_permission/__init__.py | 6 -- .../user_permission_assets/__init__.py | 1 - .../perms/api/{asset => }/asset_permission.py | 0 .../{asset => }/asset_permission_relation.py | 0 apps/perms/api/base.py | 95 ------------------- .../api/{asset => }/user_group_permission.py | 2 +- apps/perms/api/user_permission/__init__.py | 6 ++ .../api/user_permission/assets/__init__.py | 1 + .../assets/api.py} | 0 .../assets}/mixin.py | 0 .../api/{asset => }/user_permission/common.py | 0 .../api/{asset => }/user_permission/mixin.py | 0 .../nodes.py} | 0 .../nodes_with_assets.py} | 0 apps/perms/pagination.py | 8 +- 27 files changed, 67 insertions(+), 140 deletions(-) rename apps/assets/api/asset/{common.py => asset.py} (89%) delete mode 100644 apps/perms/api/asset/__init__.py delete mode 100644 apps/perms/api/asset/user_permission/__init__.py delete mode 100644 apps/perms/api/asset/user_permission/user_permission_assets/__init__.py rename apps/perms/api/{asset => }/asset_permission.py (100%) rename apps/perms/api/{asset => }/asset_permission_relation.py (100%) delete mode 100644 apps/perms/api/base.py rename apps/perms/api/{asset => }/user_group_permission.py (99%) create mode 100644 apps/perms/api/user_permission/__init__.py create mode 100644 apps/perms/api/user_permission/assets/__init__.py rename apps/perms/api/{asset/user_permission/user_permission_assets/views.py => user_permission/assets/api.py} (100%) rename apps/perms/api/{asset/user_permission/user_permission_assets => user_permission/assets}/mixin.py (100%) rename apps/perms/api/{asset => }/user_permission/common.py (100%) rename apps/perms/api/{asset => }/user_permission/mixin.py (100%) rename apps/perms/api/{asset/user_permission/user_permission_nodes.py => user_permission/nodes.py} (100%) rename apps/perms/api/{asset/user_permission/user_permission_nodes_with_assets.py => user_permission/nodes_with_assets.py} (100%) diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py index a2d88408d..f9c9ae5e6 100644 --- a/apps/assets/api/asset/__init__.py +++ b/apps/assets/api/asset/__init__.py @@ -1,4 +1,4 @@ -from .common import * +from .asset import * from .host import * from .database import * from .permission import * diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/asset.py similarity index 89% rename from apps/assets/api/asset/common.py rename to apps/assets/api/asset/asset.py index 756ce09ee..18325cb58 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/asset.py @@ -2,18 +2,20 @@ # from rest_framework.decorators import action from rest_framework.response import Response +import django_filters +from common.drf.filters import BaseFilterSet from common.utils import get_logger, get_object_or_none from common.mixins.api import SuggestionMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics -from assets.api import FilterAssetByNodeMixin from assets.models import Asset, Node, Gateway from assets import serializers from assets.tasks import ( update_assets_hardware_info_manual, test_assets_connectivity_manual, ) -from assets.filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend +from assets.filters import NodeFilterBackend, LabelFilterBackend, IpInFilterBackend +from ..mixin import NodeFilterMixin logger = get_logger(__file__) __all__ = [ @@ -21,17 +23,21 @@ __all__ = [ ] -class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet): +class AssetFilterSet(BaseFilterSet): + type = django_filters.CharFilter(field_name='platform__type', lookup_expr='exact') + category = django_filters.CharFilter(field_name='platform__category', lookup_expr='exact') + + class Meta: + model = Asset + fields = ['name', 'ip', 'is_active', 'type', 'category'] + + +class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ model = Asset - filterset_fields = { - 'name': ['exact'], - 'ip': ['exact'], - 'is_active': ['exact'], - 'protocols': ['exact', 'icontains'] - } + filterset_class = AssetFilterSet search_fields = ("name", "ip") ordering_fields = ("name", "ip", "port") ordering = ('name', ) @@ -47,9 +53,9 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) ('gateways', 'assets.view_gateway') ) extra_filter_backends = [ - FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend, + NodeFilterBackend ] def set_assets_node(self, assets): diff --git a/apps/assets/api/asset/database.py b/apps/assets/api/asset/database.py index 786ff06a3..820812d18 100644 --- a/apps/assets/api/asset/database.py +++ b/apps/assets/api/asset/database.py @@ -1,7 +1,7 @@ from assets.models import Database from assets.serializers import DatabaseSerializer -from .common import AssetViewSet +from .asset import AssetViewSet __all__ = ['DatabaseViewSet'] diff --git a/apps/assets/api/asset/host.py b/apps/assets/api/asset/host.py index a27d731f2..3094e15a8 100644 --- a/apps/assets/api/asset/host.py +++ b/apps/assets/api/asset/host.py @@ -1,7 +1,7 @@ from assets.models import Host from assets.serializers import HostSerializer -from .common import AssetViewSet +from .asset import AssetViewSet __all__ = ['HostViewSet'] diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index d814ce11f..d7d33e17b 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -1,10 +1,10 @@ from typing import List +from rest_framework.request import Request -from common.utils.common import timeit +from common.utils import lazyproperty, timeit from assets.models import Node, Asset from assets.pagination import NodeAssetTreePagination -from common.utils import lazyproperty -from assets.utils import get_node, is_query_node_all_assets +from assets.utils import get_node_from_request, is_query_node_all_assets class SerializeToTreeNodeMixin: @@ -80,8 +80,9 @@ class SerializeToTreeNodeMixin: return data -class FilterAssetByNodeMixin: +class NodeFilterMixin: pagination_class = NodeAssetTreePagination + request: Request @lazyproperty def is_query_node_all_assets(self): @@ -89,4 +90,4 @@ class FilterAssetByNodeMixin: @lazyproperty def node(self): - return get_node(self.request) + return get_node_from_request(self.request) diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 346c507b1..dd8bf80a4 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -6,7 +6,7 @@ from rest_framework import filters from django.db.models import Q from .models import Label -from assets.utils import is_query_node_all_assets, get_node +from assets.utils import is_query_node_all_assets, get_node_from_request class AssetByNodeFilterBackend(filters.BaseFilterBackend): @@ -31,7 +31,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend): return queryset.filter(nodes__key=node.key).distinct() def filter_queryset(self, request, queryset, view): - node = get_node(request) + node = get_node_from_request(request) if node is None: return queryset @@ -42,9 +42,9 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend): return self.filter_node_related_direct(queryset, node) -class FilterAssetByNodeFilterBackend(filters.BaseFilterBackend): +class NodeFilterBackend(filters.BaseFilterBackend): """ - 需要与 `assets.api.mixin.FilterAssetByNodeMixin` 配合使用 + 需要与 `assets.api.mixin.NodeFilterMixin` 配合使用 """ fields = ['node', 'all'] @@ -58,10 +58,11 @@ class FilterAssetByNodeFilterBackend(filters.BaseFilterBackend): ] def filter_queryset(self, request, queryset, view): - node = view.node + node = get_node_from_request(request) if node is None: return queryset - query_all = view.is_query_node_all_assets + + query_all = is_query_node_all_assets(request) if query_all: return queryset.filter( Q(nodes__key__istartswith=f'{node.key}:') | @@ -94,6 +95,9 @@ class LabelFilterBackend(filters.BaseFilterBackend): for kv in labels_query: if '#' in kv: self.sep = '#' + break + + for kv in labels_query: if self.sep not in kv: continue key, value = kv.strip().split(self.sep)[:2] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index dd8d07dd1..9c147e966 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -9,6 +9,7 @@ from functools import reduce from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from orgs.mixins.models import OrgManager, JMSOrgBaseModel from ..platform import Platform from ..base import AbsConnectivity @@ -110,11 +111,11 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): names.append(n.name + ':' + n.value) return names - @property + @lazyproperty def type(self): return self.platform.type - @property + @lazyproperty def category(self): return self.platform.category diff --git a/apps/assets/pagination.py b/apps/assets/pagination.py index f913e9eed..8ae42ef16 100644 --- a/apps/assets/pagination.py +++ b/apps/assets/pagination.py @@ -8,6 +8,9 @@ logger = get_logger(__name__) class AssetPaginationBase(LimitOffsetPagination): + _request = None + _view = None + _user = None def init_attrs(self, queryset, request: Request, view=None): self._request = request @@ -28,7 +31,8 @@ class AssetPaginationBase(LimitOffsetPagination): } for k, v in self._request.query_params.items(): if k not in exclude_query_params and v is not None: - logger.warn(f'Not hit node.assets_amount because find a unknow query_param `{k}` -> {self._request.get_full_path()}') + logger.warn(f'Not hit node.assets_amount because find a unknown query_param ' + f'`{k}` -> {self._request.get_full_path()}') return super().get_count(queryset) node_assets_count = self.get_count_from_nodes(queryset) if node_assets_count is None: @@ -49,4 +53,4 @@ class NodeAssetTreePagination(AssetPaginationBase): node = Node.org_root() if node: logger.debug(f'Hit node assets_amount cache: [{node.assets_amount}]') - return node.assets_amount + return node.assets_amount diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 284ec2e0d..778137e23 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -2,6 +2,7 @@ # from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.fields import ChoiceDisplayField @@ -107,7 +108,9 @@ class AssetSerializer(JMSWritableNestedModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('domain', 'platform', 'protocols') + queryset = queryset.prefetch_related('domain', 'platform', 'protocols')\ + .annotate(category=F("platform__category"))\ + .annotate(type=F("platform__type")) queryset = queryset.prefetch_related('nodes', 'labels') return queryset diff --git a/apps/assets/utils.py b/apps/assets/utils.py index c9857f802..478b2187d 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -53,7 +53,7 @@ def is_query_node_all_assets(request): return is_true(query_all_arg) -def get_node(request): +def get_node_from_request(request): node_id = dict_get_any(request.query_params, ['node', 'node_id']) if not node_id: return None diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index e5e3698dd..67986edc6 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- # -from .asset import * +from .user_permission import * +from .asset_permission import * +from .asset_permission_relation import * +from .user_group_permission import * diff --git a/apps/perms/api/asset/__init__.py b/apps/perms/api/asset/__init__.py deleted file mode 100644 index 49c998e95..000000000 --- a/apps/perms/api/asset/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .user_permission import * -from .asset_permission import * -from .asset_permission_relation import * -from .user_group_permission import * diff --git a/apps/perms/api/asset/user_permission/__init__.py b/apps/perms/api/asset/user_permission/__init__.py deleted file mode 100644 index 590235cc6..000000000 --- a/apps/perms/api/asset/user_permission/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -# -from .common import * -from .user_permission_nodes import * -from .user_permission_assets import * -from .user_permission_nodes_with_assets import * diff --git a/apps/perms/api/asset/user_permission/user_permission_assets/__init__.py b/apps/perms/api/asset/user_permission/user_permission_assets/__init__.py deleted file mode 100644 index 6b274abdd..000000000 --- a/apps/perms/api/asset/user_permission/user_permission_assets/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .views import * diff --git a/apps/perms/api/asset/asset_permission.py b/apps/perms/api/asset_permission.py similarity index 100% rename from apps/perms/api/asset/asset_permission.py rename to apps/perms/api/asset_permission.py diff --git a/apps/perms/api/asset/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py similarity index 100% rename from apps/perms/api/asset/asset_permission_relation.py rename to apps/perms/api/asset_permission_relation.py diff --git a/apps/perms/api/base.py b/apps/perms/api/base.py deleted file mode 100644 index 0e285796e..000000000 --- a/apps/perms/api/base.py +++ /dev/null @@ -1,95 +0,0 @@ -from django.db.models import Q -from common.utils import get_object_or_none -from orgs.mixins.api import OrgBulkModelViewSet -from assets.models import SystemUser -from users.models import User, UserGroup - - -__all__ = ['BasePermissionViewSet'] - - -class BasePermissionViewSet(OrgBulkModelViewSet): - custom_filter_fields = [ - 'user_id', 'username', 'system_user_id', 'system_user', - 'user_group_id', 'user_group' - ] - - def filter_valid(self, queryset): - valid_query = self.request.query_params.get('is_valid', None) - if valid_query is None: - return queryset - invalid = valid_query in ['0', 'N', 'false', 'False'] - if invalid: - queryset = queryset.invalid() - else: - queryset = queryset.valid() - return queryset - - def is_query_all(self): - query_all = self.request.query_params.get('all', '1') == '1' - return query_all - - def filter_user(self, queryset): - user_id = self.request.query_params.get('user_id') - username = self.request.query_params.get('username') - if user_id: - user = get_object_or_none(User, pk=user_id) - elif username: - user = get_object_or_none(User, username=username) - else: - return queryset - if not user: - return queryset.none() - if not self.is_query_all(): - queryset = queryset.filter(users=user) - return queryset - groups = list(user.groups.all().values_list('id', flat=True)) - queryset = queryset.filter( - Q(users=user) | Q(user_groups__in=groups) - ).distinct() - return queryset - - def filter_keyword(self, queryset): - keyword = self.request.query_params.get('search') - if not keyword: - return queryset - queryset = queryset.filter(name__icontains=keyword) - return queryset - - def filter_system_user(self, queryset): - system_user_id = self.request.query_params.get('system_user_id') - system_user_name = self.request.query_params.get('system_user') - if system_user_id: - system_user = get_object_or_none(SystemUser, pk=system_user_id) - elif system_user_name: - system_user = get_object_or_none(SystemUser, name=system_user_name) - else: - return queryset - if not system_user: - return queryset.none() - queryset = queryset.filter(system_users=system_user) - return queryset - - def filter_user_group(self, queryset): - user_group_id = self.request.query_params.get('user_group_id') - user_group_name = self.request.query_params.get('user_group') - if user_group_id: - group = get_object_or_none(UserGroup, pk=user_group_id) - elif user_group_name: - group = get_object_or_none(UserGroup, name=user_group_name) - else: - return queryset - if not group: - return queryset.none() - queryset = queryset.filter(user_groups=group) - return queryset - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_valid(queryset) - queryset = self.filter_user(queryset) - queryset = self.filter_system_user(queryset) - queryset = self.filter_user_group(queryset) - queryset = self.filter_keyword(queryset) - queryset = queryset.distinct() - return queryset diff --git a/apps/perms/api/asset/user_group_permission.py b/apps/perms/api/user_group_permission.py similarity index 99% rename from apps/perms/api/asset/user_group_permission.py rename to apps/perms/api/user_group_permission.py index 5c9e0d47e..ca090f77b 100644 --- a/apps/perms/api/asset/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -9,7 +9,7 @@ from rest_framework.response import Response from common.utils import lazyproperty from perms.models import AssetPermission from assets.models import Asset, Node -from perms.api.asset import user_permission as uapi +from . import user_permission as uapi from perms import serializers from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_group from assets.api.mixin import SerializeToTreeNodeMixin diff --git a/apps/perms/api/user_permission/__init__.py b/apps/perms/api/user_permission/__init__.py new file mode 100644 index 000000000..47f3e84a3 --- /dev/null +++ b/apps/perms/api/user_permission/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# +from .common import * +from .nodes import * +from .assets import * +from .nodes_with_assets import * diff --git a/apps/perms/api/user_permission/assets/__init__.py b/apps/perms/api/user_permission/assets/__init__.py new file mode 100644 index 000000000..0a0e47b0b --- /dev/null +++ b/apps/perms/api/user_permission/assets/__init__.py @@ -0,0 +1 @@ +from .api import * diff --git a/apps/perms/api/asset/user_permission/user_permission_assets/views.py b/apps/perms/api/user_permission/assets/api.py similarity index 100% rename from apps/perms/api/asset/user_permission/user_permission_assets/views.py rename to apps/perms/api/user_permission/assets/api.py diff --git a/apps/perms/api/asset/user_permission/user_permission_assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py similarity index 100% rename from apps/perms/api/asset/user_permission/user_permission_assets/mixin.py rename to apps/perms/api/user_permission/assets/mixin.py diff --git a/apps/perms/api/asset/user_permission/common.py b/apps/perms/api/user_permission/common.py similarity index 100% rename from apps/perms/api/asset/user_permission/common.py rename to apps/perms/api/user_permission/common.py diff --git a/apps/perms/api/asset/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py similarity index 100% rename from apps/perms/api/asset/user_permission/mixin.py rename to apps/perms/api/user_permission/mixin.py diff --git a/apps/perms/api/asset/user_permission/user_permission_nodes.py b/apps/perms/api/user_permission/nodes.py similarity index 100% rename from apps/perms/api/asset/user_permission/user_permission_nodes.py rename to apps/perms/api/user_permission/nodes.py diff --git a/apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py b/apps/perms/api/user_permission/nodes_with_assets.py similarity index 100% rename from apps/perms/api/asset/user_permission/user_permission_nodes_with_assets.py rename to apps/perms/api/user_permission/nodes_with_assets.py diff --git a/apps/perms/pagination.py b/apps/perms/pagination.py index 248958a3e..622306924 100644 --- a/apps/perms/pagination.py +++ b/apps/perms/pagination.py @@ -9,6 +9,8 @@ logger = get_logger(__name__) class GrantedAssetPaginationBase(AssetPaginationBase): + _user: object + def init_attrs(self, queryset, request: Request, view=None): super().init_attrs(queryset, request, view) self._user = view.user @@ -18,10 +20,12 @@ class NodeGrantedAssetPagination(GrantedAssetPaginationBase): def get_count_from_nodes(self, queryset): node = getattr(self._view, 'pagination_node', None) if node: - logger.debug(f'Hit node.assets_amount[{node.assets_amount}] -> {self._request.get_full_path()}') + logger.debug(f'Hit node.assets_amount[{node.assets_amount}] -> ' + f'{self._request.get_full_path()}') return node.assets_amount else: - logger.warn(f'Not hit node.assets_amount[{node}] because {self._view} not has `pagination_node` -> {self._request.get_full_path()}') + logger.warn(f'Not hit node.assets_amount[{node}] because {self._view} ' + f'not has `pagination_node` -> {self._request.get_full_path()}') return None From 97c6e2c0b21f9b86e0062ab0d4509d47cc3fee0f Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 22 Aug 2022 18:48:07 +0800 Subject: [PATCH 070/488] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dmigrate=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0099_auto_20220426_1558.py | 3 +++ apps/assets/serializers/account_template.py | 5 ++++- apps/assets/serializers/base.py | 3 ++- apps/authentication/migrations/0012_auto_20220816_1629.py | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index 8d888cf6b..6d3bb23a4 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -18,6 +18,9 @@ def create_app_platform(apps, *args): {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, {'name': 'Redis', 'category': 'database', 'type': 'redis'}, {'name': 'Chrome', 'category': 'remote_app', 'type': 'chrome'}, + {'name': 'MysqlWorkbench', 'category': 'remote_app', 'type': 'mysql_workbench'}, + {'name': 'VmwareClient', 'category': 'remote_app', 'type': 'vmware_client'}, + {'name': 'General', 'category': 'remote_app', 'type': 'general_remote_app'}, {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, ] diff --git a/apps/assets/serializers/account_template.py b/apps/assets/serializers/account_template.py index ba25ac8dd..0762789fd 100644 --- a/apps/assets/serializers/account_template.py +++ b/apps/assets/serializers/account_template.py @@ -8,12 +8,15 @@ from .account import AccountSerializer class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class Meta: model = AccountTemplate - fields_mini = ['id', 'privileged', 'username'] + fields_mini = ['id', 'privileged', 'username', 'name'] fields_write_only = AccountSerializer.Meta.fields_write_only fields_other = AccountSerializer.Meta.fields_other fields = fields_mini + fields_write_only + fields_other extra_kwargs = AccountSerializer.Meta.extra_kwargs def validate(self, attrs): + print(attrs) + + raise ValueError('test') attrs = self._validate_gen_key(attrs) return attrs diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index aba1ddfe8..9e4192fb3 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -75,7 +75,8 @@ class AuthSerializerMixin(serializers.ModelSerializer): validated_data.pop(field, None) validated_data.pop('passphrase', None) - def _validate_gen_key(self, attrs): + @staticmethod + def _validate_gen_key(attrs): private_key = attrs.get('private_key') if not private_key: return attrs diff --git a/apps/authentication/migrations/0012_auto_20220816_1629.py b/apps/authentication/migrations/0012_auto_20220816_1629.py index dac9d1302..6e22b0e0f 100644 --- a/apps/authentication/migrations/0012_auto_20220816_1629.py +++ b/apps/authentication/migrations/0012_auto_20220816_1629.py @@ -10,7 +10,7 @@ def migrate_system_user_to_account(apps, schema_editor): while True: connection_tokens = connection_token_model.objects \ - .prefetch_related('system_users')[count:bulk_size] + .prefetch_related('system_user')[count:bulk_size] if not connection_tokens: break count += len(connection_tokens) From ab461940394d556f9ca3c5f1ff7874f1cd6acdd0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 23 Aug 2022 10:23:48 +0800 Subject: [PATCH 071/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20=E5=AF=BC?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/database.py | 7 +--- apps/assets/api/asset/network.py | 15 ++++++++ apps/assets/api/mixin.py | 2 +- apps/assets/serializers/asset/category.py | 36 +++++++------------ apps/authentication/models.py | 2 +- apps/perms/api/asset_permission_relation.py | 2 +- apps/perms/api/user_group_permission.py | 2 +- .../perms/api/user_permission/assets/mixin.py | 2 +- apps/perms/api/user_permission/common.py | 2 +- apps/perms/api/user_permission/mixin.py | 2 +- apps/perms/api/user_permission/nodes.py | 2 +- .../api/user_permission/nodes_with_assets.py | 2 +- apps/perms/signal_handlers/refresh_perms.py | 2 +- apps/perms/tasks.py | 2 +- apps/perms/utils/__init__.py | 6 ++-- apps/perms/utils/asset/__init__.py | 2 -- apps/perms/utils/{asset => }/permission.py | 2 +- .../utils/{asset => }/user_permission.py | 5 ++- 18 files changed, 47 insertions(+), 48 deletions(-) create mode 100644 apps/assets/api/asset/network.py delete mode 100644 apps/perms/utils/asset/__init__.py rename apps/perms/utils/{asset => }/permission.py (97%) rename apps/perms/utils/{asset => }/user_permission.py (99%) diff --git a/apps/assets/api/asset/database.py b/apps/assets/api/asset/database.py index 820812d18..d4f135cbf 100644 --- a/apps/assets/api/asset/database.py +++ b/apps/assets/api/asset/database.py @@ -1,6 +1,6 @@ - from assets.models import Database from assets.serializers import DatabaseSerializer + from .asset import AssetViewSet __all__ = ['DatabaseViewSet'] @@ -9,11 +9,6 @@ __all__ = ['DatabaseViewSet'] class DatabaseViewSet(AssetViewSet): model = Database - def get_queryset(self): - queryset = super().get_queryset() - print("Datbase is: ", queryset) - return queryset - def get_serializer_classes(self): serializer_classes = super().get_serializer_classes() serializer_classes['default'] = DatabaseSerializer diff --git a/apps/assets/api/asset/network.py b/apps/assets/api/asset/network.py new file mode 100644 index 000000000..15822b1e1 --- /dev/null +++ b/apps/assets/api/asset/network.py @@ -0,0 +1,15 @@ + +from assets.serializers import HostSerializer +from assets.models import Network +from .asset import AssetViewSet + +__all__ = ['NetworkViewSet'] + + +class NetworkViewSet(AssetViewSet): + model = Network + + def get_serializer_classes(self): + serializer_classes = super().get_serializer_classes() + serializer_classes['default'] = HostSerializer + return serializer_classes diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index d7d33e17b..36460a5b1 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -81,7 +81,7 @@ class SerializeToTreeNodeMixin: class NodeFilterMixin: - pagination_class = NodeAssetTreePagination + # pagination_class = NodeAssetTreePagination request: Request @lazyproperty diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 78c3180cd..161dc6152 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -1,10 +1,10 @@ from rest_framework import serializers +from assets.models import DeviceInfo, Host, Database, Network, Cloud from .common import AssetSerializer -from assets.models import DeviceInfo, Host, Database __all__ = [ - 'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer' + 'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer', ] @@ -30,24 +30,14 @@ class HostSerializer(AssetSerializer): class DatabaseSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Database - fields_mini = [ - 'id', 'name', 'ip', 'port', 'db_name', - ] - fields_small = fields_mini + [ - 'is_active', 'comment', - ] - fields_fk = [ - 'domain', 'domain_display', 'platform', - ] - fields_m2m = [ - 'nodes', 'nodes_display', 'labels', 'labels_display', - ] - read_only_fields = [ - 'category', 'category_display', 'type', 'type_display', - 'created_by', 'date_created', - ] - fields = fields_small + fields_fk + fields_m2m + read_only_fields - extra_kwargs = { - **AssetSerializer.Meta.extra_kwargs, - 'db_name': {'required': True} - } + fields = AssetSerializer.Meta.fields + ['db_name'] + + +class NetworkSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Network + + +class CloudSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Cloud diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 97512ff3e..eebaa22a6 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -129,7 +129,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): actions = expired_at = None # actions 和 expired_at 在 check_valid() 中赋值 def check_valid(self): - from perms.utils.asset.permission import validate_permission as asset_validate_permission + from perms.utils.permission import validate_permission as asset_validate_permission from perms.utils.application.permission import validate_permission as app_validate_permission if self.is_expired: diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index ecf0d731b..cb112adfb 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -9,7 +9,7 @@ from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import current_org from perms import serializers from perms import models -from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils +from perms.utils.user_permission import UserGrantedAssetsQueryUtils __all__ = [ 'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet', diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index ca090f77b..4901e98fc 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -11,7 +11,7 @@ from perms.models import AssetPermission from assets.models import Asset, Node from . import user_permission as uapi from perms import serializers -from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_group +from perms.utils.permission import get_asset_system_user_ids_with_actions_by_group from assets.api.mixin import SerializeToTreeNodeMixin from users.models import UserGroup diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index c2d3fa7d3..6df224bee 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -7,7 +7,7 @@ from common.utils import get_logger from perms.pagination import NodeGrantedAssetPagination, AllGrantedAssetPagination from assets.models import Asset, Node from perms import serializers -from perms.utils.asset.user_permission import UserGrantedAssetsQueryUtils +from perms.utils.user_permission import UserGrantedAssetsQueryUtils logger = get_logger(__name__) diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 5e8e839e4..69efb75ef 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -12,7 +12,7 @@ from rest_framework.generics import ( ) from orgs.utils import tmp_to_root_org -from perms.utils.asset.permission import get_asset_system_user_ids_with_actions_by_user, validate_permission +from perms.utils.permission import get_asset_system_user_ids_with_actions_by_user, validate_permission from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index c28da6c2d..c53f548e5 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -7,7 +7,7 @@ from common.mixins.api import RoleAdminMixin as _RoleAdminMixin from common.mixins.api import RoleUserMixin as _RoleUserMixin from orgs.utils import tmp_to_root_org from users.models import User -from perms.utils.asset.user_permission import UserGrantedTreeRefreshController +from perms.utils.user_permission import UserGrantedTreeRefreshController class PermBaseMixin: diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index e2b8aec71..7dcbd85e2 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -13,7 +13,7 @@ from .mixin import AssetRoleAdminMixin, AssetRoleUserMixin from perms.hands import User from perms import serializers -from perms.utils.asset.user_permission import UserGrantedNodesQueryUtils +from perms.utils.user_permission import UserGrantedNodesQueryUtils logger = get_logger(__name__) diff --git a/apps/perms/api/user_permission/nodes_with_assets.py b/apps/perms/api/user_permission/nodes_with_assets.py index 4d4408355..6c848878f 100644 --- a/apps/perms/api/user_permission/nodes_with_assets.py +++ b/apps/perms/api/user_permission/nodes_with_assets.py @@ -11,7 +11,7 @@ from orgs.utils import tmp_to_root_org from common.permissions import IsValidUser from common.utils import get_logger, get_object_or_none from .mixin import AssetRoleUserMixin, AssetRoleAdminMixin -from perms.utils.asset.user_permission import ( +from perms.utils.user_permission import ( UserGrantedTreeBuildUtils, get_user_all_asset_perm_ids, UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, ) diff --git a/apps/perms/signal_handlers/refresh_perms.py b/apps/perms/signal_handlers/refresh_perms.py index fc2d0da1c..2e66c4475 100644 --- a/apps/perms/signal_handlers/refresh_perms.py +++ b/apps/perms/signal_handlers/refresh_perms.py @@ -10,7 +10,7 @@ from common.utils import get_logger from common.exceptions import M2MReverseNotAllowed from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR from perms.models import AssetPermission -from perms.utils.asset.user_permission import UserGrantedTreeRefreshController +from perms.utils.user_permission import UserGrantedTreeRefreshController logger = get_logger(__file__) diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index da96beb68..4cf33b500 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -16,7 +16,7 @@ from perms.notifications import ( PermedAppsWillExpireUserMsg, AppPermsWillExpireForOrgAdminMsg ) from perms.models import AssetPermission, ApplicationPermission -from perms.utils.asset.user_permission import UserGrantedTreeRefreshController +from perms.utils.user_permission import UserGrantedTreeRefreshController logger = get_logger(__file__) diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py index 15277144a..ea3cb14de 100644 --- a/apps/perms/utils/__init__.py +++ b/apps/perms/utils/__init__.py @@ -1,4 +1,2 @@ -# coding: utf-8 -# - -from .asset import * +from .permission import * +from .user_permission import * diff --git a/apps/perms/utils/asset/__init__.py b/apps/perms/utils/asset/__init__.py deleted file mode 100644 index ea3cb14de..000000000 --- a/apps/perms/utils/asset/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .permission import * -from .user_permission import * diff --git a/apps/perms/utils/asset/permission.py b/apps/perms/utils/permission.py similarity index 97% rename from apps/perms/utils/asset/permission.py rename to apps/perms/utils/permission.py index a6a68f6eb..b9037c040 100644 --- a/apps/perms/utils/asset/permission.py +++ b/apps/perms/utils/permission.py @@ -6,7 +6,7 @@ from django.db.models import Q from common.utils import get_logger from perms.models import AssetPermission, Action from perms.hands import Asset, User, UserGroup, Node -from perms.utils.asset.user_permission import get_user_all_asset_perm_ids +from perms.utils.user_permission import get_user_all_asset_perm_ids logger = get_logger(__file__) diff --git a/apps/perms/utils/asset/user_permission.py b/apps/perms/utils/user_permission.py similarity index 99% rename from apps/perms/utils/asset/user_permission.py rename to apps/perms/utils/user_permission.py index 21fd82620..4546e81a0 100644 --- a/apps/perms/utils/asset/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -12,7 +12,10 @@ from common.utils.common import lazyproperty, timeit from assets.utils import NodeAssetsUtil from common.utils import get_logger from common.decorator import on_transaction_commit -from orgs.utils import tmp_to_org, current_org, ensure_in_real_or_default_org, tmp_to_root_org +from orgs.utils import ( + tmp_to_org, current_org, + ensure_in_real_or_default_org, tmp_to_root_org +) from assets.models import ( Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet ) From b1c563b309abcf5759e0d9280848fd87dc1e51f6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 23 Aug 2022 19:15:48 +0800 Subject: [PATCH 072/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20perms=20tas?= =?UTF-8?q?ks=20=E5=BC=95=E7=94=A8=E7=9A=84=20application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/tasks.py | 48 +-------------------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 4cf33b500..b5de6d03e 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -13,9 +13,8 @@ from common.utils.timezone import local_now, dt_formatter, dt_parser from ops.celery.decorator import register_as_period_task from perms.notifications import ( PermedAssetsWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg, - PermedAppsWillExpireUserMsg, AppPermsWillExpireForOrgAdminMsg ) -from perms.models import AssetPermission, ApplicationPermission +from perms.models import AssetPermission from perms.utils.user_permission import UserGrantedTreeRefreshController logger = get_logger(__file__) @@ -101,48 +100,3 @@ def check_asset_permission_will_expired(): org_admins = org.admins.all() for org_admin in org_admins: AssetPermsWillExpireForOrgAdminMsg(org_admin, perms, org, day_count).publish_async() - - -@register_as_period_task(crontab='0 10 * * *') -@shared_task() -@atomic() -@tmp_to_root_org() -def check_app_permission_will_expired(): - start = local_now() - end = start + timedelta(days=3) - - app_perms = ApplicationPermission.objects.filter( - date_expired__gte=start, - date_expired__lte=end - ).distinct() - - user_app_remain_day_mapper = defaultdict(dict) - org_perm_remain_day_mapper = defaultdict(dict) - - for app_perm in app_perms: - date_expired = dt_parser(app_perm.date_expired) - remain_days = (end - date_expired).days - - org = app_perm.org - if org in org_perm_remain_day_mapper[remain_days]: - org_perm_remain_day_mapper[remain_days][org].add(app_perm) - else: - org_perm_remain_day_mapper[remain_days][org] = {app_perm, } - - users = app_perm.get_all_users() - apps = app_perm.applications.all() - for u in users: - if u in user_app_remain_day_mapper[remain_days]: - user_app_remain_day_mapper[remain_days][u].update(apps) - else: - user_app_remain_day_mapper[remain_days][u] = set(apps) - - for day_count, user_app_mapper in user_app_remain_day_mapper.items(): - for user, apps in user_app_mapper.items(): - PermedAppsWillExpireUserMsg(user, apps, day_count).publish_async() - - for day_count, org_perm_mapper in org_perm_remain_day_mapper.items(): - for org, perms in org_perm_mapper.items(): - org_admins = org.admins.all() - for org_admin in org_admins: - AppPermsWillExpireForOrgAdminMsg(org_admin, perms, org, day_count).publish_async() From 83bd8b600ec8b2b9da5cbbe5b5d326975035b6fa Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 23 Aug 2022 19:26:47 +0800 Subject: [PATCH 073/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20=5F=5Finit?= =?UTF-8?q?=5F=5F.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/applications/__init__.py diff --git a/apps/applications/__init__.py b/apps/applications/__init__.py new file mode 100644 index 000000000..e69de29bb From cd8adc6d3df09fe8c525866c94afcbfdbe66fb71 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 24 Aug 2022 10:57:44 +0800 Subject: [PATCH 074/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0100_auto_20220430_2126.py | 4 ++-- .../migrations/0112_auto_20220819_1526.py | 22 ------------------- apps/common/utils/common.py | 16 ++++++++++++++ apps/jumpserver/settings/_xpack.py | 3 +-- 4 files changed, 19 insertions(+), 26 deletions(-) delete mode 100644 apps/assets/migrations/0112_auto_20220819_1526.py diff --git a/apps/assets/migrations/0100_auto_20220430_2126.py b/apps/assets/migrations/0100_auto_20220430_2126.py index 17f6d6da1..78ede0501 100644 --- a/apps/assets/migrations/0100_auto_20220430_2126.py +++ b/apps/assets/migrations/0100_auto_20220430_2126.py @@ -32,8 +32,8 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='platform', - name='protocols_default', - field=models.JSONField(blank=True, default=list, max_length=128, verbose_name='Protocols default'), + name='protocols_enabled', + field=models.BooleanField(default=True, verbose_name='Protocols enabled'), ), migrations.AddField( model_name='platform', diff --git a/apps/assets/migrations/0112_auto_20220819_1526.py b/apps/assets/migrations/0112_auto_20220819_1526.py deleted file mode 100644 index 14e7d7765..000000000 --- a/apps/assets/migrations/0112_auto_20220819_1526.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.13 on 2022-08-19 07:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0111_auto_20220819_1523'), - ] - - operations = [ - migrations.RemoveField( - model_name='platform', - name='protocols_default', - ), - migrations.AddField( - model_name='platform', - name='protocols_enabled', - field=models.BooleanField(default=True, verbose_name='Protocols enabled'), - ), - ] diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 5b2180ec0..854f9f488 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -12,6 +12,7 @@ import ipaddress import psutil import platform import os +import socket from django.conf import settings @@ -365,3 +366,18 @@ def pretty_string(data: str, max_length=128, ellipsis_str='...'): def group_by_count(it, count): return [it[i:i+count] for i in range(0, len(it), count)] + + +def test_ip_connectivity(host, port, timeout=0.5): + """ + timeout: seconds + """ + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + result = sock.connect_ex((host, int(port))) + sock.close() + if result == 0: + connectivity = True + else: + connectivity = False + return connectivity diff --git a/apps/jumpserver/settings/_xpack.py b/apps/jumpserver/settings/_xpack.py index 7a2f34c8d..9f4319a35 100644 --- a/apps/jumpserver/settings/_xpack.py +++ b/apps/jumpserver/settings/_xpack.py @@ -6,8 +6,7 @@ from .. import const from .base import INSTALLED_APPS, TEMPLATES XPACK_DIR = os.path.join(const.BASE_DIR, 'xpack') -XPACK_ENABLED = False -# XPACK_ENABLED = os.path.isdir(XPACK_DIR) +XPACK_ENABLED = os.path.isdir(XPACK_DIR) XPACK_TEMPLATES_DIR = [] XPACK_CONTEXT_PROCESSOR = [] From dca1388a6753cd20b1fd5cf56e6543da619d807b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 24 Aug 2022 14:23:42 +0800 Subject: [PATCH 075/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/mixin.py | 4 +-- apps/assets/api/node.py | 2 +- .../migrations/0096_auto_20220406_1546.py | 30 ------------------- .../migrations/0097_auto_20220407_1726.py | 9 ++---- .../migrations/0099_auto_20220426_1558.py | 7 ++--- apps/assets/models/asset/common.py | 4 +++ apps/perms/filters.py | 30 +++++++++---------- requirements/requirements.txt | 2 ++ 8 files changed, 28 insertions(+), 60 deletions(-) diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 36460a5b1..3a8ccf396 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -41,7 +41,7 @@ class SerializeToTreeNodeMixin: def get_platform(self, asset: Asset): default = 'file' icon = {'windows', 'linux'} - platform = asset.platform_base.lower() + platform = asset.platform.type.lower() if platform in icon: return platform return default @@ -70,7 +70,7 @@ class SerializeToTreeNodeMixin: 'name': asset.name, 'ip': asset.ip, 'protocols': asset.protocols_as_list, - 'platform': asset.platform_base, + 'platform': asset.platform.id, 'org_name': asset.org_name }, } diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 922fd8766..24b5bf04e 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -192,7 +192,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): if not self.instance or not include_assets: return [] assets = self.instance.get_assets().only( - "id", "name", "ip", "os", "platform_id", + "id", "name", "ip", "platform_id", "org_id", "protocols", "is_active", ).prefetch_related('platform') return self.serialize_assets(assets, self.instance.key) diff --git a/apps/assets/migrations/0096_auto_20220406_1546.py b/apps/assets/migrations/0096_auto_20220406_1546.py index e2c9400e4..bfd7bc277 100644 --- a/apps/assets/migrations/0096_auto_20220406_1546.py +++ b/apps/assets/migrations/0096_auto_20220406_1546.py @@ -4,35 +4,6 @@ from itertools import groupby from django.db import migrations from django.db.models import F -category_mapper = { - 'Linux': ('host', 'linux'), - 'Unix': ('host', 'unix'), - 'MacOS': ('host', 'macos'), - 'BSD': ('host', 'unix'), - 'Windows': ('host', 'windows'), - 'Other': ('host', 'other_host'), -} - - -def migrate_category_and_type(apps, *args): - asset_model = apps.get_model('assets', 'Asset') - assets_bases = asset_model.objects.all()\ - .annotate(base=F("platform__base"))\ - .values_list('id', 'base')\ - .order_by('base') - base_assets = groupby(assets_bases, lambda a: a[1]) - - print("") - for base, grouper in base_assets: - asset_ids = [g[0] for g in grouper] - category_type = category_mapper.get(base) - if not category_type: - continue - print("Migrate {} to => {}".format(base, category_type)) - category, tp = category_type - assets = asset_model.objects.filter(id__in=asset_ids) - assets.update(category=category, type=tp) - class Migration(migrations.Migration): @@ -41,5 +12,4 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(migrate_category_and_type) ] diff --git a/apps/assets/migrations/0097_auto_20220407_1726.py b/apps/assets/migrations/0097_auto_20220407_1726.py index bd08c3ad3..a3d0dc053 100644 --- a/apps/assets/migrations/0097_auto_20220407_1726.py +++ b/apps/assets/migrations/0097_auto_20220407_1726.py @@ -26,18 +26,13 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='category', - field=models.CharField(choices=[('host', 'Host'), ('network', 'Networking'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], default='host', max_length=16, verbose_name='Category'), + field=models.CharField(default='host', max_length=16, verbose_name='Category'), preserve_default=False, ), migrations.AlterField( model_name='platform', name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vmware_client', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('general_remote_app', 'Custom'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), - ), - migrations.AlterField( - model_name='systemuser', - name='protocol', - field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol'), + field=models.CharField(default='linux', max_length=32, verbose_name='Type'), ), migrations.RunPython(migrate_platform_type_to_lower) ] diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index 6d3bb23a4..0a6d03e89 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -17,11 +17,8 @@ def create_app_platform(apps, *args): {'name': 'SQLServer', 'category': 'database', 'type': 'sqlserver'}, {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, {'name': 'Redis', 'category': 'database', 'type': 'redis'}, - {'name': 'Chrome', 'category': 'remote_app', 'type': 'chrome'}, - {'name': 'MysqlWorkbench', 'category': 'remote_app', 'type': 'mysql_workbench'}, - {'name': 'VmwareClient', 'category': 'remote_app', 'type': 'vmware_client'}, - {'name': 'General', 'category': 'remote_app', 'type': 'general_remote_app'}, {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, + {'name': 'Web', 'category': 'web', 'type': 'general'}, ] for platform in platforms: @@ -34,7 +31,7 @@ def get_prop_name_id(apps, app, category): asset_model = apps.get_model('assets', 'Asset') _id = app.id id_exists = asset_model.objects.filter(id=_id).exists() - if (id_exists): + if id_exists: _id = uuid.uuid4() name = app.name name_exists = asset_model.objects.filter(hostname=name).exists() diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 17ef90752..1842f5f1b 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -112,6 +112,10 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): names.append(n.name + ':' + n.value) return names + @property + def protocols_as_list(self): + return [{'name': p.name, 'port': p.port} for p in self.protocols.all()] + @lazyproperty def type(self): return self.platform.type diff --git a/apps/perms/filters.py b/apps/perms/filters.py index ac23b691c..c890dbcba 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -94,13 +94,13 @@ class AssetPermissionFilter(PermissionBaseFilter): node_id = filters.UUIDFilter(method='do_nothing') node = filters.CharFilter(method='do_nothing') asset_id = filters.UUIDFilter(method='do_nothing') - hostname = filters.CharFilter(method='do_nothing') + asset_name = filters.CharFilter(method='do_nothing') ip = filters.CharFilter(method='do_nothing') class Meta: model = AssetPermission fields = ( - 'user_id', 'username', 'system_user_id', 'system_user', 'user_group_id', + 'user_id', 'username', 'user_group_id', 'user_group', 'node_id', 'node', 'asset_id', 'name', 'ip', 'name', 'all', 'asset_id', 'is_valid', 'is_effective', 'from_ticket' ) @@ -141,36 +141,36 @@ class AssetPermissionFilter(PermissionBaseFilter): def filter_asset(self, queryset): is_query_all = self.get_query_param('all', True) asset_id = self.get_query_param('asset_id') - hostname = self.get_query_param('name') + asset_name = self.get_query_param('asset_name') ip = self.get_query_param('ip') if asset_id: assets = Asset.objects.filter(pk=asset_id) - elif hostname: - assets = Asset.objects.filter(hostname=hostname) + elif asset_name: + assets = Asset.objects.filter(name=asset_name) elif ip: assets = Asset.objects.filter(ip=ip) else: return queryset if not assets: return queryset.none() - assetids = list(assets.values_list('id', flat=True)) + asset_ids = list(assets.values_list('id', flat=True)) if not is_query_all: - queryset = queryset.filter(assets__in=assetids) + queryset = queryset.filter(assets__in=asset_ids) return queryset - inherit_all_nodekeys = set() - inherit_nodekeys = set(assets.values_list('nodes__key', flat=True)) + inherit_all_node_keys = set() + inherit_node_keys = set(assets.values_list('nodes__key', flat=True)) - for key in inherit_nodekeys: + for key in inherit_node_keys: ancestor_keys = Node.get_node_ancestor_keys(key, with_self=True) - inherit_all_nodekeys.update(ancestor_keys) + inherit_all_node_keys.update(ancestor_keys) - inherit_all_nodeids = Node.objects.filter(key__in=inherit_all_nodekeys).values_list('id', flat=True) - inherit_all_nodeids = list(inherit_all_nodeids) + inherit_all_node_ids = Node.objects.filter(key__in=inherit_all_node_keys).values_list('id', flat=True) + inherit_all_node_ids = list(inherit_all_node_ids) - qs1 = queryset.filter(assets__in=assetids).distinct() - qs2 = queryset.filter(nodes__in=inherit_all_nodeids).distinct() + qs1 = queryset.filter(assets__in=asset_ids).distinct() + qs2 = queryset.filter(nodes__in=inherit_all_node_ids).distinct() qs = UnionQuerySet(qs1, qs2) return qs diff --git a/requirements/requirements.txt b/requirements/requirements.txt index fcb42d5c5..ba1491849 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -137,3 +137,5 @@ ipython==8.4.0 ForgeryPy3==0.3.1 django-debug-toolbar==3.5 Pympler==1.0.1 +IPy==1.1 + From 063c42b94dec8abaefdf30536f8b6101b5e8d3e2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 24 Aug 2022 16:14:32 +0800 Subject: [PATCH 076/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20terminal?= =?UTF-8?q?=20session?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0023_auto_20220817_1716.py | 2 +- apps/assets/const.py | 19 ++-- .../migrations/0095_auto_20220406_1541.py | 26 ------ ...407_1726.py => 0095_auto_20220407_1726.py} | 5 +- .../migrations/0096_auto_20220406_1546.py | 15 --- ...426_1550.py => 0096_auto_20220426_1550.py} | 28 +++--- ...426_1558.py => 0097_auto_20220426_1558.py} | 91 +++++++++---------- ...430_2126.py => 0098_auto_20220430_2126.py} | 14 +-- ...711_1409.py => 0099_auto_20220711_1409.py} | 2 +- ...711_1413.py => 0100_auto_20220711_1413.py} | 2 +- ...803_1448.py => 0101_auto_20220803_1448.py} | 2 +- ...803_1859.py => 0102_auto_20220803_1859.py} | 2 +- ...811_1511.py => 0103_auto_20220811_1511.py} | 26 +++++- ...816_1022.py => 0104_auto_20220816_1022.py} | 2 +- .../migrations/0105_auto_20220810_1449.py | 46 ---------- ...817_1544.py => 0105_auto_20220817_1544.py} | 20 +++- .../migrations/0106_auto_20220811_1449.py | 37 -------- ...819_1523.py => 0106_auto_20220819_1523.py} | 2 +- .../migrations/0110_auto_20220817_1716.py | 32 ------- apps/assets/models/asset/__init__.py | 4 +- .../asset/{network.py => networking.py} | 2 +- apps/assets/models/asset/remote_app.py | 10 -- apps/assets/models/asset/web.py | 8 ++ apps/assets/models/platform.py | 4 +- apps/assets/serializers/asset/category.py | 8 +- apps/terminal/api/session.py | 2 +- .../migrations/0052_auto_20220707_1726.py | 16 +++- apps/terminal/models/session.py | 11 +-- apps/terminal/serializers/session.py | 12 +-- 29 files changed, 167 insertions(+), 283 deletions(-) delete mode 100644 apps/assets/migrations/0095_auto_20220406_1541.py rename apps/assets/migrations/{0097_auto_20220407_1726.py => 0095_auto_20220407_1726.py} (86%) delete mode 100644 apps/assets/migrations/0096_auto_20220406_1546.py rename apps/assets/migrations/{0098_auto_20220426_1550.py => 0096_auto_20220426_1550.py} (83%) rename apps/assets/migrations/{0099_auto_20220426_1558.py => 0097_auto_20220426_1558.py} (74%) rename apps/assets/migrations/{0100_auto_20220430_2126.py => 0098_auto_20220430_2126.py} (82%) rename apps/assets/migrations/{0101_auto_20220711_1409.py => 0099_auto_20220711_1409.py} (99%) rename apps/assets/migrations/{0102_auto_20220711_1413.py => 0100_auto_20220711_1413.py} (97%) rename apps/assets/migrations/{0103_auto_20220803_1448.py => 0101_auto_20220803_1448.py} (97%) rename apps/assets/migrations/{0104_auto_20220803_1859.py => 0102_auto_20220803_1859.py} (97%) rename apps/assets/migrations/{0107_auto_20220811_1511.py => 0103_auto_20220811_1511.py} (62%) rename apps/assets/migrations/{0108_auto_20220816_1022.py => 0104_auto_20220816_1022.py} (97%) delete mode 100644 apps/assets/migrations/0105_auto_20220810_1449.py rename apps/assets/migrations/{0109_auto_20220817_1544.py => 0105_auto_20220817_1544.py} (66%) delete mode 100644 apps/assets/migrations/0106_auto_20220811_1449.py rename apps/assets/migrations/{0111_auto_20220819_1523.py => 0106_auto_20220819_1523.py} (97%) delete mode 100644 apps/assets/migrations/0110_auto_20220817_1716.py rename apps/assets/models/asset/{network.py => networking.py} (60%) delete mode 100644 apps/assets/models/asset/remote_app.py create mode 100644 apps/assets/models/asset/web.py diff --git a/apps/applications/migrations/0023_auto_20220817_1716.py b/apps/applications/migrations/0023_auto_20220817_1716.py index b8b235fe5..b506fcc3b 100644 --- a/apps/applications/migrations/0023_auto_20220817_1716.py +++ b/apps/applications/migrations/0023_auto_20220817_1716.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): ('applications', '0022_auto_20220817_1346'), ('perms', '0031_auto_20220816_1600'), ('ops', '0022_auto_20220817_1346'), - ('assets', '0109_auto_20220817_1544'), + ('assets', '0105_auto_20220817_1544'), ('tickets', '0020_auto_20220817_1346'), ] diff --git a/apps/assets/const.py b/apps/assets/const.py index f2ec1a882..204bdcb6b 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -5,7 +5,7 @@ from common.tree import TreeNode __all__ = [ - 'Category', 'HostTypes', 'NetworkTypes', 'DatabaseTypes', + 'Category', 'HostTypes', 'NetworkingTypes', 'DatabaseTypes', 'WebTypes', 'CloudTypes', 'Protocol', 'AllTypes', ] @@ -26,7 +26,7 @@ class PlatformMixin: class Category(PlatformMixin, ChoicesMixin, models.TextChoices): HOST = 'host', _('Host') - NETWORK = 'network', _("NetworkDevice") + NETWORKING = 'networking', _("NetworkDevice") DATABASE = 'database', _("Database") CLOUD = 'cloud', _("Clouding") WEB = 'web', _("Web") @@ -42,7 +42,7 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): 'has_create_account': True, '_protocols': ['ssh', 'telnet'] }, - cls.NETWORK: { + cls.NETWORKING: { 'has_domain': True, '_protocols': ['ssh', 'telnet'] }, @@ -84,7 +84,7 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): } -class NetworkTypes(PlatformMixin, ChoicesMixin, models.TextChoices): +class NetworkingTypes(PlatformMixin, ChoicesMixin, models.TextChoices): SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") FIREWALL = 'firewall', _("Firewall") @@ -121,7 +121,7 @@ class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): choices: list includes = [ - HostTypes, NetworkTypes, DatabaseTypes, + HostTypes, NetworkingTypes, DatabaseTypes, WebTypes, CloudTypes ] @@ -150,7 +150,7 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): def category_types(cls): return ( (Category.HOST, HostTypes), - (Category.NETWORK, NetworkTypes), + (Category.NETWORKING, NetworkingTypes), (Category.DATABASE, DatabaseTypes), (Category.WEB, WebTypes), (Category.CLOUD, CloudTypes) @@ -217,6 +217,8 @@ class Protocol(ChoicesMixin, models.TextChoices): mongodb = 'mongodb', 'MongoDB' k8s = 'k8s', 'K8S' + http = 'http', 'HTTP' + https = 'https', 'HTTPS' @classmethod def host_protocols(cls): @@ -245,6 +247,9 @@ class Protocol(ChoicesMixin, models.TextChoices): cls.mongodb: 27017, cls.redis: 6379, - cls.k8s: 0 + cls.k8s: 0, + + cls.http: 80, + cls.https: 443 } diff --git a/apps/assets/migrations/0095_auto_20220406_1541.py b/apps/assets/migrations/0095_auto_20220406_1541.py deleted file mode 100644 index 7b43c92a1..000000000 --- a/apps/assets/migrations/0095_auto_20220406_1541.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 3.1.14 on 2022-04-06 07:41 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0094_auto_20220402_1736'), - ] - - operations = [ - migrations.AddField( - model_name='asset', - name='category', - field=models.CharField(choices=[('host', 'Host'), ('network', 'Networking'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], default='host', max_length=16, verbose_name='Category'), - preserve_default=False, - ), - migrations.AddField( - model_name='asset', - name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('chrome', 'Chrome'), ('vmware_client', 'vSphere client'), ('mysql_workbench', 'MySQL workbench'), ('general_remote_app', 'Custom'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), - preserve_default=False, - ), - ] diff --git a/apps/assets/migrations/0097_auto_20220407_1726.py b/apps/assets/migrations/0095_auto_20220407_1726.py similarity index 86% rename from apps/assets/migrations/0097_auto_20220407_1726.py rename to apps/assets/migrations/0095_auto_20220407_1726.py index a3d0dc053..fb20373cc 100644 --- a/apps/assets/migrations/0097_auto_20220407_1726.py +++ b/apps/assets/migrations/0095_auto_20220407_1726.py @@ -14,7 +14,7 @@ def migrate_platform_type_to_lower(apps, *args): class Migration(migrations.Migration): dependencies = [ - ('assets', '0096_auto_20220406_1546'), + ('assets', '0094_auto_20220402_1736'), ] operations = [ @@ -26,8 +26,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='category', - field=models.CharField(default='host', max_length=16, verbose_name='Category'), - preserve_default=False, + field=models.CharField(default='host', max_length=32, verbose_name='Category'), ), migrations.AlterField( model_name='platform', diff --git a/apps/assets/migrations/0096_auto_20220406_1546.py b/apps/assets/migrations/0096_auto_20220406_1546.py deleted file mode 100644 index bfd7bc277..000000000 --- a/apps/assets/migrations/0096_auto_20220406_1546.py +++ /dev/null @@ -1,15 +0,0 @@ -# Generated by Django 3.1.14 on 2022-04-06 07:46 -from itertools import groupby - -from django.db import migrations -from django.db.models import F - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0095_auto_20220406_1541'), - ] - - operations = [ - ] diff --git a/apps/assets/migrations/0098_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py similarity index 83% rename from apps/assets/migrations/0098_auto_20220426_1550.py rename to apps/assets/migrations/0096_auto_20220426_1550.py index bbce73dec..83b2aa141 100644 --- a/apps/assets/migrations/0098_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('assets', '0097_auto_20220407_1726'), + ('assets', '0095_auto_20220407_1726'), ] operations = [ @@ -23,7 +23,7 @@ class Migration(migrations.Migration): bases=('assets.asset',), ), migrations.CreateModel( - name='Network', + name='Networking', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), ], @@ -32,19 +32,6 @@ class Migration(migrations.Migration): }, bases=('assets.asset',), ), - migrations.CreateModel( - name='RemoteApp', - fields=[ - ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('connect_host', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.host')), - ('app_path', models.CharField(max_length=1024, verbose_name='App path')), - ('attrs', models.JSONField(default=dict, verbose_name='Attrs')), - ], - options={ - 'abstract': False, - }, - bases=('assets.asset',), - ), migrations.CreateModel( name='Cloud', fields=[ @@ -56,4 +43,15 @@ class Migration(migrations.Migration): }, bases=('assets.asset',), ), + migrations.CreateModel( + name='Web', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('url', models.CharField(max_length=1024, verbose_name='url')), + ], + options={ + 'abstract': False, + }, + bases=('assets.asset',), + ), ] diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0097_auto_20220426_1558.py similarity index 74% rename from apps/assets/migrations/0099_auto_20220426_1558.py rename to apps/assets/migrations/0097_auto_20220426_1558.py index 0a6d03e89..df925852f 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0097_auto_20220426_1558.py @@ -57,7 +57,6 @@ def migrate_database_to_asset(apps, *args): db = db_model( id=app.id, hostname=app.name, ip=attrs['host'], protocols='{}/{}'.format(app.type, attrs['port']), - category='database', type=app.type, db_name=attrs['database'] or '', platform=platforms_map[app.type], org_id=app.org_id @@ -69,48 +68,48 @@ def migrate_database_to_asset(apps, *args): failed_apps.append(app) pass - -def migrate_remote_app_to_asset(apps, *args): - app_model = apps.get_model('applications', 'Application') - remote_app_model = apps.get_model('assets', 'RemoteApp') - host_model = apps.get_model('assets', 'Host') - platform_model = apps.get_model('assets', 'Platform') - applications = app_model.objects.filter(category='remote_app') - platforms = platform_model.objects.filter(category='remote_app') - platforms_map = {p.type: p for p in platforms} - - connect_host_map = {} - - for app in applications: - attrs = app.attrs - connect_host = attrs.pop('asset') - if connect_host: - connect_host = host_model.objects.filter(asset_ptr_id=connect_host).first() - connect_host_map[app.id] = connect_host - - for app in applications: - tp = app.type - app_path = attrs.pop('path', '') - if tp == 'custom': - tp = 'general_remote_app' - - print("Create remote app: {}".format(app.name)) - remote_app = remote_app_model( - id=app.id, hostname=app.name, ip='', - protocols='', - category='remote_app', type=tp, - platform=platforms_map[tp], - org_id=app.org_id, - - app_path=app_path, - connect_host=connect_host_map.get(app.id), - attrs=attrs, - ) - try: - remote_app.save() - except Exception as e: - print("Error: ", e) - # remote_app.hostname = 'RemoteApp-' + remote_app.hostname +# +# def migrate_remote_app_to_asset(apps, *args): +# app_model = apps.get_model('applications', 'Application') +# remote_app_model = apps.get_model('assets', 'Web') +# host_model = apps.get_model('assets', 'Host') +# platform_model = apps.get_model('assets', 'Platform') +# applications = app_model.objects.filter(category='remote_app') +# platforms = platform_model.objects.filter(category='remote_app') +# platforms_map = {p.type: p for p in platforms} +# +# connect_host_map = {} +# +# for app in applications: +# attrs = app.attrs +# connect_host = attrs.pop('asset') +# if connect_host: +# connect_host = host_model.objects.filter(asset_ptr_id=connect_host).first() +# connect_host_map[app.id] = connect_host +# +# for app in applications: +# tp = app.type +# attrs = app.attrs +# app_path = attrs.pop('path', '') +# if tp == 'custom': +# tp = 'general_remote_app' +# +# print("Create remote app: {}".format(app.name)) +# remote_app = remote_app_model( +# id=app.id, hostname=app.name, ip='', +# protocols='', +# platform=platforms_map[tp], +# org_id=app.org_id, +# app_path=app_path, +# connect_host=connect_host_map.get(app.id), +# attrs=attrs, +# ) +# try: +# remote_app.save() +# except Exception as e: +# print("Error: ", e) +# # remote_app.hostname = 'RemoteApp-' + remote_app.hostname +# def migrate_cloud_to_asset(apps, *args): @@ -126,7 +125,6 @@ def migrate_cloud_to_asset(apps, *args): print("Create cloud: {}".format(app.name)) cloud = cloud_model( id=app.id, hostname=app.name, ip='', - category='remote_app', type='k8s', protocols='', platform=platform, org_id=app.org_id, @@ -176,7 +174,7 @@ def migrate_to_nodes(apps, *args): for org in orgs: node = create_app_nodes(apps, org.id) assets = asset_model.objects.filter( - category__in=['remote_app', 'database', 'cloud'], + platform__category__in=['remote_app', 'database', 'cloud'], org_id=org.id ) if not node: @@ -192,14 +190,13 @@ def migrate_to_nodes(apps, *args): class Migration(migrations.Migration): dependencies = [ - ('assets', '0098_auto_20220426_1550'), + ('assets', '0096_auto_20220426_1550'), ('applications', '0020_auto_20220316_2028') ] operations = [ migrations.RunPython(create_app_platform), migrations.RunPython(migrate_database_to_asset), - migrations.RunPython(migrate_remote_app_to_asset), migrations.RunPython(migrate_cloud_to_asset), migrations.RunPython(migrate_to_nodes) ] diff --git a/apps/assets/migrations/0100_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py similarity index 82% rename from apps/assets/migrations/0100_auto_20220430_2126.py rename to apps/assets/migrations/0098_auto_20220430_2126.py index 78ede0501..6d9100593 100644 --- a/apps/assets/migrations/0100_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('assets', '0099_auto_20220426_1558'), + ('assets', '0097_auto_20220426_1558'), ] operations = [ @@ -73,7 +73,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='su_enabled', - field=models.BooleanField(default=False), + field=models.BooleanField(default=False, verbose_name='Su enabled'), ), migrations.AddField( model_name='platform', @@ -90,14 +90,4 @@ class Migration(migrations.Migration): name='verify_account_method', field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method'), ), - migrations.AlterField( - model_name='asset', - name='category', - field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], max_length=16, verbose_name='Category'), - ), - migrations.AlterField( - model_name='platform', - name='category', - field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('remote_app', 'Remote app'), ('cloud', 'Clouding')], max_length=16, verbose_name='Category'), - ), ] diff --git a/apps/assets/migrations/0101_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py similarity index 99% rename from apps/assets/migrations/0101_auto_20220711_1409.py rename to apps/assets/migrations/0099_auto_20220711_1409.py index 30dd8c51f..674b1c447 100644 --- a/apps/assets/migrations/0101_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0100_auto_20220430_2126'), + ('assets', '0098_auto_20220430_2126'), ] operations = [ diff --git a/apps/assets/migrations/0102_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py similarity index 97% rename from apps/assets/migrations/0102_auto_20220711_1413.py rename to apps/assets/migrations/0100_auto_20220711_1413.py index d2cecc858..aed674b36 100644 --- a/apps/assets/migrations/0102_auto_20220711_1413.py +++ b/apps/assets/migrations/0100_auto_20220711_1413.py @@ -55,7 +55,7 @@ def migrate_accounts(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('assets', '0101_auto_20220711_1409'), + ('assets', '0099_auto_20220711_1409'), ] operations = [ diff --git a/apps/assets/migrations/0103_auto_20220803_1448.py b/apps/assets/migrations/0101_auto_20220803_1448.py similarity index 97% rename from apps/assets/migrations/0103_auto_20220803_1448.py rename to apps/assets/migrations/0101_auto_20220803_1448.py index 7af3514e2..26f4dc36b 100644 --- a/apps/assets/migrations/0103_auto_20220803_1448.py +++ b/apps/assets/migrations/0101_auto_20220803_1448.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('assets', '0102_auto_20220711_1413'), + ('assets', '0100_auto_20220711_1413'), ] operations = [ diff --git a/apps/assets/migrations/0104_auto_20220803_1859.py b/apps/assets/migrations/0102_auto_20220803_1859.py similarity index 97% rename from apps/assets/migrations/0104_auto_20220803_1859.py rename to apps/assets/migrations/0102_auto_20220803_1859.py index ad5d99ef5..e220da05a 100644 --- a/apps/assets/migrations/0104_auto_20220803_1859.py +++ b/apps/assets/migrations/0102_auto_20220803_1859.py @@ -44,7 +44,7 @@ def migrate_asset_protocols(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('assets', '0103_auto_20220803_1448'), + ('assets', '0101_auto_20220803_1448'), ] operations = [ diff --git a/apps/assets/migrations/0107_auto_20220811_1511.py b/apps/assets/migrations/0103_auto_20220811_1511.py similarity index 62% rename from apps/assets/migrations/0107_auto_20220811_1511.py rename to apps/assets/migrations/0103_auto_20220811_1511.py index c95e336e3..3a4504e46 100644 --- a/apps/assets/migrations/0107_auto_20220811_1511.py +++ b/apps/assets/migrations/0103_auto_20220811_1511.py @@ -1,15 +1,37 @@ # Generated by Django 3.2.14 on 2022-08-11 07:11 - +import assets.models.platform +import django.db.models from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('assets', '0106_auto_20220811_1449'), + ('assets', '0102_auto_20220803_1859'), ] operations = [ + migrations.AlterField( + model_name='asset', + name='platform', + field=models.ForeignKey(default=assets.models.platform.Platform.default, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.platform', verbose_name='Platform'), + ), + migrations.RemoveField( + model_name='asset', + name='_protocols', + ), + migrations.RemoveField( + model_name='asset', + name='admin_user', + ), + migrations.RemoveField( + model_name='asset', + name='number', + ), + migrations.RemoveField( + model_name='asset', + name='public_ip', + ), migrations.AlterModelOptions( name='asset', options={'ordering': ['name'], 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), ('test_assetconnectivity', 'Can test asset connectivity'), ('push_assetsystemuser', 'Can push system user to asset'), ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'), ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'}, diff --git a/apps/assets/migrations/0108_auto_20220816_1022.py b/apps/assets/migrations/0104_auto_20220816_1022.py similarity index 97% rename from apps/assets/migrations/0108_auto_20220816_1022.py rename to apps/assets/migrations/0104_auto_20220816_1022.py index 90606c2bf..45523495e 100644 --- a/apps/assets/migrations/0108_auto_20220816_1022.py +++ b/apps/assets/migrations/0104_auto_20220816_1022.py @@ -46,7 +46,7 @@ def migrate_command_filter_apps(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('assets', '0107_auto_20220811_1511'), + ('assets', '0103_auto_20220811_1511'), ] operations = [ diff --git a/apps/assets/migrations/0105_auto_20220810_1449.py b/apps/assets/migrations/0105_auto_20220810_1449.py deleted file mode 100644 index a305c8156..000000000 --- a/apps/assets/migrations/0105_auto_20220810_1449.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-10 06:49 - -import assets.models.platform -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0104_auto_20220803_1859'), - ] - - operations = [ - migrations.AlterField( - model_name='asset', - name='category', - field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('cloud', 'Clouding'), ('web', 'Web')], max_length=16, verbose_name='Category'), - ), - migrations.AlterField( - model_name='asset', - name='platform', - field=models.ForeignKey(default=assets.models.platform.Platform.default, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.platform', verbose_name='Platform'), - ), - migrations.AlterField( - model_name='asset', - name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('general', 'General'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), - ), - migrations.AlterField( - model_name='platform', - name='category', - field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('cloud', 'Clouding'), ('web', 'Web')], default='host', max_length=16, verbose_name='Category'), - ), - migrations.AlterField( - model_name='platform', - name='type', - field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('general', 'General'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), - ), - migrations.AlterField( - model_name='platform', - name='su_enabled', - field=models.BooleanField(default=False, verbose_name='Su enabled'), - ), - ] diff --git a/apps/assets/migrations/0109_auto_20220817_1544.py b/apps/assets/migrations/0105_auto_20220817_1544.py similarity index 66% rename from apps/assets/migrations/0109_auto_20220817_1544.py rename to apps/assets/migrations/0105_auto_20220817_1544.py index d9ca483fa..9aacc5a35 100644 --- a/apps/assets/migrations/0109_auto_20220817_1544.py +++ b/apps/assets/migrations/0105_auto_20220817_1544.py @@ -6,7 +6,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('assets', '0108_auto_20220816_1022'), + ('assets', '0104_auto_20220816_1022'), ] operations = [ @@ -41,4 +41,22 @@ class Migration(migrations.Migration): model_name='systemuser', name='users', ), + migrations.AlterUniqueTogether( + name='authbook', + unique_together=None, + ), + migrations.RemoveField( + model_name='authbook', + name='asset', + ), + migrations.RemoveField( + model_name='authbook', + name='systemuser', + ), + migrations.DeleteModel( + name='HistoricalAuthBook', + ), + migrations.DeleteModel( + name='AuthBook', + ), ] diff --git a/apps/assets/migrations/0106_auto_20220811_1449.py b/apps/assets/migrations/0106_auto_20220811_1449.py deleted file mode 100644 index decab283b..000000000 --- a/apps/assets/migrations/0106_auto_20220811_1449.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-11 06:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0105_auto_20220810_1449'), - ] - - operations = [ - migrations.RemoveField( - model_name='asset', - name='_protocols', - ), - migrations.RemoveField( - model_name='asset', - name='admin_user', - ), - migrations.RemoveField( - model_name='asset', - name='category', - ), - migrations.RemoveField( - model_name='asset', - name='number', - ), - migrations.RemoveField( - model_name='asset', - name='public_ip', - ), - migrations.RemoveField( - model_name='asset', - name='type', - ), - ] diff --git a/apps/assets/migrations/0111_auto_20220819_1523.py b/apps/assets/migrations/0106_auto_20220819_1523.py similarity index 97% rename from apps/assets/migrations/0111_auto_20220819_1523.py rename to apps/assets/migrations/0106_auto_20220819_1523.py index 1bed01959..6ed0be81c 100644 --- a/apps/assets/migrations/0111_auto_20220819_1523.py +++ b/apps/assets/migrations/0106_auto_20220819_1523.py @@ -9,7 +9,7 @@ import uuid class Migration(migrations.Migration): dependencies = [ - ('assets', '0110_auto_20220817_1716'), + ('assets', '0105_auto_20220817_1544'), ] operations = [ diff --git a/apps/assets/migrations/0110_auto_20220817_1716.py b/apps/assets/migrations/0110_auto_20220817_1716.py deleted file mode 100644 index 6fdaadd52..000000000 --- a/apps/assets/migrations/0110_auto_20220817_1716.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-17 09:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0109_auto_20220817_1544'), - ('applications', '0022_auto_20220817_1346'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='authbook', - unique_together=None, - ), - migrations.RemoveField( - model_name='authbook', - name='asset', - ), - migrations.RemoveField( - model_name='authbook', - name='systemuser', - ), - migrations.DeleteModel( - name='HistoricalAuthBook', - ), - migrations.DeleteModel( - name='AuthBook', - ), - ] diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py index 28f5e30c5..914a440ba 100644 --- a/apps/assets/models/asset/__init__.py +++ b/apps/assets/models/asset/__init__.py @@ -1,6 +1,6 @@ from .common import * from .host import * from .database import * -from .network import * -from .remote_app import * +from .networking import * +from .web import * from .cloud import * diff --git a/apps/assets/models/asset/network.py b/apps/assets/models/asset/networking.py similarity index 60% rename from apps/assets/models/asset/network.py rename to apps/assets/models/asset/networking.py index c6506bf6d..d58995b42 100644 --- a/apps/assets/models/asset/network.py +++ b/apps/assets/models/asset/networking.py @@ -2,5 +2,5 @@ from .common import Asset -class Network(Asset): +class Networking(Asset): pass diff --git a/apps/assets/models/asset/remote_app.py b/apps/assets/models/asset/remote_app.py deleted file mode 100644 index caa4c89a0..000000000 --- a/apps/assets/models/asset/remote_app.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.utils.translation import gettext_lazy as _ -from django.db import models - -from .common import Asset - - -class RemoteApp(Asset): - app_path = models.CharField(max_length=1024, verbose_name=_("App path")) - connect_host = models.ForeignKey('assets.Host', null=True, on_delete=models.SET_NULL) - attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) diff --git a/apps/assets/models/asset/web.py b/apps/assets/models/asset/web.py new file mode 100644 index 000000000..f695525ac --- /dev/null +++ b/apps/assets/models/asset/web.py @@ -0,0 +1,8 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from .common import Asset + + +class Web(Asset): + url = models.CharField(max_length=1024, verbose_name=_("url")) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 7b6dc1889..304b9c120 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -24,8 +24,8 @@ class Platform(models.Model): ('gbk', 'GBK'), ) name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) - category = models.CharField(max_length=16, choices=Category.choices, default=Category.HOST, verbose_name=_("Category")) - type = models.CharField(choices=AllTypes.choices, max_length=32, default='Linux', verbose_name=_("Type")) + category = models.CharField(default='host', max_length=32, verbose_name=_("Category")) + type = models.CharField(max_length=32, default='linux', verbose_name=_("Type")) charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) internal = models.BooleanField(default=False, verbose_name=_("Internal")) diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 7ffa6cfb1..4a7f1fa32 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -1,11 +1,11 @@ from rest_framework import serializers -from assets.models import DeviceInfo, Host, Database, Network, Cloud +from assets.models import DeviceInfo, Host, Database, Networking, Cloud from .common import AssetSerializer __all__ = [ 'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer', - 'NetworkSerializer', 'CloudSerializer', + 'NetworkingSerializer', 'CloudSerializer', ] @@ -34,9 +34,9 @@ class DatabaseSerializer(AssetSerializer): fields = AssetSerializer.Meta.fields + ['db_name'] -class NetworkSerializer(AssetSerializer): +class NetworkingSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): - model = Network + model = Networking class CloudSerializer(AssetSerializer): diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index e99999ab4..25477166c 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -55,7 +55,7 @@ class SessionViewSet(OrgBulkModelViewSet): 'display': serializers.SessionDisplaySerializer, } search_fields = [ - "user", "asset", "system_user", "remote_addr", + "user", "asset", "account", "remote_addr", "protocol", "is_finished", 'login_from', ] filterset_fields = search_fields + ['terminal'] diff --git a/apps/terminal/migrations/0052_auto_20220707_1726.py b/apps/terminal/migrations/0052_auto_20220707_1726.py index 22eab3a1a..a8c0b0052 100644 --- a/apps/terminal/migrations/0052_auto_20220707_1726.py +++ b/apps/terminal/migrations/0052_auto_20220707_1726.py @@ -13,6 +13,20 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='session', name='protocol', - field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S')], db_index=True, default='ssh', max_length=16), + field=models.CharField(db_index=True, default='ssh', max_length=16), + ), + migrations.RenameField( + model_name='session', + old_name='system_user', + new_name='account', + ), + migrations.RemoveField( + model_name='session', + name='system_user_id', + ), + migrations.AlterField( + model_name='session', + name='account', + field=models.CharField(db_index=True, max_length=128, verbose_name='Account'), ), ] diff --git a/apps/terminal/models/session.py b/apps/terminal/models/session.py index 932f47321..4f01bde4a 100644 --- a/apps/terminal/models/session.py +++ b/apps/terminal/models/session.py @@ -31,8 +31,8 @@ class Session(OrgModelMixin): user_id = models.CharField(blank=True, default='', max_length=36, db_index=True) asset = models.CharField(max_length=128, verbose_name=_("Asset"), db_index=True) asset_id = models.CharField(blank=True, default='', max_length=36, db_index=True) - system_user = models.CharField(max_length=128, verbose_name=_("System user"), db_index=True) - system_user_id = models.CharField(blank=True, default='', max_length=36, db_index=True) + account = models.CharField(max_length=128, verbose_name=_("Account"), db_index=True) + protocol = models.CharField(default='ssh', max_length=16, db_index=True) login_from = models.CharField(max_length=2, choices=LOGIN_FROM.choices, default="ST", verbose_name=_("Login from")) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) is_success = models.BooleanField(default=True, db_index=True) @@ -40,7 +40,6 @@ class Session(OrgModelMixin): has_replay = models.BooleanField(default=False, verbose_name=_("Replay")) has_command = models.BooleanField(default=False, verbose_name=_("Command")) terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.DO_NOTHING, db_constraint=False) - protocol = models.CharField(choices=Protocol.choices, default='ssh', max_length=16, db_index=True) date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) @@ -123,9 +122,9 @@ class Session(OrgModelMixin): return False if self.login_from == self.LOGIN_FROM.RT: return False - if Protocol in [ - Protocol.SSH, Protocol.VNC, Protocol.RDP, - Protocol.TELNET, Protocol.K8S + if self.protocol in [ + Protocol.ssh, Protocol.vnc, Protocol.rdp, + Protocol.telnet, Protocol.k8s ]: return True else: diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py index 49d7f7270..a22d17cdb 100644 --- a/apps/terminal/serializers/session.py +++ b/apps/terminal/serializers/session.py @@ -2,6 +2,8 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer + +from assets.const import Protocol from ..models import Session __all__ = [ @@ -13,16 +15,15 @@ __all__ = [ class SessionSerializer(BulkOrgResourceModelSerializer): org_id = serializers.CharField(allow_blank=True) terminal_display = serializers.CharField(read_only=True, label=_('Terminal display')) + protocol = serializers.ChoiceField(choices=Protocol.choices, label=_("Protocol")) class Meta: model = Session fields_mini = ["id"] fields_small = fields_mini + [ - "user", "asset", "system_user", - "user_id", "asset_id", "system_user_id", - "login_from", "login_from_display", "remote_addr", "protocol", - "is_success", "is_finished", "has_replay", - "date_start", "date_end", + "user", "asset", "user_id", "asset_id", 'account', "protocol", + "login_from", "login_from_display", "remote_addr", "is_success", + "is_finished", "has_replay", "date_start", "date_end", ] fields_fk = ["terminal", ] fields_custom = ["can_replay", "can_join", "can_terminate", 'terminal_display'] @@ -31,7 +32,6 @@ class SessionSerializer(BulkOrgResourceModelSerializer): "protocol": {'label': _('Protocol')}, 'user_id': {'label': _('User ID')}, 'asset_id': {'label': _('Asset ID')}, - 'system_user_id': {'label': _('System user ID')}, 'login_from_display': {'label': _('Login from display')}, 'is_success': {'label': _('Is success')}, 'can_replay': {'label': _('Can replay')}, From 76390d013e5a1d91fd2fddf87d68148feceab994 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 24 Aug 2022 16:36:42 +0800 Subject: [PATCH 077/488] =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/__init__.py | 2 - apps/assets/serializers/account/__init__.py | 3 + .../serializers/{ => account}/account.py | 36 ++++----- .../{ => account}/account_history.py | 7 +- .../serializers/account/account_template.py | 74 +++++++++++++++++++ apps/assets/serializers/account/common.py | 26 +++++++ apps/assets/serializers/account_template.py | 22 ------ apps/assets/serializers/asset/common.py | 8 +- 8 files changed, 126 insertions(+), 52 deletions(-) create mode 100644 apps/assets/serializers/account/__init__.py rename apps/assets/serializers/{ => account}/account.py (65%) rename apps/assets/serializers/{ => account}/account_history.py (80%) create mode 100644 apps/assets/serializers/account/account_template.py create mode 100644 apps/assets/serializers/account/common.py delete mode 100644 apps/assets/serializers/account_template.py diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index a86110712..ef4b40abe 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -8,7 +8,5 @@ from .domain import * from .gathered_user import * from .favorite_asset import * from .account import * -from .account_history import * -from .account_template import * from .backup import * from .platform import * diff --git a/apps/assets/serializers/account/__init__.py b/apps/assets/serializers/account/__init__.py new file mode 100644 index 000000000..7ef87134a --- /dev/null +++ b/apps/assets/serializers/account/__init__.py @@ -0,0 +1,3 @@ +from .account import * +from .account_history import * +from .account_template import * diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account/account.py similarity index 65% rename from apps/assets/serializers/account.py rename to apps/assets/serializers/account/account.py index 6695aa348..44da7837b 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account/account.py @@ -1,45 +1,35 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ from django.db.models import F +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers -from assets.models import Account from orgs.mixins.serializers import BulkOrgResourceModelSerializer - -from .base import AuthSerializerMixin from common.drf.serializers import SecretReadableMixin +from assets.models import Account +from assets.serializers.base import AuthSerializerMixin +from .account_template import AccountTemplateSerializerMixin +from .common import BaseAccountSerializer -class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +class AccountSerializer( + AccountTemplateSerializerMixin, AuthSerializerMixin, BulkOrgResourceModelSerializer +): ip = serializers.ReadOnlyField(label=_("IP")) asset_name = serializers.ReadOnlyField(label=_("Asset")) platform = serializers.ReadOnlyField(label=_("Platform")) - class Meta: + class Meta(BaseAccountSerializer.Meta): model = Account - fields_mini = [ - 'id', 'privileged', 'username', 'ip', 'asset_name', - 'platform', 'version' - ] - fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] - fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment'] - fields_small = fields_mini + fields_write_only + fields_other - fields_fk = ['asset'] - fields = fields_small + fields_fk - extra_kwargs = { - 'username': {'required': True}, - 'private_key': {'write_only': True}, - 'public_key': {'write_only': True}, - } - ref_name = 'AssetAccountSerializer' + fields = BaseAccountSerializer.Meta.fields + ['account_template', ] def validate(self, attrs): attrs = self._validate_gen_key(attrs) + attrs = super()._validate(attrs) return attrs @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('asset')\ + queryset = queryset.prefetch_related('asset') \ .annotate(ip=F('asset__ip')) \ .annotate(asset_name=F('asset__name')) return queryset diff --git a/apps/assets/serializers/account_history.py b/apps/assets/serializers/account/account_history.py similarity index 80% rename from apps/assets/serializers/account_history.py rename to apps/assets/serializers/account/account_history.py index b0846c04d..7fadc1a47 100644 --- a/apps/assets/serializers/account_history.py +++ b/apps/assets/serializers/account/account_history.py @@ -1,6 +1,7 @@ from assets.models import Account from common.drf.serializers import SecretReadableMixin +from .common import BaseAccountSerializer from .account import AccountSerializer, AccountSecretSerializer @@ -8,9 +9,9 @@ class AccountHistorySerializer(AccountSerializer): class Meta: model = Account.history.model - fields = AccountSerializer.Meta.fields_mini + \ - AccountSerializer.Meta.fields_write_only + \ - AccountSerializer.Meta.fields_fk + \ + fields = BaseAccountSerializer.Meta.fields_mini + \ + BaseAccountSerializer.Meta.fields_write_only + \ + BaseAccountSerializer.Meta.fields_fk + \ ['history_id', 'date_created', 'date_updated'] read_only_fields = fields ref_name = 'AccountHistorySerializer' diff --git a/apps/assets/serializers/account/account_template.py b/apps/assets/serializers/account/account_template.py new file mode 100644 index 000000000..dbe298f43 --- /dev/null +++ b/apps/assets/serializers/account/account_template.py @@ -0,0 +1,74 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from assets.models import AccountTemplate +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from assets.serializers.base import AuthSerializerMixin +from .common import BaseAccountSerializer + + +class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): + class Meta: + model = AccountTemplate + fields_mini = ['id', 'privileged', 'username', 'name'] + fields_write_only = BaseAccountSerializer.Meta.fields_write_only + fields_other = BaseAccountSerializer.Meta.fields_other + fields = fields_mini + fields_write_only + fields_other + extra_kwargs = { + 'username': {'required': True}, + 'private_key': {'write_only': True}, + 'public_key': {'write_only': True}, + } + + def validate(self, attrs): + attrs = self._validate_gen_key(attrs) + return attrs + + @classmethod + def validate_required(cls, attrs): + required_field_dict = {} + error = _('This field is required.') + for k, v in cls().fields.items(): + if v.required and k not in attrs: + required_field_dict[k] = error + print(required_field_dict) + if not required_field_dict: + return + raise serializers.ValidationError(required_field_dict) + + +class AccountTemplateSerializerMixin(serializers.ModelSerializer): + account_template = serializers.UUIDField( + required=False, allow_null=True, write_only=True, + label=_('Account template') + ) + + @staticmethod + def validate_account_template(value): + AccountTemplate.objects.get_or_create() + model = AccountTemplate + try: + return model.objects.get(id=value) + except AccountTemplate.DoesNotExist: + raise serializers.ValidationError(_('Account template not found')) + + @staticmethod + def replace_attrs(account_template: AccountTemplate, attrs: dict): + exclude_fields = [ + '_state', 'org_id', 'date_verified', 'id', 'date_created', 'date_updated', 'created_by' + ] + template_attrs = {k: v for k, v in account_template.__dict__.items() if k not in exclude_fields} + for k, v in template_attrs.items(): + attrs.setdefault(k, v) + + def _validate(self, attrs): + account_template = attrs.pop('account_template', None) + if account_template: + self.replace_attrs(account_template, attrs) + else: + AccountTemplateSerializer.validate_required(attrs) + return attrs + + + + diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py new file mode 100644 index 000000000..3ad033e0e --- /dev/null +++ b/apps/assets/serializers/account/common.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import serializers + +__all__ = [ + 'BaseAccountSerializer', +] + + +class BaseAccountSerializer(serializers.ModelSerializer): + class Meta: + fields_mini = [ + 'id', 'privileged', 'username', 'ip', 'asset_name', + 'platform', 'version' + ] + fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] + fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment'] + fields_small = fields_mini + fields_write_only + fields_other + fields_fk = ['asset'] + fields = fields_small + fields_fk + ref_name = 'AssetAccountSerializer' + extra_kwargs = { + 'username': {'required': True}, + 'private_key': {'write_only': True}, + 'public_key': {'write_only': True}, + } diff --git a/apps/assets/serializers/account_template.py b/apps/assets/serializers/account_template.py deleted file mode 100644 index 0762789fd..000000000 --- a/apps/assets/serializers/account_template.py +++ /dev/null @@ -1,22 +0,0 @@ -from assets.models import AccountTemplate -from orgs.mixins.serializers import BulkOrgResourceModelSerializer - -from .base import AuthSerializerMixin -from .account import AccountSerializer - - -class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): - class Meta: - model = AccountTemplate - fields_mini = ['id', 'privileged', 'username', 'name'] - fields_write_only = AccountSerializer.Meta.fields_write_only - fields_other = AccountSerializer.Meta.fields_other - fields = fields_mini + fields_write_only + fields_other - extra_kwargs = AccountSerializer.Meta.extra_kwargs - - def validate(self, attrs): - print(attrs) - - raise ValueError('test') - attrs = self._validate_gen_key(attrs) - return attrs diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 778137e23..aff489c84 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -2,6 +2,7 @@ # from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from django.db.transaction import atomic from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer @@ -72,6 +73,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): """ 资产的数据结构 """ + class Meta: model = Asset fields_mini = [ @@ -108,8 +110,8 @@ class AssetSerializer(JMSWritableNestedModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('domain', 'platform', 'protocols')\ - .annotate(category=F("platform__category"))\ + queryset = queryset.prefetch_related('domain', 'platform', 'protocols') \ + .annotate(category=F("platform__category")) \ .annotate(type=F("platform__type")) queryset = queryset.prefetch_related('nodes', 'labels') return queryset @@ -138,6 +140,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): raise serializers.ValidationError({'accounts': e}) serializer.save() + @atomic def create(self, validated_data): nodes_display = validated_data.pop('nodes_display', '') instance = super().create(validated_data) @@ -146,6 +149,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): self.perform_nodes_display_create(instance, nodes_display) return instance + @atomic def update(self, instance, validated_data): nodes_display = validated_data.pop('nodes_display', '') instance = super().update(instance, validated_data) From bb6b9e6f6e5e5916dd3fa38e9c797920c91753ea Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 24 Aug 2022 19:32:49 +0800 Subject: [PATCH 078/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=20platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/apps.py | 2 +- apps/users/serializers/group.py | 4 +--- utils/generate_fake_data/resources/assets.py | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/perms/apps.py b/apps/perms/apps.py index 432c194cd..f9d1e6f42 100644 --- a/apps/perms/apps.py +++ b/apps/perms/apps.py @@ -10,5 +10,5 @@ class PermsConfig(AppConfig): def ready(self): super().ready() - from . import signal_handlers + # from . import signal_handlers from . import notifications diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index 1638a5b91..6f75b402a 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -47,7 +47,5 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related( - Prefetch('users', queryset=User.objects.only('id')) - ).annotate(users_amount=Count('users')) + queryset = queryset.prefetch_related('users').annotate(users_amount=Count('users')) return queryset diff --git a/utils/generate_fake_data/resources/assets.py b/utils/generate_fake_data/resources/assets.py index 6e1ad8cab..df4703dc1 100644 --- a/utils/generate_fake_data/resources/assets.py +++ b/utils/generate_fake_data/resources/assets.py @@ -34,8 +34,8 @@ class PlatformGenerator(FakeDataGenerator): tp = choice(self.category_type[category].choices) data = { 'name': forgery_py.name.company_name(), - 'category': choice(self.categories), - 'type': tp + 'category': category, + 'type': tp[0] } platforms.append(Platform(**data)) Platform.objects.bulk_create(platforms, ignore_conflicts=True) From 5358f35c0825f2bd93151f14e5a02f926d3ed2ee Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Aug 2022 10:49:53 +0800 Subject: [PATCH 079/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20host=20inf?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0092_add_host.py | 14 +++--- .../migrations/0093_auto_20220403_1627.py | 5 ++- apps/assets/models/asset/host.py | 3 +- apps/assets/resources/__init__.py | 0 apps/assets/resources/platform/__init__.py | 44 +++++++++++++++++++ .../linux/change_password_ansible/main.yml | 10 +++++ .../change_password_ansible/manifest.yml | 10 +++++ .../roles/change_password/tasks/main.yml | 27 ++++++++++++ .../linux/create_account_ansible/main.yml | 15 +++++++ .../linux/create_account_ansible/manifest.yml | 5 +++ .../linux/verifiy_account_ansible/main.yml | 15 +++++++ .../verifiy_account_ansible/manifest.yml | 4 ++ apps/assets/serializers/platform.py | 14 ++++++ 13 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 apps/assets/resources/__init__.py create mode 100644 apps/assets/resources/platform/__init__.py create mode 100644 apps/assets/resources/platform/host/linux/change_password_ansible/main.yml create mode 100644 apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml create mode 100644 apps/assets/resources/platform/host/linux/change_password_ansible/roles/change_password/tasks/main.yml create mode 100644 apps/assets/resources/platform/host/linux/create_account_ansible/main.yml create mode 100644 apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml create mode 100644 apps/assets/resources/platform/host/linux/verifiy_account_ansible/main.yml create mode 100644 apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index 185842c42..3064d6b98 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -12,12 +12,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='Host', - fields=[ - ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ], - ), migrations.CreateModel( name='DeviceInfo', fields=[ @@ -39,11 +33,17 @@ class Migration(migrations.Migration): ('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')), ('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')), ('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')), - ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.host', verbose_name='Host')), ('number', models.CharField(blank=True, max_length=128, null=True, verbose_name='Asset number')), ], options={ 'verbose_name': 'DeviceInfo', }, ), + migrations.CreateModel( + name='Host', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('device_info', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.deviceinfo', verbose_name='Host')), + ], + ), ] diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py index 64e79ccda..3e2ff9b84 100644 --- a/apps/assets/migrations/0093_auto_20220403_1627.py +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -28,16 +28,19 @@ def migrate_hardware(apps, *args): break hardware_infos = [] + hosts_updated = [] for host in hosts: hardware = hardware_model() asset = asset_mapper[host.asset_ptr_id] - hardware.host = host hardware.date_updated = timezone.now() for name in fields: setattr(hardware, name, getattr(asset, name)) hardware_infos.append(hardware) + host.device_info_id = hardware.id + hosts_updated.append(host) hardware_model.objects.bulk_create(hardware_infos, ignore_conflicts=True) + host_model.objects.bulk_update(hosts_updated, ['device_info_id']) created += len(hardware_infos) diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 12f35ea5a..79df6eb58 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -7,13 +7,14 @@ from .common import Asset class Host(Asset): + device_info = models.OneToOneField('DeviceInfo', null=True, on_delete=models.SET_NULL, verbose_name=_("Host")) + def save(self, *args, **kwargs): self.category = Category.HOST return super().save(*args, **kwargs) class DeviceInfo(CommonModelMixin): - host = models.ForeignKey(Host, on_delete=models.CASCADE, verbose_name=_("Host")) # Collect vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) diff --git a/apps/assets/resources/__init__.py b/apps/assets/resources/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/resources/platform/__init__.py b/apps/assets/resources/platform/__init__.py new file mode 100644 index 000000000..1290c8930 --- /dev/null +++ b/apps/assets/resources/platform/__init__.py @@ -0,0 +1,44 @@ +import os +import yaml + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +platform_ops_methods = [] + + +def get_platform_methods(): + methods = [] + for root, dirs, files in os.walk(BASE_DIR, topdown=False): + for name in dirs: + path = os.path.join(root, name) + rel_path = path.replace(BASE_DIR, '.') + if len(rel_path.split('/')) != 4: + continue + manifest_path = os.path.join(path, 'manifest.yml') + if not os.path.exists(manifest_path): + print("Path not exists: {}".format(manifest_path)) + continue + f = open(manifest_path, 'r') + try: + manifest = yaml.safe_load(f) + except yaml.YAMLError as e: + print(e) + continue + current, category, tp, name = rel_path.split('/') + manifest.update({ + 'id': name, + 'category': category, + 'type': tp, + }) + methods.append(manifest) + return methods + + +def get_platform_method(platform, method): + methods = get_platform_methods() + + def key(m): + return m.get('method') == method \ + and m['category'] == platform.category \ + and m['type'] == platform.type + return list(filter(key, methods)) diff --git a/apps/assets/resources/platform/host/linux/change_password_ansible/main.yml b/apps/assets/resources/platform/host/linux/change_password_ansible/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/resources/platform/host/linux/change_password_ansible/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml b/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml new file mode 100644 index 000000000..b0c52754d --- /dev/null +++ b/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml @@ -0,0 +1,10 @@ +name: Change password using ansible +version: 1 +description: 使用特权账号更改账号的密码 +author: ibuler +method: change_password +vars: + account: + username: test + password: teset123 + public_key: test diff --git a/apps/assets/resources/platform/host/linux/change_password_ansible/roles/change_password/tasks/main.yml b/apps/assets/resources/platform/host/linux/change_password_ansible/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..78cc1776e --- /dev/null +++ b/apps/assets/resources/platform/host/linux/change_password_ansible/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('sha512') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/resources/platform/host/linux/create_account_ansible/main.yml b/apps/assets/resources/platform/host/linux/create_account_ansible/main.yml new file mode 100644 index 000000000..c5ec26def --- /dev/null +++ b/apps/assets/resources/platform/host/linux/create_account_ansible/main.yml @@ -0,0 +1,15 @@ +- hosts: centos + gather_facts: no + vars: + account: + username: web + password: test123 + + tasks: + - name: Verify password + ping: + vars: + ansible_ssh_user: "{{ account.username }}" + ansible_ssh_pass: "{{ account.password }}" + + diff --git a/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml b/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml new file mode 100644 index 000000000..4768a4423 --- /dev/null +++ b/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml @@ -0,0 +1,5 @@ +name: Create account by ansible +version: 1 +description: 使用特权账号更改账号的密码 +author: ibuler +method: create_account diff --git a/apps/assets/resources/platform/host/linux/verifiy_account_ansible/main.yml b/apps/assets/resources/platform/host/linux/verifiy_account_ansible/main.yml new file mode 100644 index 000000000..d681b54cc --- /dev/null +++ b/apps/assets/resources/platform/host/linux/verifiy_account_ansible/main.yml @@ -0,0 +1,15 @@ +- hosts: centos + gather_facts: no + vars: + account: + username: web + password: test123 + + tasks: + - name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + + diff --git a/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml b/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml new file mode 100644 index 000000000..b7baca4e7 --- /dev/null +++ b/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml @@ -0,0 +1,4 @@ +name: Change password using ansible +version: 1 +description: 使用特权账号更改账号的密码 +author: ibuler diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 6cbd5b898..5f44dc520 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -6,6 +6,7 @@ from common.drf.serializers import JMSWritableNestedModelSerializer from ..models import Platform, PlatformProtocol from ..const import Category, AllTypes + __all__ = ['PlatformSerializer'] @@ -20,6 +21,10 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): category = ChoiceDisplayField(choices=Category.choices, label=_("Category")) protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) type_constraints = serializers.ReadOnlyField(required=False, read_only=True) + su_method = ChoiceDisplayField( + choices=[('sudo', 'sudo su -'), ('su', 'su - ')], + label='切换方式', required=False, default='sudo' + ) class Meta: model = Platform @@ -41,5 +46,14 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): read_only_fields = [ 'category_display', 'type_display', ] + extra_kwargs = { + 'su_enabled': {'label': '启用切换账号'}, + 'verify_account_enabled': {'label': '启用校验账号'}, + 'verify_account_method': {'label': '校验账号方式'}, + 'create_account_enabled': {'label': '启用创建账号'}, + 'create_account_method': {'label': '创建账号方式'}, + 'change_password_enabled': {'label': '启用账号改密'}, + 'change_password_method': {'label': '账号改密方式'}, + } From a0b6849ccb9bc83a53dc80a6177048d28b84e3f2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Aug 2022 15:50:25 +0800 Subject: [PATCH 080/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 18 ++++++++- apps/assets/resources/platform/__init__.py | 15 +------- .../change_password_ansible/manifest.yml | 1 + .../linux/create_account_ansible/manifest.yml | 1 + .../verifiy_account_ansible/manifest.yml | 4 ++ apps/assets/serializers/asset/category.py | 1 + apps/assets/serializers/platform.py | 38 ++++++++++++++----- apps/common/drf/metadata.py | 1 - 8 files changed, 54 insertions(+), 25 deletions(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index fc038ee67..ca0e83dcf 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -6,6 +6,7 @@ from common.drf.serializers import GroupedChoiceSerailizer from assets.models import Platform from assets.serializers import PlatformSerializer from assets.const import AllTypes, Category +from assets.resources.platform import get_platform_methods __all__ = ['AssetPlatformViewSet'] @@ -21,7 +22,8 @@ class AssetPlatformViewSet(JMSModelViewSet): search_fields = ['name'] rbac_perms = { 'categories': 'assets.view_platform', - 'type_constraints': 'assets.view_platform' + 'type_constraints': 'assets.view_platform', + 'ops_methods': 'assets.view_platform' } @action(methods=['GET'], detail=False) @@ -37,6 +39,20 @@ class AssetPlatformViewSet(JMSModelViewSet): limits = AllTypes.get_constraints(category, tp) return Response(limits) + @action(methods=['GET'], detail=False, url_path='ops-methods') + def ops_methods(self, request, *args, **kwargs): + category = request.query_params.get('category') + tp = request.query_params.get('type') + item = request.query_params.get('item') + methods = get_platform_methods() + if category: + methods = list(filter(lambda x: x['category'] == category, methods)) + if tp: + methods = list(filter(lambda x: x['type'] == tp, methods)) + if item: + methods = list(filter(lambda x: x.get('method') == item, methods)) + return Response(methods) + def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( diff --git a/apps/assets/resources/platform/__init__.py b/apps/assets/resources/platform/__init__.py index 1290c8930..83da5fbf8 100644 --- a/apps/assets/resources/platform/__init__.py +++ b/apps/assets/resources/platform/__init__.py @@ -3,7 +3,7 @@ import yaml BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -platform_ops_methods = [] +platform_ops_methods = None def get_platform_methods(): @@ -16,29 +16,16 @@ def get_platform_methods(): continue manifest_path = os.path.join(path, 'manifest.yml') if not os.path.exists(manifest_path): - print("Path not exists: {}".format(manifest_path)) continue f = open(manifest_path, 'r') try: manifest = yaml.safe_load(f) except yaml.YAMLError as e: - print(e) continue current, category, tp, name = rel_path.split('/') manifest.update({ - 'id': name, 'category': category, 'type': tp, }) methods.append(manifest) return methods - - -def get_platform_method(platform, method): - methods = get_platform_methods() - - def key(m): - return m.get('method') == method \ - and m['category'] == platform.category \ - and m['type'] == platform.type - return list(filter(key, methods)) diff --git a/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml b/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml index b0c52754d..a7df6a8f3 100644 --- a/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml +++ b/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml @@ -1,3 +1,4 @@ +id: change_password_ansible name: Change password using ansible version: 1 description: 使用特权账号更改账号的密码 diff --git a/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml b/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml index 4768a4423..391bc24ec 100644 --- a/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml +++ b/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml @@ -1,3 +1,4 @@ +id: create_account_ansible name: Create account by ansible version: 1 description: 使用特权账号更改账号的密码 diff --git a/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml b/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml index b7baca4e7..07c92d1e5 100644 --- a/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml +++ b/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml @@ -1,4 +1,8 @@ +id: verify_account_ansible name: Change password using ansible version: 1 description: 使用特权账号更改账号的密码 author: ibuler +category: host +type: linux +method: verify_account diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 4a7f1fa32..8431b0169 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -42,3 +42,4 @@ class NetworkingSerializer(AssetSerializer): class CloudSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Cloud + fields = AssetSerializer.Meta.fields + ['cluster'] diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 5f44dc520..d6f34e714 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -10,7 +10,20 @@ from ..const import Category, AllTypes __all__ = ['PlatformSerializer'] +class ProtocolSettingSerializer(serializers.Serializer): + SECURITY_CHOICES = [ + ('any', 'Any'), + ('rdp', 'RDP'), + ('tls', 'TLS'), + ('nla', 'NLA'), + ] + console = serializers.BooleanField(required=False) + security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any', required=False) + + class PlatformProtocolsSerializer(serializers.ModelSerializer): + setting = ProtocolSettingSerializer(required=False) + class Meta: model = PlatformProtocol fields = ['id', 'name', 'port', 'setting'] @@ -33,18 +46,12 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'category', 'type', ] fields = fields_small + [ - 'domain_enabled', 'domain_default', - 'su_enabled', 'su_method', - 'protocols_enabled', 'protocols', - 'ping_enabled', 'ping_method', + 'domain_enabled', 'domain_default', 'su_enabled', 'su_method', + 'protocols_enabled', 'protocols', 'ping_enabled', 'ping_method', 'verify_account_enabled', 'verify_account_method', 'create_account_enabled', 'create_account_method', 'change_password_enabled', 'change_password_method', - 'type_constraints', - 'comment', 'charset', - ] - read_only_fields = [ - 'category_display', 'type_display', + 'type_constraints', 'comment', 'charset', ] extra_kwargs = { 'su_enabled': {'label': '启用切换账号'}, @@ -56,4 +63,17 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'change_password_method': {'label': '账号改密方式'}, } + def validate_verify_account_method(self, value): + if not value and self.initial_data.get('verify_account_enabled', False): + raise serializers.ValidationError(_('This field is required.')) + return value + def validate_create_account_method(self, value): + if not value and self.initial_data.get('create_account_enabled', False): + raise serializers.ValidationError(_('This field is required.')) + return value + + def validate_change_password_method(self, value): + if not value and self.initial_data.get('change_password_enabled', False): + raise serializers.ValidationError(_('This field is required.')) + return value diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index 7e4d32527..626147f2f 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -33,7 +33,6 @@ class SimpleMetadataWithFilters(SimpleMetadata): """ actions = {} view.raw_action = getattr(view, 'action', None) - print("Request in metadata: ", request.path, request.GET) for method in self.methods & set(view.allowed_methods): if hasattr(view, 'action_map'): view.action = view.action_map.get(method.lower(), view.action) From f35cef7abb421cb58d356b316c69e9ac669d11db Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 29 Aug 2022 16:19:37 +0800 Subject: [PATCH 081/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/platform.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index d6f34e714..355ed4afc 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -63,17 +63,13 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'change_password_method': {'label': '账号改密方式'}, } - def validate_verify_account_method(self, value): - if not value and self.initial_data.get('verify_account_enabled', False): - raise serializers.ValidationError(_('This field is required.')) - return value - - def validate_create_account_method(self, value): - if not value and self.initial_data.get('create_account_enabled', False): - raise serializers.ValidationError(_('This field is required.')) - return value - - def validate_change_password_method(self, value): - if not value and self.initial_data.get('change_password_enabled', False): - raise serializers.ValidationError(_('This field is required.')) - return value + def validate(self, attrs): + fields_to_check = [ + ('verify_account_enabled', 'verify_account_method'), + ('create_account_enabled', 'create_account_method'), + ('change_password_enabled', 'change_password_method'), + ] + for method_enabled, method_name in fields_to_check: + if attrs.get(method_enabled, False) and not attrs.get(method_name, False): + raise serializers.ValidationError({method_name: _('This field is required.')}) + return attrs From 3e1c832964769bb5cdf06ed28a761dcce71a3b04 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 29 Aug 2022 19:49:45 +0800 Subject: [PATCH 082/488] =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0107_alter_accountbackupplan_types.py | 59 +++++++++++++++++++ apps/assets/models/backup.py | 54 ++++++++++++----- .../serializers/account/account_template.py | 1 - apps/assets/task_handlers/backup/handlers.py | 50 ++++++++-------- 4 files changed, 121 insertions(+), 43 deletions(-) create mode 100644 apps/assets/migrations/0107_alter_accountbackupplan_types.py diff --git a/apps/assets/migrations/0107_alter_accountbackupplan_types.py b/apps/assets/migrations/0107_alter_accountbackupplan_types.py new file mode 100644 index 000000000..2ce2d9302 --- /dev/null +++ b/apps/assets/migrations/0107_alter_accountbackupplan_types.py @@ -0,0 +1,59 @@ +# Generated by Django 3.2.13 on 2022-08-29 11:46 +from django.db import migrations, models + +from assets.const import Category +from assets.models import Type + + +def update_account_backup_type(apps, schema_editor): + backup_model = apps.get_model('assets', 'AccountBackupPlan') + all_number = 4294967295 + asset_number = Type.choices_to_value([Category.HOST]) + app_number = Type.choices_to_value([ + Category.NETWORKING, Category.DATABASE, Category.CLOUD, Category.WEB] + ) + + backup_model.objects.filter(types=255).update(types=all_number) + backup_model.objects.filter(types=1).update(types=asset_number) + backup_model.objects.filter(types=2).update(types=app_number) + + backup_execution_model = apps.get_model('assets', 'AccountBackupPlanExecution') + choices_dict = { + 'all': Type.get_types(value=all_number), + 'asset': Type.get_types(value=asset_number), + 'app': Type.get_types(value=app_number) + } + qs_dict = { + 'all': backup_execution_model.objects.filter(plan__types=255), + 'asset': backup_execution_model.objects.filter(plan__types=1), + 'app': backup_execution_model.objects.filter(plan__types=2) + } + + backup_executions = [] + for k, qs in qs_dict.items(): + type_choices = choices_dict[k] + for i in qs: + i.plan_snapshot['types'] = type_choices + backup_executions.append(i) + backup_execution_model.objects.bulk_update(backup_executions, fields=['plan_snapshot']) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0106_auto_20220819_1523'), + ] + + operations = [ + migrations.AlterField( + model_name='accountbackupplan', + name='types', + field=models.BigIntegerField( + choices=[(4294967295, 'All'), (1, 'Linux'), (2, 'Windows'), (4, 'Unix'), (8, 'BSD'), (16, 'MacOS'), + (32, 'Mainframe'), (64, 'Other host'), (127, 'Host'), (128, 'Switch'), (256, 'Router'), + (512, 'Firewall'), (1024, 'Other device'), (1920, 'NetworkDevice'), (2048, 'MySQL'), + (4096, 'MariaDB'), (8192, 'PostgreSQL'), (16384, 'Oracle'), (32768, 'SQLServer'), + (65536, 'MongoDB'), (131072, 'Redis'), (260096, 'Database'), (262144, 'Clouding'), + (524288, 'Web')], default=4294967295, verbose_name='Type'), + ), + migrations.RunPython(update_account_backup_type), + ] diff --git a/apps/assets/models/backup.py b/apps/assets/models/backup.py index 437e91dbd..e27564d9a 100644 --- a/apps/assets/models/backup.py +++ b/apps/assets/models/backup.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # import uuid +from functools import reduce from celery import current_task from django.db import models @@ -13,40 +14,63 @@ from common.utils import get_logger from common.db.encoder import ModelJSONFieldEncoder from common.db.models import BitOperationChoice from common.mixins.models import CommonModelMixin +from ..const import AllTypes, Category __all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution', 'Type'] logger = get_logger(__file__) +def _choice_map(default=None): + offset = 0 + temp_key = 0b1 + + if default is None: + _all = (0b1 << 32) - 1 + else: + _all = default + + choices = { + _all: ('all', 'All') + } + + for info in AllTypes.grouped_choices_to_objs(): + temp_keys = [] + for c in info['children']: + key = temp_key << offset + temp_keys.append(key) + choices[key] = (c['value'], c['display_name']) + offset += 1 + parent_key = reduce(lambda x, y: x | y, temp_keys) + choices[parent_key] = (info['value'], info['display_name']) + return choices + + class Type(BitOperationChoice): NONE = 0 - ALL = 0xff - Asset = 0b1 - App = 0b1 << 1 + ALL = (0b1 << 32) - 1 + TYPE_MAP = _choice_map(ALL) - DB_CHOICES = ( - (ALL, _('All')), - (Asset, _('Asset')), - (App, _('Application')) - ) + DB_CHOICES = tuple((k, v[1]) for k, v in TYPE_MAP.items()) - NAME_MAP = { - ALL: "all", - Asset: "asset", - App: "application" - } + NAME_MAP = {k: v[0] for k, v in TYPE_MAP.items()} NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} CHOICES = [] for i, j in DB_CHOICES: CHOICES.append((NAME_MAP[i], j)) + @classmethod + def get_types(cls, value: int) -> list: + exclude_types = ['all'] + Category.values + current_all = cls.value_to_choices(value) + return list(filter(lambda x: x not in exclude_types, current_all)) + class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - types = models.IntegerField(choices=Type.DB_CHOICES, default=Type.ALL, verbose_name=_('Type')) + types = models.BigIntegerField(choices=Type.DB_CHOICES, default=Type.ALL, verbose_name=_('Type')) recipients = models.ManyToManyField( 'users.User', related_name='recipient_escape_route_plans', blank=True, verbose_name=_("Recipient") @@ -77,7 +101,7 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): 'crontab': self.crontab, 'org_id': self.org_id, 'created_by': self.created_by, - 'types': Type.value_to_choices(self.types), + 'types': Type.get_types(self.types), 'recipients': { str(recipient.id): (str(recipient), bool(recipient.secret_key)) for recipient in self.recipients.all() diff --git a/apps/assets/serializers/account/account_template.py b/apps/assets/serializers/account/account_template.py index dbe298f43..068239e74 100644 --- a/apps/assets/serializers/account/account_template.py +++ b/apps/assets/serializers/account/account_template.py @@ -31,7 +31,6 @@ class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSeriali for k, v in cls().fields.items(): if v.required and k not in attrs: required_field_dict[k] = error - print(required_field_dict) if not required_field_dict: return raise serializers.ValidationError(required_field_dict) diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index df7bb52da..9d0019054 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -4,10 +4,10 @@ from openpyxl import Workbook from collections import defaultdict, OrderedDict from django.conf import settings -from django.utils.translation import ugettext_lazy as _ +from django.db.models import F from rest_framework import serializers -from assets.models import Account +from assets.models import Account, Type from assets.serializers import AccountSecretSerializer from assets.notifications import AccountBackupExecutionTaskMsg from users.models import User @@ -64,34 +64,33 @@ class AssetAccountHandler(BaseAccountHandler): @staticmethod def get_filename(plan_name): filename = os.path.join( - PATH, f'{plan_name}-{_("Asset")}-{local_now_display()}-{time.time()}.xlsx' + PATH, f'{plan_name}-{local_now_display()}-{time.time()}.xlsx' ) return filename @classmethod - def create_data_map(cls): + def create_data_map(cls, types: list): data_map = defaultdict(list) - sheet_name = Account._meta.verbose_name - accounts = Account.objects.all() + # TODO 可以优化一下查询 在账号上做type的缓存 避免数据量大时连表操作 + accounts = Account.objects.filter( + asset__platform__in=types + ).annotate(type=F('asset__platform__type')) if not accounts.first(): return data_map + type_dict = dict(Type.CHOICES) header_fields = cls.get_header_fields(AccountSecretSerializer(accounts.first())) for account in accounts: - account.load_auth() + sheet_name = type_dict[account.type] row = cls.create_row(account, AccountSecretSerializer, header_fields) if sheet_name not in data_map: data_map[sheet_name].append(list(row.keys())) data_map[sheet_name].append(list(row.values())) - logger.info('\n\033[33m- 共收集 {} 条资产账号\033[0m'.format(accounts.count())) + logger.info('\n\033[33m- 共收集 {} 条账号\033[0m'.format(accounts.count())) return data_map -handler_map = { - 'asset': AssetAccountHandler, -} - class AccountBackupHandler: def __init__(self, execution): @@ -108,24 +107,21 @@ class AccountBackupHandler: # Print task start date time_start = time.time() files = [] - for account_type in self.execution.types: - handler = handler_map.get(account_type) - if not handler: - continue + types = self.execution.types - data_map = handler.create_data_map() - if not data_map: - continue + data_map = AssetAccountHandler.create_data_map(types) + if not data_map: + return files - filename = handler.get_filename(self.plan_name) + filename = AssetAccountHandler.get_filename(self.plan_name) - wb = Workbook(filename) - for sheet, data in data_map.items(): - ws = wb.create_sheet(str(sheet)) - for row in data: - ws.append(row) - wb.save(filename) - files.append(filename) + wb = Workbook(filename) + for sheet, data in data_map.items(): + ws = wb.create_sheet(str(sheet)) + for row in data: + ws.append(row) + wb.save(filename) + files.append(filename) timedelta = round((time.time() - time_start), 2) logger.info('步骤完成: 用时 {}s'.format(timedelta)) return files From 794ec394464d33ad076fe62e2d0465bac5686758 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 29 Aug 2022 19:59:00 +0800 Subject: [PATCH 083/488] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=87=E4=BB=BDbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/task_handlers/backup/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index 9d0019054..42d21484d 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -74,7 +74,7 @@ class AssetAccountHandler(BaseAccountHandler): # TODO 可以优化一下查询 在账号上做type的缓存 避免数据量大时连表操作 accounts = Account.objects.filter( - asset__platform__in=types + asset__platform__type__in=types ).annotate(type=F('asset__platform__type')) if not accounts.first(): return data_map From 951d4e4e0d2dba1dd248595d5bf40d77ef965678 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Aug 2022 10:07:03 +0800 Subject: [PATCH 084/488] =?UTF-8?q?perf:=20=E5=90=88=E5=B9=B6=E5=86=B2?= =?UTF-8?q?=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account_history.py | 11 --- .../migrations/0097_auto_20220426_1558.py | 44 +--------- .../migrations/0098_auto_20220430_2126.py | 39 +++++++++ apps/assets/resources/platform/__init__.py | 11 ++- .../main.yml | 0 .../manifest.yml | 0 .../roles/change_password/tasks/main.yml | 2 +- .../host/change_password_linux/main.yml | 10 +++ .../host/change_password_linux/manifest.yml | 11 +++ .../roles/change_password/tasks/main.yml | 23 ++++++ .../host/change_password_windows/main.yml | 10 +++ .../host/change_password_windows/manifest.yml | 11 +++ .../roles/change_password/tasks/main.yml | 27 ++++++ .../linux/create_account_ansible/main.yml | 15 ---- .../linux/create_account_ansible/manifest.yml | 6 -- .../verifiy_account_ansible/main.yml | 2 - .../verifiy_account_ansible/manifest.yml | 0 apps/audits/api.py | 5 -- apps/authentication/api/connection_token.py | 5 -- .../serializers/connection_token.py | 5 -- apps/common/utils/common.py | 3 - apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/perms/notifications.py | 82 ------------------- apps/perms/tasks.py | 48 ----------- 24 files changed, 140 insertions(+), 234 deletions(-) rename apps/assets/resources/platform/host/{linux/change_password_ansible => change_password_aix}/main.yml (100%) rename apps/assets/resources/platform/host/{linux/change_password_ansible => change_password_aix}/manifest.yml (100%) rename apps/assets/resources/platform/host/{linux/change_password_ansible => change_password_aix}/roles/change_password/tasks/main.yml (89%) create mode 100644 apps/assets/resources/platform/host/change_password_linux/main.yml create mode 100644 apps/assets/resources/platform/host/change_password_linux/manifest.yml create mode 100644 apps/assets/resources/platform/host/change_password_linux/roles/change_password/tasks/main.yml create mode 100644 apps/assets/resources/platform/host/change_password_windows/main.yml create mode 100644 apps/assets/resources/platform/host/change_password_windows/manifest.yml create mode 100644 apps/assets/resources/platform/host/change_password_windows/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/resources/platform/host/linux/create_account_ansible/main.yml delete mode 100644 apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml rename apps/assets/resources/platform/host/{linux => }/verifiy_account_ansible/main.yml (99%) rename apps/assets/resources/platform/host/{linux => }/verifiy_account_ansible/manifest.yml (100%) diff --git a/apps/assets/api/account_history.py b/apps/assets/api/account_history.py index e64d8189c..6ca4fd349 100644 --- a/apps/assets/api/account_history.py +++ b/apps/assets/api/account_history.py @@ -26,17 +26,6 @@ class AccountHistoryViewSet(AccountViewSet): } http_method_names = ['get', 'options'] -<<<<<<< HEAD -======= - def get_queryset(self): - queryset = self.model.objects.all() \ - .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) \ - .annotate(platform=F('asset__platform__name')) \ - .annotate(protocols=F('asset__protocols')) - return queryset - ->>>>>>> origin class AccountHistorySecretsViewSet(RecordViewLogMixin, AccountHistoryViewSet): serializer_classes = { diff --git a/apps/assets/migrations/0097_auto_20220426_1558.py b/apps/assets/migrations/0097_auto_20220426_1558.py index df925852f..ff82e0cf7 100644 --- a/apps/assets/migrations/0097_auto_20220426_1558.py +++ b/apps/assets/migrations/0097_auto_20220426_1558.py @@ -68,49 +68,6 @@ def migrate_database_to_asset(apps, *args): failed_apps.append(app) pass -# -# def migrate_remote_app_to_asset(apps, *args): -# app_model = apps.get_model('applications', 'Application') -# remote_app_model = apps.get_model('assets', 'Web') -# host_model = apps.get_model('assets', 'Host') -# platform_model = apps.get_model('assets', 'Platform') -# applications = app_model.objects.filter(category='remote_app') -# platforms = platform_model.objects.filter(category='remote_app') -# platforms_map = {p.type: p for p in platforms} -# -# connect_host_map = {} -# -# for app in applications: -# attrs = app.attrs -# connect_host = attrs.pop('asset') -# if connect_host: -# connect_host = host_model.objects.filter(asset_ptr_id=connect_host).first() -# connect_host_map[app.id] = connect_host -# -# for app in applications: -# tp = app.type -# attrs = app.attrs -# app_path = attrs.pop('path', '') -# if tp == 'custom': -# tp = 'general_remote_app' -# -# print("Create remote app: {}".format(app.name)) -# remote_app = remote_app_model( -# id=app.id, hostname=app.name, ip='', -# protocols='', -# platform=platforms_map[tp], -# org_id=app.org_id, -# app_path=app_path, -# connect_host=connect_host_map.get(app.id), -# attrs=attrs, -# ) -# try: -# remote_app.save() -# except Exception as e: -# print("Error: ", e) -# # remote_app.hostname = 'RemoteApp-' + remote_app.hostname -# - def migrate_cloud_to_asset(apps, *args): app_model = apps.get_model('applications', 'Application') @@ -119,6 +76,7 @@ def migrate_cloud_to_asset(apps, *args): applications = app_model.objects.filter(category='cloud') platform = platform_model.objects.filter(type='k8s').first() + print() for app in applications: attrs = app.attrs diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index 6d9100593..b96085256 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -1,9 +1,48 @@ # Generated by Django 3.1.14 on 2022-04-30 14:41 +from collections import namedtuple from django.db import migrations, models import django.db.models.deletion +def migrate_platform_set_ops(apps, *args): + platform_model = apps.get_model('assets', 'Platform') + + Attr = namedtuple('ops', [ + 'su_enabled', 'su_method', 'domain_enabled', + 'change_password_enabled', 'change_password_method', + 'verify_account_enabled', 'verify_account_method', + 'create_account_enabled', 'create_account_method', + ]) + default_ok = { + 'su_enabled': True, + 'su_method': 'sudo', + 'domain_enabled': True, + 'change_password_enabled': True, + 'change_password_method': 'change_password_ansible', + 'verify_account_enabled': True, + 'verify_account_method': 'verify_account_ansible', + 'create_account_enabled': True, + 'create_account_method': 'create_account_ansible', + } + + platform_ops_map = { + 'Linux': default_ok, + 'Windows': default_ok, + 'AIX': Attr( + True, 'sudo', True, + True, 'change_password_ansible', + True, 'verify_account_ansible', + True, 'create_account_ansible' + ) + } + platforms = platform_model.objects.all() + + for p in platforms: + p.set_ops = True + p.save() + + class Migration(migrations.Migration): dependencies = [ diff --git a/apps/assets/resources/platform/__init__.py b/apps/assets/resources/platform/__init__.py index 83da5fbf8..a844df27a 100644 --- a/apps/assets/resources/platform/__init__.py +++ b/apps/assets/resources/platform/__init__.py @@ -12,7 +12,7 @@ def get_platform_methods(): for name in dirs: path = os.path.join(root, name) rel_path = path.replace(BASE_DIR, '.') - if len(rel_path.split('/')) != 4: + if len(rel_path.split('/')) != 3: continue manifest_path = os.path.join(path, 'manifest.yml') if not os.path.exists(manifest_path): @@ -22,10 +22,9 @@ def get_platform_methods(): manifest = yaml.safe_load(f) except yaml.YAMLError as e: continue - current, category, tp, name = rel_path.split('/') - manifest.update({ - 'category': category, - 'type': tp, - }) methods.append(manifest) return methods + + +if __name__ == '__main__': + print(get_platform_methods()) diff --git a/apps/assets/resources/platform/host/linux/change_password_ansible/main.yml b/apps/assets/resources/platform/host/change_password_aix/main.yml similarity index 100% rename from apps/assets/resources/platform/host/linux/change_password_ansible/main.yml rename to apps/assets/resources/platform/host/change_password_aix/main.yml diff --git a/apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml b/apps/assets/resources/platform/host/change_password_aix/manifest.yml similarity index 100% rename from apps/assets/resources/platform/host/linux/change_password_ansible/manifest.yml rename to apps/assets/resources/platform/host/change_password_aix/manifest.yml diff --git a/apps/assets/resources/platform/host/linux/change_password_ansible/roles/change_password/tasks/main.yml b/apps/assets/resources/platform/host/change_password_aix/roles/change_password/tasks/main.yml similarity index 89% rename from apps/assets/resources/platform/host/linux/change_password_ansible/roles/change_password/tasks/main.yml rename to apps/assets/resources/platform/host/change_password_aix/roles/change_password/tasks/main.yml index 78cc1776e..903cd9115 100644 --- a/apps/assets/resources/platform/host/linux/change_password_ansible/roles/change_password/tasks/main.yml +++ b/apps/assets/resources/platform/host/change_password_aix/roles/change_password/tasks/main.yml @@ -8,7 +8,7 @@ - name: Change password user: name: "{{ account.username }}" - password: "{{ account.password | password_hash('sha512') }}" + password: "{{ account.password | password_hash('des') }}" update_password: always when: account.password diff --git a/apps/assets/resources/platform/host/change_password_linux/main.yml b/apps/assets/resources/platform/host/change_password_linux/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/resources/platform/host/change_password_linux/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/resources/platform/host/change_password_linux/manifest.yml b/apps/assets/resources/platform/host/change_password_linux/manifest.yml new file mode 100644 index 000000000..a7df6a8f3 --- /dev/null +++ b/apps/assets/resources/platform/host/change_password_linux/manifest.yml @@ -0,0 +1,11 @@ +id: change_password_ansible +name: Change password using ansible +version: 1 +description: 使用特权账号更改账号的密码 +author: ibuler +method: change_password +vars: + account: + username: test + password: teset123 + public_key: test diff --git a/apps/assets/resources/platform/host/change_password_linux/roles/change_password/tasks/main.yml b/apps/assets/resources/platform/host/change_password_linux/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..e0ba9c73f --- /dev/null +++ b/apps/assets/resources/platform/host/change_password_linux/roles/change_password/tasks/main.yml @@ -0,0 +1,23 @@ +- name: Check connection + ping: + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('sha512') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/resources/platform/host/change_password_windows/main.yml b/apps/assets/resources/platform/host/change_password_windows/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/resources/platform/host/change_password_windows/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/resources/platform/host/change_password_windows/manifest.yml b/apps/assets/resources/platform/host/change_password_windows/manifest.yml new file mode 100644 index 000000000..a7df6a8f3 --- /dev/null +++ b/apps/assets/resources/platform/host/change_password_windows/manifest.yml @@ -0,0 +1,11 @@ +id: change_password_ansible +name: Change password using ansible +version: 1 +description: 使用特权账号更改账号的密码 +author: ibuler +method: change_password +vars: + account: + username: test + password: teset123 + public_key: test diff --git a/apps/assets/resources/platform/host/change_password_windows/roles/change_password/tasks/main.yml b/apps/assets/resources/platform/host/change_password_windows/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/resources/platform/host/change_password_windows/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/resources/platform/host/linux/create_account_ansible/main.yml b/apps/assets/resources/platform/host/linux/create_account_ansible/main.yml deleted file mode 100644 index c5ec26def..000000000 --- a/apps/assets/resources/platform/host/linux/create_account_ansible/main.yml +++ /dev/null @@ -1,15 +0,0 @@ -- hosts: centos - gather_facts: no - vars: - account: - username: web - password: test123 - - tasks: - - name: Verify password - ping: - vars: - ansible_ssh_user: "{{ account.username }}" - ansible_ssh_pass: "{{ account.password }}" - - diff --git a/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml b/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml deleted file mode 100644 index 391bc24ec..000000000 --- a/apps/assets/resources/platform/host/linux/create_account_ansible/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: create_account_ansible -name: Create account by ansible -version: 1 -description: 使用特权账号更改账号的密码 -author: ibuler -method: create_account diff --git a/apps/assets/resources/platform/host/linux/verifiy_account_ansible/main.yml b/apps/assets/resources/platform/host/verifiy_account_ansible/main.yml similarity index 99% rename from apps/assets/resources/platform/host/linux/verifiy_account_ansible/main.yml rename to apps/assets/resources/platform/host/verifiy_account_ansible/main.yml index d681b54cc..4ccdb3074 100644 --- a/apps/assets/resources/platform/host/linux/verifiy_account_ansible/main.yml +++ b/apps/assets/resources/platform/host/verifiy_account_ansible/main.yml @@ -11,5 +11,3 @@ vars: ansible_user: "{{ account.username }}" ansible_pass: "{{ account.password }}" - - diff --git a/apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml b/apps/assets/resources/platform/host/verifiy_account_ansible/manifest.yml similarity index 100% rename from apps/assets/resources/platform/host/linux/verifiy_account_ansible/manifest.yml rename to apps/assets/resources/platform/host/verifiy_account_ansible/manifest.yml diff --git a/apps/audits/api.py b/apps/audits/api.py index bef3f3a05..42ae0993c 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -129,15 +129,10 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): serializer_class = CommandExecutionHostsRelationSerializer m2m_field = CommandExecution.hosts.field -<<<<<<< HEAD filterset_fields = [ 'id', 'asset', 'commandexecution' ] search_fields = ('asset__name', ) -======= - filterset_class = filters.CommandExecutionFilter - search_fields = ('asset__hostname', ) ->>>>>>> origin http_method_names = ['options', 'get'] rbac_perms = { 'GET': 'ops.view_commandexecution', diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 22d9e9b04..990e9d814 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -210,12 +210,7 @@ class ConnectionTokenMixin: class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet): filterset_fields = ( -<<<<<<< HEAD 'type', 'user_display', 'asset_display' -======= - 'type', 'user_display', 'system_user_display', - 'application_display', 'asset_display' ->>>>>>> origin ) search_fields = filterset_fields serializer_classes = { diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 5a3db5b4e..4a5b91e5b 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -153,12 +153,7 @@ class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) -<<<<<<< HEAD asset = ConnectionTokenAssetSerializer(read_only=True) -======= - asset = ConnectionTokenAssetSerializer(read_only=True, source='asset_or_remote_app_asset') - application = ConnectionTokenApplicationSerializer(read_only=True) ->>>>>>> origin remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) account = serializers.CharField(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 93283e99b..ed45417b7 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -383,8 +383,6 @@ def test_ip_connectivity(host, port, timeout=0.5): else: connectivity = False return connectivity -<<<<<<< HEAD -======= def static_or_direct(logo_path): @@ -392,4 +390,3 @@ def static_or_direct(logo_path): return static(logo_path) else: return logo_path ->>>>>>> origin diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index d675a6b92..1ad9097dd 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:c6f584a0c74107ceddce6b403ff8755b59aabb093a0e6cc0c5f9b47eb6ae49f4 -size 255 +oid sha256:a89e824cdc4abeea54ffba79270406eefe3a260b764acb79cd42e6a11d4c03a2 +size 108405 diff --git a/apps/perms/notifications.py b/apps/perms/notifications.py index c6917dcbd..9e074cd03 100644 --- a/apps/perms/notifications.py +++ b/apps/perms/notifications.py @@ -1,7 +1,3 @@ -<<<<<<< HEAD - -======= ->>>>>>> origin from django.utils.translation import ugettext as _ from django.template.loader import render_to_string @@ -83,81 +79,3 @@ class AssetPermsWillExpireForOrgAdminMsg(UserMessage): perms = AssetPermission.objects.all()[:10] org = Organization.objects.first() return cls(user, perms, org) -<<<<<<< HEAD -======= - - -class PermedAppsWillExpireUserMsg(UserMessage): - def __init__(self, user, apps, day_count=0): - super().__init__(user) - self.apps = apps - self.day_count = _('today') if day_count == 0 else day_count - - def get_html_msg(self) -> dict: - subject = _("Your permed applications is about to expire") - context = { - 'name': self.user.name, - 'count': str(self.day_count), - 'item_type': _('permed applications'), - 'items': [str(app) for app in self.apps] - } - message = render_to_string('perms/_msg_permed_items_expire.html', context) - return { - 'subject': subject, - 'message': message - } - - @classmethod - def gen_test_msg(cls): - from users.models import User - from applications.models import Application - - user = User.objects.first() - apps = Application.objects.all()[:10] - return cls(user, apps) - - -class AppPermsWillExpireForOrgAdminMsg(UserMessage): - def __init__(self, user, perms, org, day_count=0): - super().__init__(user) - self.perms = perms - self.org = org - self.day_count = _('today') if day_count == 0 else day_count - - def get_items_with_url(self): - items_with_url = [] - for perm in self.perms: - url = js_reverse( - 'perms:application-permission-detail', - kwargs={'pk': perm.id}, external=True, - api_to_ui=True, is_console=True - ) + f'?oid={perm.org_id}' - items_with_url.append([perm.name, url]) - return items_with_url - - def get_html_msg(self) -> dict: - items = self.get_items_with_url() - subject = _('Application permissions is about to expire') - context = { - 'name': self.user.name, - 'count': str(self.day_count), - 'item_type': _('application permissions of organization {}').format(self.org), - 'items_with_url': items - } - message = render_to_string('perms/_msg_item_permissions_expire.html', context) - return { - 'subject': subject, - 'message': message - } - - @classmethod - def gen_test_msg(cls): - from users.models import User - from perms.models import ApplicationPermission - from orgs.models import Organization - - user = User.objects.first() - perms = ApplicationPermission.objects.all()[:10] - org = Organization.objects.first() - return cls(user, perms, org) ->>>>>>> origin diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 58cbc66d6..30c9600d3 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -100,51 +100,3 @@ def check_asset_permission_will_expired(): org_admins = org.admins.all() for org_admin in org_admins: AssetPermsWillExpireForOrgAdminMsg(org_admin, perms, org, day_count).publish_async() -<<<<<<< HEAD -======= - - -@register_as_period_task(crontab='0 10 * * *') -@shared_task() -@atomic() -@tmp_to_root_org() -def check_app_permission_will_expired(): - start = local_now() - end = start + timedelta(days=3) - - app_perms = ApplicationPermission.objects.filter( - date_expired__gte=start, - date_expired__lte=end - ).distinct() - - user_app_remain_day_mapper = defaultdict(dict) - org_perm_remain_day_mapper = defaultdict(dict) - - for app_perm in app_perms: - date_expired = dt_parser(app_perm.date_expired) - remain_days = (date_expired - start).days - - org = app_perm.org - if org in org_perm_remain_day_mapper[remain_days]: - org_perm_remain_day_mapper[remain_days][org].add(app_perm) - else: - org_perm_remain_day_mapper[remain_days][org] = {app_perm, } - - users = app_perm.get_all_users() - apps = app_perm.applications.all() - for u in users: - if u in user_app_remain_day_mapper[remain_days]: - user_app_remain_day_mapper[remain_days][u].update(apps) - else: - user_app_remain_day_mapper[remain_days][u] = set(apps) - - for day_count, user_app_mapper in user_app_remain_day_mapper.items(): - for user, apps in user_app_mapper.items(): - PermedAppsWillExpireUserMsg(user, apps, day_count).publish_async() - - for day_count, org_perm_mapper in org_perm_remain_day_mapper.items(): - for org, perms in org_perm_mapper.items(): - org_admins = org.admins.all() - for org_admin in org_admins: - AppPermsWillExpireForOrgAdminMsg(org_admin, perms, org, day_count).publish_async() ->>>>>>> origin From 585f0c64cd86bfe288b00b61a47fe5bf9fc7e489 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Aug 2022 11:56:56 +0800 Subject: [PATCH 085/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20v3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 12 +--- .../migrations/0098_auto_20220430_2126.py | 21 ++----- .../{resources => playbooks}/__init__.py | 0 apps/assets/playbooks/platform/__init__.py | 60 +++++++++++++++++++ .../database/change_password_mysql}/main.yml | 0 .../change_password_mysql/manifest.yml | 6 ++ .../roles/change_password/tasks/main.yml | 0 .../database/change_password_oracle}/main.yml | 0 .../change_password_oracle/manifest.yml | 5 ++ .../roles/change_password/tasks/main.yml | 0 .../change_password_postgresql}/main.yml | 0 .../change_password_postgresql/manifest.yml | 5 ++ .../roles/change_password/tasks/main.yml | 27 +++++++++ .../change_password_sqlserver/main.yml | 10 ++++ .../change_password_sqlserver/manifest.yml | 7 +++ .../roles/change_password/tasks/main.yml | 27 +++++++++ .../playbooks/platform/example/playbook.yml | 9 +++ .../playbooks/platform/example/script.py | 6 ++ .../host/change_password_aix/main.yml | 10 ++++ .../host/change_password_aix/manifest.yml | 5 ++ .../roles/change_password/tasks/main.yml | 27 +++++++++ .../host/change_password_linux/main.yml | 10 ++++ .../host/change_password_linux/manifest.yml | 12 ++++ .../roles/change_password/tasks/main.yml | 0 .../change_password_local_windows/main.yml | 10 ++++ .../manifest.yml | 11 ++++ .../roles/change_password/tasks/main.yml | 27 +++++++++ .../host/verifiy_account_ansible/main.yml | 0 .../host/verifiy_account_ansible/manifest.yml | 6 ++ apps/assets/resources/platform/__init__.py | 30 ---------- .../host/change_password_aix/manifest.yml | 11 ---- .../host/change_password_linux/manifest.yml | 11 ---- .../host/change_password_windows/manifest.yml | 11 ---- .../host/verifiy_account_ansible/manifest.yml | 8 --- apps/assets/serializers/account/account.py | 3 +- apps/assets/serializers/asset/common.py | 1 - apps/assets/serializers/platform.py | 5 +- apps/jumpserver/conf.py | 4 +- 38 files changed, 293 insertions(+), 104 deletions(-) rename apps/assets/{resources => playbooks}/__init__.py (100%) create mode 100644 apps/assets/playbooks/platform/__init__.py rename apps/assets/{resources/platform/host/change_password_aix => playbooks/platform/database/change_password_mysql}/main.yml (100%) create mode 100644 apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml rename apps/assets/{resources/platform/host/change_password_aix => playbooks/platform/database/change_password_mysql}/roles/change_password/tasks/main.yml (100%) rename apps/assets/{resources/platform/host/change_password_linux => playbooks/platform/database/change_password_oracle}/main.yml (100%) create mode 100644 apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml rename apps/assets/{resources/platform/host/change_password_windows => playbooks/platform/database/change_password_oracle}/roles/change_password/tasks/main.yml (100%) rename apps/assets/{resources/platform/host/change_password_windows => playbooks/platform/database/change_password_postgresql}/main.yml (100%) create mode 100644 apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml create mode 100644 apps/assets/playbooks/platform/database/change_password_postgresql/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/platform/database/change_password_sqlserver/main.yml create mode 100644 apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml create mode 100644 apps/assets/playbooks/platform/database/change_password_sqlserver/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/platform/example/playbook.yml create mode 100644 apps/assets/playbooks/platform/example/script.py create mode 100644 apps/assets/playbooks/platform/host/change_password_aix/main.yml create mode 100644 apps/assets/playbooks/platform/host/change_password_aix/manifest.yml create mode 100644 apps/assets/playbooks/platform/host/change_password_aix/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/platform/host/change_password_linux/main.yml create mode 100644 apps/assets/playbooks/platform/host/change_password_linux/manifest.yml rename apps/assets/{resources => playbooks}/platform/host/change_password_linux/roles/change_password/tasks/main.yml (100%) create mode 100644 apps/assets/playbooks/platform/host/change_password_local_windows/main.yml create mode 100644 apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml create mode 100644 apps/assets/playbooks/platform/host/change_password_local_windows/roles/change_password/tasks/main.yml rename apps/assets/{resources => playbooks}/platform/host/verifiy_account_ansible/main.yml (100%) create mode 100644 apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml delete mode 100644 apps/assets/resources/platform/__init__.py delete mode 100644 apps/assets/resources/platform/host/change_password_aix/manifest.yml delete mode 100644 apps/assets/resources/platform/host/change_password_linux/manifest.yml delete mode 100644 apps/assets/resources/platform/host/change_password_windows/manifest.yml delete mode 100644 apps/assets/resources/platform/host/verifiy_account_ansible/manifest.yml diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index ca0e83dcf..f49ee2595 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -6,7 +6,7 @@ from common.drf.serializers import GroupedChoiceSerailizer from assets.models import Platform from assets.serializers import PlatformSerializer from assets.const import AllTypes, Category -from assets.resources.platform import get_platform_methods +from assets.playbooks.platform import filter_platform_methods __all__ = ['AssetPlatformViewSet'] @@ -43,14 +43,8 @@ class AssetPlatformViewSet(JMSModelViewSet): def ops_methods(self, request, *args, **kwargs): category = request.query_params.get('category') tp = request.query_params.get('type') - item = request.query_params.get('item') - methods = get_platform_methods() - if category: - methods = list(filter(lambda x: x['category'] == category, methods)) - if tp: - methods = list(filter(lambda x: x['type'] == tp, methods)) - if item: - methods = list(filter(lambda x: x.get('method') == item, methods)) + method = request.query_params.get('method') + methods = filter_platform_methods(category, tp, method) return Response(methods) def check_object_permissions(self, request, obj): diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index b96085256..53a18e33e 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -8,33 +8,20 @@ import django.db.models.deletion def migrate_platform_set_ops(apps, *args): platform_model = apps.get_model('assets', 'Platform') - Attr = namedtuple('ops', [ - 'su_enabled', 'su_method', 'domain_enabled', - 'change_password_enabled', 'change_password_method', - 'verify_account_enabled', 'verify_account_method', - 'create_account_enabled', 'create_account_method', - ]) default_ok = { 'su_enabled': True, 'su_method': 'sudo', 'domain_enabled': True, 'change_password_enabled': True, - 'change_password_method': 'change_password_ansible', + 'change_password_method': 'change_password_linux', 'verify_account_enabled': True, 'verify_account_method': 'verify_account_ansible', - 'create_account_enabled': True, - 'create_account_method': 'create_account_ansible', } platform_ops_map = { - 'Linux': default_ok, - 'Windows': default_ok, - 'AIX': Attr( - True, 'sudo', True, - True, 'change_password_ansible', - True, 'verify_account_ansible', - True, 'create_account_ansible' - ) + 'Linux': {**default_ok, 'change_password_method': 'change_password_linux'}, + 'Windows': {**default_ok, 'change_password_method': 'change_password_windows'}, + 'AIX': {**default_ok, 'change_password_method': 'change_password_aix'}, } platforms = platform_model.objects.all() diff --git a/apps/assets/resources/__init__.py b/apps/assets/playbooks/__init__.py similarity index 100% rename from apps/assets/resources/__init__.py rename to apps/assets/playbooks/__init__.py diff --git a/apps/assets/playbooks/platform/__init__.py b/apps/assets/playbooks/platform/__init__.py new file mode 100644 index 000000000..e19fb8a84 --- /dev/null +++ b/apps/assets/playbooks/platform/__init__.py @@ -0,0 +1,60 @@ +import os +import yaml +from functools import partial + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def check_platform_method(manifest): + required_keys = ['category', 'method', 'name', 'id'] + less_key = set(required_keys) - set(manifest.keys()) + if less_key: + raise ValueError("Manifest missing keys: {}".format(less_key)) + return True + + +def get_platform_methods(): + methods = [] + for root, dirs, files in os.walk(BASE_DIR, topdown=False): + for name in dirs: + path = os.path.join(root, name) + rel_path = path.replace(BASE_DIR, '.') + if len(rel_path.split('/')) != 3: + continue + manifest_path = os.path.join(path, 'manifest.yml') + if not os.path.exists(manifest_path): + continue + + with open(manifest_path, 'r') as f: + manifest = yaml.safe_load(f) + check_platform_method(manifest) + + methods.append(manifest) + return methods + + +def filter_key(manifest, attr, value): + manifest_value = manifest.get(attr, '') + if manifest_value == 'all': + return True + if isinstance(manifest_value, str): + manifest_value = [manifest_value] + return value in manifest_value + + +def filter_platform_methods(category, tp, method): + methods = platform_ops_methods + if category: + methods = filter(partial(filter_key, attr='category', value=category), methods) + if tp: + methods = filter(partial(filter_key, attr='type', value=tp), methods) + if method: + methods = filter(lambda x: x['method'] == method, methods) + return methods + + +platform_ops_methods = get_platform_methods() + + +if __name__ == '__main__': + print(get_platform_methods()) diff --git a/apps/assets/resources/platform/host/change_password_aix/main.yml b/apps/assets/playbooks/platform/database/change_password_mysql/main.yml similarity index 100% rename from apps/assets/resources/platform/host/change_password_aix/main.yml rename to apps/assets/playbooks/platform/database/change_password_mysql/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml b/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml new file mode 100644 index 000000000..452269c3d --- /dev/null +++ b/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml @@ -0,0 +1,6 @@ +id: change_password_mysql +name: Change password for MySQL +category: database +type: mysql +method: change_password + diff --git a/apps/assets/resources/platform/host/change_password_aix/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/database/change_password_mysql/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/resources/platform/host/change_password_aix/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/database/change_password_mysql/roles/change_password/tasks/main.yml diff --git a/apps/assets/resources/platform/host/change_password_linux/main.yml b/apps/assets/playbooks/platform/database/change_password_oracle/main.yml similarity index 100% rename from apps/assets/resources/platform/host/change_password_linux/main.yml rename to apps/assets/playbooks/platform/database/change_password_oracle/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml b/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml new file mode 100644 index 000000000..4e1f00af4 --- /dev/null +++ b/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml @@ -0,0 +1,5 @@ +id: change_password_oracle +name: Change password for Oracle +method: change_password +category: database +type: oracle diff --git a/apps/assets/resources/platform/host/change_password_windows/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/database/change_password_oracle/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/resources/platform/host/change_password_windows/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/database/change_password_oracle/roles/change_password/tasks/main.yml diff --git a/apps/assets/resources/platform/host/change_password_windows/main.yml b/apps/assets/playbooks/platform/database/change_password_postgresql/main.yml similarity index 100% rename from apps/assets/resources/platform/host/change_password_windows/main.yml rename to apps/assets/playbooks/platform/database/change_password_postgresql/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml b/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml new file mode 100644 index 000000000..7a84ef0b1 --- /dev/null +++ b/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml @@ -0,0 +1,5 @@ +id: change_password_postgresql +name: Change password for PostgreSQL +category: database +type: postgresql +method: change_password diff --git a/apps/assets/playbooks/platform/database/change_password_postgresql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/database/change_password_postgresql/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/platform/database/change_password_postgresql/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/platform/database/change_password_sqlserver/main.yml b/apps/assets/playbooks/platform/database/change_password_sqlserver/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/platform/database/change_password_sqlserver/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml b/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml new file mode 100644 index 000000000..6b4c31b32 --- /dev/null +++ b/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml @@ -0,0 +1,7 @@ +id: change_password_sqlserver +name: Change password for SQLServer +version: 1 +category: database +type: sqlserver +method: change_password + diff --git a/apps/assets/playbooks/platform/database/change_password_sqlserver/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/database/change_password_sqlserver/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/platform/database/change_password_sqlserver/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/platform/example/playbook.yml b/apps/assets/playbooks/platform/example/playbook.yml new file mode 100644 index 000000000..1e55a5007 --- /dev/null +++ b/apps/assets/playbooks/platform/example/playbook.yml @@ -0,0 +1,9 @@ +id: change_password_example +name: Change password example +category: host +method: change_password +vars: + account: + username: test + password: teset123 + public_key: test diff --git a/apps/assets/playbooks/platform/example/script.py b/apps/assets/playbooks/platform/example/script.py new file mode 100644 index 000000000..4cf9f7cf5 --- /dev/null +++ b/apps/assets/playbooks/platform/example/script.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# +""" +Will run with the args: +$0 $asset_json $account_json +""" diff --git a/apps/assets/playbooks/platform/host/change_password_aix/main.yml b/apps/assets/playbooks/platform/host/change_password_aix/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_aix/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml b/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml new file mode 100644 index 000000000..7765f6dd5 --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml @@ -0,0 +1,5 @@ +id: change_password_aix +name: Change password for AIX +version: 1 +category: host +method: change_password diff --git a/apps/assets/playbooks/platform/host/change_password_aix/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/host/change_password_aix/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_aix/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/platform/host/change_password_linux/main.yml b/apps/assets/playbooks/platform/host/change_password_linux/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_linux/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml b/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml new file mode 100644 index 000000000..b8fb5b26a --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml @@ -0,0 +1,12 @@ +id: change_password_linux +name: Change password for Linux +category: host +type: + - unix + - linux +method: change_password +vars: + account: + username: test + password: teset123 + public_key: test diff --git a/apps/assets/resources/platform/host/change_password_linux/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/host/change_password_linux/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/resources/platform/host/change_password_linux/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/host/change_password_linux/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/host/change_password_local_windows/main.yml b/apps/assets/playbooks/platform/host/change_password_local_windows/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_local_windows/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml b/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml new file mode 100644 index 000000000..5286a7b3f --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml @@ -0,0 +1,11 @@ +id: change_password_local_windows +name: Change password local account for Windows +version: 1 +method: change_password +category: host +type: windows +vars: + account: + username: test + password: teset123 + public_key: test diff --git a/apps/assets/playbooks/platform/host/change_password_local_windows/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/host/change_password_local_windows/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/platform/host/change_password_local_windows/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/resources/platform/host/verifiy_account_ansible/main.yml b/apps/assets/playbooks/platform/host/verifiy_account_ansible/main.yml similarity index 100% rename from apps/assets/resources/platform/host/verifiy_account_ansible/main.yml rename to apps/assets/playbooks/platform/host/verifiy_account_ansible/main.yml diff --git a/apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml b/apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml new file mode 100644 index 000000000..f4dd3085e --- /dev/null +++ b/apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_ansible +name: Ansible ping +description: Ansible ping +category: host +type: all +method: verify_account diff --git a/apps/assets/resources/platform/__init__.py b/apps/assets/resources/platform/__init__.py deleted file mode 100644 index a844df27a..000000000 --- a/apps/assets/resources/platform/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -import yaml - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) - -platform_ops_methods = None - - -def get_platform_methods(): - methods = [] - for root, dirs, files in os.walk(BASE_DIR, topdown=False): - for name in dirs: - path = os.path.join(root, name) - rel_path = path.replace(BASE_DIR, '.') - if len(rel_path.split('/')) != 3: - continue - manifest_path = os.path.join(path, 'manifest.yml') - if not os.path.exists(manifest_path): - continue - f = open(manifest_path, 'r') - try: - manifest = yaml.safe_load(f) - except yaml.YAMLError as e: - continue - methods.append(manifest) - return methods - - -if __name__ == '__main__': - print(get_platform_methods()) diff --git a/apps/assets/resources/platform/host/change_password_aix/manifest.yml b/apps/assets/resources/platform/host/change_password_aix/manifest.yml deleted file mode 100644 index a7df6a8f3..000000000 --- a/apps/assets/resources/platform/host/change_password_aix/manifest.yml +++ /dev/null @@ -1,11 +0,0 @@ -id: change_password_ansible -name: Change password using ansible -version: 1 -description: 使用特权账号更改账号的密码 -author: ibuler -method: change_password -vars: - account: - username: test - password: teset123 - public_key: test diff --git a/apps/assets/resources/platform/host/change_password_linux/manifest.yml b/apps/assets/resources/platform/host/change_password_linux/manifest.yml deleted file mode 100644 index a7df6a8f3..000000000 --- a/apps/assets/resources/platform/host/change_password_linux/manifest.yml +++ /dev/null @@ -1,11 +0,0 @@ -id: change_password_ansible -name: Change password using ansible -version: 1 -description: 使用特权账号更改账号的密码 -author: ibuler -method: change_password -vars: - account: - username: test - password: teset123 - public_key: test diff --git a/apps/assets/resources/platform/host/change_password_windows/manifest.yml b/apps/assets/resources/platform/host/change_password_windows/manifest.yml deleted file mode 100644 index a7df6a8f3..000000000 --- a/apps/assets/resources/platform/host/change_password_windows/manifest.yml +++ /dev/null @@ -1,11 +0,0 @@ -id: change_password_ansible -name: Change password using ansible -version: 1 -description: 使用特权账号更改账号的密码 -author: ibuler -method: change_password -vars: - account: - username: test - password: teset123 - public_key: test diff --git a/apps/assets/resources/platform/host/verifiy_account_ansible/manifest.yml b/apps/assets/resources/platform/host/verifiy_account_ansible/manifest.yml deleted file mode 100644 index 07c92d1e5..000000000 --- a/apps/assets/resources/platform/host/verifiy_account_ansible/manifest.yml +++ /dev/null @@ -1,8 +0,0 @@ -id: verify_account_ansible -name: Change password using ansible -version: 1 -description: 使用特权账号更改账号的密码 -author: ibuler -category: host -type: linux -method: verify_account diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 44da7837b..e43445251 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -11,7 +11,8 @@ from .common import BaseAccountSerializer class AccountSerializer( - AccountTemplateSerializerMixin, AuthSerializerMixin, BulkOrgResourceModelSerializer + AccountTemplateSerializerMixin, AuthSerializerMixin, + BulkOrgResourceModelSerializer ): ip = serializers.ReadOnlyField(label=_("IP")) asset_name = serializers.ReadOnlyField(label=_("Asset")) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index aff489c84..4a043595e 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -73,7 +73,6 @@ class AssetSerializer(JMSWritableNestedModelSerializer): """ 资产的数据结构 """ - class Meta: model = Asset fields_mini = [ diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 355ed4afc..ab9e70f6c 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -49,7 +49,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'domain_enabled', 'domain_default', 'su_enabled', 'su_method', 'protocols_enabled', 'protocols', 'ping_enabled', 'ping_method', 'verify_account_enabled', 'verify_account_method', - 'create_account_enabled', 'create_account_method', 'change_password_enabled', 'change_password_method', 'type_constraints', 'comment', 'charset', ] @@ -59,8 +58,8 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'verify_account_method': {'label': '校验账号方式'}, 'create_account_enabled': {'label': '启用创建账号'}, 'create_account_method': {'label': '创建账号方式'}, - 'change_password_enabled': {'label': '启用账号改密'}, - 'change_password_method': {'label': '账号改密方式'}, + 'change_password_enabled': {'label': '启用账号创建改密'}, + 'change_password_method': {'label': '账号创建改密方式'}, } def validate(self, attrs): diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 3fe4b89ff..ca6ca4589 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -124,7 +124,7 @@ class ConfigCrypto: if plaintext: value = plaintext except Exception as e: - logger.error('decrypt %s error: %s', item, e) + pass return value @classmethod @@ -134,7 +134,7 @@ class ConfigCrypto: secret_encrypt_key = os.environ.get('SECRET_ENCRYPT_KEY', '') if not secret_encrypt_key: return None - print('Info: Using SM4 to encrypt config secret value') + print('Info: try using SM4 to decrypt config secret value') return cls(secret_encrypt_key) From 3ee8cdbe88dfb2a6335e1076b3741e30c5aaef18 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 30 Aug 2022 12:50:01 +0800 Subject: [PATCH 086/488] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0022_auto_20220714_1046.py | 23 --------- .../migrations/0022_auto_20220817_1346.py | 11 ++++- .../migrations/0023_auto_20220715_1556.py | 48 ------------------- .../migrations/0023_auto_20220817_1716.py | 36 +++++++++++++- .../0013_alter_organization_options.py | 3 ++ .../0013_delete_organizationmember.py | 16 ------- .../migrations/0052_auto_20220713_1417.py | 11 +++-- ...707_1726.py => 0053_auto_20220830_1244.py} | 4 +- ...18_applyapplicationticket_apply_actions.py | 18 ------- .../migrations/0018_auto_20220728_1125.py | 11 ++++- .../migrations/0040_alter_user_source.py | 18 +++++++ 11 files changed, 85 insertions(+), 114 deletions(-) delete mode 100644 apps/applications/migrations/0022_auto_20220714_1046.py delete mode 100644 apps/applications/migrations/0023_auto_20220715_1556.py delete mode 100644 apps/orgs/migrations/0013_delete_organizationmember.py rename apps/terminal/migrations/{0052_auto_20220707_1726.py => 0053_auto_20220830_1244.py} (88%) delete mode 100644 apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py create mode 100644 apps/users/migrations/0040_alter_user_source.py diff --git a/apps/applications/migrations/0022_auto_20220714_1046.py b/apps/applications/migrations/0022_auto_20220714_1046.py deleted file mode 100644 index 8ecb1cbe6..000000000 --- a/apps/applications/migrations/0022_auto_20220714_1046.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.12 on 2022-07-14 02:46 - -from django.db import migrations - - -def migrate_db_oracle_version_to_attrs(apps, schema_editor): - db_alias = schema_editor.connection.alias - model = apps.get_model("applications", "Application") - oracles = list(model.objects.using(db_alias).filter(type='oracle')) - for o in oracles: - o.attrs['version'] = '12c' - model.objects.using(db_alias).bulk_update(oracles, ['attrs']) - - -class Migration(migrations.Migration): - - dependencies = [ - ('applications', '0021_auto_20220629_1826'), - ] - - operations = [ - migrations.RunPython(migrate_db_oracle_version_to_attrs) - ] diff --git a/apps/applications/migrations/0022_auto_20220817_1346.py b/apps/applications/migrations/0022_auto_20220817_1346.py index eae021831..0cf950848 100644 --- a/apps/applications/migrations/0022_auto_20220817_1346.py +++ b/apps/applications/migrations/0022_auto_20220817_1346.py @@ -3,13 +3,22 @@ from django.db import migrations -class Migration(migrations.Migration): +def migrate_db_oracle_version_to_attrs(apps, schema_editor): + db_alias = schema_editor.connection.alias + model = apps.get_model("applications", "Application") + oracles = list(model.objects.using(db_alias).filter(type='oracle')) + for o in oracles: + o.attrs['version'] = '12c' + model.objects.using(db_alias).bulk_update(oracles, ['attrs']) + +class Migration(migrations.Migration): dependencies = [ ('applications', '0021_auto_20220629_1826'), ] operations = [ + migrations.RunPython(migrate_db_oracle_version_to_attrs), migrations.AlterUniqueTogether( name='account', unique_together=None, diff --git a/apps/applications/migrations/0023_auto_20220715_1556.py b/apps/applications/migrations/0023_auto_20220715_1556.py deleted file mode 100644 index 03123efab..000000000 --- a/apps/applications/migrations/0023_auto_20220715_1556.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 3.1.14 on 2022-07-15 07:56 -import time -from collections import defaultdict - -from django.db import migrations - - -def migrate_account_dirty_data(apps, schema_editor): - db_alias = schema_editor.connection.alias - account_model = apps.get_model('applications', 'Account') - - count = 0 - bulk_size = 1000 - - while True: - accounts = account_model.objects.using(db_alias) \ - .filter(org_id='')[count:count + bulk_size] - - if not accounts: - break - - accounts = list(accounts) - start = time.time() - for i in accounts: - if i.app: - org_id = i.app.org_id - elif i.systemuser: - org_id = i.systemuser.org_id - else: - org_id = '' - if org_id: - i.org_id = org_id - - account_model.objects.bulk_update(accounts, ['org_id', ]) - print("Update account org is empty: {}-{} using: {:.2f}s".format( - count, count + len(accounts), time.time() - start - )) - count += len(accounts) - - -class Migration(migrations.Migration): - dependencies = [ - ('applications', '0022_auto_20220714_1046'), - ] - - operations = [ - migrations.RunPython(migrate_account_dirty_data), - ] diff --git a/apps/applications/migrations/0023_auto_20220817_1716.py b/apps/applications/migrations/0023_auto_20220817_1716.py index b506fcc3b..6f49aff69 100644 --- a/apps/applications/migrations/0023_auto_20220817_1716.py +++ b/apps/applications/migrations/0023_auto_20220817_1716.py @@ -1,10 +1,43 @@ # Generated by Django 3.2.14 on 2022-08-17 09:16 +import time from django.db import migrations -class Migration(migrations.Migration): +def migrate_account_dirty_data(apps, schema_editor): + db_alias = schema_editor.connection.alias + account_model = apps.get_model('applications', 'Account') + count = 0 + bulk_size = 1000 + + while True: + accounts = account_model.objects.using(db_alias) \ + .filter(org_id='')[count:count + bulk_size] + + if not accounts: + break + + accounts = list(accounts) + start = time.time() + for i in accounts: + if i.app: + org_id = i.app.org_id + elif i.systemuser: + org_id = i.systemuser.org_id + else: + org_id = '' + if org_id: + i.org_id = org_id + + account_model.objects.bulk_update(accounts, ['org_id', ]) + print("Update account org is empty: {}-{} using: {:.2f}s".format( + count, count + len(accounts), time.time() - start + )) + count += len(accounts) + + +class Migration(migrations.Migration): dependencies = [ ('applications', '0022_auto_20220817_1346'), ('perms', '0031_auto_20220816_1600'), @@ -14,6 +47,7 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RunPython(migrate_account_dirty_data), migrations.DeleteModel( name='Account', ), diff --git a/apps/orgs/migrations/0013_alter_organization_options.py b/apps/orgs/migrations/0013_alter_organization_options.py index e868a87a3..6dfd004da 100644 --- a/apps/orgs/migrations/0013_alter_organization_options.py +++ b/apps/orgs/migrations/0013_alter_organization_options.py @@ -14,4 +14,7 @@ class Migration(migrations.Migration): name='organization', options={'permissions': (('view_rootorg', 'Can view root org'), ('view_alljoinedorg', 'Can view all joined org')), 'verbose_name': 'Organization'}, ), + migrations.DeleteModel( + name='OrganizationMember', + ), ] diff --git a/apps/orgs/migrations/0013_delete_organizationmember.py b/apps/orgs/migrations/0013_delete_organizationmember.py deleted file mode 100644 index 78d8ad655..000000000 --- a/apps/orgs/migrations/0013_delete_organizationmember.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-11 07:11 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('orgs', '0012_auto_20220118_1054'), - ] - - operations = [ - migrations.DeleteModel( - name='OrganizationMember', - ), - ] diff --git a/apps/terminal/migrations/0052_auto_20220713_1417.py b/apps/terminal/migrations/0052_auto_20220713_1417.py index 87ad6ba6a..c30032c23 100644 --- a/apps/terminal/migrations/0052_auto_20220713_1417.py +++ b/apps/terminal/migrations/0052_auto_20220713_1417.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.12 on 2022-07-13 06:17 +# Generated by Django 3.1.14 on 2022-04-07 09:26 import common.db.fields import django.core.validators @@ -6,7 +6,6 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ ('terminal', '0051_sessionsharing_users'), ] @@ -15,11 +14,15 @@ class Migration(migrations.Migration): migrations.AddField( model_name='endpoint', name='oracle_11g_port', - field=common.db.fields.PortField(default=15211, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 11g Port'), + field=common.db.fields.PortField(default=15211, validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 11g Port'), ), migrations.AddField( model_name='endpoint', name='oracle_12c_port', - field=common.db.fields.PortField(default=15212, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 12c Port'), + field=common.db.fields.PortField(default=15212, validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 12c Port'), ), ] diff --git a/apps/terminal/migrations/0052_auto_20220707_1726.py b/apps/terminal/migrations/0053_auto_20220830_1244.py similarity index 88% rename from apps/terminal/migrations/0052_auto_20220707_1726.py rename to apps/terminal/migrations/0053_auto_20220830_1244.py index a8c0b0052..90c188df9 100644 --- a/apps/terminal/migrations/0052_auto_20220707_1726.py +++ b/apps/terminal/migrations/0053_auto_20220830_1244.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.14 on 2022-04-07 09:26 +# Generated by Django 3.2.13 on 2022-08-30 04:44 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('terminal', '0051_sessionsharing_users'), + ('terminal', '0052_auto_20220713_1417'), ] operations = [ diff --git a/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py b/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py deleted file mode 100644 index 74f5ed7a3..000000000 --- a/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.14 on 2022-07-22 08:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tickets', '0017_auto_20220623_1027'), - ] - - operations = [ - migrations.AddField( - model_name='applyapplicationticket', - name='apply_actions', - field=models.IntegerField(choices=[(255, 'All'), (1, 'Connect'), (2, 'Upload file'), (4, 'Download file'), (6, 'Upload download'), (8, 'Clipboard copy'), (16, 'Clipboard paste'), (24, 'Clipboard copy paste')], default=255, verbose_name='Actions'), - ), - ] diff --git a/apps/tickets/migrations/0018_auto_20220728_1125.py b/apps/tickets/migrations/0018_auto_20220728_1125.py index 70bb7c6bd..35ec6a798 100644 --- a/apps/tickets/migrations/0018_auto_20220728_1125.py +++ b/apps/tickets/migrations/0018_auto_20220728_1125.py @@ -4,12 +4,21 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('tickets', '0017_auto_20220623_1027'), ] operations = [ + migrations.AddField( + model_name='applyapplicationticket', + name='apply_actions', + field=models.IntegerField( + choices=[ + (255, 'All'), (1, 'Connect'), (2, 'Upload file'), (4, 'Download file'), (6, 'Upload download'), + (8, 'Clipboard copy'), (16, 'Clipboard paste'), (24, 'Clipboard copy paste') + ], default=255, + verbose_name='Actions'), + ), migrations.AlterField( model_name='applyapplicationticket', name='apply_permission_name', diff --git a/apps/users/migrations/0040_alter_user_source.py b/apps/users/migrations/0040_alter_user_source.py new file mode 100644 index 000000000..a72d97d13 --- /dev/null +++ b/apps/users/migrations/0040_alter_user_source.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-08-30 03:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0039_auto_20211229_1852'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2')], default='local', max_length=30, verbose_name='Source'), + ), + ] From 28541c48f7233ea3f2f417a5b5735b1839581006 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 30 Aug 2022 12:50:01 +0800 Subject: [PATCH 087/488] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0022_auto_20220714_1046.py | 23 --------- .../migrations/0022_auto_20220817_1346.py | 11 ++++- .../migrations/0023_auto_20220715_1556.py | 48 ------------------- .../migrations/0023_auto_20220817_1716.py | 36 +++++++++++++- .../0013_alter_organization_options.py | 3 ++ .../0013_delete_organizationmember.py | 16 ------- .../migrations/0052_auto_20220713_1417.py | 11 +++-- ...707_1726.py => 0053_auto_20220830_1244.py} | 4 +- ...18_applyapplicationticket_apply_actions.py | 10 ++-- .../migrations/0018_auto_20220728_1125.py | 23 --------- .../0019_delete_applyapplicationticket.py | 2 +- .../migrations/0020_auto_20220817_1346.py | 10 ++++ .../migrations/0040_alter_user_source.py | 18 +++++++ 13 files changed, 93 insertions(+), 122 deletions(-) delete mode 100644 apps/applications/migrations/0022_auto_20220714_1046.py delete mode 100644 apps/applications/migrations/0023_auto_20220715_1556.py delete mode 100644 apps/orgs/migrations/0013_delete_organizationmember.py rename apps/terminal/migrations/{0052_auto_20220707_1726.py => 0053_auto_20220830_1244.py} (88%) delete mode 100644 apps/tickets/migrations/0018_auto_20220728_1125.py create mode 100644 apps/users/migrations/0040_alter_user_source.py diff --git a/apps/applications/migrations/0022_auto_20220714_1046.py b/apps/applications/migrations/0022_auto_20220714_1046.py deleted file mode 100644 index 8ecb1cbe6..000000000 --- a/apps/applications/migrations/0022_auto_20220714_1046.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.12 on 2022-07-14 02:46 - -from django.db import migrations - - -def migrate_db_oracle_version_to_attrs(apps, schema_editor): - db_alias = schema_editor.connection.alias - model = apps.get_model("applications", "Application") - oracles = list(model.objects.using(db_alias).filter(type='oracle')) - for o in oracles: - o.attrs['version'] = '12c' - model.objects.using(db_alias).bulk_update(oracles, ['attrs']) - - -class Migration(migrations.Migration): - - dependencies = [ - ('applications', '0021_auto_20220629_1826'), - ] - - operations = [ - migrations.RunPython(migrate_db_oracle_version_to_attrs) - ] diff --git a/apps/applications/migrations/0022_auto_20220817_1346.py b/apps/applications/migrations/0022_auto_20220817_1346.py index eae021831..0cf950848 100644 --- a/apps/applications/migrations/0022_auto_20220817_1346.py +++ b/apps/applications/migrations/0022_auto_20220817_1346.py @@ -3,13 +3,22 @@ from django.db import migrations -class Migration(migrations.Migration): +def migrate_db_oracle_version_to_attrs(apps, schema_editor): + db_alias = schema_editor.connection.alias + model = apps.get_model("applications", "Application") + oracles = list(model.objects.using(db_alias).filter(type='oracle')) + for o in oracles: + o.attrs['version'] = '12c' + model.objects.using(db_alias).bulk_update(oracles, ['attrs']) + +class Migration(migrations.Migration): dependencies = [ ('applications', '0021_auto_20220629_1826'), ] operations = [ + migrations.RunPython(migrate_db_oracle_version_to_attrs), migrations.AlterUniqueTogether( name='account', unique_together=None, diff --git a/apps/applications/migrations/0023_auto_20220715_1556.py b/apps/applications/migrations/0023_auto_20220715_1556.py deleted file mode 100644 index 03123efab..000000000 --- a/apps/applications/migrations/0023_auto_20220715_1556.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 3.1.14 on 2022-07-15 07:56 -import time -from collections import defaultdict - -from django.db import migrations - - -def migrate_account_dirty_data(apps, schema_editor): - db_alias = schema_editor.connection.alias - account_model = apps.get_model('applications', 'Account') - - count = 0 - bulk_size = 1000 - - while True: - accounts = account_model.objects.using(db_alias) \ - .filter(org_id='')[count:count + bulk_size] - - if not accounts: - break - - accounts = list(accounts) - start = time.time() - for i in accounts: - if i.app: - org_id = i.app.org_id - elif i.systemuser: - org_id = i.systemuser.org_id - else: - org_id = '' - if org_id: - i.org_id = org_id - - account_model.objects.bulk_update(accounts, ['org_id', ]) - print("Update account org is empty: {}-{} using: {:.2f}s".format( - count, count + len(accounts), time.time() - start - )) - count += len(accounts) - - -class Migration(migrations.Migration): - dependencies = [ - ('applications', '0022_auto_20220714_1046'), - ] - - operations = [ - migrations.RunPython(migrate_account_dirty_data), - ] diff --git a/apps/applications/migrations/0023_auto_20220817_1716.py b/apps/applications/migrations/0023_auto_20220817_1716.py index b506fcc3b..6f49aff69 100644 --- a/apps/applications/migrations/0023_auto_20220817_1716.py +++ b/apps/applications/migrations/0023_auto_20220817_1716.py @@ -1,10 +1,43 @@ # Generated by Django 3.2.14 on 2022-08-17 09:16 +import time from django.db import migrations -class Migration(migrations.Migration): +def migrate_account_dirty_data(apps, schema_editor): + db_alias = schema_editor.connection.alias + account_model = apps.get_model('applications', 'Account') + count = 0 + bulk_size = 1000 + + while True: + accounts = account_model.objects.using(db_alias) \ + .filter(org_id='')[count:count + bulk_size] + + if not accounts: + break + + accounts = list(accounts) + start = time.time() + for i in accounts: + if i.app: + org_id = i.app.org_id + elif i.systemuser: + org_id = i.systemuser.org_id + else: + org_id = '' + if org_id: + i.org_id = org_id + + account_model.objects.bulk_update(accounts, ['org_id', ]) + print("Update account org is empty: {}-{} using: {:.2f}s".format( + count, count + len(accounts), time.time() - start + )) + count += len(accounts) + + +class Migration(migrations.Migration): dependencies = [ ('applications', '0022_auto_20220817_1346'), ('perms', '0031_auto_20220816_1600'), @@ -14,6 +47,7 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RunPython(migrate_account_dirty_data), migrations.DeleteModel( name='Account', ), diff --git a/apps/orgs/migrations/0013_alter_organization_options.py b/apps/orgs/migrations/0013_alter_organization_options.py index e868a87a3..6dfd004da 100644 --- a/apps/orgs/migrations/0013_alter_organization_options.py +++ b/apps/orgs/migrations/0013_alter_organization_options.py @@ -14,4 +14,7 @@ class Migration(migrations.Migration): name='organization', options={'permissions': (('view_rootorg', 'Can view root org'), ('view_alljoinedorg', 'Can view all joined org')), 'verbose_name': 'Organization'}, ), + migrations.DeleteModel( + name='OrganizationMember', + ), ] diff --git a/apps/orgs/migrations/0013_delete_organizationmember.py b/apps/orgs/migrations/0013_delete_organizationmember.py deleted file mode 100644 index 78d8ad655..000000000 --- a/apps/orgs/migrations/0013_delete_organizationmember.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-11 07:11 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('orgs', '0012_auto_20220118_1054'), - ] - - operations = [ - migrations.DeleteModel( - name='OrganizationMember', - ), - ] diff --git a/apps/terminal/migrations/0052_auto_20220713_1417.py b/apps/terminal/migrations/0052_auto_20220713_1417.py index 87ad6ba6a..c30032c23 100644 --- a/apps/terminal/migrations/0052_auto_20220713_1417.py +++ b/apps/terminal/migrations/0052_auto_20220713_1417.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.12 on 2022-07-13 06:17 +# Generated by Django 3.1.14 on 2022-04-07 09:26 import common.db.fields import django.core.validators @@ -6,7 +6,6 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ ('terminal', '0051_sessionsharing_users'), ] @@ -15,11 +14,15 @@ class Migration(migrations.Migration): migrations.AddField( model_name='endpoint', name='oracle_11g_port', - field=common.db.fields.PortField(default=15211, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 11g Port'), + field=common.db.fields.PortField(default=15211, validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 11g Port'), ), migrations.AddField( model_name='endpoint', name='oracle_12c_port', - field=common.db.fields.PortField(default=15212, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 12c Port'), + field=common.db.fields.PortField(default=15212, validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 12c Port'), ), ] diff --git a/apps/terminal/migrations/0052_auto_20220707_1726.py b/apps/terminal/migrations/0053_auto_20220830_1244.py similarity index 88% rename from apps/terminal/migrations/0052_auto_20220707_1726.py rename to apps/terminal/migrations/0053_auto_20220830_1244.py index a8c0b0052..90c188df9 100644 --- a/apps/terminal/migrations/0052_auto_20220707_1726.py +++ b/apps/terminal/migrations/0053_auto_20220830_1244.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.14 on 2022-04-07 09:26 +# Generated by Django 3.2.13 on 2022-08-30 04:44 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('terminal', '0051_sessionsharing_users'), + ('terminal', '0052_auto_20220713_1417'), ] operations = [ diff --git a/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py b/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py index 74f5ed7a3..a2890c82d 100644 --- a/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py +++ b/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py @@ -1,10 +1,9 @@ -# Generated by Django 3.1.14 on 2022-07-22 08:03 +# Generated by Django 3.2.14 on 2022-07-28 03:25 from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('tickets', '0017_auto_20220623_1027'), ] @@ -13,6 +12,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='applyapplicationticket', name='apply_actions', - field=models.IntegerField(choices=[(255, 'All'), (1, 'Connect'), (2, 'Upload file'), (4, 'Download file'), (6, 'Upload download'), (8, 'Clipboard copy'), (16, 'Clipboard paste'), (24, 'Clipboard copy paste')], default=255, verbose_name='Actions'), + field=models.IntegerField( + choices=[ + (255, 'All'), (1, 'Connect'), (2, 'Upload file'), (4, 'Download file'), (6, 'Upload download'), + (8, 'Clipboard copy'), (16, 'Clipboard paste'), (24, 'Clipboard copy paste') + ], default=255, + verbose_name='Actions'), ), ] diff --git a/apps/tickets/migrations/0018_auto_20220728_1125.py b/apps/tickets/migrations/0018_auto_20220728_1125.py deleted file mode 100644 index 70bb7c6bd..000000000 --- a/apps/tickets/migrations/0018_auto_20220728_1125.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.14 on 2022-07-28 03:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tickets', '0017_auto_20220623_1027'), - ] - - operations = [ - migrations.AlterField( - model_name='applyapplicationticket', - name='apply_permission_name', - field=models.CharField(max_length=128, verbose_name='Permission name'), - ), - migrations.AlterField( - model_name='applyassetticket', - name='apply_permission_name', - field=models.CharField(max_length=128, verbose_name='Permission name'), - ), - ] diff --git a/apps/tickets/migrations/0019_delete_applyapplicationticket.py b/apps/tickets/migrations/0019_delete_applyapplicationticket.py index 67586593d..d198f2228 100644 --- a/apps/tickets/migrations/0019_delete_applyapplicationticket.py +++ b/apps/tickets/migrations/0019_delete_applyapplicationticket.py @@ -6,7 +6,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('tickets', '0018_auto_20220728_1125'), + ('tickets', '0018_applyapplicationticket_apply_actions'), ] operations = [ diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index b657e7fd3..6b6e9626b 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -50,6 +50,16 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AlterField( + model_name='applyapplicationticket', + name='apply_permission_name', + field=models.CharField(max_length=128, verbose_name='Permission name'), + ), + migrations.AlterField( + model_name='applyassetticket', + name='apply_permission_name', + field=models.CharField(max_length=128, verbose_name='Permission name'), + ), migrations.AddField( model_name='applyassetticket', name='apply_accounts', diff --git a/apps/users/migrations/0040_alter_user_source.py b/apps/users/migrations/0040_alter_user_source.py new file mode 100644 index 000000000..a72d97d13 --- /dev/null +++ b/apps/users/migrations/0040_alter_user_source.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-08-30 03:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0039_auto_20211229_1852'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2')], default='local', max_length=30, verbose_name='Source'), + ), + ] From 62f8fac392b23c7f3a268033b32c1f402c441973 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 30 Aug 2022 12:50:01 +0800 Subject: [PATCH 088/488] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0022_auto_20220714_1046.py | 23 --------- .../migrations/0022_auto_20220817_1346.py | 11 ++++- .../migrations/0023_auto_20220715_1556.py | 48 ------------------- .../migrations/0023_auto_20220817_1716.py | 36 +++++++++++++- .../0013_alter_organization_options.py | 3 ++ .../0013_delete_organizationmember.py | 16 ------- .../migrations/0052_auto_20220713_1417.py | 11 +++-- ...707_1726.py => 0053_auto_20220830_1244.py} | 4 +- ...18_applyapplicationticket_apply_actions.py | 10 ++-- .../migrations/0018_auto_20220728_1125.py | 23 --------- .../0019_delete_applyapplicationticket.py | 2 +- .../migrations/0020_auto_20220817_1346.py | 5 ++ .../migrations/0040_alter_user_source.py | 18 +++++++ 13 files changed, 88 insertions(+), 122 deletions(-) delete mode 100644 apps/applications/migrations/0022_auto_20220714_1046.py delete mode 100644 apps/applications/migrations/0023_auto_20220715_1556.py delete mode 100644 apps/orgs/migrations/0013_delete_organizationmember.py rename apps/terminal/migrations/{0052_auto_20220707_1726.py => 0053_auto_20220830_1244.py} (88%) delete mode 100644 apps/tickets/migrations/0018_auto_20220728_1125.py create mode 100644 apps/users/migrations/0040_alter_user_source.py diff --git a/apps/applications/migrations/0022_auto_20220714_1046.py b/apps/applications/migrations/0022_auto_20220714_1046.py deleted file mode 100644 index 8ecb1cbe6..000000000 --- a/apps/applications/migrations/0022_auto_20220714_1046.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.12 on 2022-07-14 02:46 - -from django.db import migrations - - -def migrate_db_oracle_version_to_attrs(apps, schema_editor): - db_alias = schema_editor.connection.alias - model = apps.get_model("applications", "Application") - oracles = list(model.objects.using(db_alias).filter(type='oracle')) - for o in oracles: - o.attrs['version'] = '12c' - model.objects.using(db_alias).bulk_update(oracles, ['attrs']) - - -class Migration(migrations.Migration): - - dependencies = [ - ('applications', '0021_auto_20220629_1826'), - ] - - operations = [ - migrations.RunPython(migrate_db_oracle_version_to_attrs) - ] diff --git a/apps/applications/migrations/0022_auto_20220817_1346.py b/apps/applications/migrations/0022_auto_20220817_1346.py index eae021831..0cf950848 100644 --- a/apps/applications/migrations/0022_auto_20220817_1346.py +++ b/apps/applications/migrations/0022_auto_20220817_1346.py @@ -3,13 +3,22 @@ from django.db import migrations -class Migration(migrations.Migration): +def migrate_db_oracle_version_to_attrs(apps, schema_editor): + db_alias = schema_editor.connection.alias + model = apps.get_model("applications", "Application") + oracles = list(model.objects.using(db_alias).filter(type='oracle')) + for o in oracles: + o.attrs['version'] = '12c' + model.objects.using(db_alias).bulk_update(oracles, ['attrs']) + +class Migration(migrations.Migration): dependencies = [ ('applications', '0021_auto_20220629_1826'), ] operations = [ + migrations.RunPython(migrate_db_oracle_version_to_attrs), migrations.AlterUniqueTogether( name='account', unique_together=None, diff --git a/apps/applications/migrations/0023_auto_20220715_1556.py b/apps/applications/migrations/0023_auto_20220715_1556.py deleted file mode 100644 index 03123efab..000000000 --- a/apps/applications/migrations/0023_auto_20220715_1556.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 3.1.14 on 2022-07-15 07:56 -import time -from collections import defaultdict - -from django.db import migrations - - -def migrate_account_dirty_data(apps, schema_editor): - db_alias = schema_editor.connection.alias - account_model = apps.get_model('applications', 'Account') - - count = 0 - bulk_size = 1000 - - while True: - accounts = account_model.objects.using(db_alias) \ - .filter(org_id='')[count:count + bulk_size] - - if not accounts: - break - - accounts = list(accounts) - start = time.time() - for i in accounts: - if i.app: - org_id = i.app.org_id - elif i.systemuser: - org_id = i.systemuser.org_id - else: - org_id = '' - if org_id: - i.org_id = org_id - - account_model.objects.bulk_update(accounts, ['org_id', ]) - print("Update account org is empty: {}-{} using: {:.2f}s".format( - count, count + len(accounts), time.time() - start - )) - count += len(accounts) - - -class Migration(migrations.Migration): - dependencies = [ - ('applications', '0022_auto_20220714_1046'), - ] - - operations = [ - migrations.RunPython(migrate_account_dirty_data), - ] diff --git a/apps/applications/migrations/0023_auto_20220817_1716.py b/apps/applications/migrations/0023_auto_20220817_1716.py index b506fcc3b..6f49aff69 100644 --- a/apps/applications/migrations/0023_auto_20220817_1716.py +++ b/apps/applications/migrations/0023_auto_20220817_1716.py @@ -1,10 +1,43 @@ # Generated by Django 3.2.14 on 2022-08-17 09:16 +import time from django.db import migrations -class Migration(migrations.Migration): +def migrate_account_dirty_data(apps, schema_editor): + db_alias = schema_editor.connection.alias + account_model = apps.get_model('applications', 'Account') + count = 0 + bulk_size = 1000 + + while True: + accounts = account_model.objects.using(db_alias) \ + .filter(org_id='')[count:count + bulk_size] + + if not accounts: + break + + accounts = list(accounts) + start = time.time() + for i in accounts: + if i.app: + org_id = i.app.org_id + elif i.systemuser: + org_id = i.systemuser.org_id + else: + org_id = '' + if org_id: + i.org_id = org_id + + account_model.objects.bulk_update(accounts, ['org_id', ]) + print("Update account org is empty: {}-{} using: {:.2f}s".format( + count, count + len(accounts), time.time() - start + )) + count += len(accounts) + + +class Migration(migrations.Migration): dependencies = [ ('applications', '0022_auto_20220817_1346'), ('perms', '0031_auto_20220816_1600'), @@ -14,6 +47,7 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RunPython(migrate_account_dirty_data), migrations.DeleteModel( name='Account', ), diff --git a/apps/orgs/migrations/0013_alter_organization_options.py b/apps/orgs/migrations/0013_alter_organization_options.py index e868a87a3..6dfd004da 100644 --- a/apps/orgs/migrations/0013_alter_organization_options.py +++ b/apps/orgs/migrations/0013_alter_organization_options.py @@ -14,4 +14,7 @@ class Migration(migrations.Migration): name='organization', options={'permissions': (('view_rootorg', 'Can view root org'), ('view_alljoinedorg', 'Can view all joined org')), 'verbose_name': 'Organization'}, ), + migrations.DeleteModel( + name='OrganizationMember', + ), ] diff --git a/apps/orgs/migrations/0013_delete_organizationmember.py b/apps/orgs/migrations/0013_delete_organizationmember.py deleted file mode 100644 index 78d8ad655..000000000 --- a/apps/orgs/migrations/0013_delete_organizationmember.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.14 on 2022-08-11 07:11 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('orgs', '0012_auto_20220118_1054'), - ] - - operations = [ - migrations.DeleteModel( - name='OrganizationMember', - ), - ] diff --git a/apps/terminal/migrations/0052_auto_20220713_1417.py b/apps/terminal/migrations/0052_auto_20220713_1417.py index 87ad6ba6a..c30032c23 100644 --- a/apps/terminal/migrations/0052_auto_20220713_1417.py +++ b/apps/terminal/migrations/0052_auto_20220713_1417.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.12 on 2022-07-13 06:17 +# Generated by Django 3.1.14 on 2022-04-07 09:26 import common.db.fields import django.core.validators @@ -6,7 +6,6 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ ('terminal', '0051_sessionsharing_users'), ] @@ -15,11 +14,15 @@ class Migration(migrations.Migration): migrations.AddField( model_name='endpoint', name='oracle_11g_port', - field=common.db.fields.PortField(default=15211, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 11g Port'), + field=common.db.fields.PortField(default=15211, validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 11g Port'), ), migrations.AddField( model_name='endpoint', name='oracle_12c_port', - field=common.db.fields.PortField(default=15212, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 12c Port'), + field=common.db.fields.PortField(default=15212, validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(65535)], verbose_name='Oracle 12c Port'), ), ] diff --git a/apps/terminal/migrations/0052_auto_20220707_1726.py b/apps/terminal/migrations/0053_auto_20220830_1244.py similarity index 88% rename from apps/terminal/migrations/0052_auto_20220707_1726.py rename to apps/terminal/migrations/0053_auto_20220830_1244.py index a8c0b0052..90c188df9 100644 --- a/apps/terminal/migrations/0052_auto_20220707_1726.py +++ b/apps/terminal/migrations/0053_auto_20220830_1244.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.14 on 2022-04-07 09:26 +# Generated by Django 3.2.13 on 2022-08-30 04:44 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('terminal', '0051_sessionsharing_users'), + ('terminal', '0052_auto_20220713_1417'), ] operations = [ diff --git a/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py b/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py index 74f5ed7a3..a2890c82d 100644 --- a/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py +++ b/apps/tickets/migrations/0018_applyapplicationticket_apply_actions.py @@ -1,10 +1,9 @@ -# Generated by Django 3.1.14 on 2022-07-22 08:03 +# Generated by Django 3.2.14 on 2022-07-28 03:25 from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('tickets', '0017_auto_20220623_1027'), ] @@ -13,6 +12,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='applyapplicationticket', name='apply_actions', - field=models.IntegerField(choices=[(255, 'All'), (1, 'Connect'), (2, 'Upload file'), (4, 'Download file'), (6, 'Upload download'), (8, 'Clipboard copy'), (16, 'Clipboard paste'), (24, 'Clipboard copy paste')], default=255, verbose_name='Actions'), + field=models.IntegerField( + choices=[ + (255, 'All'), (1, 'Connect'), (2, 'Upload file'), (4, 'Download file'), (6, 'Upload download'), + (8, 'Clipboard copy'), (16, 'Clipboard paste'), (24, 'Clipboard copy paste') + ], default=255, + verbose_name='Actions'), ), ] diff --git a/apps/tickets/migrations/0018_auto_20220728_1125.py b/apps/tickets/migrations/0018_auto_20220728_1125.py deleted file mode 100644 index 70bb7c6bd..000000000 --- a/apps/tickets/migrations/0018_auto_20220728_1125.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.14 on 2022-07-28 03:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tickets', '0017_auto_20220623_1027'), - ] - - operations = [ - migrations.AlterField( - model_name='applyapplicationticket', - name='apply_permission_name', - field=models.CharField(max_length=128, verbose_name='Permission name'), - ), - migrations.AlterField( - model_name='applyassetticket', - name='apply_permission_name', - field=models.CharField(max_length=128, verbose_name='Permission name'), - ), - ] diff --git a/apps/tickets/migrations/0019_delete_applyapplicationticket.py b/apps/tickets/migrations/0019_delete_applyapplicationticket.py index 67586593d..d198f2228 100644 --- a/apps/tickets/migrations/0019_delete_applyapplicationticket.py +++ b/apps/tickets/migrations/0019_delete_applyapplicationticket.py @@ -6,7 +6,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('tickets', '0018_auto_20220728_1125'), + ('tickets', '0018_applyapplicationticket_apply_actions'), ] operations = [ diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index b657e7fd3..fbfc27dfd 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -50,6 +50,11 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AlterField( + model_name='applyassetticket', + name='apply_permission_name', + field=models.CharField(max_length=128, verbose_name='Permission name'), + ), migrations.AddField( model_name='applyassetticket', name='apply_accounts', diff --git a/apps/users/migrations/0040_alter_user_source.py b/apps/users/migrations/0040_alter_user_source.py new file mode 100644 index 000000000..a72d97d13 --- /dev/null +++ b/apps/users/migrations/0040_alter_user_source.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-08-30 03:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0039_auto_20211229_1852'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2')], default='local', max_length=30, verbose_name='Source'), + ), + ] From 5c73cb9b4e259045c85f376f503bd66cdb8a4b2c Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 30 Aug 2022 14:13:33 +0800 Subject: [PATCH 089/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20v3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 5 +++-- apps/assets/const.py | 3 ++- .../platform/host/change_password_aix/manifest.yml | 1 - .../platform/host/change_password_linux/manifest.yml | 5 ----- apps/assets/serializers/platform.py | 10 +++++++++- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index f49ee2595..16ef2ef15 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -4,7 +4,7 @@ from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.drf.serializers import GroupedChoiceSerailizer from assets.models import Platform -from assets.serializers import PlatformSerializer +from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer from assets.const import AllTypes, Category from assets.playbooks.platform import filter_platform_methods @@ -45,7 +45,8 @@ class AssetPlatformViewSet(JMSModelViewSet): tp = request.query_params.get('type') method = request.query_params.get('method') methods = filter_platform_methods(category, tp, method) - return Response(methods) + serializer = PlatformOpsMethodSerializer(methods, many=True) + return Response(serializer.data) def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: diff --git a/apps/assets/const.py b/apps/assets/const.py index 204bdcb6b..788c821ab 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -76,7 +76,8 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] }, cls.WINDOWS: { - '_protocols': ['ssh', 'rdp', 'vnc'] + '_protocols': ['ssh', 'rdp', 'vnc'], + 'has_su': False }, cls.MACOS: { '_protocols': ['ssh', 'vnc'] diff --git a/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml b/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml index 7765f6dd5..32f6f25c5 100644 --- a/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml +++ b/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml @@ -1,5 +1,4 @@ id: change_password_aix name: Change password for AIX -version: 1 category: host method: change_password diff --git a/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml b/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml index b8fb5b26a..25183c25d 100644 --- a/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml +++ b/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml @@ -5,8 +5,3 @@ type: - unix - linux method: change_password -vars: - account: - username: test - password: teset123 - public_key: test diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index ab9e70f6c..75f24897e 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -7,7 +7,7 @@ from ..models import Platform, PlatformProtocol from ..const import Category, AllTypes -__all__ = ['PlatformSerializer'] +__all__ = ['PlatformSerializer', 'PlatformOpsMethodSerializer'] class ProtocolSettingSerializer(serializers.Serializer): @@ -72,3 +72,11 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): if attrs.get(method_enabled, False) and not attrs.get(method_name, False): raise serializers.ValidationError({method_name: _('This field is required.')}) return attrs + + +class PlatformOpsMethodSerializer(serializers.Serializer): + id = serializers.CharField(read_only=True) + name = serializers.CharField(max_length=50, label=_('Name')) + category = serializers.CharField(max_length=50, label=_('Category')) + type = serializers.ListSerializer(child=serializers.CharField()) + method = serializers.CharField() From f81805f3611f3ad4785cb3c47c7785471a14c1ce Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 30 Aug 2022 15:43:29 +0800 Subject: [PATCH 090/488] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4perms=20applic?= =?UTF-8?q?ation=E5=A4=9A=E4=BD=99=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/application/application_permission.py | 67 ------------------- apps/perms/urls/application_permission.py | 50 -------------- 2 files changed, 117 deletions(-) delete mode 100644 apps/perms/api/application/application_permission.py delete mode 100644 apps/perms/urls/application_permission.py diff --git a/apps/perms/api/application/application_permission.py b/apps/perms/api/application/application_permission.py deleted file mode 100644 index bd8fb3452..000000000 --- a/apps/perms/api/application/application_permission.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework.response import Response -from rest_framework.generics import RetrieveAPIView - -from perms import serializers -from perms.models import ApplicationPermission -from applications.models import Application -from common.permissions import IsValidUser -from ..base import BasePermissionViewSet - - -class ApplicationPermissionViewSet(BasePermissionViewSet): - """ - 应用授权列表的增删改查API - """ - model = ApplicationPermission - serializer_class = serializers.ApplicationPermissionSerializer - filterset_fields = { - 'name': ['exact'], - 'category': ['exact'], - 'type': ['exact', 'in'], - 'from_ticket': ['exact'] - } - search_fields = ['name', 'category', 'type'] - custom_filter_fields = BasePermissionViewSet.custom_filter_fields + [ - 'application_id', 'application', 'app', 'app_name' - ] - ordering_fields = ('name',) - ordering = ('name',) - - def get_queryset(self): - queryset = super().get_queryset().prefetch_related( - "applications", "users", "user_groups", "system_users" - ) - return queryset - - def filter_application(self, queryset): - app_id = self.request.query_params.get('application_id') or \ - self.request.query_params.get('app') - app_name = self.request.query_params.get('application') or \ - self.request.query_params.get('app_name') - - if app_id: - applications = Application.objects.filter(pk=app_id) - elif app_name: - applications = Application.objects.filter(name=app_name) - else: - return queryset - if not applications: - return queryset.none() - queryset = queryset.filter(applications__in=applications) - return queryset - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_application(queryset) - return queryset - - -class ApplicationPermissionActionsApi(RetrieveAPIView): - permission_classes = (IsValidUser,) - - def retrieve(self, request, *args, **kwargs): - category = request.GET.get('category') - actions = ApplicationPermission.get_include_actions_choices(category=category) - return Response(data=actions) diff --git a/apps/perms/urls/application_permission.py b/apps/perms/urls/application_permission.py deleted file mode 100644 index 50772a8d5..000000000 --- a/apps/perms/urls/application_permission.py +++ /dev/null @@ -1,50 +0,0 @@ -# coding: utf-8 -# - -from django.urls import path, include -from rest_framework_bulk.routes import BulkRouter -from .. import api - - -router = BulkRouter() -router.register('application-permissions', api.ApplicationPermissionViewSet, 'application-permission') -router.register('application-permissions-users-relations', api.ApplicationPermissionUserRelationViewSet, 'application-permissions-users-relation') -router.register('application-permissions-user-groups-relations', api.ApplicationPermissionUserGroupRelationViewSet, 'application-permissions-user-groups-relation') -router.register('application-permissions-applications-relations', api.ApplicationPermissionApplicationRelationViewSet, 'application-permissions-application-relation') -router.register('application-permissions-system-users-relations', api.ApplicationPermissionSystemUserRelationViewSet, 'application-permissions-system-users-relation') - -user_permission_urlpatterns = [ - path('/applications/', api.UserAllGrantedApplicationsApi.as_view(), name='user-applications'), - path('applications/', api.MyAllGrantedApplicationsApi.as_view(), name='my-applications'), - - # Application As Tree - path('/applications/tree/', api.UserAllGrantedApplicationsAsTreeApi.as_view(), name='user-applications-as-tree'), - path('applications/tree/', api.MyAllGrantedApplicationsAsTreeApi.as_view(), name='my-applications-as-tree'), - - # Application System Users - path('/applications//system-users/', api.UserGrantedApplicationSystemUsersApi.as_view(), name='user-application-system-users'), - path('applications//system-users/', api.MyGrantedApplicationSystemUsersApi.as_view(), name='my-application-system-users'), -] - -user_group_permission_urlpatterns = [ - path('/applications/', api.UserGroupGrantedApplicationsApi.as_view(), name='user-group-applications'), -] - -permission_urlpatterns = [ - # 授权规则中授权的用户和应用 - path('/applications/all/', api.ApplicationPermissionAllApplicationListApi.as_view(), name='application-permission-all-applications'), - path('/users/all/', api.ApplicationPermissionAllUserListApi.as_view(), name='application-permission-all-users'), - - # 验证用户是否有某个应用的权限 - path('user/validate/', api.ValidateUserApplicationPermissionApi.as_view(), name='validate-user-application-permission'), - - path('applications/actions/', api.ApplicationPermissionActionsApi.as_view(), name='application-actions'), -] - -application_permission_urlpatterns = [ - path('users/', include(user_permission_urlpatterns)), - path('user-groups/', include(user_group_permission_urlpatterns)), - path('application-permissions/', include(permission_urlpatterns)) -] - -application_permission_urlpatterns += router.urls From d5c13df6be97c75b5366ebfd4ddef99e030f6f37 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 30 Aug 2022 17:29:54 +0800 Subject: [PATCH 091/488] =?UTF-8?q?=E8=A1=A5=E5=85=A8=E8=B5=84=E4=BA=A7api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/__init__.py | 3 +++ apps/assets/api/asset/asset.py | 4 +--- apps/assets/api/asset/cloud.py | 15 +++++++++++++++ apps/assets/api/asset/network.py | 4 ++-- apps/assets/api/asset/web.py | 15 +++++++++++++++ apps/assets/serializers/asset/category.py | 14 ++++++++++---- apps/assets/urls/api_urls.py | 8 ++++---- 7 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 apps/assets/api/asset/cloud.py create mode 100644 apps/assets/api/asset/web.py diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py index f9c9ae5e6..bca1dec50 100644 --- a/apps/assets/api/asset/__init__.py +++ b/apps/assets/api/asset/__init__.py @@ -1,4 +1,7 @@ from .asset import * from .host import * from .database import * +from .web import * +from .cloud import * +from .network import * from .permission import * diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 18325cb58..4bfd778c4 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -40,7 +40,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): filterset_class = AssetFilterSet search_fields = ("name", "ip") ordering_fields = ("name", "ip", "port") - ordering = ('name', ) + ordering = ('name',) serializer_classes = ( ('default', serializers.AssetSerializer), ('suggestion', serializers.MiniAssetSerializer), @@ -173,5 +173,3 @@ class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): has = self.request.user.has_perm(perm_required) if not has: self.permission_denied(request) - - diff --git a/apps/assets/api/asset/cloud.py b/apps/assets/api/asset/cloud.py new file mode 100644 index 000000000..64ab6b738 --- /dev/null +++ b/apps/assets/api/asset/cloud.py @@ -0,0 +1,15 @@ +from assets.models import Cloud +from assets.serializers import CloudSerializer + +from .asset import AssetViewSet + +__all__ = ['CloudViewSet'] + + +class CloudViewSet(AssetViewSet): + model = Cloud + + def get_serializer_classes(self): + serializer_classes = super().get_serializer_classes() + serializer_classes['default'] = CloudSerializer + return serializer_classes diff --git a/apps/assets/api/asset/network.py b/apps/assets/api/asset/network.py index 15822b1e1..608a70d1a 100644 --- a/apps/assets/api/asset/network.py +++ b/apps/assets/api/asset/network.py @@ -1,13 +1,13 @@ from assets.serializers import HostSerializer -from assets.models import Network +from assets.models import Networking from .asset import AssetViewSet __all__ = ['NetworkViewSet'] class NetworkViewSet(AssetViewSet): - model = Network + model = Networking def get_serializer_classes(self): serializer_classes = super().get_serializer_classes() diff --git a/apps/assets/api/asset/web.py b/apps/assets/api/asset/web.py new file mode 100644 index 000000000..92aaeff9b --- /dev/null +++ b/apps/assets/api/asset/web.py @@ -0,0 +1,15 @@ +from assets.models import Web +from assets.serializers import WebSerializer + +from .asset import AssetViewSet + +__all__ = ['WebViewSet'] + + +class WebViewSet(AssetViewSet): + model = Web + + def get_serializer_classes(self): + serializer_classes = super().get_serializer_classes() + serializer_classes['default'] = WebSerializer + return serializer_classes diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 8431b0169..e4129bb1a 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -1,11 +1,11 @@ from rest_framework import serializers -from assets.models import DeviceInfo, Host, Database, Networking, Cloud +from assets.models import DeviceInfo, Host, Database, Networking, Cloud, Web from .common import AssetSerializer __all__ = [ 'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer', - 'NetworkingSerializer', 'CloudSerializer', + 'NetworkingSerializer', 'CloudSerializer', 'WebSerializer', ] @@ -34,12 +34,18 @@ class DatabaseSerializer(AssetSerializer): fields = AssetSerializer.Meta.fields + ['db_name'] -class NetworkingSerializer(AssetSerializer): +class WebSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): - model = Networking + model = Web + fields = AssetSerializer.Meta.fields + ['url'] class CloudSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Cloud fields = AssetSerializer.Meta.fields + ['cluster'] + + +class NetworkingSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Networking diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 9d73acfa6..d1b1455d6 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -1,10 +1,7 @@ # coding:utf-8 -from django.urls import path, re_path -from rest_framework_nested import routers +from django.urls import path from rest_framework_bulk.routes import BulkRouter -from common import api as capi - from .. import api app_name = 'assets' @@ -13,6 +10,9 @@ router = BulkRouter() router.register(r'assets', api.AssetViewSet, 'asset') router.register(r'hosts', api.HostViewSet, 'host') router.register(r'databases', api.DatabaseViewSet, 'database') +router.register(r'webs', api.WebViewSet, 'web') +router.register(r'clouds', api.CloudViewSet, 'cloud') +router.register(r'networks', api.NetworkViewSet, 'network') router.register(r'accounts', api.AccountViewSet, 'account') router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') From 728dc43b6cd8c9bc542e6f1a11f6ec0727597a4a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 31 Aug 2022 10:06:16 +0800 Subject: [PATCH 092/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20fields=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20display=20choices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0098_auto_20220430_2126.py | 27 ------- .../migrations/0100_auto_20220711_1413.py | 9 ++- apps/assets/models/platform.py | 70 ++++++++++++++++++- apps/assets/playbooks/platform/__init__.py | 25 ++++--- .../change_password_mysql/manifest.yml | 4 +- .../change_password_oracle/manifest.yml | 3 +- .../change_password_postgresql/manifest.yml | 3 +- .../change_password_sqlserver/manifest.yml | 3 +- .../main.yml | 0 .../host/ansible_posix_ping/manifest.yml | 10 +++ .../platform/host/ansible_win_ping/main.yml | 13 ++++ .../host/ansible_win_ping/manifest.yml | 6 ++ .../host/change_password_aix/manifest.yml | 2 + .../manifest.yml | 8 +-- .../host/verifiy_account_ansible/manifest.yml | 6 -- apps/common/drf/fields.py | 5 ++ apps/common/drf/metadata.py | 2 + 17 files changed, 142 insertions(+), 54 deletions(-) rename apps/assets/playbooks/platform/host/{verifiy_account_ansible => ansible_posix_ping}/main.yml (100%) create mode 100644 apps/assets/playbooks/platform/host/ansible_posix_ping/manifest.yml create mode 100644 apps/assets/playbooks/platform/host/ansible_win_ping/main.yml create mode 100644 apps/assets/playbooks/platform/host/ansible_win_ping/manifest.yml delete mode 100644 apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index 53a18e33e..943e8241f 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -1,35 +1,8 @@ # Generated by Django 3.1.14 on 2022-04-30 14:41 - -from collections import namedtuple from django.db import migrations, models import django.db.models.deletion -def migrate_platform_set_ops(apps, *args): - platform_model = apps.get_model('assets', 'Platform') - - default_ok = { - 'su_enabled': True, - 'su_method': 'sudo', - 'domain_enabled': True, - 'change_password_enabled': True, - 'change_password_method': 'change_password_linux', - 'verify_account_enabled': True, - 'verify_account_method': 'verify_account_ansible', - } - - platform_ops_map = { - 'Linux': {**default_ok, 'change_password_method': 'change_password_linux'}, - 'Windows': {**default_ok, 'change_password_method': 'change_password_windows'}, - 'AIX': {**default_ok, 'change_password_method': 'change_password_aix'}, - } - platforms = platform_model.objects.all() - - for p in platforms: - p.set_ops = True - p.save() - - class Migration(migrations.Migration): dependencies = [ diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py index aed674b36..7aad60e2c 100644 --- a/apps/assets/migrations/0100_auto_20220711_1413.py +++ b/apps/assets/migrations/0100_auto_20220711_1413.py @@ -2,6 +2,12 @@ import time from django.db import migrations +from assets.models import Platform + + +def migrate_platform_set_ops(apps, *args): + platform_model = apps.get_model('assets', 'Platform') + Platform.set_default_platforms_ops(platform_model) def migrate_accounts(apps, schema_editor): @@ -59,5 +65,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(migrate_accounts) + migrations.RunPython(migrate_accounts), + migrations.RunPython(migrate_platform_set_ops) ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 304b9c120..06bdc4de3 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -40,7 +40,7 @@ class Platform(models.Model): # Accounts # 这应该和账号有关 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) - su_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) + su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) ping_enabled = models.BooleanField(default=False) ping_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) @@ -61,6 +61,74 @@ class Platform(models.Model): ) return linux.id + @staticmethod + def set_default_platforms_ops(platform_model): + default_ok = { + 'su_enabled': True, + 'su_method': 'sudo', + 'domain_enabled': True, + 'change_password_enabled': True, + 'change_password_method': 'change_password_linux', + 'verify_account_enabled': True, + 'verify_account_method': 'ansible_posix_ping', + } + db_default = { + 'su_enabled': False, + 'domain_enabled': True, + 'change_password_enabled': True, + 'verify_account_enabled': True, + } + + platform_ops_map = { + ('host', 'linux'): { + **default_ok, + 'change_password_method': 'change_password_linux', + 'verify_account_method': 'ansible_posix_ping' + }, + ('host', 'windows'): { + **default_ok, + 'su_enabled': False, + 'change_password_method': 'change_password_windows', + 'verify_account_method': 'ansible_win_ping' + }, + ('host', 'unix'): { + **default_ok, + 'verify_account_method': 'ansible_posix_ping', + 'change_password_method': 'change_password_aix' + }, + ('database', 'mysql'): { + **db_default, + 'verify_account_method': 'mysql_ping', + 'change_password_method': 'change_password_mysql' + }, + ('database', 'postgresql'): { + **db_default, + 'verify_account_method': 'postgresql_ping', + 'change_password_method': 'change_password_postgresql' + }, + ('database', 'oracle'): { + **db_default, + 'verify_account_method': 'oracle_ping', + 'change_password_method': 'change_password_oracle' + }, + ('database', 'sqlserver'): { + **db_default, + 'verify_account_method': 'mysql_ping', + 'change_password_method': 'change_password_sqlserver' + }, + } + platforms = platform_model.objects.all() + + updated = [] + for p in platforms: + attrs = platform_ops_map.get((p.category, p.type), {}) + if not attrs: + continue + for k, v in attrs.items(): + setattr(p, k, v) + updated.append(p) + platform_model.objects.bulk_update(updated, list(default_ok.keys())) + def __str__(self): return self.name diff --git a/apps/assets/playbooks/platform/__init__.py b/apps/assets/playbooks/platform/__init__.py index e19fb8a84..3f6be47d7 100644 --- a/apps/assets/playbooks/platform/__init__.py +++ b/apps/assets/playbooks/platform/__init__.py @@ -5,14 +5,23 @@ from functools import partial BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -def check_platform_method(manifest): - required_keys = ['category', 'method', 'name', 'id'] +def check_platform_method(manifest, manifest_path): + required_keys = ['category', 'method', 'name', 'id', 'type'] less_key = set(required_keys) - set(manifest.keys()) if less_key: - raise ValueError("Manifest missing keys: {}".format(less_key)) + raise ValueError("Manifest missing keys: {}, {}".format(less_key, manifest_path)) + if not isinstance(manifest['type'], list): + raise ValueError("Manifest type must be a list: {}".format(manifest_path)) return True +def check_platform_methods(methods): + ids = [m['id'] for m in methods] + for i, _id in enumerate(ids): + if _id in ids[i+1:]: + raise ValueError("Duplicate id: {}".format(_id)) + + def get_platform_methods(): methods = [] for root, dirs, files in os.walk(BASE_DIR, topdown=False): @@ -21,25 +30,25 @@ def get_platform_methods(): rel_path = path.replace(BASE_DIR, '.') if len(rel_path.split('/')) != 3: continue + manifest_path = os.path.join(path, 'manifest.yml') if not os.path.exists(manifest_path): continue with open(manifest_path, 'r') as f: manifest = yaml.safe_load(f) - check_platform_method(manifest) - + check_platform_method(manifest, manifest_path) methods.append(manifest) + + check_platform_methods(methods) return methods def filter_key(manifest, attr, value): manifest_value = manifest.get(attr, '') - if manifest_value == 'all': - return True if isinstance(manifest_value, str): manifest_value = [manifest_value] - return value in manifest_value + return value in manifest_value or 'all' in manifest_value def filter_platform_methods(category, tp, method): diff --git a/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml b/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml index 452269c3d..043549ec6 100644 --- a/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml +++ b/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml @@ -1,6 +1,6 @@ id: change_password_mysql name: Change password for MySQL category: database -type: mysql +type: + - mysql method: change_password - diff --git a/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml b/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml index 4e1f00af4..d3bab86e1 100644 --- a/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml +++ b/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml @@ -2,4 +2,5 @@ id: change_password_oracle name: Change password for Oracle method: change_password category: database -type: oracle +type: + - oracle diff --git a/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml b/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml index 7a84ef0b1..9abe184be 100644 --- a/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml +++ b/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml @@ -1,5 +1,6 @@ id: change_password_postgresql name: Change password for PostgreSQL category: database -type: postgresql +type: + - postgresql method: change_password diff --git a/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml b/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml index 6b4c31b32..b16a24dc9 100644 --- a/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml +++ b/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml @@ -2,6 +2,7 @@ id: change_password_sqlserver name: Change password for SQLServer version: 1 category: database -type: sqlserver +type: + - sqlserver method: change_password diff --git a/apps/assets/playbooks/platform/host/verifiy_account_ansible/main.yml b/apps/assets/playbooks/platform/host/ansible_posix_ping/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/verifiy_account_ansible/main.yml rename to apps/assets/playbooks/platform/host/ansible_posix_ping/main.yml diff --git a/apps/assets/playbooks/platform/host/ansible_posix_ping/manifest.yml b/apps/assets/playbooks/platform/host/ansible_posix_ping/manifest.yml new file mode 100644 index 000000000..6cd223f1c --- /dev/null +++ b/apps/assets/playbooks/platform/host/ansible_posix_ping/manifest.yml @@ -0,0 +1,10 @@ +id: ansible_posix_ping +name: Ansible posix ping +description: Ansible ping +category: host +type: + - linux + - unix + - macos + - bsd +method: verify_account diff --git a/apps/assets/playbooks/platform/host/ansible_win_ping/main.yml b/apps/assets/playbooks/platform/host/ansible_win_ping/main.yml new file mode 100644 index 000000000..726d04a53 --- /dev/null +++ b/apps/assets/playbooks/platform/host/ansible_win_ping/main.yml @@ -0,0 +1,13 @@ +- hosts: centos + gather_facts: no + vars: + account: + username: web + password: test123 + + tasks: + - name: Verify password + win_ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" diff --git a/apps/assets/playbooks/platform/host/ansible_win_ping/manifest.yml b/apps/assets/playbooks/platform/host/ansible_win_ping/manifest.yml new file mode 100644 index 000000000..fe881de3b --- /dev/null +++ b/apps/assets/playbooks/platform/host/ansible_win_ping/manifest.yml @@ -0,0 +1,6 @@ +id: ansible_win_ping +name: Ansible win ping +category: host +type: + - windows +method: verify_account diff --git a/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml b/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml index 32f6f25c5..451c10f8e 100644 --- a/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml +++ b/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml @@ -1,4 +1,6 @@ id: change_password_aix name: Change password for AIX category: host +type: + - aix method: change_password diff --git a/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml b/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml index 5286a7b3f..7f34008e6 100644 --- a/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml +++ b/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml @@ -3,9 +3,5 @@ name: Change password local account for Windows version: 1 method: change_password category: host -type: windows -vars: - account: - username: test - password: teset123 - public_key: test +type: + - windows diff --git a/apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml b/apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml deleted file mode 100644 index f4dd3085e..000000000 --- a/apps/assets/playbooks/platform/host/verifiy_account_ansible/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: verify_account_ansible -name: Ansible ping -description: Ansible ping -category: host -type: all -method: verify_account diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index d925164da..30fd6ce05 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -54,3 +54,8 @@ class ChoiceDisplayField(ChoiceField): 'value': value, 'label': self.choice_mapper.get(six.text_type(value), value), } + + def to_internal_value(self, data): + if isinstance(data, dict): + return data.get('value') + return super(ChoiceDisplayField, self).to_internal_value(data) diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index 2210b222b..d1c771ffc 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -90,6 +90,8 @@ class SimpleMetadataWithFilters(SimpleMetadata): for choice_value, choice_name in dict(field.choices).items() ] + if field.__class__.__name__ == 'ChoiceDisplayField': + field_info['type'] = 'display_choice' return field_info def get_filters_fields(self, request, view): From 60eb385c1e8796fe51166d72d7c0a919cb54a190 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 31 Aug 2022 11:42:20 +0800 Subject: [PATCH 093/488] =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/accounts.py | 46 ++++++++++------------------ apps/assets/api/asset.py | 4 --- apps/assets/api/platform.py | 2 +- apps/common/drf/filters.py | 11 ++++++- apps/locale/ja/LC_MESSAGES/django.mo | 4 +-- apps/orgs/filters.py | 4 --- 6 files changed, 29 insertions(+), 42 deletions(-) diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index 459b1013c..00f569357 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -1,5 +1,3 @@ -from django.db.models import Q -from django.shortcuts import get_object_or_404 from django_filters import rest_framework as filters from rest_framework.decorators import action from rest_framework.response import Response @@ -7,52 +5,40 @@ from rest_framework.generics import CreateAPIView from orgs.mixins.api import OrgBulkModelViewSet from rbac.permissions import RBACPermission -from common.drf.filters import BaseFilterSet +from common.drf.filters import BaseFilterSet, UUIDInFilter from common.mixins import RecordViewLogMixin from common.permissions import UserConfirmation from authentication.const import ConfirmType from ..tasks.account_connectivity import test_accounts_connectivity_manual -from ..models import Node, Account +from ..models import Account, Node from .. import serializers __all__ = ['AccountFilterSet', 'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] class AccountFilterSet(BaseFilterSet): - username = filters.CharFilter(method='do_nothing') ip = filters.CharFilter(field_name='ip', lookup_expr='exact') hostname = filters.CharFilter(field_name='name', lookup_expr='exact') - node = filters.CharFilter(method='do_nothing') + username = filters.CharFilter(field_name="username", lookup_expr='exact') + assets = UUIDInFilter(field_name='asset_id', lookup_expr='in') + nodes = UUIDInFilter(method='filter_nodes') - @property - def qs(self): - qs = super().qs - qs = self.filter_username(qs) - qs = self.filter_node(qs) - qs = qs.distinct() - return qs + def filter_nodes(self, queryset, name, value): + nodes = Node.objects.filter(id__in=value) + if not nodes: + return queryset - def filter_username(self, qs): - username = self.get_query_param('username') - if not username: - return qs - qs = qs.filter(Q(username=username) | Q(systemuser__username=username)).distinct() - return qs - - def filter_node(self, qs): - node_id = self.get_query_param('node') - if not node_id: - return qs - node = get_object_or_404(Node, pk=node_id) - node_ids = node.get_all_children(with_self=True).values_list('id', flat=True) - node_ids = list(node_ids) - qs = qs.filter(asset__nodes__in=node_ids) - return qs + node_qs = Node.objects.none() + for node in nodes: + node_qs |= node.get_all_children(with_self=True) + node_ids = list(node_qs.values_list('id', flat=True)) + queryset = queryset.filter(asset__nodes__in=node_ids) + return queryset class Meta: model = Account fields = [ - 'asset', 'id', + 'asset', 'id' ] diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 1e7cbdfe8..881395f5b 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,5 +1,3 @@ -<<<<<<< HEAD -======= # -*- coding: utf-8 -*- # from rest_framework.viewsets import ModelViewSet @@ -309,5 +307,3 @@ class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissio user_group_id = self.kwargs.get('perm_user_group_id') user_group = get_object_or_404(UserGroup, pk=user_group_id) return user_group - ->>>>>>> origin diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index ca0e83dcf..e538e12ee 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -5,7 +5,7 @@ from common.drf.api import JMSModelViewSet from common.drf.serializers import GroupedChoiceSerailizer from assets.models import Platform from assets.serializers import PlatformSerializer -from assets.const import AllTypes, Category +from assets.const import AllTypes from assets.resources.platform import get_platform_methods diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index 1c89f49ac..80074edf9 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -85,7 +85,7 @@ class DatetimeRangeFilter(filters.BaseFilterBackend): lookup = "__gte" else: lookup = "__lte" - kwargs[attr+lookup] = value + kwargs[attr + lookup] = value except ValidationError as e: print(e) continue @@ -171,4 +171,13 @@ def current_user_filter(user_field='user'): class CurrentUserFilter(filters.BaseFilterBackend): def filter_queryset(self, request, queryset, view): return queryset.filter(**{user_field: request.user}) + return CurrentUserFilter + + +class UUIDInFilter(drf_filters.BaseInFilter, drf_filters.UUIDFilter): + pass + + +class NumberInFilter(drf_filters.BaseInFilter, drf_filters.NumberFilter): + pass diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 9fc9c6640..4378759fd 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:261eee68117787809a9bc6b2034846ee7b222677224f97055f7d7398d427b1d7 -size 255 +oid sha256:d7d4ace7d7ec976b0321bde41789f994f02e3ab6f034828cbfb7e675a313611f +size 131531 diff --git a/apps/orgs/filters.py b/apps/orgs/filters.py index ee68a0ed8..139597f9c 100644 --- a/apps/orgs/filters.py +++ b/apps/orgs/filters.py @@ -1,6 +1,2 @@ -from django_filters.rest_framework import filters -class UUIDInFilter(filters.BaseInFilter, filters.UUIDFilter): - pass - From 94f898b55d09d561bca88e15aba6b52bd2f5eef0 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 1 Sep 2022 10:37:22 +0800 Subject: [PATCH 094/488] login asset acl --- apps/acls/api/login_asset_check.py | 4 +-- .../migrations/0004_auto_20220831_1658.py | 33 +++++++++++++++++ apps/acls/models/login_asset_acl.py | 23 ++++++------ apps/acls/serializers/login_asset_acl.py | 15 +++----- apps/acls/serializers/login_asset_check.py | 35 +++---------------- 5 files changed, 53 insertions(+), 57 deletions(-) create mode 100644 apps/acls/migrations/0004_auto_20220831_1658.py diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index a7e8990c7..bedf78d41 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -26,7 +26,7 @@ class LoginAssetCheckAPI(CreateAPIView): def check_if_need_confirm(self): queries = { 'user': self.serializer.user, 'asset': self.serializer.asset, - 'system_user': self.serializer.system_user, + 'account': self.serializer.account, 'action': LoginAssetACL.ActionChoices.login_confirm } with tmp_to_org(self.serializer.org): @@ -45,7 +45,7 @@ class LoginAssetCheckAPI(CreateAPIView): ticket = LoginAssetACL.create_login_asset_confirm_ticket( user=self.serializer.user, asset=self.serializer.asset, - system_user=self.serializer.system_user, + account=self.serializer.account, assignees=acl.reviewers.all(), org_id=self.serializer.org.id, ) diff --git a/apps/acls/migrations/0004_auto_20220831_1658.py b/apps/acls/migrations/0004_auto_20220831_1658.py new file mode 100644 index 000000000..e4392992b --- /dev/null +++ b/apps/acls/migrations/0004_auto_20220831_1658.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.13 on 2022-08-31 08:58 + +from django.db import migrations, models + + +def migrate_system_users_to_accounts(apps, schema_editor): + login_asset_acl_model = apps.get_model('acls', 'LoginAssetACL') + qs = login_asset_acl_model.objects.all() + login_asset_acls = [] + for instance in qs: + instance.accounts = instance.system_users + login_asset_acls.append(instance) + login_asset_acl_model.objects.bulk_update(login_asset_acls, ['accounts']) + + +class Migration(migrations.Migration): + dependencies = [ + ('acls', '0003_auto_20211130_1037'), + ] + + operations = [ + migrations.AddField( + model_name='loginassetacl', + name='accounts', + field=models.JSONField(default=dict, verbose_name='Account'), + ), + migrations.RunPython(migrate_system_users_to_accounts), + migrations.RemoveField( + model_name='loginassetacl', + name='system_users', + ), + + ] diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 5727e521b..8d920e4d5 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -18,7 +18,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): # 条件 users = models.JSONField(verbose_name=_('User')) - system_users = models.JSONField(verbose_name=_('System User')) + accounts = models.JSONField(verbose_name=_('Account'), default=dict) assets = models.JSONField(verbose_name=_('Asset')) # 动作 action = models.CharField( @@ -43,11 +43,11 @@ class LoginAssetACL(BaseACL, OrgModelMixin): return self.name @classmethod - def filter(cls, user, asset, system_user, action): + def filter(cls, user, asset, account, action): queryset = cls.objects.filter(action=action) queryset = cls.filter_user(user, queryset) queryset = cls.filter_asset(asset, queryset) - queryset = cls.filter_system_user(system_user, queryset) + queryset = cls.filter_account(account, queryset) return queryset @classmethod @@ -69,21 +69,18 @@ class LoginAssetACL(BaseACL, OrgModelMixin): return queryset @classmethod - def filter_system_user(cls, system_user, queryset): + def filter_account(cls, account, queryset): queryset = queryset.filter( - Q(system_users__name_group__contains=system_user.name) | - Q(system_users__name_group__contains='*') + Q(accounts__name_group__contains=account.name) | + Q(accounts__name_group__contains='*') ).filter( - Q(system_users__username_group__contains=system_user.username) | - Q(system_users__username_group__contains='*') - ).filter( - Q(system_users__protocol_group__contains=system_user.protocol) | - Q(system_users__protocol_group__contains='*') + Q(accounts__username_group__contains=account.username) | + Q(accounts__username_group__contains='*') ) return queryset @classmethod - def create_login_asset_confirm_ticket(cls, user, asset, system_user, assignees, org_id): + def create_login_asset_confirm_ticket(cls, user, asset, account, assignees, org_id): from tickets.const import TicketType from tickets.models import ApplyLoginAssetTicket title = _('Login asset confirm') + ' ({})'.format(user) @@ -93,7 +90,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): 'applicant': user, 'apply_login_user': user, 'apply_login_asset': asset, - 'apply_login_system_user': system_user, + 'apply_login_account': account, 'org_id': org_id, } ticket = ApplyLoginAssetTicket.objects.create(**data) diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index b30876f27..884b75c52 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -38,7 +38,7 @@ class LoginAssetACLAssestsSerializer(serializers.Serializer): ) -class LoginAssetACLSystemUsersSerializer(serializers.Serializer): +class LoginAssetACLAccountsSerializer(serializers.Serializer): protocol_group_help_text = _( 'Format for comma-delimited string, with * indicating a match all. ' 'Protocol options: {}' @@ -52,18 +52,12 @@ class LoginAssetACLSystemUsersSerializer(serializers.Serializer): default=['*'], child=serializers.CharField(max_length=128), label=_('Username'), help_text=common_help_text ) - protocol_group = serializers.ListField( - default=['*'], child=serializers.CharField(max_length=16), label=_('Protocol'), - help_text=protocol_group_help_text.format( - ', '.join([Protocol.ssh, Protocol.telnet]) - ) - ) class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): users = LoginAssetACLUsersSerializer() assets = LoginAssetACLAssestsSerializer() - system_users = LoginAssetACLSystemUsersSerializer() + account = LoginAssetACLAccountsSerializer() reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count') action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) @@ -71,9 +65,8 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): model = models.LoginAssetACL fields_mini = ['id', 'name'] fields_small = fields_mini + [ - 'users', 'system_users', 'assets', - 'is_active', - 'date_created', 'date_updated', + 'users', 'accounts', 'assets', + 'is_active', 'date_created', 'date_updated', 'priority', 'action', 'action_display', 'comment', 'created_by', 'org_id' ] fields_m2m = ['reviewers', 'reviewers_amount'] diff --git a/apps/acls/serializers/login_asset_check.py b/apps/acls/serializers/login_asset_check.py index 2c52506d5..b85d092ae 100644 --- a/apps/acls/serializers/login_asset_check.py +++ b/apps/acls/serializers/login_asset_check.py @@ -11,15 +11,15 @@ __all__ = ['LoginAssetCheckSerializer'] class LoginAssetCheckSerializer(serializers.Serializer): user_id = serializers.UUIDField(required=True, allow_null=False) asset_id = serializers.UUIDField(required=True, allow_null=False) - system_user_id = serializers.UUIDField(required=True, allow_null=False) - system_user_username = serializers.CharField(max_length=128, default='') + account_id = serializers.UUIDField(required=True, allow_null=False) + account_username = serializers.CharField(max_length=128, default='') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.user = None self.asset = None - self._system_user = None - self._system_user_username = None + self._account = None + self._account_username = None def validate_user_id(self, user_id): self.user = self.validate_object_exist(User, user_id) @@ -29,22 +29,6 @@ class LoginAssetCheckSerializer(serializers.Serializer): self.asset = self.validate_object_exist(Asset, asset_id) return asset_id - # def validate_system_user_id(self, system_user_id): - # self._system_user = self.validate_object_exist(SystemUser, system_user_id) - # return system_user_id - # - # def validate_system_user_username(self, system_user_username): - # system_user_id = self.initial_data.get('system_user_id') - # system_user = self.validate_object_exist(SystemUser, system_user_id) - # if self._system_user.login_mode == SystemUser.LOGIN_MANUAL \ - # and not system_user.username \ - # and not system_user.username_same_with_user \ - # and not system_user_username: - # error = 'Missing parameter: system_user_username' - # raise serializers.ValidationError(error) - # self._system_user_username = system_user_username - # return system_user_username - @staticmethod def validate_object_exist(model, field_id): with tmp_to_root_org(): @@ -54,17 +38,6 @@ class LoginAssetCheckSerializer(serializers.Serializer): raise serializers.ValidationError(error) return obj - # @lazyproperty - # def system_user(self): - # if self._system_user.username_same_with_user: - # username = self.user.username - # elif self._system_user.login_mode == SystemUser.LOGIN_MANUAL: - # username = self._system_user_username - # else: - # username = self._system_user.username - # self._system_user.username = username - # return self._system_user - @lazyproperty def org(self): return self.asset.org From d7d9fe271857e9b0d25694b3d0d394ab10323f9b Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Sep 2022 14:46:31 +0800 Subject: [PATCH 095/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0098_auto_20220430_2126.py | 2 +- .../migrations/0108_auto_20220901_1034.py | 37 +++ .../migrations/0109_auto_20220901_1431.py | 30 +++ .../models/{authbook.py => _authbook.py} | 0 apps/assets/models/_user.py | 3 +- apps/assets/models/account.py | 3 - apps/assets/models/asset/common.py | 3 +- apps/assets/models/asset/database.py | 1 + apps/assets/models/asset/host.py | 53 ---- apps/assets/models/base.py | 2 +- apps/assets/models/domain.py | 4 +- apps/assets/models/platform.py | 11 +- apps/assets/serializers/account/account.py | 3 +- apps/assets/serializers/asset.py | 226 ------------------ apps/assets/serializers/asset/__init__.py | 6 +- apps/assets/serializers/asset/category.py | 46 +--- apps/assets/serializers/asset/cloud.py | 11 + apps/assets/serializers/asset/common.py | 36 +-- apps/assets/serializers/asset/database.py | 11 + apps/assets/serializers/asset/host.py | 37 +++ apps/assets/serializers/asset/networking.py | 10 + apps/assets/serializers/asset/web.py | 11 + apps/assets/serializers/platform.py | 10 +- apps/common/drf/fields.py | 37 ++- apps/common/drf/metadata.py | 6 +- 25 files changed, 216 insertions(+), 383 deletions(-) create mode 100644 apps/assets/migrations/0108_auto_20220901_1034.py create mode 100644 apps/assets/migrations/0109_auto_20220901_1431.py rename apps/assets/models/{authbook.py => _authbook.py} (100%) delete mode 100644 apps/assets/serializers/asset.py create mode 100644 apps/assets/serializers/asset/cloud.py create mode 100644 apps/assets/serializers/asset/database.py create mode 100644 apps/assets/serializers/asset/host.py create mode 100644 apps/assets/serializers/asset/networking.py create mode 100644 apps/assets/serializers/asset/web.py diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index 943e8241f..fd3e27ba0 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -77,7 +77,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='su_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='SU method'), + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='SU method'), ), migrations.AddField( model_name='platform', diff --git a/apps/assets/migrations/0108_auto_20220901_1034.py b/apps/assets/migrations/0108_auto_20220901_1034.py new file mode 100644 index 000000000..d19749cf7 --- /dev/null +++ b/apps/assets/migrations/0108_auto_20220901_1034.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.14 on 2022-09-01 02:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_alter_accountbackupplan_types'), + ] + + operations = [ + migrations.RemoveField( + model_name='platform', + name='create_account_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='create_account_method', + ), + migrations.RemoveField( + model_name='platform', + name='domain_default', + ), + migrations.RemoveField( + model_name='platform', + name='domain_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='ping_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='ping_method', + ), + ] diff --git a/apps/assets/migrations/0109_auto_20220901_1431.py b/apps/assets/migrations/0109_auto_20220901_1431.py new file mode 100644 index 000000000..07fad818e --- /dev/null +++ b/apps/assets/migrations/0109_auto_20220901_1431.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.14 on 2022-09-01 06:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0108_auto_20220901_1034'), + ] + + operations = [ + migrations.RemoveField( + model_name='host', + name='device_info', + ), + migrations.AddField( + model_name='asset', + name='info', + field=models.JSONField(blank=True, default=dict, verbose_name='Info'), + ), + migrations.AddField( + model_name='database', + name='version', + field=models.CharField(blank=True, max_length=16, verbose_name='Version'), + ), + migrations.DeleteModel( + name='DeviceInfo', + ), + ] diff --git a/apps/assets/models/authbook.py b/apps/assets/models/_authbook.py similarity index 100% rename from apps/assets/models/authbook.py rename to apps/assets/models/_authbook.py diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index 83aaec2c0..ab593b016 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -16,7 +16,7 @@ __all__ = ['SystemUser'] logger = logging.getLogger(__name__) -class SystemUser(ProtocolMixin, BaseAccount): +class SystemUser(BaseAccount, ProtocolMixin): LOGIN_AUTO = 'auto' LOGIN_MANUAL = 'manual' LOGIN_MODE_CHOICES = ( @@ -44,6 +44,7 @@ class SystemUser(ProtocolMixin, BaseAccount): # linux su 命令 (switch user) su_enabled = models.BooleanField(default=False, verbose_name=_('User switch')) su_from = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='su_to', null=True, verbose_name=_("Switch from")) + privileged = None class Meta: ordering = ['name'] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 585307324..fc76e86ee 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -2,14 +2,12 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords -from common.db import fields from .base import BaseAccount, AbsConnectivity __all__ = ['Account', 'AccountTemplate'] class Account(BaseAccount, AbsConnectivity): - token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) @@ -30,7 +28,6 @@ class Account(BaseAccount, AbsConnectivity): class AccountTemplate(BaseAccount, AbsConnectivity): - token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) class Meta: diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 1842f5f1b..0a995d8cc 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -11,7 +11,6 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty from orgs.mixins.models import OrgManager, JMSOrgBaseModel -from ...const import Category from ..platform import Platform from ..base import AbsConnectivity @@ -82,7 +81,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - + info = models.JSONField(verbose_name='Info', default=dict, blank=True) objects = AssetManager.from_queryset(AssetQuerySet)() def __str__(self): diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index cb97c95cd..9d5ee1325 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -6,6 +6,7 @@ from .common import Asset class Database(Asset): db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + version = models.CharField(max_length=16, verbose_name=_("Version"), blank=True) def __str__(self): return '{}({}://{}/{})'.format(self.name, self.type, self.ip, self.db_name) diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 79df6eb58..3f29fbe8f 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,61 +1,8 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - -from common.mixins.models import CommonModelMixin from assets.const import Category from .common import Asset class Host(Asset): - device_info = models.OneToOneField('DeviceInfo', null=True, on_delete=models.SET_NULL, verbose_name=_("Host")) - def save(self, *args, **kwargs): self.category = Category.HOST return super().save(*args, **kwargs) - - -class DeviceInfo(CommonModelMixin): - # Collect - vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor')) - model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model')) - sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) - - cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model')) - cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count')) - cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores')) - cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus')) - memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory')) - disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total')) - disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info')) - - os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS')) - os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version')) - os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) - hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) - number = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Asset number')) - - @property - def cpu_info(self): - info = "" - if self.cpu_model: - info += self.cpu_model - if self.cpu_count and self.cpu_cores: - info += "{}*{}".format(self.cpu_count, self.cpu_cores) - return info - - @property - def hardware_info(self): - if self.cpu_count: - return '{} Core {} {}'.format( - self.cpu_vcpus or self.cpu_count * self.cpu_cores, - self.memory, self.disk_total - ) - else: - return '' - - def __str__(self): - return '{} of {}'.format(self.hardware_info, self.host.name) - - class Meta: - verbose_name = _("DeviceInfo") - diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index c32de09e1..9cdac9e1f 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -176,7 +176,7 @@ class BaseAccount(OrgModelMixin, AuthMixin): password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) - # token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) + token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index a4ab3888f..72a5c748c 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -22,8 +22,7 @@ class Domain(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) comment = models.TextField(blank=True, verbose_name=_('Comment')) - date_created = models.DateTimeField(auto_now_add=True, null=True, - verbose_name=_('Date created')) + date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) class Meta: verbose_name = _("Domain") @@ -64,6 +63,7 @@ class Gateway(BaseAccount): domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) + token = None def __str__(self): return self.name diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 06bdc4de3..71ebf769f 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -1,7 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from assets.const import Category, AllTypes +from assets.const import AllTypes from common.db.fields import JsonDictTextField @@ -30,23 +30,14 @@ class Platform(models.Model): meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) internal = models.BooleanField(default=False, verbose_name=_("Internal")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) - domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) - domain_default = models.ForeignKey( - 'assets.Domain', null=True, on_delete=models.SET_NULL, - verbose_name=_("Domain default") - ) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) # Accounts # 这应该和账号有关 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) - ping_enabled = models.BooleanField(default=False) - ping_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) - create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) - create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index e43445251..0e73f6a1b 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -11,7 +11,8 @@ from .common import BaseAccountSerializer class AccountSerializer( - AccountTemplateSerializerMixin, AuthSerializerMixin, + AccountTemplateSerializerMixin, + AuthSerializerMixin, BulkOrgResourceModelSerializer ): ip = serializers.ReadOnlyField(label=_("IP")) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py deleted file mode 100644 index 5211cfef6..000000000 --- a/apps/assets/serializers/asset.py +++ /dev/null @@ -1,226 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import serializers -from django.core.validators import RegexValidator -from django.utils.translation import ugettext_lazy as _ - -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from ..models import Asset, Node, Platform, SystemUser - -__all__ = [ - 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', - 'ProtocolsField', 'PlatformSerializer', - 'AssetTaskSerializer', 'AssetsTaskSerializer', 'ProtocolsField', -] - - -class ProtocolField(serializers.RegexField): - protocols = '|'.join(dict(Asset.Protocol.choices).keys()) - default_error_messages = { - 'invalid': _('Protocol format should {}/{}').format(protocols, '1-65535') - } - regex = r'^(%s)/(\d{1,5})$' % protocols - - def __init__(self, *args, **kwargs): - super().__init__(self.regex, **kwargs) - - -def validate_duplicate_protocols(values): - errors = [] - names = [] - - for value in values: - if not value or '/' not in value: - continue - name = value.split('/')[0] - if name in names: - errors.append(_("Protocol duplicate: {}").format(name)) - names.append(name) - errors.append('') - if any(errors): - raise serializers.ValidationError(errors) - - -class ProtocolsField(serializers.ListField): - default_validators = [validate_duplicate_protocols] - - def __init__(self, *args, **kwargs): - kwargs['child'] = ProtocolField() - kwargs['allow_null'] = True - kwargs['allow_empty'] = True - kwargs['min_length'] = 1 - kwargs['max_length'] = 4 - super().__init__(*args, **kwargs) - - def to_representation(self, value): - if not value: - return [] - return value.split(' ') - - -class AssetSerializer(BulkOrgResourceModelSerializer): - platform = serializers.SlugRelatedField( - slug_field='name', queryset=Platform.objects.all(), label=_("Platform") - ) - protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22']) - domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name')) - nodes_display = serializers.ListField( - child=serializers.CharField(), label=_('Nodes name'), required=False - ) - labels_display = serializers.ListField( - child=serializers.CharField(), label=_('Labels name'), required=False, read_only=True - ) - - """ - 资产的数据结构 - """ - - class Meta: - model = Asset - fields_mini = ['id', 'hostname', 'ip', 'platform', 'protocols'] - fields_small = fields_mini + [ - 'protocol', 'port', 'protocols', 'is_active', - 'public_ip', 'number', 'comment', - ] - fields_hardware = [ - 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', - 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', - 'os', 'os_version', 'os_arch', 'hostname_raw', - 'cpu_info', 'hardware_info', - ] - fields_fk = [ - 'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display' - ] - fields_m2m = [ - 'nodes', 'nodes_display', 'labels', 'labels_display', - ] - read_only_fields = [ - 'connectivity', 'date_verified', 'cpu_info', 'hardware_info', - 'created_by', 'date_created', - ] - fields = fields_small + fields_hardware + fields_fk + fields_m2m + read_only_fields - extra_kwargs = { - 'protocol': {'write_only': True}, - 'port': {'write_only': True}, - 'hardware_info': {'label': _('Hardware info'), 'read_only': True}, - 'admin_user_display': {'label': _('Admin user display'), 'read_only': True}, - 'cpu_info': {'label': _('CPU info')}, - } - - def get_fields(self): - fields = super().get_fields() - - admin_user_field = fields.get('admin_user') - # 因为 mixin 中对 fields 有处理,可能不需要返回 admin_user - if admin_user_field: - admin_user_field.queryset = SystemUser.objects.filter(type=SystemUser.Type.admin) - return fields - - @classmethod - def setup_eager_loading(cls, queryset): - """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('domain', 'platform', 'admin_user') - queryset = queryset.prefetch_related('nodes', 'labels') - return queryset - - def compatible_with_old_protocol(self, validated_data): - protocols_data = validated_data.pop("protocols", []) - - # 兼容老的api - name = validated_data.get("protocol") - port = validated_data.get("port") - if not protocols_data and name and port: - protocols_data.insert(0, '/'.join([name, str(port)])) - elif not name and not port and protocols_data: - protocol = protocols_data[0].split('/') - validated_data["protocol"] = protocol[0] - validated_data["port"] = int(protocol[1]) - if protocols_data: - validated_data["protocols"] = ' '.join(protocols_data) - - def perform_nodes_display_create(self, instance, nodes_display): - if not nodes_display: - return - nodes_to_set = [] - for full_value in nodes_display: - node = Node.objects.filter(full_value=full_value).first() - if node: - nodes_to_set.append(node) - else: - node = Node.create_node_by_full_value(full_value) - nodes_to_set.append(node) - instance.nodes.set(nodes_to_set) - - def create(self, validated_data): - self.compatible_with_old_protocol(validated_data) - nodes_display = validated_data.pop('nodes_display', '') - instance = super().create(validated_data) - self.perform_nodes_display_create(instance, nodes_display) - return instance - - def update(self, instance, validated_data): - nodes_display = validated_data.pop('nodes_display', '') - self.compatible_with_old_protocol(validated_data) - instance = super().update(instance, validated_data) - self.perform_nodes_display_create(instance, nodes_display) - return instance - - -class MiniAssetSerializer(serializers.ModelSerializer): - class Meta: - model = Asset - fields = AssetSerializer.Meta.fields_mini - - -class PlatformSerializer(serializers.ModelSerializer): - meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # TODO 修复 drf SlugField RegexValidator bug,之后记得删除 - validators = self.fields['name'].validators - if isinstance(validators[-1], RegexValidator): - validators.pop() - - class Meta: - model = Platform - fields = [ - 'id', 'name', 'base', 'charset', - 'internal', 'meta', 'comment' - ] - extra_kwargs = { - 'internal': {'read_only': True}, - } - - -class AssetSimpleSerializer(serializers.ModelSerializer): - class Meta: - model = Asset - fields = ['id', 'hostname', 'ip', 'port', 'connectivity', 'date_verified'] - - -class AssetsTaskSerializer(serializers.Serializer): - ACTION_CHOICES = ( - ('refresh', 'refresh'), - ('test', 'test'), - ) - task = serializers.CharField(read_only=True) - action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True) - assets = serializers.PrimaryKeyRelatedField( - queryset=Asset.objects, required=False, allow_empty=True, many=True - ) - - -class AssetTaskSerializer(AssetsTaskSerializer): - ACTION_CHOICES = tuple(list(AssetsTaskSerializer.ACTION_CHOICES) + [ - ('push_system_user', 'push_system_user'), - ('test_system_user', 'test_system_user') - ]) - action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True) - asset = serializers.PrimaryKeyRelatedField( - queryset=Asset.objects, required=False, allow_empty=True, many=False - ) - system_users = serializers.PrimaryKeyRelatedField( - queryset=SystemUser.objects, required=False, allow_empty=True, many=True - ) diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py index c6e5732be..93d35b736 100644 --- a/apps/assets/serializers/asset/__init__.py +++ b/apps/assets/serializers/asset/__init__.py @@ -1,2 +1,6 @@ from .common import * -from .category import * +from .host import * +from .database import * +from .networking import * +from .cloud import * +from .web import * diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index e4129bb1a..1b69dc79d 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -1,49 +1,7 @@ -from rest_framework import serializers - -from assets.models import DeviceInfo, Host, Database, Networking, Cloud, Web +from assets.models import Networking from .common import AssetSerializer -__all__ = [ - 'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer', - 'NetworkingSerializer', 'CloudSerializer', 'WebSerializer', -] - - -class DeviceSerializer(serializers.ModelSerializer): - class Meta: - model = DeviceInfo - fields = [ - 'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', - 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', - 'os', 'os_version', 'os_arch', 'hostname_raw', 'number', - 'cpu_info', 'hardware_info', 'date_updated' - ] - - -class HostSerializer(AssetSerializer): - device_info = DeviceSerializer(read_only=True, allow_null=True) - - class Meta(AssetSerializer.Meta): - model = Host - fields = AssetSerializer.Meta.fields + ['device_info'] - - -class DatabaseSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Database - fields = AssetSerializer.Meta.fields + ['db_name'] - - -class WebSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Web - fields = AssetSerializer.Meta.fields + ['url'] - - -class CloudSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Cloud - fields = AssetSerializer.Meta.fields + ['cluster'] +__all__ = ['NetworkingSerializer'] class NetworkingSerializer(AssetSerializer): diff --git a/apps/assets/serializers/asset/cloud.py b/apps/assets/serializers/asset/cloud.py new file mode 100644 index 000000000..38e95bc2c --- /dev/null +++ b/apps/assets/serializers/asset/cloud.py @@ -0,0 +1,11 @@ +from assets.models import Cloud +from .common import AssetSerializer + +__all__ = ['CloudSerializer'] + + +class CloudSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Cloud + fields = AssetSerializer.Meta.fields + ['cluster'] + diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 4a043595e..11c800224 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -6,7 +6,7 @@ from django.db.transaction import atomic from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer -from common.drf.fields import ChoiceDisplayField +from common.drf.fields import LabeledChoiceField, ObjectedRelatedField from ..account import AccountSerializer from ...models import Asset, Node, Platform, Protocol, Label, Domain from ...const import Category, AllTypes @@ -42,33 +42,15 @@ class AssetPlatformSerializer(serializers.ModelSerializer): } -class AssetDomainSerializer(serializers.ModelSerializer): - class Meta: - model = Domain - fields = ['id', 'name'] - extra_kwargs = { - 'name': {'required': False} - } - - -class AssetNodesSerializer(serializers.ModelSerializer): - class Meta: - model = Node - fields = ['id', 'value'] - extra_kwargs = { - 'value': {'required': False} - } - - class AssetSerializer(JMSWritableNestedModelSerializer): - category = ChoiceDisplayField(choices=Category.choices, read_only=True, label=_('Category')) - type = ChoiceDisplayField(choices=AllTypes.choices, read_only=True, label=_('Type')) - domain = AssetDomainSerializer(required=False) - platform = AssetPlatformSerializer(required=False) - labels = AssetLabelSerializer(many=True, required=False) - nodes = AssetNodesSerializer(many=True, required=False) - accounts = AccountSerializer(many=True, required=False) - protocols = AssetProtocolsSerializer(many=True, required=False) + category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) + type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type')) + domain = ObjectedRelatedField(required=False, queryset=Domain.objects, label=_('Domain')) + platform = ObjectedRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) + nodes = ObjectedRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) + labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) + accounts = AccountSerializer(many=True, required=False, label=_('Accounts')) + protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) """ 资产的数据结构 diff --git a/apps/assets/serializers/asset/database.py b/apps/assets/serializers/asset/database.py new file mode 100644 index 000000000..d168d2ffe --- /dev/null +++ b/apps/assets/serializers/asset/database.py @@ -0,0 +1,11 @@ + +from assets.models import Database +from .common import AssetSerializer + +__all__ = ['DatabaseSerializer'] + + +class DatabaseSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Database + fields = AssetSerializer.Meta.fields + ['db_name'] diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py new file mode 100644 index 000000000..62638035d --- /dev/null +++ b/apps/assets/serializers/asset/host.py @@ -0,0 +1,37 @@ +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ + +from assets.models import Host +from .common import AssetSerializer + + +__all__ = ['HostInfoSerializer', 'HostSerializer'] + + +class HostInfoSerializer(serializers.Serializer): + vendor = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Vendor')) + model = serializers.CharField(max_length=54, required=False, allow_blank=True, label=_('Model')) + sn = serializers.CharField(max_length=128, required=False, allow_blank=True, label=_('Serial number')) + + cpu_model = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('CPU model')) + cpu_count = serializers.IntegerField(required=False, label=_('CPU count')) + cpu_cores = serializers.IntegerField(required=False, label=_('CPU cores')) + cpu_vcpus = serializers.IntegerField(required=False, label=_('CPU vcpus')) + memory = serializers.CharField(max_length=64, allow_blank=True, required=False, label=_('Memory')) + disk_total = serializers.CharField(max_length=1024, allow_blank=True, required=False, label=_('Disk total')) + disk_info = serializers.CharField(max_length=1024, allow_blank=True, required=False, label=_('Disk info')) + + os = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('OS')) + os_version = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS version')) + os_arch = serializers.CharField(max_length=16, allow_blank=True, required=False, label=_('OS arch')) + hostname_raw = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Hostname raw')) + number = serializers.CharField(max_length=128, allow_blank=True, required=False, label=_('Asset number')) + + +class HostSerializer(AssetSerializer): + info = HostInfoSerializer(allow_null=True) + + class Meta(AssetSerializer.Meta): + model = Host + fields = AssetSerializer.Meta.fields + ['info'] + diff --git a/apps/assets/serializers/asset/networking.py b/apps/assets/serializers/asset/networking.py new file mode 100644 index 000000000..aff838bd5 --- /dev/null +++ b/apps/assets/serializers/asset/networking.py @@ -0,0 +1,10 @@ + +from assets.models import Networking +from .common import AssetSerializer + +__all__ = ['NetworkingSerializer'] + + +class NetworkingSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Networking diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py new file mode 100644 index 000000000..fc726c68e --- /dev/null +++ b/apps/assets/serializers/asset/web.py @@ -0,0 +1,11 @@ + +from assets.models import Web +from .common import AssetSerializer + +__all__ = ['WebSerializer'] + + +class WebSerializer(AssetSerializer): + class Meta(AssetSerializer.Meta): + model = Web + fields = AssetSerializer.Meta.fields + ['url'] diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 75f24897e..c346a49ec 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -1,7 +1,7 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ -from common.drf.fields import ChoiceDisplayField +from common.drf.fields import LabeledChoiceField from common.drf.serializers import JMSWritableNestedModelSerializer from ..models import Platform, PlatformProtocol from ..const import Category, AllTypes @@ -30,11 +30,11 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer): class PlatformSerializer(JMSWritableNestedModelSerializer): - type = ChoiceDisplayField(choices=AllTypes.choices, label=_("Type")) - category = ChoiceDisplayField(choices=Category.choices, label=_("Category")) + type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type")) + category = LabeledChoiceField(choices=Category.choices, label=_("Category")) protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) type_constraints = serializers.ReadOnlyField(required=False, read_only=True) - su_method = ChoiceDisplayField( + su_method = LabeledChoiceField( choices=[('sudo', 'sudo su -'), ('su', 'su - ')], label='切换方式', required=False, default='sudo' ) @@ -54,6 +54,8 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): ] extra_kwargs = { 'su_enabled': {'label': '启用切换账号'}, + 'domain_enabled': {'label': "启用网域"}, + 'domain_default': {'label': "默认网域"}, 'verify_account_enabled': {'label': '启用校验账号'}, 'verify_account_method': {'label': '校验账号方式'}, 'create_account_enabled': {'label': '启用创建账号'}, diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 30fd6ce05..a61a59b04 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -4,11 +4,13 @@ import six from rest_framework.fields import ChoiceField from rest_framework import serializers +from django.core.exceptions import ObjectDoesNotExist from common.utils import decrypt_password __all__ = [ - 'ReadableHiddenField', 'EncryptedField', 'ChoiceDisplayField' + 'ReadableHiddenField', 'EncryptedField', 'LabeledChoiceField', + 'ObjectedRelatedField', ] @@ -40,9 +42,9 @@ class EncryptedField(serializers.CharField): return decrypt_password(value) -class ChoiceDisplayField(ChoiceField): +class LabeledChoiceField(ChoiceField): def __init__(self, *args, **kwargs): - super(ChoiceDisplayField, self).__init__(*args, **kwargs) + super(LabeledChoiceField, self).__init__(*args, **kwargs) self.choice_mapper = { six.text_type(key): value for key, value in self.choices.items() } @@ -58,4 +60,31 @@ class ChoiceDisplayField(ChoiceField): def to_internal_value(self, data): if isinstance(data, dict): return data.get('value') - return super(ChoiceDisplayField, self).to_internal_value(data) + return super(LabeledChoiceField, self).to_internal_value(data) + + +class ObjectedRelatedField(serializers.RelatedField): + def __init__(self, **kwargs): + self.attrs = kwargs.pop('attrs', None) or ('id', 'name') + super().__init__(**kwargs) + + def to_representation(self, value): + data = {} + for attr in self.attrs: + data[attr] = getattr(value, attr) + return data + + def to_internal_value(self, data): + if isinstance(data, dict): + pk = data.get(self.attrs[0]) + else: + pk = data + queryset = self.get_queryset() + try: + if isinstance(data, bool): + raise TypeError + return queryset.get(pk=pk) + except ObjectDoesNotExist: + self.fail('does_not_exist', pk_value=pk) + except (TypeError, ValueError): + self.fail('incorrect_type', data_type=type(pk).__name__) diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index d1c771ffc..e84731321 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -85,13 +85,13 @@ class SimpleMetadataWithFilters(SimpleMetadata): field_info['choices'] = [ { 'value': choice_value, - 'display_name': force_text(choice_name, strings_only=True) + 'label': force_text(choice_name, strings_only=True) } for choice_value, choice_name in dict(field.choices).items() ] - if field.__class__.__name__ == 'ChoiceDisplayField': - field_info['type'] = 'display_choice' + if field.__class__.__name__ == 'LabeledChoiceField': + field_info['type'] = 'labeled_choice' return field_info def get_filters_fields(self, request, view): From 81219e1e7c5a79b86dc75eb59f9ce63a752bdd62 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Sep 2022 17:42:48 +0800 Subject: [PATCH 096/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=E5=90=84?= =?UTF-8?q?=E7=A7=8D=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 36 +++++++++------- .../migrations/0110_auto_20220901_1542.py | 43 +++++++++++++++++++ apps/assets/models/platform.py | 16 ++++--- apps/assets/serializers/platform.py | 11 ++++- 4 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 apps/assets/migrations/0110_auto_20220901_1542.py diff --git a/apps/assets/const.py b/apps/assets/const.py index 788c821ab..6a4fb8cf4 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -14,12 +14,13 @@ class PlatformMixin: @classmethod def platform_constraints(cls): return { - 'has_domain': False, - 'has_su': False, - 'has_ping': False, - 'has_change_password': False, - 'has_verify_account': False, - 'has_create_account': False, + 'domain_enabled': False, + 'gather_facts_enabled': False, + 'su_enabled': False, + 'change_password_enabled': False, + 'verify_account_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, '_protocols': [] } @@ -35,26 +36,29 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): def platform_constraints(cls) -> dict: return { cls.HOST: { - 'has_domain': True, - 'has_ping': True, - 'has_verify_account': True, - 'has_change_password': True, - 'has_create_account': True, + 'domain_enabled': True, + 'su_enabled': True, + 'ping_enabled': True, + 'gather_facts_enabled': True, + 'verify_account_enabled': True, + 'change_password_enabled': True, + 'create_account_enabled': True, + 'gather_accounts_enabled': True, '_protocols': ['ssh', 'telnet'] }, cls.NETWORKING: { - 'has_domain': True, + 'domain_enabled': True, '_protocols': ['ssh', 'telnet'] }, cls.DATABASE: { - 'has_domain': True, + 'domain_enabled': True, }, cls.WEB: { - 'has_domain': False, + 'domain_enabled': False, '_protocols': [] }, cls.CLOUD: { - 'has_domain': False, + 'domain_enabled': False, '_protocols': [] } } @@ -77,7 +81,7 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): }, cls.WINDOWS: { '_protocols': ['ssh', 'rdp', 'vnc'], - 'has_su': False + 'su_enabled': False }, cls.MACOS: { '_protocols': ['ssh', 'vnc'] diff --git a/apps/assets/migrations/0110_auto_20220901_1542.py b/apps/assets/migrations/0110_auto_20220901_1542.py new file mode 100644 index 000000000..1c37f19dd --- /dev/null +++ b/apps/assets/migrations/0110_auto_20220901_1542.py @@ -0,0 +1,43 @@ +# Generated by Django 3.2.14 on 2022-09-01 07:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0109_auto_20220901_1431'), + ] + + operations = [ + migrations.AddField( + model_name='platform', + name='create_account_enabled', + field=models.BooleanField(default=False, verbose_name='Create account enabled'), + ), + migrations.AddField( + model_name='platform', + name='create_account_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method'), + ), + migrations.AddField( + model_name='platform', + name='gather_accounts_enabled', + field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), + ), + migrations.AddField( + model_name='platform', + name='gather_accounts_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), + ), + migrations.AddField( + model_name='platform', + name='gather_facts_enabled', + field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), + ), + migrations.AddField( + model_name='platform', + name='gather_facts_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), + ), + ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 71ebf769f..9feb5325a 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -26,20 +26,26 @@ class Platform(models.Model): name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) category = models.CharField(default='host', max_length=32, verbose_name=_("Category")) type = models.CharField(max_length=32, default='linux', verbose_name=_("Type")) - charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) internal = models.BooleanField(default=False, verbose_name=_("Internal")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + # 资产有关的 + charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) - # Accounts - # 这应该和账号有关 + gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) + gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) + # 账号有关的 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) - verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) - verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) + create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) + verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) + verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) + gather_accounts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) @property def type_constraints(self): diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index c346a49ec..cf888ba1a 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -46,8 +46,11 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'category', 'type', ] fields = fields_small + [ - 'domain_enabled', 'domain_default', 'su_enabled', 'su_method', - 'protocols_enabled', 'protocols', 'ping_enabled', 'ping_method', + 'protocols_enabled', 'protocols', + 'gather_facts_enabled', 'gather_facts_method', + 'su_enabled', 'su_method', + 'gather_accounts_enabled', 'gather_accounts_method', + 'create_account_enabled', 'create_account_method', 'verify_account_enabled', 'verify_account_method', 'change_password_enabled', 'change_password_method', 'type_constraints', 'comment', 'charset', @@ -56,12 +59,16 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'su_enabled': {'label': '启用切换账号'}, 'domain_enabled': {'label': "启用网域"}, 'domain_default': {'label': "默认网域"}, + 'gather_facts_enabled': {'label': '启用收集信息'}, + 'gather_facts_method': {'label': '收集信息方式'}, 'verify_account_enabled': {'label': '启用校验账号'}, 'verify_account_method': {'label': '校验账号方式'}, 'create_account_enabled': {'label': '启用创建账号'}, 'create_account_method': {'label': '创建账号方式'}, 'change_password_enabled': {'label': '启用账号创建改密'}, 'change_password_method': {'label': '账号创建改密方式'}, + 'gather_accounts_enabled': {'label': '启用账号收集'}, + 'gather_accounts_method': {'label': '收集账号方式'}, } def validate(self, attrs): From 7ae395f7e87c89bd5fd509a3109759ab5f94d3c6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Sep 2022 21:00:04 +0800 Subject: [PATCH 097/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20metadata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 8 ++++---- apps/common/drf/fields.py | 5 +++-- apps/common/drf/metadata.py | 13 ++++++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 11c800224..439d96483 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -6,7 +6,7 @@ from django.db.transaction import atomic from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer -from common.drf.fields import LabeledChoiceField, ObjectedRelatedField +from common.drf.fields import LabeledChoiceField, ObjectRelatedField from ..account import AccountSerializer from ...models import Asset, Node, Platform, Protocol, Label, Domain from ...const import Category, AllTypes @@ -45,9 +45,9 @@ class AssetPlatformSerializer(serializers.ModelSerializer): class AssetSerializer(JMSWritableNestedModelSerializer): category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type')) - domain = ObjectedRelatedField(required=False, queryset=Domain.objects, label=_('Domain')) - platform = ObjectedRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) - nodes = ObjectedRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) + domain = ObjectRelatedField(required=False, queryset=Domain.objects, label=_('Domain')) + platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) + nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) accounts = AccountSerializer(many=True, required=False, label=_('Accounts')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index a61a59b04..31386b0a0 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -10,7 +10,7 @@ from common.utils import decrypt_password __all__ = [ 'ReadableHiddenField', 'EncryptedField', 'LabeledChoiceField', - 'ObjectedRelatedField', + 'ObjectRelatedField', ] @@ -63,9 +63,10 @@ class LabeledChoiceField(ChoiceField): return super(LabeledChoiceField, self).to_internal_value(data) -class ObjectedRelatedField(serializers.RelatedField): +class ObjectRelatedField(serializers.RelatedField): def __init__(self, **kwargs): self.attrs = kwargs.pop('attrs', None) or ('id', 'name') + self.many = kwargs.get('many', False) super().__init__(**kwargs) def to_representation(self, value): diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index e84731321..939c1f314 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -90,8 +90,19 @@ class SimpleMetadataWithFilters(SimpleMetadata): for choice_value, choice_name in dict(field.choices).items() ] - if field.__class__.__name__ == 'LabeledChoiceField': + class_name = field.__class__.__name__ + if class_name == 'LabeledChoiceField': field_info['type'] = 'labeled_choice' + elif class_name == 'ObjectRelatedField': + field_info['type'] = 'object_related_field' + elif class_name == 'ManyRelatedField': + child_relation_class_name = field.child_relation.__class__.__name__ + if child_relation_class_name == 'ObjectRelatedField': + field_info['type'] = 'm2m_related_field' + + # if field.label == '系统平台': + # print("Field: ", class_name, field, field_info) + return field_info def get_filters_fields(self, request, view): From 757e688ab36e545e451b8f4047929f8865936831 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Sun, 4 Sep 2022 16:33:36 +0800 Subject: [PATCH 098/488] =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=A4=87=E4=BB=BD?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/task_handlers/backup/handlers.py | 42 ++++++++++++-------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index 42d21484d..3b8eac87d 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -49,16 +49,23 @@ class BaseAccountHandler: return header_fields @classmethod - def create_row(cls, account, serializer_cls, header_fields=None): - serializer = serializer_cls(account) - if not header_fields: - header_fields = cls.get_header_fields(serializer) - data = cls.unpack_data(serializer.data) + def create_row(cls, data, header_fields): + data = cls.unpack_data(data) row_dict = {} for field, header_name in header_fields.items(): - row_dict[header_name] = str(data[field]) + row_dict[header_name] = str(data.get(field, field)) return row_dict + @classmethod + def add_rows(cls, data, header_fields, sheet): + data_map = defaultdict(list) + for i in data: + row = cls.create_row(i, header_fields) + if sheet not in data_map: + data_map[sheet].append(list(row.keys())) + data_map[sheet].append(list(row.values())) + return data_map + class AssetAccountHandler(BaseAccountHandler): @staticmethod @@ -73,22 +80,25 @@ class AssetAccountHandler(BaseAccountHandler): data_map = defaultdict(list) # TODO 可以优化一下查询 在账号上做type的缓存 避免数据量大时连表操作 - accounts = Account.objects.filter( + qs = Account.objects.filter( asset__platform__type__in=types ).annotate(type=F('asset__platform__type')) - if not accounts.first(): + if not qs.exists(): return data_map type_dict = dict(Type.CHOICES) - header_fields = cls.get_header_fields(AccountSecretSerializer(accounts.first())) - for account in accounts: - sheet_name = type_dict[account.type] - row = cls.create_row(account, AccountSecretSerializer, header_fields) - if sheet_name not in data_map: - data_map[sheet_name].append(list(row.keys())) - data_map[sheet_name].append(list(row.values())) + header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first())) + account_type_map = defaultdict(list) + for account in qs: + account_type_map[account.type].append(account) - logger.info('\n\033[33m- 共收集 {} 条账号\033[0m'.format(accounts.count())) + data_map = {} + for tp, accounts in account_type_map.items(): + sheet_name = type_dict[tp] + data = AccountSecretSerializer(accounts, many=True).data + data_map.update(cls.add_rows(data, header_fields, sheet_name)) + + logger.info('\n\033[33m- 共收集 {} 条账号\033[0m'.format(qs.count())) return data_map From 2354650b8203c3d704f2969453ee5165b85baa10 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Sep 2022 12:47:01 +0800 Subject: [PATCH 099/488] =?UTF-8?q?perf:=20=E6=9A=82=E6=97=B6=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=20csrf=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/views/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 88f580279..a804ca353 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -147,7 +147,7 @@ class UserLoginContextMixin: @method_decorator(sensitive_post_parameters(), name='dispatch') -@method_decorator(csrf_protect, name='dispatch') +# @method_decorator(csrf_protect, name='dispatch') @method_decorator(never_cache, name='dispatch') class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView): redirect_field_name = 'next' From d9663036f8565e646abebce4e33cd6d84af8c501 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Sep 2022 13:07:20 +0800 Subject: [PATCH 100/488] perf: revert csrf token project --- apps/authentication/views/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index a804ca353..88f580279 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -147,7 +147,7 @@ class UserLoginContextMixin: @method_decorator(sensitive_post_parameters(), name='dispatch') -# @method_decorator(csrf_protect, name='dispatch') +@method_decorator(csrf_protect, name='dispatch') @method_decorator(never_cache, name='dispatch') class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView): redirect_field_name = 'next' From 4276ddc2ccd7a70b9fc632d64e3cdd6cabef7438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B9=BF=E5=AE=8F=E4=BC=9F?= Date: Mon, 5 Sep 2022 18:50:33 +0800 Subject: [PATCH 101/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=84=9A?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/mac_pkg.sh | 11 ++++++++++- requirements/requirements.txt | 7 +++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/requirements/mac_pkg.sh b/requirements/mac_pkg.sh index 5108b36fa..45049eb0a 100644 --- a/requirements/mac_pkg.sh +++ b/requirements/mac_pkg.sh @@ -3,7 +3,9 @@ BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" PROJECT_DIR=$(dirname "$BASE_DIR") echo "1. 安装依赖" -brew install libtiff libjpeg webp little-cms2 openssl gettext git git-lfs mysql libxml2 libxmlsec1 pkg-config postgresql freetds openssl +brew install libtiff libjpeg webp little-cms2 openssl gettext git \ + git-lfs mysql libxml2 libxmlsec1 pkg-config postgresql freetds openssl \ + libffi echo "2. 下载 IP 数据库" ip_db_path="${PROJECT_DIR}/apps/common/utils/geoip/GeoLite2-City.mmdb" @@ -11,3 +13,10 @@ wget "https://download.jumpserver.org/files/GeoLite2-City.mmdb" -O "${ip_db_path echo "3. 安装依赖的插件" git lfs install + +if ! uname -a | grep 'ARM64' &> /dev/null;then + exit 0 +fi + +echo "4. For Apple processor" +LDFLAGS="-L$(brew --prefix freetds)/lib -L$(brew --prefix openssl@1.1)/lib" CFLAGS="-I$(brew --prefix freetds)/include" pip install $(grep 'pymssql' requirements.txt) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 7c3a4710b..20c33d2bc 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -4,7 +4,7 @@ asn1crypto==0.24.0 bcrypt==3.1.4 billiard==3.6.4.0 certifi==2018.1.18 -cffi==1.13.2 +cffi==1.15.1 chardet==3.0.4 configparser==3.5.0 decorator==4.1.2 @@ -23,7 +23,7 @@ paramiko==2.11.0 passlib==1.7.4 pyasn1==0.4.8 pycparser==2.21 -cryptography==36.0.1 +cryptography==37.0.4 pycryptodome==3.15.0 pycryptodomex==3.15.0 gmssl==3.2.1 @@ -130,7 +130,7 @@ mysqlclient==2.1.0 PyMySQL==1.0.2 oracledb==1.0.1 psycopg2-binary==2.9.1 -pymssql==2.1.5 +pymssql==2.2.5 django-mysql==3.9.0 django-redis==5.2.0 python-redis-lock==3.7.0 @@ -141,4 +141,3 @@ ForgeryPy3==0.3.1 django-debug-toolbar==3.5 Pympler==1.0.1 IPy==1.1 -IPy==1.1 From 001182378979ad94814e7ae09ae36e2d37a449ba Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Sep 2022 20:06:41 +0800 Subject: [PATCH 102/488] =?UTF-8?q?perf:=20=E5=8E=BB=E6=8E=89=20pymysql,?= =?UTF-8?q?=20mysqlclie=E5=B7=B2=E6=94=AF=E6=8C=81=20m1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/base.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index a65266a1f..d009238cd 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -1,12 +1,4 @@ import os -import platform - -if platform.system() == 'Darwin' and platform.machine() == 'arm64': - import pymysql - - pymysql.version_info = (1, 4, 2, "final", 0) - pymysql.install_as_MySQLdb() - from django.urls import reverse_lazy from .. import const From deba0c9057848d47c8903474ed58cfd66ea050fc Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Sep 2022 20:25:37 +0800 Subject: [PATCH 103/488] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20py3.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/db/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/common/db/models.py b/apps/common/db/models.py index f180b0da1..4969bd1a7 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -28,6 +28,7 @@ class IncludesTextChoicesMeta(type): assert includes attrs = _EnumDict() + attrs._cls_name = classname for k, v in classdict.items(): attrs[k] = v From 984b8dfb28a88b35a8bf715b2391f0830fa80c15 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 6 Sep 2022 13:27:47 +0800 Subject: [PATCH 104/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20m2m=20?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/host.py | 2 +- apps/audits/signal_handlers.py | 34 ++++++++++++++++----------- apps/common/drf/fields.py | 13 +++++++--- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py index 62638035d..35f1ca00a 100644 --- a/apps/assets/serializers/asset/host.py +++ b/apps/assets/serializers/asset/host.py @@ -29,7 +29,7 @@ class HostInfoSerializer(serializers.Serializer): class HostSerializer(AssetSerializer): - info = HostInfoSerializer(allow_null=True) + info = HostInfoSerializer(required=False) class Meta(AssetSerializer.Meta): model = Host diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index 206b5e5f8..f395965c3 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -64,39 +64,39 @@ AUTH_BACKEND_LABEL_MAPPING = AuthBackendLabelMapping() M2M_NEED_RECORD = { - User.groups.through._meta.object_name: ( + User.groups.through.__name__: ( _('User and Group'), _('{User} JOINED {UserGroup}'), _('{User} LEFT {UserGroup}') ), - Asset.nodes.through._meta.object_name: ( + Asset.nodes.through.__name__: ( _('Node and Asset'), _('{Node} ADD {Asset}'), _('{Node} REMOVE {Asset}') ), - AssetPermission.users.through._meta.object_name: ( + AssetPermission.users.through.__name__: ( _('User asset permissions'), _('{AssetPermission} ADD {User}'), _('{AssetPermission} REMOVE {User}'), ), - AssetPermission.user_groups.through._meta.object_name: ( + AssetPermission.user_groups.through.__name__: ( _('User group asset permissions'), _('{AssetPermission} ADD {UserGroup}'), _('{AssetPermission} REMOVE {UserGroup}'), ), - AssetPermission.assets.through._meta.object_name: ( + AssetPermission.assets.through.__name__: ( _('Asset permission'), _('{AssetPermission} ADD {Asset}'), _('{AssetPermission} REMOVE {Asset}'), ), - AssetPermission.nodes.through._meta.object_name: ( + AssetPermission.nodes.through.__name__: ( _('Node permission'), _('{AssetPermission} ADD {Node}'), _('{AssetPermission} REMOVE {Node}'), ), } -M2M_ACTION = { +M2M_ACTION_MAPER = { POST_ADD: OperateLog.ACTION_CREATE, POST_REMOVE: OperateLog.ACTION_DELETE, POST_CLEAR: OperateLog.ACTION_DELETE, @@ -104,34 +104,40 @@ M2M_ACTION = { @receiver(m2m_changed) -def on_m2m_changed(sender, action, instance, reverse, model, pk_set, **kwargs): - if action not in M2M_ACTION: +def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs): + if action not in M2M_ACTION_MAPER: return user = current_request.user if current_request else None if not user or not user.is_authenticated: return - sender_name = sender._meta.object_name + sender_name = sender.__name__ if sender_name in M2M_NEED_RECORD: org_id = current_org.id remote_addr = get_request_ip(current_request) user = str(user) resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[sender_name] - action = M2M_ACTION[action] + + action = M2M_ACTION_MAPER[action] if action == OperateLog.ACTION_CREATE: resource_tmpl = resource_tmpl_add elif action == OperateLog.ACTION_DELETE: resource_tmpl = resource_tmpl_remove + else: + return to_create = [] objs = model.objects.filter(pk__in=pk_set) - instance_name = instance._meta.object_name + if isinstance(instance, Asset): + instance_name = Asset.__name__ + else: + instance_name = instance.__class__.__name__ instance_value = str(instance) + model_name = model.__name__ - model_name = model._meta.object_name - + print("Instace name: ", instance_name, instance_value) for obj in objs: resource = resource_tmpl.format(**{ instance_name: instance_value, diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 31386b0a0..8dc88ae9e 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -4,6 +4,7 @@ import six from rest_framework.fields import ChoiceField from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ObjectDoesNotExist from common.utils import decrypt_password @@ -64,6 +65,12 @@ class LabeledChoiceField(ChoiceField): class ObjectRelatedField(serializers.RelatedField): + default_error_messages = { + 'required': _('This field is required.'), + 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'), + 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'), + } + def __init__(self, **kwargs): self.attrs = kwargs.pop('attrs', None) or ('id', 'name') self.many = kwargs.get('many', False) @@ -76,10 +83,10 @@ class ObjectRelatedField(serializers.RelatedField): return data def to_internal_value(self, data): - if isinstance(data, dict): - pk = data.get(self.attrs[0]) - else: + if not isinstance(data, dict): pk = data + else: + pk = data.get('id') or data.get('pk') or data.get(self.attrs[0]) queryset = self.get_queryset() try: if isinstance(data, bool): From 585ce6b46a0b31c8be174bca1ba202df7d77f8b5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 6 Sep 2022 19:57:03 +0800 Subject: [PATCH 105/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=A1=A8?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/const.py | 91 ----- .../0010_appaccount_historicalappaccount.py | 2 +- apps/applications/models/application.py | 320 ------------------ .../attrs/application_category/remote_app.py | 60 ---- .../attrs/application_type/oracle.py | 16 - .../migrations/0099_auto_20220711_1409.py | 8 - .../migrations/0106_auto_20220819_1523.py | 6 +- apps/assets/models/_authbook.py | 138 -------- apps/assets/models/_user.py | 23 +- apps/assets/models/account.py | 28 +- apps/assets/models/asset/common.py | 1 - apps/assets/models/base.py | 77 ++--- apps/assets/models/domain.py | 2 + apps/assets/models/protocol.py | 64 ---- apps/assets/serializers/account/account.py | 62 +++- .../serializers/account/account_history.py | 11 +- .../serializers/account/account_template.py | 48 +-- apps/assets/serializers/account/common.py | 19 +- apps/assets/serializers/asset/common.py | 3 +- apps/assets/serializers/base.py | 45 +-- apps/assets/serializers/domain.py | 8 +- .../models/ticket/apply_application.py | 13 +- 22 files changed, 159 insertions(+), 886 deletions(-) delete mode 100644 apps/applications/const.py delete mode 100644 apps/applications/models/application.py delete mode 100644 apps/applications/serializers/attrs/application_category/remote_app.py delete mode 100644 apps/applications/serializers/attrs/application_type/oracle.py delete mode 100644 apps/assets/models/_authbook.py diff --git a/apps/applications/const.py b/apps/applications/const.py deleted file mode 100644 index 4e0d2fe50..000000000 --- a/apps/applications/const.py +++ /dev/null @@ -1,91 +0,0 @@ -# coding: utf-8 -# -from django.db import models -from django.utils.translation import ugettext_lazy as _ - - -class AppCategory(models.TextChoices): - db = 'db', _('Database') - remote_app = 'remote_app', _('Remote app') - cloud = 'cloud', 'Cloud' - - @classmethod - def get_label(cls, category): - return dict(cls.choices).get(category, '') - - @classmethod - def is_xpack(cls, category): - return category in ['remote_app'] - - -class AppType(models.TextChoices): - # db category - mysql = 'mysql', 'MySQL' - mariadb = 'mariadb', 'MariaDB' - oracle = 'oracle', 'Oracle' - pgsql = 'postgresql', 'PostgreSQL' - sqlserver = 'sqlserver', 'SQLServer' - redis = 'redis', 'Redis' - mongodb = 'mongodb', 'MongoDB' - - # remote-app category - chrome = 'chrome', 'Chrome' - mysql_workbench = 'mysql_workbench', 'MySQL Workbench' - vmware_client = 'vmware_client', 'vSphere Client' - custom = 'custom', _('Custom') - - # cloud category - k8s = 'k8s', 'Kubernetes' - - @classmethod - def category_types_mapper(cls): - return { - AppCategory.db: [ - cls.mysql, cls.mariadb, cls.oracle, cls.pgsql, - cls.sqlserver, cls.redis, cls.mongodb - ], - AppCategory.remote_app: [ - cls.chrome, cls.mysql_workbench, - cls.vmware_client, cls.custom - ], - AppCategory.cloud: [cls.k8s] - } - - @classmethod - def type_category_mapper(cls): - mapper = {} - for category, tps in cls.category_types_mapper().items(): - for tp in tps: - mapper[tp] = category - return mapper - - @classmethod - def get_label(cls, tp): - return dict(cls.choices).get(tp, '') - - @classmethod - def db_types(cls): - return [tp.value for tp in cls.category_types_mapper()[AppCategory.db]] - - @classmethod - def remote_app_types(cls): - return [tp.value for tp in cls.category_types_mapper()[AppCategory.remote_app]] - - @classmethod - def cloud_types(cls): - return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]] - - @classmethod - def is_xpack(cls, tp): - tp_category_mapper = cls.type_category_mapper() - category = tp_category_mapper[tp] - - if AppCategory.is_xpack(category): - return True - return tp in ['oracle', 'postgresql', 'sqlserver'] - - -class OracleVersion(models.TextChoices): - version_11g = '11g', '11g' - version_12c = '12c', '12c' - version_other = 'other', _('Other') diff --git a/apps/applications/migrations/0010_appaccount_historicalappaccount.py b/apps/applications/migrations/0010_appaccount_historicalappaccount.py index f79fd2475..cd0bf88d1 100644 --- a/apps/applications/migrations/0010_appaccount_historicalappaccount.py +++ b/apps/applications/migrations/0010_appaccount_historicalappaccount.py @@ -71,6 +71,6 @@ class Migration(migrations.Migration): 'verbose_name': 'Account', 'unique_together': {('username', 'app', 'systemuser')}, }, - bases=(models.Model, assets.models.base.AuthMixin), + bases=(models.Model,), ), ] diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py deleted file mode 100644 index cc98abaf8..000000000 --- a/apps/applications/models/application.py +++ /dev/null @@ -1,320 +0,0 @@ -from collections import defaultdict -from urllib.parse import urlencode, parse_qsl - -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.conf import settings - -from orgs.mixins.models import OrgModelMixin -from common.mixins import CommonModelMixin -from common.tree import TreeNode -from common.utils import is_uuid -from assets.models import Asset, SystemUser -from ..const import OracleVersion - -from ..utils import KubernetesTree -from .. import const - - -class ApplicationTreeNodeMixin: - id: str - name: str - type: str - category: str - attrs: dict - - @staticmethod - def create_tree_id(pid, type, v): - i = dict(parse_qsl(pid)) - i[type] = v - tree_id = urlencode(i) - return tree_id - - @classmethod - def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None, - show_empty=True, show_count=True): - count = counts.get(c.value, 0) - if count == 0 and not show_empty: - return None - label = c.label - if count is not None and show_count: - label = '{} ({})'.format(label, count) - data = { - 'id': id_, - 'name': label, - 'title': label, - 'pId': pid, - 'isParent': bool(count), - 'open': opened, - 'iconSkin': '', - 'meta': { - 'type': tp, - 'data': { - 'name': c.name, - 'value': c.value - } - } - } - return TreeNode(**data) - - @classmethod - def create_root_tree_node(cls, queryset, show_count=True): - count = queryset.count() if show_count else None - root_id = 'applications' - root_name = _('Applications') - if count is not None and show_count: - root_name = '{} ({})'.format(root_name, count) - node = TreeNode(**{ - 'id': root_id, - 'name': root_name, - 'title': root_name, - 'pId': '', - 'isParent': True, - 'open': True, - 'iconSkin': '', - 'meta': { - 'type': 'applications_root', - } - }) - return node - - @classmethod - def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True): - nodes = [] - categories = const.AppType.category_types_mapper().keys() - for category in categories: - if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category): - continue - i = cls.create_tree_id(pid, 'category', category.value) - node = cls.create_choice_node( - category, i, pid=pid, tp='category', - counts=counts, opened=False, show_empty=show_empty, - show_count=show_count - ) - if not node: - continue - nodes.append(node) - return nodes - - @classmethod - def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True): - nodes = [] - temp_pid = pid - type_category_mapper = const.AppType.type_category_mapper() - types = const.AppType.type_category_mapper().keys() - - for tp in types: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp): - continue - category = type_category_mapper.get(tp) - pid = cls.create_tree_id(pid, 'category', category.value) - i = cls.create_tree_id(pid, 'type', tp.value) - node = cls.create_choice_node( - tp, i, pid, tp='type', counts=counts, opened=False, - show_empty=show_empty, show_count=show_count - ) - pid = temp_pid - if not node: - continue - nodes.append(node) - return nodes - - @staticmethod - def get_tree_node_counts(queryset): - counts = defaultdict(int) - values = queryset.values_list('type', 'category') - for i in values: - tp = i[0] - category = i[1] - counts[tp] += 1 - counts[category] += 1 - return counts - - @classmethod - def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True): - counts = cls.get_tree_node_counts(queryset) - tree_nodes = [] - - # 类别的节点 - tree_nodes += cls.create_category_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - - # 类型的节点 - tree_nodes += cls.create_types_tree_nodes( - pid, counts, show_empty=show_empty, - show_count=show_count - ) - return tree_nodes - - @classmethod - def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): - tree_nodes = [] - - # 根节点有可能是组织名称 - if root_node is None: - root_node = cls.create_root_tree_node(queryset, show_count=show_count) - tree_nodes.append(root_node) - - tree_nodes += cls.create_category_type_tree_nodes( - queryset, root_node.id, show_empty=show_empty, show_count=show_count - ) - - # 应用的节点 - for app in queryset: - if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type): - continue - node = app.as_tree_node(root_node.id) - tree_nodes.append(node) - return tree_nodes - - def create_app_tree_pid(self, root_id): - pid = self.create_tree_id(root_id, 'category', self.category) - pid = self.create_tree_id(pid, 'type', self.type) - return pid - - def as_tree_node(self, pid, k8s_as_tree=False): - if self.type == const.AppType.k8s and k8s_as_tree: - node = KubernetesTree(pid).as_tree_node(self) - else: - node = self._as_tree_node(pid) - return node - - def _attrs_to_tree(self): - if self.category == const.AppCategory.db: - return self.attrs - return {} - - def _as_tree_node(self, pid): - icon_skin_category_mapper = { - 'remote_app': 'chrome', - 'db': 'database', - 'cloud': 'cloud' - } - icon_skin = icon_skin_category_mapper.get(self.category, 'file') - pid = self.create_app_tree_pid(pid) - node = TreeNode(**{ - 'id': str(self.id), - 'name': self.name, - 'title': self.name, - 'pId': pid, - 'isParent': False, - 'open': False, - 'iconSkin': icon_skin, - 'meta': { - 'type': 'application', - 'data': { - 'category': self.category, - 'type': self.type, - 'attrs': self._attrs_to_tree() - } - } - }) - return node - - -class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin): - APP_TYPE = const.AppType - - name = models.CharField(max_length=128, verbose_name=_('Name')) - category = models.CharField( - max_length=16, choices=const.AppCategory.choices, verbose_name=_('Category') - ) - type = models.CharField( - max_length=16, choices=const.AppType.choices, verbose_name=_('Type') - ) - domain = models.ForeignKey( - 'assets.Domain', null=True, blank=True, related_name='applications', - on_delete=models.SET_NULL, verbose_name=_("Domain"), - ) - attrs = models.JSONField(default=dict, verbose_name=_('Attrs')) - comment = models.TextField( - max_length=128, default='', blank=True, verbose_name=_('Comment') - ) - - class Meta: - verbose_name = _('Application') - unique_together = [('org_id', 'name')] - ordering = ('name',) - permissions = [ - ('match_application', _('Can match application')), - ] - - def __str__(self): - category_display = self.get_category_display() - type_display = self.get_type_display() - return f'{self.name}({type_display})[{category_display}]' - - @property - def category_remote_app(self): - return self.category == const.AppCategory.remote_app.value - - @property - def category_cloud(self): - return self.category == const.AppCategory.cloud.value - - @property - def category_db(self): - return self.category == const.AppCategory.db.value - - def is_type(self, tp): - return self.type == tp - - def get_rdp_remote_app_setting(self): - from applications.serializers.attrs import get_serializer_class_by_application_type - if not self.category_remote_app: - raise ValueError(f"Not a remote app application: {self.name}") - serializer_class = get_serializer_class_by_application_type(self.type) - fields = serializer_class().get_fields() - - parameters = [self.type] - for field_name in list(fields.keys()): - if field_name in ['asset']: - continue - value = self.attrs.get(field_name) - if not value: - continue - if field_name == 'path': - value = '\"%s\"' % value - parameters.append(str(value)) - - parameters = ' '.join(parameters) - return { - 'program': '||jmservisor', - 'working_directory': '', - 'parameters': parameters - } - - def get_remote_app_asset(self, raise_exception=True): - asset_id = self.attrs.get('asset') - if is_uuid(asset_id): - return Asset.objects.filter(id=asset_id).first() - if raise_exception: - raise ValueError("Remote App not has asset attr") - - def get_target_ip(self): - target_ip = '' - if self.category_remote_app: - asset = self.get_remote_app_asset() - target_ip = asset.ip if asset else target_ip - elif self.category_cloud: - target_ip = self.attrs.get('cluster') - elif self.category_db: - target_ip = self.attrs.get('host') - return target_ip - - def get_target_protocol_for_oracle(self): - """ Oracle 类型需要单独处理,因为要携带版本号 """ - if not self.is_type(self.APP_TYPE.oracle): - return - version = self.attrs.get('version', OracleVersion.version_12c) - if version == OracleVersion.version_other: - return - return 'oracle_%s' % version - - -class ApplicationUser(SystemUser): - class Meta: - proxy = True - verbose_name = _('Application user') diff --git a/apps/applications/serializers/attrs/application_category/remote_app.py b/apps/applications/serializers/attrs/application_category/remote_app.py deleted file mode 100644 index 063af6daa..000000000 --- a/apps/applications/serializers/attrs/application_category/remote_app.py +++ /dev/null @@ -1,60 +0,0 @@ -# coding: utf-8 -# - -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ -from django.core.exceptions import ObjectDoesNotExist - -from common.utils import get_logger, is_uuid, get_object_or_none -from assets.models import Asset - -logger = get_logger(__file__) - -__all__ = ['RemoteAppSerializer'] - - -class ExistAssetPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): - - def to_internal_value(self, data): - instance = super().to_internal_value(data) - return str(instance.id) - - def to_representation(self, _id): - # _id 是 instance.id - if self.pk_field is not None: - return self.pk_field.to_representation(_id) - # 解决删除资产后,远程应用更新页面会显示资产ID的问题 - asset = get_object_or_none(Asset, id=_id) - if not asset: - return None - return _id - - -class RemoteAppSerializer(serializers.Serializer): - asset_info = serializers.SerializerMethodField(label=_('Asset Info')) - asset = ExistAssetPrimaryKeyRelatedField( - queryset=Asset.objects, required=True, label=_("Asset"), allow_null=True - ) - path = serializers.CharField( - max_length=128, label=_('Application path'), allow_null=True - ) - - def validate_asset(self, asset): - if not asset: - raise serializers.ValidationError(_('This field is required.')) - return asset - - @staticmethod - def get_asset_info(obj): - asset_id = obj.get('asset') - if not asset_id or not is_uuid(asset_id): - return {} - try: - asset = Asset.objects.get(id=str(asset_id)) - except ObjectDoesNotExist as e: - logger.error(e) - return {} - if not asset: - return {} - asset_info = {'id': str(asset.id), 'hostname': asset.hostname} - return asset_info diff --git a/apps/applications/serializers/attrs/application_type/oracle.py b/apps/applications/serializers/attrs/application_type/oracle.py deleted file mode 100644 index fdc8016d2..000000000 --- a/apps/applications/serializers/attrs/application_type/oracle.py +++ /dev/null @@ -1,16 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from ..application_category import DBSerializer -from applications.const import OracleVersion - -__all__ = ['OracleSerializer'] - - -class OracleSerializer(DBSerializer): - version = serializers.ChoiceField( - choices=OracleVersion.choices, default=OracleVersion.version_12c, - allow_null=True, label=_('Version'), - help_text=_('Magnus currently supports only 11g and 12c connections') - ) - port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True) diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index 674b1c447..52c8750dc 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -1,6 +1,5 @@ # Generated by Django 3.2.12 on 2022-07-11 08:59 -import assets.models.base import common.db.fields from django.conf import settings from django.db import migrations, models @@ -21,10 +20,7 @@ class Migration(migrations.Migration): name='HistoricalAccount', fields=[ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), - ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), - ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), @@ -55,10 +51,7 @@ class Migration(migrations.Migration): name='Account', fields=[ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), - ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), @@ -77,6 +70,5 @@ class Migration(migrations.Migration): 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], 'unique_together': {('username', 'asset')}, }, - bases=(models.Model, assets.models.base.AuthMixin, assets.models.protocol.ProtocolMixin), ), ] diff --git a/apps/assets/migrations/0106_auto_20220819_1523.py b/apps/assets/migrations/0106_auto_20220819_1523.py index 6ed0be81c..c77fdb09b 100644 --- a/apps/assets/migrations/0106_auto_20220819_1523.py +++ b/apps/assets/migrations/0106_auto_20220819_1523.py @@ -17,8 +17,6 @@ class Migration(migrations.Migration): name='AccountTemplate', fields=[ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), - ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), @@ -34,7 +32,7 @@ class Migration(migrations.Migration): ], options={ 'verbose_name': 'Account template', + 'unique_together': {('name', 'org_id')}, }, - bases=(models.Model, assets.models.base.AuthMixin), - ) + ), ] diff --git a/apps/assets/models/_authbook.py b/apps/assets/models/_authbook.py deleted file mode 100644 index a6b52927b..000000000 --- a/apps/assets/models/_authbook.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.db import models -from django.db.models import F -from django.utils.translation import ugettext_lazy as _ -from simple_history.models import HistoricalRecords - -from common.utils import lazyproperty, get_logger -from .base import BaseUser, AbsConnectivity - -logger = get_logger(__name__) - - -__all__ = ['AuthBook'] - - -class AuthBook(BaseUser, AbsConnectivity): - asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) - systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) - version = models.IntegerField(default=1, verbose_name=_('Version')) - history = HistoricalRecords() - - auth_attrs = ['username', 'password', 'private_key', 'public_key'] - - class Meta: - verbose_name = _('AuthBook') - unique_together = [('username', 'asset', 'systemuser')] - permissions = [ - ('test_authbook', _('Can test asset account connectivity')), - ('view_assetaccountsecret', _('Can view asset account secret')), - ('change_assetaccountsecret', _('Can change asset account secret')), - ('view_assethistoryaccount', _('Can view asset history account')), - ('view_assethistoryaccountsecret', _('Can view asset history account secret')), - ] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.auth_snapshot = {} - - def get_or_systemuser_attr(self, attr): - val = getattr(self, attr, None) - if val: - return val - if self.systemuser: - return getattr(self.systemuser, attr, '') - return '' - - def load_auth(self): - for attr in self.auth_attrs: - value = self.get_or_systemuser_attr(attr) - self.auth_snapshot[attr] = [getattr(self, attr), value] - setattr(self, attr, value) - - def unload_auth(self): - if not self.systemuser: - return - - for attr, values in self.auth_snapshot.items(): - origin_value, loaded_value = values - current_value = getattr(self, attr, '') - if current_value == loaded_value: - setattr(self, attr, origin_value) - - def save(self, *args, **kwargs): - self.unload_auth() - instance = super().save(*args, **kwargs) - self.load_auth() - return instance - - @property - def username_display(self): - return self.get_or_systemuser_attr('username') or '*' - - @lazyproperty - def systemuser_display(self): - if not self.systemuser: - return '' - return str(self.systemuser) - - @property - def smart_name(self): - username = self.username_display - - if self.asset: - asset = str(self.asset) - else: - asset = '*' - return '{}@{}'.format(username, asset) - - def sync_to_system_user_account(self): - if self.systemuser: - return - matched = AuthBook.objects.filter( - asset=self.asset, systemuser__username=self.username - ) - if not matched: - return - - for i in matched: - i.password = self.password - i.private_key = self.private_key - i.public_key = self.public_key - i.comment = 'Update triggered by account {}'.format(self.id) - - # 不触发post_save信号 - self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key']) - - def remove_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser: - return - self.asset.admin_user = None - self.asset.save() - logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser)) - - def update_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser: - return - self.asset.admin_user = self.systemuser - self.asset.save() - logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) - - @classmethod - def get_queryset(cls): - queryset = cls.objects.all() \ - .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) \ - .annotate(platform=F('asset__platform__name')) \ - .annotate(protocols=F('asset__protocols')) - return queryset - - def __str__(self): - return self.smart_name - diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index ab593b016..d7809a2b3 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -3,20 +3,21 @@ # import logging +import uuid +from common.db import fields from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator -from .base import BaseAccount -from .protocol import ProtocolMixin +from orgs.mixins.models import OrgModelMixin __all__ = ['SystemUser'] logger = logging.getLogger(__name__) -class SystemUser(BaseAccount, ProtocolMixin): +class SystemUser(OrgModelMixin): LOGIN_AUTO = 'auto' LOGIN_MANUAL = 'manual' LOGIN_MODE_CHOICES = ( @@ -28,6 +29,19 @@ class SystemUser(BaseAccount, ProtocolMixin): common = 'common', _('Common user') admin = 'admin', _('Admin user') + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) + password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) + public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) + token = models.TextField(default='', verbose_name=_('Token')) + + comment = models.TextField(blank=True, verbose_name=_('Comment')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) + date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) + created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) + username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user")) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) @@ -37,7 +51,6 @@ class SystemUser(BaseAccount, ProtocolMixin): shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) - token = models.TextField(default='', verbose_name=_('Token')) home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True) ad_domain = models.CharField(default='', max_length=256) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index fc76e86ee..6f122cfe2 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -2,13 +2,14 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords -from .base import BaseAccount, AbsConnectivity +from common.utils import lazyproperty + +from .base import BaseAccount __all__ = ['Account', 'AccountTemplate'] -class Account(BaseAccount, AbsConnectivity): - privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) +class Account(BaseAccount): asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) history = HistoricalRecords() @@ -23,15 +24,28 @@ class Account(BaseAccount, AbsConnectivity): ('view_historyaccountsecret', _('Can view asset history account secret')), ] + @property + def name(self): + return "{}({})_{}".format(self.asset_name, self.ip, self.username) + + @lazyproperty + def ip(self): + return self.asset.ip + + @lazyproperty + def asset_name(self): + return self.asset.name + def __str__(self): return '{}@{}'.format(self.username, self.asset.name) -class AccountTemplate(BaseAccount, AbsConnectivity): - privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) - +class AccountTemplate(BaseAccount): class Meta: verbose_name = _('Account template') + unique_together = ( + ('name', 'org_id'), + ) def __str__(self): - return '{}@{}'.format(self.username, self.name) + return self.username diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 0a995d8cc..fad119a03 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -78,7 +78,6 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) - labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) info = models.JSONField(verbose_name='Info', default=dict, blank=True) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 9cdac9e1f..ce7b99da3 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -13,11 +13,10 @@ from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django.db.models import QuerySet -from common.utils import random_string from common.utils import ( - ssh_key_string_to_obj, ssh_key_gen, get_logger + ssh_key_string_to_obj, ssh_key_gen, get_logger, + random_string, ssh_pubkey_gen, ) -from common.utils.encode import ssh_pubkey_gen from common.db import fields from orgs.mixins.models import OrgModelMixin @@ -55,11 +54,29 @@ class AbsConnectivity(models.Model): abstract = True -class AuthMixin: - private_key = '' - password = '' - public_key = '' - username = '' +class BaseAccount(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_("Name")) + username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) + password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) + public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) + token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) + privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) + comment = models.TextField(blank=True, verbose_name=_('Comment')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) + date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) + created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) + + ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT" + ASSET_USER_CACHE_TIME = 600 + + APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT" + APP_USER_CACHE_TIME = 600 + + def expire_assets_amount(self): + cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) + cache.delete(cache_key) @property def ssh_key_fingerprint(self): @@ -115,18 +132,11 @@ class AuthMixin: pass return None - def set_auth(self, password=None, private_key=None, public_key=None): + def set_auth(self, **kwargs): update_fields = [] - if password: - self.password = password - update_fields.append('password') - if private_key: - self.private_key = private_key - update_fields.append('private_key') - if public_key: - self.public_key = public_key - update_fields.append('public_key') - + for k, v in kwargs.items(): + setattr(self, k, v) + update_fields.append(k) if update_fields: self.save(update_fields=update_fields) @@ -141,6 +151,7 @@ class AuthMixin: self.password = '' self.private_key = '' self.public_key = '' + self.token = '' self.save() @staticmethod @@ -168,33 +179,6 @@ class AuthMixin: public_key=_public_key ) - -class BaseAccount(OrgModelMixin, AuthMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_('Name')) - username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) - password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) - private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) - public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) - token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) - comment = models.TextField(blank=True, verbose_name=_('Comment')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) - date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) - created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - - ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT" - ASSET_USER_CACHE_TIME = 600 - - APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT" - APP_USER_CACHE_TIME = 600 - - def get_username(self): - return self.username - - def expire_assets_amount(self): - cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) - cache.delete(cache_key) - def _to_secret_json(self): """Push system user use it""" return { @@ -203,6 +187,7 @@ class BaseAccount(OrgModelMixin, AuthMixin): 'password': self.password, 'public_key': self.public_key, 'private_key': self.private_key_file, + 'token': self.token } class Meta: diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 72a5c748c..12da79df1 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -57,6 +57,7 @@ class Gateway(BaseAccount): class Protocol(models.TextChoices): ssh = 'ssh', 'SSH' + name = models.CharField(max_length=128, verbose_name='Name') ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) port = models.IntegerField(default=22, verbose_name=_('Port')) protocol = models.CharField(choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")) @@ -64,6 +65,7 @@ class Gateway(BaseAccount): comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) token = None + privileged = None def __str__(self): return self.name diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py index 22d330339..d42ee2754 100644 --- a/apps/assets/models/protocol.py +++ b/apps/assets/models/protocol.py @@ -5,67 +5,3 @@ from django.utils.translation import gettext_lazy as _ class Protocol(models.Model): name = models.CharField(max_length=32, verbose_name=_("Name")) port = models.IntegerField(verbose_name=_("Port")) - - -class ProtocolMixin: - protocol: str - - class Protocol(models.TextChoices): - ssh = 'ssh', 'SSH' - rdp = 'rdp', 'RDP' - telnet = 'telnet', 'Telnet' - vnc = 'vnc', 'VNC' - mysql = 'mysql', 'MySQL' - oracle = 'oracle', 'Oracle' - mariadb = 'mariadb', 'MariaDB' - postgresql = 'postgresql', 'PostgreSQL' - sqlserver = 'sqlserver', 'SQLServer' - redis = 'redis', 'Redis' - mongodb = 'mongodb', 'MongoDB' - k8s = 'k8s', 'K8S' - - SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp] - - ASSET_CATEGORY_PROTOCOLS = [ - Protocol.ssh, Protocol.rdp, Protocol.telnet, Protocol.vnc - ] - APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [ - Protocol.rdp - ] - APPLICATION_CATEGORY_DB_PROTOCOLS = [ - Protocol.mysql, Protocol.mariadb, Protocol.oracle, - Protocol.postgresql, Protocol.sqlserver, - Protocol.redis, Protocol.mongodb - ] - APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [ - Protocol.k8s - ] - APPLICATION_CATEGORY_PROTOCOLS = [ - *APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS, - *APPLICATION_CATEGORY_DB_PROTOCOLS, - *APPLICATION_CATEGORY_CLOUD_PROTOCOLS - ] - - @property - def is_protocol_support_push(self): - return self.protocol in self.SUPPORT_PUSH_PROTOCOLS - - @classmethod - def get_protocol_by_application_type(cls, app_type): - from applications.const import AppType - if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS: - protocol = app_type - elif app_type in AppType.remote_app_types(): - protocol = cls.Protocol.rdp - else: - protocol = None - return protocol - - @property - def can_perm_to_asset(self): - return self.protocol in self.ASSET_CATEGORY_PROTOCOLS - - @property - def is_asset_protocol(self): - return self.protocol in self.ASSET_CATEGORY_PROTOCOLS - diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 0e73f6a1b..1c60e5d98 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -4,29 +4,58 @@ from rest_framework import serializers from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin -from assets.models import Account -from assets.serializers.base import AuthSerializerMixin -from .account_template import AccountTemplateSerializerMixin -from .common import BaseAccountSerializer +from assets.models import Account, AccountTemplate +from assets.serializers.base import AuthValidateMixin +from .common import AccountFieldsSerializerMixin -class AccountSerializer( - AccountTemplateSerializerMixin, - AuthSerializerMixin, - BulkOrgResourceModelSerializer -): +class AccountSerializerCreateMixin(serializers.ModelSerializer): + template = serializers.UUIDField( + required=False, allow_null=True, write_only=True, + label=_('Account template') + ) + push_to_asset = serializers.BooleanField(default=False, label=_("Push to asset"), write_only=True) + + @staticmethod + def validate_template(value): + AccountTemplate.objects.get_or_create() + model = AccountTemplate + try: + return model.objects.get(id=value) + except AccountTemplate.DoesNotExist: + raise serializers.ValidationError(_('Account template not found')) + + @staticmethod + def replace_attrs(account_template: AccountTemplate, attrs: dict): + exclude_fields = [ + '_state', 'org_id', 'date_verified', 'id', + 'date_created', 'date_updated', 'created_by' + ] + template_attrs = {k: v for k, v in account_template.__dict__.items() if k not in exclude_fields} + for k, v in template_attrs.items(): + attrs.setdefault(k, v) + + def validate(self, attrs): + account_template = attrs.pop('template', None) + if account_template: + self.replace_attrs(account_template, attrs) + push_to_asset = attrs.pop('push_to_asset', False) + return super().validate(attrs) + + +class AccountSerializer(AuthValidateMixin, + AccountSerializerCreateMixin, + AccountFieldsSerializerMixin, + BulkOrgResourceModelSerializer): + name = serializers.CharField(max_length=128, read_only=True, label=_("Name")) ip = serializers.ReadOnlyField(label=_("IP")) asset_name = serializers.ReadOnlyField(label=_("Asset")) platform = serializers.ReadOnlyField(label=_("Platform")) - class Meta(BaseAccountSerializer.Meta): + class Meta(AccountFieldsSerializerMixin.Meta): model = Account - fields = BaseAccountSerializer.Meta.fields + ['account_template', ] - - def validate(self, attrs): - attrs = self._validate_gen_key(attrs) - attrs = super()._validate(attrs) - return attrs + fields = AccountFieldsSerializerMixin.Meta.fields \ + + ['template', 'push_to_asset'] @classmethod def setup_eager_loading(cls, queryset): @@ -47,7 +76,6 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): 'password': {'write_only': False}, 'private_key': {'write_only': False}, 'public_key': {'write_only': False}, - 'systemuser_display': {'label': _('System user display')} } diff --git a/apps/assets/serializers/account/account_history.py b/apps/assets/serializers/account/account_history.py index 7fadc1a47..0a6c2042f 100644 --- a/apps/assets/serializers/account/account_history.py +++ b/apps/assets/serializers/account/account_history.py @@ -1,18 +1,17 @@ from assets.models import Account from common.drf.serializers import SecretReadableMixin -from .common import BaseAccountSerializer +from .common import AccountFieldsSerializerMixin from .account import AccountSerializer, AccountSecretSerializer class AccountHistorySerializer(AccountSerializer): - class Meta: model = Account.history.model - fields = BaseAccountSerializer.Meta.fields_mini + \ - BaseAccountSerializer.Meta.fields_write_only + \ - BaseAccountSerializer.Meta.fields_fk + \ - ['history_id', 'date_created', 'date_updated'] + fields = AccountFieldsSerializerMixin.Meta.fields_mini + \ + AccountFieldsSerializerMixin.Meta.fields_write_only + \ + AccountFieldsSerializerMixin.Meta.fields_fk + \ + ['history_id', 'date_created', 'date_updated'] read_only_fields = fields ref_name = 'AccountHistorySerializer' diff --git a/apps/assets/serializers/account/account_template.py b/apps/assets/serializers/account/account_template.py index 068239e74..5fe53c26c 100644 --- a/apps/assets/serializers/account/account_template.py +++ b/apps/assets/serializers/account/account_template.py @@ -3,16 +3,16 @@ from rest_framework import serializers from assets.models import AccountTemplate from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from assets.serializers.base import AuthSerializerMixin -from .common import BaseAccountSerializer +from assets.serializers.base import AuthValidateMixin +from .common import AccountFieldsSerializerMixin -class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +class AccountTemplateSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class Meta: model = AccountTemplate - fields_mini = ['id', 'privileged', 'username', 'name'] - fields_write_only = BaseAccountSerializer.Meta.fields_write_only - fields_other = BaseAccountSerializer.Meta.fields_other + fields_mini = ['id', 'privileged', 'username'] + fields_write_only = AccountFieldsSerializerMixin.Meta.fields_write_only + fields_other = AccountFieldsSerializerMixin.Meta.fields_other fields = fields_mini + fields_write_only + fields_other extra_kwargs = { 'username': {'required': True}, @@ -35,39 +35,3 @@ class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSeriali return raise serializers.ValidationError(required_field_dict) - -class AccountTemplateSerializerMixin(serializers.ModelSerializer): - account_template = serializers.UUIDField( - required=False, allow_null=True, write_only=True, - label=_('Account template') - ) - - @staticmethod - def validate_account_template(value): - AccountTemplate.objects.get_or_create() - model = AccountTemplate - try: - return model.objects.get(id=value) - except AccountTemplate.DoesNotExist: - raise serializers.ValidationError(_('Account template not found')) - - @staticmethod - def replace_attrs(account_template: AccountTemplate, attrs: dict): - exclude_fields = [ - '_state', 'org_id', 'date_verified', 'id', 'date_created', 'date_updated', 'created_by' - ] - template_attrs = {k: v for k, v in account_template.__dict__.items() if k not in exclude_fields} - for k, v in template_attrs.items(): - attrs.setdefault(k, v) - - def _validate(self, attrs): - account_template = attrs.pop('account_template', None) - if account_template: - self.replace_attrs(account_template, attrs) - else: - AccountTemplateSerializer.validate_required(attrs) - return attrs - - - - diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py index 3ad033e0e..1627bf8c3 100644 --- a/apps/assets/serializers/account/common.py +++ b/apps/assets/serializers/account/common.py @@ -2,25 +2,26 @@ # from rest_framework import serializers -__all__ = [ - 'BaseAccountSerializer', -] +__all__ = ['AccountFieldsSerializerMixin'] -class BaseAccountSerializer(serializers.ModelSerializer): +class AccountFieldsSerializerMixin(serializers.ModelSerializer): class Meta: fields_mini = [ - 'id', 'privileged', 'username', 'ip', 'asset_name', - 'platform', 'version' + 'id', 'name', 'username', 'privileged', 'ip', + 'asset_name', 'platform', 'version' ] fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] - fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment'] + fields_other = ['date_created', 'date_updated', 'comment'] fields_small = fields_mini + fields_write_only + fields_other fields_fk = ['asset'] fields = fields_small + fields_fk - ref_name = 'AssetAccountSerializer' extra_kwargs = { - 'username': {'required': True}, 'private_key': {'write_only': True}, 'public_key': {'write_only': True}, } + + def validate_name(self, value): + if not value: + return self.initial_data.get('username') + return '' diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 439d96483..8d41eee84 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -67,8 +67,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): 'domain', 'platform', 'platform', ] fields_m2m = [ - 'nodes', 'labels', 'accounts', 'protocols', - 'nodes_display', + 'nodes', 'labels', 'accounts', 'protocols', 'nodes_display', ] read_only_fields = [ 'category', 'type', 'connectivity', 'date_verified', diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 9e4192fb3..e421b8f43 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -11,44 +11,20 @@ from assets.models import Type from .utils import validate_password_for_ansible -class AuthSerializer(serializers.ModelSerializer): - password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password')) - private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384, - label=_('Private key')) - - def gen_keys(self, private_key=None, password=None): - if private_key is None: - return None, None - public_key = ssh_pubkey_gen(private_key=private_key, password=password) - return private_key, public_key - - def save(self, **kwargs): - password = self.validated_data.pop('password', None) or None - private_key = self.validated_data.pop('private_key', None) or None - self.instance = super().save(**kwargs) - if password or private_key: - private_key, public_key = self.gen_keys(private_key, password) - self.instance.set_auth(password=password, private_key=private_key, - public_key=public_key) - return self.instance - - -class AuthSerializerMixin(serializers.ModelSerializer): +class AuthValidateMixin(serializers.Serializer): password = EncryptedField( - label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, - validators=[validate_password_for_ansible] + label=_('Password'), required=False, allow_blank=True, allow_null=True, + max_length=1024, validators=[validate_password_for_ansible] ) private_key = EncryptedField( - label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, max_length=16384 + label=_('SSH private key'), required=False, allow_blank=True, + allow_null=True, max_length=16384 ) passphrase = serializers.CharField( allow_blank=True, allow_null=True, required=False, max_length=512, write_only=True, label=_('Key password') ) - def validate_password(self, password): - return password - def validate_private_key(self, private_key): if not private_key: return @@ -64,9 +40,6 @@ class AuthSerializerMixin(serializers.ModelSerializer): private_key = string_io.getvalue() return private_key - def validate_public_key(self, public_key): - return public_key - @staticmethod def clean_auth_fields(validated_data): for field in ('password', 'private_key', 'public_key'): @@ -87,6 +60,10 @@ class AuthSerializerMixin(serializers.ModelSerializer): attrs['public_key'] = public_key return attrs + def validate(self, attrs): + attrs = self._validate_gen_key(attrs) + return super().validate(attrs) + def create(self, validated_data): self.clean_auth_fields(validated_data) return super().create(validated_data) @@ -97,9 +74,9 @@ class AuthSerializerMixin(serializers.ModelSerializer): class TypesField(serializers.MultipleChoiceField): - def __init__(self, *args, **kwargs): + def __init__(self, **kwargs): kwargs['choices'] = Type.CHOICES - super().__init__(*args, **kwargs) + super().__init__(**kwargs) def to_representation(self, value): return Type.value_to_choices(value) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index d67b624e4..82fd9433f 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -7,7 +7,7 @@ from common.validators import alphanumeric from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin from ..models import Domain, Gateway -from .base import AuthSerializerMixin +from .base import AuthValidateMixin class DomainSerializer(BulkOrgResourceModelSerializer): @@ -38,17 +38,17 @@ class DomainSerializer(BulkOrgResourceModelSerializer): return obj.gateway_set.all().count() -class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +class GatewaySerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) class Meta: model = Gateway - fields_mini = ['id', 'name'] + fields_mini = ['id', 'username'] fields_write_only = [ 'password', 'private_key', 'public_key', 'passphrase' ] fields_small = fields_mini + fields_write_only + [ - 'username', 'ip', 'port', 'protocol', + 'ip', 'port', 'protocol', 'is_active', 'is_connective', 'date_created', 'date_updated', 'created_by', 'comment', diff --git a/apps/tickets/models/ticket/apply_application.py b/apps/tickets/models/ticket/apply_application.py index 378047db2..115d45a9f 100644 --- a/apps/tickets/models/ticket/apply_application.py +++ b/apps/tickets/models/ticket/apply_application.py @@ -2,7 +2,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from perms.models import Action -from applications.const import AppCategory, AppType from .general import Ticket __all__ = ['ApplyApplicationTicket'] @@ -12,10 +11,10 @@ class ApplyApplicationTicket(Ticket): apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name')) # 申请信息 apply_category = models.CharField( - max_length=16, choices=AppCategory.choices, verbose_name=_('Category') + max_length=16, verbose_name=_('Category') ) apply_type = models.CharField( - max_length=16, choices=AppType.choices, verbose_name=_('Type') + max_length=16, verbose_name=_('Type') ) apply_applications = models.ManyToManyField( 'applications.Application', verbose_name=_('Apply applications'), @@ -29,14 +28,6 @@ class ApplyApplicationTicket(Ticket): apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) - @property - def apply_category_display(self): - return AppCategory.get_label(self.apply_category) - - @property - def apply_type_display(self): - return AppType.get_label(self.apply_type) - @property def apply_actions_display(self): return Action.value_to_choices_display(self.apply_actions) From 56abf0da23644329d3abe0b46154d739191e87f7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Sep 2022 17:12:53 +0800 Subject: [PATCH 106/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=8D=8F=E8=AE=AE=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 33 ++++++++++++++++++++++--- apps/assets/models/asset/common.py | 2 +- apps/assets/models/protocol.py | 4 +++ apps/assets/serializers/asset/common.py | 23 ++++++----------- apps/assets/serializers/platform.py | 7 +++++- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/apps/assets/const.py b/apps/assets/const.py index 6a4fb8cf4..cbb3abd89 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ + from common.db.models import IncludesTextChoicesMeta, ChoicesMixin from common.tree import TreeNode @@ -44,21 +45,35 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): 'change_password_enabled': True, 'create_account_enabled': True, 'gather_accounts_enabled': True, - '_protocols': ['ssh', 'telnet'] + '_protocols': ['ssh', 'sftp'] }, cls.NETWORKING: { 'domain_enabled': True, + 'su_enabled': False, + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, '_protocols': ['ssh', 'telnet'] }, cls.DATABASE: { 'domain_enabled': True, + 'su_enabled': False, + 'gather_facts_enabled': True, + 'verify_account_enabled': True, + 'change_password_enabled': True, + 'create_account_enabled': True, + 'gather_accounts_enabled': True, }, cls.WEB: { 'domain_enabled': False, - '_protocols': [] + 'su_enabled': False, + '_protocols': ['http', 'https'] }, cls.CLOUD: { 'domain_enabled': False, + 'su_enabled': False, '_protocols': [] } } @@ -77,7 +92,7 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): def platform_constraints(cls): return { cls.LINUX: { - '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] + '_protocols': ['ssh', 'sftp', 'rdp', 'vnc', 'telnet'] }, cls.WINDOWS: { '_protocols': ['ssh', 'rdp', 'vnc'], @@ -110,7 +125,7 @@ class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): meta = {} for name, label in cls.choices: meta[name] = { - 'protocols': [name] + '_protocols': [name] } return meta @@ -122,6 +137,14 @@ class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices): class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): K8S = 'k8s', 'Kubernetes' + @classmethod + def platform_constraints(cls): + return { + cls.K8S: { + '_protocols': ['k8s'] + } + } + class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): choices: list @@ -209,6 +232,7 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): class Protocol(ChoicesMixin, models.TextChoices): ssh = 'ssh', 'SSH' + sftp = 'sftp', 'SFTP' rdp = 'rdp', 'RDP' telnet = 'telnet', 'Telnet' vnc = 'vnc', 'VNC' @@ -240,6 +264,7 @@ class Protocol(ChoicesMixin, models.TextChoices): def default_ports(cls): return { cls.ssh: 22, + cls.sftp: 22, cls.rdp: 3389, cls.vnc: 5900, cls.telnet: 21, diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index fad119a03..a9396e17c 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -68,7 +68,7 @@ class NodesRelationMixin: class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_('Hostname')) + name = models.CharField(max_length=128, verbose_name=_('Name')) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocols"), blank=True) platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py index d42ee2754..a2aa2283f 100644 --- a/apps/assets/models/protocol.py +++ b/apps/assets/models/protocol.py @@ -5,3 +5,7 @@ from django.utils.translation import gettext_lazy as _ class Protocol(models.Model): name = models.CharField(max_length=32, verbose_name=_("Name")) port = models.IntegerField(verbose_name=_("Port")) + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None): + pass diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 8d41eee84..61e32a7a8 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -22,11 +22,18 @@ class AssetProtocolsSerializer(serializers.ModelSerializer): model = Protocol fields = ['id', 'name', 'port'] + def create(self, validated_data): + instance = Protocol.objects.filter(**validated_data).first() + if instance: + return instance + instance = Protocol.objects.create(**validated_data) + return instance + class AssetLabelSerializer(serializers.ModelSerializer): class Meta: model = Label - fields = ['id', 'name', 'value'] + fields = ['name', 'value'] extra_kwargs = { 'name': {'required': False}, 'value': {'required': False} @@ -51,7 +58,6 @@ class AssetSerializer(JMSWritableNestedModelSerializer): labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) accounts = AccountSerializer(many=True, required=False, label=_('Accounts')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) - """ 资产的数据结构 """ @@ -109,23 +115,10 @@ class AssetSerializer(JMSWritableNestedModelSerializer): nodes_to_set.append(node) instance.nodes.set(nodes_to_set) - @staticmethod - def add_accounts(instance, accounts_data): - for data in accounts_data: - data['asset'] = instance.id - serializer = AccountSerializer(data=accounts_data, many=True) - try: - serializer.is_valid(raise_exception=True) - except Exception as e: - raise serializers.ValidationError({'accounts': e}) - serializer.save() - @atomic def create(self, validated_data): nodes_display = validated_data.pop('nodes_display', '') instance = super().create(validated_data) - if self.accounts_data: - self.add_accounts(instance, self.accounts_data) self.perform_nodes_display_create(instance, nodes_display) return instance diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index cf888ba1a..01f2e8956 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -17,12 +17,17 @@ class ProtocolSettingSerializer(serializers.Serializer): ('tls', 'TLS'), ('nla', 'NLA'), ] + # Common + required = serializers.BooleanField(required=True, initial=False, label=_("Required")) + # RDP console = serializers.BooleanField(required=False) security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any', required=False) + # SFTP + sftp_home = serializers.CharField(default='/tmp', required=False) class PlatformProtocolsSerializer(serializers.ModelSerializer): - setting = ProtocolSettingSerializer(required=False) + setting = ProtocolSettingSerializer(required=False, allow_null=True) class Meta: model = PlatformProtocol From a27aeca2fd2203ef382d8ed570932cbce9706191 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 7 Sep 2022 17:35:23 +0800 Subject: [PATCH 107/488] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E7=9B=B8=E5=85=B3Model,Serializer,API=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../serializers/connection_token.py | 2 +- apps/jumpserver/settings/_xpack.py | 3 +- apps/perms/exceptions.py | 14 ----- apps/perms/filters.py | 11 +--- apps/perms/models/asset_permission.py | 5 ++ apps/perms/serializers/__init__.py | 5 +- apps/perms/serializers/asset/__init__.py | 3 - apps/perms/serializers/base.py | 56 ----------------- .../serializers/{asset => }/permission.py | 63 +++++++++++++++---- .../{asset => }/permission_relation.py | 0 .../{asset => }/user_permission.py | 2 +- .../serializers/ticket/apply_application.py | 2 +- .../tickets/serializers/ticket/apply_asset.py | 2 +- 13 files changed, 67 insertions(+), 101 deletions(-) delete mode 100644 apps/perms/exceptions.py delete mode 100644 apps/perms/serializers/asset/__init__.py delete mode 100644 apps/perms/serializers/base.py rename apps/perms/serializers/{asset => }/permission.py (65%) rename apps/perms/serializers/{asset => }/permission_relation.py (100%) rename apps/perms/serializers/{asset => }/user_permission.py (95%) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 4a5b91e5b..0905d5c60 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -7,7 +7,7 @@ from common.utils import pretty_string from common.utils.random import random_string from assets.models import Asset, Gateway, Domain, CommandFilterRule from users.models import User -from perms.serializers.base import ActionsField +from perms.serializers.permission import ActionsField __all__ = [ diff --git a/apps/jumpserver/settings/_xpack.py b/apps/jumpserver/settings/_xpack.py index 9f4319a35..2650e30b9 100644 --- a/apps/jumpserver/settings/_xpack.py +++ b/apps/jumpserver/settings/_xpack.py @@ -6,7 +6,8 @@ from .. import const from .base import INSTALLED_APPS, TEMPLATES XPACK_DIR = os.path.join(const.BASE_DIR, 'xpack') -XPACK_ENABLED = os.path.isdir(XPACK_DIR) +# XPACK_ENABLED = os.path.isdir(XPACK_DIR) +XPACK_ENABLED = False XPACK_TEMPLATES_DIR = [] XPACK_CONTEXT_PROCESSOR = [] diff --git a/apps/perms/exceptions.py b/apps/perms/exceptions.py deleted file mode 100644 index 684a5da88..000000000 --- a/apps/perms/exceptions.py +++ /dev/null @@ -1,14 +0,0 @@ -from rest_framework import status -from django.utils.translation import gettext_lazy as _ - -from common.exceptions import JMSException - - -class AdminIsModifyingPerm(JMSException): - status_code = status.HTTP_409_CONFLICT - default_detail = _('The administrator is modifying permissions. Please wait') - - -class CanNotRemoveAssetPermNow(JMSException): - status_code = status.HTTP_409_CONFLICT - default_detail = _('The authorization cannot be revoked for the time being') diff --git a/apps/perms/filters.py b/apps/perms/filters.py index c890dbcba..ee3c03e91 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -183,22 +183,15 @@ class AssetPermissionFilter(PermissionBaseFilter): if is_effective: have_user_q = Q(users__isnull=False) | Q(user_groups__isnull=False) have_asset_q = Q(assets__isnull=False) | Q(nodes__isnull=False) - have_system_user_q = Q(system_users__isnull=False) have_action_q = Q(actions__gt=0) - queryset = queryset.filter( - have_user_q & have_asset_q & have_system_user_q & have_action_q - ) + queryset = queryset.filter(have_user_q & have_asset_q & have_action_q) queryset &= AssetPermission.objects.valid() else: not_have_user_q = Q(users__isnull=True) & Q(user_groups__isnull=True) not_have_asset_q = Q(assets__isnull=True) & Q(nodes__isnull=True) - not_have_system_user_q = Q(system_users__isnull=True) not_have_action_q = Q(actions=0) - queryset = queryset.filter( - not_have_user_q | not_have_asset_q | not_have_system_user_q | - not_have_action_q - ) + queryset = queryset.filter(not_have_user_q | not_have_asset_q | not_have_action_q) queryset |= AssetPermission.objects.invalid() return queryset diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index bf70e9b6f..04b90c0ca 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -89,6 +89,11 @@ class AssetPermission(OrgModelMixin): user_groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User group"), related_name='%(class)ss') assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) + # 只保存 @ALL (@INPUT @USER 默认包含,将来在全局设置中进行控制) + # 特殊的账号描述 + # ['@ALL',] + # 指定账号授权 + # ['web', 'root',] accounts = models.JSONField(default=list, verbose_name=_("Accounts")) actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions")) is_active = models.BooleanField(default=True, verbose_name=_('Active')) diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py index 39f7912a3..a28be49ec 100644 --- a/apps/perms/serializers/__init__.py +++ b/apps/perms/serializers/__init__.py @@ -1,4 +1,5 @@ # coding: utf-8 # -from .base import * -from .asset import * +from .permission import * +from .permission_relation import * +from .user_permission import * diff --git a/apps/perms/serializers/asset/__init__.py b/apps/perms/serializers/asset/__init__.py deleted file mode 100644 index 5fb99849f..000000000 --- a/apps/perms/serializers/asset/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .permission import * -from .permission_relation import * -from .user_permission import * diff --git a/apps/perms/serializers/base.py b/apps/perms/serializers/base.py deleted file mode 100644 index cbed4a2f8..000000000 --- a/apps/perms/serializers/base.py +++ /dev/null @@ -1,56 +0,0 @@ -from rest_framework import serializers -from perms.models import Action -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from rest_framework.fields import empty - -__all__ = ['ActionsDisplayField', 'ActionsField', 'BasePermissionSerializer'] - - -class ActionsField(serializers.MultipleChoiceField): - def __init__(self, *args, **kwargs): - kwargs['choices'] = Action.CHOICES - super().__init__(*args, **kwargs) - - def run_validation(self, data=empty): - data = super(ActionsField, self).run_validation(data) - if isinstance(data, list): - data = Action.choices_to_value(value=data) - return data - - def to_representation(self, value): - return Action.value_to_choices(value) - - def to_internal_value(self, data): - if not self.allow_empty and not data: - self.fail('empty') - - if not data: - return data - - return Action.choices_to_value(data) - - -class ActionsDisplayField(ActionsField): - def to_representation(self, value): - values = super().to_representation(value) - choices = dict(Action.CHOICES) - return [choices.get(i) for i in values] - - -class BasePermissionSerializer(BulkOrgResourceModelSerializer): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_actions_field() - - def set_actions_field(self): - actions = self.fields.get('actions') - if not actions: - return - choices = actions._choices - choices = self._filter_actions_choices(choices) - actions._choices = choices - actions.default = list(choices.keys()) - - def _filter_actions_choices(self, choices): - return choices diff --git a/apps/perms/serializers/asset/permission.py b/apps/perms/serializers/permission.py similarity index 65% rename from apps/perms/serializers/asset/permission.py rename to apps/perms/serializers/permission.py index 490b9e57a..5a4bf5105 100644 --- a/apps/perms/serializers/asset/permission.py +++ b/apps/perms/serializers/permission.py @@ -2,50 +2,89 @@ # from rest_framework import serializers +from rest_framework.fields import empty from django.utils.translation import ugettext_lazy as _ from django.db.models import Q -from perms.models import AssetPermission, Action from assets.models import Asset, Node from users.models import User, UserGroup -from ..base import ActionsField, BasePermissionSerializer +from perms.models import AssetPermission, Action +from orgs.mixins.serializers import BulkOrgResourceModelSerializer -__all__ = ['AssetPermissionSerializer'] +__all__ = ['AssetPermissionSerializer', 'ActionsField'] -class AssetPermissionSerializer(BasePermissionSerializer): +class ActionsField(serializers.MultipleChoiceField): + def __init__(self, **kwargs): + kwargs['choices'] = Action.CHOICES + super().__init__(**kwargs) + + def run_validation(self, data=empty): + data = super(ActionsField, self).run_validation(data) + if isinstance(data, list): + data = Action.choices_to_value(value=data) + return data + + def to_representation(self, value): + return Action.value_to_choices(value) + + def to_internal_value(self, data): + if not self.allow_empty and not data: + self.fail('empty') + if not data: + return data + return Action.choices_to_value(data) + + +class ActionsDisplayField(ActionsField): + def to_representation(self, value): + values = super().to_representation(value) + choices = dict(Action.CHOICES) + return [choices.get(i) for i in values] + + +class AssetPermissionSerializer(BulkOrgResourceModelSerializer): + users_display = serializers.ListField( + child=serializers.CharField(), label=_('Users display'), required=False + ) + user_groups_display = serializers.ListField( + child=serializers.CharField(), label=_('User groups display'), required=False + ) + assets_display = serializers.ListField( + child=serializers.CharField(), label=_('Assets display'), required=False + ) + nodes_display = serializers.ListField( + child=serializers.CharField(), label=_('Nodes display'), required=False + ) actions = ActionsField(required=False, allow_null=True, label=_("Actions")) is_valid = serializers.BooleanField(read_only=True, label=_("Is valid")) is_expired = serializers.BooleanField(read_only=True, label=_('Is expired')) - users_display = serializers.ListField(child=serializers.CharField(), label=_('Users display'), required=False) - user_groups_display = serializers.ListField(child=serializers.CharField(), label=_('User groups display'), required=False) - assets_display = serializers.ListField(child=serializers.CharField(), label=_('Assets display'), required=False) - nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes display'), required=False) class Meta: model = AssetPermission fields_mini = ['id', 'name'] fields_small = fields_mini + [ 'is_active', 'is_expired', 'is_valid', 'actions', + 'accounts', 'created_by', 'date_created', 'date_expired', 'date_start', 'comment', 'from_ticket' ] fields_m2m = [ 'users', 'users_display', 'user_groups', 'user_groups_display', 'assets', - 'assets_display', 'nodes', 'nodes_display', 'accounts', + 'assets_display', 'nodes', 'nodes_display', 'users_amount', 'user_groups_amount', 'assets_amount', 'nodes_amount', ] fields = fields_small + fields_m2m read_only_fields = ['created_by', 'date_created', 'from_ticket'] extra_kwargs = { - 'is_expired': {'label': _('Is expired')}, - 'is_valid': {'label': _('Is valid')}, - 'actions': {'label': _('Actions')}, 'users_amount': {'label': _('Users amount')}, 'user_groups_amount': {'label': _('User groups amount')}, 'assets_amount': {'label': _('Assets amount')}, 'nodes_amount': {'label': _('Nodes amount')}, + 'actions': {'label': _('Actions')}, + 'is_expired': {'label': _('Is expired')}, + 'is_valid': {'label': _('Is valid')}, } @classmethod diff --git a/apps/perms/serializers/asset/permission_relation.py b/apps/perms/serializers/permission_relation.py similarity index 100% rename from apps/perms/serializers/asset/permission_relation.py rename to apps/perms/serializers/permission_relation.py diff --git a/apps/perms/serializers/asset/user_permission.py b/apps/perms/serializers/user_permission.py similarity index 95% rename from apps/perms/serializers/asset/user_permission.py rename to apps/perms/serializers/user_permission.py index c0d9b4c74..f7f541e7a 100644 --- a/apps/perms/serializers/asset/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -5,7 +5,7 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from assets.models import Node, Asset, Platform -from perms.serializers.base import ActionsField +from perms.serializers.permission import ActionsField __all__ = [ 'NodeGrantedSerializer', diff --git a/apps/tickets/serializers/ticket/apply_application.py b/apps/tickets/serializers/ticket/apply_application.py index 12b3f230d..d70e850a5 100644 --- a/apps/tickets/serializers/ticket/apply_application.py +++ b/apps/tickets/serializers/ticket/apply_application.py @@ -2,7 +2,7 @@ from django.utils.translation import ugettext as _ from rest_framework import serializers from perms.models import ApplicationPermission -from perms.serializers.base import ActionsField +from perms.serializers.permission import ActionsField from orgs.utils import tmp_to_org from applications.models import Application from tickets.models import ApplyApplicationTicket diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 93a4026c1..f2ed156ad 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from perms.serializers.base import ActionsField +from perms.serializers.permission import ActionsField from perms.models import AssetPermission from orgs.utils import tmp_to_org from assets.models import Asset, Node From 746c6e424253b917d747af8bcec649eab390fd0e Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 7 Sep 2022 18:33:34 +0800 Subject: [PATCH 108/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E3=80=81=E6=8E=88=E6=9D=83=E5=88=9B=E5=BB=BA=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E5=B0=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/protocol.py | 4 ---- apps/perms/serializers/permission.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py index a2aa2283f..d42ee2754 100644 --- a/apps/assets/models/protocol.py +++ b/apps/assets/models/protocol.py @@ -5,7 +5,3 @@ from django.utils.translation import gettext_lazy as _ class Protocol(models.Model): name = models.CharField(max_length=32, verbose_name=_("Name")) port = models.IntegerField(verbose_name=_("Port")) - - def save(self, force_insert=False, force_update=False, using=None, - update_fields=None): - pass diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index 5a4bf5105..18fa96563 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -111,7 +111,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): # 资产 assets_to_set = Asset.objects.filter( Q(ip__in=kwargs.get('assets_display')) | - Q(hostname__in=kwargs.get('assets_display')) + Q(name__in=kwargs.get('assets_display')) ).distinct() instance.assets.add(*assets_to_set) # 节点 From b910180a1241958d2b4365682a550678212a8ed5 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 7 Sep 2022 19:49:42 +0800 Subject: [PATCH 109/488] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8C=96=E7=AD=96?= =?UTF-8?q?=E7=95=A5=20=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/backup.py | 7 +- apps/common/const/choices.py | 7 ++ apps/ops/const.py | 29 +++++ apps/ops/models/__init__.py | 1 + apps/ops/models/automation/__init__.py | 5 + apps/ops/models/automation/base.py | 2 + apps/ops/models/automation/change_auth.py | 71 +++++++++++ apps/ops/models/automation/collect.py | 16 +++ apps/ops/models/automation/common.py | 111 ++++++++++++++++++ apps/ops/models/automation/push.py | 16 +++ apps/ops/models/automation/verify.py | 16 +++ apps/ops/task_handlers/__init__.py | 1 + apps/ops/task_handlers/base/__init__.py | 2 + apps/ops/task_handlers/base/handlers.py | 16 +++ apps/ops/task_handlers/base/manager.py | 78 ++++++++++++ .../ops/task_handlers/change_auth/__init__.py | 2 + .../ops/task_handlers/change_auth/handlers.py | 10 ++ apps/ops/task_handlers/change_auth/manager.py | 12 ++ apps/ops/task_handlers/collect/__init__.py | 2 + apps/ops/task_handlers/collect/handlers.py | 10 ++ apps/ops/task_handlers/collect/manager.py | 10 ++ apps/ops/task_handlers/endpoint.py | 31 +++++ apps/ops/task_handlers/push/__init__.py | 2 + apps/ops/task_handlers/push/handlers.py | 10 ++ apps/ops/task_handlers/push/manager.py | 10 ++ apps/ops/task_handlers/verify/__init__.py | 2 + apps/ops/task_handlers/verify/handlers.py | 10 ++ apps/ops/task_handlers/verify/manager.py | 10 ++ apps/ops/tasks.py | 12 ++ apps/ops/utils.py | 16 ++- 30 files changed, 520 insertions(+), 7 deletions(-) create mode 100644 apps/ops/const.py create mode 100644 apps/ops/models/automation/__init__.py create mode 100644 apps/ops/models/automation/base.py create mode 100644 apps/ops/models/automation/change_auth.py create mode 100644 apps/ops/models/automation/collect.py create mode 100644 apps/ops/models/automation/common.py create mode 100644 apps/ops/models/automation/push.py create mode 100644 apps/ops/models/automation/verify.py create mode 100644 apps/ops/task_handlers/__init__.py create mode 100644 apps/ops/task_handlers/base/__init__.py create mode 100644 apps/ops/task_handlers/base/handlers.py create mode 100644 apps/ops/task_handlers/base/manager.py create mode 100644 apps/ops/task_handlers/change_auth/__init__.py create mode 100644 apps/ops/task_handlers/change_auth/handlers.py create mode 100644 apps/ops/task_handlers/change_auth/manager.py create mode 100644 apps/ops/task_handlers/collect/__init__.py create mode 100644 apps/ops/task_handlers/collect/handlers.py create mode 100644 apps/ops/task_handlers/collect/manager.py create mode 100644 apps/ops/task_handlers/endpoint.py create mode 100644 apps/ops/task_handlers/push/__init__.py create mode 100644 apps/ops/task_handlers/push/handlers.py create mode 100644 apps/ops/task_handlers/push/manager.py create mode 100644 apps/ops/task_handlers/verify/__init__.py create mode 100644 apps/ops/task_handlers/verify/handlers.py create mode 100644 apps/ops/task_handlers/verify/manager.py diff --git a/apps/assets/models/backup.py b/apps/assets/models/backup.py index e27564d9a..debec255c 100644 --- a/apps/assets/models/backup.py +++ b/apps/assets/models/backup.py @@ -14,6 +14,7 @@ from common.utils import get_logger from common.db.encoder import ModelJSONFieldEncoder from common.db.models import BitOperationChoice from common.mixins.models import CommonModelMixin +from common.const.choices import Trigger from ..const import AllTypes, Category __all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution', 'Type'] @@ -89,7 +90,7 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): from ..tasks import execute_account_backup_plan name = "account_backup_plan_period_{}".format(str(self.id)[:8]) task = execute_account_backup_plan.name - args = (str(self.id), AccountBackupPlanExecution.Trigger.timing) + args = (str(self.id), Trigger.timing) kwargs = {} return name, task, args, kwargs @@ -120,10 +121,6 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): class AccountBackupPlanExecution(OrgModelMixin): - class Trigger(models.TextChoices): - manual = 'manual', _('Manual trigger') - timing = 'timing', _('Timing trigger') - id = models.UUIDField(default=uuid.uuid4, primary_key=True) date_start = models.DateTimeField( auto_now_add=True, verbose_name=_('Date start') diff --git a/apps/common/const/choices.py b/apps/common/const/choices.py index 6bff02254..f752bd50e 100644 --- a/apps/common/const/choices.py +++ b/apps/common/const/choices.py @@ -1,4 +1,11 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ ADMIN = 'Admin' USER = 'User' AUDITOR = 'Auditor' + + +class Trigger(models.TextChoices): + manual = 'manual', _('Manual trigger') + timing = 'timing', _('Timing trigger') diff --git a/apps/ops/const.py b/apps/ops/const.py new file mode 100644 index 000000000..ee2a17340 --- /dev/null +++ b/apps/ops/const.py @@ -0,0 +1,29 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class StrategyChoice(models.TextChoices): + push = 'push', _('Push') + verify = 'verify', _('Verify') + collect = 'collect', _('Collect') + change_auth = 'change_auth', _('Change auth') + + +class SSHKeyStrategy(models.TextChoices): + add = 'add', _('Append SSH KEY') + set = 'set', _('Empty and append SSH KEY') + set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ') + + +class PasswordStrategy(models.TextChoices): + custom = 'custom', _('Custom password') + random_one = 'random_one', _('All assets use the same random password') + random_all = 'random_all', _('All assets use different random password') + + +string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~' +DEFAULT_PASSWORD_LENGTH = 30 +DEFAULT_PASSWORD_RULES = { + 'length': DEFAULT_PASSWORD_LENGTH, + 'symbol_set': string_punctuation +} diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index 0a9ed463c..f925b14a5 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -4,3 +4,4 @@ from .adhoc import * from .celery import * from .command import * +from .automation import * diff --git a/apps/ops/models/automation/__init__.py b/apps/ops/models/automation/__init__.py new file mode 100644 index 000000000..83fe4a6f9 --- /dev/null +++ b/apps/ops/models/automation/__init__.py @@ -0,0 +1,5 @@ +from .change_auth import * +from .collect import * +from .push import * +from .verify import * +from .common import * diff --git a/apps/ops/models/automation/base.py b/apps/ops/models/automation/base.py new file mode 100644 index 000000000..ec51c5a2b --- /dev/null +++ b/apps/ops/models/automation/base.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# diff --git a/apps/ops/models/automation/change_auth.py b/apps/ops/models/automation/change_auth.py new file mode 100644 index 000000000..71adb010f --- /dev/null +++ b/apps/ops/models/automation/change_auth.py @@ -0,0 +1,71 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from ops.const import SSHKeyStrategy, PasswordStrategy, StrategyChoice +from ops.utils import generate_random_password +from common.db.fields import ( + EncryptCharField, EncryptTextField, JsonDictCharField +) +from .common import AutomationStrategy + + +class ChangeAuthStrategy(AutomationStrategy): + is_password = models.BooleanField(default=True) + password_strategy = models.CharField( + max_length=128, blank=True, null=True, choices=PasswordStrategy.choices, + verbose_name=_('Password strategy') + ) + password_rules = JsonDictCharField( + max_length=2048, blank=True, null=True, verbose_name=_('Password rules') + ) + password = EncryptCharField( + max_length=256, blank=True, null=True, verbose_name=_('Password') + ) + + is_ssh_key = models.BooleanField(default=False) + ssh_key_strategy = models.CharField( + max_length=128, blank=True, null=True, choices=SSHKeyStrategy.choices, + verbose_name=_('SSH Key strategy') + ) + private_key = EncryptTextField( + max_length=4096, blank=True, null=True, verbose_name=_('SSH private key') + ) + public_key = EncryptTextField( + max_length=4096, blank=True, null=True, verbose_name=_('SSH public key') + ) + recipients = models.ManyToManyField( + 'users.User', related_name='recipients_change_auth_strategy', blank=True, + verbose_name=_("Recipient") + ) + + class Meta: + verbose_name = _("Change auth strategy") + + def gen_execute_password(self): + if self.password_strategy == PasswordStrategy.custom: + return self.password + elif self.password_strategy == PasswordStrategy.random_one: + return generate_random_password(**self.password_rules) + else: + return None + + def to_attr_json(self): + attr_json = super().to_attr_json() + attr_json.update({ + 'type': StrategyChoice.change_auth, + + 'password': self.gen_execute_password(), + 'is_password': self.is_password, + 'password_rules': self.password_rules, + 'password_strategy': self.password_strategy, + + 'is_ssh_key': self.is_ssh_key, + 'public_key': self.public_key, + 'private_key': self.private_key, + 'ssh_key_strategy': self.ssh_key_strategy, + 'recipients': { + str(recipient.id): (str(recipient), bool(recipient.secret_key)) + for recipient in self.recipients.all() + } + }) + return attr_json diff --git a/apps/ops/models/automation/collect.py b/apps/ops/models/automation/collect.py new file mode 100644 index 000000000..9710e5c52 --- /dev/null +++ b/apps/ops/models/automation/collect.py @@ -0,0 +1,16 @@ +from django.utils.translation import ugettext_lazy as _ + +from ops.const import StrategyChoice +from .common import AutomationStrategy + + +class CollectStrategy(AutomationStrategy): + class Meta: + verbose_name = _("Collect strategy") + + def to_attr_json(self): + attr_json = super().to_attr_json() + attr_json.update({ + 'type': StrategyChoice.collect + }) + return attr_json diff --git a/apps/ops/models/automation/common.py b/apps/ops/models/automation/common.py new file mode 100644 index 000000000..a586c85a9 --- /dev/null +++ b/apps/ops/models/automation/common.py @@ -0,0 +1,111 @@ +import uuid +from celery import current_task +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from common.const.choices import Trigger +from common.mixins.models import CommonModelMixin +from common.db.fields import EncryptJsonDictTextField +from orgs.mixins.models import OrgModelMixin +from ops.mixin import PeriodTaskModelMixin +from ops.tasks import execute_automation_strategy +from ops.task_handlers import ExecutionManager + + +class AutomationStrategy(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + accounts = models.JSONField(default=list, verbose_name=_("Accounts")) + nodes = models.ManyToManyField( + 'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes") + ) + assets = models.ManyToManyField( + 'assets.Asset', related_name='automation_strategy', blank=True, verbose_name=_("Assets") + ) + comment = models.TextField(blank=True, verbose_name=_('Comment')) + + def __str__(self): + return self.name + '@' + str(self.created_by) + + def get_register_task(self): + name = "automation_strategy_period_{}".format(str(self.id)[:8]) + task = execute_automation_strategy.name + args = (str(self.id), Trigger.timing) + kwargs = {} + return name, task, args, kwargs + + def to_attr_json(self): + return { + 'name': self.name, + 'accounts': self.accounts, + 'assets': list(self.assets.all().values_list('id', flat=True)), + 'nodes': list(self.assets.all().values_list('id', flat=True)), + } + + def execute(self, trigger): + try: + eid = current_task.request.id + except AttributeError: + eid = str(uuid.uuid4()) + execution = AutomationStrategyExecution.objects.create( + id=eid, strategy=self, snapshot=self.to_attr_json(), trigger=trigger + ) + return execution.start() + + class Meta: + unique_together = [('org_id', 'name')] + verbose_name = _("Automation plan") + + +class AutomationStrategyExecution(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + + date_created = models.DateTimeField(auto_now_add=True) + timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True) + date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) + + snapshot = EncryptJsonDictTextField( + default=dict, blank=True, null=True, verbose_name=_('Automation snapshot') + ) + strategy = models.ForeignKey( + 'AutomationStrategy', related_name='execution', on_delete=models.CASCADE, + verbose_name=_('Automation strategy') + ) + trigger = models.CharField( + max_length=128, default=Trigger.manual, choices=Trigger.choices, verbose_name=_('Trigger mode') + ) + + class Meta: + verbose_name = _('Automation strategy execution') + + @property + def manager_type(self): + return self.snapshot['type'] + + def start(self): + manager = ExecutionManager(execution=self) + return manager.run() + + +class AutomationStrategyTask(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + asset = models.ForeignKey( + 'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset') + ) + account = models.ForeignKey( + 'assets.Account', on_delete=models.CASCADE, verbose_name=_('Account') + ) + is_success = models.BooleanField(default=False, verbose_name=_('Is success')) + timedelta = models.FloatField(default=0.0, null=True, verbose_name=_('Time')) + date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) + reason = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_('Reason')) + execution = models.ForeignKey( + 'AutomationStrategyExecution', related_name='task', on_delete=models.CASCADE, + verbose_name=_('Automation strategy execution') + ) + + class Meta: + verbose_name = _('Automation strategy task') + + @property + def handler_type(self): + return self.execution.snapshot['type'] diff --git a/apps/ops/models/automation/push.py b/apps/ops/models/automation/push.py new file mode 100644 index 000000000..f7a1bd4be --- /dev/null +++ b/apps/ops/models/automation/push.py @@ -0,0 +1,16 @@ +from django.utils.translation import ugettext_lazy as _ + +from ops.const import StrategyChoice +from .common import AutomationStrategy + + +class PushStrategy(AutomationStrategy): + class Meta: + verbose_name = _("Push strategy") + + def to_attr_json(self): + attr_json = super().to_attr_json() + attr_json.update({ + 'type': StrategyChoice.push + }) + return attr_json diff --git a/apps/ops/models/automation/verify.py b/apps/ops/models/automation/verify.py new file mode 100644 index 000000000..0726704f9 --- /dev/null +++ b/apps/ops/models/automation/verify.py @@ -0,0 +1,16 @@ +from django.utils.translation import ugettext_lazy as _ + +from ops.const import StrategyChoice +from .common import AutomationStrategy + + +class VerifyStrategy(AutomationStrategy): + class Meta: + verbose_name = _("Verify strategy") + + def to_attr_json(self): + attr_json = super().to_attr_json() + attr_json.update({ + 'type': StrategyChoice.verify + }) + return attr_json diff --git a/apps/ops/task_handlers/__init__.py b/apps/ops/task_handlers/__init__.py new file mode 100644 index 000000000..d557c449e --- /dev/null +++ b/apps/ops/task_handlers/__init__.py @@ -0,0 +1 @@ +from .endpoint import * diff --git a/apps/ops/task_handlers/base/__init__.py b/apps/ops/task_handlers/base/__init__.py new file mode 100644 index 000000000..2dc0b1a17 --- /dev/null +++ b/apps/ops/task_handlers/base/__init__.py @@ -0,0 +1,2 @@ +from .manager import * +from .handlers import * diff --git a/apps/ops/task_handlers/base/handlers.py b/apps/ops/task_handlers/base/handlers.py new file mode 100644 index 000000000..1df6d946c --- /dev/null +++ b/apps/ops/task_handlers/base/handlers.py @@ -0,0 +1,16 @@ +""" +执行改密计划的基类 +""" +from common.utils import get_logger + +logger = get_logger(__file__) + + +class BaseHandler: + def __init__(self, task, show_step_info=True): + self.task = task + self.conn = None + self.retry_times = 3 + self.current_step = 0 + self.is_frozen = False # 任务状态冻结标志 + self.show_step_info = show_step_info diff --git a/apps/ops/task_handlers/base/manager.py b/apps/ops/task_handlers/base/manager.py new file mode 100644 index 000000000..080bf866c --- /dev/null +++ b/apps/ops/task_handlers/base/manager.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# +import time +from openpyxl import Workbook +from django.utils import timezone + +from common.utils import get_logger +from common.utils.timezone import local_now_display + +logger = get_logger(__file__) + + +class BaseExecutionManager: + task_back_up_serializer: None + + def __init__(self, execution): + self.execution = execution + self.date_start = timezone.now() + self.time_start = time.time() + self.date_end = None + self.time_end = None + self.timedelta = 0 + self.total_tasks = [] + + def on_tasks_pre_run(self, tasks): + raise NotImplementedError + + def on_per_task_pre_run(self, task, total, index): + raise NotImplementedError + + def create_csv_file(self, tasks, file_name): + raise NotImplementedError + + def get_handler_cls(self): + raise NotImplemented + + def do_run(self): + tasks = self.total_tasks = self.execution.create_plan_tasks() + self.on_tasks_pre_run(tasks) + total = len(tasks) + + for index, task in enumerate(tasks, start=1): + self.on_per_task_pre_run(task, total, index) + task.start(show_step_info=False) + + def pre_run(self): + self.execution.date_start = self.date_start + self.execution.save() + self.show_execution_steps() + + def show_execution_steps(self): + pass + + def show_summary(self): + split_line = '#' * 40 + summary = self.execution.result_summary + logger.info(f'\n{split_line} 改密计划执行结果汇总 {split_line}') + logger.info( + '\n成功: {succeed}, 失败: {failed}, 总数: {total}\n' + ''.format(**summary) + ) + + def post_run(self): + self.time_end = time.time() + self.date_end = timezone.now() + + logger.info('\n\n' + '-' * 80) + logger.info('任务执行结束 {}\n'.format(local_now_display())) + self.timedelta = int(self.time_end - self.time_start) + logger.info('用时: {}s'.format(self.timedelta)) + self.execution.timedelta = self.timedelta + self.execution.save() + self.show_summary() + + def run(self): + self.pre_run() + self.do_run() + self.post_run() diff --git a/apps/ops/task_handlers/change_auth/__init__.py b/apps/ops/task_handlers/change_auth/__init__.py new file mode 100644 index 000000000..2dc0b1a17 --- /dev/null +++ b/apps/ops/task_handlers/change_auth/__init__.py @@ -0,0 +1,2 @@ +from .manager import * +from .handlers import * diff --git a/apps/ops/task_handlers/change_auth/handlers.py b/apps/ops/task_handlers/change_auth/handlers.py new file mode 100644 index 000000000..c982a089d --- /dev/null +++ b/apps/ops/task_handlers/change_auth/handlers.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseHandler + +logger = get_logger(__name__) + + +class ChangeAuthHandler(BaseHandler): + pass diff --git a/apps/ops/task_handlers/change_auth/manager.py b/apps/ops/task_handlers/change_auth/manager.py new file mode 100644 index 000000000..a9f77927c --- /dev/null +++ b/apps/ops/task_handlers/change_auth/manager.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseExecutionManager +from .handlers import ChangeAuthHandler + +logger = get_logger(__name__) + + +class ChangeAuthExecutionManager(BaseExecutionManager): + def get_handler_cls(self): + return ChangeAuthHandler diff --git a/apps/ops/task_handlers/collect/__init__.py b/apps/ops/task_handlers/collect/__init__.py new file mode 100644 index 000000000..2dc0b1a17 --- /dev/null +++ b/apps/ops/task_handlers/collect/__init__.py @@ -0,0 +1,2 @@ +from .manager import * +from .handlers import * diff --git a/apps/ops/task_handlers/collect/handlers.py b/apps/ops/task_handlers/collect/handlers.py new file mode 100644 index 000000000..81b1e748d --- /dev/null +++ b/apps/ops/task_handlers/collect/handlers.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseHandler + +logger = get_logger(__name__) + + +class CollectHandler(BaseHandler): + pass diff --git a/apps/ops/task_handlers/collect/manager.py b/apps/ops/task_handlers/collect/manager.py new file mode 100644 index 000000000..18a685d7f --- /dev/null +++ b/apps/ops/task_handlers/collect/manager.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseExecutionManager + +logger = get_logger(__name__) + + +class CollectExecutionManager(object): + pass diff --git a/apps/ops/task_handlers/endpoint.py b/apps/ops/task_handlers/endpoint.py new file mode 100644 index 000000000..4bca2631b --- /dev/null +++ b/apps/ops/task_handlers/endpoint.py @@ -0,0 +1,31 @@ +from ops.const import StrategyChoice +from .push import PushExecutionManager, PushHandler +from .verify import VerifyExecutionManager, VerifyHandler +from .collect import CollectExecutionManager, CollectHandler +from .change_auth import ChangeAuthExecutionManager, ChangeAuthHandler + + +class ExecutionManager: + manager_type = { + StrategyChoice.push: PushExecutionManager, + StrategyChoice.verify: VerifyExecutionManager, + StrategyChoice.collect: CollectExecutionManager, + StrategyChoice.change_auth: ChangeAuthExecutionManager, + } + + def __new__(cls, execution): + manager = cls.manager_type[execution.manager_type] + return manager(execution) + + +class TaskHandler: + handler_type = { + StrategyChoice.push: PushHandler, + StrategyChoice.verify: VerifyHandler, + StrategyChoice.collect: CollectHandler, + StrategyChoice.change_auth: ChangeAuthHandler, + } + + def __new__(cls, task, show_step_info): + handler = cls.handler_type[task.handler_type] + return handler(task, show_step_info) diff --git a/apps/ops/task_handlers/push/__init__.py b/apps/ops/task_handlers/push/__init__.py new file mode 100644 index 000000000..2dc0b1a17 --- /dev/null +++ b/apps/ops/task_handlers/push/__init__.py @@ -0,0 +1,2 @@ +from .manager import * +from .handlers import * diff --git a/apps/ops/task_handlers/push/handlers.py b/apps/ops/task_handlers/push/handlers.py new file mode 100644 index 000000000..891ac4bb6 --- /dev/null +++ b/apps/ops/task_handlers/push/handlers.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseHandler + +logger = get_logger(__name__) + + +class PushHandler(BaseHandler): + pass diff --git a/apps/ops/task_handlers/push/manager.py b/apps/ops/task_handlers/push/manager.py new file mode 100644 index 000000000..933f9a0cc --- /dev/null +++ b/apps/ops/task_handlers/push/manager.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseExecutionManager + +logger = get_logger(__name__) + + +class PushExecutionManager(BaseExecutionManager): + pass diff --git a/apps/ops/task_handlers/verify/__init__.py b/apps/ops/task_handlers/verify/__init__.py new file mode 100644 index 000000000..2dc0b1a17 --- /dev/null +++ b/apps/ops/task_handlers/verify/__init__.py @@ -0,0 +1,2 @@ +from .manager import * +from .handlers import * diff --git a/apps/ops/task_handlers/verify/handlers.py b/apps/ops/task_handlers/verify/handlers.py new file mode 100644 index 000000000..7a7f69881 --- /dev/null +++ b/apps/ops/task_handlers/verify/handlers.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseHandler + +logger = get_logger(__name__) + + +class VerifyHandler(BaseHandler): + pass diff --git a/apps/ops/task_handlers/verify/manager.py b/apps/ops/task_handlers/verify/manager.py new file mode 100644 index 000000000..a3727f1de --- /dev/null +++ b/apps/ops/task_handlers/verify/manager.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# +from common.utils import get_logger +from ..base import BaseExecutionManager + +logger = get_logger(__name__) + + +class VerifyExecutionManager(BaseExecutionManager): + pass diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index e68b4c55c..2b739e3d6 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -179,3 +179,15 @@ def add_m(x): res = chain(*tuple(s))() return res + +@shared_task +def execute_automation_strategy(pid, trigger): + from .models import AutomationStrategy + with tmp_to_root_org(): + instance = get_object_or_none(AutomationStrategy, pk=pid) + if not instance: + logger.error("No automation plan found: {}".format(pid)) + return + with tmp_to_org(instance.org): + instance.execute(trigger) + diff --git a/apps/ops/utils.py b/apps/ops/utils.py index a01543b95..ea9d74cbc 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -5,11 +5,11 @@ import uuid from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, get_object_or_none -from common.tasks import send_mail_async from orgs.utils import org_aware_func from jumpserver.const import PROJECT_DIR from .models import Task, AdHoc +from .const import DEFAULT_PASSWORD_RULES logger = get_logger(__file__) @@ -29,7 +29,7 @@ def update_or_create_ansible_task( interval=None, crontab=None, is_periodic=False, callback=None, pattern='all', options=None, run_as_admin=False, run_as=None, system_user=None, become_info=None, - ): +): if not hosts or not tasks or not task_name: return None, None if options is None: @@ -80,3 +80,15 @@ def get_task_log_path(base_path, task_id, level=2): path = os.path.join(base_path, rel_path) os.makedirs(os.path.dirname(path), exist_ok=True) return path + + +def generate_random_password(**kwargs): + import random + import string + length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length'])) + symbol_set = kwargs.get('symbol_set') + if symbol_set is None: + symbol_set = DEFAULT_PASSWORD_RULES['symbol_set'] + chars = string.ascii_letters + string.digits + symbol_set + password = ''.join([random.choice(chars) for _ in range(length)]) + return password From e9bb5223ebc51d39b97d02f186285dc54d9c36ff Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Sep 2022 20:01:04 +0800 Subject: [PATCH 110/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platforms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 2 +- apps/assets/serializers/asset/common.py | 7 +------ apps/assets/serializers/platform.py | 1 + 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 6f122cfe2..7b9e543a2 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -10,7 +10,7 @@ __all__ = ['Account', 'AccountTemplate'] class Account(BaseAccount): - asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) + asset = models.ForeignKey('assets.Asset', related_name='accounts', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) history = HistoricalRecords() diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 61e32a7a8..f55a73c3d 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -8,7 +8,7 @@ from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.fields import LabeledChoiceField, ObjectRelatedField from ..account import AccountSerializer -from ...models import Asset, Node, Platform, Protocol, Label, Domain +from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account from ...const import Category, AllTypes __all__ = [ @@ -88,11 +88,6 @@ class AssetSerializer(JMSWritableNestedModelSerializer): 'admin_user_display': {'label': _('Admin user display'), 'read_only': True}, } - def __init__(self, *args, **kwargs): - data = kwargs.get('data', {}) - self.accounts_data = data.pop('accounts', []) - super().__init__(*args, **kwargs) - @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 01f2e8956..600098b86 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -19,6 +19,7 @@ class ProtocolSettingSerializer(serializers.Serializer): ] # Common required = serializers.BooleanField(required=True, initial=False, label=_("Required")) + # RDP console = serializers.BooleanField(required=False) security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any', required=False) From 50cf40eaeb88249d6d8dd0b5a90da39c784b2b8b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 7 Sep 2022 20:24:48 +0800 Subject: [PATCH 111/488] =?UTF-8?q?perf:=20=E5=AE=8C=E7=BE=8E=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=20accounts=20=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/apps.py | 2 +- apps/assets/serializers/account/common.py | 2 ++ apps/assets/serializers/asset/common.py | 13 ++++++++++++- apps/assets/signal_handlers/account.py | 2 ++ apps/orgs/mixins/serializers.py | 4 +++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/assets/apps.py b/apps/assets/apps.py index 712033455..e1bb43544 100644 --- a/apps/assets/apps.py +++ b/apps/assets/apps.py @@ -13,4 +13,4 @@ class AssetsConfig(AppConfig): def ready(self): super().ready() - # from . import signal_handlers + from . import signal_handlers diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py index 1627bf8c3..43c5c193b 100644 --- a/apps/assets/serializers/account/common.py +++ b/apps/assets/serializers/account/common.py @@ -19,6 +19,8 @@ class AccountFieldsSerializerMixin(serializers.ModelSerializer): extra_kwargs = { 'private_key': {'write_only': True}, 'public_key': {'write_only': True}, + 'token': {'write_only': True}, + 'password': {'write_only': True}, } def validate_name(self, value): diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index f55a73c3d..64854b3f4 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -49,6 +49,17 @@ class AssetPlatformSerializer(serializers.ModelSerializer): } +class AssetAccountSerializer(AccountSerializer): + add_org_fields = False + + class Meta(AccountSerializer.Meta): + fields_mini = [ + 'id', 'name', 'username', 'privileged', 'version' + ] + fields_write_only = ['password', 'private_key', 'public_key', 'passphrase', 'token'] + fields = fields_mini + fields_write_only + + class AssetSerializer(JMSWritableNestedModelSerializer): category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type')) @@ -56,7 +67,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) - accounts = AccountSerializer(many=True, required=False, label=_('Accounts')) + accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) """ 资产的数据结构 diff --git a/apps/assets/signal_handlers/account.py b/apps/assets/signal_handlers/account.py index 8020e4087..26cf75d02 100644 --- a/apps/assets/signal_handlers/account.py +++ b/apps/assets/signal_handlers/account.py @@ -9,6 +9,8 @@ logger = get_logger(__name__) @receiver(pre_save, sender=Account) def on_account_pre_create(sender, instance, **kwargs): + # Todo: 是否只有更改密码的时候才有版本增加, bitwarden 只有再改密码的时候, + # 才会有版本,代表的是 password_version # 升级版本号 instance.version += 1 # 即使在 root 组织也不怕 diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py index fed9d1713..5c1a561e3 100644 --- a/apps/orgs/mixins/serializers.py +++ b/apps/orgs/mixins/serializers.py @@ -24,6 +24,7 @@ class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer): """ org_id = serializers.ReadOnlyField(default=get_current_org_id_for_serializer, label=_("Organization")) org_name = serializers.ReadOnlyField(label=_("Org name")) + add_org_fields = True def get_validators(self): _validators = super().get_validators() @@ -38,7 +39,8 @@ class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer): def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) - fields.extend(["org_id", "org_name"]) + if self.add_org_fields: + fields.extend(["org_id", "org_name"]) return fields From 25bded69ab540e36d67d1897efc2cd8ba4c41917 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 8 Sep 2022 20:31:04 +0800 Subject: [PATCH 112/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=92=8C=E8=B5=84=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/category.py | 0 apps/assets/const.py | 2 +- .../migrations/0111_auto_20220908_1958.py | 29 +++++ apps/assets/models/platform.py | 1 + .../host/change_password_linux/main.yml | 2 - apps/assets/serializers/platform.py | 16 +-- ...tegytask_changeauthstrategy_collectstra.py | 123 ++++++++++++++++++ 7 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 apps/assets/api/category.py create mode 100644 apps/assets/migrations/0111_auto_20220908_1958.py create mode 100644 apps/ops/migrations/0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py diff --git a/apps/assets/api/category.py b/apps/assets/api/category.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/const.py b/apps/assets/const.py index cbb3abd89..93b6032d3 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -92,7 +92,7 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): def platform_constraints(cls): return { cls.LINUX: { - '_protocols': ['ssh', 'sftp', 'rdp', 'vnc', 'telnet'] + '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] }, cls.WINDOWS: { '_protocols': ['ssh', 'rdp', 'vnc'], diff --git a/apps/assets/migrations/0111_auto_20220908_1958.py b/apps/assets/migrations/0111_auto_20220908_1958.py new file mode 100644 index 000000000..7fb913fae --- /dev/null +++ b/apps/assets/migrations/0111_auto_20220908_1958.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.14 on 2022-09-08 11:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_auto_20220901_1542'), + ] + + operations = [ + migrations.AddField( + model_name='platform', + name='domain_enabled', + field=models.BooleanField(default=True, verbose_name='Domain enalbed'), + ), + migrations.AlterField( + model_name='account', + name='asset', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset'), + ), + migrations.AlterField( + model_name='asset', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 9feb5325a..ebf289038 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -31,6 +31,7 @@ class Platform(models.Model): comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) # 资产有关的 charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) + domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enalbed")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) diff --git a/apps/assets/playbooks/platform/host/change_password_linux/main.yml b/apps/assets/playbooks/platform/host/change_password_linux/main.yml index 402c7fa8d..6b5f0df66 100644 --- a/apps/assets/playbooks/platform/host/change_password_linux/main.yml +++ b/apps/assets/playbooks/platform/host/change_password_linux/main.yml @@ -1,4 +1,3 @@ -{% for account in accounts %} - hosts: {{ account.asset.name }} vars: account: @@ -7,4 +6,3 @@ public_key: {{ account.public_key }} roles: - change_password -{% endfor %} diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 600098b86..9559ea70d 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -17,14 +17,13 @@ class ProtocolSettingSerializer(serializers.Serializer): ('tls', 'TLS'), ('nla', 'NLA'), ] - # Common - required = serializers.BooleanField(required=True, initial=False, label=_("Required")) - # RDP console = serializers.BooleanField(required=False) - security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any', required=False) + security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any') + # SFTP - sftp_home = serializers.CharField(default='/tmp', required=False) + sftp_enabled = serializers.BooleanField(default=True, label=_("SFTP enabled")) + sftp_home = serializers.CharField(default='/tmp', label=_("SFTP home")) class PlatformProtocolsSerializer(serializers.ModelSerializer): @@ -39,7 +38,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type")) category = LabeledChoiceField(choices=Category.choices, label=_("Category")) protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) - type_constraints = serializers.ReadOnlyField(required=False, read_only=True) su_method = LabeledChoiceField( choices=[('sudo', 'sudo su -'), ('su', 'su - ')], label='切换方式', required=False, default='sudo' @@ -49,17 +47,17 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): model = Platform fields_mini = ['id', 'name', 'internal'] fields_small = fields_mini + [ - 'category', 'type', + 'category', 'type', 'charset', ] fields = fields_small + [ - 'protocols_enabled', 'protocols', + 'protocols_enabled', 'protocols', 'domain_enabled', 'gather_facts_enabled', 'gather_facts_method', 'su_enabled', 'su_method', 'gather_accounts_enabled', 'gather_accounts_method', 'create_account_enabled', 'create_account_method', 'verify_account_enabled', 'verify_account_method', 'change_password_enabled', 'change_password_method', - 'type_constraints', 'comment', 'charset', + 'comment', ] extra_kwargs = { 'su_enabled': {'label': '启用切换账号'}, diff --git a/apps/ops/migrations/0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py b/apps/ops/migrations/0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py new file mode 100644 index 000000000..361869794 --- /dev/null +++ b/apps/ops/migrations/0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py @@ -0,0 +1,123 @@ +# Generated by Django 3.2.14 on 2022-09-08 11:58 + +import common.db.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0111_auto_20220908_1958'), + ('ops', '0022_auto_20220817_1346'), + ] + + operations = [ + migrations.CreateModel( + name='AutomationStrategy', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('is_periodic', models.BooleanField(default=False)), + ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), + ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('accounts', models.JSONField(default=list, verbose_name='Accounts')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('assets', models.ManyToManyField(blank=True, related_name='automation_strategy', to='assets.Asset', verbose_name='Assets')), + ('nodes', models.ManyToManyField(blank=True, related_name='automation_strategy', to='assets.Node', verbose_name='Nodes')), + ], + options={ + 'verbose_name': 'Automation plan', + 'unique_together': {('org_id', 'name')}, + }, + ), + migrations.CreateModel( + name='AutomationStrategyExecution', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')), + ('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')), + ('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')), + ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), + ('strategy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='execution', to='ops.automationstrategy', verbose_name='Automation strategy')), + ], + options={ + 'verbose_name': 'Automation strategy execution', + }, + ), + migrations.CreateModel( + name='CollectStrategy', + fields=[ + ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), + ], + options={ + 'verbose_name': 'Collect strategy', + }, + bases=('ops.automationstrategy',), + ), + migrations.CreateModel( + name='PushStrategy', + fields=[ + ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), + ], + options={ + 'verbose_name': 'Push strategy', + }, + bases=('ops.automationstrategy',), + ), + migrations.CreateModel( + name='VerifyStrategy', + fields=[ + ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), + ], + options={ + 'verbose_name': 'Verify strategy', + }, + bases=('ops.automationstrategy',), + ), + migrations.CreateModel( + name='AutomationStrategyTask', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('is_success', models.BooleanField(default=False, verbose_name='Is success')), + ('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')), + ('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')), + ('reason', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Reason')), + ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.account', verbose_name='Account')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')), + ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='task', to='ops.automationstrategyexecution', verbose_name='Automation strategy execution')), + ], + options={ + 'verbose_name': 'Automation strategy task', + }, + ), + migrations.CreateModel( + name='ChangeAuthStrategy', + fields=[ + ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), + ('is_password', models.BooleanField(default=True)), + ('password_strategy', models.CharField(blank=True, choices=[('custom', 'Custom password'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], max_length=128, null=True, verbose_name='Password strategy')), + ('password_rules', common.db.fields.JsonDictCharField(blank=True, max_length=2048, null=True, verbose_name='Password rules')), + ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('is_ssh_key', models.BooleanField(default=False)), + ('ssh_key_strategy', models.CharField(blank=True, choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], max_length=128, null=True, verbose_name='SSH Key strategy')), + ('private_key', common.db.fields.EncryptTextField(blank=True, max_length=4096, null=True, verbose_name='SSH private key')), + ('public_key', common.db.fields.EncryptTextField(blank=True, max_length=4096, null=True, verbose_name='SSH public key')), + ('recipients', models.ManyToManyField(blank=True, related_name='recipients_change_auth_strategy', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), + ], + options={ + 'verbose_name': 'Change auth strategy', + }, + bases=('ops.automationstrategy',), + ), + ] From da772b572a0504cc4c71b63eb6c7ca9af2e86d78 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 8 Sep 2022 20:31:57 +0800 Subject: [PATCH 113/488] =?UTF-8?q?fix:=20=E6=8E=88=E6=9D=83=20API=20?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/asset_permission_relation.py | 12 ++++-------- apps/perms/api/user_permission/assets/mixin.py | 8 ++++---- apps/perms/urls/asset_permission.py | 3 +++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index 41a6f083f..a3b65f3f3 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -40,8 +40,7 @@ class AssetPermissionUserRelationViewSet(RelationMixin): def get_queryset(self): queryset = super().get_queryset() - queryset = queryset \ - .annotate(user_display=F('user__name')) + queryset = queryset.annotate(user_display=F('user__name')) return queryset @@ -69,8 +68,7 @@ class AssetPermissionUserGroupRelationViewSet(RelationMixin): def get_queryset(self): queryset = super().get_queryset() - queryset = queryset \ - .annotate(usergroup_display=F('usergroup__name')) + queryset = queryset.annotate(usergroup_display=F('usergroup__name')) return queryset @@ -84,8 +82,7 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): def get_queryset(self): queryset = super().get_queryset() - queryset = queryset \ - .annotate(asset_display=F('asset__name')) + queryset = queryset.annotate(asset_display=F('asset__name')) return queryset @@ -111,7 +108,6 @@ class AssetPermissionNodeRelationViewSet(RelationMixin): def get_queryset(self): queryset = super().get_queryset() - queryset = queryset \ - .annotate(node_key=F('node__key')) + queryset = queryset.annotate(node_key=F('node__key')) return queryset diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index 743881eba..096bd21b3 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -32,15 +32,15 @@ class UserDirectGrantedAssetsQuerysetMixin: class UserAllGrantedAssetsQuerysetMixin: only_fields = serializers.AssetGrantedSerializer.Meta.only_fields pagination_class = AllGrantedAssetPagination - user: User ordering_fields = ("hostname", "ip", "port", "cpu_cores") ordering = ('hostname', ) - + + user: User + def get_queryset(self): if getattr(self, 'swagger_fake_view', False): return Asset.objects.none() - queryset = UserGrantedAssetsQueryUtils(self.user) \ - .get_all_granted_assets() + queryset = UserGrantedAssetsQueryUtils(self.user).get_all_granted_assets() queryset = queryset.prefetch_related('platform').only(*self.only_fields) return queryset diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index a39f61a80..4258a2698 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -5,6 +5,7 @@ from rest_framework_bulk.routes import BulkRouter from .. import api +# v3 Done router = BulkRouter() router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, 'asset-permissions-users-relation') @@ -86,6 +87,8 @@ user_group_permission_urlpatterns = [ ] permission_urlpatterns = [ + # Todo: 获取规则中授权的所有账号列表 + # # 授权规则中授权的资产 path('/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'), path('/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'), From 6f71989553f105750eac838840cd3b8bb6c0820b Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 8 Sep 2022 20:46:56 +0800 Subject: [PATCH 114/488] =?UTF-8?q?perf:=20=E4=B8=8B=E4=B8=80=E6=AD=A5?= =?UTF-8?q?=E6=95=B4=E7=90=86=E6=8E=88=E6=9D=83=E6=A8=A1=E5=9D=97=E6=89=80?= =?UTF-8?q?=E6=9C=89=20API=20=E7=9A=84=E7=BB=A7=E6=89=BF=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/assets/api.py | 9 ++++++--- apps/perms/urls/asset_permission.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/perms/api/user_permission/assets/api.py b/apps/perms/api/user_permission/assets/api.py index da304ee6c..3808cf7fc 100644 --- a/apps/perms/api/user_permission/assets/api.py +++ b/apps/perms/api/user_permission/assets/api.py @@ -9,9 +9,12 @@ from .mixin import ( ) __all__ = [ - 'UserDirectGrantedAssetsForAdminApi', 'MyDirectGrantedAssetsApi', 'UserFavoriteGrantedAssetsForAdminApi', - 'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeForAdminApi', 'MyUngroupAssetsAsTreeApi', - 'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi', 'UserGrantedNodeAssetsForAdminApi', + 'UserDirectGrantedAssetsForAdminApi', 'MyDirectGrantedAssetsApi', + 'UserFavoriteGrantedAssetsForAdminApi', + 'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeForAdminApi', + 'MyUngroupAssetsAsTreeApi', + 'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi', + 'UserGrantedNodeAssetsForAdminApi', 'MyGrantedNodeAssetsApi', ] diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 4258a2698..a00adffbe 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -71,6 +71,7 @@ user_permission_urlpatterns = [ path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsForAdminApi.as_view(), name='user-ungrouped-assets'), path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), + # Todo: 删除 # Asset System users path('/assets//system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'), path('assets//system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), From aed7b32d6c4c832b53b568205712b6b5a6ce7bb7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Sep 2022 11:00:09 +0800 Subject: [PATCH 115/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20assets=20a?= =?UTF-8?q?pi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 309 ------------------------ apps/assets/api/asset/asset.py | 20 +- apps/assets/models/platform.py | 2 +- apps/assets/serializers/asset/common.py | 30 +-- 4 files changed, 19 insertions(+), 342 deletions(-) delete mode 100644 apps/assets/api/asset.py diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py deleted file mode 100644 index 881395f5b..000000000 --- a/apps/assets/api/asset.py +++ /dev/null @@ -1,309 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework.viewsets import ModelViewSet -from rest_framework.generics import RetrieveAPIView, ListAPIView -from django.shortcuts import get_object_or_404 -from django.db.models import Q - -from common.utils import get_logger, get_object_or_none -from common.mixins.api import SuggestionMixin, RenderToJsonMixin -from users.models import User, UserGroup -from users.serializers import UserSerializer, UserGroupSerializer -from users.filters import UserFilter -from perms.models import AssetPermission -from perms.serializers import AssetPermissionSerializer -from perms.filters import AssetPermissionFilter -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.mixins import generics -from assets.api import FilterAssetByNodeMixin -from ..models import Asset, Node, Platform, Gateway -from .. import serializers -from ..tasks import ( - update_assets_hardware_info_manual, test_assets_connectivity_manual, - test_system_users_connectivity_a_asset, push_system_users_a_asset -) -from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend - -logger = get_logger(__file__) -__all__ = [ - 'AssetViewSet', 'AssetPlatformRetrieveApi', - 'AssetGatewayListApi', 'AssetPlatformViewSet', - 'AssetTaskCreateApi', 'AssetsTaskCreateApi', - 'AssetPermUserListApi', 'AssetPermUserPermissionsListApi', - 'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi', -] - - -class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet): - """ - API endpoint that allows Asset to be viewed or edited. - """ - model = Asset - filterset_fields = { - 'hostname': ['exact'], - 'ip': ['exact'], - 'system_users__id': ['exact'], - 'platform__base': ['exact'], - 'is_active': ['exact'], - 'protocols': ['exact', 'icontains'] - } - search_fields = ("hostname", "ip") - ordering_fields = ("hostname", "ip", "port", "cpu_cores") - ordering = ('hostname', ) - serializer_classes = { - 'default': serializers.AssetSerializer, - 'suggestion': serializers.MiniAssetSerializer - } - rbac_perms = { - 'match': 'assets.match_asset' - } - extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend] - - def set_assets_node(self, assets): - if not isinstance(assets, list): - assets = [assets] - node_id = self.request.query_params.get('node_id') - if not node_id: - return - node = get_object_or_none(Node, pk=node_id) - if not node: - return - node.assets.add(*assets) - - def perform_create(self, serializer): - assets = serializer.save() - self.set_assets_node(assets) - - -class AssetPlatformRetrieveApi(RetrieveAPIView): - queryset = Platform.objects.all() - serializer_class = serializers.PlatformSerializer - rbac_perms = { - 'retrieve': 'assets.view_gateway' - } - - def get_object(self): - asset_pk = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_pk) - return asset.platform - - -class AssetPlatformViewSet(ModelViewSet, RenderToJsonMixin): - queryset = Platform.objects.all() - serializer_class = serializers.PlatformSerializer - filterset_fields = ['name', 'base'] - search_fields = ['name'] - - def check_object_permissions(self, request, obj): - if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: - self.permission_denied( - request, message={"detail": "Internal platform"} - ) - return super().check_object_permissions(request, obj) - - -class AssetsTaskMixin: - - def perform_assets_task(self, serializer): - data = serializer.validated_data - action = data['action'] - assets = data.get('assets', []) - if action == "refresh": - task = update_assets_hardware_info_manual.delay(assets) - else: - # action == 'test': - task = test_assets_connectivity_manual.delay(assets) - return task - - def perform_create(self, serializer): - task = self.perform_assets_task(serializer) - self.set_task_to_serializer_data(serializer, task) - - def set_task_to_serializer_data(self, serializer, task): - data = getattr(serializer, '_data', {}) - data["task"] = task.id - setattr(serializer, '_data', data) - - -class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): - model = Asset - serializer_class = serializers.AssetTaskSerializer - - def create(self, request, *args, **kwargs): - pk = self.kwargs.get('pk') - request.data['asset'] = pk - request.data['assets'] = [pk] - return super().create(request, *args, **kwargs) - - def check_permissions(self, request): - action = request.data.get('action') - action_perm_require = { - 'refresh': 'assets.refresh_assethardwareinfo', - 'push_system_user': 'assets.push_assetsystemuser', - 'test': 'assets.test_assetconnectivity', - 'test_system_user': 'assets.test_assetconnectivity' - } - perm_required = action_perm_require.get(action) - has = self.request.user.has_perm(perm_required) - - if not has: - self.permission_denied(request) - - def perform_asset_task(self, serializer): - data = serializer.validated_data - action = data['action'] - if action not in ['push_system_user', 'test_system_user']: - return - - asset = data['asset'] - system_users = data.get('system_users') - if not system_users: - system_users = asset.get_all_system_users() - if action == 'push_system_user': - task = push_system_users_a_asset.delay(system_users, asset=asset) - elif action == 'test_system_user': - task = test_system_users_connectivity_a_asset.delay(system_users, asset=asset) - else: - task = None - return task - - def perform_create(self, serializer): - task = self.perform_asset_task(serializer) - if not task: - task = self.perform_assets_task(serializer) - self.set_task_to_serializer_data(serializer, task) - - -class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): - model = Asset - serializer_class = serializers.AssetsTaskSerializer - - def check_permissions(self, request): - action = request.data.get('action') - action_perm_require = { - 'refresh': 'assets.refresh_assethardwareinfo', - } - perm_required = action_perm_require.get(action) - has = self.request.user.has_perm(perm_required) - if not has: - self.permission_denied(request) - - -class AssetGatewayListApi(generics.ListAPIView): - serializer_class = serializers.GatewayWithAuthSerializer - rbac_perms = { - 'list': 'assets.view_gateway' - } - - def get_queryset(self): - asset_id = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - if not asset.domain: - return Gateway.objects.none() - queryset = asset.domain.gateways.filter(protocol='ssh') - return queryset - - -class BaseAssetPermUserOrUserGroupListApi(ListAPIView): - rbac_perms = { - 'GET': 'perms.view_assetpermission' - } - - def get_object(self): - asset_id = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - return asset - - def get_asset_related_perms(self): - asset = self.get_object() - nodes = asset.get_all_nodes(flat=True) - perms = AssetPermission.objects.filter(Q(assets=asset) | Q(nodes__in=nodes)) - return perms - - -class AssetPermUserListApi(BaseAssetPermUserOrUserGroupListApi): - filterset_class = UserFilter - search_fields = ('username', 'email', 'name', 'id', 'source', 'role') - serializer_class = UserSerializer - rbac_perms = { - 'GET': 'perms.view_assetpermission' - } - - def get_queryset(self): - perms = self.get_asset_related_perms() - users = User.objects.filter( - Q(assetpermissions__in=perms) | Q(groups__assetpermissions__in=perms) - ).distinct() - return users - - -class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi): - serializer_class = UserGroupSerializer - - def get_queryset(self): - perms = self.get_asset_related_perms() - user_groups = UserGroup.objects.filter(assetpermissions__in=perms).distinct() - return user_groups - - -class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView): - model = AssetPermission - serializer_class = AssetPermissionSerializer - filterset_class = AssetPermissionFilter - search_fields = ('name',) - rbac_perms = { - 'list': 'perms.view_assetpermission' - } - - def get_object(self): - asset_id = self.kwargs.get('pk') - asset = get_object_or_404(Asset, pk=asset_id) - return asset - - def filter_asset_related(self, queryset): - asset = self.get_object() - nodes = asset.get_all_nodes(flat=True) - perms = queryset.filter(Q(assets=asset) | Q(nodes__in=nodes)) - return perms - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_asset_related(queryset) - return queryset - - -class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin): - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_user_related(queryset) - queryset = queryset.distinct() - return queryset - - def filter_user_related(self, queryset): - user = self.get_perm_user() - user_groups = user.groups.all() - perms = queryset.filter(Q(users=user) | Q(user_groups__in=user_groups)) - return perms - - def get_perm_user(self): - user_id = self.kwargs.get('perm_user_id') - user = get_object_or_404(User, pk=user_id) - return user - - -class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin): - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset = self.filter_user_group_related(queryset) - queryset = queryset.distinct() - return queryset - - def filter_user_group_related(self, queryset): - user_group = self.get_perm_user_group() - perms = queryset.filter(user_groups=user_group) - return perms - - def get_perm_user_group(self): - user_group_id = self.kwargs.get('perm_user_group_id') - user_group = get_object_or_404(UserGroup, pk=user_group_id) - return user_group diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 4bfd778c4..a4175b58a 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -26,10 +26,11 @@ __all__ = [ class AssetFilterSet(BaseFilterSet): type = django_filters.CharFilter(field_name='platform__type', lookup_expr='exact') category = django_filters.CharFilter(field_name='platform__category', lookup_expr='exact') + hostname = django_filters.CharFilter(field_name='name', lookup_expr='exact') class Meta: model = Asset - fields = ['name', 'ip', 'is_active', 'type', 'category'] + fields = ['name', 'ip', 'is_active', 'type', 'category', 'hostname'] class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): @@ -39,7 +40,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): model = Asset filterset_class = AssetFilterSet search_fields = ("name", "ip") - ordering_fields = ("name", "ip", "port") + ordering_fields = ("name", "ip") ordering = ('name',) serializer_classes = ( ('default', serializers.AssetSerializer), @@ -58,21 +59,6 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): NodeFilterBackend ] - def set_assets_node(self, assets): - if not isinstance(assets, list): - assets = [assets] - node_id = self.request.query_params.get('node_id') - if not node_id: - return - node = get_object_or_none(Node, pk=node_id) - if not node: - return - node.assets.add(*assets) - - def perform_create(self, serializer): - assets = serializer.save() - self.set_assets_node(assets) - @action(methods=['GET'], detail=True, url_path='platform') def platform(self, *args, **kwargs): asset = self.get_object() diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index ebf289038..38e6c6c90 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -31,7 +31,7 @@ class Platform(models.Model): comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) # 资产有关的 charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) - domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enalbed")) + domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 64854b3f4..b9f57725c 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -69,20 +69,12 @@ class AssetSerializer(JMSWritableNestedModelSerializer): labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) - """ - 资产的数据结构 - """ + class Meta: model = Asset - fields_mini = [ - 'id', 'name', 'ip', - ] - fields_small = fields_mini + [ - 'is_active', 'comment', - ] - fields_fk = [ - 'domain', 'platform', 'platform', - ] + fields_mini = ['id', 'name', 'ip'] + fields_small = fields_mini + ['is_active', 'comment'] + fields_fk = ['domain', 'platform', 'platform'] fields_m2m = [ 'nodes', 'labels', 'accounts', 'protocols', 'nodes_display', ] @@ -94,9 +86,6 @@ class AssetSerializer(JMSWritableNestedModelSerializer): extra_kwargs = { 'name': {'label': _("Name")}, 'ip': {'label': _('IP/Host')}, - 'protocol': {'write_only': True}, - 'port': {'write_only': True}, - 'admin_user_display': {'label': _('Admin user display'), 'read_only': True}, } @classmethod @@ -121,6 +110,17 @@ class AssetSerializer(JMSWritableNestedModelSerializer): nodes_to_set.append(node) instance.nodes.set(nodes_to_set) + def validate_nodes(self, nodes): + print("Nodes: ", nodes) + if nodes: + return nodes + request = self.context.get('request') + if not request: + return [] + node_id = request.query_params.get('node_id') + if not node_id: + return [] + @atomic def create(self, validated_data): nodes_display = validated_data.pop('nodes_display', '') From f6fdc258b1f11bf9b669e24ce67ed949f1cf182f Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Sep 2022 15:47:40 +0800 Subject: [PATCH 116/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20playbook?= =?UTF-8?q?=20=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 2 +- apps/assets/models/asset/web.py | 4 ++ apps/assets/playbooks/__init__.py | 0 apps/assets/playbooks/platform/__init__.py | 69 ------------------- .../database/change_password_mysql/main.yml | 0 .../change_password_mysql/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../database/change_password_oracle/main.yml | 0 .../change_password_oracle/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../change_password_postgresql/main.yml | 0 .../change_password_postgresql/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../change_password_sqlserver/main.yml | 0 .../change_password_sqlserver/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../host/change_password_aix/main.yml | 0 .../host/change_password_aix/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../host/change_password_linux/main.yml | 0 .../host/change_password_linux/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../change_password_local_windows/main.yml | 0 .../manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../playbooks/platform/example/playbook.yml | 9 --- .../playbooks/platform/example/script.py | 6 -- .../host/ansible_posix_ping/main.yml | 0 .../host/ansible_posix_ping/manifest.yml | 0 .../host/ansible_win_ping/main.yml | 0 .../host/ansible_win_ping/manifest.yml | 0 31 files changed, 5 insertions(+), 85 deletions(-) delete mode 100644 apps/assets/playbooks/__init__.py delete mode 100644 apps/assets/playbooks/platform/__init__.py rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_mysql/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_mysql/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_mysql/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_oracle/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_oracle/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_oracle/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_postgresql/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_postgresql/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_postgresql/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_sqlserver/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_sqlserver/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/database/change_password_sqlserver/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_aix/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_aix/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_aix/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_linux/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_linux/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_linux/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_local_windows/main.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_local_windows/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => change_password}/host/change_password_local_windows/roles/change_password/tasks/main.yml (100%) delete mode 100644 apps/assets/playbooks/platform/example/playbook.yml delete mode 100644 apps/assets/playbooks/platform/example/script.py rename apps/assets/playbooks/platform/{ => verify_account}/host/ansible_posix_ping/main.yml (100%) rename apps/assets/playbooks/platform/{ => verify_account}/host/ansible_posix_ping/manifest.yml (100%) rename apps/assets/playbooks/platform/{ => verify_account}/host/ansible_win_ping/main.yml (100%) rename apps/assets/playbooks/platform/{ => verify_account}/host/ansible_win_ping/manifest.yml (100%) diff --git a/apps/assets/const.py b/apps/assets/const.py index 93b6032d3..1fafcda80 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -131,7 +131,7 @@ class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices): - General = 'general', 'General' + WEBSITE = 'website', _('General Website') class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): diff --git a/apps/assets/models/asset/web.py b/apps/assets/models/asset/web.py index f695525ac..34e23828a 100644 --- a/apps/assets/models/asset/web.py +++ b/apps/assets/models/asset/web.py @@ -6,3 +6,7 @@ from .common import Asset class Web(Asset): url = models.CharField(max_length=1024, verbose_name=_("url")) + + username_selector = models.CharField() + password_selector = models.CharField() + confirm_selector = models.CharField() diff --git a/apps/assets/playbooks/__init__.py b/apps/assets/playbooks/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/assets/playbooks/platform/__init__.py b/apps/assets/playbooks/platform/__init__.py deleted file mode 100644 index 3f6be47d7..000000000 --- a/apps/assets/playbooks/platform/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import yaml -from functools import partial - -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) - - -def check_platform_method(manifest, manifest_path): - required_keys = ['category', 'method', 'name', 'id', 'type'] - less_key = set(required_keys) - set(manifest.keys()) - if less_key: - raise ValueError("Manifest missing keys: {}, {}".format(less_key, manifest_path)) - if not isinstance(manifest['type'], list): - raise ValueError("Manifest type must be a list: {}".format(manifest_path)) - return True - - -def check_platform_methods(methods): - ids = [m['id'] for m in methods] - for i, _id in enumerate(ids): - if _id in ids[i+1:]: - raise ValueError("Duplicate id: {}".format(_id)) - - -def get_platform_methods(): - methods = [] - for root, dirs, files in os.walk(BASE_DIR, topdown=False): - for name in dirs: - path = os.path.join(root, name) - rel_path = path.replace(BASE_DIR, '.') - if len(rel_path.split('/')) != 3: - continue - - manifest_path = os.path.join(path, 'manifest.yml') - if not os.path.exists(manifest_path): - continue - - with open(manifest_path, 'r') as f: - manifest = yaml.safe_load(f) - check_platform_method(manifest, manifest_path) - methods.append(manifest) - - check_platform_methods(methods) - return methods - - -def filter_key(manifest, attr, value): - manifest_value = manifest.get(attr, '') - if isinstance(manifest_value, str): - manifest_value = [manifest_value] - return value in manifest_value or 'all' in manifest_value - - -def filter_platform_methods(category, tp, method): - methods = platform_ops_methods - if category: - methods = filter(partial(filter_key, attr='category', value=category), methods) - if tp: - methods = filter(partial(filter_key, attr='type', value=tp), methods) - if method: - methods = filter(lambda x: x['method'] == method, methods) - return methods - - -platform_ops_methods = get_platform_methods() - - -if __name__ == '__main__': - print(get_platform_methods()) diff --git a/apps/assets/playbooks/platform/database/change_password_mysql/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_mysql/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_mysql/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_mysql/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml b/apps/assets/playbooks/platform/change_password/database/change_password_mysql/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_mysql/manifest.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_mysql/manifest.yml diff --git a/apps/assets/playbooks/platform/database/change_password_mysql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_mysql/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_oracle/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_oracle/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_oracle/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_oracle/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml b/apps/assets/playbooks/platform/change_password/database/change_password_oracle/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_oracle/manifest.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_oracle/manifest.yml diff --git a/apps/assets/playbooks/platform/database/change_password_oracle/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_oracle/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_postgresql/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_postgresql/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_postgresql/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_postgresql/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml b/apps/assets/playbooks/platform/change_password/database/change_password_postgresql/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_postgresql/manifest.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_postgresql/manifest.yml diff --git a/apps/assets/playbooks/platform/database/change_password_postgresql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_postgresql/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_sqlserver/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_sqlserver/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/main.yml diff --git a/apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml b/apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_sqlserver/manifest.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/manifest.yml diff --git a/apps/assets/playbooks/platform/database/change_password_sqlserver/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/database/change_password_sqlserver/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/host/change_password_aix/main.yml b/apps/assets/playbooks/platform/change_password/host/change_password_aix/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_aix/main.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_aix/main.yml diff --git a/apps/assets/playbooks/platform/host/change_password_aix/manifest.yml b/apps/assets/playbooks/platform/change_password/host/change_password_aix/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_aix/manifest.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_aix/manifest.yml diff --git a/apps/assets/playbooks/platform/host/change_password_aix/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/change_password/host/change_password_aix/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_aix/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_aix/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/host/change_password_linux/main.yml b/apps/assets/playbooks/platform/change_password/host/change_password_linux/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_linux/main.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_linux/main.yml diff --git a/apps/assets/playbooks/platform/host/change_password_linux/manifest.yml b/apps/assets/playbooks/platform/change_password/host/change_password_linux/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_linux/manifest.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_linux/manifest.yml diff --git a/apps/assets/playbooks/platform/host/change_password_linux/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/change_password/host/change_password_linux/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_linux/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_linux/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/host/change_password_local_windows/main.yml b/apps/assets/playbooks/platform/change_password/host/change_password_local_windows/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_local_windows/main.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_local_windows/main.yml diff --git a/apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml b/apps/assets/playbooks/platform/change_password/host/change_password_local_windows/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_local_windows/manifest.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_local_windows/manifest.yml diff --git a/apps/assets/playbooks/platform/host/change_password_local_windows/roles/change_password/tasks/main.yml b/apps/assets/playbooks/platform/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/change_password_local_windows/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/platform/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/example/playbook.yml b/apps/assets/playbooks/platform/example/playbook.yml deleted file mode 100644 index 1e55a5007..000000000 --- a/apps/assets/playbooks/platform/example/playbook.yml +++ /dev/null @@ -1,9 +0,0 @@ -id: change_password_example -name: Change password example -category: host -method: change_password -vars: - account: - username: test - password: teset123 - public_key: test diff --git a/apps/assets/playbooks/platform/example/script.py b/apps/assets/playbooks/platform/example/script.py deleted file mode 100644 index 4cf9f7cf5..000000000 --- a/apps/assets/playbooks/platform/example/script.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python -# -""" -Will run with the args: -$0 $asset_json $account_json -""" diff --git a/apps/assets/playbooks/platform/host/ansible_posix_ping/main.yml b/apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/ansible_posix_ping/main.yml rename to apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/main.yml diff --git a/apps/assets/playbooks/platform/host/ansible_posix_ping/manifest.yml b/apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/host/ansible_posix_ping/manifest.yml rename to apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/manifest.yml diff --git a/apps/assets/playbooks/platform/host/ansible_win_ping/main.yml b/apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/main.yml similarity index 100% rename from apps/assets/playbooks/platform/host/ansible_win_ping/main.yml rename to apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/main.yml diff --git a/apps/assets/playbooks/platform/host/ansible_win_ping/manifest.yml b/apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/host/ansible_win_ping/manifest.yml rename to apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/manifest.yml From 910eaf1228d82228ecac61d9fad28802dd4fab09 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Sep 2022 19:07:45 +0800 Subject: [PATCH 117/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20web=20?= =?UTF-8?q?=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../change_password/database/change_password_mysql/main.yml | 0 .../change_password/database/change_password_mysql/manifest.yml | 0 .../change_password_mysql/roles/change_password/tasks/main.yml | 0 .../change_password/database/change_password_oracle/main.yml | 0 .../change_password/database/change_password_oracle/manifest.yml | 0 .../change_password_oracle/roles/change_password/tasks/main.yml | 0 .../change_password/database/change_password_postgresql/main.yml | 0 .../database/change_password_postgresql/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../change_password/database/change_password_sqlserver/main.yml | 0 .../database/change_password_sqlserver/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../change_password/host/change_password_aix/main.yml | 0 .../change_password/host/change_password_aix/manifest.yml | 0 .../host/change_password_aix/roles/change_password/tasks/main.yml | 0 .../change_password/host/change_password_linux/main.yml | 0 .../change_password/host/change_password_linux/manifest.yml | 0 .../change_password_linux/roles/change_password/tasks/main.yml | 0 .../change_password/host/change_password_local_windows/main.yml | 0 .../host/change_password_local_windows/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../verify_account/host/ansible_posix_ping/main.yml | 0 .../verify_account/host/ansible_posix_ping/manifest.yml | 0 .../{platform => }/verify_account/host/ansible_win_ping/main.yml | 0 .../verify_account/host/ansible_win_ping/manifest.yml | 0 25 files changed, 0 insertions(+), 0 deletions(-) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_mysql/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_mysql/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_oracle/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_oracle/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_postgresql/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_postgresql/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_sqlserver/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_sqlserver/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_aix/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_aix/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_aix/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_linux/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_linux/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_linux/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_local_windows/main.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_local_windows/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml (100%) rename apps/assets/playbooks/{platform => }/verify_account/host/ansible_posix_ping/main.yml (100%) rename apps/assets/playbooks/{platform => }/verify_account/host/ansible_posix_ping/manifest.yml (100%) rename apps/assets/playbooks/{platform => }/verify_account/host/ansible_win_ping/main.yml (100%) rename apps/assets/playbooks/{platform => }/verify_account/host/ansible_win_ping/manifest.yml (100%) diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_mysql/main.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_mysql/main.yml rename to apps/assets/playbooks/change_password/database/change_password_mysql/main.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_mysql/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_mysql/manifest.yml rename to apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_oracle/main.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_oracle/main.yml rename to apps/assets/playbooks/change_password/database/change_password_oracle/main.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_oracle/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_oracle/manifest.yml rename to apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_postgresql/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_postgresql/main.yml rename to apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_postgresql/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_postgresql/manifest.yml rename to apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/main.yml rename to apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/manifest.yml rename to apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml diff --git a/apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_aix/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_aix/main.yml rename to apps/assets/playbooks/change_password/host/change_password_aix/main.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_aix/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_aix/manifest.yml rename to apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_aix/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_aix/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_linux/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_linux/main.yml rename to apps/assets/playbooks/change_password/host/change_password_linux/main.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_linux/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_linux/manifest.yml rename to apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_linux/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_linux/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_local_windows/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_local_windows/main.yml rename to apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_local_windows/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_local_windows/manifest.yml rename to apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml diff --git a/apps/assets/playbooks/platform/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/platform/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml rename to apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/main.yml b/apps/assets/playbooks/verify_account/host/ansible_posix_ping/main.yml similarity index 100% rename from apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/main.yml rename to apps/assets/playbooks/verify_account/host/ansible_posix_ping/main.yml diff --git a/apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/manifest.yml b/apps/assets/playbooks/verify_account/host/ansible_posix_ping/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/verify_account/host/ansible_posix_ping/manifest.yml rename to apps/assets/playbooks/verify_account/host/ansible_posix_ping/manifest.yml diff --git a/apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/main.yml b/apps/assets/playbooks/verify_account/host/ansible_win_ping/main.yml similarity index 100% rename from apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/main.yml rename to apps/assets/playbooks/verify_account/host/ansible_win_ping/main.yml diff --git a/apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/manifest.yml b/apps/assets/playbooks/verify_account/host/ansible_win_ping/manifest.yml similarity index 100% rename from apps/assets/playbooks/platform/verify_account/host/ansible_win_ping/manifest.yml rename to apps/assets/playbooks/verify_account/host/ansible_win_ping/manifest.yml From 6bf7f7cb48b2a13f23539c19be90a381e0d52fb6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 9 Sep 2022 19:17:25 +0800 Subject: [PATCH 118/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 2 +- .../migrations/0112_auto_20220909_1907.py | 38 +++++++++++ apps/assets/models/asset/web.py | 8 +-- apps/assets/playbooks/__init__.py | 65 +++++++++++++++++++ apps/assets/serializers/asset/web.py | 5 +- apps/assets/urls/api_urls.py | 2 +- 6 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 apps/assets/migrations/0112_auto_20220909_1907.py create mode 100644 apps/assets/playbooks/__init__.py diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 16ef2ef15..12daa7b0a 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -6,7 +6,7 @@ from common.drf.serializers import GroupedChoiceSerailizer from assets.models import Platform from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer from assets.const import AllTypes, Category -from assets.playbooks.platform import filter_platform_methods +from assets.playbooks import filter_platform_methods __all__ = ['AssetPlatformViewSet'] diff --git a/apps/assets/migrations/0112_auto_20220909_1907.py b/apps/assets/migrations/0112_auto_20220909_1907.py new file mode 100644 index 000000000..6aaba7513 --- /dev/null +++ b/apps/assets/migrations/0112_auto_20220909_1907.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.14 on 2022-09-09 11:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0111_auto_20220908_1958'), + ] + + operations = [ + migrations.AddField( + model_name='web', + name='autofill', + field=models.CharField(default='basic', max_length=16), + ), + migrations.AddField( + model_name='web', + name='password_selector', + field=models.CharField(blank=True, default='', max_length=128), + ), + migrations.AddField( + model_name='web', + name='submit_selector', + field=models.CharField(blank=True, default='', max_length=128), + ), + migrations.AddField( + model_name='web', + name='username_selector', + field=models.CharField(blank=True, default='', max_length=128), + ), + migrations.AlterField( + model_name='platform', + name='domain_enabled', + field=models.BooleanField(default=True, verbose_name='Domain enabled'), + ), + ] diff --git a/apps/assets/models/asset/web.py b/apps/assets/models/asset/web.py index 34e23828a..5b66cc71b 100644 --- a/apps/assets/models/asset/web.py +++ b/apps/assets/models/asset/web.py @@ -6,7 +6,7 @@ from .common import Asset class Web(Asset): url = models.CharField(max_length=1024, verbose_name=_("url")) - - username_selector = models.CharField() - password_selector = models.CharField() - confirm_selector = models.CharField() + autofill = models.CharField(max_length=16, default='basic') + username_selector = models.CharField(max_length=128, blank=True, default='') + password_selector = models.CharField(max_length=128, blank=True, default='') + submit_selector = models.CharField(max_length=128, blank=True, default='') diff --git a/apps/assets/playbooks/__init__.py b/apps/assets/playbooks/__init__.py new file mode 100644 index 000000000..0acc08789 --- /dev/null +++ b/apps/assets/playbooks/__init__.py @@ -0,0 +1,65 @@ +import os +import yaml +from functools import partial + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def check_platform_method(manifest, manifest_path): + required_keys = ['category', 'method', 'name', 'id', 'type'] + less_key = set(required_keys) - set(manifest.keys()) + if less_key: + raise ValueError("Manifest missing keys: {}, {}".format(less_key, manifest_path)) + if not isinstance(manifest['type'], list): + raise ValueError("Manifest type must be a list: {}".format(manifest_path)) + return True + + +def check_platform_methods(methods): + ids = [m['id'] for m in methods] + for i, _id in enumerate(ids): + if _id in ids[i+1:]: + raise ValueError("Duplicate id: {}".format(_id)) + + +def get_platform_methods(): + methods = [] + for root, dirs, files in os.walk(BASE_DIR, topdown=False): + for name in files: + path = os.path.join(root, name) + if not path.endswith('manifest.yml'): + continue + + with open(path, 'r') as f: + manifest = yaml.safe_load(f) + check_platform_method(manifest, path) + manifest['dir'] = os.path.dirname(path) + methods.append(manifest) + + check_platform_methods(methods) + return methods + + +def filter_key(manifest, attr, value): + manifest_value = manifest.get(attr, '') + if isinstance(manifest_value, str): + manifest_value = [manifest_value] + return value in manifest_value or 'all' in manifest_value + + +def filter_platform_methods(category, tp, method): + methods = platform_ops_methods + if category: + methods = filter(partial(filter_key, attr='category', value=category), methods) + if tp: + methods = filter(partial(filter_key, attr='type', value=tp), methods) + if method: + methods = filter(lambda x: x['method'] == method, methods) + return methods + + +platform_ops_methods = get_platform_methods() + + +if __name__ == '__main__': + print(get_platform_methods()) diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py index fc726c68e..c553a8062 100644 --- a/apps/assets/serializers/asset/web.py +++ b/apps/assets/serializers/asset/web.py @@ -8,4 +8,7 @@ __all__ = ['WebSerializer'] class WebSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Web - fields = AssetSerializer.Meta.fields + ['url'] + fields = AssetSerializer.Meta.fields + [ + 'url', 'autofill', 'username_selector', + 'password_selector', 'submit_selector' + ] diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index d1b1455d6..6a233cf07 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -10,7 +10,7 @@ router = BulkRouter() router.register(r'assets', api.AssetViewSet, 'asset') router.register(r'hosts', api.HostViewSet, 'host') router.register(r'databases', api.DatabaseViewSet, 'database') -router.register(r'webs', api.WebViewSet, 'web') +router.register(r'web', api.WebViewSet, 'web') router.register(r'clouds', api.CloudViewSet, 'cloud') router.register(r'networks', api.NetworkViewSet, 'network') router.register(r'accounts', api.AccountViewSet, 'account') From 3e794ec41ac1b6086b68889c90367878e8092295 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 13 Sep 2022 10:41:49 +0800 Subject: [PATCH 119/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 6 +++--- apps/common/drf/serializers.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 12daa7b0a..ca6696629 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -2,10 +2,10 @@ from rest_framework.decorators import action from rest_framework.response import Response from common.drf.api import JMSModelViewSet -from common.drf.serializers import GroupedChoiceSerailizer +from common.drf.serializers import GroupedChoiceSerializer from assets.models import Platform from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer -from assets.const import AllTypes, Category +from assets.const import AllTypes from assets.playbooks import filter_platform_methods @@ -16,7 +16,7 @@ class AssetPlatformViewSet(JMSModelViewSet): queryset = Platform.objects.all() serializer_classes = { 'default': PlatformSerializer, - 'categories': GroupedChoiceSerailizer + 'categories': GroupedChoiceSerializer } filterset_fields = ['name', 'category', 'type'] search_fields = ['name'] diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index 627ef728c..ab6df10f7 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -14,6 +14,7 @@ __all__ = [ 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer', 'SecretReadableMixin', 'JMSWritableNestedModelSerializer', + 'GroupedChoiceSerializer', ] @@ -90,7 +91,7 @@ class ChoiceSerializer(serializers.Serializer): value = serializers.CharField(label=_("Value")) -class GroupedChoiceSerailizer(ChoiceSerializer): +class GroupedChoiceSerializer(ChoiceSerializer): children = ChoiceSerializer(many=True, label=_("Children")) From 9a734e7069904acec6ef25249702c3ccab3d4a58 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 13 Sep 2022 14:06:25 +0800 Subject: [PATCH 120/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/__init__.py | 5 +--- apps/assets/api/account/__init__.py | 4 ++++ .../api/{accounts.py => account/account.py} | 6 ++--- .../{account_backup.py => account/backup.py} | 6 ++--- .../history.py} | 6 ++--- .../template.py} | 0 .../migrations/0113_auto_20220913_1311.py | 24 +++++++++++++++++++ apps/assets/models/account.py | 9 ++++++- 8 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 apps/assets/api/account/__init__.py rename apps/assets/api/{accounts.py => account/account.py} (96%) rename apps/assets/api/{account_backup.py => account/backup.py} (93%) rename apps/assets/api/{account_history.py => account/history.py} (92%) rename apps/assets/api/{account_template.py => account/template.py} (100%) create mode 100644 apps/assets/migrations/0113_auto_20220913_1311.py diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 14fd38e9a..5dba09522 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -2,11 +2,8 @@ from .mixin import * from .platform import * from .asset import * from .label import * -from .accounts import * +from .account import * from .node import * from .domain import * from .gathered_user import * from .favorite_asset import * -from .account_template import * -from .account_backup import * -from .account_history import * diff --git a/apps/assets/api/account/__init__.py b/apps/assets/api/account/__init__.py new file mode 100644 index 000000000..6e402a550 --- /dev/null +++ b/apps/assets/api/account/__init__.py @@ -0,0 +1,4 @@ +from .account import * +from .backup import * +from .history import * +from .template import * diff --git a/apps/assets/api/accounts.py b/apps/assets/api/account/account.py similarity index 96% rename from apps/assets/api/accounts.py rename to apps/assets/api/account/account.py index 00f569357..3e73fd353 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/account/account.py @@ -9,9 +9,9 @@ from common.drf.filters import BaseFilterSet, UUIDInFilter from common.mixins import RecordViewLogMixin from common.permissions import UserConfirmation from authentication.const import ConfirmType -from ..tasks.account_connectivity import test_accounts_connectivity_manual -from ..models import Account, Node -from .. import serializers +from assets.tasks.account_connectivity import test_accounts_connectivity_manual +from assets.models import Account, Node +from assets import serializers __all__ = ['AccountFilterSet', 'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] diff --git a/apps/assets/api/account_backup.py b/apps/assets/api/account/backup.py similarity index 93% rename from apps/assets/api/account_backup.py rename to apps/assets/api/account/backup.py index bce4ce55b..79ae721f8 100644 --- a/apps/assets/api/account_backup.py +++ b/apps/assets/api/account/backup.py @@ -4,9 +4,9 @@ from rest_framework import status, viewsets from rest_framework.response import Response from orgs.mixins.api import OrgBulkModelViewSet -from .. import serializers -from ..tasks import execute_account_backup_plan -from ..models import ( +from assets import serializers +from assets.tasks import execute_account_backup_plan +from assets.models import ( AccountBackupPlan, AccountBackupPlanExecution ) diff --git a/apps/assets/api/account_history.py b/apps/assets/api/account/history.py similarity index 92% rename from apps/assets/api/account_history.py rename to apps/assets/api/account/history.py index 6ca4fd349..0db682177 100644 --- a/apps/assets/api/account_history.py +++ b/apps/assets/api/account/history.py @@ -1,9 +1,9 @@ -from assets.api.accounts import ( +from .account import ( AccountFilterSet, AccountViewSet, AccountSecretsViewSet ) from common.mixins import RecordViewLogMixin -from .. import serializers -from ..models import Account +from assets import serializers +from assets.models import Account __all__ = ['AccountHistoryViewSet', 'AccountHistorySecretsViewSet'] diff --git a/apps/assets/api/account_template.py b/apps/assets/api/account/template.py similarity index 100% rename from apps/assets/api/account_template.py rename to apps/assets/api/account/template.py diff --git a/apps/assets/migrations/0113_auto_20220913_1311.py b/apps/assets/migrations/0113_auto_20220913_1311.py new file mode 100644 index 000000000..d2d19483f --- /dev/null +++ b/apps/assets/migrations/0113_auto_20220913_1311.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-09-13 05:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0112_auto_20220909_1907'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='su_from', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from'), + ), + migrations.AddField( + model_name='historicalaccount', + name='su_from', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.account', verbose_name='Su from'), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 7b9e543a2..3a9f668bf 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -10,7 +10,14 @@ __all__ = ['Account', 'AccountTemplate'] class Account(BaseAccount): - asset = models.ForeignKey('assets.Asset', related_name='accounts', on_delete=models.CASCADE, verbose_name=_('Asset')) + asset = models.ForeignKey( + 'assets.Asset', related_name='accounts', + on_delete=models.CASCADE, verbose_name=_('Asset') + ) + su_from = models.ForeignKey( + 'assets.Account', related_name='su_to', null=True, + on_delete=models.SET_NULL, verbose_name=_("Su from") + ) version = models.IntegerField(default=0, verbose_name=_('Version')) history = HistoricalRecords() From a2c006f01b9dda7836ff7b74d444358d69027288 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 13 Sep 2022 15:41:39 +0800 Subject: [PATCH 121/488] =?UTF-8?q?perf:=20=E6=95=B4=E7=90=86=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E8=B5=84=E4=BA=A7=E7=9A=84API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/mixins/api/permission.py | 3 +- apps/perms/api/user_permission/assets/api.py | 86 ++++++++----------- .../perms/api/user_permission/assets/mixin.py | 4 + apps/perms/api/user_permission/mixin.py | 10 +-- apps/perms/urls/asset_permission.py | 8 +- 5 files changed, 52 insertions(+), 59 deletions(-) diff --git a/apps/common/mixins/api/permission.py b/apps/common/mixins/api/permission.py index 64efbe7f5..3a57658c3 100644 --- a/apps/common/mixins/api/permission.py +++ b/apps/common/mixins/api/permission.py @@ -27,7 +27,6 @@ class RoleAdminMixin: user_id = self.kwargs.get(self.user_id_url_kwarg) if hasattr(self, 'swagger_fake_view') and not user_id: return self.request.user # NOQA - user_model = get_user_model() return user_model.objects.get(id=user_id) @@ -37,4 +36,4 @@ class RoleUserMixin: @lazyproperty def user(self): - return self.request.user \ No newline at end of file + return self.request.user diff --git a/apps/perms/api/user_permission/assets/api.py b/apps/perms/api/user_permission/assets/api.py index 3808cf7fc..9c616adc3 100644 --- a/apps/perms/api/user_permission/assets/api.py +++ b/apps/perms/api/user_permission/assets/api.py @@ -9,57 +9,52 @@ from .mixin import ( ) __all__ = [ - 'UserDirectGrantedAssetsForAdminApi', 'MyDirectGrantedAssetsApi', - 'UserFavoriteGrantedAssetsForAdminApi', - 'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeForAdminApi', + 'UserDirectGrantedAssetsApi', 'MyDirectGrantedAssetsApi', + 'UserFavoriteGrantedAssetsApi', + 'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeApi', 'MyUngroupAssetsAsTreeApi', 'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi', - 'UserGrantedNodeAssetsForAdminApi', + 'UserGrantedNodeAssetsApi', 'MyGrantedNodeAssetsApi', ] logger = get_logger(__name__) -class UserDirectGrantedAssetsForAdminApi(UserDirectGrantedAssetsQuerysetMixin, - AssetRoleAdminMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class UserDirectGrantedAssetsApi( + AssetRoleAdminMixin, + UserDirectGrantedAssetsQuerysetMixin, AssetsSerializerFormatMixin, ListAPIView +): + """ 直接授权给用户的资产 """ pass -class MyDirectGrantedAssetsApi(UserDirectGrantedAssetsQuerysetMixin, - AssetRoleUserMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class MyDirectGrantedAssetsApi(AssetRoleUserMixin, UserDirectGrantedAssetsApi): + """ 直接授权给我的资产 """ pass -class UserFavoriteGrantedAssetsForAdminApi(UserFavoriteGrantedAssetsMixin, - AssetRoleAdminMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class UserFavoriteGrantedAssetsApi( + AssetRoleAdminMixin, + UserFavoriteGrantedAssetsMixin, AssetsSerializerFormatMixin, ListAPIView +): + """ 用户收藏的授权资产 """ pass -class MyFavoriteGrantedAssetsApi(UserFavoriteGrantedAssetsMixin, - AssetRoleUserMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class MyFavoriteGrantedAssetsApi(AssetRoleUserMixin, UserFavoriteGrantedAssetsApi): + """ 我收藏的授权资产 """ pass -class UserDirectGrantedAssetsAsTreeForAdminApi(UserDirectGrantedAssetsQuerysetMixin, - AssetRoleAdminMixin, - AssetsTreeFormatMixin, - ListAPIView): +class UserDirectGrantedAssetsAsTreeApi(AssetsTreeFormatMixin, UserDirectGrantedAssetsApi): + """ 用户直接授权的资产作为树 """ pass -class MyUngroupAssetsAsTreeApi(UserDirectGrantedAssetsQuerysetMixin, - AssetRoleUserMixin, - AssetsTreeFormatMixin, - ListAPIView): +class MyUngroupAssetsAsTreeApi(AssetRoleUserMixin, UserDirectGrantedAssetsAsTreeApi): + """ 我的未分组节点下的资产作为树 """ + def get_queryset(self): queryset = super().get_queryset() if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: @@ -67,36 +62,31 @@ class MyUngroupAssetsAsTreeApi(UserDirectGrantedAssetsQuerysetMixin, return queryset -class UserAllGrantedAssetsApi(UserAllGrantedAssetsQuerysetMixin, - AssetRoleAdminMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class UserAllGrantedAssetsApi( + AssetRoleAdminMixin, + UserAllGrantedAssetsQuerysetMixin, AssetsSerializerFormatMixin, ListAPIView +): + """ 授权给用户的所有资产 """ pass -class MyAllGrantedAssetsApi(UserAllGrantedAssetsQuerysetMixin, - AssetRoleUserMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class MyAllGrantedAssetsApi(AssetRoleUserMixin, UserAllGrantedAssetsApi): + """ 授权给我的所有资产 """ pass -class MyAllAssetsAsTreeApi(UserAllGrantedAssetsQuerysetMixin, - AssetRoleUserMixin, - AssetsTreeFormatMixin, - ListAPIView): +class MyAllAssetsAsTreeApi(AssetsTreeFormatMixin, MyAllGrantedAssetsApi): + """ 授权给我的所有资产作为树 """ pass -class UserGrantedNodeAssetsForAdminApi(AssetRoleAdminMixin, - UserGrantedNodeAssetsMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class UserGrantedNodeAssetsApi( + AssetRoleAdminMixin, UserGrantedNodeAssetsMixin, AssetsSerializerFormatMixin, ListAPIView +): + """ 授权给用户的节点资产 """ pass -class MyGrantedNodeAssetsApi(AssetRoleUserMixin, - UserGrantedNodeAssetsMixin, - AssetsSerializerFormatMixin, - ListAPIView): +class MyGrantedNodeAssetsApi(AssetRoleUserMixin, UserGrantedNodeAssetsApi): + """ 授权给我的节点资产 """ pass diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index 096bd21b3..5e123091a 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -64,6 +64,7 @@ class UserGrantedNodeAssetsMixin: pagination_class = NodeGrantedAssetPagination pagination_node: Node user: User + kwargs: dict def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -91,6 +92,9 @@ class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): """ 将 资产 序列化成树的结构返回 """ + filter_queryset: callable + get_queryset: callable + filterset_fields = ['name', 'ip', 'id', 'comment'] search_fields = ['name', 'ip', 'comment'] diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index c53f548e5..da9691f38 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -3,14 +3,14 @@ from rest_framework.request import Request from common.http import is_true -from common.mixins.api import RoleAdminMixin as _RoleAdminMixin -from common.mixins.api import RoleUserMixin as _RoleUserMixin +from common.mixins.api import RoleAdminMixin +from common.mixins.api import RoleUserMixin from orgs.utils import tmp_to_root_org from users.models import User from perms.utils.user_permission import UserGrantedTreeRefreshController -class PermBaseMixin: +class RebuildTreeMixin: user: User def get(self, request: Request, *args, **kwargs): @@ -20,7 +20,7 @@ class PermBaseMixin: return super().get(request, *args, **kwargs) -class AssetRoleAdminMixin(PermBaseMixin, _RoleAdminMixin): +class AssetRoleAdminMixin(RebuildTreeMixin, RoleAdminMixin): rbac_perms = ( ('list', 'perms.view_userassets'), ('retrieve', 'perms.view_userassets'), @@ -29,7 +29,7 @@ class AssetRoleAdminMixin(PermBaseMixin, _RoleAdminMixin): ) -class AssetRoleUserMixin(PermBaseMixin, _RoleUserMixin): +class AssetRoleUserMixin(RebuildTreeMixin, RoleUserMixin): rbac_perms = ( ('list', 'perms.view_myassets'), ('retrieve', 'perms.view_myassets'), diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index a00adffbe..7099aa137 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -24,7 +24,7 @@ user_permission_urlpatterns = [ path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'), # Tree Node 的数据格式返回 - path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeForAdminApi.as_view(), name='user-assets-as-tree'), + path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'), path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'), path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'), # ^--------------------------------------------------------^ @@ -60,15 +60,15 @@ user_permission_urlpatterns = [ path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'), # 查询授权树上某个节点的所有资产 - path('/nodes//assets/', api.UserGrantedNodeAssetsForAdminApi.as_view(), name='user-node-assets'), + path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), path('nodes//assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'), # 未分组的资产 - path('/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsForAdminApi.as_view(), name='user-ungrouped-assets'), + path('/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), # 收藏的资产 - path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsForAdminApi.as_view(), name='user-ungrouped-assets'), + path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), # Todo: 删除 From 4fcbdfa3f40ed8be26968b1f53d3ecf968688377 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 13 Sep 2022 21:07:20 +0800 Subject: [PATCH 122/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20account=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/template.py | 4 ++-- .../assets/migrations/0099_auto_20220711_1409.py | 4 +++- .../assets/migrations/0100_auto_20220711_1413.py | 3 ++- apps/assets/models/account.py | 9 ++++----- apps/assets/serializers/account/account.py | 16 +++++++++++++--- apps/assets/serializers/asset/common.py | 7 +++++-- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/assets/api/account/template.py b/apps/assets/api/account/template.py index b88fcd0f6..c04fd8ab6 100644 --- a/apps/assets/api/account/template.py +++ b/apps/assets/api/account/template.py @@ -1,6 +1,6 @@ from orgs.mixins.api import OrgBulkModelViewSet -from ..models import AccountTemplate -from .. import serializers +from assets.models import AccountTemplate +from assets import serializers class AccountTemplateViewSet(OrgBulkModelViewSet): diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index 52c8750dc..0934080b2 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -21,6 +21,7 @@ class Migration(migrations.Migration): fields=[ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), + ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), @@ -52,6 +53,7 @@ class Migration(migrations.Migration): fields=[ ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), @@ -68,7 +70,7 @@ class Migration(migrations.Migration): options={ 'verbose_name': 'Account', 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], - 'unique_together': {('username', 'asset')}, + 'unique_together': {('username', 'asset'), ('name', 'asset')}, }, ), ] diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py index 7aad60e2c..7f2e7eca7 100644 --- a/apps/assets/migrations/0100_auto_20220711_1413.py +++ b/apps/assets/migrations/0100_auto_20220711_1413.py @@ -40,7 +40,7 @@ def migrate_accounts(apps, schema_editor): values['version'] = 1 system_user = auth_book.systemuser - if auth_book.systemuser: + if system_user: values.update({attr: getattr(system_user, attr) for attr in auth_attrs}) values['created_by'] = str(system_user.id) values['privileged'] = system_user.type == 'admin' @@ -48,6 +48,7 @@ def migrate_accounts(apps, schema_editor): auth_book_auth = {attr: getattr(auth_book, attr) for attr in auth_attrs} auth_book_auth = {attr: value for attr, value in auth_book_auth.items() if value} values.update(auth_book_auth) + values['name'] = values['username'] account = account_model(**values) accounts.append(account) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 3a9f668bf..ad39612ae 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -23,7 +23,10 @@ class Account(BaseAccount): class Meta: verbose_name = _('Account') - unique_together = [('username', 'asset')] + unique_together = [ + ('username', 'asset'), + ('name', 'asset'), + ] permissions = [ ('view_accountsecret', _('Can view asset account secret')), ('change_accountsecret', _('Can change asset account secret')), @@ -31,10 +34,6 @@ class Account(BaseAccount): ('view_historyaccountsecret', _('Can view asset history account secret')), ] - @property - def name(self): - return "{}({})_{}".format(self.asset_name, self.ip, self.username) - @lazyproperty def ip(self): return self.asset.ip diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 1c60e5d98..fa245f07e 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -14,7 +14,9 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): required=False, allow_null=True, write_only=True, label=_('Account template') ) - push_to_asset = serializers.BooleanField(default=False, label=_("Push to asset"), write_only=True) + push_now = serializers.BooleanField( + default=False, label=_("Push now"), write_only=True + ) @staticmethod def validate_template(value): @@ -39,9 +41,17 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): account_template = attrs.pop('template', None) if account_template: self.replace_attrs(account_template, attrs) - push_to_asset = attrs.pop('push_to_asset', False) + self.push_now = attrs.pop('push_now', False) return super().validate(attrs) + def create(self, validated_data): + instance = super().create(validated_data) + if self.push_now: + print("Start push account to asset") + # Todo: push it + pass + return instance + class AccountSerializer(AuthValidateMixin, AccountSerializerCreateMixin, @@ -55,7 +65,7 @@ class AccountSerializer(AuthValidateMixin, class Meta(AccountFieldsSerializerMixin.Meta): model = Account fields = AccountFieldsSerializerMixin.Meta.fields \ - + ['template', 'push_to_asset'] + + ['template', 'push_now'] @classmethod def setup_eager_loading(cls, queryset): diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index b9f57725c..2717547cd 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -54,9 +54,12 @@ class AssetAccountSerializer(AccountSerializer): class Meta(AccountSerializer.Meta): fields_mini = [ - 'id', 'name', 'username', 'privileged', 'version' + 'id', 'name', 'username', 'privileged', 'version', + ] + fields_write_only = [ + 'password', 'private_key', 'public_key', + 'passphrase', 'token', 'push_now' ] - fields_write_only = ['password', 'private_key', 'public_key', 'passphrase', 'token'] fields = fields_mini + fields_write_only From ae189ebdfeb58db7d839863a51d97f664c46ff13 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 13 Sep 2022 21:18:04 +0800 Subject: [PATCH 123/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20account=20?= =?UTF-8?q?serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/account/account.py | 7 +++---- apps/assets/serializers/account/common.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index fa245f07e..44438c5eb 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -4,7 +4,8 @@ from rest_framework import serializers from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin -from assets.models import Account, AccountTemplate +from common.drf.fields import ObjectRelatedField +from assets.models import Account, AccountTemplate, Asset from assets.serializers.base import AuthValidateMixin from .common import AccountFieldsSerializerMixin @@ -57,9 +58,7 @@ class AccountSerializer(AuthValidateMixin, AccountSerializerCreateMixin, AccountFieldsSerializerMixin, BulkOrgResourceModelSerializer): - name = serializers.CharField(max_length=128, read_only=True, label=_("Name")) - ip = serializers.ReadOnlyField(label=_("IP")) - asset_name = serializers.ReadOnlyField(label=_("Asset")) + asset = ObjectRelatedField(required=False, queryset=Asset.objects, label=_('Asset'), attrs=('id', 'name', 'ip')) platform = serializers.ReadOnlyField(label=_("Platform")) class Meta(AccountFieldsSerializerMixin.Meta): diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py index 43c5c193b..6e8022aa7 100644 --- a/apps/assets/serializers/account/common.py +++ b/apps/assets/serializers/account/common.py @@ -8,8 +8,8 @@ __all__ = ['AccountFieldsSerializerMixin'] class AccountFieldsSerializerMixin(serializers.ModelSerializer): class Meta: fields_mini = [ - 'id', 'name', 'username', 'privileged', 'ip', - 'asset_name', 'platform', 'version' + 'id', 'name', 'username', 'privileged', + 'platform', 'version' ] fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] fields_other = ['date_created', 'date_updated', 'comment'] From 37bbf75f6652719d9e2b8a83526a15f8f1ff8ba6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 14 Sep 2022 20:55:14 +0800 Subject: [PATCH 124/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=BF=81=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 52 +++++++++---- apps/assets/migrations/0092_add_host.py | 31 +------- .../migrations/0093_auto_20220403_1627.py | 70 ++++++++---------- .../migrations/0096_auto_20220426_1550.py | 4 + .../migrations/0098_auto_20220430_2126.py | 30 ++++++-- .../migrations/0099_auto_20220711_1409.py | 4 +- .../migrations/0103_auto_20220811_1511.py | 5 ++ .../0107_alter_accountbackupplan_types.py | 49 +----------- .../migrations/0108_auto_20220901_1034.py | 37 ---------- .../migrations/0109_auto_20220901_1431.py | 30 -------- .../migrations/0110_auto_20220901_1542.py | 43 ----------- .../migrations/0111_auto_20220908_1958.py | 29 -------- .../migrations/0112_auto_20220909_1907.py | 38 ---------- .../migrations/0113_auto_20220913_1311.py | 24 ------ apps/assets/models/account.py | 1 - apps/assets/models/asset/database.py | 1 - apps/assets/models/asset/networking.py | 1 + apps/assets/models/backup.py | 2 +- apps/assets/models/platform.py | 74 +++---------------- apps/assets/models/utils.py | 52 ++++++++++++- .../host/change_password_linux/main.yml | 2 +- apps/assets/serializers/account/account.py | 4 +- ...ectstra.py => 0023_automation_strategy.py} | 2 +- 23 files changed, 172 insertions(+), 413 deletions(-) delete mode 100644 apps/assets/migrations/0108_auto_20220901_1034.py delete mode 100644 apps/assets/migrations/0109_auto_20220901_1431.py delete mode 100644 apps/assets/migrations/0110_auto_20220901_1542.py delete mode 100644 apps/assets/migrations/0111_auto_20220908_1958.py delete mode 100644 apps/assets/migrations/0112_auto_20220909_1907.py delete mode 100644 apps/assets/migrations/0113_auto_20220913_1311.py rename apps/ops/migrations/{0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py => 0023_automation_strategy.py} (99%) diff --git a/apps/assets/const.py b/apps/assets/const.py index 1fafcda80..ca33a93bb 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -38,18 +38,19 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): return { cls.HOST: { 'domain_enabled': True, - 'su_enabled': True, - 'ping_enabled': True, - 'gather_facts_enabled': True, - 'verify_account_enabled': True, - 'change_password_enabled': True, - 'create_account_enabled': True, - 'gather_accounts_enabled': True, - '_protocols': ['ssh', 'sftp'] + 'su_enabled': True, 'su_method': 'sudo', + 'ping_enabled': True, 'ping_method': 'ping', + 'gather_facts_enabled': True, 'gather_facts_method': 'gather_facts_posix', + 'verify_account_enabled': True, 'verify_account_method': 'verify_account_posix', + 'change_password_enabled': True, 'change_password_method': 'change_password_posix', + 'create_account_enabled': True, 'create_account_method': 'create_account_posix', + 'gather_accounts_enabled': True, 'gather_accounts_method': 'gather_accounts_posix', + '_protocols': ['ssh', 'telnet'], }, cls.NETWORKING: { 'domain_enabled': True, 'su_enabled': False, + 'ping_enabled': True, 'ping_method': 'ping', 'gather_facts_enabled': False, 'verify_account_enabled': False, 'change_password_enabled': False, @@ -65,15 +66,28 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): 'change_password_enabled': True, 'create_account_enabled': True, 'gather_accounts_enabled': True, + '_protocols': [] }, cls.WEB: { 'domain_enabled': False, 'su_enabled': False, + 'ping_enabled': False, + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, '_protocols': ['http', 'https'] }, cls.CLOUD: { 'domain_enabled': False, 'su_enabled': False, + 'ping_enabled': False, + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, '_protocols': [] } } @@ -83,9 +97,6 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): LINUX = 'linux', 'Linux' WINDOWS = 'windows', 'Windows' UNIX = 'unix', 'Unix' - BSD = 'bsd', 'BSD' - MACOS = 'macos', 'MacOS' - MAINFRAME = 'mainframe', _("Mainframe") OTHER_HOST = 'other_host', _("Other host") @classmethod @@ -95,20 +106,26 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] }, cls.WINDOWS: { - '_protocols': ['ssh', 'rdp', 'vnc'], + 'gather_facts_method': 'gather_facts_windows', + 'verify_account_method': 'verify_account_windows', + 'change_password_method': 'change_password_windows', + 'create_account_method': 'create_account_windows', + 'gather_accounts_method': 'gather_accounts_windows', + '_protocols': ['rdp', 'ssh', 'vnc'], 'su_enabled': False }, - cls.MACOS: { + cls.UNIX: { '_protocols': ['ssh', 'vnc'] } } class NetworkingTypes(PlatformMixin, ChoicesMixin, models.TextChoices): + GENERAL = 'general', _("General device") SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") FIREWALL = 'firewall', _("Firewall") - OTHER_NETWORK = 'other_network', _("Other device") + OTHER_NETWORK = 'other', _("Other device") class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): @@ -125,7 +142,12 @@ class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): meta = {} for name, label in cls.choices: meta[name] = { - '_protocols': [name] + '_protocols': [name], + 'gather_facts_method': f'gather_facts_{name}', + 'verify_account_method': f'verify_account_{name}', + 'change_password_method': f'change_password_{name}', + 'create_account_method': f'create_account_{name}', + 'gather_accounts_method': f'gather_accounts_{name}', } return meta diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index 3064d6b98..92e6aad69 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -12,38 +12,15 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='DeviceInfo', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('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')), - ('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')), - ('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')), - ('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')), - ('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')), - ('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')), - ('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')), - ('cpu_vcpus', models.IntegerField(null=True, verbose_name='CPU vcpus')), - ('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')), - ('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')), - ('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')), - ('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')), - ('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')), - ('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')), - ('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')), - ('number', models.CharField(blank=True, max_length=128, null=True, verbose_name='Asset number')), - ], - options={ - 'verbose_name': 'DeviceInfo', - }, + migrations.AddField( + model_name='asset', + name='info', + field=models.JSONField(blank=True, default=dict, verbose_name='Info'), ), migrations.CreateModel( name='Host', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('device_info', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.deviceinfo', verbose_name='Host')), ], ), ] diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py index 3e2ff9b84..b013efdfe 100644 --- a/apps/assets/migrations/0093_auto_20220403_1627.py +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -4,46 +4,6 @@ from django.utils import timezone from django.db import migrations, models -def migrate_hardware(apps, *args): - host_model = apps.get_model('assets', 'Host') - asset_model = apps.get_model('assets', 'Asset') - hardware_model = apps.get_model('assets', 'DeviceInfo') - - created = 0 - batch_size = 1000 - - excludes = ['id', 'host', 'date_updated'] - fields = [f.name for f in hardware_model._meta.fields] - fields = [name for name in fields if name not in excludes] - - while True: - start = created - end = created + batch_size - hosts = host_model.objects.all()[start:end] - asset_ids = [h.asset_ptr_id for h in hosts] - assets = asset_model.objects.filter(id__in=asset_ids) - asset_mapper = {a.id: a for a in assets} - - if not hosts: - break - - hardware_infos = [] - hosts_updated = [] - for host in hosts: - hardware = hardware_model() - asset = asset_mapper[host.asset_ptr_id] - hardware.date_updated = timezone.now() - for name in fields: - setattr(hardware, name, getattr(asset, name)) - hardware_infos.append(hardware) - host.device_info_id = hardware.id - hosts_updated.append(host) - - hardware_model.objects.bulk_create(hardware_infos, ignore_conflicts=True) - host_model.objects.bulk_update(hosts_updated, ['device_info_id']) - created += len(hardware_infos) - - def migrate_to_host(apps, schema_editor): asset_model = apps.get_model("assets", "Asset") host_model = apps.get_model("assets", 'Host') @@ -64,6 +24,34 @@ def migrate_to_host(apps, schema_editor): created += len(hosts) +def migrate_hardware_info(apps, *args): + asset_model = apps.get_model("assets", "Asset") + + count = 0 + batch_size = 1000 + hardware_fields = [ + 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', 'cpu_cores', + 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', 'os', 'os_arch', + 'os_version', 'hostname_raw', 'number' + ] + + while True: + start = count + end = count + batch_size + assets = asset_model.objects.all()[start:end] + if not assets: + break + + updated = [] + for asset in assets: + info = {getattr(asset, field) for field in hardware_fields if getattr(asset, field)} + if not info: + continue + asset.info = info + updated.append(asset) + asset_model.objects.bulk_update(updated, ['info']) + + class Migration(migrations.Migration): dependencies = [ @@ -71,6 +59,6 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RunPython(migrate_hardware_info), migrations.RunPython(migrate_to_host), - migrations.RunPython(migrate_hardware), ] diff --git a/apps/assets/migrations/0096_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py index 83b2aa141..bf7a93ac3 100644 --- a/apps/assets/migrations/0096_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -48,6 +48,10 @@ class Migration(migrations.Migration): fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), ('url', models.CharField(max_length=1024, verbose_name='url')), + ('autofill', models.CharField(default='basic', max_length=16)), + ('password_selector', models.CharField(blank=True, default='', max_length=128)), + ('submit_selector', models.CharField(blank=True, default='', max_length=128)), + ('username_selector', models.CharField(blank=True, default='', max_length=128)) ], options={ 'abstract': False, diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index fd3e27ba0..b52e5026f 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -1,6 +1,5 @@ # Generated by Django 3.1.14 on 2022-04-30 14:41 from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -19,11 +18,6 @@ class Migration(migrations.Migration): ('setting', models.JSONField(default=dict, verbose_name='Setting')), ], ), - migrations.AddField( - model_name='platform', - name='domain_default', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.domain', verbose_name='Domain default'), - ), migrations.AddField( model_name='platform', name='domain_enabled', @@ -62,12 +56,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='ping_enabled', - field=models.BooleanField(default=False), + field=models.BooleanField(default=False, verbose_name='Ping enabled'), ), migrations.AddField( model_name='platform', name='ping_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Ping method'), + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method'), ), migrations.AddField( model_name='platform', @@ -89,4 +83,24 @@ class Migration(migrations.Migration): name='verify_account_method', field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method'), ), + migrations.AddField( + model_name='platform', + name='gather_accounts_enabled', + field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), + ), + migrations.AddField( + model_name='platform', + name='gather_accounts_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), + ), + migrations.AddField( + model_name='platform', + name='gather_facts_enabled', + field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), + ), + migrations.AddField( + model_name='platform', + name='gather_facts_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), + ), ] diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index 0934080b2..af767c864 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -39,6 +39,7 @@ class Migration(migrations.Migration): ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), ('asset', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.asset', verbose_name='Asset')), ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('su_from', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.account', verbose_name='Su from')), ], options={ 'verbose_name': 'historical Account', @@ -65,7 +66,8 @@ class Migration(migrations.Migration): ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged account')), ('version', models.IntegerField(default=0, verbose_name='Version')), - ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset')), + ('su_from', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from')), ], options={ 'verbose_name': 'Account', diff --git a/apps/assets/migrations/0103_auto_20220811_1511.py b/apps/assets/migrations/0103_auto_20220811_1511.py index 3a4504e46..f9dda2b56 100644 --- a/apps/assets/migrations/0103_auto_20220811_1511.py +++ b/apps/assets/migrations/0103_auto_20220811_1511.py @@ -41,6 +41,11 @@ class Migration(migrations.Migration): old_name='hostname', new_name='name', ), + migrations.AlterField( + model_name='asset', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), migrations.AddField( model_name='asset', name='date_updated', diff --git a/apps/assets/migrations/0107_alter_accountbackupplan_types.py b/apps/assets/migrations/0107_alter_accountbackupplan_types.py index 2ce2d9302..d2d0eedb2 100644 --- a/apps/assets/migrations/0107_alter_accountbackupplan_types.py +++ b/apps/assets/migrations/0107_alter_accountbackupplan_types.py @@ -1,44 +1,10 @@ -# Generated by Django 3.2.13 on 2022-08-29 11:46 +# Generated by Django 3.2.14 on 2022-09-14 12:45 + from django.db import migrations, models -from assets.const import Category -from assets.models import Type - - -def update_account_backup_type(apps, schema_editor): - backup_model = apps.get_model('assets', 'AccountBackupPlan') - all_number = 4294967295 - asset_number = Type.choices_to_value([Category.HOST]) - app_number = Type.choices_to_value([ - Category.NETWORKING, Category.DATABASE, Category.CLOUD, Category.WEB] - ) - - backup_model.objects.filter(types=255).update(types=all_number) - backup_model.objects.filter(types=1).update(types=asset_number) - backup_model.objects.filter(types=2).update(types=app_number) - - backup_execution_model = apps.get_model('assets', 'AccountBackupPlanExecution') - choices_dict = { - 'all': Type.get_types(value=all_number), - 'asset': Type.get_types(value=asset_number), - 'app': Type.get_types(value=app_number) - } - qs_dict = { - 'all': backup_execution_model.objects.filter(plan__types=255), - 'asset': backup_execution_model.objects.filter(plan__types=1), - 'app': backup_execution_model.objects.filter(plan__types=2) - } - - backup_executions = [] - for k, qs in qs_dict.items(): - type_choices = choices_dict[k] - for i in qs: - i.plan_snapshot['types'] = type_choices - backup_executions.append(i) - backup_execution_model.objects.bulk_update(backup_executions, fields=['plan_snapshot']) - class Migration(migrations.Migration): + dependencies = [ ('assets', '0106_auto_20220819_1523'), ] @@ -47,13 +13,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='accountbackupplan', name='types', - field=models.BigIntegerField( - choices=[(4294967295, 'All'), (1, 'Linux'), (2, 'Windows'), (4, 'Unix'), (8, 'BSD'), (16, 'MacOS'), - (32, 'Mainframe'), (64, 'Other host'), (127, 'Host'), (128, 'Switch'), (256, 'Router'), - (512, 'Firewall'), (1024, 'Other device'), (1920, 'NetworkDevice'), (2048, 'MySQL'), - (4096, 'MariaDB'), (8192, 'PostgreSQL'), (16384, 'Oracle'), (32768, 'SQLServer'), - (65536, 'MongoDB'), (131072, 'Redis'), (260096, 'Database'), (262144, 'Clouding'), - (524288, 'Web')], default=4294967295, verbose_name='Type'), + field=models.BigIntegerField(), ), - migrations.RunPython(update_account_backup_type), ] diff --git a/apps/assets/migrations/0108_auto_20220901_1034.py b/apps/assets/migrations/0108_auto_20220901_1034.py deleted file mode 100644 index d19749cf7..000000000 --- a/apps/assets/migrations/0108_auto_20220901_1034.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-01 02:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0107_alter_accountbackupplan_types'), - ] - - operations = [ - migrations.RemoveField( - model_name='platform', - name='create_account_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='create_account_method', - ), - migrations.RemoveField( - model_name='platform', - name='domain_default', - ), - migrations.RemoveField( - model_name='platform', - name='domain_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='ping_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='ping_method', - ), - ] diff --git a/apps/assets/migrations/0109_auto_20220901_1431.py b/apps/assets/migrations/0109_auto_20220901_1431.py deleted file mode 100644 index 07fad818e..000000000 --- a/apps/assets/migrations/0109_auto_20220901_1431.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-01 06:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0108_auto_20220901_1034'), - ] - - operations = [ - migrations.RemoveField( - model_name='host', - name='device_info', - ), - migrations.AddField( - model_name='asset', - name='info', - field=models.JSONField(blank=True, default=dict, verbose_name='Info'), - ), - migrations.AddField( - model_name='database', - name='version', - field=models.CharField(blank=True, max_length=16, verbose_name='Version'), - ), - migrations.DeleteModel( - name='DeviceInfo', - ), - ] diff --git a/apps/assets/migrations/0110_auto_20220901_1542.py b/apps/assets/migrations/0110_auto_20220901_1542.py deleted file mode 100644 index 1c37f19dd..000000000 --- a/apps/assets/migrations/0110_auto_20220901_1542.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-01 07:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0109_auto_20220901_1431'), - ] - - operations = [ - migrations.AddField( - model_name='platform', - name='create_account_enabled', - field=models.BooleanField(default=False, verbose_name='Create account enabled'), - ), - migrations.AddField( - model_name='platform', - name='create_account_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method'), - ), - migrations.AddField( - model_name='platform', - name='gather_accounts_enabled', - field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), - ), - migrations.AddField( - model_name='platform', - name='gather_accounts_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), - ), - migrations.AddField( - model_name='platform', - name='gather_facts_enabled', - field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), - ), - migrations.AddField( - model_name='platform', - name='gather_facts_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), - ), - ] diff --git a/apps/assets/migrations/0111_auto_20220908_1958.py b/apps/assets/migrations/0111_auto_20220908_1958.py deleted file mode 100644 index 7fb913fae..000000000 --- a/apps/assets/migrations/0111_auto_20220908_1958.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-08 11:58 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0110_auto_20220901_1542'), - ] - - operations = [ - migrations.AddField( - model_name='platform', - name='domain_enabled', - field=models.BooleanField(default=True, verbose_name='Domain enalbed'), - ), - migrations.AlterField( - model_name='account', - name='asset', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset'), - ), - migrations.AlterField( - model_name='asset', - name='name', - field=models.CharField(max_length=128, verbose_name='Name'), - ), - ] diff --git a/apps/assets/migrations/0112_auto_20220909_1907.py b/apps/assets/migrations/0112_auto_20220909_1907.py deleted file mode 100644 index 6aaba7513..000000000 --- a/apps/assets/migrations/0112_auto_20220909_1907.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-09 11:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0111_auto_20220908_1958'), - ] - - operations = [ - migrations.AddField( - model_name='web', - name='autofill', - field=models.CharField(default='basic', max_length=16), - ), - migrations.AddField( - model_name='web', - name='password_selector', - field=models.CharField(blank=True, default='', max_length=128), - ), - migrations.AddField( - model_name='web', - name='submit_selector', - field=models.CharField(blank=True, default='', max_length=128), - ), - migrations.AddField( - model_name='web', - name='username_selector', - field=models.CharField(blank=True, default='', max_length=128), - ), - migrations.AlterField( - model_name='platform', - name='domain_enabled', - field=models.BooleanField(default=True, verbose_name='Domain enabled'), - ), - ] diff --git a/apps/assets/migrations/0113_auto_20220913_1311.py b/apps/assets/migrations/0113_auto_20220913_1311.py deleted file mode 100644 index d2d19483f..000000000 --- a/apps/assets/migrations/0113_auto_20220913_1311.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-13 05:11 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0112_auto_20220909_1907'), - ] - - operations = [ - migrations.AddField( - model_name='account', - name='su_from', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from'), - ), - migrations.AddField( - model_name='historicalaccount', - name='su_from', - field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.account', verbose_name='Su from'), - ), - ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index ad39612ae..5e141fd28 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,7 +3,6 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from common.utils import lazyproperty - from .base import BaseAccount __all__ = ['Account', 'AccountTemplate'] diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index 9d5ee1325..cb97c95cd 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -6,7 +6,6 @@ from .common import Asset class Database(Asset): db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) - version = models.CharField(max_length=16, verbose_name=_("Version"), blank=True) def __str__(self): return '{}({}://{}/{})'.format(self.name, self.type, self.ip, self.db_name) diff --git a/apps/assets/models/asset/networking.py b/apps/assets/models/asset/networking.py index d58995b42..48d73a4d9 100644 --- a/apps/assets/models/asset/networking.py +++ b/apps/assets/models/asset/networking.py @@ -3,4 +3,5 @@ from .common import Asset class Networking(Asset): + pass diff --git a/apps/assets/models/backup.py b/apps/assets/models/backup.py index debec255c..d7788a350 100644 --- a/apps/assets/models/backup.py +++ b/apps/assets/models/backup.py @@ -71,7 +71,7 @@ class Type(BitOperationChoice): class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - types = models.BigIntegerField(choices=Type.DB_CHOICES, default=Type.ALL, verbose_name=_('Type')) + types = models.BigIntegerField() recipients = models.ManyToManyField( 'users.User', related_name='recipient_escape_route_plans', blank=True, verbose_name=_("Recipient") diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 38e6c6c90..8ca79254a 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -9,6 +9,12 @@ __all__ = ['Platform', 'PlatformProtocol'] class PlatformProtocol(models.Model): + SETTING_ATTRS = { + 'console': True, + 'security': 'any,tls,rdp', + 'sftp_enabled': True, + 'sftp_home': '/tmp' + } name = models.CharField(max_length=32, verbose_name=_('Name')) port = models.IntegerField(verbose_name=_('Port')) setting = models.JSONField(verbose_name=_('Setting'), default=dict) @@ -34,6 +40,8 @@ class Platform(models.Model): domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) + ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) + ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) # 账号有关的 @@ -61,71 +69,7 @@ class Platform(models.Model): @staticmethod def set_default_platforms_ops(platform_model): - default_ok = { - 'su_enabled': True, - 'su_method': 'sudo', - 'domain_enabled': True, - 'change_password_enabled': True, - 'change_password_method': 'change_password_linux', - 'verify_account_enabled': True, - 'verify_account_method': 'ansible_posix_ping', - } - db_default = { - 'su_enabled': False, - 'domain_enabled': True, - 'change_password_enabled': True, - 'verify_account_enabled': True, - } - - platform_ops_map = { - ('host', 'linux'): { - **default_ok, - 'change_password_method': 'change_password_linux', - 'verify_account_method': 'ansible_posix_ping' - }, - ('host', 'windows'): { - **default_ok, - 'su_enabled': False, - 'change_password_method': 'change_password_windows', - 'verify_account_method': 'ansible_win_ping' - }, - ('host', 'unix'): { - **default_ok, - 'verify_account_method': 'ansible_posix_ping', - 'change_password_method': 'change_password_aix' - }, - ('database', 'mysql'): { - **db_default, - 'verify_account_method': 'mysql_ping', - 'change_password_method': 'change_password_mysql' - }, - ('database', 'postgresql'): { - **db_default, - 'verify_account_method': 'postgresql_ping', - 'change_password_method': 'change_password_postgresql' - }, - ('database', 'oracle'): { - **db_default, - 'verify_account_method': 'oracle_ping', - 'change_password_method': 'change_password_oracle' - }, - ('database', 'sqlserver'): { - **db_default, - 'verify_account_method': 'mysql_ping', - 'change_password_method': 'change_password_sqlserver' - }, - } - platforms = platform_model.objects.all() - - updated = [] - for p in platforms: - attrs = platform_ops_map.get((p.category, p.type), {}) - if not attrs: - continue - for k, v in attrs.items(): - setattr(p, k, v) - updated.append(p) - platform_model.objects.bulk_update(updated, list(default_ok.keys())) + pass def __str__(self): return self.name diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index bc6bdb21f..e2c89cd63 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -2,8 +2,6 @@ # -*- coding: utf-8 -*- # -from django.utils import timezone -from django.core.cache import cache from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ @@ -21,3 +19,53 @@ def private_key_validator(value): _('%(value)s is not an even number'), params={'value': value}, ) + + +def update_internal_platforms(platform_model): + from assets.const import AllTypes + + platforms = [ + {'name': 'Linux', 'category': 'host', 'type': 'linux'}, + {'name': 'BSD', 'category': 'host', 'type': 'unix'}, + {'name': 'Unix', 'category': 'host', 'type': 'unix'}, + {'name': 'MacOS', 'category': 'host', 'type': 'unix'}, + {'name': 'Windows', 'category': 'host', 'type': 'unix'}, + { + 'name': 'AIX', 'category': 'host', 'type': 'unix', + 'create_account_method': 'create_account_aix', + 'change_password_method': 'change_password_aix', + }, + {'name': 'Windows', 'category': 'host', 'type': 'windows'}, + {'name': 'Windows-TLS', 'category': 'host', 'type': 'windows'}, + {'name': 'Windows-RDP', 'category': 'host', 'type': 'windows'}, + + # 数据库 + {'name': 'MySQL', 'category': 'database', 'type': 'mysql'}, + {'name': 'PostgreSQL', 'category': 'database', 'type': 'postgresql'}, + {'name': 'Oracle', 'category': 'database', 'type': 'oracle'}, + {'name': 'SQLServer', 'category': 'database', 'type': 'sqlserver'}, + {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, + {'name': 'Redis', 'category': 'database', 'type': 'redis'}, + + # 网络设备 + {'name': 'Generic', 'category': 'networking', 'type': 'general'}, + {'name': 'Huawei', 'category': 'networking', 'type': 'general'}, + {'name': 'Cisco', 'category': 'networking', 'type': 'general'}, + {'name': 'H3C', 'category': 'networking', 'type': 'general'}, + + # Web + + # Cloud + ] + + platforms = platform_model.objects.all() + + updated = [] + for p in platforms: + attrs = platform_ops_map.get((p.category, p.type), {}) + if not attrs: + continue + for k, v in attrs.items(): + setattr(p, k, v) + updated.append(p) + platform_model.objects.bulk_update(updated, list(default_ok.keys())) diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml index 6b5f0df66..a7d0f9417 100644 --- a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml +++ b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml @@ -1,4 +1,4 @@ -- hosts: {{ account.asset.name }} +- hosts: all vars: account: username: {{ account.username }} diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 44438c5eb..4ddcc1d49 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -69,9 +69,7 @@ class AccountSerializer(AuthValidateMixin, @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('asset') \ - .annotate(ip=F('asset__ip')) \ - .annotate(asset_name=F('asset__name')) + queryset = queryset.prefetch_related('asset') return queryset diff --git a/apps/ops/migrations/0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py b/apps/ops/migrations/0023_automation_strategy.py similarity index 99% rename from apps/ops/migrations/0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py rename to apps/ops/migrations/0023_automation_strategy.py index 361869794..5389c86e9 100644 --- a/apps/ops/migrations/0023_automationstrategy_automationstrategyexecution_automationstrategytask_changeauthstrategy_collectstra.py +++ b/apps/ops/migrations/0023_automation_strategy.py @@ -11,7 +11,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0111_auto_20220908_1958'), + ('assets', '0106_auto_20220819_1523'), ('ops', '0022_auto_20220817_1346'), ] From d23446016dadad78245ae66535f68fc9b509d418 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 15 Sep 2022 10:46:57 +0800 Subject: [PATCH 125/488] =?UTF-8?q?refactor:=20=E6=B7=BB=E5=8A=A0=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E7=9B=B8=E5=85=B3=E7=9A=84=E8=8E=B7=E5=8F=96=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E8=B4=A6=E5=8F=B7API=E3=80=81Model=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset/common.py | 24 +++- apps/perms/api/user_permission/common.py | 38 +++++++ apps/perms/api/user_permission/nodes.py | 6 +- apps/perms/models/asset_permission.py | 131 +++++++++++++++++++--- apps/perms/serializers/user_permission.py | 18 ++- apps/perms/urls/asset_permission.py | 19 +--- apps/users/models/user.py | 15 +++ generateV3Data.py | 38 +++++++ 8 files changed, 250 insertions(+), 39 deletions(-) create mode 100644 generateV3Data.py diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index a9396e17c..fe8027cbd 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -5,8 +5,10 @@ import logging import uuid from functools import reduce +from collections import Iterable from django.db import models +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty @@ -57,13 +59,17 @@ class NodesRelationMixin: return nodes def get_all_nodes(self, flat=False): - nodes = [] + from ..node import Node + node_keys = set() for node in self.get_nodes(): - _nodes = node.get_ancestors(with_self=True) - nodes.append(_nodes) + ancestor_keys = node.get_ancestor_keys(with_self=True) + node_keys.update(ancestor_keys) + nodes = Node.objects.filter(key__in=node_keys).distinct() if flat: - nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) - return nodes + node_ids = set(nodes.values_list('id', flat=True)) + return node_ids + else: + return nodes class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): @@ -161,6 +167,14 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): tree_node = TreeNode(**data) return tree_node + def filter_accounts(self, account_names=None): + if account_names is None: + return self.accounts.all() + assert isinstance(account_names, Iterable), '`account_names` must be an iterable object' + queries = Q(name__in=account_names) | Q(username__in=account_names) + accounts = self.accounts.filter(queries) + return accounts + class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset") diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index f4c615921..87b78fe8c 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -3,6 +3,7 @@ import uuid import time +from django.db.models import Q from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator from rest_framework.views import APIView, Response @@ -20,6 +21,7 @@ from common.utils import get_logger, lazyproperty from perms.hands import User, Asset from perms import serializers +from perms.models import AssetPermission logger = get_logger(__name__) @@ -28,6 +30,8 @@ __all__ = [ 'ValidateUserAssetPermissionApi', 'GetUserAssetPermissionActionsApi', 'MyGrantedAssetSystemUsersApi', + 'UserGrantedAssetAccounts', + 'MyGrantedAssetAccounts', ] @@ -138,3 +142,37 @@ class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi): def user(self): return self.request.user + +class UserGrantedAssetAccounts(ListAPIView): + serializer_class = serializers.AccountsGrantedSerializer + rbac_perms = { + 'list': 'perms.view_userassets' + } + + @lazyproperty + def user(self): + user_id = self.kwargs.get('pk') + return User.objects.get(id=user_id) + + @lazyproperty + def asset(self): + asset_id = self.kwargs.get('asset_id') + kwargs = {'id': asset_id, 'is_active': True} + asset = get_object_or_404(Asset, **kwargs) + return asset + + def get_queryset(self): + # 获取用户-资产的授权规则 + assetperms = AssetPermission.filter_permissions(self.user, self.asset) + account_names = AssetPermission.get_account_names(assetperms) + accounts = self.asset.filter_accounts(account_names) + # 构造默认包含的账号,如: @INPUT @USER + return accounts + + +class MyGrantedAssetAccounts(UserGrantedAssetAccounts): + permission_classes = (IsValidUser,) + + @lazyproperty + def user(self): + return self.request.user diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index 7dcbd85e2..74f0b6728 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -19,7 +19,7 @@ from perms.utils.user_permission import UserGrantedNodesQueryUtils logger = get_logger(__name__) __all__ = [ - 'UserGrantedNodesForAdminApi', + 'UserGrantedNodesApi', 'MyGrantedNodesApi', 'MyGrantedNodesAsTreeApi', 'UserGrantedNodeChildrenForAdminApi', @@ -118,11 +118,11 @@ class MyGrantedNodeChildrenAsTreeApi(AssetRoleUserMixin, UserGrantedNodeChildren return permissions -class UserGrantedNodesForAdminApi(AssetRoleAdminMixin, UserGrantedNodesMixin, BaseGrantedNodeApi): +class UserGrantedNodesApi(AssetRoleAdminMixin, UserGrantedNodesMixin, BaseGrantedNodeApi): pass -class MyGrantedNodesApi(AssetRoleUserMixin, UserGrantedNodesMixin, BaseGrantedNodeApi): +class MyGrantedNodesApi(AssetRoleUserMixin, UserGrantedNodesApi): pass diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 04b90c0ca..61f5a4fd2 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -1,17 +1,17 @@ import uuid import logging +from functools import reduce from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.db import models from django.db.models import F, Q, TextChoices -from assets.models import Asset, Node, FamilyMixin +from assets.models import Asset, Node, FamilyMixin, Account from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgManager from common.utils import lazyproperty, date_expired_default from common.db.models import BaseCreateUpdateModel, BitOperationChoice, UnionQuerySet - __all__ = [ 'AssetPermission', 'PermNode', 'UserAssetGrantedTreeNodeRelation', @@ -85,20 +85,27 @@ class AssetPermissionManager(OrgManager): class AssetPermission(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) - users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), related_name='%(class)ss') - user_groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User group"), related_name='%(class)ss') - assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) - nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) + users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), + related_name='%(class)ss') + user_groups = models.ManyToManyField('users.UserGroup', blank=True, + verbose_name=_("User group"), related_name='%(class)ss') + assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', + blank=True, verbose_name=_("Asset")) + nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, + verbose_name=_("Nodes")) # 只保存 @ALL (@INPUT @USER 默认包含,将来在全局设置中进行控制) # 特殊的账号描述 # ['@ALL',] # 指定账号授权 # ['web', 'root',] accounts = models.JSONField(default=list, verbose_name=_("Accounts")) - actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions")) + actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, + verbose_name=_("Actions")) is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) - date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) + date_start = models.DateTimeField(default=timezone.now, db_index=True, + verbose_name=_("Date start")) + date_expired = models.DateTimeField(default=date_expired_default, db_index=True, + verbose_name=_('Date expired')) created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket')) @@ -106,6 +113,11 @@ class AssetPermission(OrgModelMixin): objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)() + class SpecialAccount(models.TextChoices): + ALL = '@ALL', 'All' + INPUT = '@INPUT', 'Input' + USER = '@USER', 'User' + class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset permission") @@ -174,14 +186,17 @@ class AssetPermission(OrgModelMixin): models.Prefetch('assets', queryset=Asset.objects.all().only('id')), ).order_by() - def get_all_assets(self): + def get_all_assets(self, flat=False): from assets.models import Node nodes_keys = self.nodes.all().values_list('key', flat=True) asset_ids = set(self.assets.all().values_list('id', flat=True)) nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys) asset_ids.update(nodes_asset_ids) - assets = Asset.objects.filter(id__in=asset_ids) - return assets + if flat: + return asset_ids + else: + assets = Asset.objects.filter(id__in=asset_ids) + return assets def users_display(self): names = [user.username for user in self.users.all()] @@ -199,6 +214,94 @@ class AssetPermission(OrgModelMixin): names = [node.full_value for node in self.nodes.all()] return names + def get_asset_accounts(self): + asset_ids = self.get_all_assets(flat=True) + queries = Q(asset_id__in=asset_ids) \ + & (Q(username__in=self.accounts) | Q(name__in=self.accounts)) + accounts = Account.objects.filter(queries) + return accounts + + @classmethod + def get_account_names(cls, perms): + account_names = set() + for perm in perms: + perm: cls + if not isinstance(perm.accounts, list): + continue + account_names.update(perm.accounts) + return account_names + + @classmethod + def filter_permissions(cls, user=None, asset=None, account=None): + """ 获取同时包含 用户-资产-账号 的授权规则 """ + assetperm_ids = [] + if user: + user_assetperm_ids = cls.filter_permissions_by_user(user, flat=True) + assetperm_ids.append(user_assetperm_ids) + if asset: + asset_assetperm_ids = cls.filter_permissions_by_asset(asset, flat=True) + assetperm_ids.append(asset_assetperm_ids) + if account: + account_assetperm_ids = cls.filter_permissions_by_account(account, flat=True) + assetperm_ids.append(account_assetperm_ids) + # & 是同时满足,比如有用户,但是用户的规则是空,那么返回也应该是空 + assetperm_ids = list(reduce(lambda x, y: set(x) & set(y), assetperm_ids)) + assetperms = cls.objects.filter(id__in=assetperm_ids).valid().order_by('-date_expired') + return assetperms + + @classmethod + def filter_permissions_by_user(cls, user, with_group=True, flat=False): + assetperm_ids = set() + user_assetperm_ids = AssetPermission.users.through.objects \ + .filter(user_id=user.id) \ + .values_list('assetpermission_id', flat=True) \ + .distinct() + assetperm_ids.update(user_assetperm_ids) + + if with_group: + usergroup_ids = user.get_groups(flat=True) + usergroups_assetperm_id = AssetPermission.user_groups.through.objects \ + .filter(usergroup_id__in=usergroup_ids) \ + .values_list('assetpermission_id', flat=True) \ + .distinct() + assetperm_ids.update(usergroups_assetperm_id) + + if flat: + return assetperm_ids + else: + assetperms = cls.objects.filter(id__in=assetperm_ids).valid() + return assetperms + + @classmethod + def filter_permissions_by_asset(cls, asset, with_node=True, flat=False): + assetperm_ids = set() + asset_assetperm_ids = AssetPermission.assets.through.objects \ + .filter(asset_id=asset.id) \ + .values_list('assetpermission_id', flat=True) + assetperm_ids.update(asset_assetperm_ids) + + if with_node: + node_ids = asset.get_all_nodes(flat=True) + node_assetperm_ids = AssetPermission.nodes.through.objects \ + .filter(node_id__in=node_ids) \ + .values_list('assetpermission_id', flat=True) + assetperm_ids.update(node_assetperm_ids) + + if flat: + return assetperm_ids + else: + assetperms = cls.objects.filter(id__in=assetperm_ids).valid() + return assetperms + + @classmethod + def filter_permissions_by_account(cls, account, flat=False): + assetperms = cls.objects.filter(accounts__contains=account).valid() + if flat: + assetperm_ids = assetperms.values_list('id', flat=True) + return assetperm_ids + else: + return assetperms + class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): class NodeFrom(TextChoices): @@ -210,7 +313,8 @@ class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpd node = models.ForeignKey('assets.Node', default=None, on_delete=models.CASCADE, db_constraint=False, null=False, related_name='granted_node_rels') node_key = models.CharField(max_length=64, verbose_name=_("Key"), db_index=True) - node_parent_key = models.CharField(max_length=64, default='', verbose_name=_('Parent key'), db_index=True) + node_parent_key = models.CharField(max_length=64, default='', verbose_name=_('Parent key'), + db_index=True) node_from = models.CharField(choices=NodeFrom.choices, max_length=16, db_index=True) node_assets_amount = models.IntegerField(default=0) @@ -297,4 +401,3 @@ class PermedAsset(Asset): ('view_userassets', _('Can view user assets')), ('view_usergroupassets', _('Can view usergroup assets')), ] - diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index f7f541e7a..c1d24ba05 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -4,20 +4,19 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from assets.models import Node, Asset, Platform +from assets.models import Node, Asset, Platform, Account from perms.serializers.permission import ActionsField __all__ = [ 'NodeGrantedSerializer', 'AssetGrantedSerializer', 'ActionsSerializer', + 'AccountsGrantedSerializer' ] class AssetGrantedSerializer(serializers.ModelSerializer): - """ - 被授权资产的数据结构 - """ + """ 被授权资产的数据结构 """ platform = serializers.SlugRelatedField( slug_field='name', queryset=Platform.objects.all(), label=_("Platform") ) @@ -44,3 +43,14 @@ class NodeGrantedSerializer(serializers.ModelSerializer): class ActionsSerializer(serializers.Serializer): actions = ActionsField(read_only=True) + +class AccountsGrantedSerializer(serializers.ModelSerializer): + """ 授权的账号序列类 """ + + # Todo: 添加前端登录逻辑中需要的一些字段,比如:是否需要手动输入密码 + # need_manual = serializers.BooleanField(label=_('Need manual input')) + + class Meta: + model = Account + fields = ['id', 'name', 'username'] + read_only_fields = fields diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 7099aa137..28af1a94d 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -5,7 +5,6 @@ from rest_framework_bulk.routes import BulkRouter from .. import api -# v3 Done router = BulkRouter() router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, 'asset-permissions-users-relation') @@ -14,42 +13,31 @@ router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRe router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation') user_permission_urlpatterns = [ - # 统一说明: - # ``: `User.pk` - # 直接授权:在 `AssetPermission` 中关联的对象 - - # --------------------------------------------------------- # 以 serializer 格式返回 path('/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'), path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'), - # Tree Node 的数据格式返回 path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'), path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'), path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'), - # ^--------------------------------------------------------^ # 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点 # 以 serializer 格式返回 - path('/nodes/', api.UserGrantedNodesForAdminApi.as_view(), name='user-nodes'), + path('/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'), - # 以 Tree Node 的数据格式返回 path('/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'), path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'), - # ^--------------------------------------------------------^ # 一层一层的获取用户授权的节点, # 以 Serializer 的数据格式返回 path('/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'), path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'), - # 以 Tree Node 的数据格式返回 path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), name='user-nodes-children-as-tree'), # 部分调用位置 # - 普通用户 -> 我的资产 -> 展开节点 时调用 path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'), - # ^--------------------------------------------------------^ # 此接口会返回整棵树 # 普通用户 -> 命令执行 -> 左侧树 @@ -75,6 +63,11 @@ user_permission_urlpatterns = [ # Asset System users path('/assets//system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'), path('assets//system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), + + # Todo: 增加 + # 获取所有和资产相关联的账号列表 + path('/assets//accounts/', api.UserGrantedAssetAccounts.as_view(), name='user-asset-accounts'), + path('assets//accounts/', api.MyGrantedAssetAccounts.as_view(), name='my-asset-accounts') ] user_group_permission_urlpatterns = [ diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 78d5eb540..659a894a9 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -918,6 +918,21 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): return True return False + def get_groups(self, flat=False): + from users.models import UserGroup + usergroup_ids = self.groups.through.objects\ + .filter(user_id=self.id)\ + .distinct()\ + .values_list('usergroup_id', flat=True) + usergroups = UserGroup.objects.filter(id__in=usergroup_ids) + if flat: + usergroup_ids = usergroups.values_list('id', flat=True) + return usergroup_ids + else: + return usergroups + + + class UserPasswordHistory(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) diff --git a/generateV3Data.py b/generateV3Data.py new file mode 100644 index 000000000..673037e99 --- /dev/null +++ b/generateV3Data.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# + +# >>> Django 环境配置 +import django +import os +import sys + +if os.path.exists('../apps'): + sys.path.insert(0, '../apps') +elif os.path.exists('./apps'): + sys.path.insert(0, './apps') + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +APPS_DIR = os.path.join(BASE_DIR, 'apps') +sys.path.insert(0, APPS_DIR) + +os.environ.setdefault('PYTHONOPTIMIZE', '1') +if os.getuid() == 0: + os.environ.setdefault('C_FORCE_ROOT', '1') + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") +django.setup() + +# <<< + + +class Generator(object): + + def generate(self): + pass + + def generate_assets(self): + pass + + +if __name__ == '__main__': + pass From 9edd786bb41226587be885b2d9934c60d615c19e Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 15 Sep 2022 16:22:01 +0800 Subject: [PATCH 126/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platform?= =?UTF-8?q?=20=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 14 ++- .../migrations/0108_auto_20220915_1032.py | 90 +++++++++++++++++ apps/assets/models/cmd_filter.py | 1 - apps/assets/models/platform.py | 31 +++--- apps/assets/serializers/platform.py | 99 +++++++++++-------- 5 files changed, 178 insertions(+), 57 deletions(-) create mode 100644 apps/assets/migrations/0108_auto_20220915_1032.py diff --git a/apps/assets/const.py b/apps/assets/const.py index ca33a93bb..5a46d8d31 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -16,8 +16,10 @@ class PlatformMixin: def platform_constraints(cls): return { 'domain_enabled': False, - 'gather_facts_enabled': False, 'su_enabled': False, + 'vendor_enabled': False, + 'ping_enabled': False, + 'gather_facts_enabled': False, 'change_password_enabled': False, 'verify_account_enabled': False, 'create_account_enabled': False, @@ -49,6 +51,15 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): }, cls.NETWORKING: { 'domain_enabled': True, + 'brand_enabled': True, + 'brands': [ + ('huawei', 'Huawei'), + ('cisco', 'Cisco'), + ('juniper', 'Juniper'), + ('h3c', 'H3C'), + ('dell', 'Dell'), + ('other', 'Other'), + ], 'su_enabled': False, 'ping_enabled': True, 'ping_method': 'ping', 'gather_facts_enabled': False, @@ -125,7 +136,6 @@ class NetworkingTypes(PlatformMixin, ChoicesMixin, models.TextChoices): SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") FIREWALL = 'firewall', _("Firewall") - OTHER_NETWORK = 'other', _("Other device") class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): diff --git a/apps/assets/migrations/0108_auto_20220915_1032.py b/apps/assets/migrations/0108_auto_20220915_1032.py new file mode 100644 index 000000000..21f663df8 --- /dev/null +++ b/apps/assets/migrations/0108_auto_20220915_1032.py @@ -0,0 +1,90 @@ +# Generated by Django 3.2.14 on 2022-09-15 02:32 + +from django.db import migrations, models +import django + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_alter_accountbackupplan_types'), + ] + + operations = [ + migrations.AddField( + model_name='platform', + name='brand', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Brand'), + ), + migrations.CreateModel( + name='PlatformAutomation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')), + ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), + ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), + ('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), + ('create_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), + ('create_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')), + ('change_password_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')), + ('change_password_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')), + ('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')), + ('verify_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')), + ('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), + ('gather_accounts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), + ], + ), + migrations.RemoveField( + model_name='platform', + name='change_password_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='change_password_method', + ), + migrations.RemoveField( + model_name='platform', + name='create_account_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='create_account_method', + ), + migrations.RemoveField( + model_name='platform', + name='gather_accounts_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='gather_accounts_method', + ), + migrations.RemoveField( + model_name='platform', + name='gather_facts_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='gather_facts_method', + ), + migrations.RemoveField( + model_name='platform', + name='ping_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='ping_method', + ), + migrations.RemoveField( + model_name='platform', + name='verify_account_enabled', + ), + migrations.RemoveField( + model_name='platform', + name='verify_account_method', + ), + migrations.AddField( + model_name='platform', + name='automation', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='platform', to='assets.platformautomation', verbose_name='Automation'), + ), + ] diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 53189018c..916170cfd 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -101,7 +101,6 @@ class CommandFilterRule(OrgModelMixin): s = self.construct_command_regex(content=self.content) else: s = r'{0}'.format(self.content) - return s @classmethod diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 8ca79254a..513e8a7c5 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -5,7 +5,7 @@ from assets.const import AllTypes from common.db.fields import JsonDictTextField -__all__ = ['Platform', 'PlatformProtocol'] +__all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] class PlatformProtocol(models.Model): @@ -20,6 +20,21 @@ class PlatformProtocol(models.Model): setting = models.JSONField(verbose_name=_('Setting'), default=dict) +class PlatformAutomation(models.Model): + ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) + ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) + gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) + gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) + create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) + create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) + change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) + change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) + verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) + verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) + gather_accounts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) + + class Platform(models.Model): """ 对资产提供 约束和默认值 @@ -37,24 +52,14 @@ class Platform(models.Model): comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) # 资产有关的 charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) + brand = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Brand")) # 厂商主要是给网络设备 domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) - ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) - ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) - gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) - gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) # 账号有关的 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) - create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) - create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) - change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) - change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) - verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) - verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) - gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) - gather_accounts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) + automation = models.OneToOneField(PlatformAutomation, on_delete=models.CASCADE, related_name='platform', blank=True, null=True, verbose_name=_("Automation")) @property def type_constraints(self): diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 9559ea70d..9c94a45f2 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from common.drf.fields import LabeledChoiceField from common.drf.serializers import JMSWritableNestedModelSerializer -from ..models import Platform, PlatformProtocol +from ..models import Platform, PlatformProtocol, PlatformAutomation from ..const import Category, AllTypes @@ -26,43 +26,18 @@ class ProtocolSettingSerializer(serializers.Serializer): sftp_home = serializers.CharField(default='/tmp', label=_("SFTP home")) -class PlatformProtocolsSerializer(serializers.ModelSerializer): - setting = ProtocolSettingSerializer(required=False, allow_null=True) - +class PlatformAutomationSerializer(serializers.ModelSerializer): class Meta: - model = PlatformProtocol - fields = ['id', 'name', 'port', 'setting'] - - -class PlatformSerializer(JMSWritableNestedModelSerializer): - type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type")) - category = LabeledChoiceField(choices=Category.choices, label=_("Category")) - protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) - su_method = LabeledChoiceField( - choices=[('sudo', 'sudo su -'), ('su', 'su - ')], - label='切换方式', required=False, default='sudo' - ) - - class Meta: - model = Platform - fields_mini = ['id', 'name', 'internal'] - fields_small = fields_mini + [ - 'category', 'type', 'charset', - ] - fields = fields_small + [ - 'protocols_enabled', 'protocols', 'domain_enabled', + model = PlatformAutomation + fields = [ + 'id', 'ping_enabled', 'ping_method', 'gather_facts_enabled', 'gather_facts_method', - 'su_enabled', 'su_method', - 'gather_accounts_enabled', 'gather_accounts_method', 'create_account_enabled', 'create_account_method', - 'verify_account_enabled', 'verify_account_method', 'change_password_enabled', 'change_password_method', - 'comment', + 'verify_account_enabled', 'verify_account_method', + 'gather_accounts_enabled', 'gather_accounts_method', ] extra_kwargs = { - 'su_enabled': {'label': '启用切换账号'}, - 'domain_enabled': {'label': "启用网域"}, - 'domain_default': {'label': "默认网域"}, 'gather_facts_enabled': {'label': '启用收集信息'}, 'gather_facts_method': {'label': '收集信息方式'}, 'verify_account_enabled': {'label': '启用校验账号'}, @@ -75,16 +50,58 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'gather_accounts_method': {'label': '收集账号方式'}, } - def validate(self, attrs): - fields_to_check = [ - ('verify_account_enabled', 'verify_account_method'), - ('create_account_enabled', 'create_account_method'), - ('change_password_enabled', 'change_password_method'), + +class PlatformProtocolsSerializer(serializers.ModelSerializer): + setting = ProtocolSettingSerializer(required=False, allow_null=True) + + class Meta: + model = PlatformProtocol + fields = ['id', 'name', 'port', 'setting'] + + +class PlatformSerializer(JMSWritableNestedModelSerializer): + type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type")) + category = LabeledChoiceField(choices=Category.choices, label=_("Category")) + protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) + automation = PlatformAutomationSerializer(label=_('Automation'), required=False) + su_method = LabeledChoiceField( + choices=[('sudo', 'sudo su -'), ('su', 'su - ')], + label='切换方式', required=False, default='sudo' + ) + brand = LabeledChoiceField(choices=[], label='厂商', required=False, allow_null=True) + + class Meta: + model = Platform + fields_mini = ['id', 'name', 'internal'] + fields_small = fields_mini + [ + 'category', 'type', 'charset', ] - for method_enabled, method_name in fields_to_check: - if attrs.get(method_enabled, False) and not attrs.get(method_name, False): - raise serializers.ValidationError({method_name: _('This field is required.')}) - return attrs + fields = fields_small + [ + 'protocols_enabled', 'protocols', 'domain_enabled', + 'su_enabled', 'su_method', 'brand', 'automation', 'comment', + ] + extra_kwargs = { + 'su_enabled': {'label': '启用切换账号'}, + 'domain_enabled': {'label': "启用网域"}, + 'domain_default': {'label': "默认网域"}, + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_brand_choices() + + def set_brand_choices(self): + field = self.fields.get('brand') + request = self.context.get('request') + if not field or not request: + return + category = request.query_params.get('category', '') + constraints = Category.platform_constraints().get(category) + if not constraints: + return + field.choices = constraints.get('brands', []) + if field.choices: + field.required = True class PlatformOpsMethodSerializer(serializers.Serializer): From 139540fafe8257819a09df55ebeeaea33a03fcc4 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 14 Sep 2022 15:51:04 +0800 Subject: [PATCH 127/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9change=20pass?= =?UTF-8?q?word=20linux=20ansible=20yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/accounts.py | 4 +- apps/assets/models/asset/common.py | 4 + apps/assets/models/platform.py | 4 +- .../database/change_password_mysql/main.yml | 10 -- .../change_password_mysql/manifest.yml | 6 - .../roles/change_password/tasks/main.yml | 27 ----- .../database/change_password_oracle/main.yml | 10 -- .../change_password_oracle/manifest.yml | 6 - .../roles/change_password/tasks/main.yml | 27 ----- .../change_password_postgresql/main.yml | 10 -- .../change_password_postgresql/manifest.yml | 6 - .../roles/change_password/tasks/main.yml | 27 ----- .../change_password_sqlserver/main.yml | 10 -- .../change_password_sqlserver/manifest.yml | 8 -- .../roles/change_password/tasks/main.yml | 27 ----- .../host/change_password_aix/main.yml | 10 -- .../host/change_password_aix/manifest.yml | 6 - .../roles/change_password/tasks/main.yml | 27 ----- .../host/change_password_linux/main.yml | 8 -- .../host/change_password_linux/manifest.yml | 7 -- .../roles/change_password/tasks/main.yml | 23 ---- .../change_password_local_windows/main.yml | 10 -- .../manifest.yml | 7 -- .../roles/change_password/tasks/main.yml | 27 ----- .../playbooks/generate_playbook/__init__.py | 0 .../playbooks/generate_playbook/base.py | 32 ++++++ .../generate_playbook/change_password.py | 104 ++++++++++++++++++ .../playbooks/generate_playbook/verify.py | 0 .../change_password/roles/linux/main.yml | 12 ++ .../roles/linux/tasks/main.yml | 36 ++++++ .../strategy/verify/roles/linux/main.yml | 7 ++ .../verify/roles/linux/tasks/main.yml | 8 ++ .../host/ansible_posix_ping/main.yml | 13 --- .../host/ansible_posix_ping/manifest.yml | 10 -- .../host/ansible_win_ping/main.yml | 13 --- .../host/ansible_win_ping/manifest.yml | 6 - apps/ops/const.py | 2 +- apps/ops/task_handlers/endpoint.py | 4 +- 38 files changed, 210 insertions(+), 348 deletions(-) delete mode 100644 apps/assets/playbooks/change_password/database/change_password_mysql/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_oracle/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/generate_playbook/__init__.py create mode 100644 apps/assets/playbooks/generate_playbook/base.py create mode 100644 apps/assets/playbooks/generate_playbook/change_password.py create mode 100644 apps/assets/playbooks/generate_playbook/verify.py create mode 100644 apps/assets/playbooks/strategy/change_password/roles/linux/main.yml create mode 100644 apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml create mode 100644 apps/assets/playbooks/strategy/verify/roles/linux/main.yml create mode 100644 apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml delete mode 100644 apps/assets/playbooks/verify_account/host/ansible_posix_ping/main.yml delete mode 100644 apps/assets/playbooks/verify_account/host/ansible_posix_ping/manifest.yml delete mode 100644 apps/assets/playbooks/verify_account/host/ansible_win_ping/main.yml delete mode 100644 apps/assets/playbooks/verify_account/host/ansible_win_ping/manifest.yml diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index 00f569357..909f0ad27 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -49,10 +49,10 @@ class AccountViewSet(OrgBulkModelViewSet): filterset_class = AccountFilterSet serializer_classes = { 'default': serializers.AccountSerializer, - 'verify_account': serializers.AssetTaskSerializer + 'verify': serializers.AssetTaskSerializer } rbac_perms = { - 'verify_account': 'assets.test_authbook', + 'verify': 'assets.test_authbook', 'partial_update': 'assets.change_assetaccountsecret', } diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index a9396e17c..eead9667a 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -89,6 +89,10 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): def get_target_ip(self): return self.ip + def get_target_ssh_port(self): + protocol = self.protocols.all().filter(name='ssh').first() + return protocol.port if protocol else 22 + @property def is_valid(self): warning = '' diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 38e6c6c90..0fe784a16 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -66,7 +66,7 @@ class Platform(models.Model): 'su_method': 'sudo', 'domain_enabled': True, 'change_password_enabled': True, - 'change_password_method': 'change_password_linux', + 'change_password_method': 'linux', 'verify_account_enabled': True, 'verify_account_method': 'ansible_posix_ping', } @@ -80,7 +80,7 @@ class Platform(models.Model): platform_ops_map = { ('host', 'linux'): { **default_ok, - 'change_password_method': 'change_password_linux', + 'change_password_method': 'linux', 'verify_account_method': 'ansible_posix_ping' }, ('host', 'windows'): { diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml deleted file mode 100644 index 043549ec6..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: change_password_mysql -name: Change password for MySQL -category: database -type: - - mysql -method: change_password diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml deleted file mode 100644 index d3bab86e1..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: change_password_oracle -name: Change password for Oracle -method: change_password -category: database -type: - - oracle diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml deleted file mode 100644 index 9abe184be..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: change_password_postgresql -name: Change password for PostgreSQL -category: database -type: - - postgresql -method: change_password diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml deleted file mode 100644 index b16a24dc9..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml +++ /dev/null @@ -1,8 +0,0 @@ -id: change_password_sqlserver -name: Change password for SQLServer -version: 1 -category: database -type: - - sqlserver -method: change_password - diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_aix/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml deleted file mode 100644 index 451c10f8e..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: change_password_aix -name: Change password for AIX -category: host -type: - - aix -method: change_password diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml deleted file mode 100644 index 6b5f0df66..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml +++ /dev/null @@ -1,8 +0,0 @@ -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml deleted file mode 100644 index 25183c25d..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml +++ /dev/null @@ -1,7 +0,0 @@ -id: change_password_linux -name: Change password for Linux -category: host -type: - - unix - - linux -method: change_password diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml deleted file mode 100644 index e0ba9c73f..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,23 +0,0 @@ -- name: Check connection - ping: - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('sha512') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml deleted file mode 100644 index 7f34008e6..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml +++ /dev/null @@ -1,7 +0,0 @@ -id: change_password_local_windows -name: Change password local account for Windows -version: 1 -method: change_password -category: host -type: - - windows diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/generate_playbook/__init__.py b/apps/assets/playbooks/generate_playbook/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/playbooks/generate_playbook/base.py b/apps/assets/playbooks/generate_playbook/base.py new file mode 100644 index 000000000..12a39f12c --- /dev/null +++ b/apps/assets/playbooks/generate_playbook/base.py @@ -0,0 +1,32 @@ +import os +import time +import shutil +from typing import List + +from django.conf import settings +from assets.models import Asset + + +class BaseGeneratePlaybook: + src_filepath: str + + def __init__(self, assets: List[Asset], strategy): + self.assets = assets + self.strategy = strategy + self.temp_folder = self.temp_folder_path() + + @staticmethod + def temp_folder_path(): + project_dir = settings.PROJECT_DIR + tmp_dir = os.path.join(project_dir, 'tmp') + filepath = os.path.join(tmp_dir, str(time.time())) + return filepath + + def del_temp_folder(self): + shutil.rmtree(self.temp_folder) + + def generate_temp_playbook(self): + src = self.src_filepath + dst = os.path.join(self.temp_folder, self.strategy) + shutil.copytree(src, dst) + return dst diff --git a/apps/assets/playbooks/generate_playbook/change_password.py b/apps/assets/playbooks/generate_playbook/change_password.py new file mode 100644 index 000000000..a3f723d28 --- /dev/null +++ b/apps/assets/playbooks/generate_playbook/change_password.py @@ -0,0 +1,104 @@ +import os +import yaml +import jinja2 +from typing import List + +from django.conf import settings +from assets.models import Asset +from .base import BaseGeneratePlaybook + + +class GenerateChangePasswordPlaybook(BaseGeneratePlaybook): + + def __init__( + self, assets: List[Asset], strategy, usernames, password='', + private_key='', public_key='', key_strategy='' + ): + super().__init__(assets, strategy) + self.password = password + self.public_key = public_key + self.private_key = private_key + self.key_strategy = key_strategy + self.relation_asset_map = self.get_username_relation_asset_map(usernames) + + def get_username_relation_asset_map(self, usernames): + # TODO 没牛逼用户的资产 网关 + + complete_map = { + asset: list(asset.accounts.value_list('username', flat=True)) + for asset in self.assets + } + + if '*' in usernames: + return complete_map + + relation_map = {} + for asset, usernames in complete_map.items(): + relation_map[asset] = list(set(usernames) & set(usernames)) + return relation_map + + @property + def src_filepath(self): + return os.path.join( + settings.BASE_DIR, 'assets', 'playbooks', 'strategy', + 'change_password', 'roles', self.strategy + ) + + def generate_hosts(self): + host_pathname = os.path.join(self.temp_folder, 'hosts') + with open(host_pathname, 'w', encoding='utf8') as f: + for asset in self.relation_asset_map.keys(): + f.write(f'{asset.name}\n') + + def generate_host_vars(self): + host_vars_pathname = os.path.join(self.temp_folder, 'hosts', 'host_vars') + os.makedirs(host_vars_pathname, exist_ok=True) + for asset, usernames in self.relation_asset_map.items(): + host_vars = { + 'ansible_host': asset.get_target_ip(), + 'ansible_port': asset.get_target_ssh_port(), # TODO 需要根绝协议取端口号 + 'ansible_user': asset.admin_user.username, + 'ansible_pass': asset.admin_user.username, + 'ansible_connection': 'ssh', + 'usernames': usernames, + } + pathname = os.path.join(host_vars_pathname, f'{asset.name}.yml') + with open(pathname, 'w', encoding='utf8') as f: + f.write(yaml.dump(host_vars, allow_unicode=True)) + + def generate_secret_key_files(self): + if not self.private_key and not self.public_key: + return + + file_pathname = os.path.join(self.temp_folder, self.strategy, 'files') + public_pathname = os.path.join(file_pathname, 'id_rsa.pub') + private_pathname = os.path.join(file_pathname, 'id_rsa') + + os.makedirs(file_pathname, exist_ok=True) + with open(public_pathname, 'w', encoding='utf8') as f: + f.write(self.public_key) + with open(private_pathname, 'w', encoding='utf8') as f: + f.write(self.private_key) + + def generate_role_main(self): + task_main_pathname = os.path.join(self.temp_folder, 'main.yaml') + context = { + 'password': self.password, + 'key_strategy': self.key_strategy, + 'private_key_file': 'id_rsa' if self.private_key else '', + 'exclusive': 'no' if self.key_strategy == 'all' else 'yes', + 'jms_key': self.public_key.split()[2].strip() if self.public_key else '', + } + with open(task_main_pathname, 'r+', encoding='utf8') as f: + string_var = f.read() + f.seek(0, 0) + response = jinja2.Template(string_var).render(context) + results = yaml.safe_load(response) + f.write(yaml.dump(results, allow_unicode=True)) + + def execute(self): + self.generate_temp_playbook() + self.generate_hosts() + self.generate_host_vars() + self.generate_secret_key_files() + self.generate_role_main() diff --git a/apps/assets/playbooks/generate_playbook/verify.py b/apps/assets/playbooks/generate_playbook/verify.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml b/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml new file mode 100644 index 000000000..cb229d9d6 --- /dev/null +++ b/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml @@ -0,0 +1,12 @@ +- hosts: all + vars: + connection_type: ssh + password: + value: {{ password}} + public_key: + value: {{ jms_key }} + exclusive: {{ exclusive }} + key_strategy: {{ key_strategy }} + private_key_file: {{ private_key_file }} + roles: + - linux diff --git a/apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml b/apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml new file mode 100644 index 000000000..cc9467ca1 --- /dev/null +++ b/apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml @@ -0,0 +1,36 @@ +- name: Check connection + ping: + +- name: Change password + user: + name: "{{ item }}" + password: "{{ password.value | password_hash('sha512') }}" + update_password: always + with_items: "{{ usernames }}" + when: "{{ password.value }}" + +- name: Change public key + authorized_key: + user: "{{ item }}" + key: "{{ lookup('file', id_rsa.pub) }}" + state: present + exclusive: "{{ public_key.exclusive }}" + with_items: "{{ usernames }}" + when: "{{ public_key.value and key_strategy != 'set_jms' }}" + +- name: Change public key + lineinfile: + user: "{{ item }}" + dest: /home/{{ item }}/.ssh/authorized_keys regexp='.*{{ public_key.value }}$ + state: absent + with_items: "{{ usernames }}" + when: "{{ public_key.value and key_strategy == 'set_jms' }}" + +- name: Verify user + ping: + vars: + ansible_user: "{{ item }}" + ansible_pass: "{{ password.value }}" + ansible_ssh_private_key_file: "{{ private_key_file }}" + ansible_connection: "{{ connection_type | default('ssh') }}" + with_items: "{{ usernames }}" diff --git a/apps/assets/playbooks/strategy/verify/roles/linux/main.yml b/apps/assets/playbooks/strategy/verify/roles/linux/main.yml new file mode 100644 index 000000000..ba07ece17 --- /dev/null +++ b/apps/assets/playbooks/strategy/verify/roles/linux/main.yml @@ -0,0 +1,7 @@ +- hosts: all + vars: + connection_type: ssh + password: + value: {{ password}} + roles: + - linux diff --git a/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml b/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml new file mode 100644 index 000000000..0bf6e8ee1 --- /dev/null +++ b/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml @@ -0,0 +1,8 @@ +- name: Verify user + ping: + vars: + ansible_user: "{{ item }}" + ansible_pass: "{{ password }}" + ansible_connection: "{{ connection_type | default('ssh') }}" + ansible_ssh_private_key_file: "{{ private_key_file }}" + with_items: "{{ usernames }}" diff --git a/apps/assets/playbooks/verify_account/host/ansible_posix_ping/main.yml b/apps/assets/playbooks/verify_account/host/ansible_posix_ping/main.yml deleted file mode 100644 index 4ccdb3074..000000000 --- a/apps/assets/playbooks/verify_account/host/ansible_posix_ping/main.yml +++ /dev/null @@ -1,13 +0,0 @@ -- hosts: centos - gather_facts: no - vars: - account: - username: web - password: test123 - - tasks: - - name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" diff --git a/apps/assets/playbooks/verify_account/host/ansible_posix_ping/manifest.yml b/apps/assets/playbooks/verify_account/host/ansible_posix_ping/manifest.yml deleted file mode 100644 index 6cd223f1c..000000000 --- a/apps/assets/playbooks/verify_account/host/ansible_posix_ping/manifest.yml +++ /dev/null @@ -1,10 +0,0 @@ -id: ansible_posix_ping -name: Ansible posix ping -description: Ansible ping -category: host -type: - - linux - - unix - - macos - - bsd -method: verify_account diff --git a/apps/assets/playbooks/verify_account/host/ansible_win_ping/main.yml b/apps/assets/playbooks/verify_account/host/ansible_win_ping/main.yml deleted file mode 100644 index 726d04a53..000000000 --- a/apps/assets/playbooks/verify_account/host/ansible_win_ping/main.yml +++ /dev/null @@ -1,13 +0,0 @@ -- hosts: centos - gather_facts: no - vars: - account: - username: web - password: test123 - - tasks: - - name: Verify password - win_ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" diff --git a/apps/assets/playbooks/verify_account/host/ansible_win_ping/manifest.yml b/apps/assets/playbooks/verify_account/host/ansible_win_ping/manifest.yml deleted file mode 100644 index fe881de3b..000000000 --- a/apps/assets/playbooks/verify_account/host/ansible_win_ping/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: ansible_win_ping -name: Ansible win ping -category: host -type: - - windows -method: verify_account diff --git a/apps/ops/const.py b/apps/ops/const.py index ee2a17340..b9016f450 100644 --- a/apps/ops/const.py +++ b/apps/ops/const.py @@ -6,7 +6,7 @@ class StrategyChoice(models.TextChoices): push = 'push', _('Push') verify = 'verify', _('Verify') collect = 'collect', _('Collect') - change_auth = 'change_auth', _('Change auth') + change_password = 'change_password', _('Change password') class SSHKeyStrategy(models.TextChoices): diff --git a/apps/ops/task_handlers/endpoint.py b/apps/ops/task_handlers/endpoint.py index 4bca2631b..2d95dcad5 100644 --- a/apps/ops/task_handlers/endpoint.py +++ b/apps/ops/task_handlers/endpoint.py @@ -10,7 +10,7 @@ class ExecutionManager: StrategyChoice.push: PushExecutionManager, StrategyChoice.verify: VerifyExecutionManager, StrategyChoice.collect: CollectExecutionManager, - StrategyChoice.change_auth: ChangeAuthExecutionManager, + StrategyChoice.change_password: ChangeAuthExecutionManager, } def __new__(cls, execution): @@ -23,7 +23,7 @@ class TaskHandler: StrategyChoice.push: PushHandler, StrategyChoice.verify: VerifyHandler, StrategyChoice.collect: CollectHandler, - StrategyChoice.change_auth: ChangeAuthHandler, + StrategyChoice.change_password: ChangeAuthHandler, } def __new__(cls, task, show_step_info): From a4d0ef3706387b5de4885c900441b618da22df65 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 15 Sep 2022 21:14:14 +0800 Subject: [PATCH 128/488] perf: verify ansible linux --- .../generate_playbook/change_password.py | 6 +- .../playbooks/generate_playbook/verify.py | 86 +++++++++++++++++++ .../strategy/verify/roles/linux/main.yml | 2 - .../verify/roles/linux/tasks/main.yml | 8 +- 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/apps/assets/playbooks/generate_playbook/change_password.py b/apps/assets/playbooks/generate_playbook/change_password.py index a3f723d28..20c3f0889 100644 --- a/apps/assets/playbooks/generate_playbook/change_password.py +++ b/apps/assets/playbooks/generate_playbook/change_password.py @@ -22,7 +22,7 @@ class GenerateChangePasswordPlaybook(BaseGeneratePlaybook): self.relation_asset_map = self.get_username_relation_asset_map(usernames) def get_username_relation_asset_map(self, usernames): - # TODO 没牛逼用户的资产 网关 + # TODO 没特权用户的资产 要考虑网关 complete_map = { asset: list(asset.accounts.value_list('username', flat=True)) @@ -34,6 +34,9 @@ class GenerateChangePasswordPlaybook(BaseGeneratePlaybook): relation_map = {} for asset, usernames in complete_map.items(): + usernames = list(set(usernames) & set(usernames)) + if not usernames: + continue relation_map[asset] = list(set(usernames) & set(usernames)) return relation_map @@ -59,7 +62,6 @@ class GenerateChangePasswordPlaybook(BaseGeneratePlaybook): 'ansible_port': asset.get_target_ssh_port(), # TODO 需要根绝协议取端口号 'ansible_user': asset.admin_user.username, 'ansible_pass': asset.admin_user.username, - 'ansible_connection': 'ssh', 'usernames': usernames, } pathname = os.path.join(host_vars_pathname, f'{asset.name}.yml') diff --git a/apps/assets/playbooks/generate_playbook/verify.py b/apps/assets/playbooks/generate_playbook/verify.py index e69de29bb..88695b814 100644 --- a/apps/assets/playbooks/generate_playbook/verify.py +++ b/apps/assets/playbooks/generate_playbook/verify.py @@ -0,0 +1,86 @@ +import os +import yaml +from typing import List + +from django.conf import settings +from assets.models import Asset +from .base import BaseGeneratePlaybook + + +class GenerateVerifyPlaybook(BaseGeneratePlaybook): + + def __init__( + self, assets: List[Asset], strategy, usernames + ): + super().__init__(assets, strategy) + self.relation_asset_map = self.get_account_relation_asset_map(usernames) + + def get_account_relation_asset_map(self, usernames): + # TODO 没特权用户的资产 要考虑网关 + complete_map = { + asset: list(asset.accounts.all()) + for asset in self.assets + } + + if '*' in usernames: + return complete_map + + relation_map = {} + for asset, accounts in complete_map.items(): + account_map = {account.username: account for account in accounts} + accounts = [account_map[i] for i in (set(usernames) & set(account_map))] + if not accounts: + continue + relation_map[asset] = accounts + return relation_map + + @property + def src_filepath(self): + return os.path.join( + settings.BASE_DIR, 'assets', 'playbooks', 'strategy', + 'verify', 'roles', self.strategy + ) + + def generate_hosts(self): + host_pathname = os.path.join(self.temp_folder, 'hosts') + with open(host_pathname, 'w', encoding='utf8') as f: + for asset in self.relation_asset_map.keys(): + f.write(f'{asset.name}\n') + + def generate_host_vars(self): + host_vars_pathname = os.path.join(self.temp_folder, 'hosts', 'host_vars') + os.makedirs(host_vars_pathname, exist_ok=True) + for asset, accounts in self.relation_asset_map.items(): + account_info = [] + for account in accounts: + private_key_filename = f'{asset.name}_{account.username}' if account.private_key else '' + account_info.append({ + 'username': account.username, + 'password': account.password, + 'private_key_filename': private_key_filename, + }) + host_vars = { + 'ansible_host': asset.get_target_ip(), + 'ansible_port': asset.get_target_ssh_port(), # TODO 需要根绝协议取端口号 + 'account_info': account_info, + } + pathname = os.path.join(host_vars_pathname, f'{asset.name}.yml') + with open(pathname, 'w', encoding='utf8') as f: + f.write(yaml.dump(host_vars, allow_unicode=True)) + + def generate_secret_key_files(self): + file_pathname = os.path.join(self.temp_folder, self.strategy, 'files') + os.makedirs(file_pathname, exist_ok=True) + for asset, accounts in self.relation_asset_map.items(): + for account in accounts: + if account.private_key: + path_name = os.path.join(file_pathname, f'{asset.name}_{account.username}') + with open(path_name, 'w', encoding='utf8') as f: + f.write(account.private_key) + + def execute(self): + self.generate_temp_playbook() + self.generate_hosts() + self.generate_host_vars() + self.generate_secret_key_files() + # self.generate_role_main() # TODO Linux 暂时不需要 diff --git a/apps/assets/playbooks/strategy/verify/roles/linux/main.yml b/apps/assets/playbooks/strategy/verify/roles/linux/main.yml index ba07ece17..03c666df7 100644 --- a/apps/assets/playbooks/strategy/verify/roles/linux/main.yml +++ b/apps/assets/playbooks/strategy/verify/roles/linux/main.yml @@ -1,7 +1,5 @@ - hosts: all vars: connection_type: ssh - password: - value: {{ password}} roles: - linux diff --git a/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml b/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml index 0bf6e8ee1..ff9e1eb99 100644 --- a/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml +++ b/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml @@ -1,8 +1,8 @@ - name: Verify user ping: vars: - ansible_user: "{{ item }}" - ansible_pass: "{{ password }}" + ansible_user: "{{ item.username }}" + ansible_pass: "{{ item.username }}" ansible_connection: "{{ connection_type | default('ssh') }}" - ansible_ssh_private_key_file: "{{ private_key_file }}" - with_items: "{{ usernames }}" + ansible_ssh_private_key_file: "{{ item.private_key_file }}" + with_items: "{{ account_info }}" From 9b2acfe4a4524f5d9192d1cdfb5a21d69f8f9ca4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 15 Sep 2022 21:20:56 +0800 Subject: [PATCH 129/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/network.py | 8 ++++---- apps/assets/const.py | 20 +++++++++---------- .../migrations/0096_auto_20220426_1550.py | 2 +- .../migrations/0099_auto_20220711_1409.py | 4 ++-- .../migrations/0106_auto_20220819_1523.py | 2 +- apps/assets/models/asset/__init__.py | 2 +- .../models/asset/{networking.py => device.py} | 3 +-- apps/assets/models/base.py | 2 +- apps/assets/serializers/account/__init__.py | 4 ++-- .../{account_history.py => history.py} | 0 .../{account_template.py => template.py} | 3 ++- apps/assets/serializers/asset/__init__.py | 2 +- apps/assets/serializers/asset/category.py | 4 ++-- .../asset/{networking.py => device.py} | 4 ++-- apps/assets/serializers/platform.py | 3 +++ apps/assets/urls/api_urls.py | 4 ++-- 16 files changed, 35 insertions(+), 32 deletions(-) rename apps/assets/models/asset/{networking.py => device.py} (59%) rename apps/assets/serializers/account/{account_history.py => history.py} (100%) rename apps/assets/serializers/account/{account_template.py => template.py} (92%) rename apps/assets/serializers/asset/{networking.py => device.py} (71%) diff --git a/apps/assets/api/asset/network.py b/apps/assets/api/asset/network.py index 608a70d1a..e64f69e1f 100644 --- a/apps/assets/api/asset/network.py +++ b/apps/assets/api/asset/network.py @@ -1,13 +1,13 @@ from assets.serializers import HostSerializer -from assets.models import Networking +from assets.models import Device from .asset import AssetViewSet -__all__ = ['NetworkViewSet'] +__all__ = ['DeviceViewSet'] -class NetworkViewSet(AssetViewSet): - model = Networking +class DeviceViewSet(AssetViewSet): + model = Device def get_serializer_classes(self): serializer_classes = super().get_serializer_classes() diff --git a/apps/assets/const.py b/apps/assets/const.py index 5a46d8d31..9c5603052 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -6,7 +6,7 @@ from common.tree import TreeNode __all__ = [ - 'Category', 'HostTypes', 'NetworkingTypes', 'DatabaseTypes', + 'Category', 'HostTypes', 'DeviceTypes', 'DatabaseTypes', 'WebTypes', 'CloudTypes', 'Protocol', 'AllTypes', ] @@ -17,7 +17,7 @@ class PlatformMixin: return { 'domain_enabled': False, 'su_enabled': False, - 'vendor_enabled': False, + 'brand_enabled': False, 'ping_enabled': False, 'gather_facts_enabled': False, 'change_password_enabled': False, @@ -30,9 +30,9 @@ class PlatformMixin: class Category(PlatformMixin, ChoicesMixin, models.TextChoices): HOST = 'host', _('Host') - NETWORKING = 'networking', _("NetworkDevice") + DEVICE = 'device', _("Device") DATABASE = 'database', _("Database") - CLOUD = 'cloud', _("Clouding") + CLOUD = 'cloud', _("Cloud service") WEB = 'web', _("Web") @classmethod @@ -49,7 +49,7 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): 'gather_accounts_enabled': True, 'gather_accounts_method': 'gather_accounts_posix', '_protocols': ['ssh', 'telnet'], }, - cls.NETWORKING: { + cls.DEVICE: { 'domain_enabled': True, 'brand_enabled': True, 'brands': [ @@ -108,7 +108,7 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): LINUX = 'linux', 'Linux' WINDOWS = 'windows', 'Windows' UNIX = 'unix', 'Unix' - OTHER_HOST = 'other_host', _("Other host") + OTHER_HOST = 'other', _("Other") @classmethod def platform_constraints(cls): @@ -131,7 +131,7 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): } -class NetworkingTypes(PlatformMixin, ChoicesMixin, models.TextChoices): +class DeviceTypes(PlatformMixin, ChoicesMixin, models.TextChoices): GENERAL = 'general', _("General device") SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") @@ -163,7 +163,7 @@ class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices): - WEBSITE = 'website', _('General Website') + WEBSITE = 'website', _('General website') class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): @@ -181,7 +181,7 @@ class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): choices: list includes = [ - HostTypes, NetworkingTypes, DatabaseTypes, + HostTypes, DeviceTypes, DatabaseTypes, WebTypes, CloudTypes ] @@ -210,7 +210,7 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): def category_types(cls): return ( (Category.HOST, HostTypes), - (Category.NETWORKING, NetworkingTypes), + (Category.DEVICE, DeviceTypes), (Category.DATABASE, DatabaseTypes), (Category.WEB, WebTypes), (Category.CLOUD, CloudTypes) diff --git a/apps/assets/migrations/0096_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py index bf7a93ac3..a0b1035d0 100644 --- a/apps/assets/migrations/0096_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -23,7 +23,7 @@ class Migration(migrations.Migration): bases=('assets.asset',), ), migrations.CreateModel( - name='Networking', + name='Device', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), ], diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index af767c864..78495549c 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -31,7 +31,7 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('privileged', models.BooleanField(default=False, verbose_name='Privileged account')), + ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('version', models.IntegerField(default=0, verbose_name='Version')), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), @@ -64,7 +64,7 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('privileged', models.BooleanField(default=False, verbose_name='Privileged account')), + ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('version', models.IntegerField(default=0, verbose_name='Version')), ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset')), ('su_from', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from')), diff --git a/apps/assets/migrations/0106_auto_20220819_1523.py b/apps/assets/migrations/0106_auto_20220819_1523.py index c77fdb09b..81c4e29a9 100644 --- a/apps/assets/migrations/0106_auto_20220819_1523.py +++ b/apps/assets/migrations/0106_auto_20220819_1523.py @@ -28,7 +28,7 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), ('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')), - ('privileged', models.BooleanField(default=False, verbose_name='Privileged account')), + ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ], options={ 'verbose_name': 'Account template', diff --git a/apps/assets/models/asset/__init__.py b/apps/assets/models/asset/__init__.py index 914a440ba..793df7455 100644 --- a/apps/assets/models/asset/__init__.py +++ b/apps/assets/models/asset/__init__.py @@ -1,6 +1,6 @@ from .common import * from .host import * from .database import * -from .networking import * +from .device import * from .web import * from .cloud import * diff --git a/apps/assets/models/asset/networking.py b/apps/assets/models/asset/device.py similarity index 59% rename from apps/assets/models/asset/networking.py rename to apps/assets/models/asset/device.py index 48d73a4d9..24c1d2bd4 100644 --- a/apps/assets/models/asset/networking.py +++ b/apps/assets/models/asset/device.py @@ -2,6 +2,5 @@ from .common import Asset -class Networking(Asset): - +class Device(Asset): pass diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index ce7b99da3..a18c7ae5e 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -62,7 +62,7 @@ class BaseAccount(OrgModelMixin): private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) - privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) + privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) diff --git a/apps/assets/serializers/account/__init__.py b/apps/assets/serializers/account/__init__.py index 7ef87134a..70c013231 100644 --- a/apps/assets/serializers/account/__init__.py +++ b/apps/assets/serializers/account/__init__.py @@ -1,3 +1,3 @@ from .account import * -from .account_history import * -from .account_template import * +from .history import * +from .template import * diff --git a/apps/assets/serializers/account/account_history.py b/apps/assets/serializers/account/history.py similarity index 100% rename from apps/assets/serializers/account/account_history.py rename to apps/assets/serializers/account/history.py diff --git a/apps/assets/serializers/account/account_template.py b/apps/assets/serializers/account/template.py similarity index 92% rename from apps/assets/serializers/account/account_template.py rename to apps/assets/serializers/account/template.py index 5fe53c26c..1df7ea65a 100644 --- a/apps/assets/serializers/account/account_template.py +++ b/apps/assets/serializers/account/template.py @@ -10,12 +10,13 @@ from .common import AccountFieldsSerializerMixin class AccountTemplateSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class Meta: model = AccountTemplate - fields_mini = ['id', 'privileged', 'username'] + fields_mini = ['id', 'name', 'username', 'privileged'] fields_write_only = AccountFieldsSerializerMixin.Meta.fields_write_only fields_other = AccountFieldsSerializerMixin.Meta.fields_other fields = fields_mini + fields_write_only + fields_other extra_kwargs = { 'username': {'required': True}, + 'name': {'required': True}, 'private_key': {'write_only': True}, 'public_key': {'write_only': True}, } diff --git a/apps/assets/serializers/asset/__init__.py b/apps/assets/serializers/asset/__init__.py index 93d35b736..12f1eb66c 100644 --- a/apps/assets/serializers/asset/__init__.py +++ b/apps/assets/serializers/asset/__init__.py @@ -1,6 +1,6 @@ from .common import * from .host import * from .database import * -from .networking import * +from .device import * from .cloud import * from .web import * diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py index 1b69dc79d..8cf62d99d 100644 --- a/apps/assets/serializers/asset/category.py +++ b/apps/assets/serializers/asset/category.py @@ -1,4 +1,4 @@ -from assets.models import Networking +from assets.models import Device from .common import AssetSerializer __all__ = ['NetworkingSerializer'] @@ -6,4 +6,4 @@ __all__ = ['NetworkingSerializer'] class NetworkingSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): - model = Networking + model = Device diff --git a/apps/assets/serializers/asset/networking.py b/apps/assets/serializers/asset/device.py similarity index 71% rename from apps/assets/serializers/asset/networking.py rename to apps/assets/serializers/asset/device.py index aff838bd5..1c6e59f9f 100644 --- a/apps/assets/serializers/asset/networking.py +++ b/apps/assets/serializers/asset/device.py @@ -1,5 +1,5 @@ -from assets.models import Networking +from assets.models import Device from .common import AssetSerializer __all__ = ['NetworkingSerializer'] @@ -7,4 +7,4 @@ __all__ = ['NetworkingSerializer'] class NetworkingSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): - model = Networking + model = Device diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 9c94a45f2..785cf6c39 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -38,6 +38,8 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): 'gather_accounts_enabled', 'gather_accounts_method', ] extra_kwargs = { + 'ping_enabled': {'label': '启用资产探测'}, + 'ping_method': {'label': '探测方式'}, 'gather_facts_enabled': {'label': '启用收集信息'}, 'gather_facts_method': {'label': '收集信息方式'}, 'verify_account_enabled': {'label': '启用校验账号'}, @@ -82,6 +84,7 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): ] extra_kwargs = { 'su_enabled': {'label': '启用切换账号'}, + 'protocols_enabled': {'label': '启用协议'}, 'domain_enabled': {'label': "启用网域"}, 'domain_default': {'label': "默认网域"}, } diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 6a233cf07..04df314f9 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -9,10 +9,10 @@ app_name = 'assets' router = BulkRouter() router.register(r'assets', api.AssetViewSet, 'asset') router.register(r'hosts', api.HostViewSet, 'host') +router.register(r'devices', api.DeviceViewSet, 'device') router.register(r'databases', api.DatabaseViewSet, 'database') -router.register(r'web', api.WebViewSet, 'web') +router.register(r'webs', api.WebViewSet, 'web') router.register(r'clouds', api.CloudViewSet, 'cloud') -router.register(r'networks', api.NetworkViewSet, 'network') router.register(r'accounts', api.AccountViewSet, 'account') router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') From a86d5c14561ab1b8a69ffcebafb5b1d626c24e03 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 16 Sep 2022 11:45:50 +0800 Subject: [PATCH 130/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0093_auto_20220403_1627.py | 16 +++++------- .../migrations/0099_auto_20220711_1409.py | 12 +++++++-- apps/assets/models/utils.py | 25 +++++++++++++------ .../host/change_password_linux/main.yml | 0 .../change_password/roles/linux/main.yml | 2 +- 5 files changed, 35 insertions(+), 20 deletions(-) delete mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/main.yml diff --git a/apps/assets/migrations/0093_auto_20220403_1627.py b/apps/assets/migrations/0093_auto_20220403_1627.py index b013efdfe..0fe74ed50 100644 --- a/apps/assets/migrations/0093_auto_20220403_1627.py +++ b/apps/assets/migrations/0093_auto_20220403_1627.py @@ -9,19 +9,16 @@ def migrate_to_host(apps, schema_editor): host_model = apps.get_model("assets", 'Host') db_alias = schema_editor.connection.alias - created = 0 + count = 0 batch_size = 1000 while True: - start = created - end = created + batch_size - assets = asset_model.objects.using(db_alias).all()[start:end] + assets = asset_model.objects.using(db_alias).all()[count:count+batch_size] if not assets: break - + count += len(assets) hosts = [host_model(asset_ptr=asset) for asset in assets] host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True) - created += len(hosts) def migrate_hardware_info(apps, *args): @@ -36,15 +33,14 @@ def migrate_hardware_info(apps, *args): ] while True: - start = count - end = count + batch_size - assets = asset_model.objects.all()[start:end] + assets = asset_model.objects.all()[count:count+batch_size] if not assets: break + count += len(assets) updated = [] for asset in assets: - info = {getattr(asset, field) for field in hardware_fields if getattr(asset, field)} + info = {field: getattr(asset, field) for field in hardware_fields if getattr(asset, field)} if not info: continue asset.info = info diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index 78495549c..7e5e7e0e6 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -39,7 +39,6 @@ class Migration(migrations.Migration): ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), ('asset', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.asset', verbose_name='Asset')), ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), - ('su_from', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.account', verbose_name='Su from')), ], options={ 'verbose_name': 'historical Account', @@ -67,7 +66,6 @@ class Migration(migrations.Migration): ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ('version', models.IntegerField(default=0, verbose_name='Version')), ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset')), - ('su_from', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from')), ], options={ 'verbose_name': 'Account', @@ -75,4 +73,14 @@ class Migration(migrations.Migration): 'unique_together': {('username', 'asset'), ('name', 'asset')}, }, ), + migrations.AddField( + model_name='account', + name='su_from', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from'), + ), + migrations.AddField( + model_name='historicalaccount', + name='su_from', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.account', verbose_name='Su from'), + ), ] diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index e2c89cd63..c8e8c6a08 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -36,9 +36,20 @@ def update_internal_platforms(platform_model): 'change_password_method': 'change_password_aix', }, {'name': 'Windows', 'category': 'host', 'type': 'windows'}, - {'name': 'Windows-TLS', 'category': 'host', 'type': 'windows'}, - {'name': 'Windows-RDP', 'category': 'host', 'type': 'windows'}, - + { + 'name': 'Windows-TLS', 'category': 'host', 'type': 'windows', + 'protocols': [ + {'name': 'rdp', 'port': 3389, 'setting': {'security': 'tls'}}, + {'name': 'ssh', 'port': 22}, + ] + }, + { + 'name': 'Windows-RDP', 'category': 'host', 'type': 'windows', + 'protocols': [ + {'name': 'rdp', 'port': 3389, 'setting': {'security': 'rdp'}}, + {'name': 'ssh', 'port': 22}, + ] + }, # 数据库 {'name': 'MySQL', 'category': 'database', 'type': 'mysql'}, {'name': 'PostgreSQL', 'category': 'database', 'type': 'postgresql'}, @@ -48,10 +59,10 @@ def update_internal_platforms(platform_model): {'name': 'Redis', 'category': 'database', 'type': 'redis'}, # 网络设备 - {'name': 'Generic', 'category': 'networking', 'type': 'general'}, - {'name': 'Huawei', 'category': 'networking', 'type': 'general'}, - {'name': 'Cisco', 'category': 'networking', 'type': 'general'}, - {'name': 'H3C', 'category': 'networking', 'type': 'general'}, + {'name': 'Generic', 'category': 'networking', 'type': 'general', 'brand': 'other'}, + {'name': 'Huawei', 'category': 'networking', 'type': 'general', 'brand': 'huawei'}, + {'name': 'Cisco', 'category': 'networking', 'type': 'general', 'brand': 'cisco'}, + {'name': 'H3C', 'category': 'networking', 'type': 'general', 'brand': 'h3c'}, # Web diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml b/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml index cb229d9d6..16f0d1037 100644 --- a/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml +++ b/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml @@ -2,7 +2,7 @@ vars: connection_type: ssh password: - value: {{ password}} + value: {{ password }} public_key: value: {{ jms_key }} exclusive: {{ exclusive }} From 389094f615e1f590c46b86e87fecb2f8d9db0049 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Fri, 16 Sep 2022 17:24:27 +0800 Subject: [PATCH 131/488] =?UTF-8?q?perf:=20=E8=B4=A6=E5=8F=B7=E5=A4=87?= =?UTF-8?q?=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0109_auto_20220916_1556.py | 66 +++++++++++++++++++ apps/assets/models/backup.py | 61 ++--------------- apps/assets/serializers/backup.py | 6 +- apps/assets/serializers/base.py | 22 ------- apps/assets/task_handlers/backup/handlers.py | 29 ++++---- 5 files changed, 90 insertions(+), 94 deletions(-) create mode 100644 apps/assets/migrations/0109_auto_20220916_1556.py diff --git a/apps/assets/migrations/0109_auto_20220916_1556.py b/apps/assets/migrations/0109_auto_20220916_1556.py new file mode 100644 index 000000000..de9e6c0d9 --- /dev/null +++ b/apps/assets/migrations/0109_auto_20220916_1556.py @@ -0,0 +1,66 @@ +# Generated by Django 3.2.13 on 2022-09-16 07:56 +from functools import reduce +from django.db import migrations, models +from assets.const import AllTypes, HostTypes + + +def migrate_backup_types(apps, schema_editor): + all_types = list(reduce( + lambda x, y: x + y, + [ + [j['value'] for j in i['children']] + for i in AllTypes.grouped_choices_to_objs() + ] + )) + asset_types = [i[0] for i in HostTypes.choices] + app_types = list(set(all_types) - set(asset_types)) + + backup_model = apps.get_model("assets", "AccountBackupPlan") + backup_objs = [] + for instance in backup_model.objects.all(): + types = instance.types + if types == 1: + instance.categories = asset_types + elif types == 2: + instance.categories = app_types + elif types == 255: + instance.categories = all_types + else: + instance.categories = [] + backup_objs.append(instance) + backup_model.objects.bulk_update(backup_objs, ['categories']) + + backup_execution_model = apps.get_model("assets", "AccountBackupPlanExecution") + backup_execution_objs = [] + for instance in backup_execution_model.objects.all(): + types = instance.plan_snapshot.get('types', []) + if 'all' in types: + instance.plan_snapshot['categories'] = all_types + elif 'asset' in types: + instance.plan_snapshot['categories'] = asset_types + elif 'application' in types: + instance.plan_snapshot['categories'] = app_types + else: + instance.categories = [] + instance.plan_snapshot.pop('types', None) + backup_execution_objs.append(instance) + backup_execution_model.objects.bulk_update(backup_execution_objs, ['plan_snapshot']) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0108_auto_20220915_1032'), + ] + + operations = [ + migrations.AddField( + model_name='accountbackupplan', + name='categories', + field=models.JSONField(default=list), + ), + migrations.RunPython(migrate_backup_types), + migrations.RemoveField( + model_name='accountbackupplan', + name='types', + ), + ] diff --git a/apps/assets/models/backup.py b/apps/assets/models/backup.py index d7788a350..3a27adb45 100644 --- a/apps/assets/models/backup.py +++ b/apps/assets/models/backup.py @@ -12,66 +12,17 @@ from orgs.mixins.models import OrgModelMixin from ops.mixin import PeriodTaskModelMixin from common.utils import get_logger from common.db.encoder import ModelJSONFieldEncoder -from common.db.models import BitOperationChoice from common.mixins.models import CommonModelMixin from common.const.choices import Trigger -from ..const import AllTypes, Category -__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution', 'Type'] +__all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution'] logger = get_logger(__file__) -def _choice_map(default=None): - offset = 0 - temp_key = 0b1 - - if default is None: - _all = (0b1 << 32) - 1 - else: - _all = default - - choices = { - _all: ('all', 'All') - } - - for info in AllTypes.grouped_choices_to_objs(): - temp_keys = [] - for c in info['children']: - key = temp_key << offset - temp_keys.append(key) - choices[key] = (c['value'], c['display_name']) - offset += 1 - parent_key = reduce(lambda x, y: x | y, temp_keys) - choices[parent_key] = (info['value'], info['display_name']) - return choices - - -class Type(BitOperationChoice): - NONE = 0 - - ALL = (0b1 << 32) - 1 - TYPE_MAP = _choice_map(ALL) - - DB_CHOICES = tuple((k, v[1]) for k, v in TYPE_MAP.items()) - - NAME_MAP = {k: v[0] for k, v in TYPE_MAP.items()} - - NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} - CHOICES = [] - for i, j in DB_CHOICES: - CHOICES.append((NAME_MAP[i], j)) - - @classmethod - def get_types(cls, value: int) -> list: - exclude_types = ['all'] + Category.values - current_all = cls.value_to_choices(value) - return list(filter(lambda x: x not in exclude_types, current_all)) - - class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - types = models.BigIntegerField() + categories = models.JSONField(default=list) recipients = models.ManyToManyField( 'users.User', related_name='recipient_escape_route_plans', blank=True, verbose_name=_("Recipient") @@ -102,7 +53,7 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): 'crontab': self.crontab, 'org_id': self.org_id, 'created_by': self.created_by, - 'types': Type.get_types(self.types), + 'categories': self.categories, 'recipients': { str(recipient.id): (str(recipient), bool(recipient.secret_key)) for recipient in self.recipients.all() @@ -149,9 +100,9 @@ class AccountBackupPlanExecution(OrgModelMixin): verbose_name = _('Account backup execution') @property - def types(self): - types = self.plan_snapshot.get('types') - return types + def categories(self): + categories = self.plan_snapshot.get('categories') + return categories @property def recipients(self): diff --git a/apps/assets/serializers/backup.py b/apps/assets/serializers/backup.py index c95d0f394..c95a806d3 100644 --- a/apps/assets/serializers/backup.py +++ b/apps/assets/serializers/backup.py @@ -7,8 +7,6 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ops.mixin import PeriodTaskSerializerMixin from common.utils import get_logger -from .base import TypesField - from ..models import AccountBackupPlan, AccountBackupPlanExecution logger = get_logger(__file__) @@ -17,14 +15,12 @@ __all__ = ['AccountBackupPlanSerializer', 'AccountBackupPlanExecutionSerializer' class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer): - types = TypesField(required=False, allow_null=True, label=_("Actions")) - class Meta: model = AccountBackupPlan fields = [ 'id', 'name', 'is_periodic', 'interval', 'crontab', 'date_created', 'date_updated', 'created_by', 'periodic_display', 'comment', - 'recipients', 'types' + 'recipients', 'categories' ] extra_kwargs = { 'name': {'required': True}, diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index e421b8f43..91aa2213a 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -7,7 +7,6 @@ from rest_framework import serializers from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key from common.drf.fields import EncryptedField -from assets.models import Type from .utils import validate_password_for_ansible @@ -71,24 +70,3 @@ class AuthValidateMixin(serializers.Serializer): def update(self, instance, validated_data): self.clean_auth_fields(validated_data) return super().update(instance, validated_data) - - -class TypesField(serializers.MultipleChoiceField): - def __init__(self, **kwargs): - kwargs['choices'] = Type.CHOICES - super().__init__(**kwargs) - - def to_representation(self, value): - return Type.value_to_choices(value) - - def to_internal_value(self, data): - if data is None: - return data - return Type.choices_to_value(data) - - -class ActionsDisplayField(TypesField): - def to_representation(self, value): - values = super().to_representation(value) - choices = dict(Type.CHOICES) - return [choices.get(i) for i in values] diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index 3b8eac87d..d8c7955c9 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -7,7 +7,8 @@ from django.conf import settings from django.db.models import F from rest_framework import serializers -from assets.models import Account, Type +from assets.models import Account +from assets.const import AllTypes from assets.serializers import AccountSecretSerializer from assets.notifications import AccountBackupExecutionTaskMsg from users.models import User @@ -76,25 +77,29 @@ class AssetAccountHandler(BaseAccountHandler): return filename @classmethod - def create_data_map(cls, types: list): + def create_data_map(cls, categories: list): data_map = defaultdict(list) - # TODO 可以优化一下查询 在账号上做type的缓存 避免数据量大时连表操作 + # TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作 qs = Account.objects.filter( - asset__platform__type__in=types - ).annotate(type=F('asset__platform__type')) + asset__platform__category__in=categories + ).annotate(category=F('asset__platform__category')) if not qs.exists(): return data_map - type_dict = dict(Type.CHOICES) + category_dict = {} + for i in AllTypes.grouped_choices_to_objs(): + for j in i['children']: + category_dict[j['value']] = j['display_name'] + header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first())) - account_type_map = defaultdict(list) + account_category_map = defaultdict(list) for account in qs: - account_type_map[account.type].append(account) + account_category_map[account.category].append(account) data_map = {} - for tp, accounts in account_type_map.items(): - sheet_name = type_dict[tp] + for category, accounts in account_category_map.items(): + sheet_name = category_dict.get(category, category) data = AccountSecretSerializer(accounts, many=True).data data_map.update(cls.add_rows(data, header_fields, sheet_name)) @@ -117,9 +122,9 @@ class AccountBackupHandler: # Print task start date time_start = time.time() files = [] - types = self.execution.types + categories = self.execution.categories - data_map = AssetAccountHandler.create_data_map(types) + data_map = AssetAccountHandler.create_data_map(categories) if not data_map: return files From 65331e13aca87deece7484bbbd0e23569fff2f32 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 19 Sep 2022 00:07:59 +0800 Subject: [PATCH 132/488] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const.py | 317 ---------------------------------- apps/assets/const/__init__.py | 3 + apps/assets/const/base.py | 30 ++++ apps/assets/const/category.py | 23 +++ apps/assets/const/cloud.py | 31 ++++ apps/assets/const/database.py | 36 ++++ apps/assets/const/device.py | 30 ++++ apps/assets/const/host.py | 40 +++++ apps/assets/const/protocol.py | 59 +++++++ apps/assets/const/types.py | 93 ++++++++++ apps/assets/const/web.py | 16 ++ 11 files changed, 361 insertions(+), 317 deletions(-) delete mode 100644 apps/assets/const.py create mode 100644 apps/assets/const/__init__.py create mode 100644 apps/assets/const/base.py create mode 100644 apps/assets/const/category.py create mode 100644 apps/assets/const/cloud.py create mode 100644 apps/assets/const/database.py create mode 100644 apps/assets/const/device.py create mode 100644 apps/assets/const/host.py create mode 100644 apps/assets/const/protocol.py create mode 100644 apps/assets/const/types.py create mode 100644 apps/assets/const/web.py diff --git a/apps/assets/const.py b/apps/assets/const.py deleted file mode 100644 index 9c5603052..000000000 --- a/apps/assets/const.py +++ /dev/null @@ -1,317 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - -from common.db.models import IncludesTextChoicesMeta, ChoicesMixin -from common.tree import TreeNode - - -__all__ = [ - 'Category', 'HostTypes', 'DeviceTypes', 'DatabaseTypes', - 'WebTypes', 'CloudTypes', 'Protocol', 'AllTypes', -] - - -class PlatformMixin: - @classmethod - def platform_constraints(cls): - return { - 'domain_enabled': False, - 'su_enabled': False, - 'brand_enabled': False, - 'ping_enabled': False, - 'gather_facts_enabled': False, - 'change_password_enabled': False, - 'verify_account_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': [] - } - - -class Category(PlatformMixin, ChoicesMixin, models.TextChoices): - HOST = 'host', _('Host') - DEVICE = 'device', _("Device") - DATABASE = 'database', _("Database") - CLOUD = 'cloud', _("Cloud service") - WEB = 'web', _("Web") - - @classmethod - def platform_constraints(cls) -> dict: - return { - cls.HOST: { - 'domain_enabled': True, - 'su_enabled': True, 'su_method': 'sudo', - 'ping_enabled': True, 'ping_method': 'ping', - 'gather_facts_enabled': True, 'gather_facts_method': 'gather_facts_posix', - 'verify_account_enabled': True, 'verify_account_method': 'verify_account_posix', - 'change_password_enabled': True, 'change_password_method': 'change_password_posix', - 'create_account_enabled': True, 'create_account_method': 'create_account_posix', - 'gather_accounts_enabled': True, 'gather_accounts_method': 'gather_accounts_posix', - '_protocols': ['ssh', 'telnet'], - }, - cls.DEVICE: { - 'domain_enabled': True, - 'brand_enabled': True, - 'brands': [ - ('huawei', 'Huawei'), - ('cisco', 'Cisco'), - ('juniper', 'Juniper'), - ('h3c', 'H3C'), - ('dell', 'Dell'), - ('other', 'Other'), - ], - 'su_enabled': False, - 'ping_enabled': True, 'ping_method': 'ping', - 'gather_facts_enabled': False, - 'verify_account_enabled': False, - 'change_password_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': ['ssh', 'telnet'] - }, - cls.DATABASE: { - 'domain_enabled': True, - 'su_enabled': False, - 'gather_facts_enabled': True, - 'verify_account_enabled': True, - 'change_password_enabled': True, - 'create_account_enabled': True, - 'gather_accounts_enabled': True, - '_protocols': [] - }, - cls.WEB: { - 'domain_enabled': False, - 'su_enabled': False, - 'ping_enabled': False, - 'gather_facts_enabled': False, - 'verify_account_enabled': False, - 'change_password_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': ['http', 'https'] - }, - cls.CLOUD: { - 'domain_enabled': False, - 'su_enabled': False, - 'ping_enabled': False, - 'gather_facts_enabled': False, - 'verify_account_enabled': False, - 'change_password_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': [] - } - } - - -class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices): - LINUX = 'linux', 'Linux' - WINDOWS = 'windows', 'Windows' - UNIX = 'unix', 'Unix' - OTHER_HOST = 'other', _("Other") - - @classmethod - def platform_constraints(cls): - return { - cls.LINUX: { - '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] - }, - cls.WINDOWS: { - 'gather_facts_method': 'gather_facts_windows', - 'verify_account_method': 'verify_account_windows', - 'change_password_method': 'change_password_windows', - 'create_account_method': 'create_account_windows', - 'gather_accounts_method': 'gather_accounts_windows', - '_protocols': ['rdp', 'ssh', 'vnc'], - 'su_enabled': False - }, - cls.UNIX: { - '_protocols': ['ssh', 'vnc'] - } - } - - -class DeviceTypes(PlatformMixin, ChoicesMixin, models.TextChoices): - GENERAL = 'general', _("General device") - SWITCH = 'switch', _("Switch") - ROUTER = 'router', _("Router") - FIREWALL = 'firewall', _("Firewall") - - -class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices): - MYSQL = 'mysql', 'MySQL' - MARIADB = 'mariadb', 'MariaDB' - POSTGRESQL = 'postgresql', 'PostgreSQL' - ORACLE = 'oracle', 'Oracle' - SQLSERVER = 'sqlserver', 'SQLServer' - MONGODB = 'mongodb', 'MongoDB' - REDIS = 'redis', 'Redis' - - @classmethod - def platform_constraints(cls): - meta = {} - for name, label in cls.choices: - meta[name] = { - '_protocols': [name], - 'gather_facts_method': f'gather_facts_{name}', - 'verify_account_method': f'verify_account_{name}', - 'change_password_method': f'change_password_{name}', - 'create_account_method': f'create_account_{name}', - 'gather_accounts_method': f'gather_accounts_{name}', - } - return meta - - -class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices): - WEBSITE = 'website', _('General website') - - -class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): - K8S = 'k8s', 'Kubernetes' - - @classmethod - def platform_constraints(cls): - return { - cls.K8S: { - '_protocols': ['k8s'] - } - } - - -class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): - choices: list - includes = [ - HostTypes, DeviceTypes, DatabaseTypes, - WebTypes, CloudTypes - ] - - @classmethod - def get_constraints(cls, category, tp): - constraints = PlatformMixin.platform_constraints() - category_constraints = Category.platform_constraints().get(category) or {} - constraints.update(category_constraints) - - types_cls = dict(cls.category_types()).get(category) - if not types_cls: - return constraints - type_constraints = types_cls.platform_constraints().get(tp) or {} - constraints.update(type_constraints) - - _protocols = constraints.pop('_protocols', []) - default_ports = Protocol.default_ports() - protocols = [] - for p in _protocols: - port = default_ports.get(p, 0) - protocols.append({'name': p, 'port': port}) - constraints['protocols'] = protocols - return constraints - - @classmethod - def category_types(cls): - return ( - (Category.HOST, HostTypes), - (Category.DEVICE, DeviceTypes), - (Category.DATABASE, DatabaseTypes), - (Category.WEB, WebTypes), - (Category.CLOUD, CloudTypes) - ) - - @classmethod - def grouped_choices(cls): - grouped_types = [(str(ca), tp.choices) for ca, tp in cls.category_types()] - return grouped_types - - @classmethod - def grouped_choices_to_objs(cls): - choices = cls.serialize_to_objs(Category.choices) - mapper = dict(cls.grouped_choices()) - for choice in choices: - children = cls.serialize_to_objs(mapper[choice['value']]) - choice['children'] = children - return choices - - @staticmethod - def serialize_to_objs(choices): - title = ['value', 'display_name'] - return [dict(zip(title, choice)) for choice in choices] - - @staticmethod - def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): - node = TreeNode(**{ - 'id': choice.name, - 'name': choice.label, - 'title': choice.label, - 'pId': pid, - 'open': opened, - 'isParent': is_parent, - }) - if meta: - node.meta = meta - return node - - @classmethod - def to_tree_nodes(cls): - root = TreeNode(id='ROOT', name='类型节点', title='类型节点') - nodes = [root] - for category, types in cls.category_types(): - category_node = cls.choice_to_node(category, 'ROOT', meta={'type': 'category'}) - nodes.append(category_node) - for tp in types: - tp_node = cls.choice_to_node(tp, category_node.id, meta={'type': 'type'}) - nodes.append(tp_node) - return nodes - - -class Protocol(ChoicesMixin, models.TextChoices): - ssh = 'ssh', 'SSH' - sftp = 'sftp', 'SFTP' - rdp = 'rdp', 'RDP' - telnet = 'telnet', 'Telnet' - vnc = 'vnc', 'VNC' - - mysql = 'mysql', 'MySQL' - mariadb = 'mariadb', 'MariaDB' - oracle = 'oracle', 'Oracle' - postgresql = 'postgresql', 'PostgreSQL' - sqlserver = 'sqlserver', 'SQLServer' - redis = 'redis', 'Redis' - mongodb = 'mongodb', 'MongoDB' - - k8s = 'k8s', 'K8S' - http = 'http', 'HTTP' - https = 'https', 'HTTPS' - - @classmethod - def host_protocols(cls): - return [cls.ssh, cls.rdp, cls.telnet, cls.vnc] - - @classmethod - def db_protocols(cls): - return [ - cls.mysql, cls.mariadb, cls.postgresql, cls.oracle, - cls.sqlserver, cls.redis, cls.mongodb, - ] - - @classmethod - def default_ports(cls): - return { - cls.ssh: 22, - cls.sftp: 22, - cls.rdp: 3389, - cls.vnc: 5900, - cls.telnet: 21, - - cls.mysql: 3306, - cls.mariadb: 3306, - cls.postgresql: 5432, - cls.oracle: 1521, - cls.sqlserver: 1433, - cls.mongodb: 27017, - cls.redis: 6379, - - cls.k8s: 0, - - cls.http: 80, - cls.https: 443 - } - diff --git a/apps/assets/const/__init__.py b/apps/assets/const/__init__.py new file mode 100644 index 000000000..e3e822fb3 --- /dev/null +++ b/apps/assets/const/__init__.py @@ -0,0 +1,3 @@ +from .protocol import * +from .category import * +from .types import * diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py new file mode 100644 index 000000000..cb1419636 --- /dev/null +++ b/apps/assets/const/base.py @@ -0,0 +1,30 @@ + + +class ConstrainMixin: + def get_constrains(self): + pass + + def _get_category_constrains(self) -> dict: + raise NotImplementedError + + def _get_protocol_constrains(self) -> dict: + raise NotImplementedError + + def _get_automation_constrains(self) -> dict: + raise NotImplementedError + + @classmethod + def platform_constraints(cls): + return { + 'domain_enabled': False, + 'su_enabled': False, + 'brand_enabled': False, + 'ping_enabled': False, + 'gather_facts_enabled': False, + 'change_password_enabled': False, + 'verify_account_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, + '_protocols': [] + } + diff --git a/apps/assets/const/category.py b/apps/assets/const/category.py new file mode 100644 index 000000000..db3c96ebc --- /dev/null +++ b/apps/assets/const/category.py @@ -0,0 +1,23 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from common.db.models import IncludesTextChoicesMeta, ChoicesMixin + + + +__all__ = [ + 'Category', 'ConstrainMixin' +] + + + +class Category(ConstrainMixin, ChoicesMixin, models.TextChoices): + HOST = 'host', _('Host') + DEVICE = 'device', _("Device") + DATABASE = 'database', _("Database") + CLOUD = 'cloud', _("Cloud service") + WEB = 'web', _("Web") + + + + diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py new file mode 100644 index 000000000..95e0be171 --- /dev/null +++ b/apps/assets/const/cloud.py @@ -0,0 +1,31 @@ +from django.db import models + +from common.db.models import ChoicesMixin + + +from .category import ConstrainMixin + + +class CloudTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): + K8S = 'k8s', 'Kubernetes' + + def category_constrains(self): + return { + 'domain_enabled': False, + 'su_enabled': False, + 'ping_enabled': False, + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, + '_protocols': [] + } + + @classmethod + def platform_constraints(cls): + return { + cls.K8S: { + '_protocols': ['k8s'] + } + } diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py new file mode 100644 index 000000000..a80b89529 --- /dev/null +++ b/apps/assets/const/database.py @@ -0,0 +1,36 @@ + + +class DatabaseTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): + MYSQL = 'mysql', 'MySQL' + MARIADB = 'mariadb', 'MariaDB' + POSTGRESQL = 'postgresql', 'PostgreSQL' + ORACLE = 'oracle', 'Oracle' + SQLSERVER = 'sqlserver', 'SQLServer' + MONGODB = 'mongodb', 'MongoDB' + REDIS = 'redis', 'Redis' + + def category_constrains(self): + return { + 'domain_enabled': True, + 'su_enabled': False, + 'gather_facts_enabled': True, + 'verify_account_enabled': True, + 'change_password_enabled': True, + 'create_account_enabled': True, + 'gather_accounts_enabled': True, + '_protocols': [] + } + + @classmethod + def platform_constraints(cls): + meta = {} + for name, label in cls.choices: + meta[name] = { + '_protocols': [name], + 'gather_facts_method': f'gather_facts_{name}', + 'verify_account_method': f'verify_account_{name}', + 'change_password_method': f'change_password_{name}', + 'create_account_method': f'create_account_{name}', + 'gather_accounts_method': f'gather_accounts_{name}', + } + return meta diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py new file mode 100644 index 000000000..488806b3d --- /dev/null +++ b/apps/assets/const/device.py @@ -0,0 +1,30 @@ + + +class DeviceTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): + GENERAL = 'general', _("General device") + SWITCH = 'switch', _("Switch") + ROUTER = 'router', _("Router") + FIREWALL = 'firewall', _("Firewall") + + @classmethod + def category_constrains(cls): + return { + 'domain_enabled': True, + 'brand_enabled': True, + 'brands': [ + ('huawei', 'Huawei'), + ('cisco', 'Cisco'), + ('juniper', 'Juniper'), + ('h3c', 'H3C'), + ('dell', 'Dell'), + ('other', 'Other'), + ], + 'su_enabled': False, + 'ping_enabled': True, 'ping_method': 'ping', + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, + '_protocols': ['ssh', 'telnet'] + } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py new file mode 100644 index 000000000..1ffc0e728 --- /dev/null +++ b/apps/assets/const/host.py @@ -0,0 +1,40 @@ + +class HostTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): + LINUX = 'linux', 'Linux' + WINDOWS = 'windows', 'Windows' + UNIX = 'unix', 'Unix' + OTHER_HOST = 'other', _("Other") + + @staticmethod + def category_constrains(): + return { + 'domain_enabled': True, + 'su_enabled': True, 'su_method': 'sudo', + 'ping_enabled': True, 'ping_method': 'ping', + 'gather_facts_enabled': True, 'gather_facts_method': 'gather_facts_posix', + 'verify_account_enabled': True, 'verify_account_method': 'verify_account_posix', + 'change_password_enabled': True, 'change_password_method': 'change_password_posix', + 'create_account_enabled': True, 'create_account_method': 'create_account_posix', + 'gather_accounts_enabled': True, 'gather_accounts_method': 'gather_accounts_posix', + '_protocols': ['ssh', 'telnet'], + } + + @classmethod + def platform_constraints(cls): + return { + cls.LINUX: { + '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] + }, + cls.WINDOWS: { + 'gather_facts_method': 'gather_facts_windows', + 'verify_account_method': 'verify_account_windows', + 'change_password_method': 'change_password_windows', + 'create_account_method': 'create_account_windows', + 'gather_accounts_method': 'gather_accounts_windows', + '_protocols': ['rdp', 'ssh', 'vnc'], + 'su_enabled': False + }, + cls.UNIX: { + '_protocols': ['ssh', 'vnc'] + } + } \ No newline at end of file diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py new file mode 100644 index 000000000..7b283428b --- /dev/null +++ b/apps/assets/const/protocol.py @@ -0,0 +1,59 @@ +from django.db import models +from common.db.models import ChoicesMixin + +__all__ = ['Protocol'] + + +class Protocol(ChoicesMixin, models.TextChoices): + ssh = 'ssh', 'SSH' + sftp = 'sftp', 'SFTP' + rdp = 'rdp', 'RDP' + telnet = 'telnet', 'Telnet' + vnc = 'vnc', 'VNC' + + mysql = 'mysql', 'MySQL' + mariadb = 'mariadb', 'MariaDB' + oracle = 'oracle', 'Oracle' + postgresql = 'postgresql', 'PostgreSQL' + sqlserver = 'sqlserver', 'SQLServer' + redis = 'redis', 'Redis' + mongodb = 'mongodb', 'MongoDB' + + k8s = 'k8s', 'K8S' + http = 'http', 'HTTP' + https = 'https', 'HTTPS' + + @classmethod + def host_protocols(cls): + return [cls.ssh, cls.rdp, cls.telnet, cls.vnc] + + @classmethod + def db_protocols(cls): + return [ + cls.mysql, cls.mariadb, cls.postgresql, cls.oracle, + cls.sqlserver, cls.redis, cls.mongodb, + ] + + @classmethod + def default_ports(cls): + return { + cls.ssh: 22, + cls.sftp: 22, + cls.rdp: 3389, + cls.vnc: 5900, + cls.telnet: 21, + + cls.mysql: 3306, + cls.mariadb: 3306, + cls.postgresql: 5432, + cls.oracle: 1521, + cls.sqlserver: 1433, + cls.mongodb: 27017, + cls.redis: 6379, + + cls.k8s: 0, + + cls.http: 80, + cls.https: 443 + } + diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py new file mode 100644 index 000000000..e19a7c28d --- /dev/null +++ b/apps/assets/const/types.py @@ -0,0 +1,93 @@ +from common.db.models import IncludesTextChoicesMeta, ChoicesMixin +from common.tree import TreeNode + +from .category import Category +from .host import HostTypes +from .device import DeviceTypes +from .database import DatabaseTypes +from .web import WebTypes +from .cloud import CloudTypes + + +class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): + choices: list + includes = [ + HostTypes, DeviceTypes, DatabaseTypes, + WebTypes, CloudTypes + ] + + @classmethod + def get_constraints(cls, category, tp): + constraints = ConstrainMixin.platform_constraints() + category_constraints = Category.platform_constraints().get(category) or {} + constraints.update(category_constraints) + + types_cls = dict(cls.category_types()).get(category) + if not types_cls: + return constraints + type_constraints = types_cls.platform_constraints().get(tp) or {} + constraints.update(type_constraints) + + _protocols = constraints.pop('_protocols', []) + default_ports = Protocol.default_ports() + protocols = [] + for p in _protocols: + port = default_ports.get(p, 0) + protocols.append({'name': p, 'port': port}) + constraints['protocols'] = protocols + return constraints + + @classmethod + def category_types(cls): + return ( + (Category.HOST, HostTypes), + (Category.DEVICE, DeviceTypes), + (Category.DATABASE, DatabaseTypes), + (Category.WEB, WebTypes), + (Category.CLOUD, CloudTypes) + ) + + @classmethod + def grouped_choices(cls): + grouped_types = [(str(ca), tp.choices) for ca, tp in cls.category_types()] + return grouped_types + + @classmethod + def grouped_choices_to_objs(cls): + choices = cls.serialize_to_objs(Category.choices) + mapper = dict(cls.grouped_choices()) + for choice in choices: + children = cls.serialize_to_objs(mapper[choice['value']]) + choice['children'] = children + return choices + + @staticmethod + def serialize_to_objs(choices): + title = ['value', 'display_name'] + return [dict(zip(title, choice)) for choice in choices] + + @staticmethod + def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): + node = TreeNode(**{ + 'id': choice.name, + 'name': choice.label, + 'title': choice.label, + 'pId': pid, + 'open': opened, + 'isParent': is_parent, + }) + if meta: + node.meta = meta + return node + + @classmethod + def to_tree_nodes(cls): + root = TreeNode(id='ROOT', name='类型节点', title='类型节点') + nodes = [root] + for category, types in cls.category_types(): + category_node = cls.choice_to_node(category, 'ROOT', meta={'type': 'category'}) + nodes.append(category_node) + for tp in types: + tp_node = cls.choice_to_node(tp, category_node.id, meta={'type': 'type'}) + nodes.append(tp_node) + return nodes diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py new file mode 100644 index 000000000..f9bcffef4 --- /dev/null +++ b/apps/assets/const/web.py @@ -0,0 +1,16 @@ + +class WebTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): + WEBSITE = 'website', _('General website') + + def category_constrains(self): + return { + 'domain_enabled': False, + 'su_enabled': False, + 'ping_enabled': False, + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, + '_protocols': ['http', 'https'] + } \ No newline at end of file From b50d28ff9c1d660d195e384f42dd4dc34f3de953 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 19 Sep 2022 09:52:09 +0800 Subject: [PATCH 133/488] =?UTF-8?q?perf:=20=E6=9A=82=E5=AD=98=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - apps/assets/const.py | 7 ++- apps/assets/models/utils.py | 11 +++-- apps/assets/playbooks/base/__init__.py | 0 .../base.py => base/generator.py} | 3 +- apps/assets/playbooks/base/runner.py | 47 +++++++++++++++++++ .../playbooks/change_password/__init__.py | 0 .../database/change_password_mysql/main.yml | 10 ++++ .../change_password_mysql/manifest.yml | 6 +++ .../roles/change_password/tasks/main.yml | 27 +++++++++++ .../database/change_password_oracle/main.yml | 10 ++++ .../change_password_oracle/manifest.yml | 6 +++ .../roles/change_password/tasks/main.yml | 27 +++++++++++ .../change_password_postgresql/main.yml | 10 ++++ .../change_password_postgresql/manifest.yml | 6 +++ .../roles/change_password/tasks/main.yml | 27 +++++++++++ .../change_password_sqlserver/main.yml | 10 ++++ .../change_password_sqlserver/manifest.yml | 8 ++++ .../roles/change_password/tasks/main.yml | 27 +++++++++++ .../host/change_password_aix/main.yml | 10 ++++ .../host/change_password_aix/manifest.yml | 6 +++ .../roles/change_password/tasks/main.yml | 27 +++++++++++ .../host/change_password_linux/main.yml | 8 ++++ .../host/change_password_linux/manifest.yml | 7 +++ .../roles/change_password/tasks/main.yml | 23 +++++++++ .../change_password_local_windows/main.yml | 10 ++++ .../manifest.yml | 7 +++ .../roles/change_password/tasks/main.yml | 27 +++++++++++ .../host/ansible_posix_ping/main.yml | 13 +++++ .../host/ansible_posix_ping/manifest.yml | 10 ++++ .../playbooks/host/ansible_win_ping/main.yml | 13 +++++ .../host/ansible_win_ping/manifest.yml | 6 +++ 32 files changed, 403 insertions(+), 7 deletions(-) create mode 100644 apps/assets/playbooks/base/__init__.py rename apps/assets/playbooks/{generate_playbook/base.py => base/generator.py} (96%) create mode 100644 apps/assets/playbooks/base/runner.py create mode 100644 apps/assets/playbooks/change_password/__init__.py create mode 100644 apps/assets/playbooks/change_password/database/change_password_mysql/main.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_oracle/main.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml create mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/main.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/main.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml create mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml create mode 100644 apps/assets/playbooks/host/ansible_posix_ping/main.yml create mode 100644 apps/assets/playbooks/host/ansible_posix_ping/manifest.yml create mode 100644 apps/assets/playbooks/host/ansible_win_ping/main.yml create mode 100644 apps/assets/playbooks/host/ansible_win_ping/manifest.yml diff --git a/.gitignore b/.gitignore index 372644811..ec0378141 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,6 @@ media celerybeat.pid django.db celerybeat-schedule.db -data/static docs/_build/ xpack xpack.bak diff --git a/apps/assets/const.py b/apps/assets/const.py index 9c5603052..f403a4131 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -16,6 +16,7 @@ class PlatformMixin: def platform_constraints(cls): return { 'domain_enabled': False, + 'url_enabled': False, 'su_enabled': False, 'brand_enabled': False, 'ping_enabled': False, @@ -88,7 +89,7 @@ class Category(PlatformMixin, ChoicesMixin, models.TextChoices): 'change_password_enabled': False, 'create_account_enabled': False, 'gather_accounts_enabled': False, - '_protocols': ['http', 'https'] + '_protocols': ['http'] }, cls.CLOUD: { 'domain_enabled': False, @@ -168,12 +169,16 @@ class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices): class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices): K8S = 'k8s', 'Kubernetes' + VSPHERE = 'vsphere', 'VMware vSphere' @classmethod def platform_constraints(cls): return { cls.K8S: { '_protocols': ['k8s'] + }, + cls.VSPHERE: { + '_protocols': ['vsphere'] } } diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index c8e8c6a08..dfc2ace2c 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -59,14 +59,17 @@ def update_internal_platforms(platform_model): {'name': 'Redis', 'category': 'database', 'type': 'redis'}, # 网络设备 - {'name': 'Generic', 'category': 'networking', 'type': 'general', 'brand': 'other'}, - {'name': 'Huawei', 'category': 'networking', 'type': 'general', 'brand': 'huawei'}, - {'name': 'Cisco', 'category': 'networking', 'type': 'general', 'brand': 'cisco'}, - {'name': 'H3C', 'category': 'networking', 'type': 'general', 'brand': 'h3c'}, + {'name': 'Generic', 'category': 'device', 'type': 'general', 'brand': 'other'}, + {'name': 'Huawei', 'category': 'device', 'type': 'general', 'brand': 'huawei'}, + {'name': 'Cisco', 'category': 'device', 'type': 'general', 'brand': 'cisco'}, + {'name': 'H3C', 'category': 'device', 'type': 'general', 'brand': 'h3c'}, # Web + {'name': 'Website', 'category': 'web', 'type': 'general'}, # Cloud + {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, + {'name': 'VMware vSphere', 'category': 'cloud', 'type': 'vsphere'}, ] platforms = platform_model.objects.all() diff --git a/apps/assets/playbooks/base/__init__.py b/apps/assets/playbooks/base/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/playbooks/generate_playbook/base.py b/apps/assets/playbooks/base/generator.py similarity index 96% rename from apps/assets/playbooks/generate_playbook/base.py rename to apps/assets/playbooks/base/generator.py index 12a39f12c..972d12409 100644 --- a/apps/assets/playbooks/generate_playbook/base.py +++ b/apps/assets/playbooks/base/generator.py @@ -4,10 +4,11 @@ import shutil from typing import List from django.conf import settings + from assets.models import Asset -class BaseGeneratePlaybook: +class BaseRunner: src_filepath: str def __init__(self, assets: List[Asset], strategy): diff --git a/apps/assets/playbooks/base/runner.py b/apps/assets/playbooks/base/runner.py new file mode 100644 index 000000000..1370db6ba --- /dev/null +++ b/apps/assets/playbooks/base/runner.py @@ -0,0 +1,47 @@ +import os +import tempfile +import shutil +from typing import List + +from django.conf import settings + +from assets.models import Asset + + +class BasePlaybookGenerator: + def __init__(self, assets: list[Asset], strategy, ansible_connection='ssh'): + self.assets = assets + self.strategy = strategy + self.playbook_dir = self.temp_folder_path() + + def generate(self): + self.prepare_playbook_dir() + self.generate_inventory() + self.generate_playbook() + + def prepare_playbook_dir(self): + pass + + def generate_inventory(self): + pass + + def generate_playbook(self): + pass + + @property + def base_dir(self): + tmp_dir = os.path.join(settings.PROJECT_DIR, 'tmp') + path = os.path.join(tmp_dir, self.strategy) + return path + + def temp_folder_path(self): + return tempfile.mkdtemp(dir=self.base_dir) + + def del_temp_folder(self): + shutil.rmtree(self.playbook_dir) + + def generate_temp_playbook(self): + src = self.src_filepath + dst = os.path.join(self.temp_folder, self.strategy) + shutil.copytree(src, dst) + return dst diff --git a/apps/assets/playbooks/change_password/__init__.py b/apps/assets/playbooks/change_password/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml new file mode 100644 index 000000000..043549ec6 --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml @@ -0,0 +1,6 @@ +id: change_password_mysql +name: Change password for MySQL +category: database +type: + - mysql +method: change_password diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml new file mode 100644 index 000000000..d3bab86e1 --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml @@ -0,0 +1,6 @@ +id: change_password_oracle +name: Change password for Oracle +method: change_password +category: database +type: + - oracle diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml new file mode 100644 index 000000000..9abe184be --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: change_password_postgresql +name: Change password for PostgreSQL +category: database +type: + - postgresql +method: change_password diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml new file mode 100644 index 000000000..b16a24dc9 --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml @@ -0,0 +1,8 @@ +id: change_password_sqlserver +name: Change password for SQLServer +version: 1 +category: database +type: + - sqlserver +method: change_password + diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_aix/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml new file mode 100644 index 000000000..451c10f8e --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml @@ -0,0 +1,6 @@ +id: change_password_aix +name: Change password for AIX +category: host +type: + - aix +method: change_password diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml new file mode 100644 index 000000000..a7d0f9417 --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml @@ -0,0 +1,8 @@ +- hosts: all + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml new file mode 100644 index 000000000..25183c25d --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml @@ -0,0 +1,7 @@ +id: change_password_linux +name: Change password for Linux +category: host +type: + - unix + - linux +method: change_password diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..e0ba9c73f --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml @@ -0,0 +1,23 @@ +- name: Check connection + ping: + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('sha512') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml new file mode 100644 index 000000000..7f34008e6 --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml @@ -0,0 +1,7 @@ +id: change_password_local_windows +name: Change password local account for Windows +version: 1 +method: change_password +category: host +type: + - windows diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/host/ansible_posix_ping/main.yml b/apps/assets/playbooks/host/ansible_posix_ping/main.yml new file mode 100644 index 000000000..4ccdb3074 --- /dev/null +++ b/apps/assets/playbooks/host/ansible_posix_ping/main.yml @@ -0,0 +1,13 @@ +- hosts: centos + gather_facts: no + vars: + account: + username: web + password: test123 + + tasks: + - name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" diff --git a/apps/assets/playbooks/host/ansible_posix_ping/manifest.yml b/apps/assets/playbooks/host/ansible_posix_ping/manifest.yml new file mode 100644 index 000000000..6cd223f1c --- /dev/null +++ b/apps/assets/playbooks/host/ansible_posix_ping/manifest.yml @@ -0,0 +1,10 @@ +id: ansible_posix_ping +name: Ansible posix ping +description: Ansible ping +category: host +type: + - linux + - unix + - macos + - bsd +method: verify_account diff --git a/apps/assets/playbooks/host/ansible_win_ping/main.yml b/apps/assets/playbooks/host/ansible_win_ping/main.yml new file mode 100644 index 000000000..726d04a53 --- /dev/null +++ b/apps/assets/playbooks/host/ansible_win_ping/main.yml @@ -0,0 +1,13 @@ +- hosts: centos + gather_facts: no + vars: + account: + username: web + password: test123 + + tasks: + - name: Verify password + win_ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" diff --git a/apps/assets/playbooks/host/ansible_win_ping/manifest.yml b/apps/assets/playbooks/host/ansible_win_ping/manifest.yml new file mode 100644 index 000000000..fe881de3b --- /dev/null +++ b/apps/assets/playbooks/host/ansible_win_ping/manifest.yml @@ -0,0 +1,6 @@ +id: ansible_win_ping +name: Ansible win ping +category: host +type: + - windows +method: verify_account From 108ccf5a8b383eb190e5a4d86f3d9ab51f2b851f Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 19 Sep 2022 17:00:03 +0800 Subject: [PATCH 134/488] =?UTF-8?q?perf:=20=E8=B4=A6=E5=8F=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/account.py | 36 +++------------------ apps/assets/api/account/history.py | 7 ++-- apps/assets/filters.py | 37 +++++++++++++++++++--- apps/assets/serializers/account/account.py | 29 +++++++++-------- apps/assets/tasks/account_connectivity.py | 4 +-- apps/rbac/const.py | 8 ++--- apps/rbac/tree.py | 2 +- 7 files changed, 62 insertions(+), 61 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 25a465e1d..5113d104b 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -1,45 +1,19 @@ -from django_filters import rest_framework as filters from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.generics import CreateAPIView from orgs.mixins.api import OrgBulkModelViewSet from rbac.permissions import RBACPermission -from common.drf.filters import BaseFilterSet, UUIDInFilter + from common.mixins import RecordViewLogMixin from common.permissions import UserConfirmation from authentication.const import ConfirmType +from assets.models import Account +from assets.filters import AccountFilterSet from assets.tasks.account_connectivity import test_accounts_connectivity_manual -from assets.models import Account, Node from assets import serializers -__all__ = ['AccountFilterSet', 'AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] - - -class AccountFilterSet(BaseFilterSet): - ip = filters.CharFilter(field_name='ip', lookup_expr='exact') - hostname = filters.CharFilter(field_name='name', lookup_expr='exact') - username = filters.CharFilter(field_name="username", lookup_expr='exact') - assets = UUIDInFilter(field_name='asset_id', lookup_expr='in') - nodes = UUIDInFilter(method='filter_nodes') - - def filter_nodes(self, queryset, name, value): - nodes = Node.objects.filter(id__in=value) - if not nodes: - return queryset - - node_qs = Node.objects.none() - for node in nodes: - node_qs |= node.get_all_children(with_self=True) - node_ids = list(node_qs.values_list('id', flat=True)) - queryset = queryset.filter(asset__nodes__in=node_ids) - return queryset - - class Meta: - model = Account - fields = [ - 'asset', 'id' - ] +__all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] class AccountViewSet(OrgBulkModelViewSet): @@ -52,7 +26,7 @@ class AccountViewSet(OrgBulkModelViewSet): 'verify': serializers.AssetTaskSerializer } rbac_perms = { - 'verify': 'assets.test_authbook', + 'verify': 'assets.test_account', 'partial_update': 'assets.change_assetaccountsecret', } diff --git a/apps/assets/api/account/history.py b/apps/assets/api/account/history.py index 0db682177..0ddc659ad 100644 --- a/apps/assets/api/account/history.py +++ b/apps/assets/api/account/history.py @@ -1,9 +1,8 @@ -from .account import ( - AccountFilterSet, AccountViewSet, AccountSecretsViewSet -) -from common.mixins import RecordViewLogMixin from assets import serializers from assets.models import Account +from assets.filters import AccountFilterSet +from common.mixins import RecordViewLogMixin +from .account import AccountViewSet, AccountSecretsViewSet __all__ = ['AccountHistoryViewSet', 'AccountHistorySecretsViewSet'] diff --git a/apps/assets/filters.py b/apps/assets/filters.py index dd8bf80a4..fc75b16a8 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # - -from rest_framework.compat import coreapi, coreschema -from rest_framework import filters from django.db.models import Q +from rest_framework import filters +from rest_framework.compat import coreapi, coreschema -from .models import Label +from common.drf.filters import BaseFilterSet, UUIDInFilter from assets.utils import is_query_node_all_assets, get_node_from_request +from .models import Label, Node, Account class AssetByNodeFilterBackend(filters.BaseFilterBackend): @@ -109,7 +109,7 @@ class LabelFilterBackend(filters.BaseFilterBackend): q = Q(name=key, value=value) if not q: return [] - labels = Label.objects.filter(q, is_active=True)\ + labels = Label.objects.filter(q, is_active=True) \ .values_list('id', flat=True) return labels @@ -154,3 +154,30 @@ class IpInFilterBackend(filters.BaseFilterBackend): ) ) ] + + +class AccountFilterSet(BaseFilterSet): + from django_filters import rest_framework as filters + ip = filters.CharFilter(field_name='ip', lookup_expr='exact') + hostname = filters.CharFilter(field_name='name', lookup_expr='exact') + username = filters.CharFilter(field_name="username", lookup_expr='exact') + assets = UUIDInFilter(field_name='asset_id', lookup_expr='in') + nodes = UUIDInFilter(method='filter_nodes') + + def filter_nodes(self, queryset, name, value): + nodes = Node.objects.filter(id__in=value) + if not nodes: + return queryset + + node_qs = Node.objects.none() + for node in nodes: + node_qs |= node.get_all_children(with_self=True) + node_ids = list(node_qs.values_list('id', flat=True)) + queryset = queryset.filter(asset__nodes__in=node_ids) + return queryset + + class Meta: + model = Account + fields = [ + 'asset', 'id' + ] diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 4ddcc1d49..8adca0a35 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -1,4 +1,3 @@ -from django.db.models import F from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers @@ -21,20 +20,20 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): @staticmethod def validate_template(value): - AccountTemplate.objects.get_or_create() - model = AccountTemplate try: - return model.objects.get(id=value) + return AccountTemplate.objects.get(id=value) except AccountTemplate.DoesNotExist: raise serializers.ValidationError(_('Account template not found')) @staticmethod def replace_attrs(account_template: AccountTemplate, attrs: dict): exclude_fields = [ - '_state', 'org_id', 'date_verified', 'id', - 'date_created', 'date_updated', 'created_by' + '_state', 'org_id', 'id', 'date_created', 'date_updated' ] - template_attrs = {k: v for k, v in account_template.__dict__.items() if k not in exclude_fields} + template_attrs = { + k: v for k, v in account_template.__dict__.items() + if k not in exclude_fields + } for k, v in template_attrs.items(): attrs.setdefault(k, v) @@ -48,17 +47,19 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): def create(self, validated_data): instance = super().create(validated_data) if self.push_now: - print("Start push account to asset") # Todo: push it - pass + print("Start push account to asset") return instance -class AccountSerializer(AuthValidateMixin, - AccountSerializerCreateMixin, - AccountFieldsSerializerMixin, - BulkOrgResourceModelSerializer): - asset = ObjectRelatedField(required=False, queryset=Asset.objects, label=_('Asset'), attrs=('id', 'name', 'ip')) +class AccountSerializer( + AuthValidateMixin, AccountSerializerCreateMixin, + AccountFieldsSerializerMixin, BulkOrgResourceModelSerializer +): + asset = ObjectRelatedField( + required=False, queryset=Asset.objects, + label=_('Asset'), attrs=('id', 'name', 'ip') + ) platform = serializers.ReadOnlyField(label=_("Platform")) class Meta(AccountFieldsSerializerMixin.Meta): diff --git a/apps/assets/tasks/account_connectivity.py b/apps/assets/tasks/account_connectivity.py index cec4ed3af..c28f93110 100644 --- a/apps/assets/tasks/account_connectivity.py +++ b/apps/assets/tasks/account_connectivity.py @@ -74,7 +74,7 @@ def test_user_connectivity(task_name, asset, username, password=None, private_ke @org_aware_func("account") def test_account_connectivity_util(account, task_name): """ - :param account: 对象 + :param account: 对象 :param task_name: :return: """ @@ -101,7 +101,7 @@ def test_account_connectivity_util(account, task_name): @shared_task(queue="ansible") def test_accounts_connectivity_manual(accounts): """ - :param accounts: 对象 + :param accounts: 对象 """ for account in accounts: task_name = gettext_noop("Test account connectivity: ") + str(account) diff --git a/apps/rbac/const.py b/apps/rbac/const.py index f8789a110..037358be0 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -35,14 +35,14 @@ exclude_permissions = ( ('assets', 'assetgroup', '*', '*'), ('assets', 'cluster', '*', '*'), ('assets', 'favoriteasset', '*', '*'), - ('assets', 'historicalauthbook', '*', '*'), + ('assets', 'historicalaccount', '*', '*'), ('assets', 'assetuser', '*', '*'), ('assets', 'gathereduser', 'add,delete,change', 'gathereduser'), ('assets', 'accountbackupplanexecution', 'delete,change', 'accountbackupplanexecution'), - ('assets', 'authbook', 'change', 'authbook'), + ('assets', 'account', 'change', 'account'), # TODO 暂时去掉历史账号的权限 - ('assets', 'authbook', '*', 'assethistoryaccount'), - ('assets', 'authbook', '*', 'assethistoryaccountsecret'), + ('assets', 'account', '*', 'assethistoryaccount'), + ('assets', 'account', '*', 'assethistoryaccountsecret'), ('perms', 'userassetgrantedtreenoderelation', '*', '*'), ('perms', 'usergrantedmappingnode', '*', '*'), diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py index 0b08c565d..abce5759a 100644 --- a/apps/rbac/tree.py +++ b/apps/rbac/tree.py @@ -61,7 +61,7 @@ extra_nodes_data = [ # 将 model 放到其它节点下,而不是本来的 app 中 special_pid_mapper = { 'common.permission': 'view_other', - "assets.authbook": "accounts", + "assets.account": "accounts", "applications.account": "accounts", 'xpack.account': 'cloud_import', 'xpack.syncinstancedetail': 'cloud_import', From 7b4c2ce97eb0c0cb1fcad5787f6916a83e10f29b Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 19 Sep 2022 19:04:57 +0800 Subject: [PATCH 135/488] perf: xpack problem --- apps/common/utils/common.py | 1 - apps/jumpserver/settings/_xpack.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index ed45417b7..6f1e34b0a 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # import re -import socket from django.templatetags.static import static from collections import OrderedDict from itertools import chain diff --git a/apps/jumpserver/settings/_xpack.py b/apps/jumpserver/settings/_xpack.py index 2650e30b9..9f4319a35 100644 --- a/apps/jumpserver/settings/_xpack.py +++ b/apps/jumpserver/settings/_xpack.py @@ -6,8 +6,7 @@ from .. import const from .base import INSTALLED_APPS, TEMPLATES XPACK_DIR = os.path.join(const.BASE_DIR, 'xpack') -# XPACK_ENABLED = os.path.isdir(XPACK_DIR) -XPACK_ENABLED = False +XPACK_ENABLED = os.path.isdir(XPACK_DIR) XPACK_TEMPLATES_DIR = [] XPACK_CONTEXT_PROCESSOR = [] From 5d48d1ab15f723d0e85cdf909e2dbaf9eb0b44fe Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 19 Sep 2022 20:11:55 +0800 Subject: [PATCH 136/488] perf: stash it --- apps/assets/const/base.py | 63 ++++++++++++------- apps/assets/const/category.py | 10 +-- apps/assets/const/cloud.py | 51 ++++++++------- apps/assets/const/database.py | 49 ++++++++------- apps/assets/const/device.py | 50 +++++++++------ apps/assets/const/host.py | 62 +++++++++--------- apps/assets/const/types.py | 21 ++----- apps/assets/const/web.py | 45 +++++++++---- .../migrations/0101_auto_20220803_1448.py | 30 ++++----- .../migrations/0102_auto_20220803_1859.py | 2 +- apps/assets/models/__init__.py | 1 - apps/assets/models/asset/common.py | 9 ++- apps/assets/models/base.py | 2 + apps/assets/models/protocol.py | 7 --- apps/assets/serializers/asset/common.py | 2 +- apps/assets/serializers/platform.py | 17 ----- 16 files changed, 226 insertions(+), 195 deletions(-) delete mode 100644 apps/assets/models/protocol.py diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index cb1419636..cad913e6b 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -1,30 +1,51 @@ +from django.db.models import TextChoices + +from .protocol import Protocol -class ConstrainMixin: - def get_constrains(self): - pass +class BaseType(TextChoices): + """ + 约束应该考虑代是对平台对限制,避免多余对选项,如: mysql 开启 ssh, 或者开启了也没有作用, 比如 k8s 开启了 domain,目前还不支持 + """ + @classmethod + def get_constrains(cls): + constrains = {} - def _get_category_constrains(self) -> dict: - raise NotImplementedError + base = cls._get_base_constrains() + protocols = cls._get_protocol_constrains() + automation = cls._get_automation_constrains() - def _get_protocol_constrains(self) -> dict: - raise NotImplementedError + base_default = base.pop('*', {}) + protocols_default = protocols.pop('*', {}) + automation_default = automation.pop('*', {}) - def _get_automation_constrains(self) -> dict: + for k, v in cls.choices: + tp_base = {**base_default, **base.get(k, {})} + tp_auto = {**automation_default, **automation.get(k, {})} + tp_protocols = {**protocols_default, **protocols.get(k, {})} + tp_protocols = cls._parse_protocols(tp_protocols, k) + tp_constrains = {**tp_base, 'protocols': tp_protocols, 'automation': tp_auto} + constrains[k] = tp_constrains + return constrains + + @classmethod + def _parse_protocols(cls, protocol, tp): + default_ports = Protocol.default_ports() + choices = protocol.get('choices', []) + if choices == '__self__': + choices = [tp] + protocols = [{'name': name, 'port': default_ports.get(name, 0)} for name in choices] + return protocols + + @classmethod + def _get_base_constrains(cls) -> dict: raise NotImplementedError @classmethod - def platform_constraints(cls): - return { - 'domain_enabled': False, - 'su_enabled': False, - 'brand_enabled': False, - 'ping_enabled': False, - 'gather_facts_enabled': False, - 'change_password_enabled': False, - 'verify_account_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': [] - } + def _get_protocol_constrains(cls) -> dict: + raise NotImplementedError + + @classmethod + def _get_automation_constrains(cls) -> dict: + raise NotImplementedError diff --git a/apps/assets/const/category.py b/apps/assets/const/category.py index db3c96ebc..9e76946f3 100644 --- a/apps/assets/const/category.py +++ b/apps/assets/const/category.py @@ -1,17 +1,13 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from common.db.models import IncludesTextChoicesMeta, ChoicesMixin +from common.db.models import ChoicesMixin - -__all__ = [ - 'Category', 'ConstrainMixin' -] +__all__ = ['Category'] - -class Category(ConstrainMixin, ChoicesMixin, models.TextChoices): +class Category(ChoicesMixin, models.TextChoices): HOST = 'host', _('Host') DEVICE = 'device', _("Device") DATABASE = 'database', _("Database") diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index 95e0be171..81819b034 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -1,31 +1,40 @@ -from django.db import models - -from common.db.models import ChoicesMixin +from .base import BaseType -from .category import ConstrainMixin - - -class CloudTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): +class CloudTypes(BaseType): + PUBLIC = 'public', 'Public cloud' + PRIVATE = 'private', 'Private cloud' K8S = 'k8s', 'Kubernetes' - def category_constrains(self): + @classmethod + def _get_base_constrains(cls) -> dict: return { - 'domain_enabled': False, - 'su_enabled': False, - 'ping_enabled': False, - 'gather_facts_enabled': False, - 'verify_account_enabled': False, - 'change_password_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': [] + '*': { + 'domain_enabled': False, + 'su_enabled': False, + } } @classmethod - def platform_constraints(cls): - return { - cls.K8S: { - '_protocols': ['k8s'] + def _get_automation_constrains(cls) -> dict: + constrains = { + '*': { + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, + } + } + return constrains + + @classmethod + def _get_protocol_constrains(cls) -> dict: + return { + '*': { + 'choices': ['http', 'api'], + }, + cls.K8S: { + 'choices': ['k8s'] } } diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py index a80b89529..dd8026359 100644 --- a/apps/assets/const/database.py +++ b/apps/assets/const/database.py @@ -1,6 +1,8 @@ +from .base import BaseType + -class DatabaseTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): +class DatabaseTypes(BaseType): MYSQL = 'mysql', 'MySQL' MARIADB = 'mariadb', 'MariaDB' POSTGRESQL = 'postgresql', 'PostgreSQL' @@ -9,28 +11,33 @@ class DatabaseTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): MONGODB = 'mongodb', 'MongoDB' REDIS = 'redis', 'Redis' - def category_constrains(self): + @classmethod + def _get_base_constrains(cls) -> dict: return { - 'domain_enabled': True, - 'su_enabled': False, - 'gather_facts_enabled': True, - 'verify_account_enabled': True, - 'change_password_enabled': True, - 'create_account_enabled': True, - 'gather_accounts_enabled': True, - '_protocols': [] + '*': { + 'domain_enabled': True, + 'su_enabled': False, + } } @classmethod - def platform_constraints(cls): - meta = {} - for name, label in cls.choices: - meta[name] = { - '_protocols': [name], - 'gather_facts_method': f'gather_facts_{name}', - 'verify_account_method': f'verify_account_{name}', - 'change_password_method': f'change_password_{name}', - 'create_account_method': f'create_account_{name}', - 'gather_accounts_method': f'gather_accounts_{name}', + def _get_automation_constrains(cls) -> dict: + constrains = { + '*': { + 'gather_facts_enabled': True, + 'gather_accounts_enabled': True, + 'verify_account_enabled': True, + 'change_password_enabled': True, + 'create_account_enabled': True, } - return meta + } + return constrains + + @classmethod + def _get_protocol_constrains(cls) -> dict: + return { + '*': { + 'choices': '__self__', + } + } + diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index 488806b3d..a3c325341 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -1,30 +1,40 @@ +from django.utils.translation import gettext_lazy as _ + +from .base import BaseType -class DeviceTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): +class DeviceTypes(BaseType): GENERAL = 'general', _("General device") SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") FIREWALL = 'firewall', _("Firewall") @classmethod - def category_constrains(cls): + def _get_base_constrains(cls) -> dict: return { - 'domain_enabled': True, - 'brand_enabled': True, - 'brands': [ - ('huawei', 'Huawei'), - ('cisco', 'Cisco'), - ('juniper', 'Juniper'), - ('h3c', 'H3C'), - ('dell', 'Dell'), - ('other', 'Other'), - ], - 'su_enabled': False, - 'ping_enabled': True, 'ping_method': 'ping', - 'gather_facts_enabled': False, - 'verify_account_enabled': False, - 'change_password_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': ['ssh', 'telnet'] + '*': { + 'domain_enabled': True, + 'su_enabled': False, + } + } + + @classmethod + def _get_protocol_constrains(cls) -> dict: + return { + '*': { + 'choices': ['ssh', 'telnet'] + } + } + + @classmethod + def _get_automation_constrains(cls) -> dict: + return { + '*': { + 'ping_enabled': True, + 'gather_facts_enabled': False, + 'gather_accounts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + } } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 1ffc0e728..f317fa292 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -1,40 +1,44 @@ +from .base import BaseType -class HostTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): + +class HostTypes(BaseType): LINUX = 'linux', 'Linux' WINDOWS = 'windows', 'Windows' UNIX = 'unix', 'Unix' - OTHER_HOST = 'other', _("Other") + OTHER_HOST = 'other', "Other" - @staticmethod - def category_constrains(): + @classmethod + def _get_base_constrains(cls) -> dict: return { - 'domain_enabled': True, - 'su_enabled': True, 'su_method': 'sudo', - 'ping_enabled': True, 'ping_method': 'ping', - 'gather_facts_enabled': True, 'gather_facts_method': 'gather_facts_posix', - 'verify_account_enabled': True, 'verify_account_method': 'verify_account_posix', - 'change_password_enabled': True, 'change_password_method': 'change_password_posix', - 'create_account_enabled': True, 'create_account_method': 'create_account_posix', - 'gather_accounts_enabled': True, 'gather_accounts_method': 'gather_accounts_posix', - '_protocols': ['ssh', 'telnet'], + '*': { + 'domain_enabled': True, + 'su_enabled': True, + }, + cls.WINDOWS: { + 'su_enabled': False, + }, + cls.OTHER_HOST: { + 'su_enabled': False, + } } @classmethod - def platform_constraints(cls): + def _get_protocol_constrains(cls) -> dict: return { - cls.LINUX: { - '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] - }, - cls.WINDOWS: { - 'gather_facts_method': 'gather_facts_windows', - 'verify_account_method': 'verify_account_windows', - 'change_password_method': 'change_password_windows', - 'create_account_method': 'create_account_windows', - 'gather_accounts_method': 'gather_accounts_windows', - '_protocols': ['rdp', 'ssh', 'vnc'], - 'su_enabled': False - }, - cls.UNIX: { - '_protocols': ['ssh', 'vnc'] + '*': { + 'choices': ['ssh', 'telnet', 'vnc', 'rdp'] } - } \ No newline at end of file + } + + @classmethod + def _get_automation_constrains(cls) -> dict: + return { + '*': { + 'ping_enabled': True, + 'gather_facts_enabled': True, + 'gather_accounts_enabled': True, + 'verify_account_enabled': True, + 'change_password_enabled': True, + 'create_account_enabled': True, + } + } diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index e19a7c28d..ebee189aa 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -1,12 +1,14 @@ from common.db.models import IncludesTextChoicesMeta, ChoicesMixin from common.tree import TreeNode +from .base import BaseType from .category import Category from .host import HostTypes from .device import DeviceTypes from .database import DatabaseTypes from .web import WebTypes from .cloud import CloudTypes +from .protocol import Protocol class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): @@ -18,24 +20,11 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): @classmethod def get_constraints(cls, category, tp): - constraints = ConstrainMixin.platform_constraints() - category_constraints = Category.platform_constraints().get(category) or {} - constraints.update(category_constraints) - types_cls = dict(cls.category_types()).get(category) if not types_cls: - return constraints - type_constraints = types_cls.platform_constraints().get(tp) or {} - constraints.update(type_constraints) - - _protocols = constraints.pop('_protocols', []) - default_ports = Protocol.default_ports() - protocols = [] - for p in _protocols: - port = default_ports.get(p, 0) - protocols.append({'name': p, 'port': port}) - constraints['protocols'] = protocols - return constraints + return {} + type_constraints = types_cls.get_constrains() + return type_constraints.get(tp, {}) @classmethod def category_types(cls): diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py index f9bcffef4..48c575909 100644 --- a/apps/assets/const/web.py +++ b/apps/assets/const/web.py @@ -1,16 +1,37 @@ +from django.utils.translation import gettext_lazy as _ -class WebTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): +from .base import BaseType + + +class WebTypes(BaseType): WEBSITE = 'website', _('General website') - def category_constrains(self): + @classmethod + def _get_base_constrains(cls) -> dict: return { - 'domain_enabled': False, - 'su_enabled': False, - 'ping_enabled': False, - 'gather_facts_enabled': False, - 'verify_account_enabled': False, - 'change_password_enabled': False, - 'create_account_enabled': False, - 'gather_accounts_enabled': False, - '_protocols': ['http', 'https'] - } \ No newline at end of file + '*': { + 'domain_enabled': False, + 'su_enabled': False, + } + } + + @classmethod + def _get_automation_constrains(cls) -> dict: + constrains = { + '*': { + 'gather_facts_enabled': False, + 'verify_account_enabled': False, + 'change_password_enabled': False, + 'create_account_enabled': False, + 'gather_accounts_enabled': False, + } + } + return constrains + + @classmethod + def _get_protocol_constrains(cls) -> dict: + return { + '*': { + 'choices': ['http', 'api'], + } + } diff --git a/apps/assets/migrations/0101_auto_20220803_1448.py b/apps/assets/migrations/0101_auto_20220803_1448.py index 26f4dc36b..bfd07a7aa 100644 --- a/apps/assets/migrations/0101_auto_20220803_1448.py +++ b/apps/assets/migrations/0101_auto_20220803_1448.py @@ -10,14 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='Protocol', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=32, verbose_name='Name')), - ('port', models.IntegerField(verbose_name='Port')), - ], - ), migrations.RemoveField( model_name='asset', name='port', @@ -26,24 +18,24 @@ class Migration(migrations.Migration): model_name='asset', name='protocol', ), - migrations.AddField( + migrations.RenameField( model_name='asset', - name='_protocols', - field=models.CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'), - ), - migrations.RemoveField( - model_name='asset', - name='protocols', + old_name='protocols', + new_name='_protocols', ), migrations.AlterField( model_name='systemuser', name='protocol', field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'), ), - migrations.AddField( - model_name='asset', - name='protocols', - field=models.ManyToManyField(blank=True, to='assets.Protocol', verbose_name='Protocols'), + migrations.CreateModel( + name='Protocol', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32, verbose_name='Name')), + ('port', models.IntegerField(verbose_name='Port')), + ('asset', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.asset', verbose_name='Asset')), + ], ), migrations.DeleteModel( name='Cluster', diff --git a/apps/assets/migrations/0102_auto_20220803_1859.py b/apps/assets/migrations/0102_auto_20220803_1859.py index e220da05a..266d9b37b 100644 --- a/apps/assets/migrations/0102_auto_20220803_1859.py +++ b/apps/assets/migrations/0102_auto_20220803_1859.py @@ -31,7 +31,7 @@ def migrate_asset_protocols(apps, schema_editor): protocol = protocol_map.get(name_port) if not protocol: protocol = protocol_model.objects.get_or_create( - defaults={'name': name, 'port': port}, + defaults={'name': name, 'port': port, 'asset': asset}, name=name, port=port )[0] assets_protocols.append(asset_protocol_through(asset_id=asset.id, protocol_id=protocol.id)) diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 6b4bd31a1..e79b2fff5 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -13,6 +13,5 @@ from .backup import * from ._user import * # 废弃以下 # from ._authbook import * -from .protocol import * from .cmd_filter import * diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 76369b94c..7d2810da5 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -16,7 +16,7 @@ from orgs.mixins.models import OrgManager, JMSOrgBaseModel from ..platform import Platform from ..base import AbsConnectivity -__all__ = ['Asset', 'AssetQuerySet', 'default_node'] +__all__ = ['Asset', 'AssetQuerySet', 'default_node', 'Protocol'] logger = logging.getLogger(__name__) @@ -72,11 +72,16 @@ class NodesRelationMixin: return nodes +class Protocol(models.Model): + name = models.CharField(max_length=32, verbose_name=_("Name")) + port = models.IntegerField(verbose_name=_("Port")) + asset = models.ForeignKey('Asset', on_delete=models.CASCADE, related_name='protocols', verbose_name=_("Asset")) + + class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) - protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocols"), blank=True) platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index a18c7ae5e..d9e7097a7 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -58,6 +58,8 @@ class BaseAccount(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) + secret_type = models.CharField(max_length=16, default='password', verbose_name=_('Secret type')) + secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py deleted file mode 100644 index d42ee2754..000000000 --- a/apps/assets/models/protocol.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ - - -class Protocol(models.Model): - name = models.CharField(max_length=32, verbose_name=_("Name")) - port = models.IntegerField(verbose_name=_("Port")) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 2717547cd..325aa4f34 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -8,7 +8,7 @@ from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.fields import LabeledChoiceField, ObjectRelatedField from ..account import AccountSerializer -from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account +from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol from ...const import Category, AllTypes __all__ = [ diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 785cf6c39..380c91f01 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -89,23 +89,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): 'domain_default': {'label': "默认网域"}, } - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_brand_choices() - - def set_brand_choices(self): - field = self.fields.get('brand') - request = self.context.get('request') - if not field or not request: - return - category = request.query_params.get('category', '') - constraints = Category.platform_constraints().get(category) - if not constraints: - return - field.choices = constraints.get('brands', []) - if field.choices: - field.required = True - class PlatformOpsMethodSerializer(serializers.Serializer): id = serializers.CharField(read_only=True) From 8c72bab82df34f604d9dbe3720aa142bea50e5ad Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Sep 2022 13:54:25 +0800 Subject: [PATCH 137/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20account=20?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0099_auto_20220711_1409.py | 14 +++---- .../migrations/0100_auto_20220711_1413.py | 42 ++++++++++++++----- .../migrations/0101_auto_20220803_1448.py | 13 ------ .../migrations/0102_auto_20220803_1859.py | 26 ++++++------ .../migrations/0103_auto_20220811_1511.py | 17 +++++++- .../migrations/0106_auto_20220819_1523.py | 6 +-- .../migrations/0108_auto_20220915_1032.py | 5 --- apps/assets/models/account.py | 2 +- apps/assets/models/asset/common.py | 3 ++ apps/assets/models/base.py | 34 ++++++++++++--- apps/assets/models/domain.py | 8 +++- apps/assets/models/platform.py | 1 - apps/assets/serializers/account/common.py | 8 ++-- apps/assets/serializers/asset/common.py | 11 +---- apps/assets/serializers/platform.py | 3 +- .../migrations/0004_auto_20211201_1901.py | 1 + .../migrations/0020_auto_20220817_1346.py | 2 +- 17 files changed, 116 insertions(+), 80 deletions(-) diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index 7e5e7e0e6..f91de6483 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -23,10 +23,8 @@ class Migration(migrations.Migration): ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), - ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), - ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), - ('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')), + ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), @@ -55,10 +53,8 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), - ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), - ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), - ('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')), + ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), @@ -70,7 +66,7 @@ class Migration(migrations.Migration): options={ 'verbose_name': 'Account', 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], - 'unique_together': {('username', 'asset'), ('name', 'asset')}, + 'unique_together': {('name', 'asset'), ('username', 'asset', 'secret_type')}, }, ), migrations.AddField( diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py index 7f2e7eca7..ec84ebe2a 100644 --- a/apps/assets/migrations/0100_auto_20220711_1413.py +++ b/apps/assets/migrations/0100_auto_20220711_1413.py @@ -29,29 +29,51 @@ def migrate_accounts(apps, schema_editor): accounts = [] # auth book 和 account 相同的属性 same_attrs = [ - 'id', 'comment', 'date_created', 'date_updated', + 'id', 'username', 'comment', 'date_created', 'date_updated', 'created_by', 'asset_id', 'org_id', ] # 认证的属性,可能是 authbook 的,可能是 systemuser 的 - auth_attrs = ['username', 'password', 'private_key', 'public_key'] + auth_attrs = ['password', 'private_key', 'token'] + all_attrs = same_attrs + auth_attrs for auth_book in auth_books: - values = {attr: getattr(auth_book, attr) for attr in same_attrs} - values['version'] = 1 + values = {'version': 1} system_user = auth_book.systemuser if system_user: - values.update({attr: getattr(system_user, attr) for attr in auth_attrs}) + # 更新一次系统用户的认证属性 + values.update({attr: getattr(system_user, attr, '') for attr in all_attrs}) values['created_by'] = str(system_user.id) values['privileged'] = system_user.type == 'admin' - auth_book_auth = {attr: getattr(auth_book, attr) for attr in auth_attrs} - auth_book_auth = {attr: value for attr, value in auth_book_auth.items() if value} + auth_book_auth = {attr: getattr(auth_book, attr, '') for attr in all_attrs if getattr(auth_book, attr, '')} + # 最终使用 authbook 的认证属性 values.update(auth_book_auth) - values['name'] = values['username'] - account = account_model(**values) - accounts.append(account) + auth_infos = [] + username = values['username'] + for attr in auth_attrs: + secret = values.pop(attr, None) + if not secret: + continue + + if attr == 'private_key': + secret_type = 'ssh_key' + name = f'{username}(ssh key)' + elif attr == 'token': + secret_type = 'token' + name = f'{username}(token)' + else: + secret_type = attr + name = username + auth_infos.append((name, secret_type, secret)) + + if not auth_infos: + auth_infos.append((username, 'password', '')) + + for name, secret_type, secret in auth_infos: + account = account_model(**values, name=name, secret=secret, secret_type=secret_type) + accounts.append(account) account_model.objects.bulk_create(accounts, ignore_conflicts=True) print("Create accounts: {}-{} using: {:.2f}s".format( diff --git a/apps/assets/migrations/0101_auto_20220803_1448.py b/apps/assets/migrations/0101_auto_20220803_1448.py index bfd07a7aa..bebfbfb42 100644 --- a/apps/assets/migrations/0101_auto_20220803_1448.py +++ b/apps/assets/migrations/0101_auto_20220803_1448.py @@ -10,24 +10,11 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( - model_name='asset', - name='port', - ), - migrations.RemoveField( - model_name='asset', - name='protocol', - ), migrations.RenameField( model_name='asset', old_name='protocols', new_name='_protocols', ), - migrations.AlterField( - model_name='systemuser', - name='protocol', - field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'), - ), migrations.CreateModel( name='Protocol', fields=[ diff --git a/apps/assets/migrations/0102_auto_20220803_1859.py b/apps/assets/migrations/0102_auto_20220803_1859.py index 266d9b37b..f03332127 100644 --- a/apps/assets/migrations/0102_auto_20220803_1859.py +++ b/apps/assets/migrations/0102_auto_20220803_1859.py @@ -6,12 +6,10 @@ from django.db import migrations def migrate_asset_protocols(apps, schema_editor): asset_model = apps.get_model('assets', 'Asset') protocol_model = apps.get_model('assets', 'Protocol') - asset_protocol_through = asset_model.protocols.through count = 0 bulk_size = 1000 print("\nStart migrate asset protocols") - protocol_map = {} while True: start = time.time() assets = asset_model.objects.all()[count:count+bulk_size] @@ -19,23 +17,25 @@ def migrate_asset_protocols(apps, schema_editor): break count += len(assets) assets_protocols = [] - for asset in assets: - old_protocols = asset._protocols - for name_port in old_protocols.split(','): + for asset in assets: + old_protocols = asset._protocols or '{}/{}'.format(asset.protocol, asset.port) or 'ssh/22' + + if ',' in old_protocols: + _protocols = old_protocols.split(',') + else: + _protocols = old_protocols.split() + + for name_port in _protocols: name_port_list = name_port.split('/') if len(name_port_list) != 2: continue name, port = name_port_list - protocol = protocol_map.get(name_port) - if not protocol: - protocol = protocol_model.objects.get_or_create( - defaults={'name': name, 'port': port, 'asset': asset}, - name=name, port=port - )[0] - assets_protocols.append(asset_protocol_through(asset_id=asset.id, protocol_id=protocol.id)) - asset_model.protocols.through.objects.bulk_create(assets_protocols, ignore_conflicts=True) + protocol = protocol_model(**{'name': name, 'port': port, 'asset': asset}) + assets_protocols.append(protocol) + + protocol_model.objects.bulk_create(assets_protocols, ignore_conflicts=True) print("Create asset protocols: {}-{} using: {:.2f}s".format( count - len(assets), count, time.time()-start )) diff --git a/apps/assets/migrations/0103_auto_20220811_1511.py b/apps/assets/migrations/0103_auto_20220811_1511.py index f9dda2b56..df467d984 100644 --- a/apps/assets/migrations/0103_auto_20220811_1511.py +++ b/apps/assets/migrations/0103_auto_20220811_1511.py @@ -18,11 +18,24 @@ class Migration(migrations.Migration): ), migrations.RemoveField( model_name='asset', - name='_protocols', + name='admin_user', ), migrations.RemoveField( model_name='asset', - name='admin_user', + name='port', + ), + migrations.RemoveField( + model_name='asset', + name='protocol', + ), + migrations.RemoveField( + model_name='asset', + name='_protocols', + ), + migrations.AlterField( + model_name='systemuser', + name='protocol', + field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'), ), migrations.RemoveField( model_name='asset', diff --git a/apps/assets/migrations/0106_auto_20220819_1523.py b/apps/assets/migrations/0106_auto_20220819_1523.py index 81c4e29a9..98ec0f73a 100644 --- a/apps/assets/migrations/0106_auto_20220819_1523.py +++ b/apps/assets/migrations/0106_auto_20220819_1523.py @@ -20,14 +20,12 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), - ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), - ('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),), + ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')), ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), ], options={ diff --git a/apps/assets/migrations/0108_auto_20220915_1032.py b/apps/assets/migrations/0108_auto_20220915_1032.py index 21f663df8..3f98f70c1 100644 --- a/apps/assets/migrations/0108_auto_20220915_1032.py +++ b/apps/assets/migrations/0108_auto_20220915_1032.py @@ -11,11 +11,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='platform', - name='brand', - field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Brand'), - ), migrations.CreateModel( name='PlatformAutomation', fields=[ diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 5e141fd28..ca96adf87 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -23,7 +23,7 @@ class Account(BaseAccount): class Meta: verbose_name = _('Account') unique_together = [ - ('username', 'asset'), + ('username', 'asset', 'secret_type'), ('name', 'asset'), ] permissions = [ diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 7d2810da5..98e4fc1ac 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -77,6 +77,9 @@ class Protocol(models.Model): port = models.IntegerField(verbose_name=_("Port")) asset = models.ForeignKey('Asset', on_delete=models.CASCADE, related_name='protocols', verbose_name=_("Asset")) + def __str__(self): + return '{}/{}'.format(self.name, self.port) + class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index d9e7097a7..ba919a6a0 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -55,15 +55,17 @@ class AbsConnectivity(models.Model): class BaseAccount(OrgModelMixin): + class SecretType(models.TextChoices): + password = 'password', _('Password') + ssh_key = 'ssh_key', _('SSH key') + access_key = 'access_key', _('Access key') + token = 'token', _('Token') + id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) - secret_type = models.CharField(max_length=16, default='password', verbose_name=_('Secret type')) + secret_type = models.CharField(max_length=16, choices=SecretType.choices, default='password', verbose_name=_('Secret type')) secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) - password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) - private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) - public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) - token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token')) privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) @@ -76,6 +78,28 @@ class BaseAccount(OrgModelMixin): APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT" APP_USER_CACHE_TIME = 600 + @property + def public_key(self): + return '' + + @property + def private_key(self): + return '' + + @private_key.setter + def private_key(self, value): + self.secret = value + self.secret_type = 'private_key' + + @property + def password(self): + return self.secret + + @password.setter + def password(self, value): + self.secret = value + self.secret_type = 'password' + def expire_assets_amount(self): cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) cache.delete(cache_key) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 12da79df1..79b9deea9 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -10,6 +10,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty +from common.db import fields from orgs.mixins.models import OrgModelMixin from .base import BaseAccount @@ -64,7 +65,12 @@ class Gateway(BaseAccount): domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) - token = None + password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) + private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) + public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) + + secret = None + secret_type = None privileged = None def __str__(self): diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 513e8a7c5..afc187675 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -52,7 +52,6 @@ class Platform(models.Model): comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) # 资产有关的 charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) - brand = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Brand")) # 厂商主要是给网络设备 domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py index 6e8022aa7..dff27bbc0 100644 --- a/apps/assets/serializers/account/common.py +++ b/apps/assets/serializers/account/common.py @@ -9,16 +9,16 @@ class AccountFieldsSerializerMixin(serializers.ModelSerializer): class Meta: fields_mini = [ 'id', 'name', 'username', 'privileged', - 'platform', 'version' + 'platform', 'version', 'secret_type', ] - fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] + fields_write_only = ['secret', 'passphrase'] fields_other = ['date_created', 'date_updated', 'comment'] fields_small = fields_mini + fields_write_only + fields_other fields_fk = ['asset'] fields = fields_small + fields_fk extra_kwargs = { - 'private_key': {'write_only': True}, - 'public_key': {'write_only': True}, + 'secret': {'write_only': True}, + 'passphrase': {'write_only': True}, 'token': {'write_only': True}, 'password': {'write_only': True}, } diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 325aa4f34..0f551d05f 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -22,13 +22,6 @@ class AssetProtocolsSerializer(serializers.ModelSerializer): model = Protocol fields = ['id', 'name', 'port'] - def create(self, validated_data): - instance = Protocol.objects.filter(**validated_data).first() - if instance: - return instance - instance = Protocol.objects.create(**validated_data) - return instance - class AssetLabelSerializer(serializers.ModelSerializer): class Meta: @@ -55,10 +48,10 @@ class AssetAccountSerializer(AccountSerializer): class Meta(AccountSerializer.Meta): fields_mini = [ 'id', 'name', 'username', 'privileged', 'version', + 'secret_type', ] fields_write_only = [ - 'password', 'private_key', 'public_key', - 'passphrase', 'token', 'push_now' + 'secret', 'passphrase', 'push_now' ] fields = fields_mini + fields_write_only diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 380c91f01..72b9ea581 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -70,7 +70,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): choices=[('sudo', 'sudo su -'), ('su', 'su - ')], label='切换方式', required=False, default='sudo' ) - brand = LabeledChoiceField(choices=[], label='厂商', required=False, allow_null=True) class Meta: model = Platform @@ -80,7 +79,7 @@ class PlatformSerializer(JMSWritableNestedModelSerializer): ] fields = fields_small + [ 'protocols_enabled', 'protocols', 'domain_enabled', - 'su_enabled', 'su_method', 'brand', 'automation', 'comment', + 'su_enabled', 'su_method', 'automation', 'comment', ] extra_kwargs = { 'su_enabled': {'label': '启用切换账号'}, diff --git a/apps/rbac/migrations/0004_auto_20211201_1901.py b/apps/rbac/migrations/0004_auto_20211201_1901.py index 9d59d99fc..811876f58 100644 --- a/apps/rbac/migrations/0004_auto_20211201_1901.py +++ b/apps/rbac/migrations/0004_auto_20211201_1901.py @@ -13,6 +13,7 @@ def migrate_system_role_binding(apps, schema_editor): count = 0 bulk_size = 1000 + print('') while True: users = user_model.objects.using(db_alias) \ .only('role', 'id') \ diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index fbfc27dfd..2da21692e 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -16,7 +16,7 @@ def migrate_system_to_account(apps, schema_editor): ) for model, old_field, new_field, m2m in model_system_user_account: - print("Start migrate '{}' system user to account".format(model)) + print("Start migrate '{}' system user to account".format(model.__name__)) count = 0 bulk_size = 1000 From 615f36c6f695de07434ec0204cc7f33c0bec6ac8 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 20 Sep 2022 16:18:23 +0800 Subject: [PATCH 138/488] =?UTF-8?q?perf:=20=E6=94=B6=E9=9B=86=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 6 +++--- apps/assets/const.py | 2 +- apps/assets/models/gathered_user.py | 2 +- apps/assets/serializers/__init__.py | 2 +- apps/assets/serializers/{ => account}/backup.py | 2 +- apps/assets/serializers/account/common.py | 3 +-- apps/assets/serializers/gathered_user.py | 5 ++--- apps/assets/task_handlers/backup/handlers.py | 2 +- apps/authentication/urls/api_urls.py | 2 +- apps/common/drf/serializers.py | 2 +- 10 files changed, 13 insertions(+), 15 deletions(-) rename apps/assets/serializers/{ => account}/backup.py (95%) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index a4175b58a..fde2c5ed1 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- # +import django_filters from rest_framework.decorators import action from rest_framework.response import Response -import django_filters +from common.utils import get_logger from common.drf.filters import BaseFilterSet -from common.utils import get_logger, get_object_or_none from common.mixins.api import SuggestionMixin from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics -from assets.models import Asset, Node, Gateway from assets import serializers +from assets.models import Asset, Gateway from assets.tasks import ( update_assets_hardware_info_manual, test_assets_connectivity_manual, ) diff --git a/apps/assets/const.py b/apps/assets/const.py index 9c5603052..073b5f60a 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -232,7 +232,7 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): @staticmethod def serialize_to_objs(choices): - title = ['value', 'display_name'] + title = ['value', 'label'] return [dict(zip(title, choice)) for choice in choices] @staticmethod diff --git a/apps/assets/models/gathered_user.py b/apps/assets/models/gathered_user.py index b00ea0843..e8a2de825 100644 --- a/apps/assets/models/gathered_user.py +++ b/apps/assets/models/gathered_user.py @@ -20,7 +20,7 @@ class GatheredUser(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) @property - def hostname(self): + def name(self): return self.asset.name @property diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index ef4b40abe..93a122209 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -8,5 +8,5 @@ from .domain import * from .gathered_user import * from .favorite_asset import * from .account import * -from .backup import * +from assets.serializers.account.backup import * from .platform import * diff --git a/apps/assets/serializers/backup.py b/apps/assets/serializers/account/backup.py similarity index 95% rename from apps/assets/serializers/backup.py rename to apps/assets/serializers/account/backup.py index c95a806d3..455ef5bf3 100644 --- a/apps/assets/serializers/backup.py +++ b/apps/assets/serializers/account/backup.py @@ -7,7 +7,7 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ops.mixin import PeriodTaskSerializerMixin from common.utils import get_logger -from ..models import AccountBackupPlan, AccountBackupPlanExecution +from assets.models import AccountBackupPlan, AccountBackupPlanExecution logger = get_logger(__file__) diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py index 6e8022aa7..e20f21239 100644 --- a/apps/assets/serializers/account/common.py +++ b/apps/assets/serializers/account/common.py @@ -8,8 +8,7 @@ __all__ = ['AccountFieldsSerializerMixin'] class AccountFieldsSerializerMixin(serializers.ModelSerializer): class Meta: fields_mini = [ - 'id', 'name', 'username', 'privileged', - 'platform', 'version' + 'id', 'name', 'username', 'privileged', 'platform', 'version' ] fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] fields_other = ['date_created', 'date_updated', 'comment'] diff --git a/apps/assets/serializers/gathered_user.py b/apps/assets/serializers/gathered_user.py index 572b9f80b..6cb90f46e 100644 --- a/apps/assets/serializers/gathered_user.py +++ b/apps/assets/serializers/gathered_user.py @@ -12,11 +12,10 @@ class GatheredUserSerializer(OrgResourceModelSerializerMixin): model = GatheredUser fields_mini = ['id'] fields_small = fields_mini + [ - 'username', 'ip_last_login', - 'present', + 'username', 'ip_last_login', 'present', 'name', 'date_last_login', 'date_created', 'date_updated' ] - fields_fk = ['asset', 'name', 'ip'] + fields_fk = ['asset', 'ip'] fields = fields_small + fields_fk read_only_fields = fields extra_kwargs = { diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index d8c7955c9..2addebb86 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -90,7 +90,7 @@ class AssetAccountHandler(BaseAccountHandler): category_dict = {} for i in AllTypes.grouped_choices_to_objs(): for j in i['children']: - category_dict[j['value']] = j['display_name'] + category_dict[j['value']] = j['label'] header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first())) account_category_map = defaultdict(list) diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index cfbac879f..bca9d7430 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -32,7 +32,7 @@ urlpatterns = [ path('mfa/verify/', api.MFAChallengeVerifyApi.as_view(), name='mfa-verify'), path('mfa/challenge/', api.MFAChallengeVerifyApi.as_view(), name='mfa-challenge'), path('mfa/select/', api.MFASendCodeApi.as_view(), name='mfa-select'), - path('mfa/send-code/', api.MFASendCodeApi.as_view(), name='mfa-send-codej'), + path('mfa/send-code/', api.MFASendCodeApi.as_view(), name='mfa-send-code'), path('password/verify/', api.UserPasswordVerifyApi.as_view(), name='user-password-verify'), path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'), ] diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index ab6df10f7..aa0023b90 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -87,7 +87,7 @@ class CeleryTaskSerializer(serializers.Serializer): class ChoiceSerializer(serializers.Serializer): - display_name = serializers.CharField(label=_("Display name")) + label = serializers.CharField(label=_("Label")) value = serializers.CharField(label=_("Value")) From d0999dd1eff2b757bfb81b793895b46247d5222b Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Sep 2022 17:33:15 +0800 Subject: [PATCH 139/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=88=9B=E5=BB=BA=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/__init__.py | 1 + apps/assets/api/category.py | 22 ++++++++++ apps/assets/const/types.py | 52 +++++++++++++++++++++-- apps/assets/playbooks/__init__.py | 10 ++--- apps/assets/serializers/__init__.py | 1 + apps/assets/serializers/asset/category.py | 9 ---- apps/assets/serializers/asset/device.py | 4 +- apps/assets/serializers/cagegory.py | 15 +++++++ apps/assets/urls/api_urls.py | 2 + 9 files changed, 97 insertions(+), 19 deletions(-) delete mode 100644 apps/assets/serializers/asset/category.py create mode 100644 apps/assets/serializers/cagegory.py diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index 5dba09522..c14d8999f 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -1,4 +1,5 @@ from .mixin import * +from .category import * from .platform import * from .asset import * from .label import * diff --git a/apps/assets/api/category.py b/apps/assets/api/category.py index e69de29bb..cfcfd8c3c 100644 --- a/apps/assets/api/category.py +++ b/apps/assets/api/category.py @@ -0,0 +1,22 @@ +from rest_framework.generics import ListAPIView + +from assets.serializers import CategorySerializer, TypeSerializer +from assets.const import AllTypes + +__all__ = ['CategoryListApi', 'TypeListApi'] + + +class CategoryListApi(ListAPIView): + serializer_class = CategorySerializer + permission_classes = () + + def get_queryset(self): + return AllTypes.categories() + + +class TypeListApi(ListAPIView): + serializer_class = TypeSerializer + permission_classes = () + + def get_queryset(self): + return AllTypes.types() diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index ebee189aa..b98e208c3 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -1,14 +1,12 @@ from common.db.models import IncludesTextChoicesMeta, ChoicesMixin from common.tree import TreeNode -from .base import BaseType from .category import Category from .host import HostTypes from .device import DeviceTypes from .database import DatabaseTypes from .web import WebTypes from .cloud import CloudTypes -from .protocol import Protocol class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): @@ -17,6 +15,7 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): HostTypes, DeviceTypes, DatabaseTypes, WebTypes, CloudTypes ] + _category_constrains = {} @classmethod def get_constraints(cls, category, tp): @@ -24,7 +23,54 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): if not types_cls: return {} type_constraints = types_cls.get_constrains() - return type_constraints.get(tp, {}) + constraints = type_constraints.get(tp, {}) + cls.set_automation_methods(category, tp, constraints) + return constraints + + @classmethod + def set_automation_methods(cls, category, tp, constraints): + from assets.playbooks import filter_platform_methods + automation = constraints.get('automation', {}) + automation_methods = {} + for item, enabled in automation.items(): + if not enabled: + continue + item_name = item.replace('_enabled', '') + methods = filter_platform_methods(category, tp, item_name) + methods = [{'name': m['name'], 'id': m['id']} for m in methods] + automation_methods[item_name+'_methods'] = methods + automation.update(automation_methods) + constraints['automation'] = automation + return constraints + + @classmethod + def types(cls): + types = [] + for category, tps in cls.category_types(): + for tp in tps: + types.append(cls.serialize_type(category, tp)) + return types + + @classmethod + def categories(cls): + categories = [] + for category, tps in cls.category_types(): + category_data = { + 'id': category.value, + 'name': category.label, + 'children': [cls.serialize_type(category, tp) for tp in tps] + } + categories.append(category_data) + return categories + + @classmethod + def serialize_type(cls, category, tp): + return { + 'id': tp.value, + 'name': tp.label, + 'category': category, + 'constraints': cls.get_constraints(category, tp) + } @classmethod def category_types(cls): diff --git a/apps/assets/playbooks/__init__.py b/apps/assets/playbooks/__init__.py index 0acc08789..59f705fb9 100644 --- a/apps/assets/playbooks/__init__.py +++ b/apps/assets/playbooks/__init__.py @@ -22,7 +22,7 @@ def check_platform_methods(methods): raise ValueError("Duplicate id: {}".format(_id)) -def get_platform_methods(): +def get_platform_automation_methods(): methods = [] for root, dirs, files in os.walk(BASE_DIR, topdown=False): for name in files: @@ -47,8 +47,8 @@ def filter_key(manifest, attr, value): return value in manifest_value or 'all' in manifest_value -def filter_platform_methods(category, tp, method): - methods = platform_ops_methods +def filter_platform_methods(category, tp, method=None): + methods = platform_automation_methods if category: methods = filter(partial(filter_key, attr='category', value=category), methods) if tp: @@ -58,8 +58,8 @@ def filter_platform_methods(category, tp, method): return methods -platform_ops_methods = get_platform_methods() +platform_automation_methods = get_platform_automation_methods() if __name__ == '__main__': - print(get_platform_methods()) + print(get_platform_automation_methods()) diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index ef4b40abe..e638e84b3 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -10,3 +10,4 @@ from .favorite_asset import * from .account import * from .backup import * from .platform import * +from .cagegory import * diff --git a/apps/assets/serializers/asset/category.py b/apps/assets/serializers/asset/category.py deleted file mode 100644 index 8cf62d99d..000000000 --- a/apps/assets/serializers/asset/category.py +++ /dev/null @@ -1,9 +0,0 @@ -from assets.models import Device -from .common import AssetSerializer - -__all__ = ['NetworkingSerializer'] - - -class NetworkingSerializer(AssetSerializer): - class Meta(AssetSerializer.Meta): - model = Device diff --git a/apps/assets/serializers/asset/device.py b/apps/assets/serializers/asset/device.py index 1c6e59f9f..edad96ad1 100644 --- a/apps/assets/serializers/asset/device.py +++ b/apps/assets/serializers/asset/device.py @@ -2,9 +2,9 @@ from assets.models import Device from .common import AssetSerializer -__all__ = ['NetworkingSerializer'] +__all__ = ['DeviceSerializer'] -class NetworkingSerializer(AssetSerializer): +class DeviceSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Device diff --git a/apps/assets/serializers/cagegory.py b/apps/assets/serializers/cagegory.py new file mode 100644 index 000000000..f0b1bf67e --- /dev/null +++ b/apps/assets/serializers/cagegory.py @@ -0,0 +1,15 @@ +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ + + +class TypeSerializer(serializers.Serializer): + id = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('id')) + name = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Name')) + category = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Category')) + constraints = serializers.JSONField(required=False, allow_null=True, label=_('Constraints')) + + +class CategorySerializer(serializers.Serializer): + id = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('id')) + name = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Name')) + children = TypeSerializer(many=True, required=False, label=_('Children'), read_only=True) diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 04df314f9..57b265245 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -31,6 +31,8 @@ router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutio urlpatterns = [ # path('assets//gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), + path('categories/', api.CategoryListApi.as_view(), name='category-list'), + path('categories/types/', api.TypeListApi.as_view(), name='type-list'), path('assets//tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'), path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'), path('assets//perm-users/', api.AssetPermUserListApi.as_view(), name='asset-perm-user-list'), From cf5c50b3431dd8a5bf4a9b3a00919d56daed112f Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 20 Sep 2022 21:19:05 +0800 Subject: [PATCH 140/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/node.py | 2 +- apps/assets/const/base.py | 4 +- apps/assets/const/protocol.py | 96 +++++++++++++++++-- apps/assets/const/types.py | 2 +- .../migrations/0096_auto_20220426_1550.py | 2 +- .../migrations/0097_auto_20220426_1558.py | 2 +- apps/assets/models/asset/cloud.py | 2 +- apps/assets/models/base.py | 6 -- 8 files changed, 94 insertions(+), 22 deletions(-) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 45b2b27bf..6519c1020 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -202,7 +202,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): return [] assets = self.instance.get_assets().only( "id", "name", "ip", "platform_id", - "org_id", "protocols", "is_active", + "org_id", "is_active", ).prefetch_related('platform') return self.serialize_assets(assets, self.instance.key) diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index cad913e6b..e9d90958a 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -30,11 +30,11 @@ class BaseType(TextChoices): @classmethod def _parse_protocols(cls, protocol, tp): - default_ports = Protocol.default_ports() + settings = Protocol.settings() choices = protocol.get('choices', []) if choices == '__self__': choices = [tp] - protocols = [{'name': name, 'port': default_ports.get(name, 0)} for name in choices] + protocols = [{'name': name, **settings.get(name, {})} for name in choices] return protocols @classmethod diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index 7b283428b..5a556a17e 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -6,7 +6,6 @@ __all__ = ['Protocol'] class Protocol(ChoicesMixin, models.TextChoices): ssh = 'ssh', 'SSH' - sftp = 'sftp', 'SFTP' rdp = 'rdp', 'RDP' telnet = 'telnet', 'Telnet' vnc = 'vnc', 'VNC' @@ -24,15 +23,95 @@ class Protocol(ChoicesMixin, models.TextChoices): https = 'https', 'HTTPS' @classmethod - def host_protocols(cls): - return [cls.ssh, cls.rdp, cls.telnet, cls.vnc] + def device_settings(cls): + return { + cls.ssh: { + 'port': 22, + 'secret_type': ['password', 'ssh_key'], + 'setting': { + 'sftp_enabled': True, + 'sftp_home': '/tmp', + } + }, + cls.rdp: { + 'port': 3389, + 'secret_type': ['password'], + 'setting': { + 'console': True, + 'security': 'any', + } + }, + cls.vnc: { + 'port': 5900, + 'secret_type': ['password'], + }, + cls.telnet: { + 'port': 23, + 'secret_type': ['password'], + }, + } @classmethod - def db_protocols(cls): - return [ - cls.mysql, cls.mariadb, cls.postgresql, cls.oracle, - cls.sqlserver, cls.redis, cls.mongodb, - ] + def db_settings(cls): + return { + cls.mysql: { + 'port': 3306, + 'secret_type': ['password'], + 'setting': { + } + }, + cls.mariadb: { + 'port': 3306, + 'secret_type': ['password'], + }, + cls.postgresql: { + 'port': 5432, + 'secret_type': ['password'], + }, + cls.oracle: { + 'port': 1521, + 'secret_type': ['password'], + }, + cls.sqlserver: { + 'port': 1433, + 'secret_type': ['password'], + }, + cls.mongodb: { + 'port': 27017, + 'secret_type': ['password'], + }, + cls.redis: { + 'port': 6379, + 'secret_type': ['password'], + }, + } + + @classmethod + def cloud_settings(cls): + return { + cls.k8s: { + 'port': 443, + 'secret_type': ['token'], + 'setting': { + 'via_http': True + } + }, + cls.http: { + 'port': 80, + 'secret_type': ['password'], + 'setting': { + 'ssl': True + } + }, + } + + @classmethod + def settings(cls): + return { + **cls.device_settings(), + **cls.db_settings(), + **cls.cloud_settings() + } @classmethod def default_ports(cls): @@ -54,6 +133,5 @@ class Protocol(ChoicesMixin, models.TextChoices): cls.k8s: 0, cls.http: 80, - cls.https: 443 } diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 24c145a8b..67d4b9ff0 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -13,7 +13,7 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): choices: list includes = [ HostTypes, DeviceTypes, DatabaseTypes, - WebTypes, CloudTypes + CloudTypes, WebTypes, ] _category_constrains = {} diff --git a/apps/assets/migrations/0096_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py index a0b1035d0..e03272504 100644 --- a/apps/assets/migrations/0096_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -36,7 +36,7 @@ class Migration(migrations.Migration): name='Cloud', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('cluster', models.CharField(max_length=4096, verbose_name='Cluster')), + ('url', models.CharField(max_length=4096, verbose_name='Cluster')), ], options={ 'abstract': False, diff --git a/apps/assets/migrations/0097_auto_20220426_1558.py b/apps/assets/migrations/0097_auto_20220426_1558.py index ff82e0cf7..02cbda4ce 100644 --- a/apps/assets/migrations/0097_auto_20220426_1558.py +++ b/apps/assets/migrations/0097_auto_20220426_1558.py @@ -86,7 +86,7 @@ def migrate_cloud_to_asset(apps, *args): protocols='', platform=platform, org_id=app.org_id, - cluster=attrs.get('cluster', '') + url=attrs.get('cluster', '') ) try: diff --git a/apps/assets/models/asset/cloud.py b/apps/assets/models/asset/cloud.py index 8810f199c..f4874b9c1 100644 --- a/apps/assets/models/asset/cloud.py +++ b/apps/assets/models/asset/cloud.py @@ -5,7 +5,7 @@ from .common import Asset class Cloud(Asset): - cluster = models.CharField(max_length=4096, verbose_name=_("Cluster")) + url = models.CharField(max_length=4096, verbose_name=_("Url")) def __str__(self): return self.name diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index ba919a6a0..429a4a0a5 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -72,12 +72,6 @@ class BaseAccount(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT" - ASSET_USER_CACHE_TIME = 600 - - APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT" - APP_USER_CACHE_TIME = 600 - @property def public_key(self): return '' From 1b0195cb82dfba5b1a732f8dfd1ea7a609ac729f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 11:17:14 +0800 Subject: [PATCH 141/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20asset=20?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/account.py | 4 +- apps/assets/api/asset/asset.py | 6 +- apps/assets/api/domain.py | 4 +- apps/assets/api/mixin.py | 4 +- apps/assets/api/node.py | 2 +- apps/assets/const.py | 0 apps/assets/const/protocol.py | 69 ++++++------------- apps/assets/filters.py | 4 +- apps/assets/migrations/0092_add_host.py | 5 ++ .../migrations/0096_auto_20220426_1550.py | 2 - apps/assets/models/account.py | 2 +- apps/assets/models/asset/cloud.py | 4 -- apps/assets/models/asset/common.py | 8 +-- apps/assets/models/asset/database.py | 6 +- apps/assets/models/asset/device.py | 1 - apps/assets/models/asset/host.py | 4 +- apps/assets/models/asset/web.py | 2 - apps/assets/models/domain.py | 2 +- apps/assets/models/gathered_user.py | 2 +- apps/assets/serializers/account/account.py | 4 +- apps/assets/serializers/asset/cloud.py | 9 ++- apps/assets/serializers/asset/common.py | 6 +- apps/assets/serializers/asset/web.py | 8 ++- apps/assets/serializers/platform.py | 2 + apps/assets/tasks/gather_asset_users.py | 2 +- apps/audits/utils.py | 4 +- apps/authentication/errors/const.py | 2 +- apps/common/utils/ip/utils.py | 2 +- apps/ops/ansible/inventory.py | 2 +- apps/ops/inventory.py | 4 +- apps/perms/api/asset_permission_relation.py | 2 +- apps/perms/api/user_group_permission.py | 8 +-- .../perms/api/user_permission/assets/mixin.py | 10 +-- apps/perms/filters.py | 2 +- apps/perms/serializers/permission_relation.py | 2 +- apps/perms/serializers/user_permission.py | 2 +- jms | 2 +- 37 files changed, 95 insertions(+), 109 deletions(-) delete mode 100644 apps/assets/const.py diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 5113d104b..bb60fdfd9 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -18,8 +18,8 @@ __all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] class AccountViewSet(OrgBulkModelViewSet): model = Account - filterset_fields = ("username", "asset", 'ip', 'name') - search_fields = ('username', 'ip', 'name') + filterset_fields = ("username", "asset", 'address', 'name') + search_fields = ('username', 'address', 'name') filterset_class = AccountFilterSet serializer_classes = { 'default': serializers.AccountSerializer, diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index fde2c5ed1..f1d09f6a5 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -30,7 +30,7 @@ class AssetFilterSet(BaseFilterSet): class Meta: model = Asset - fields = ['name', 'ip', 'is_active', 'type', 'category', 'hostname'] + fields = ['name', 'address', 'is_active', 'type', 'category', 'hostname'] class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): @@ -39,8 +39,8 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): """ model = Asset filterset_class = AssetFilterSet - search_fields = ("name", "ip") - ordering_fields = ("name", "ip") + search_fields = ("name", "address") + ordering_fields = ("name", "address") ordering = ('name',) serializer_classes = ( ('default', serializers.AssetSerializer), diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 1e500c29a..2a58a21f0 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -31,8 +31,8 @@ class DomainViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet): model = Gateway - filterset_fields = ("domain__name", "name", "username", "ip", "domain") - search_fields = ("domain__name", "name", "username", "ip") + filterset_fields = ("domain__name", "name", "username", "address", "domain") + search_fields = ("domain__name", "name", "username", "address") serializer_class = serializers.GatewaySerializer diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index ff69562bb..9452a76f5 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -57,7 +57,7 @@ class SerializeToTreeNodeMixin: { 'id': str(asset.id), 'name': asset.name, - 'title': asset.ip, + 'title': asset.address, 'pId': get_pid(asset), 'isParent': False, 'open': False, @@ -68,7 +68,7 @@ class SerializeToTreeNodeMixin: 'data': { 'id': asset.id, 'name': asset.name, - 'ip': asset.ip, + 'address': asset.address, 'protocols': asset.protocols_as_list, 'platform': asset.platform.id, 'org_name': asset.org_name diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 6519c1020..a32e6644d 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -201,7 +201,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi): if not self.instance or not include_assets: return [] assets = self.instance.get_assets().only( - "id", "name", "ip", "platform_id", + "id", "name", "address", "platform_id", "org_id", "is_active", ).prefetch_related('platform') return self.serialize_assets(assets, self.instance.key) diff --git a/apps/assets/const.py b/apps/assets/const.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index 5a556a17e..23b82b082 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -20,14 +20,13 @@ class Protocol(ChoicesMixin, models.TextChoices): k8s = 'k8s', 'K8S' http = 'http', 'HTTP' - https = 'https', 'HTTPS' @classmethod - def device_settings(cls): + def device_protocols(cls): return { cls.ssh: { 'port': 22, - 'secret_type': ['password', 'ssh_key'], + 'secret_types': ['password', 'ssh_key'], 'setting': { 'sftp_enabled': True, 'sftp_home': '/tmp', @@ -35,7 +34,7 @@ class Protocol(ChoicesMixin, models.TextChoices): }, cls.rdp: { 'port': 3389, - 'secret_type': ['password'], + 'secret_types': ['password'], 'setting': { 'console': True, 'security': 'any', @@ -43,64 +42,63 @@ class Protocol(ChoicesMixin, models.TextChoices): }, cls.vnc: { 'port': 5900, - 'secret_type': ['password'], + 'secret_types': ['password'], }, cls.telnet: { 'port': 23, - 'secret_type': ['password'], + 'secret_types': ['password'], }, } @classmethod - def db_settings(cls): + def database_protocols(cls): return { cls.mysql: { 'port': 3306, - 'secret_type': ['password'], + 'secret_types': ['password'], 'setting': { } }, cls.mariadb: { 'port': 3306, - 'secret_type': ['password'], + 'secret_types': ['password'], }, cls.postgresql: { 'port': 5432, - 'secret_type': ['password'], + 'secret_types': ['password'], }, cls.oracle: { 'port': 1521, - 'secret_type': ['password'], + 'secret_types': ['password'], }, cls.sqlserver: { 'port': 1433, - 'secret_type': ['password'], + 'secret_types': ['password'], }, cls.mongodb: { 'port': 27017, - 'secret_type': ['password'], + 'secret_types': ['password'], }, cls.redis: { 'port': 6379, - 'secret_type': ['password'], + 'secret_types': ['password'], }, } @classmethod - def cloud_settings(cls): + def cloud_protocols(cls): return { cls.k8s: { 'port': 443, - 'secret_type': ['token'], - 'setting': { - 'via_http': True - } + 'secret_types': ['token'], }, cls.http: { 'port': 80, - 'secret_type': ['password'], + 'secret_types': ['password'], 'setting': { - 'ssl': True + 'username_selector': '', + 'password_selector': '', + 'submit_selector': '', } }, } @@ -108,30 +106,7 @@ class Protocol(ChoicesMixin, models.TextChoices): @classmethod def settings(cls): return { - **cls.device_settings(), - **cls.db_settings(), - **cls.cloud_settings() + **cls.device_protocols(), + **cls.database_protocols(), + **cls.cloud_protocols() } - - @classmethod - def default_ports(cls): - return { - cls.ssh: 22, - cls.sftp: 22, - cls.rdp: 3389, - cls.vnc: 5900, - cls.telnet: 21, - - cls.mysql: 3306, - cls.mariadb: 3306, - cls.postgresql: 5432, - cls.oracle: 1521, - cls.sqlserver: 1433, - cls.mongodb: 27017, - cls.redis: 6379, - - cls.k8s: 0, - - cls.http: 80, - } - diff --git a/apps/assets/filters.py b/apps/assets/filters.py index fc75b16a8..e44378ef7 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -150,7 +150,7 @@ class IpInFilterBackend(filters.BaseFilterBackend): name='ips', location='query', required=False, type='string', schema=coreschema.String( title='ips', - description='ip in filter' + description='address in filter' ) ) ] @@ -158,7 +158,7 @@ class IpInFilterBackend(filters.BaseFilterBackend): class AccountFilterSet(BaseFilterSet): from django_filters import rest_framework as filters - ip = filters.CharFilter(field_name='ip', lookup_expr='exact') + ip = filters.CharFilter(field_name='address', lookup_expr='exact') hostname = filters.CharFilter(field_name='name', lookup_expr='exact') username = filters.CharFilter(field_name="username", lookup_expr='exact') assets = UUIDInFilter(field_name='asset_id', lookup_expr='in') diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index 92e6aad69..fb369dd1d 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -17,6 +17,11 @@ class Migration(migrations.Migration): name='info', field=models.JSONField(blank=True, default=dict, verbose_name='Info'), ), + migrations.RenameField( + model_name='asset', + old_name='ip', + new_name='address', + ), migrations.CreateModel( name='Host', fields=[ diff --git a/apps/assets/migrations/0096_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py index e03272504..562353b7f 100644 --- a/apps/assets/migrations/0096_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -36,7 +36,6 @@ class Migration(migrations.Migration): name='Cloud', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('url', models.CharField(max_length=4096, verbose_name='Cluster')), ], options={ 'abstract': False, @@ -47,7 +46,6 @@ class Migration(migrations.Migration): name='Web', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('url', models.CharField(max_length=1024, verbose_name='url')), ('autofill', models.CharField(default='basic', max_length=16)), ('password_selector', models.CharField(blank=True, default='', max_length=128)), ('submit_selector', models.CharField(blank=True, default='', max_length=128)), diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index ca96adf87..3159cc3b6 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -35,7 +35,7 @@ class Account(BaseAccount): @lazyproperty def ip(self): - return self.asset.ip + return self.asset.address @lazyproperty def asset_name(self): diff --git a/apps/assets/models/asset/cloud.py b/apps/assets/models/asset/cloud.py index f4874b9c1..c45631331 100644 --- a/apps/assets/models/asset/cloud.py +++ b/apps/assets/models/asset/cloud.py @@ -1,11 +1,7 @@ -from django.db import models -from django.utils.translation import gettext_lazy as _ from .common import Asset class Cloud(Asset): - url = models.CharField(max_length=4096, verbose_name=_("Url")) - def __str__(self): return self.name diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 98e4fc1ac..eadb0593c 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -84,7 +84,7 @@ class Protocol(models.Model): class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) - ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) + address = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', @@ -101,7 +101,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): return '{0.name}({0.ip})'.format(self) def get_target_ip(self): - return self.ip + return self.address def get_target_ssh_port(self): protocol = self.protocols.all().filter(name='ssh').first() @@ -161,7 +161,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): data = { 'id': str(self.id), 'name': self.name, - 'title': self.ip, + 'title': self.address, 'pId': parent_node.key, 'isParent': False, 'open': False, @@ -171,7 +171,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): 'data': { 'id': self.id, 'name': self.name, - 'ip': self.ip, + 'address': self.address, 'protocols': self.protocols, } } diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index cb97c95cd..a8f0daf15 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -8,7 +8,11 @@ class Database(Asset): db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) def __str__(self): - return '{}({}://{}/{})'.format(self.name, self.type, self.ip, self.db_name) + return '{}({}://{}/{})'.format(self.name, self.type, self.address, self.db_name) + + @property + def ip(self): + return self.address class Meta: verbose_name = _("Database") diff --git a/apps/assets/models/asset/device.py b/apps/assets/models/asset/device.py index 24c1d2bd4..c629a5fe4 100644 --- a/apps/assets/models/asset/device.py +++ b/apps/assets/models/asset/device.py @@ -1,4 +1,3 @@ - from .common import Asset diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 3f29fbe8f..4ce4be5c9 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -3,6 +3,4 @@ from .common import Asset class Host(Asset): - def save(self, *args, **kwargs): - self.category = Category.HOST - return super().save(*args, **kwargs) + pass diff --git a/apps/assets/models/asset/web.py b/apps/assets/models/asset/web.py index 5b66cc71b..afe7bed72 100644 --- a/apps/assets/models/asset/web.py +++ b/apps/assets/models/asset/web.py @@ -1,11 +1,9 @@ -from django.utils.translation import gettext_lazy as _ from django.db import models from .common import Asset class Web(Asset): - url = models.CharField(max_length=1024, verbose_name=_("url")) autofill = models.CharField(max_length=16, default='basic') username_selector = models.CharField(max_length=128, blank=True, default='') password_selector = models.CharField(max_length=128, blank=True, default='') diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 79b9deea9..219595d2c 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -136,7 +136,7 @@ class Gateway(BaseAccount): socket.gaierror) as e: err = str(e) if err.startswith('[Errno None] Unable to connect to port'): - err = _('Unable to connect to port {port} on {ip}') + err = _('Unable to connect to port {port} on {address}') err = err.format(port=self.port, ip=self.ip) elif err == 'Authentication failed.': err = _('Authentication failed') diff --git a/apps/assets/models/gathered_user.py b/apps/assets/models/gathered_user.py index e8a2de825..3c0a743b9 100644 --- a/apps/assets/models/gathered_user.py +++ b/apps/assets/models/gathered_user.py @@ -25,7 +25,7 @@ class GatheredUser(OrgModelMixin): @property def ip(self): - return self.asset.ip + return self.asset.address class Meta: verbose_name = _('GatherUser') diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 8adca0a35..eb37e3bd9 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -58,7 +58,7 @@ class AccountSerializer( ): asset = ObjectRelatedField( required=False, queryset=Asset.objects, - label=_('Asset'), attrs=('id', 'name', 'ip') + label=_('Asset'), attrs=('id', 'name', 'address') ) platform = serializers.ReadOnlyField(label=_("Platform")) @@ -77,7 +77,7 @@ class AccountSerializer( class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): fields_backup = [ - 'name', 'ip', 'platform', 'protocols', 'username', 'password', + 'name', 'address', 'platform', 'protocols', 'username', 'password', 'private_key', 'public_key', 'date_created', 'date_updated', 'version' ] extra_kwargs = { diff --git a/apps/assets/serializers/asset/cloud.py b/apps/assets/serializers/asset/cloud.py index 38e95bc2c..fa1e7d33a 100644 --- a/apps/assets/serializers/asset/cloud.py +++ b/apps/assets/serializers/asset/cloud.py @@ -7,5 +7,10 @@ __all__ = ['CloudSerializer'] class CloudSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Cloud - fields = AssetSerializer.Meta.fields + ['cluster'] - + fields = AssetSerializer.Meta.fields + extra_kwargs = { + **AssetSerializer.Meta.extra_kwargs, + 'address': { + 'label': 'URL' + } + } diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 0f551d05f..2ec7c95bb 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -68,7 +68,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): class Meta: model = Asset - fields_mini = ['id', 'name', 'ip'] + fields_mini = ['id', 'name', 'address'] fields_small = fields_mini + ['is_active', 'comment'] fields_fk = ['domain', 'platform', 'platform'] fields_m2m = [ @@ -81,7 +81,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { 'name': {'label': _("Name")}, - 'ip': {'label': _('IP/Host')}, + 'address': {'label': _('Address')}, } @classmethod @@ -142,7 +142,7 @@ class AssetSimpleSerializer(serializers.ModelSerializer): class Meta: model = Asset fields = [ - 'id', 'name', 'ip', 'port', + 'id', 'name', 'address', 'port', 'connectivity', 'date_verified' ] diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py index c553a8062..b98360022 100644 --- a/apps/assets/serializers/asset/web.py +++ b/apps/assets/serializers/asset/web.py @@ -9,6 +9,12 @@ class WebSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Web fields = AssetSerializer.Meta.fields + [ - 'url', 'autofill', 'username_selector', + 'autofill', 'username_selector', 'password_selector', 'submit_selector' ] + extra_kwargs = { + **AssetSerializer.Meta.extra_kwargs, + 'address': { + 'label': 'URL' + } + } diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 72b9ea581..bef146327 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -25,6 +25,8 @@ class ProtocolSettingSerializer(serializers.Serializer): sftp_enabled = serializers.BooleanField(default=True, label=_("SFTP enabled")) sftp_home = serializers.CharField(default='/tmp', label=_("SFTP home")) + via_http = serializers.BooleanField(default=False, label=_("Via HTTP")) + class PlatformAutomationSerializer(serializers.ModelSerializer): class Meta: diff --git a/apps/assets/tasks/gather_asset_users.py b/apps/assets/tasks/gather_asset_users.py index f5a46c099..acacbb33d 100644 --- a/apps/assets/tasks/gather_asset_users.py +++ b/apps/assets/tasks/gather_asset_users.py @@ -95,7 +95,7 @@ def add_asset_users(assets, results): for username, data in users.items(): defaults = {'asset': asset, 'username': username, 'present': True} if data.get("ip"): - defaults["ip_last_login"] = data["ip"][:32] + defaults["ip_last_login"] = data["address"][:32] if data.get("date"): defaults["date_last_login"] = data["date"] GatheredUser.objects.update_or_create( diff --git a/apps/audits/utils.py b/apps/audits/utils.py index 1fadbccee..f635652f2 100644 --- a/apps/audits/utils.py +++ b/apps/audits/utils.py @@ -36,13 +36,13 @@ def write_content_to_excel(response, header=None, login_logs=None, fields=None): def write_login_log(*args, **kwargs): from audits.models import UserLoginLog - ip = kwargs.get('ip') or '' + ip = kwargs.get('address') or '' if not (ip and validate_ip(ip)): ip = ip[:15] city = DEFAULT_CITY else: city = get_ip_city(ip) or DEFAULT_CITY - kwargs.update({'ip': ip, 'city': city}) + kwargs.update({'address': ip, 'city': city}) UserLoginLog.objects.create(**kwargs) diff --git a/apps/authentication/errors/const.py b/apps/authentication/errors/const.py index 530bcf150..e9a617f97 100644 --- a/apps/authentication/errors/const.py +++ b/apps/authentication/errors/const.py @@ -48,7 +48,7 @@ block_user_login_msg = _( "(please contact admin to unlock it or try again after {} minutes)" ) block_ip_login_msg = _( - "The ip has been locked " + "The address has been locked " "(please contact admin to unlock it or try again after {} minutes)" ) block_mfa_msg = _( diff --git a/apps/common/utils/ip/utils.py b/apps/common/utils/ip/utils.py index d62cba00d..46b4a0e46 100644 --- a/apps/common/utils/ip/utils.py +++ b/apps/common/utils/ip/utils.py @@ -75,7 +75,7 @@ def contains_ip(ip, ip_group): def get_ip_city(ip): if not ip or not isinstance(ip, str): - return _("Invalid ip") + return _("Invalid address") if ':' in ip: return 'IPv6' diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 01a806172..c94290592 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -40,7 +40,7 @@ class BaseHost(Host): def __set_required_variables(self): host_data = self.host_data - self.set_variable('ansible_host', host_data['ip']) + self.set_variable('ansible_host', host_data['address']) self.set_variable('ansible_port', host_data['port']) if host_data.get('username'): diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index 45b174228..d6943f5c5 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -19,7 +19,7 @@ class JMSBaseInventory(BaseInventory): info = { 'id': asset.id, 'name': asset.name, - 'ip': asset.ip, + 'ip': asset.address, 'port': asset.ssh_port, 'vars': dict(), 'groups': [], @@ -49,7 +49,7 @@ class JMSBaseInventory(BaseInventory): proxy_command_list = [ "ssh", "-o", "Port={}".format(gateway.port), "-o", "StrictHostKeyChecking=no", - "{}@{}".format(gateway.username, gateway.ip), + "{}@{}".format(gateway.username, gateway.address), "-W", "%h:%p", "-q", ] diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index a3b65f3f3..247333086 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -88,7 +88,7 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): class AssetPermissionAllAssetListApi(generics.ListAPIView): serializer_class = serializers.AssetPermissionAllAssetSerializer - filterset_fields = ("name", "ip") + filterset_fields = ("name", "address") search_fields = filterset_fields def get_queryset(self): diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 4901e98fc..6d45de865 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -33,8 +33,8 @@ class UserGroupMixin: class UserGroupGrantedAssetsApi(ListAPIView): serializer_class = serializers.AssetGrantedSerializer only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - filterset_fields = ['name', 'ip', 'id', 'comment'] - search_fields = ['name', 'ip', 'comment'] + filterset_fields = ['name', 'address', 'id', 'comment'] + search_fields = ['name', 'address', 'comment'] rbac_perms = { 'list': 'perms.view_usergroupassets', } @@ -70,8 +70,8 @@ class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView): serializer_class = serializers.AssetGrantedSerializer only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - filterset_fields = ['name', 'ip', 'id', 'comment'] - search_fields = ['name', 'ip', 'comment'] + filterset_fields = ['name', 'address', 'id', 'comment'] + search_fields = ['name', 'address', 'comment'] rbac_perms = { 'list': 'perms.view_usergroupassets', } diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index 5e123091a..ad50c5b19 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -32,7 +32,7 @@ class UserDirectGrantedAssetsQuerysetMixin: class UserAllGrantedAssetsQuerysetMixin: only_fields = serializers.AssetGrantedSerializer.Meta.only_fields pagination_class = AllGrantedAssetPagination - ordering_fields = ("hostname", "ip", "port", "cpu_cores") + ordering_fields = ("hostname", "address", "port", "cpu_cores") ordering = ('hostname', ) user: User @@ -84,8 +84,8 @@ class UserGrantedNodeAssetsMixin: class AssetsSerializerFormatMixin: serializer_class = serializers.AssetGrantedSerializer - filterset_fields = ['name', 'ip', 'id', 'comment'] - search_fields = ['name', 'ip', 'comment'] + filterset_fields = ['name', 'address', 'id', 'comment'] + search_fields = ['name', 'address', 'comment'] class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): @@ -95,8 +95,8 @@ class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): filter_queryset: callable get_queryset: callable - filterset_fields = ['name', 'ip', 'id', 'comment'] - search_fields = ['name', 'ip', 'comment'] + filterset_fields = ['name', 'address', 'id', 'comment'] + search_fields = ['name', 'address', 'comment'] def list(self, request: Request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) diff --git a/apps/perms/filters.py b/apps/perms/filters.py index ee3c03e91..e64e919ea 100644 --- a/apps/perms/filters.py +++ b/apps/perms/filters.py @@ -142,7 +142,7 @@ class AssetPermissionFilter(PermissionBaseFilter): is_query_all = self.get_query_param('all', True) asset_id = self.get_query_param('asset_id') asset_name = self.get_query_param('asset_name') - ip = self.get_query_param('ip') + ip = self.get_query_param('address') if asset_id: assets = Asset.objects.filter(pk=asset_id) diff --git a/apps/perms/serializers/permission_relation.py b/apps/perms/serializers/permission_relation.py index 983768422..2384c4845 100644 --- a/apps/perms/serializers/permission_relation.py +++ b/apps/perms/serializers/permission_relation.py @@ -83,7 +83,7 @@ class AssetPermissionAllAssetSerializer(serializers.Serializer): asset_display = serializers.SerializerMethodField() class Meta: - only_fields = ['id', 'name', 'ip'] + only_fields = ['id', 'name', 'address'] @staticmethod def get_asset_display(obj): diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index c1d24ba05..40754b637 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -24,7 +24,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): class Meta: model = Asset only_fields = [ - "id", "name", "ip", "protocols", 'domain', + "id", "name", "address", "protocols", 'domain', "platform", "comment", "org_id", "is_active" ] fields = only_fields + ['org_name'] diff --git a/jms b/jms index 5d5b5c433..4f1fb9a99 100755 --- a/jms +++ b/jms @@ -117,7 +117,7 @@ def download_ip_db(): path = os.path.join(db_base_dir, *p) if os.path.isfile(path) and os.path.getsize(path) > 1000: continue - print("Download ip db: {}".format(path)) + print("Download address db: {}".format(path)) download_file(src, path) From 584ce0afe3bb6f09dceb7c6a3defaa0234b03eb3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 13:42:12 +0800 Subject: [PATCH 142/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0097_auto_20220426_1558.py | 9 ++++----- apps/audits/utils.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/assets/migrations/0097_auto_20220426_1558.py b/apps/assets/migrations/0097_auto_20220426_1558.py index 02cbda4ce..ed645cc57 100644 --- a/apps/assets/migrations/0097_auto_20220426_1558.py +++ b/apps/assets/migrations/0097_auto_20220426_1558.py @@ -55,7 +55,7 @@ def migrate_database_to_asset(apps, *args): attrs.update(_attrs) db = db_model( - id=app.id, hostname=app.name, ip=attrs['host'], + id=app.id, hostname=app.name, address=attrs['host'], protocols='{}/{}'.format(app.type, attrs['port']), db_name=attrs['database'] or '', platform=platforms_map[app.type], @@ -82,11 +82,10 @@ def migrate_cloud_to_asset(apps, *args): attrs = app.attrs print("Create cloud: {}".format(app.name)) cloud = cloud_model( - id=app.id, hostname=app.name, ip='', - protocols='', - platform=platform, + id=app.id, hostname=app.name, + address=attrs.get('cluster', ''), + protocols='', platform=platform, org_id=app.org_id, - url=attrs.get('cluster', '') ) try: diff --git a/apps/audits/utils.py b/apps/audits/utils.py index f635652f2..1fadbccee 100644 --- a/apps/audits/utils.py +++ b/apps/audits/utils.py @@ -36,13 +36,13 @@ def write_content_to_excel(response, header=None, login_logs=None, fields=None): def write_login_log(*args, **kwargs): from audits.models import UserLoginLog - ip = kwargs.get('address') or '' + ip = kwargs.get('ip') or '' if not (ip and validate_ip(ip)): ip = ip[:15] city = DEFAULT_CITY else: city = get_ip_city(ip) or DEFAULT_CITY - kwargs.update({'address': ip, 'city': city}) + kwargs.update({'ip': ip, 'city': city}) UserLoginLog.objects.create(**kwargs) From e498a645d37467321fa98e76041009559b783143 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 14:01:24 +0800 Subject: [PATCH 143/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=96=87?= =?UTF-8?q?=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jms b/jms index 4f1fb9a99..5d5b5c433 100755 --- a/jms +++ b/jms @@ -117,7 +117,7 @@ def download_ip_db(): path = os.path.join(db_base_dir, *p) if os.path.isfile(path) and os.path.getsize(path) > 1000: continue - print("Download address db: {}".format(path)) + print("Download ip db: {}".format(path)) download_file(src, path) From ffdb3f3b837701a2639d2ec58242ea97ba673242 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Wed, 21 Sep 2022 18:19:17 +0800 Subject: [PATCH 144/488] perf: ticket --- apps/acls/models/login_asset_acl.py | 2 +- apps/acls/serializers/login_asset_check.py | 9 ++- apps/assets/models/cmd_filter.py | 28 ++++---- apps/tickets/const.py | 1 - apps/tickets/handlers/apply_application.py | 67 ------------------- apps/tickets/handlers/apply_asset.py | 4 +- apps/tickets/handlers/base.py | 2 +- .../migrations/0021_auto_20220921_1814.py | 34 ++++++++++ .../serializers/ticket/apply_application.py | 65 ------------------ .../tickets/serializers/ticket/apply_asset.py | 21 +++--- .../serializers/ticket/command_confirm.py | 2 +- apps/tickets/serializers/ticket/common.py | 7 +- .../serializers/ticket/login_asset_confirm.py | 2 +- 13 files changed, 69 insertions(+), 175 deletions(-) delete mode 100644 apps/tickets/handlers/apply_application.py create mode 100644 apps/tickets/migrations/0021_auto_20220921_1814.py delete mode 100644 apps/tickets/serializers/ticket/apply_application.py diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 8d920e4d5..3425ac8de 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -90,7 +90,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): 'applicant': user, 'apply_login_user': user, 'apply_login_asset': asset, - 'apply_login_account': account, + 'apply_login_account': str(account), 'org_id': org_id, } ticket = ApplyLoginAssetTicket.objects.create(**data) diff --git a/apps/acls/serializers/login_asset_check.py b/apps/acls/serializers/login_asset_check.py index b85d092ae..2240cb8d6 100644 --- a/apps/acls/serializers/login_asset_check.py +++ b/apps/acls/serializers/login_asset_check.py @@ -2,8 +2,7 @@ from rest_framework import serializers from orgs.utils import tmp_to_root_org from common.utils import get_object_or_none, lazyproperty from users.models import User -from assets.models import Asset - +from assets.models import Asset, Account __all__ = ['LoginAssetCheckSerializer'] @@ -18,7 +17,7 @@ class LoginAssetCheckSerializer(serializers.Serializer): super().__init__(*args, **kwargs) self.user = None self.asset = None - self._account = None + self.account = None self._account_username = None def validate_user_id(self, user_id): @@ -29,6 +28,10 @@ class LoginAssetCheckSerializer(serializers.Serializer): self.asset = self.validate_object_exist(Asset, asset_id) return asset_id + def validate_account_id(self, account_id): + self.account = self.validate_object_exist(Account, account_id) + return account_id + @staticmethod def validate_object_exist(model, field_id): with tmp_to_root_org(): diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 916170cfd..e26393fff 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -import uuid import re +import uuid from django.db import models from django.db.models import Q @@ -9,10 +9,9 @@ from django.core.validators import MinValueValidator, MaxValueValidator from django.utils.translation import ugettext_lazy as _ from users.models import User, UserGroup -from ..models import Asset - -from common.utils import lazyproperty, get_logger, get_object_or_none from orgs.mixins.models import OrgModelMixin +from common.utils import lazyproperty, get_logger, get_object_or_none +from ..models import Asset, Account logger = get_logger(__file__) @@ -167,7 +166,7 @@ class CommandFilterRule(OrgModelMixin): 'applicant': session.user_obj, 'apply_run_user_id': session.user_id, 'apply_run_asset': str(session.asset), - 'apply_run_system_user_id': session.system_user_id, + 'apply_run_account': str(session.account), 'apply_run_command': run_command[:4090], 'apply_from_session_id': str(session.id), 'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id), @@ -180,9 +179,10 @@ class CommandFilterRule(OrgModelMixin): return ticket @classmethod - def get_queryset(cls, user_id=None, user_group_id=None, system_user_id=None, - asset_id=None, application_id=None, org_id=None): - from applications.models import Application + def get_queryset( + cls, user_id=None, user_group_id=None, system_user_id=None, + asset_id=None, org_id=None + ): user_groups = [] user = get_object_or_none(User, pk=user_id) if user: @@ -191,23 +191,19 @@ class CommandFilterRule(OrgModelMixin): if user_group: org_id = user_group.org_id user_groups.append(user_group) - system_user = get_object_or_none(SystemUser, pk=system_user_id) + account = get_object_or_none(Account, pk=system_user_id) asset = get_object_or_none(Asset, pk=asset_id) - application = get_object_or_none(Application, pk=application_id) q = Q() if user: q |= Q(users=user) if user_groups: q |= Q(user_groups__in=set(user_groups)) - if system_user: - org_id = system_user.org_id - q |= Q(system_users=system_user) + if account: + org_id = account.org_id + q |= Q(accounts=account) if asset: org_id = asset.org_id q |= Q(assets=asset) - if application: - org_id = application.org_id - q |= Q(applications=application) if q: cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True) if org_id: diff --git a/apps/tickets/const.py b/apps/tickets/const.py index d97e6716e..f52c74930 100644 --- a/apps/tickets/const.py +++ b/apps/tickets/const.py @@ -8,7 +8,6 @@ class TicketType(TextChoices): general = 'general', _("General") login_confirm = 'login_confirm', _("Login confirm") apply_asset = 'apply_asset', _('Apply for asset') - apply_application = 'apply_application', _('Apply for application') login_asset_confirm = 'login_asset_confirm', _('Login asset confirm') command_confirm = 'command_confirm', _('Command confirm') diff --git a/apps/tickets/handlers/apply_application.py b/apps/tickets/handlers/apply_application.py deleted file mode 100644 index 287c70c4a..000000000 --- a/apps/tickets/handlers/apply_application.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.utils.translation import ugettext as _ - -from orgs.utils import tmp_to_org -from perms.models import ApplicationPermission -from tickets.models import ApplyApplicationTicket -from .base import BaseHandler - - -class Handler(BaseHandler): - ticket: ApplyApplicationTicket - - def _on_step_approved(self, step): - is_finished = super()._on_step_approved(step) - if is_finished: - self._create_application_permission() - - # permission - def _create_application_permission(self): - org_id = self.ticket.org_id - with tmp_to_org(org_id): - application_permission = ApplicationPermission.objects.filter(id=self.ticket.id).first() - if application_permission: - return application_permission - - apply_applications = self.ticket.apply_applications.all() - apply_system_users = self.ticket.apply_system_users.all() - - apply_permission_name = self.ticket.apply_permission_name - apply_actions = self.ticket.apply_actions - apply_category = self.ticket.apply_category - apply_type = self.ticket.apply_type - apply_date_start = self.ticket.apply_date_start - apply_date_expired = self.ticket.apply_date_expired - permission_created_by = '{}:{}'.format( - str(self.ticket.__class__.__name__), str(self.ticket.id) - ) - permission_comment = _( - 'Created by the ticket, ' - 'ticket title: {}, ' - 'ticket applicant: {}, ' - 'ticket processor: {}, ' - 'ticket ID: {}' - ).format( - self.ticket.title, - self.ticket.applicant, - ','.join([i['processor_display'] for i in self.ticket.process_map]), - str(self.ticket.id) - ) - permissions_data = { - 'id': self.ticket.id, - 'name': apply_permission_name, - 'from_ticket': True, - 'category': apply_category, - 'actions': apply_actions, - 'type': apply_type, - 'comment': str(permission_comment), - 'created_by': permission_created_by, - 'date_start': apply_date_start, - 'date_expired': apply_date_expired, - } - with tmp_to_org(self.ticket.org_id): - application_permission = ApplicationPermission.objects.create(**permissions_data) - application_permission.users.add(self.ticket.applicant) - application_permission.applications.set(apply_applications) - application_permission.system_users.set(apply_system_users) - - return application_permission diff --git a/apps/tickets/handlers/apply_asset.py b/apps/tickets/handlers/apply_asset.py index e9d29697d..3f2051120 100644 --- a/apps/tickets/handlers/apply_asset.py +++ b/apps/tickets/handlers/apply_asset.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext as _ from perms.models import AssetPermission -from orgs.utils import tmp_to_org, tmp_to_root_org +from orgs.utils import tmp_to_org from tickets.models import ApplyAssetTicket from .base import BaseHandler @@ -24,7 +24,6 @@ class Handler(BaseHandler): apply_nodes = self.ticket.apply_nodes.all() apply_assets = self.ticket.apply_assets.all() - apply_system_users = self.ticket.apply_system_users.all() apply_permission_name = self.ticket.apply_permission_name apply_actions = self.ticket.apply_actions @@ -61,6 +60,5 @@ class Handler(BaseHandler): asset_permission.users.add(self.ticket.applicant) asset_permission.nodes.set(apply_nodes) asset_permission.assets.set(apply_assets) - asset_permission.system_users.set(apply_system_users) return asset_permission diff --git a/apps/tickets/handlers/base.py b/apps/tickets/handlers/base.py index 81341ce8e..7b311799f 100644 --- a/apps/tickets/handlers/base.py +++ b/apps/tickets/handlers/base.py @@ -65,7 +65,7 @@ class BaseHandler: if state != TicketState.approved: return diff_context - if self.ticket.type not in [TicketType.apply_asset, TicketType.apply_application]: + if self.ticket.type == TicketType.apply_asset: return diff_context # 企业微信,钉钉审批不做diff diff --git a/apps/tickets/migrations/0021_auto_20220921_1814.py b/apps/tickets/migrations/0021_auto_20220921_1814.py new file mode 100644 index 000000000..5e9db6e06 --- /dev/null +++ b/apps/tickets/migrations/0021_auto_20220921_1814.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.13 on 2022-09-21 10:14 + +from django.db import migrations, models + + +def migrate_remove_application_flow(apps, schema_editor): + flow_model = apps.get_model('tickets', 'TicketFlow') + flow_model.objects.filter(type='apply_application').delete() + + +class Migration(migrations.Migration): + dependencies = [ + ('tickets', '0020_auto_20220817_1346'), + ] + + operations = [ + migrations.AlterField( + model_name='ticket', + name='type', + field=models.CharField( + choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('apply_asset', 'Apply for asset'), + ('login_asset_confirm', 'Login asset confirm'), ('command_confirm', 'Command confirm')], + default='general', max_length=64, verbose_name='Type'), + ), + migrations.AlterField( + model_name='ticketflow', + name='type', + field=models.CharField( + choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('apply_asset', 'Apply for asset'), + ('login_asset_confirm', 'Login asset confirm'), ('command_confirm', 'Command confirm')], + default='general', max_length=64, verbose_name='Type'), + ), + migrations.RunPython(migrate_remove_application_flow), + ] diff --git a/apps/tickets/serializers/ticket/apply_application.py b/apps/tickets/serializers/ticket/apply_application.py deleted file mode 100644 index d70e850a5..000000000 --- a/apps/tickets/serializers/ticket/apply_application.py +++ /dev/null @@ -1,65 +0,0 @@ -from django.utils.translation import ugettext as _ -from rest_framework import serializers - -from perms.models import ApplicationPermission -from perms.serializers.permission import ActionsField -from orgs.utils import tmp_to_org -from applications.models import Application -from tickets.models import ApplyApplicationTicket -from .ticket import TicketApplySerializer -from .common import BaseApplyAssetApplicationSerializer - -__all__ = ['ApplyApplicationSerializer', 'ApplyApplicationDisplaySerializer', 'ApproveApplicationSerializer'] - - -class ApplyApplicationSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer): - apply_actions = ActionsField(required=True, allow_empty=False) - permission_model = ApplicationPermission - - class Meta: - model = ApplyApplicationTicket - writeable_fields = [ - 'id', 'title', 'type', 'apply_category', - 'apply_type', 'apply_applications', 'apply_system_users', - 'apply_actions', 'apply_date_start', 'apply_date_expired', 'org_id' - ] - fields = TicketApplySerializer.Meta.fields + \ - writeable_fields + ['apply_permission_name', 'apply_actions_display'] - read_only_fields = list(set(fields) - set(writeable_fields)) - ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs - extra_kwargs = { - 'apply_applications': {'required': False, 'allow_empty': True}, - 'apply_system_users': {'required': False, 'allow_empty': True}, - } - extra_kwargs.update(ticket_extra_kwargs) - - def validate_apply_applications(self, applications): - if self.is_final_approval and not applications: - raise serializers.ValidationError(_('This field is required.')) - tp = self.initial_data.get('apply_type') - return self.filter_many_to_many_field(Application, applications, type=tp) - - -class ApproveApplicationSerializer(ApplyApplicationSerializer): - class Meta(ApplyApplicationSerializer.Meta): - read_only_fields = ApplyApplicationSerializer.Meta.read_only_fields + ['title', 'type'] - - -class ApplyApplicationDisplaySerializer(ApplyApplicationSerializer): - apply_applications = serializers.SerializerMethodField() - apply_system_users = serializers.SerializerMethodField() - - class Meta: - model = ApplyApplicationSerializer.Meta.model - fields = ApplyApplicationSerializer.Meta.fields - read_only_fields = fields - - @staticmethod - def get_apply_applications(instance): - with tmp_to_org(instance.org_id): - return instance.apply_applications.values_list('id', flat=True) - - @staticmethod - def get_apply_system_users(instance): - with tmp_to_org(instance.org_id): - return instance.apply_system_users.values_list('id', flat=True) diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index f2ed156ad..2dcbb6eb6 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -23,17 +23,18 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria model = ApplyAssetTicket writeable_fields = [ 'id', 'title', 'type', 'apply_nodes', 'apply_assets', - 'apply_system_users', 'apply_actions', - 'apply_date_start', 'apply_date_expired', 'org_id' + 'apply_accounts', 'apply_actions', 'org_id', + 'apply_date_start', 'apply_date_expired' + ] + fields = TicketApplySerializer.Meta.fields + writeable_fields + [ + 'apply_permission_name', 'apply_actions_display' ] - fields = TicketApplySerializer.Meta.fields + \ - writeable_fields + ['apply_permission_name', 'apply_actions_display'] read_only_fields = list(set(fields) - set(writeable_fields)) ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs extra_kwargs = { 'apply_nodes': {'required': False, 'allow_empty': True}, 'apply_assets': {'required': False, 'allow_empty': True}, - 'apply_system_users': {'required': False, 'allow_empty': True}, + 'apply_accounts': {'required': False, 'allow_empty': True}, } extra_kwargs.update(ticket_extra_kwargs) @@ -58,13 +59,14 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria class ApproveAssetSerializer(ApplyAssetSerializer): class Meta(ApplyAssetSerializer.Meta): - read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + ['title', 'type'] + read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + [ + 'title', 'type' + ] class ApplyAssetDisplaySerializer(ApplyAssetSerializer): apply_nodes = serializers.SerializerMethodField() apply_assets = serializers.SerializerMethodField() - apply_system_users = serializers.SerializerMethodField() class Meta: model = ApplyAssetSerializer.Meta.model @@ -80,8 +82,3 @@ class ApplyAssetDisplaySerializer(ApplyAssetSerializer): def get_apply_assets(instance): with tmp_to_org(instance.org_id): return instance.apply_assets.values_list('id', flat=True) - - @staticmethod - def get_apply_system_users(instance): - with tmp_to_org(instance.org_id): - return instance.apply_system_users.values_list('id', flat=True) diff --git a/apps/tickets/serializers/ticket/command_confirm.py b/apps/tickets/serializers/ticket/command_confirm.py index c52bfd153..fced49976 100644 --- a/apps/tickets/serializers/ticket/command_confirm.py +++ b/apps/tickets/serializers/ticket/command_confirm.py @@ -10,7 +10,7 @@ class ApplyCommandConfirmSerializer(TicketApplySerializer): class Meta: model = ApplyCommandTicket fields = TicketApplySerializer.Meta.fields + [ - 'apply_run_user', 'apply_run_asset', 'apply_run_system_user', + 'apply_run_user', 'apply_run_asset', 'apply_run_account', 'apply_run_command', 'apply_from_session', 'apply_from_cmd_filter', 'apply_from_cmd_filter_rule' ] diff --git a/apps/tickets/serializers/ticket/common.py b/apps/tickets/serializers/ticket/common.py index 8da30e458..f38f97a43 100644 --- a/apps/tickets/serializers/ticket/common.py +++ b/apps/tickets/serializers/ticket/common.py @@ -53,17 +53,16 @@ class BaseApplyAssetApplicationSerializer(serializers.Serializer): qs = model.objects.filter(id__in=ids, **kwargs).values_list('id', flat=True) return list(qs) - def validate_apply_account(self, system_users): - if self.is_final_approval and not system_users: + def validate_apply_accounts(self, accounts): + if self.is_final_approval and not accounts: raise serializers.ValidationError(_('This field is required.')) - return self.filter_many_to_many_field(SystemUser, system_users) + return accounts def validate(self, attrs): attrs = super().validate(attrs) apply_date_start = attrs['apply_date_start'].strftime('%Y-%m-%d %H:%M:%S') apply_date_expired = attrs['apply_date_expired'].strftime('%Y-%m-%d %H:%M:%S') - if apply_date_expired <= apply_date_start: error = _('The expiration date should be greater than the start date') raise serializers.ValidationError({'apply_date_expired': error}) diff --git a/apps/tickets/serializers/ticket/login_asset_confirm.py b/apps/tickets/serializers/ticket/login_asset_confirm.py index 9e3076f0f..43d54d327 100644 --- a/apps/tickets/serializers/ticket/login_asset_confirm.py +++ b/apps/tickets/serializers/ticket/login_asset_confirm.py @@ -10,5 +10,5 @@ class LoginAssetConfirmSerializer(TicketApplySerializer): class Meta: model = ApplyLoginAssetTicket fields = TicketApplySerializer.Meta.fields + [ - 'apply_login_user', 'apply_login_asset', 'apply_login_system_user' + 'apply_login_user', 'apply_login_asset', 'apply_login_account' ] From ea1cb158b53df6d8bc02c8d0e087c562b02edd59 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 19:03:06 +0800 Subject: [PATCH 145/488] perf: add charset control --- apps/assets/const/cloud.py | 3 ++- apps/assets/const/database.py | 1 + apps/assets/const/device.py | 3 ++- apps/assets/const/host.py | 1 + apps/assets/const/protocol.py | 6 +++--- apps/assets/const/web.py | 5 +++-- apps/assets/serializers/platform.py | 6 +++++- apps/locale/zh/LC_MESSAGES/django.po | 2 +- 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index 81819b034..8699cb286 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -10,6 +10,7 @@ class CloudTypes(BaseType): def _get_base_constrains(cls) -> dict: return { '*': { + 'charset_enabled': False, 'domain_enabled': False, 'su_enabled': False, } @@ -32,7 +33,7 @@ class CloudTypes(BaseType): def _get_protocol_constrains(cls) -> dict: return { '*': { - 'choices': ['http', 'api'], + 'choices': ['http'], }, cls.K8S: { 'choices': ['k8s'] diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py index dd8026359..ce63fd7eb 100644 --- a/apps/assets/const/database.py +++ b/apps/assets/const/database.py @@ -15,6 +15,7 @@ class DatabaseTypes(BaseType): def _get_base_constrains(cls) -> dict: return { '*': { + 'charset_enabled': False, 'domain_enabled': True, 'su_enabled': False, } diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index a3c325341..1fbb4774e 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -4,7 +4,7 @@ from .base import BaseType class DeviceTypes(BaseType): - GENERAL = 'general', _("General device") + GENERAL = 'general', _("General") SWITCH = 'switch', _("Switch") ROUTER = 'router', _("Router") FIREWALL = 'firewall', _("Firewall") @@ -13,6 +13,7 @@ class DeviceTypes(BaseType): def _get_base_constrains(cls) -> dict: return { '*': { + 'charset_enabled': False, 'domain_enabled': True, 'su_enabled': False, } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index f317fa292..2a20adc1f 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -11,6 +11,7 @@ class HostTypes(BaseType): def _get_base_constrains(cls) -> dict: return { '*': { + 'charset_enabled': True, 'domain_enabled': True, 'su_enabled': True, }, diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index 23b82b082..d7367b85d 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -96,9 +96,9 @@ class Protocol(ChoicesMixin, models.TextChoices): 'port': 80, 'secret_types': ['password'], 'setting': { - 'username_selector': '', - 'password_selector': '', - 'submit_selector': '', + 'username_selector': 'input[type=text]', + 'password_selector': 'input[type=password]', + 'submit_selector': 'button[type=submit]', } }, } diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py index 48c575909..abe085c58 100644 --- a/apps/assets/const/web.py +++ b/apps/assets/const/web.py @@ -4,12 +4,13 @@ from .base import BaseType class WebTypes(BaseType): - WEBSITE = 'website', _('General website') + WEBSITE = 'website', _('Website') @classmethod def _get_base_constrains(cls) -> dict: return { '*': { + 'charset_enabled': False, 'domain_enabled': False, 'su_enabled': False, } @@ -32,6 +33,6 @@ class WebTypes(BaseType): def _get_protocol_constrains(cls) -> dict: return { '*': { - 'choices': ['http', 'api'], + 'choices': ['http'], } } diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index bef146327..19611b5d6 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -25,7 +25,11 @@ class ProtocolSettingSerializer(serializers.Serializer): sftp_enabled = serializers.BooleanField(default=True, label=_("SFTP enabled")) sftp_home = serializers.CharField(default='/tmp', label=_("SFTP home")) - via_http = serializers.BooleanField(default=False, label=_("Via HTTP")) + # HTTP + auto_fill = serializers.BooleanField(default=False, label=_("Auto fill")) + username_selector = serializers.CharField(default='', label=_("Username selector")) + password_selector = serializers.CharField(default='', label=_("Password selector")) + submit_selector = serializers.CharField(default='', label=_("Submit selector")) class PlatformAutomationSerializer(serializers.ModelSerializer): diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 7a9a812ff..da00d3ee0 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -526,7 +526,7 @@ msgstr "内部的" #: perms/serializers/asset/user_permission.py:43 #: xpack/plugins/cloud/serializers/account_attrs.py:162 msgid "Platform" -msgstr "系统平台" +msgstr "资产平台" #: assets/models/asset.py:168 msgid "Vendor" From 21a60bf55e26188f8223aa59a6d2b68088eb045d Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 21 Sep 2022 20:13:28 +0800 Subject: [PATCH 146/488] perf: change cateogory data strucature --- apps/assets/const/types.py | 29 +++++------------------------ apps/assets/serializers/cagegory.py | 10 +++++----- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 67d4b9ff0..90391a72c 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -55,9 +55,9 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): categories = [] for category, tps in cls.category_types(): category_data = { - 'id': category.value, - 'name': category.label, - 'children': [cls.serialize_type(category, tp, with_constraints) for tp in tps] + 'value': category.value, + 'label': category.label, + 'types': [cls.serialize_type(category, tp, with_constraints) for tp in tps] } categories.append(category_data) return categories @@ -65,8 +65,8 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): @classmethod def serialize_type(cls, category, tp, with_constraints=True): data = { - 'id': tp.value, - 'name': tp.label, + 'value': tp.value, + 'label': tp.label, 'category': category, } @@ -86,25 +86,6 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): (Category.CLOUD, CloudTypes) ) - @classmethod - def grouped_choices(cls): - grouped_types = [(str(ca), tp.choices) for ca, tp in cls.category_types()] - return grouped_types - - @classmethod - def grouped_choices_to_objs(cls): - choices = cls.serialize_to_objs(Category.choices) - mapper = dict(cls.grouped_choices()) - for choice in choices: - children = cls.serialize_to_objs(mapper[choice['value']]) - choice['children'] = children - return choices - - @staticmethod - def serialize_to_objs(choices): - title = ['value', 'display_name'] - return [dict(zip(title, choice)) for choice in choices] - @staticmethod def choice_to_node(choice, pid, opened=True, is_parent=True, meta=None): node = TreeNode(**{ diff --git a/apps/assets/serializers/cagegory.py b/apps/assets/serializers/cagegory.py index f0b1bf67e..7f8b61571 100644 --- a/apps/assets/serializers/cagegory.py +++ b/apps/assets/serializers/cagegory.py @@ -3,13 +3,13 @@ from django.utils.translation import gettext_lazy as _ class TypeSerializer(serializers.Serializer): - id = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('id')) - name = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Name')) + label = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Label')) + value = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Value')) category = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Category')) constraints = serializers.JSONField(required=False, allow_null=True, label=_('Constraints')) class CategorySerializer(serializers.Serializer): - id = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('id')) - name = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Name')) - children = TypeSerializer(many=True, required=False, label=_('Children'), read_only=True) + label = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Label')) + value = serializers.CharField(max_length=64, required=False, allow_blank=True, label=_('Value')) + types = TypeSerializer(many=True, required=False, label=_('Types'), read_only=True) From 33948d614b24a31c52725ea0a9334625ae642348 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 22 Sep 2022 15:07:03 +0800 Subject: [PATCH 147/488] perf: ip -> address --- apps/assets/api/gathered_user.py | 4 ++-- apps/assets/models/asset/common.py | 2 +- apps/audits/api.py | 2 +- apps/audits/filters.py | 2 +- apps/perms/api/asset_permission_relation.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py index c5c9ece64..8fcf59456 100644 --- a/apps/assets/api/gathered_user.py +++ b/apps/assets/api/gathered_user.py @@ -16,5 +16,5 @@ class GatheredUserViewSet(OrgModelViewSet): serializer_class = GatheredUserSerializer extra_filter_backends = [AssetRelatedByNodeFilterBackend] - filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__name', 'asset_id'] - search_fields = ['username', 'asset__ip', 'asset__name'] + filterset_fields = ['asset', 'username', 'present', 'asset__address', 'asset__name', 'asset_id'] + search_fields = ['username', 'asset__address', 'asset__name'] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index eadb0593c..f94143c6e 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -98,7 +98,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): objects = AssetManager.from_queryset(AssetQuerySet)() def __str__(self): - return '{0.name}({0.ip})'.format(self) + return '{0.name}({0.address})'.format(self) def get_target_ip(self): return self.address diff --git a/apps/audits/api.py b/apps/audits/api.py index 42ae0993c..6cb2e1283 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -144,7 +144,7 @@ class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet) queryset = queryset.annotate( asset_display=Concat( F('asset__name'), Value('('), - F('asset__ip'), Value(')') + F('asset__address'), Value(')') ) ) return queryset diff --git a/apps/audits/filters.py b/apps/audits/filters.py index b8bf466ec..a6c44b5c5 100644 --- a/apps/audits/filters.py +++ b/apps/audits/filters.py @@ -49,7 +49,7 @@ class CommandExecutionFilter(BaseFilterSet): queryset = queryset.annotate( hostname_ip=Concat( F('asset__hostname'), Value('('), - F('asset__ip'), Value(')') + F('asset__address'), Value(')') ) ).filter(hostname_ip__icontains=value) return queryset diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index 247333086..d63b326b2 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -78,7 +78,7 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): filterset_fields = [ 'id', 'asset', 'assetpermission', ] - search_fields = ["id", "asset__name", "asset__ip", "assetpermission__name"] + search_fields = ["id", "asset__name", "asset__address", "assetpermission__name"] def get_queryset(self): queryset = super().get_queryset() From cc859f302a9691eabb1d03ad49848de59e047b40 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 22 Sep 2022 15:24:32 +0800 Subject: [PATCH 148/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20asset=20mi?= =?UTF-8?q?grations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/base.py | 7 ++ apps/assets/const/cloud.py | 8 ++ apps/assets/const/database.py | 12 +++ apps/assets/const/device.py | 9 ++ apps/assets/const/host.py | 38 ++++++++ apps/assets/const/types.py | 61 +++++++++++++ apps/assets/const/web.py | 8 ++ apps/assets/migrations/0092_add_host.py | 5 -- .../migrations/0094_auto_20220402_1736.py | 3 + .../migrations/0097_auto_20220426_1558.py | 4 +- .../migrations/0098_auto_20220430_2126.py | 89 +++++-------------- .../migrations/0099_auto_20220711_1409.py | 20 +++++ .../migrations/0101_auto_20220803_1448.py | 3 - .../migrations/0103_auto_20220811_1511.py | 6 ++ .../migrations/0106_auto_20220819_1523.py | 36 -------- ...916_1556.py => 0106_auto_20220916_1556.py} | 7 +- .../0107_alter_accountbackupplan_types.py | 18 ---- .../migrations/0108_auto_20220915_1032.py | 85 ------------------ apps/assets/models/platform.py | 5 +- apps/assets/models/utils.py | 10 +-- .../migrations/0023_automation_strategy.py | 2 +- 21 files changed, 213 insertions(+), 223 deletions(-) delete mode 100644 apps/assets/migrations/0106_auto_20220819_1523.py rename apps/assets/migrations/{0109_auto_20220916_1556.py => 0106_auto_20220916_1556.py} (91%) delete mode 100644 apps/assets/migrations/0107_alter_accountbackupplan_types.py delete mode 100644 apps/assets/migrations/0108_auto_20220915_1032.py diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index e9d90958a..71e68cd83 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -49,3 +49,10 @@ class BaseType(TextChoices): def _get_automation_constrains(cls) -> dict: raise NotImplementedError + @classmethod + def internal_platforms(cls): + raise NotImplementedError + + @classmethod + def create_or_update_internal_platforms(cls): + data = cls._internal_platforms() diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index 8699cb286..e344d08fb 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -39,3 +39,11 @@ class CloudTypes(BaseType): 'choices': ['k8s'] } } + + @classmethod + def internal_platforms(cls): + return { + cls.PUBLIC: [], + cls.PRIVATE: [{'name': 'Vmware-vSphere'}], + cls.K8S: [{'name': 'Kubernetes'}], + } diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py index ce63fd7eb..372a941d5 100644 --- a/apps/assets/const/database.py +++ b/apps/assets/const/database.py @@ -42,3 +42,15 @@ class DatabaseTypes(BaseType): } } + @classmethod + def internal_platforms(cls): + return { + cls.MYSQL: [{'name': 'MySQL'}], + cls.MARIADB: [{'name': 'MariaDB'}], + cls.POSTGRESQL: [{'name': 'PostgreSQL'}], + cls.ORACLE: [{'name': 'Oracle'}], + cls.SQLSERVER: [{'name': 'SQLServer'}], + cls.MONGODB: [{'name': 'MongoDB'}], + cls.REDIS: [{'name': 'Redis'}], + } + diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index 1fbb4774e..1cb0f47d8 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -39,3 +39,12 @@ class DeviceTypes(BaseType): 'create_account_enabled': False, } } + + @classmethod + def internal_platforms(cls): + return { + cls.GENERAL: [{'name': 'General'}, {'name': 'Cisco'}, {'name': 'Huawei'}, {'name': 'H3C'}], + cls.SWITCH: [], + cls.ROUTER: [], + cls.FIREWALL: [] + } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 2a20adc1f..40b712c67 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -12,8 +12,19 @@ class HostTypes(BaseType): return { '*': { 'charset_enabled': True, + 'charset': 'utf-8', # default 'domain_enabled': True, 'su_enabled': True, + 'su_methods': [ + { + 'name': 'sudo su', + 'id': 'sudo su' + }, + { + 'name': 'su -', + 'id': 'su -' + } + ], }, cls.WINDOWS: { 'su_enabled': False, @@ -43,3 +54,30 @@ class HostTypes(BaseType): 'create_account_enabled': True, } } + + @classmethod + def internal_platforms(cls): + return { + cls.LINUX: [ + {'name': 'Linux'}, + ], + cls.UNIX: [ + {'name': 'Unix'}, + {'name': 'macOS'}, + {'name': 'BSD'}, + {'name': 'AIX', 'automation': { + 'create_account_method': 'create_account_aix', + 'change_password_method': 'change_password_aix' + }}, + ], + cls.WINDOWS: [ + {'name': 'Windows'}, + {'name': 'Windows-TLS', 'protocol_settings': { + 'rdp': {'security': 'tls'}, + }}, + {'name': 'Windows-RDP', 'protocol_settings': { + 'rdp': {'security': 'rdp'}, + }} + ], + cls.OTHER_HOST: [] + } diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 90391a72c..265e01e92 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -76,6 +76,25 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): data['constraints'] = [] return data + @classmethod + def grouped_choices(cls): + grouped_types = [(str(ca), tp.choices) for ca, tp in cls.category_types()] + return grouped_types + + @classmethod + def grouped_choices_to_objs(cls): + choices = cls.serialize_to_objs(Category.choices) + mapper = dict(cls.grouped_choices()) + for choice in choices: + children = cls.serialize_to_objs(mapper[choice['value']]) + choice['children'] = children + return choices + + @staticmethod + def serialize_to_objs(choices): + title = ['value', 'display_name'] + return [dict(zip(title, choice)) for choice in choices] + @classmethod def category_types(cls): return ( @@ -111,3 +130,45 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): tp_node = cls.choice_to_node(tp, category_node.id, meta={'type': 'type'}) nodes.append(tp_node) return nodes + + @classmethod + def get_type_default_platform(cls, category, tp): + constraints = cls.get_constraints(category, tp) + data = { + 'name': tp.label, 'category': category.value, + 'type': tp.value, 'internal': True, + 'charset': constraints.get('charset', 'utf-8'), + 'domain_enabled': constraints.get('domain_enabled', False), + 'su_enabled': constraints.get('su_enabled', False), + } + if data['su_enabled'] and data.get('su_methods'): + data['su_method'] = data['su_methods'][0]['id'] + + protocols = constraints.get('protocols', []) + for p in protocols: + p.pop('secret_types', None) + data['protocols'] = protocols + + automation = constraints.get('automation', {}) + enable_fields = {k: v for k, v in automation.items() if k.endswith('_enabled')} + for k, v in enable_fields.items(): + auto_item = k.replace('_enabled', '') + methods = automation.pop(auto_item + '_methods', []) + if methods: + automation[auto_item + '_method'] = methods[0]['id'] + data['automation'] = automation + return data + + @classmethod + def create_or_update_internal_platforms(cls): + from assets.models import Platform + for category, type_cls in cls.category_types(): + data = type_cls.internal_platforms() + for tp, platform_datas in data.items(): + for d in platform_datas: + platform_data = { + **d, 'category': category.value, 'type': tp.value, + 'internal': True, 'charset': 'utf-8' + } + + diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py index abe085c58..d8b99aba5 100644 --- a/apps/assets/const/web.py +++ b/apps/assets/const/web.py @@ -36,3 +36,11 @@ class WebTypes(BaseType): 'choices': ['http'], } } + + @classmethod + def internal_platforms(cls): + return { + cls.WEBSITE: [ + {'name': 'Website'}, + ], + } diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index fb369dd1d..92e6aad69 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -17,11 +17,6 @@ class Migration(migrations.Migration): name='info', field=models.JSONField(blank=True, default=dict, verbose_name='Info'), ), - migrations.RenameField( - model_name='asset', - old_name='ip', - new_name='address', - ), migrations.CreateModel( name='Host', fields=[ diff --git a/apps/assets/migrations/0094_auto_20220402_1736.py b/apps/assets/migrations/0094_auto_20220402_1736.py index daf53270b..526e6a4c3 100644 --- a/apps/assets/migrations/0094_auto_20220402_1736.py +++ b/apps/assets/migrations/0094_auto_20220402_1736.py @@ -66,4 +66,7 @@ class Migration(migrations.Migration): model_name='asset', name='vendor', ), + migrations.DeleteModel( + name='Cluster', + ), ] diff --git a/apps/assets/migrations/0097_auto_20220426_1558.py b/apps/assets/migrations/0097_auto_20220426_1558.py index ed645cc57..827779aad 100644 --- a/apps/assets/migrations/0097_auto_20220426_1558.py +++ b/apps/assets/migrations/0097_auto_20220426_1558.py @@ -55,7 +55,7 @@ def migrate_database_to_asset(apps, *args): attrs.update(_attrs) db = db_model( - id=app.id, hostname=app.name, address=attrs['host'], + id=app.id, hostname=app.name, ip=attrs['host'], protocols='{}/{}'.format(app.type, attrs['port']), db_name=attrs['database'] or '', platform=platforms_map[app.type], @@ -83,7 +83,7 @@ def migrate_cloud_to_asset(apps, *args): print("Create cloud: {}".format(app.name)) cloud = cloud_model( id=app.id, hostname=app.name, - address=attrs.get('cluster', ''), + ip=attrs.get('cluster', ''), protocols='', platform=platform, org_id=app.org_id, ) diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index b52e5026f..854af0a72 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -16,8 +16,32 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=32, verbose_name='Name')), ('port', models.IntegerField(verbose_name='Port')), ('setting', models.JSONField(default=dict, verbose_name='Setting')), + ('platform', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.platform'),), ], ), + migrations.CreateModel( + name='PlatformAutomation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')), + ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), + ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), + ('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), + ('create_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), + ('create_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')), + ('change_password_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')), + ('change_password_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')), + ('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')), + ('verify_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')), + ('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), + ('gather_accounts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), + ], + ), + migrations.AddField( + model_name='platform', + name='automation', + field=models.OneToOneField(blank=True, null=True, on_delete=models.deletion.CASCADE, related_name='platform', to='assets.platformautomation', verbose_name='Automation'), + ), migrations.AddField( model_name='platform', name='domain_enabled', @@ -28,41 +52,6 @@ class Migration(migrations.Migration): name='protocols_enabled', field=models.BooleanField(default=True, verbose_name='Protocols enabled'), ), - migrations.AddField( - model_name='platform', - name='protocols', - field=models.ManyToManyField(blank=True, to='assets.PlatformProtocol', verbose_name='Protocols'), - ), - migrations.AddField( - model_name='platform', - name='change_password_enabled', - field=models.BooleanField(default=False, verbose_name='Change password enabled'), - ), - migrations.AddField( - model_name='platform', - name='change_password_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method'), - ), - migrations.AddField( - model_name='platform', - name='create_account_enabled', - field=models.BooleanField(default=False, verbose_name='Create account enabled'), - ), - migrations.AddField( - model_name='platform', - name='create_account_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method'), - ), - migrations.AddField( - model_name='platform', - name='ping_enabled', - field=models.BooleanField(default=False, verbose_name='Ping enabled'), - ), - migrations.AddField( - model_name='platform', - name='ping_method', - field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method'), - ), migrations.AddField( model_name='platform', name='su_enabled', @@ -73,34 +62,4 @@ class Migration(migrations.Migration): name='su_method', field=models.CharField(blank=True, max_length=32, null=True, verbose_name='SU method'), ), - migrations.AddField( - model_name='platform', - name='verify_account_enabled', - field=models.BooleanField(default=False, verbose_name='Verify account enabled'), - ), - migrations.AddField( - model_name='platform', - name='verify_account_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method'), - ), - migrations.AddField( - model_name='platform', - name='gather_accounts_enabled', - field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), - ), - migrations.AddField( - model_name='platform', - name='gather_accounts_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), - ), - migrations.AddField( - model_name='platform', - name='gather_facts_enabled', - field=models.BooleanField(default=False, verbose_name='Gather facts enabled'), - ), - migrations.AddField( - model_name='platform', - name='gather_facts_method', - field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method'), - ), ] diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index f91de6483..e5ead66c8 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -79,4 +79,24 @@ class Migration(migrations.Migration): name='su_from', field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.account', verbose_name='Su from'), ), + migrations.CreateModel( + name='AccountTemplate', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), + ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),), + ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), + ], + options={ + 'verbose_name': 'Account template', + 'unique_together': {('name', 'org_id')}, + }, + ), ] diff --git a/apps/assets/migrations/0101_auto_20220803_1448.py b/apps/assets/migrations/0101_auto_20220803_1448.py index bebfbfb42..9a07c9593 100644 --- a/apps/assets/migrations/0101_auto_20220803_1448.py +++ b/apps/assets/migrations/0101_auto_20220803_1448.py @@ -24,7 +24,4 @@ class Migration(migrations.Migration): ('asset', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.asset', verbose_name='Asset')), ], ), - migrations.DeleteModel( - name='Cluster', - ), ] diff --git a/apps/assets/migrations/0103_auto_20220811_1511.py b/apps/assets/migrations/0103_auto_20220811_1511.py index df467d984..03b5a637c 100644 --- a/apps/assets/migrations/0103_auto_20220811_1511.py +++ b/apps/assets/migrations/0103_auto_20220811_1511.py @@ -59,6 +59,11 @@ class Migration(migrations.Migration): name='name', field=models.CharField(max_length=128, verbose_name='Name'), ), + migrations.RenameField( + model_name='asset', + old_name='ip', + new_name='address', + ), migrations.AddField( model_name='asset', name='date_updated', @@ -74,4 +79,5 @@ class Migration(migrations.Migration): name='created_by', field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), ), + ] diff --git a/apps/assets/migrations/0106_auto_20220819_1523.py b/apps/assets/migrations/0106_auto_20220819_1523.py deleted file mode 100644 index 98ec0f73a..000000000 --- a/apps/assets/migrations/0106_auto_20220819_1523.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 3.2.13 on 2022-08-19 07:23 - -import assets.models.base -import common.db.fields -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0105_auto_20220817_1544'), - ] - - operations = [ - migrations.CreateModel( - name='AccountTemplate', - fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, verbose_name='Name')), - ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), - ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'),), - ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), - ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), - ], - options={ - 'verbose_name': 'Account template', - 'unique_together': {('name', 'org_id')}, - }, - ), - ] diff --git a/apps/assets/migrations/0109_auto_20220916_1556.py b/apps/assets/migrations/0106_auto_20220916_1556.py similarity index 91% rename from apps/assets/migrations/0109_auto_20220916_1556.py rename to apps/assets/migrations/0106_auto_20220916_1556.py index de9e6c0d9..70db1a2f8 100644 --- a/apps/assets/migrations/0109_auto_20220916_1556.py +++ b/apps/assets/migrations/0106_auto_20220916_1556.py @@ -49,10 +49,15 @@ def migrate_backup_types(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('assets', '0108_auto_20220915_1032'), + ('assets', '0105_auto_20220817_1544'), ] operations = [ + migrations.AlterField( + model_name='accountbackupplan', + name='types', + field=models.BigIntegerField(), + ), migrations.AddField( model_name='accountbackupplan', name='categories', diff --git a/apps/assets/migrations/0107_alter_accountbackupplan_types.py b/apps/assets/migrations/0107_alter_accountbackupplan_types.py deleted file mode 100644 index d2d0eedb2..000000000 --- a/apps/assets/migrations/0107_alter_accountbackupplan_types.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-14 12:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0106_auto_20220819_1523'), - ] - - operations = [ - migrations.AlterField( - model_name='accountbackupplan', - name='types', - field=models.BigIntegerField(), - ), - ] diff --git a/apps/assets/migrations/0108_auto_20220915_1032.py b/apps/assets/migrations/0108_auto_20220915_1032.py deleted file mode 100644 index 3f98f70c1..000000000 --- a/apps/assets/migrations/0108_auto_20220915_1032.py +++ /dev/null @@ -1,85 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-15 02:32 - -from django.db import migrations, models -import django - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0107_alter_accountbackupplan_types'), - ] - - operations = [ - migrations.CreateModel( - name='PlatformAutomation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')), - ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), - ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), - ('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), - ('create_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), - ('create_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')), - ('change_password_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')), - ('change_password_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')), - ('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')), - ('verify_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')), - ('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), - ('gather_accounts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), - ], - ), - migrations.RemoveField( - model_name='platform', - name='change_password_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='change_password_method', - ), - migrations.RemoveField( - model_name='platform', - name='create_account_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='create_account_method', - ), - migrations.RemoveField( - model_name='platform', - name='gather_accounts_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='gather_accounts_method', - ), - migrations.RemoveField( - model_name='platform', - name='gather_facts_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='gather_facts_method', - ), - migrations.RemoveField( - model_name='platform', - name='ping_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='ping_method', - ), - migrations.RemoveField( - model_name='platform', - name='verify_account_enabled', - ), - migrations.RemoveField( - model_name='platform', - name='verify_account_method', - ), - migrations.AddField( - model_name='platform', - name='automation', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='platform', to='assets.platformautomation', verbose_name='Automation'), - ), - ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index afc187675..c55a0f9b6 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -18,6 +18,7 @@ class PlatformProtocol(models.Model): name = models.CharField(max_length=32, verbose_name=_('Name')) port = models.IntegerField(verbose_name=_('Port')) setting = models.JSONField(verbose_name=_('Setting'), default=dict) + platform = models.ForeignKey('Platform', on_delete=models.CASCADE, related_name='protocols') class PlatformAutomation(models.Model): @@ -54,11 +55,11 @@ class Platform(models.Model): charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) - protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) # 账号有关的 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) su_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) - automation = models.OneToOneField(PlatformAutomation, on_delete=models.CASCADE, related_name='platform', blank=True, null=True, verbose_name=_("Automation")) + automation = models.OneToOneField(PlatformAutomation, on_delete=models.CASCADE, related_name='platform', + blank=True, null=True, verbose_name=_("Automation")) @property def type_constraints(self): diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index dfc2ace2c..62e493248 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -59,17 +59,17 @@ def update_internal_platforms(platform_model): {'name': 'Redis', 'category': 'database', 'type': 'redis'}, # 网络设备 - {'name': 'Generic', 'category': 'device', 'type': 'general', 'brand': 'other'}, - {'name': 'Huawei', 'category': 'device', 'type': 'general', 'brand': 'huawei'}, - {'name': 'Cisco', 'category': 'device', 'type': 'general', 'brand': 'cisco'}, - {'name': 'H3C', 'category': 'device', 'type': 'general', 'brand': 'h3c'}, + {'name': 'Generic', 'category': 'device', 'type': 'general'}, + {'name': 'Huawei', 'category': 'device', 'type': 'general'}, + {'name': 'Cisco', 'category': 'device', 'type': 'general'}, + {'name': 'H3C', 'category': 'device', 'type': 'general'}, # Web {'name': 'Website', 'category': 'web', 'type': 'general'}, # Cloud {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, - {'name': 'VMware vSphere', 'category': 'cloud', 'type': 'vsphere'}, + {'name': 'VMware vSphere', 'category': 'cloud', 'type': 'private'}, ] platforms = platform_model.objects.all() diff --git a/apps/ops/migrations/0023_automation_strategy.py b/apps/ops/migrations/0023_automation_strategy.py index 5389c86e9..d2807023f 100644 --- a/apps/ops/migrations/0023_automation_strategy.py +++ b/apps/ops/migrations/0023_automation_strategy.py @@ -11,7 +11,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0106_auto_20220819_1523'), + ('assets', '0105_auto_20220817_1544'), ('ops', '0022_auto_20220817_1346'), ] From a35e0c5efa6631604a44498adaecffbc197b1917 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 22 Sep 2022 16:39:41 +0800 Subject: [PATCH 149/488] =?UTF-8?q?perf:=20=E5=88=9B=E5=BB=BA=E5=86=85?= =?UTF-8?q?=E7=BD=AE=20platforms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/host.py | 14 ++++-------- apps/assets/const/types.py | 45 +++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 40b712c67..387a2e7e7 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -16,14 +16,8 @@ class HostTypes(BaseType): 'domain_enabled': True, 'su_enabled': True, 'su_methods': [ - { - 'name': 'sudo su', - 'id': 'sudo su' - }, - { - 'name': 'su -', - 'id': 'su -' - } + {'name': 'sudo su', 'id': 'sudo su'}, + {'name': 'su -', 'id': 'su -'} ], }, cls.WINDOWS: { @@ -72,10 +66,10 @@ class HostTypes(BaseType): ], cls.WINDOWS: [ {'name': 'Windows'}, - {'name': 'Windows-TLS', 'protocol_settings': { + {'name': 'Windows-TLS', 'protocols_setting': { 'rdp': {'security': 'tls'}, }}, - {'name': 'Windows-RDP', 'protocol_settings': { + {'name': 'Windows-RDP', 'protocols_setting': { 'rdp': {'security': 'rdp'}, }} ], diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 265e01e92..f0eece68b 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from common.db.models import IncludesTextChoicesMeta, ChoicesMixin from common.tree import TreeNode @@ -161,14 +163,47 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): @classmethod def create_or_update_internal_platforms(cls): - from assets.models import Platform + from assets.models import Platform, PlatformAutomation, PlatformProtocol + print("Create internal platforms") for category, type_cls in cls.category_types(): + print("## Category: {}".format(category.label)) data = type_cls.internal_platforms() + for tp, platform_datas in data.items(): + print(" >> Type: {}".format(tp.label)) + default_platform_data = cls.get_type_default_platform(category, tp) + default_automation = default_platform_data.pop('automation', {}) + default_protocols = default_platform_data.pop('protocols', []) + for d in platform_datas: - platform_data = { - **d, 'category': category.value, 'type': tp.value, - 'internal': True, 'charset': 'utf-8' - } + name = d['name'] + print(" - Platform: {}".format(name)) + _automation = d.pop('automation', {}) + _protocols = d.pop('_protocols', []) + _protocols_setting = d.pop('protocols_setting', {}) + protocols_data = deepcopy(default_protocols) + if _protocols: + protocols_data = [p for p in protocols_data if p['name'] in _protocols] + for p in protocols_data: + p['setting'] = {**_protocols_setting.get(p['name'], {}), **p.get('setting', {})} + + platform_data = {**default_platform_data, **d} + automation_data = {**default_automation, **_automation} + platform, created = Platform.objects.update_or_create( + defaults=platform_data, name=platform_data['name'] + ) + + if not platform.automation: + automation = PlatformAutomation.objects.create() + platform.automation = automation + platform.save() + else: + automation = platform.automation + for k, v in automation_data.items(): + setattr(automation, k, v) + automation.save() + + platform.protocols.all().delete() + [PlatformProtocol.objects.create(**p, platform=platform) for p in protocols_data] From 8f31a25fda7349fbba1361b99b34363effd73120 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 23 Sep 2022 10:15:07 +0800 Subject: [PATCH 150/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/types.py | 69 +++++-- apps/assets/migrations/0092_add_host.py | 79 ++++++++ .../migrations/0094_auto_20220402_1736.py | 3 - .../migrations/0095_auto_20220407_1726.py | 20 ++ .../migrations/0096_auto_20220426_1550.py | 60 +++--- .../migrations/0097_auto_20220426_1558.py | 151 +------------- .../migrations/0098_auto_20220430_2126.py | 184 ++++++++++++------ .../migrations/0100_auto_20220711_1413.py | 6 - .../migrations/0103_auto_20220811_1511.py | 35 ---- .../migrations/0105_auto_20220817_1544.py | 3 + .../migrations/0010_auto_20210812_1618.py | 2 +- .../migrations/0017_auto_20220623_1027.py | 2 +- 12 files changed, 314 insertions(+), 300 deletions(-) diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index f0eece68b..043779790 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -137,8 +137,8 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): def get_type_default_platform(cls, category, tp): constraints = cls.get_constraints(category, tp) data = { - 'name': tp.label, 'category': category.value, - 'type': tp.value, 'internal': True, + 'category': category, + 'type': tp, 'internal': True, 'charset': constraints.get('charset', 'utf-8'), 'domain_enabled': constraints.get('domain_enabled', False), 'su_enabled': constraints.get('su_enabled', False), @@ -162,8 +162,30 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): return data @classmethod - def create_or_update_internal_platforms(cls): + def create_or_update_by_platform_data(cls, name, platform_data): from assets.models import Platform, PlatformAutomation, PlatformProtocol + + automation_data = platform_data.pop('automation', {}) + protocols_data = platform_data.pop('protocols', []) + + platform, created = Platform.objects.update_or_create( + defaults=platform_data, name=name + ) + if not platform.automation: + automation = PlatformAutomation.objects.create() + platform.automation = automation + platform.save() + else: + automation = platform.automation + for k, v in automation_data.items(): + setattr(automation, k, v) + automation.save() + + platform.protocols.all().delete() + [PlatformProtocol.objects.create(**p, platform=platform) for p in protocols_data] + + @classmethod + def create_or_update_internal_platforms(cls): print("Create internal platforms") for category, type_cls in cls.category_types(): print("## Category: {}".format(category.label)) @@ -188,22 +210,31 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): for p in protocols_data: p['setting'] = {**_protocols_setting.get(p['name'], {}), **p.get('setting', {})} - platform_data = {**default_platform_data, **d} - automation_data = {**default_automation, **_automation} - platform, created = Platform.objects.update_or_create( - defaults=platform_data, name=platform_data['name'] - ) + platform_data = { + **default_platform_data, **d, + 'automation': {**default_automation, **_automation}, + 'protocols': protocols_data + } + cls.create_or_update_by_platform_data(name, platform_data) + + @classmethod + def update_user_create_platforms(cls, platform_cls): + internal_platforms = [] + for category, type_cls in cls.category_types(): + data = type_cls.internal_platforms() + for tp, platform_datas in data.items(): + for d in platform_datas: + internal_platforms.append(d['name']) + + user_platforms = platform_cls.objects.exclude(name__in=internal_platforms) + user_platforms.update(internal=False) + + for platform in user_platforms: + print("Update platform: {}".format(platform.name)) + platform_data = cls.get_type_default_platform(platform.category, platform.type) + cls.create_or_update_by_platform_data(platform.name, platform_data) + + - if not platform.automation: - automation = PlatformAutomation.objects.create() - platform.automation = automation - platform.save() - else: - automation = platform.automation - for k, v in automation_data.items(): - setattr(automation, k, v) - automation.save() - platform.protocols.all().delete() - [PlatformProtocol.objects.create(**p, platform=platform) for p in protocols_data] diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index 92e6aad69..a6138c30a 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -17,10 +17,89 @@ class Migration(migrations.Migration): name='info', field=models.JSONField(blank=True, default=dict, verbose_name='Info'), ), + migrations.RenameField( + model_name='asset', + old_name='hostname', + new_name='name', + ), + migrations.AlterField( + model_name='asset', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterModelOptions( + name='asset', + options={'ordering': ['name'], 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), ('test_assetconnectivity', 'Can test asset connectivity'), ('push_assetsystemuser', 'Can push system user to asset'), ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'), ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'}, + ), + migrations.RenameField( + model_name='asset', + old_name='ip', + new_name='address', + ), + migrations.AddField( + model_name='asset', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='asset', + name='updated_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='asset', + name='created_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), + ), migrations.CreateModel( name='Host', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), ], ), + migrations.CreateModel( + name='Database', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('db_name', models.CharField(blank=True, max_length=1024, verbose_name='Database')), + ], + options={ + 'verbose_name': 'Database', + }, + bases=('assets.asset',), + ), + migrations.CreateModel( + name='Device', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ], + options={ + 'abstract': False, + }, + bases=('assets.asset',), + ), + migrations.CreateModel( + name='Cloud', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ], + options={ + 'abstract': False, + }, + bases=('assets.asset',), + ), + migrations.CreateModel( + name='Web', + fields=[ + ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('autofill', models.CharField(default='basic', max_length=16)), + ('password_selector', models.CharField(blank=True, default='', max_length=128)), + ('submit_selector', models.CharField(blank=True, default='', max_length=128)), + ('username_selector', models.CharField(blank=True, default='', max_length=128)) + ], + options={ + 'abstract': False, + }, + bases=('assets.asset',), + ), ] diff --git a/apps/assets/migrations/0094_auto_20220402_1736.py b/apps/assets/migrations/0094_auto_20220402_1736.py index 526e6a4c3..daf53270b 100644 --- a/apps/assets/migrations/0094_auto_20220402_1736.py +++ b/apps/assets/migrations/0094_auto_20220402_1736.py @@ -66,7 +66,4 @@ class Migration(migrations.Migration): model_name='asset', name='vendor', ), - migrations.DeleteModel( - name='Cluster', - ), ] diff --git a/apps/assets/migrations/0095_auto_20220407_1726.py b/apps/assets/migrations/0095_auto_20220407_1726.py index fb20373cc..c11517a35 100644 --- a/apps/assets/migrations/0095_auto_20220407_1726.py +++ b/apps/assets/migrations/0095_auto_20220407_1726.py @@ -33,5 +33,25 @@ class Migration(migrations.Migration): name='type', field=models.CharField(default='linux', max_length=32, verbose_name='Type'), ), + migrations.AddField( + model_name='platform', + name='domain_enabled', + field=models.BooleanField(default=True, verbose_name='Domain enabled'), + ), + migrations.AddField( + model_name='platform', + name='protocols_enabled', + field=models.BooleanField(default=True, verbose_name='Protocols enabled'), + ), + migrations.AddField( + model_name='platform', + name='su_enabled', + field=models.BooleanField(default=False, verbose_name='Su enabled'), + ), + migrations.AddField( + model_name='platform', + name='su_method', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='SU method'), + ), migrations.RunPython(migrate_platform_type_to_lower) ] diff --git a/apps/assets/migrations/0096_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py index 562353b7f..486a3883f 100644 --- a/apps/assets/migrations/0096_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -12,48 +12,36 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Database', + name='PlatformProtocol', fields=[ - ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('db_name', models.CharField(blank=True, max_length=1024, verbose_name='Database')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32, verbose_name='Name')), + ('port', models.IntegerField(verbose_name='Port')), + ('setting', models.JSONField(default=dict, verbose_name='Setting')), + ('platform', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.platform'),), ], - options={ - 'verbose_name': 'Database', - }, - bases=('assets.asset',), ), migrations.CreateModel( - name='Device', + name='PlatformAutomation', fields=[ - ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')), + ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), + ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), + ('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), + ('create_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), + ('create_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')), + ('change_password_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')), + ('change_password_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')), + ('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')), + ('verify_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')), + ('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), + ('gather_accounts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), ], - options={ - 'abstract': False, - }, - bases=('assets.asset',), ), - migrations.CreateModel( - name='Cloud', - fields=[ - ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ], - options={ - 'abstract': False, - }, - bases=('assets.asset',), - ), - migrations.CreateModel( - name='Web', - fields=[ - ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('autofill', models.CharField(default='basic', max_length=16)), - ('password_selector', models.CharField(blank=True, default='', max_length=128)), - ('submit_selector', models.CharField(blank=True, default='', max_length=128)), - ('username_selector', models.CharField(blank=True, default='', max_length=128)) - ], - options={ - 'abstract': False, - }, - bases=('assets.asset',), + migrations.AddField( + model_name='platform', + name='automation', + field=models.OneToOneField(blank=True, null=True, on_delete=models.deletion.CASCADE, related_name='platform', to='assets.platformautomation', verbose_name='Automation'), ), ] diff --git a/apps/assets/migrations/0097_auto_20220426_1558.py b/apps/assets/migrations/0097_auto_20220426_1558.py index 827779aad..f563d50d7 100644 --- a/apps/assets/migrations/0097_auto_20220426_1558.py +++ b/apps/assets/migrations/0097_auto_20220426_1558.py @@ -1,159 +1,24 @@ # Generated by Django 3.1.14 on 2022-04-26 07:58 -import uuid from django.db import migrations - -failed_apps = [] +from assets.const import AllTypes -def create_app_platform(apps, *args): - platform_model = apps.get_model('assets', 'Platform') - platforms = [ - # DB - {'name': 'MySQL', 'category': 'database', 'type': 'mysql'}, - {'name': 'MariaDB', 'category': 'database', 'type': 'mariadb'}, - {'name': 'PostgreSQL', 'category': 'database', 'type': 'postgresql'}, - {'name': 'Oracle', 'category': 'database', 'type': 'oracle'}, - {'name': 'SQLServer', 'category': 'database', 'type': 'sqlserver'}, - {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, - {'name': 'Redis', 'category': 'database', 'type': 'redis'}, - {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, - {'name': 'Web', 'category': 'web', 'type': 'general'}, - ] - - for platform in platforms: - platform['internal'] = True - print("Create platform: {}".format(platform['name'])) - platform_model.objects.update_or_create(defaults=platform, name=platform['name']) +def create_internal_platforms(apps, *args): + AllTypes.create_or_update_internal_platforms() -def get_prop_name_id(apps, app, category): - asset_model = apps.get_model('assets', 'Asset') - _id = app.id - id_exists = asset_model.objects.filter(id=_id).exists() - if id_exists: - _id = uuid.uuid4() - name = app.name - name_exists = asset_model.objects.filter(hostname=name).exists() - if name_exists: - name = category + '-' + app.name - return _id, name - - -def migrate_database_to_asset(apps, *args): - app_model = apps.get_model('applications', 'Application') - db_model = apps.get_model('assets', 'Database') - platform_model = apps.get_model('assets', 'Platform') - - applications = app_model.objects.filter(category='db') - platforms = platform_model.objects.all() - platforms_map = {p.type: p for p in platforms} - - for app in applications: - attrs = {'host': '', 'port': 0, 'database': ''} - _attrs = app.attrs or {} - attrs.update(_attrs) - - db = db_model( - id=app.id, hostname=app.name, ip=attrs['host'], - protocols='{}/{}'.format(app.type, attrs['port']), - db_name=attrs['database'] or '', - platform=platforms_map[app.type], - org_id=app.org_id - ) - try: - print("Create database: ", app.name) - db.save() - except: - failed_apps.append(app) - pass - - -def migrate_cloud_to_asset(apps, *args): - app_model = apps.get_model('applications', 'Application') - cloud_model = apps.get_model('assets', 'Cloud') - platform_model = apps.get_model('assets', 'Platform') - - applications = app_model.objects.filter(category='cloud') - platform = platform_model.objects.filter(type='k8s').first() - print() - - for app in applications: - attrs = app.attrs - print("Create cloud: {}".format(app.name)) - cloud = cloud_model( - id=app.id, hostname=app.name, - ip=attrs.get('cluster', ''), - protocols='', platform=platform, - org_id=app.org_id, - ) - - try: - cloud.save() - except Exception as e: - failed_apps.append(cloud) - print("Error: ", e) - - -def create_app_nodes(apps, org_id): - node_model = apps.get_model('assets', 'Node') - - child_pattern = r'^[0-9]+:[0-9]+$' - node_keys = node_model.objects.filter(org_id=org_id) \ - .filter(key__regex=child_pattern) \ - .values_list('key', flat=True) - if not node_keys: - return - node_key_split = [key.split(':') for key in node_keys] - next_value = max([int(k[1]) for k in node_key_split]) + 1 - parent_key = node_key_split[0][0] - next_key = '{}:{}'.format(parent_key, next_value) - name = 'Apps' - parent = node_model.objects.get(key=parent_key) - full_value = parent.full_value + '/' + name - defaults = { - 'key': next_key, 'value': name, 'parent_key': parent_key, - 'full_value': full_value, 'org_id': org_id - } - node, created = node_model.objects.get_or_create( - defaults=defaults, value=name, org_id=org_id, - ) - node.parent = parent - return node - - -def migrate_to_nodes(apps, *args): - org_model = apps.get_model('orgs', 'Organization') - asset_model = apps.get_model('assets', 'Asset') - orgs = org_model.objects.all() - - # Todo: 优化一些 - for org in orgs: - node = create_app_nodes(apps, org.id) - assets = asset_model.objects.filter( - platform__category__in=['remote_app', 'database', 'cloud'], - org_id=org.id - ) - if not node: - continue - print("Set node asset: ", node) - node.assets_amount = len(assets) - node.save() - node.assets.set(assets) - parent = node.parent - parent.assets_amount += len(assets) - parent.save() +def update_user_platforms(apps, *args): + platform_cls = apps.get_model('assets', 'Platform') + AllTypes.update_user_create_platforms(platform_cls) class Migration(migrations.Migration): dependencies = [ ('assets', '0096_auto_20220426_1550'), - ('applications', '0020_auto_20220316_2028') ] operations = [ - migrations.RunPython(create_app_platform), - migrations.RunPython(migrate_database_to_asset), - migrations.RunPython(migrate_cloud_to_asset), - migrations.RunPython(migrate_to_nodes) + migrations.RunPython(create_internal_platforms), + migrations.RunPython(update_user_platforms), ] diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index 854af0a72..7dc157977 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -1,65 +1,137 @@ -# Generated by Django 3.1.14 on 2022-04-30 14:41 -from django.db import migrations, models +# Generated by Django 3.1.14 on 2022-04-26 07:58 +import uuid + +from django.db import migrations + +failed_apps = [] + + +def get_prop_name_id(apps, app, category): + asset_model = apps.get_model('assets', 'Asset') + _id = app.id + id_exists = asset_model.objects.filter(id=_id).exists() + if id_exists: + _id = uuid.uuid4() + name = app.name + name_exists = asset_model.objects.filter(name=name).exists() + if name_exists: + name = category + '-' + app.name + return _id, name + + +def migrate_database_to_asset(apps, *args): + app_model = apps.get_model('applications', 'Application') + db_model = apps.get_model('assets', 'Database') + platform_model = apps.get_model('assets', 'Platform') + + applications = app_model.objects.filter(category='db') + platforms = platform_model.objects.all().filter(internal=True) + platforms_map = {p.type: p for p in platforms} + + for app in applications: + attrs = {'host': '', 'port': 0, 'database': ''} + _attrs = app.attrs or {} + attrs.update(_attrs) + + db = db_model( + id=app.id, name=app.name, address=attrs['host'], + protocols='{}/{}'.format(app.type, attrs['port']), + db_name=attrs['database'] or '', + platform=platforms_map[app.type], + org_id=app.org_id + ) + try: + print("Create database: ", app.name) + db.save() + except: + failed_apps.append(app) + pass + + +def migrate_cloud_to_asset(apps, *args): + app_model = apps.get_model('applications', 'Application') + cloud_model = apps.get_model('assets', 'Cloud') + platform_model = apps.get_model('assets', 'Platform') + + applications = app_model.objects.filter(category='cloud') + platform = platform_model.objects.filter(type='k8s').first() + print() + + for app in applications: + attrs = app.attrs + print("Create cloud: {}".format(app.name)) + cloud = cloud_model( + id=app.id, name=app.name, + address=attrs.get('cluster', ''), + protocols='', platform=platform, + org_id=app.org_id, + ) + + try: + cloud.save() + except Exception as e: + failed_apps.append(cloud) + print("Error: ", e) + + +def create_app_nodes(apps, org_id): + node_model = apps.get_model('assets', 'Node') + + child_pattern = r'^[0-9]+:[0-9]+$' + node_keys = node_model.objects.filter(org_id=org_id) \ + .filter(key__regex=child_pattern) \ + .values_list('key', flat=True) + if not node_keys: + return + node_key_split = [key.split(':') for key in node_keys] + next_value = max([int(k[1]) for k in node_key_split]) + 1 + parent_key = node_key_split[0][0] + next_key = '{}:{}'.format(parent_key, next_value) + name = 'Apps' + parent = node_model.objects.get(key=parent_key) + full_value = parent.full_value + '/' + name + defaults = { + 'key': next_key, 'value': name, 'parent_key': parent_key, + 'full_value': full_value, 'org_id': org_id + } + node, created = node_model.objects.get_or_create( + defaults=defaults, value=name, org_id=org_id, + ) + node.parent = parent + return node + + +def migrate_to_nodes(apps, *args): + org_model = apps.get_model('orgs', 'Organization') + asset_model = apps.get_model('assets', 'Asset') + orgs = org_model.objects.all() + + # Todo: 优化一些 + for org in orgs: + node = create_app_nodes(apps, org.id) + assets = asset_model.objects.filter( + platform__category__in=['remote_app', 'database', 'cloud'], + org_id=org.id + ) + if not node: + continue + print("Set node asset: ", node) + node.assets_amount = len(assets) + node.save() + node.assets.set(assets) + parent = node.parent + parent.assets_amount += len(assets) + parent.save() class Migration(migrations.Migration): - dependencies = [ ('assets', '0097_auto_20220426_1558'), + ('applications', '0020_auto_20220316_2028') ] operations = [ - migrations.CreateModel( - name='PlatformProtocol', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=32, verbose_name='Name')), - ('port', models.IntegerField(verbose_name='Port')), - ('setting', models.JSONField(default=dict, verbose_name='Setting')), - ('platform', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.platform'),), - ], - ), - migrations.CreateModel( - name='PlatformAutomation', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')), - ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), - ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), - ('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), - ('create_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), - ('create_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')), - ('change_password_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')), - ('change_password_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')), - ('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')), - ('verify_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method')), - ('gather_accounts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), - ('gather_accounts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), - ], - ), - migrations.AddField( - model_name='platform', - name='automation', - field=models.OneToOneField(blank=True, null=True, on_delete=models.deletion.CASCADE, related_name='platform', to='assets.platformautomation', verbose_name='Automation'), - ), - migrations.AddField( - model_name='platform', - name='domain_enabled', - field=models.BooleanField(default=True, verbose_name='Domain enabled'), - ), - migrations.AddField( - model_name='platform', - name='protocols_enabled', - field=models.BooleanField(default=True, verbose_name='Protocols enabled'), - ), - migrations.AddField( - model_name='platform', - name='su_enabled', - field=models.BooleanField(default=False, verbose_name='Su enabled'), - ), - migrations.AddField( - model_name='platform', - name='su_method', - field=models.CharField(blank=True, max_length=32, null=True, verbose_name='SU method'), - ), + migrations.RunPython(migrate_database_to_asset), + migrations.RunPython(migrate_cloud_to_asset), + migrations.RunPython(migrate_to_nodes) ] diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py index ec84ebe2a..9f7ec8f73 100644 --- a/apps/assets/migrations/0100_auto_20220711_1413.py +++ b/apps/assets/migrations/0100_auto_20220711_1413.py @@ -5,11 +5,6 @@ from django.db import migrations from assets.models import Platform -def migrate_platform_set_ops(apps, *args): - platform_model = apps.get_model('assets', 'Platform') - Platform.set_default_platforms_ops(platform_model) - - def migrate_accounts(apps, schema_editor): auth_book_model = apps.get_model('assets', 'AuthBook') account_model = apps.get_model('assets', 'Account') @@ -89,5 +84,4 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(migrate_accounts), - migrations.RunPython(migrate_platform_set_ops) ] diff --git a/apps/assets/migrations/0103_auto_20220811_1511.py b/apps/assets/migrations/0103_auto_20220811_1511.py index 03b5a637c..15cfff68b 100644 --- a/apps/assets/migrations/0103_auto_20220811_1511.py +++ b/apps/assets/migrations/0103_auto_20220811_1511.py @@ -45,39 +45,4 @@ class Migration(migrations.Migration): model_name='asset', name='public_ip', ), - migrations.AlterModelOptions( - name='asset', - options={'ordering': ['name'], 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), ('test_assetconnectivity', 'Can test asset connectivity'), ('push_assetsystemuser', 'Can push system user to asset'), ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'), ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'}, - ), - migrations.RenameField( - model_name='asset', - old_name='hostname', - new_name='name', - ), - migrations.AlterField( - model_name='asset', - name='name', - field=models.CharField(max_length=128, verbose_name='Name'), - ), - migrations.RenameField( - model_name='asset', - old_name='ip', - new_name='address', - ), - migrations.AddField( - model_name='asset', - name='date_updated', - field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), - ), - migrations.AddField( - model_name='asset', - name='updated_by', - field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), - ), - migrations.AlterField( - model_name='asset', - name='created_by', - field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), - ), - ] diff --git a/apps/assets/migrations/0105_auto_20220817_1544.py b/apps/assets/migrations/0105_auto_20220817_1544.py index 9aacc5a35..1b2bb7ad0 100644 --- a/apps/assets/migrations/0105_auto_20220817_1544.py +++ b/apps/assets/migrations/0105_auto_20220817_1544.py @@ -13,6 +13,9 @@ class Migration(migrations.Migration): migrations.DeleteModel( name='AdminUser', ), + migrations.DeleteModel( + name='Cluster', + ), migrations.RemoveField( model_name='historicalauthbook', name='asset', diff --git a/apps/tickets/migrations/0010_auto_20210812_1618.py b/apps/tickets/migrations/0010_auto_20210812_1618.py index 36e4db4e2..e858f9acf 100644 --- a/apps/tickets/migrations/0010_auto_20210812_1618.py +++ b/apps/tickets/migrations/0010_auto_20210812_1618.py @@ -82,7 +82,7 @@ def create_ticket_flow_and_approval_rule(apps, schema_editor): super_user = user_model.objects.filter(role='Admin') assignees_display = ['{0.name}({0.username})'.format(i) for i in super_user] with transaction.atomic(): - for ticket_type in [TicketType.apply_asset, TicketType.apply_application]: + for ticket_type in [TicketType.apply_asset, 'apply_application']: ticket_flow_instance = ticket_flow_model.objects.create(created_by='System', type=ticket_type, org_id=org_id) approval_rule_instance = approval_rule_model.objects.create(strategy=TicketApprovalStrategy.super_admin, assignees_display=assignees_display) approval_rule_instance.assignees.set(list(super_user)) diff --git a/apps/tickets/migrations/0017_auto_20220623_1027.py b/apps/tickets/migrations/0017_auto_20220623_1027.py index 746d267c8..87752a469 100644 --- a/apps/tickets/migrations/0017_auto_20220623_1027.py +++ b/apps/tickets/migrations/0017_auto_20220623_1027.py @@ -112,7 +112,7 @@ def apply_application_migrate(apps, *args): init_global_dict(apps) ticket_model = apps.get_model('tickets', 'Ticket') - tickets = ticket_model.objects.filter(type=TicketType.apply_application) + tickets = ticket_model.objects.filter(type='apply_application') ticket_apply_app_model = apps.get_model('tickets', 'ApplyApplicationTicket') for instance in tickets: From 3a884388cdd31dba5fed4fe928053947f2e4a2e6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 23 Sep 2022 10:38:28 +0800 Subject: [PATCH 151/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=90=8D?= =?UTF-8?q?=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0060_node_full_value.py | 2 +- apps/assets/migrations/0105_auto_20220817_1544.py | 12 ++++++------ apps/tickets/migrations/0020_auto_20220817_1346.py | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/assets/migrations/0060_node_full_value.py b/apps/assets/migrations/0060_node_full_value.py index bf3afbbc0..f5e86030d 100644 --- a/apps/assets/migrations/0060_node_full_value.py +++ b/apps/assets/migrations/0060_node_full_value.py @@ -31,7 +31,7 @@ def migrate_nodes_full_value(apps, schema_editor): model = apps.get_model("assets", "Node") db_alias = schema_editor.connection.alias nodes = model.objects.using(db_alias).all() - print("- Start migrate node full value") + print("\n- Start migrate node full value") for i, node in enumerate(list(nodes)): print("{} start migrate {} node full value".format(i, node.value)) ancestor_keys = get_node_ancestor_keys(node.key, True) diff --git a/apps/assets/migrations/0105_auto_20220817_1544.py b/apps/assets/migrations/0105_auto_20220817_1544.py index 1b2bb7ad0..a83428382 100644 --- a/apps/assets/migrations/0105_auto_20220817_1544.py +++ b/apps/assets/migrations/0105_auto_20220817_1544.py @@ -10,12 +10,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.DeleteModel( - name='AdminUser', - ), - migrations.DeleteModel( - name='Cluster', - ), migrations.RemoveField( model_name='historicalauthbook', name='asset', @@ -62,4 +56,10 @@ class Migration(migrations.Migration): migrations.DeleteModel( name='AuthBook', ), + # migrations.DeleteModel( + # name='AdminUser', + # ), + migrations.DeleteModel( + name='Cluster', + ), ] diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index 2da21692e..a48675bc9 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -15,8 +15,9 @@ def migrate_system_to_account(apps, schema_editor): (apply_login_asset_ticket_model, 'apply_login_system_user', 'apply_login_account', False), ) + print("\nStart migrate system user to account") for model, old_field, new_field, m2m in model_system_user_account: - print("Start migrate '{}' system user to account".format(model.__name__)) + print(" - migrate '{}'".format(model.__name__)) count = 0 bulk_size = 1000 From 286d0e4ac189cc786719a234fd6955dd8ff9698c Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 23 Sep 2022 10:53:44 +0800 Subject: [PATCH 152/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0105_auto_20220817_1544.py | 12 ++++++------ apps/tickets/migrations/0020_auto_20220817_1346.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/assets/migrations/0105_auto_20220817_1544.py b/apps/assets/migrations/0105_auto_20220817_1544.py index a83428382..40084ffdd 100644 --- a/apps/assets/migrations/0105_auto_20220817_1544.py +++ b/apps/assets/migrations/0105_auto_20220817_1544.py @@ -50,16 +50,16 @@ class Migration(migrations.Migration): model_name='authbook', name='systemuser', ), + migrations.DeleteModel( + name='Cluster', + ), + migrations.DeleteModel( + name='AdminUser', + ), migrations.DeleteModel( name='HistoricalAuthBook', ), migrations.DeleteModel( name='AuthBook', ), - # migrations.DeleteModel( - # name='AdminUser', - # ), - migrations.DeleteModel( - name='Cluster', - ), ] diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index a48675bc9..91748dd76 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -39,7 +39,7 @@ def migrate_system_to_account(apps, schema_editor): setattr(obj, new_field, new_value) updated.append(obj) model.objects.bulk_update(updated, [new_field]) - print("Migrate account: {}-{} using: {:.2f}s".format( + print(" Migrate account: {}-{} using: {:.2f}s".format( count - len(objects), count, time.time()-start )) From 65b942ffa4b51d0617ce2d23bced6ef3253288bd Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 23 Sep 2022 14:45:09 +0800 Subject: [PATCH 153/488] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=20Permission?= =?UTF-8?q?=20filter=20=E6=96=B9=E6=B3=95=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/common.py | 2 +- apps/perms/models/asset_permission.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 87b78fe8c..2e4ff90fb 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -163,7 +163,7 @@ class UserGrantedAssetAccounts(ListAPIView): def get_queryset(self): # 获取用户-资产的授权规则 - assetperms = AssetPermission.filter_permissions(self.user, self.asset) + assetperms = AssetPermission.filter(self.user, self.asset) account_names = AssetPermission.get_account_names(assetperms) accounts = self.asset.filter_accounts(account_names) # 构造默认包含的账号,如: @INPUT @USER diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 61f5a4fd2..d83cd84b3 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -232,17 +232,17 @@ class AssetPermission(OrgModelMixin): return account_names @classmethod - def filter_permissions(cls, user=None, asset=None, account=None): + def filter(cls, user=None, asset=None, account=None): """ 获取同时包含 用户-资产-账号 的授权规则 """ assetperm_ids = [] if user: - user_assetperm_ids = cls.filter_permissions_by_user(user, flat=True) + user_assetperm_ids = cls.filter_by_user(user, flat=True) assetperm_ids.append(user_assetperm_ids) if asset: - asset_assetperm_ids = cls.filter_permissions_by_asset(asset, flat=True) + asset_assetperm_ids = cls.filter_by_asset(asset, flat=True) assetperm_ids.append(asset_assetperm_ids) if account: - account_assetperm_ids = cls.filter_permissions_by_account(account, flat=True) + account_assetperm_ids = cls.filter_by_account(account, flat=True) assetperm_ids.append(account_assetperm_ids) # & 是同时满足,比如有用户,但是用户的规则是空,那么返回也应该是空 assetperm_ids = list(reduce(lambda x, y: set(x) & set(y), assetperm_ids)) @@ -250,7 +250,7 @@ class AssetPermission(OrgModelMixin): return assetperms @classmethod - def filter_permissions_by_user(cls, user, with_group=True, flat=False): + def filter_by_user(cls, user, with_group=True, flat=False): assetperm_ids = set() user_assetperm_ids = AssetPermission.users.through.objects \ .filter(user_id=user.id) \ @@ -273,7 +273,7 @@ class AssetPermission(OrgModelMixin): return assetperms @classmethod - def filter_permissions_by_asset(cls, asset, with_node=True, flat=False): + def filter_by_asset(cls, asset, with_node=True, flat=False): assetperm_ids = set() asset_assetperm_ids = AssetPermission.assets.through.objects \ .filter(asset_id=asset.id) \ @@ -294,7 +294,7 @@ class AssetPermission(OrgModelMixin): return assetperms @classmethod - def filter_permissions_by_account(cls, account, flat=False): + def filter_by_account(cls, account, flat=False): assetperms = cls.objects.filter(accounts__contains=account).valid() if flat: assetperm_ids = assetperms.values_list('id', flat=True) From 399c6285f816b4aa20c3166c4855193c91b4e1d2 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 23 Sep 2022 15:59:37 +0800 Subject: [PATCH 154/488] =?UTF-8?q?feat:=20=E8=8E=B7=E5=8F=96=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E3=80=81=E8=B5=84=E4=BA=A7=E6=8E=88=E6=9D=83=E7=9A=84?= =?UTF-8?q?=E6=89=80=E6=9C=89=E8=B4=A6=E5=8F=B7API,=20=E8=BF=94=E5=9B=9E@I?= =?UTF-8?q?NPUT=E3=80=81@USER=E7=AD=89=E5=86=85=E9=83=A8=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 14 ++++++++++++++ apps/assets/models/asset/common.py | 6 +++--- apps/perms/api/user_permission/common.py | 11 +++++++---- apps/perms/hands.py | 4 ++-- apps/perms/models/asset_permission.py | 11 +++++------ 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 3159cc3b6..12ab80412 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -9,6 +9,10 @@ __all__ = ['Account', 'AccountTemplate'] class Account(BaseAccount): + class InnerAccount(models.TextChoices): + INPUT = '@INPUT', '@INPUT' + USER = '@USER', '@USER' + asset = models.ForeignKey( 'assets.Asset', related_name='accounts', on_delete=models.CASCADE, verbose_name=_('Asset') @@ -44,6 +48,16 @@ class Account(BaseAccount): def __str__(self): return '{}@{}'.format(self.username, self.asset.name) + @classmethod + def get_input_account(cls): + """ @INPUT 手动登录的账号(any) """ + return cls(name=cls.InnerAccount.INPUT.value, username='') + + @classmethod + def get_user_account(cls, username): + """ @USER 动态用户的账号(self) """ + return cls(name=cls.InnerAccount.USER.value, username=username) + class AccountTemplate(BaseAccount): class Meta: diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index f94143c6e..163d1a873 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -4,8 +4,6 @@ import logging import uuid -from functools import reduce -from collections import Iterable from django.db import models from django.db.models import Q @@ -180,9 +178,11 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): return tree_node def filter_accounts(self, account_names=None): + from perms.models import AssetPermission if account_names is None: return self.accounts.all() - assert isinstance(account_names, Iterable), '`account_names` must be an iterable object' + if AssetPermission.SpecialAccount.ALL in account_names: + return self.accounts.all() queries = Q(name__in=account_names) | Q(username__in=account_names) accounts = self.accounts.filter(queries) return accounts diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 2e4ff90fb..962d079bb 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -19,7 +19,7 @@ from perms.utils.permission import ( from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty -from perms.hands import User, Asset +from perms.hands import User, Asset, Account from perms import serializers from perms.models import AssetPermission @@ -150,7 +150,7 @@ class UserGrantedAssetAccounts(ListAPIView): } @lazyproperty - def user(self): + def user(self) -> User: user_id = self.kwargs.get('pk') return User.objects.get(id=user_id) @@ -165,9 +165,12 @@ class UserGrantedAssetAccounts(ListAPIView): # 获取用户-资产的授权规则 assetperms = AssetPermission.filter(self.user, self.asset) account_names = AssetPermission.get_account_names(assetperms) - accounts = self.asset.filter_accounts(account_names) + accounts = list(self.asset.filter_accounts(account_names)) + # @INPUT @USER + inner_accounts = [Account.get_input_account(), Account.get_user_account(self.user.username)] + all_accounts = accounts + inner_accounts # 构造默认包含的账号,如: @INPUT @USER - return accounts + return all_accounts class MyGrantedAssetAccounts(UserGrantedAssetAccounts): diff --git a/apps/perms/hands.py b/apps/perms/hands.py index 1537b7260..dabb9f7c0 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -2,12 +2,12 @@ # from users.models import User, UserGroup -from assets.models import Asset, Node, Label, FavoriteAsset +from assets.models import Asset, Node, Label, FavoriteAsset, Account from assets.serializers import NodeSerializer __all__ = [ 'User', 'UserGroup', 'Asset', 'Node', 'Label', 'FavoriteAsset', - 'NodeSerializer', + 'NodeSerializer', 'Account' ] diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index d83cd84b3..fe9d1b1b7 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -83,6 +83,9 @@ class AssetPermissionManager(OrgManager): class AssetPermission(OrgModelMixin): + class SpecialAccount(models.TextChoices): + ALL = '@ALL', 'All' + id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), @@ -113,11 +116,6 @@ class AssetPermission(OrgModelMixin): objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)() - class SpecialAccount(models.TextChoices): - ALL = '@ALL', 'All' - INPUT = '@INPUT', 'Input' - USER = '@USER', 'User' - class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset permission") @@ -295,7 +293,8 @@ class AssetPermission(OrgModelMixin): @classmethod def filter_by_account(cls, account, flat=False): - assetperms = cls.objects.filter(accounts__contains=account).valid() + queries = Q(accounts__contains=account) | Q(accounts__contains=cls.SpecialAccount.ALL.value) + assetperms = cls.objects.filter(queries).valid() if flat: assetperm_ids = assetperms.values_list('id', flat=True) return assetperm_ids From 234acd63175beab14c96bd31e7333a3da7ccc0f1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 23 Sep 2022 18:59:19 +0800 Subject: [PATCH 155/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/protocol.py | 7 +-- apps/assets/models/account.py | 8 +-- apps/assets/models/base.py | 54 ++++----------------- apps/assets/models/platform.py | 9 ++++ apps/assets/serializers/account/account.py | 33 +++++++------ apps/assets/serializers/account/base.py | 52 ++++++++++++++++++++ apps/assets/serializers/account/common.py | 29 ----------- apps/assets/serializers/account/history.py | 7 +-- apps/assets/serializers/account/template.py | 23 ++------- apps/assets/serializers/asset/common.py | 11 +++-- apps/assets/serializers/platform.py | 4 +- apps/common/drf/serializers.py | 1 - apps/common/mixins/serializers.py | 29 ++++++----- apps/orgs/caches.py | 2 - apps/orgs/serializers.py | 2 - apps/rbac/builtin.py | 3 +- 16 files changed, 127 insertions(+), 147 deletions(-) create mode 100644 apps/assets/serializers/account/base.py delete mode 100644 apps/assets/serializers/account/common.py diff --git a/apps/assets/const/protocol.py b/apps/assets/const/protocol.py index d7367b85d..92ca5f3a1 100644 --- a/apps/assets/const/protocol.py +++ b/apps/assets/const/protocol.py @@ -20,6 +20,7 @@ class Protocol(ChoicesMixin, models.TextChoices): k8s = 'k8s', 'K8S' http = 'http', 'HTTP' + _settings = None @classmethod def device_protocols(cls): @@ -106,7 +107,7 @@ class Protocol(ChoicesMixin, models.TextChoices): @classmethod def settings(cls): return { - **cls.device_protocols(), - **cls.database_protocols(), - **cls.cloud_protocols() + **cls.device_protocols(), + **cls.database_protocols(), + **cls.cloud_protocols() } diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 3159cc3b6..65e6c74f0 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -34,12 +34,8 @@ class Account(BaseAccount): ] @lazyproperty - def ip(self): - return self.asset.address - - @lazyproperty - def asset_name(self): - return self.asset.name + def platform(self): + return self.asset.platform def __str__(self): return '{}@{}'.format(self.username, self.asset.name) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 429a4a0a5..7f96a07e9 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -73,11 +73,17 @@ class BaseAccount(OrgModelMixin): created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) @property - def public_key(self): - return '' + def has_secret(self): + return bool(self.secret) @property def private_key(self): + if self.secret_type == self.SecretType.ssh_key: + return self.secret + return None + + @property + def public_key(self): return '' @private_key.setter @@ -94,10 +100,6 @@ class BaseAccount(OrgModelMixin): self.secret = value self.secret_type = 'password' - def expire_assets_amount(self): - cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id) - cache.delete(cache_key) - @property def ssh_key_fingerprint(self): if self.public_key: @@ -152,53 +154,15 @@ class BaseAccount(OrgModelMixin): pass return None - def set_auth(self, **kwargs): - update_fields = [] - for k, v in kwargs.items(): - setattr(self, k, v) - update_fields.append(k) - if update_fields: - self.save(update_fields=update_fields) - - def _merge_auth(self, other): - if other.password: - self.password = other.password - if other.public_key or other.private_key: - self.private_key = other.private_key - self.public_key = other.public_key - - def clear_auth(self): - self.password = '' - self.private_key = '' - self.public_key = '' - self.token = '' - self.save() - @staticmethod def gen_password(length=36): return random_string(length, special_char=True) @staticmethod def gen_key(username): - private_key, public_key = ssh_key_gen( - username=username - ) + private_key, public_key = ssh_key_gen(username=username) return private_key, public_key - def auto_gen_auth(self, password=True, key=True): - _password = None - _private_key = None - _public_key = None - - if password: - _password = self.gen_password() - if key: - _private_key, _public_key = self.gen_key(self.username) - self.set_auth( - password=_password, private_key=_private_key, - public_key=_public_key - ) - def _to_secret_json(self): """Push system user use it""" return { diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index c55a0f9b6..30f377b00 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _ from assets.const import AllTypes from common.db.fields import JsonDictTextField +from assets.const import Protocol + __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] @@ -20,6 +22,13 @@ class PlatformProtocol(models.Model): setting = models.JSONField(verbose_name=_('Setting'), default=dict) platform = models.ForeignKey('Platform', on_delete=models.CASCADE, related_name='protocols') + def __str__(self): + return '{}/{}'.format(self.name, self.port) + + @property + def secret_types(self): + return Protocol.settings().get(self.name, {}).get('secret_types') + class PlatformAutomation(models.Model): ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index eb37e3bd9..ecf8e8490 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -1,12 +1,10 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin from common.drf.fields import ObjectRelatedField from assets.models import Account, AccountTemplate, Asset -from assets.serializers.base import AuthValidateMixin -from .common import AccountFieldsSerializerMixin +from .base import BaseAccountSerializer class AccountSerializerCreateMixin(serializers.ModelSerializer): @@ -28,7 +26,8 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): @staticmethod def replace_attrs(account_template: AccountTemplate, attrs: dict): exclude_fields = [ - '_state', 'org_id', 'id', 'date_created', 'date_updated' + '_state', 'org_id', 'id', 'date_created', + 'date_updated' ] template_attrs = { k: v for k, v in account_template.__dict__.items() @@ -52,34 +51,36 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): return instance -class AccountSerializer( - AuthValidateMixin, AccountSerializerCreateMixin, - AccountFieldsSerializerMixin, BulkOrgResourceModelSerializer -): +class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): asset = ObjectRelatedField( required=False, queryset=Asset.objects, label=_('Asset'), attrs=('id', 'name', 'address') ) - platform = serializers.ReadOnlyField(label=_("Platform")) - class Meta(AccountFieldsSerializerMixin.Meta): + class Meta(BaseAccountSerializer.Meta): model = Account - fields = AccountFieldsSerializerMixin.Meta.fields \ + fields = BaseAccountSerializer.Meta.fields \ + + ['su_from', 'version', 'asset'] \ + ['template', 'push_now'] + extra_kwargs = { + **BaseAccountSerializer.Meta.extra_kwargs, + 'name': {'required': False, 'allow_null': True}, + } + + def __init__(self, *args, data=None, **kwargs): + super().__init__(*args, data=data, **kwargs) + if data and 'name' not in data: + data['name'] = data.get('username') @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('asset') + queryset = queryset.prefetch_related('asset', 'asset__platform') return queryset class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): - fields_backup = [ - 'name', 'address', 'platform', 'protocols', 'username', 'password', - 'private_key', 'public_key', 'date_created', 'date_updated', 'version' - ] extra_kwargs = { 'password': {'write_only': False}, 'private_key': {'write_only': False}, diff --git a/apps/assets/serializers/account/base.py b/apps/assets/serializers/account/base.py new file mode 100644 index 000000000..262272a0a --- /dev/null +++ b/apps/assets/serializers/account/base.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +from io import StringIO + +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from common.utils import validate_ssh_private_key, ssh_private_key_gen +from common.drf.fields import EncryptedField +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from assets.models import BaseAccount + +__all__ = ['BaseAccountSerializer'] + + +class BaseAccountSerializer(BulkOrgResourceModelSerializer): + secret = EncryptedField( + label=_('Secret'), required=False, allow_blank=True, + allow_null=True, max_length=40960 + ) + + class Meta: + model = BaseAccount + fields_mini = ['id', 'name', 'username'] + fields_small = fields_mini + ['privileged', 'secret_type', 'secret', 'has_secret'] + fields_other = ['created_by', 'date_created', 'date_updated', 'comment'] + fields = fields_small + fields_other + extra_kwargs = { + 'secret': {'write_only': True}, + 'passphrase': {'write_only': True}, + } + + def validate_private_key(self, private_key): + if not private_key: + return '' + passphrase = self.initial_data.get('passphrase') + passphrase = passphrase if passphrase else None + valid = validate_ssh_private_key(private_key, password=passphrase) + if not valid: + raise serializers.ValidationError(_("private key invalid or passphrase error")) + + private_key = ssh_private_key_gen(private_key, password=passphrase) + string_io = StringIO() + private_key.write_private_key(string_io) + private_key = string_io.getvalue() + return private_key + + def validate_secret(self, value): + secret_type = self.initial_data.get('secret_type') + if secret_type == 'ssh_key': + value = self.validate_private_key(value) + return value + diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py deleted file mode 100644 index dff27bbc0..000000000 --- a/apps/assets/serializers/account/common.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import serializers - -__all__ = ['AccountFieldsSerializerMixin'] - - -class AccountFieldsSerializerMixin(serializers.ModelSerializer): - class Meta: - fields_mini = [ - 'id', 'name', 'username', 'privileged', - 'platform', 'version', 'secret_type', - ] - fields_write_only = ['secret', 'passphrase'] - fields_other = ['date_created', 'date_updated', 'comment'] - fields_small = fields_mini + fields_write_only + fields_other - fields_fk = ['asset'] - fields = fields_small + fields_fk - extra_kwargs = { - 'secret': {'write_only': True}, - 'passphrase': {'write_only': True}, - 'token': {'write_only': True}, - 'password': {'write_only': True}, - } - - def validate_name(self, value): - if not value: - return self.initial_data.get('username') - return '' diff --git a/apps/assets/serializers/account/history.py b/apps/assets/serializers/account/history.py index 0a6c2042f..a49636334 100644 --- a/apps/assets/serializers/account/history.py +++ b/apps/assets/serializers/account/history.py @@ -1,17 +1,14 @@ from assets.models import Account from common.drf.serializers import SecretReadableMixin -from .common import AccountFieldsSerializerMixin +from .base import BaseAccountSerializer from .account import AccountSerializer, AccountSecretSerializer class AccountHistorySerializer(AccountSerializer): class Meta: model = Account.history.model - fields = AccountFieldsSerializerMixin.Meta.fields_mini + \ - AccountFieldsSerializerMixin.Meta.fields_write_only + \ - AccountFieldsSerializerMixin.Meta.fields_fk + \ - ['history_id', 'date_created', 'date_updated'] + fields = BaseAccountSerializer.Meta.fields_mini read_only_fields = fields ref_name = 'AccountHistorySerializer' diff --git a/apps/assets/serializers/account/template.py b/apps/assets/serializers/account/template.py index 1df7ea65a..09c5b4541 100644 --- a/apps/assets/serializers/account/template.py +++ b/apps/assets/serializers/account/template.py @@ -2,31 +2,16 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from assets.models import AccountTemplate -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from assets.serializers.base import AuthValidateMixin -from .common import AccountFieldsSerializerMixin +from .base import BaseAccountSerializer -class AccountTemplateSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): - class Meta: +class AccountTemplateSerializer(BaseAccountSerializer): + class Meta(BaseAccountSerializer.Meta): model = AccountTemplate - fields_mini = ['id', 'name', 'username', 'privileged'] - fields_write_only = AccountFieldsSerializerMixin.Meta.fields_write_only - fields_other = AccountFieldsSerializerMixin.Meta.fields_other - fields = fields_mini + fields_write_only + fields_other - extra_kwargs = { - 'username': {'required': True}, - 'name': {'required': True}, - 'private_key': {'write_only': True}, - 'public_key': {'write_only': True}, - } - - def validate(self, attrs): - attrs = self._validate_gen_key(attrs) - return attrs @classmethod def validate_required(cls, attrs): + # Todo: why ? required_field_dict = {} error = _('This field is required.') for k, v in cls().fields.items(): diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 2ec7c95bb..b2746493c 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # +from io import StringIO + from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from django.db.transaction import atomic @@ -7,6 +9,7 @@ from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.fields import LabeledChoiceField, ObjectRelatedField +from common.utils import validate_ssh_private_key, ssh_private_key_gen from ..account import AccountSerializer from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol from ...const import Category, AllTypes @@ -47,15 +50,17 @@ class AssetAccountSerializer(AccountSerializer): class Meta(AccountSerializer.Meta): fields_mini = [ - 'id', 'name', 'username', 'privileged', 'version', - 'secret_type', + 'id', 'name', 'username', 'privileged', + 'version', 'secret_type', ] fields_write_only = [ - 'secret', 'passphrase', 'push_now' + 'secret', 'push_now' ] fields = fields_mini + fields_write_only + + class AssetSerializer(JMSWritableNestedModelSerializer): category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type')) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 19611b5d6..c592f73e5 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _ from common.drf.fields import LabeledChoiceField from common.drf.serializers import JMSWritableNestedModelSerializer from ..models import Platform, PlatformProtocol, PlatformAutomation -from ..const import Category, AllTypes +from ..const import Category, AllTypes, Protocol __all__ = ['PlatformSerializer', 'PlatformOpsMethodSerializer'] @@ -64,7 +64,7 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer): class Meta: model = PlatformProtocol - fields = ['id', 'name', 'port', 'setting'] + fields = ['id', 'name', 'port', 'secret_types', 'setting'] class PlatformSerializer(JMSWritableNestedModelSerializer): diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers.py index aa0023b90..4f2080c95 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers.py @@ -23,7 +23,6 @@ __all__ = [ class MethodSerializer(serializers.Serializer): - def __init__(self, method_name=None, **kwargs): self.method_name = method_name super().__init__(**kwargs) diff --git a/apps/common/mixins/serializers.py b/apps/common/mixins/serializers.py index 72b7610d4..93c0a2050 100644 --- a/apps/common/mixins/serializers.py +++ b/apps/common/mixins/serializers.py @@ -2,13 +2,18 @@ # from collections import Iterable -from django.db.models import Prefetch, F, NOT_PROVIDED +from django.db.models import NOT_PROVIDED from django.core.exceptions import ObjectDoesNotExist from rest_framework.utils import html from rest_framework.settings import api_settings from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty -__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin', 'CommonSerializerMixin', 'CommonBulkSerializerMixin'] + + +__all__ = [ + 'BulkSerializerMixin', 'BulkListSerializerMixin', + 'CommonSerializerMixin', 'CommonBulkSerializerMixin' +] class BulkSerializerMixin(object): @@ -281,20 +286,12 @@ class DynamicFieldsMixin: self.fields.pop(field, None) -class EagerLoadQuerySetFields: - def setup_eager_loading(self, queryset): - """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related( - Prefetch('nodes'), - Prefetch('labels'), - ).select_related('admin_user', 'domain', 'platform') \ - .annotate(platform_base=F('platform__base')) - return queryset - - class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin): instance: None initial_data: dict + common_fields = [ + 'comment', 'created_by', 'date_created', 'date_updated', + ] def get_initial_value(self, attr, default=None): value = self.initial_data.get(attr) @@ -305,6 +302,12 @@ class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin): return value return default + def get_field_names(self, declared_fields, info): + names = super().get_field_names(declared_fields, info) + common_names = [i for i in self.common_fields if i in names] + primary_names = [i for i in names if i not in self.common_fields] + return primary_names + common_names + class CommonBulkSerializerMixin(BulkSerializerMixin, CommonSerializerMixin): pass diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index e9e5bc700..c3a1cb86d 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -52,8 +52,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache): assets_amount = IntegerField() nodes_amount = IntegerField(queryset=Node.objects) - admin_users_amount = IntegerField() - system_users_amount = IntegerField() domains_amount = IntegerField(queryset=Domain.objects) gateways_amount = IntegerField(queryset=Gateway.objects) asset_perms_amount = IntegerField(queryset=AssetPermission.objects) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index fadf1ef8d..c080332dd 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -11,8 +11,6 @@ class ResourceStatisticsSerializer(serializers.Serializer): assets_amount = serializers.IntegerField(required=False) nodes_amount = serializers.IntegerField(required=False) - admin_users_amount = serializers.IntegerField(required=False) - system_users_amount = serializers.IntegerField(required=False) domains_amount = serializers.IntegerField(required=False) gateways_amount = serializers.IntegerField(required=False) diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index c56326601..3bca6138f 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -164,8 +164,9 @@ class BuiltinRole: @classmethod def sync_to_db(cls, show_msg=False): roles = cls.get_roles() + print("\n Update builtin roles") for pre_role in roles.values(): role, created = pre_role.update_or_create_role() if show_msg: - print("Update builtin Role: {} - {}".format(role.name, created)) + print(" - Update: {} - {}".format(role.name, created)) From 6ed3b519280642e8b98ff1e6d94eeb2bafe06016 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 26 Sep 2022 10:43:18 +0800 Subject: [PATCH 156/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20serailizer?= =?UTF-8?q?=20=E7=BB=9F=E4=B8=80=E5=88=B0=20drf=20=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/favorite_asset.py | 2 +- apps/audits/serializers.py | 3 +- apps/common/drf/serializers/__init__.py | 2 + .../{serializers.py => serializers/common.py} | 41 ++-------------- .../serializers/mixin.py} | 47 ++++++++++++++++--- apps/common/mixins/__init__.py | 1 - apps/orgs/mixins/serializers.py | 2 +- apps/perms/serializers/permission_relation.py | 2 +- apps/terminal/serializers/terminal.py | 2 +- apps/users/serializers/user.py | 2 +- 10 files changed, 52 insertions(+), 52 deletions(-) create mode 100644 apps/common/drf/serializers/__init__.py rename apps/common/drf/{serializers.py => serializers/common.py} (67%) rename apps/common/{mixins/serializers.py => drf/serializers/mixin.py} (87%) diff --git a/apps/assets/serializers/favorite_asset.py b/apps/assets/serializers/favorite_asset.py index 7c024bf1a..cc0647943 100644 --- a/apps/assets/serializers/favorite_asset.py +++ b/apps/assets/serializers/favorite_asset.py @@ -4,7 +4,7 @@ from rest_framework import serializers from orgs.utils import tmp_to_root_org -from common.mixins import BulkSerializerMixin +from common.drf.serializers import BulkSerializerMixin from ..models import FavoriteAsset diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 9895176fd..8b9d28005 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -2,9 +2,8 @@ # from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from django.db.models import F -from common.mixins import BulkSerializerMixin +from common.drf.serializers import BulkSerializerMixin from terminal.models import Session from ops.models import CommandExecution from . import models diff --git a/apps/common/drf/serializers/__init__.py b/apps/common/drf/serializers/__init__.py new file mode 100644 index 000000000..7ecadafc7 --- /dev/null +++ b/apps/common/drf/serializers/__init__.py @@ -0,0 +1,2 @@ +from .common import * +from .mixin import * diff --git a/apps/common/drf/serializers.py b/apps/common/drf/serializers/common.py similarity index 67% rename from apps/common/drf/serializers.py rename to apps/common/drf/serializers/common.py index 4f2080c95..836914a60 100644 --- a/apps/common/drf/serializers.py +++ b/apps/common/drf/serializers/common.py @@ -1,3 +1,4 @@ + from rest_framework import serializers from rest_framework.serializers import Serializer from rest_framework.serializers import ModelSerializer @@ -6,22 +7,17 @@ from django.utils.translation import gettext_lazy as _ from django.utils.functional import cached_property from drf_writable_nested.serializers import WritableNestedModelSerializer -from common.mixins import BulkListSerializerMixin -from common.mixins.serializers import BulkSerializerMixin -from common.drf.fields import EncryptedField +from .mixin import BulkListSerializerMixin, BulkSerializerMixin + __all__ = [ 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer', - 'SecretReadableMixin', 'JMSWritableNestedModelSerializer', + 'JMSWritableNestedModelSerializer', 'GroupedChoiceSerializer', ] -# MethodSerializer -# ---------------- - - class MethodSerializer(serializers.Serializer): def __init__(self, method_name=None, **kwargs): self.method_name = method_name @@ -65,10 +61,6 @@ class MethodSerializer(serializers.Serializer): return self.serializer.get_initial() -# Other Serializer -# ---------------- - - class EmptySerializer(Serializer): pass @@ -94,30 +86,5 @@ class GroupedChoiceSerializer(ChoiceSerializer): children = ChoiceSerializer(many=True, label=_("Children")) -class SecretReadableMixin(serializers.Serializer): - """ 加密字段 (EncryptedField) 可读性 """ - - def __init__(self, *args, **kwargs): - super(SecretReadableMixin, self).__init__(*args, **kwargs) - if not hasattr(self, 'Meta') or not hasattr(self.Meta, 'extra_kwargs'): - return - extra_kwargs = self.Meta.extra_kwargs - for field_name, serializer_field in self.fields.items(): - if not isinstance(serializer_field, EncryptedField): - continue - if field_name not in extra_kwargs: - continue - field_extra_kwargs = extra_kwargs[field_name] - if 'write_only' not in field_extra_kwargs: - continue - serializer_field.write_only = field_extra_kwargs['write_only'] - - class JMSWritableNestedModelSerializer(WritableNestedModelSerializer): pass - # - # def _get_related_pk(self, data, model_class): - # pk = data.get('pk') or data.get('id') or data.get(model_class._meta.pk.attname) - # if pk: - # return str(pk) - # return None diff --git a/apps/common/mixins/serializers.py b/apps/common/drf/serializers/mixin.py similarity index 87% rename from apps/common/mixins/serializers.py rename to apps/common/drf/serializers/mixin.py index 93c0a2050..b0cbee27e 100644 --- a/apps/common/mixins/serializers.py +++ b/apps/common/drf/serializers/mixin.py @@ -1,21 +1,43 @@ -# -*- coding: utf-8 -*- -# from collections import Iterable from django.db.models import NOT_PROVIDED from django.core.exceptions import ObjectDoesNotExist from rest_framework.utils import html +from rest_framework import serializers from rest_framework.settings import api_settings from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty +from common.drf.fields import EncryptedField +from common.utils import lazyproperty + __all__ = [ 'BulkSerializerMixin', 'BulkListSerializerMixin', - 'CommonSerializerMixin', 'CommonBulkSerializerMixin' + 'CommonSerializerMixin', 'CommonBulkSerializerMixin', + 'SecretReadableMixin', ] +class SecretReadableMixin(serializers.Serializer): + """ 加密字段 (EncryptedField) 可读性 """ + + def __init__(self, *args, **kwargs): + super(SecretReadableMixin, self).__init__(*args, **kwargs) + if not hasattr(self, 'Meta') or not hasattr(self.Meta, 'extra_kwargs'): + return + extra_kwargs = self.Meta.extra_kwargs + for field_name, serializer_field in self.fields.items(): + if not isinstance(serializer_field, EncryptedField): + continue + if field_name not in extra_kwargs: + continue + field_extra_kwargs = extra_kwargs[field_name] + if 'write_only' not in field_extra_kwargs: + continue + serializer_field.write_only = field_extra_kwargs['write_only'] + + class BulkSerializerMixin(object): """ Become rest_framework_bulk not support uuid as a primary key @@ -56,15 +78,15 @@ class BulkSerializerMixin(object): @classmethod def many_init(cls, *args, **kwargs): + from .common import AdaptedBulkListSerializer meta = getattr(cls, 'Meta', None) assert meta is not None, 'Must have `Meta`' if not hasattr(meta, 'list_serializer_class'): - from common.drf.serializers import AdaptedBulkListSerializer meta.list_serializer_class = AdaptedBulkListSerializer return super(BulkSerializerMixin, cls).many_init(*args, **kwargs) -class BulkListSerializerMixin(object): +class BulkListSerializerMixin: """ Become rest_framework_bulk doing bulk update raise Exception: 'QuerySet' object has no attribute 'pk' when doing bulk update @@ -289,9 +311,12 @@ class DynamicFieldsMixin: class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin): instance: None initial_data: dict - common_fields = [ + common_fields = ( 'comment', 'created_by', 'date_created', 'date_updated', - ] + ) + secret_fields = ( + 'password', 'token', 'secret', 'key', 'private_key', 'public_key', + ) def get_initial_value(self, attr, default=None): value = self.initial_data.get(attr) @@ -302,6 +327,14 @@ class CommonSerializerMixin(DynamicFieldsMixin, DefaultValueFieldsMixin): return value return default + def get_fields(self): + fields = super().get_fields() + for name, field in fields.items(): + if name in self.secret_fields and \ + not isinstance(self, SecretReadableMixin): + field.write_only = True + return fields + def get_field_names(self, declared_fields, info): names = super().get_field_names(declared_fields, info) common_names = [i for i in self.common_fields if i in names] diff --git a/apps/common/mixins/__init__.py b/apps/common/mixins/__init__.py index 4249b3d64..b2a7ec7e4 100644 --- a/apps/common/mixins/__init__.py +++ b/apps/common/mixins/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # from .models import * -from .serializers import * from .api import * from .views import * diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py index 5c1a561e3..4ec936c2f 100644 --- a/apps/orgs/mixins/serializers.py +++ b/apps/orgs/mixins/serializers.py @@ -5,7 +5,7 @@ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from common.validators import ProjectUniqueValidator -from common.mixins import BulkSerializerMixin, CommonSerializerMixin +from common.drf.serializers import BulkSerializerMixin, CommonSerializerMixin from ..utils import get_current_org_id_for_serializer diff --git a/apps/perms/serializers/permission_relation.py b/apps/perms/serializers/permission_relation.py index 2384c4845..4c76ae3fa 100644 --- a/apps/perms/serializers/permission_relation.py +++ b/apps/perms/serializers/permission_relation.py @@ -2,7 +2,7 @@ # from rest_framework import serializers -from common.mixins import BulkSerializerMixin +from common.drf.serializers import BulkSerializerMixin from assets.models import Asset, Node from perms.models import AssetPermission from users.models import User diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index dd6565348..1ef4f6158 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -1,7 +1,7 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from common.drf.serializers import BulkModelSerializer, AdaptedBulkListSerializer +from common.drf.serializers import BulkModelSerializer from common.utils import is_uuid from users.serializers import ServiceAccountSerializer from common.utils import get_request_ip, pretty_string diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index eb03a0e6f..833752727 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -4,7 +4,7 @@ from functools import partial from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.mixins import CommonBulkSerializerMixin +from common.drf.serializers import CommonBulkSerializerMixin from common.validators import PhoneValidator from common.utils import pretty_string, get_logger from common.drf.fields import EncryptedField From 00e1dd6996c0859e0d33c941e86b25e6ffbeb3b9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 26 Sep 2022 11:01:21 +0800 Subject: [PATCH 157/488] fix: remote gateway filter address --- apps/assets/api/domain.py | 4 ++-- apps/assets/api/system_user.py | 0 requirements/requirements.txt | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) delete mode 100644 apps/assets/api/system_user.py diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 2a58a21f0..39f2b44f9 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -31,8 +31,8 @@ class DomainViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet): model = Gateway - filterset_fields = ("domain__name", "name", "username", "address", "domain") - search_fields = ("domain__name", "name", "username", "address") + filterset_fields = ("domain__name", "name", "username", "domain") + search_fields = ("domain__name", "name", "username", ) serializer_class = serializers.GatewaySerializer diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 20c33d2bc..070764f1b 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,6 @@ amqp==5.0.9 -ansible==2.10.7 +ansible==6.4.0 +ansible-runner==4.8.0 asn1crypto==0.24.0 bcrypt==3.1.4 billiard==3.6.4.0 @@ -141,3 +142,4 @@ ForgeryPy3==0.3.1 django-debug-toolbar==3.5 Pympler==1.0.1 IPy==1.1 + From 72b0fb427443b1f9e54b693508c371efe5fc57c4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 26 Sep 2022 18:03:48 +0800 Subject: [PATCH 158/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platform?= =?UTF-8?q?=20=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE=20ansible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/base.py | 4 ---- apps/assets/const/cloud.py | 2 ++ apps/assets/const/database.py | 4 ++++ apps/assets/const/device.py | 4 ++++ apps/assets/const/host.py | 12 +++++++++- apps/assets/const/types.py | 11 +++++++-- .../migrations/0096_auto_20220426_1550.py | 2 ++ apps/assets/models/platform.py | 2 ++ apps/assets/serializers/asset/common.py | 4 +--- apps/assets/serializers/platform.py | 7 +++--- apps/common/db/models.py | 24 ------------------- apps/ops/ansible/inventory.py | 13 +++++++++- apps/ops/ansible/new_runner.py | 15 ++++++++++++ 13 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 apps/ops/ansible/new_runner.py diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index 71e68cd83..e39f8c09c 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -52,7 +52,3 @@ class BaseType(TextChoices): @classmethod def internal_platforms(cls): raise NotImplementedError - - @classmethod - def create_or_update_internal_platforms(cls): - data = cls._internal_platforms() diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index e344d08fb..fb9214c65 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -20,6 +20,8 @@ class CloudTypes(BaseType): def _get_automation_constrains(cls) -> dict: constrains = { '*': { + 'ansible_enabled': False, + 'ansible_config': {}, 'gather_facts_enabled': False, 'verify_account_enabled': False, 'change_password_enabled': False, diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py index 372a941d5..0a6bf0853 100644 --- a/apps/assets/const/database.py +++ b/apps/assets/const/database.py @@ -25,6 +25,10 @@ class DatabaseTypes(BaseType): def _get_automation_constrains(cls) -> dict: constrains = { '*': { + 'ansible_enabled': True, + 'ansible_config': { + 'ansible_connection': 'local', + }, 'gather_facts_enabled': True, 'gather_accounts_enabled': True, 'verify_account_enabled': True, diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index 1cb0f47d8..3524c3c7a 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -31,6 +31,10 @@ class DeviceTypes(BaseType): def _get_automation_constrains(cls) -> dict: return { '*': { + 'ansible_enabled': True, + 'ansible_config': { + 'ansible_connection': 'local', + }, 'ping_enabled': True, 'gather_facts_enabled': False, 'gather_accounts_enabled': False, diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 387a2e7e7..160d6754b 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -40,13 +40,23 @@ class HostTypes(BaseType): def _get_automation_constrains(cls) -> dict: return { '*': { + 'ansible_enabled': True, + 'ansible_config': { + 'ansible_connection': 'smart', + }, 'ping_enabled': True, 'gather_facts_enabled': True, 'gather_accounts_enabled': True, 'verify_account_enabled': True, 'change_password_enabled': True, 'create_account_enabled': True, - } + }, + cls.WINDOWS: { + 'ansible_config': { + 'ansible_shell_type': 'powershell', + 'ansible_connection': 'ssh', + }, + }, } @classmethod diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 043779790..454a5d358 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -1,6 +1,6 @@ from copy import deepcopy -from common.db.models import IncludesTextChoicesMeta, ChoicesMixin +from common.db.models import ChoicesMixin from common.tree import TreeNode from .category import Category @@ -11,7 +11,7 @@ from .web import WebTypes from .cloud import CloudTypes -class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): +class AllTypes(ChoicesMixin): choices: list includes = [ HostTypes, DeviceTypes, DatabaseTypes, @@ -19,6 +19,13 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): ] _category_constrains = {} + @classmethod + def choices(cls): + choices = [] + for tp in cls.includes: + choices.extend(tp.choices) + return choices + @classmethod def get_constraints(cls, category, tp): types_cls = dict(cls.category_types()).get(category) diff --git a/apps/assets/migrations/0096_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py index 486a3883f..acfa93e7d 100644 --- a/apps/assets/migrations/0096_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -25,6 +25,8 @@ class Migration(migrations.Migration): name='PlatformAutomation', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ansible_enabled', models.BooleanField(default=False, verbose_name='Enabled')), + ('ansible_config', models.JSONField(default=dict, verbose_name='Ansible config')), ('ping_enabled', models.BooleanField(default=False, verbose_name='Ping enabled')), ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 30f377b00..3b2ea2ae5 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -31,6 +31,8 @@ class PlatformProtocol(models.Model): class PlatformAutomation(models.Model): + ansible_enabled = models.BooleanField(default=False, verbose_name=_("Enabled")) + ansible_config = models.JSONField(default=dict, verbose_name=_("Ansible config")) ping_enabled = models.BooleanField(default=False, verbose_name=_("Ping enabled")) ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index b2746493c..419beba2b 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -59,11 +59,9 @@ class AssetAccountSerializer(AccountSerializer): fields = fields_mini + fields_write_only - - class AssetSerializer(JMSWritableNestedModelSerializer): category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) - type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type')) + type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type')) domain = ObjectRelatedField(required=False, queryset=Domain.objects, label=_('Domain')) platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index c592f73e5..b1e6820cd 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _ from common.drf.fields import LabeledChoiceField from common.drf.serializers import JMSWritableNestedModelSerializer from ..models import Platform, PlatformProtocol, PlatformAutomation -from ..const import Category, AllTypes, Protocol +from ..const import Category, AllTypes __all__ = ['PlatformSerializer', 'PlatformOpsMethodSerializer'] @@ -36,7 +36,8 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): class Meta: model = PlatformAutomation fields = [ - 'id', 'ping_enabled', 'ping_method', + 'id', 'ansible_enabled', 'ansible_config', + 'ping_enabled', 'ping_method', 'gather_facts_enabled', 'gather_facts_method', 'create_account_enabled', 'create_account_method', 'change_password_enabled', 'change_password_method', @@ -68,7 +69,7 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer): class PlatformSerializer(JMSWritableNestedModelSerializer): - type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type")) + type = LabeledChoiceField(choices=AllTypes.choices(), label=_("Type")) category = LabeledChoiceField(choices=Category.choices, label=_("Category")) protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) automation = PlatformAutomationSerializer(label=_('Automation'), required=False) diff --git a/apps/common/db/models.py b/apps/common/db/models.py index 4969bd1a7..bac1f1b52 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -15,36 +15,12 @@ import inspect from django.db import models from django.db.models import F, Value, ExpressionWrapper -from enum import _EnumDict from django.db import transaction from django.db.models import QuerySet from django.db.models.functions import Concat from django.utils.translation import ugettext_lazy as _ -class IncludesTextChoicesMeta(type): - def __new__(metacls, classname, bases, classdict): - includes = classdict.pop('includes', None) - assert includes - - attrs = _EnumDict() - attrs._cls_name = classname - for k, v in classdict.items(): - attrs[k] = v - - for cls in includes: - _member_names_ = cls._member_names_ - _member_map_ = cls._member_map_ - _value2label_map_ = cls._value2label_map_ - - for name in _member_names_: - value = str(_member_map_[name]) - label = _value2label_map_[value] - attrs[name] = value, label - bases = (models.TextChoices,) - return type(classname, bases, attrs) - - class BitOperationChoice: NONE = 0 NAME_MAP: dict diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index c94290592..e024bc45b 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -83,7 +83,7 @@ class BaseInventory(InventoryManager): 用于生成动态构建Ansible Inventory. super().__init__ 会自动调用 host_list: [{ "name": "", - "ip": "", + "address": "", "port": "", "username": "", "password": "", @@ -154,3 +154,14 @@ class BaseInventory(InventoryManager): return self.get_hosts(pattern) +class JMSInventory: + def __init__(self, assets, account=None, ansible_connection='ssh', + account_policy='smart', host_var_callback=None): + """ + :param assets: + :param account: account username name if not set use account_policy + :param ansible_connection: ssh, local, + :param account_policy: + :param host_var_callback: + """ + pass diff --git a/apps/ops/ansible/new_runner.py b/apps/ops/ansible/new_runner.py new file mode 100644 index 000000000..7802c1a82 --- /dev/null +++ b/apps/ops/ansible/new_runner.py @@ -0,0 +1,15 @@ +import ansible_runner + + +class AnsibleInventory: + def __init__(self, assets, account=None, ansible_connection='ssh'): + self.assets = assets + self.account = account + + +class AdHocRunner: + pass + + +class PlaybookRunner: + pass From 0eccd313ffaace2e57c7d712554558fcbbc10434 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 27 Sep 2022 16:14:18 +0800 Subject: [PATCH 159/488] =?UTF-8?q?fix:=20=E9=94=81=E5=AE=9A=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=8C=85=E7=89=88=E6=9C=AC=20pyOpenSSL=3D=3D22.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 070764f1b..cc7df5f69 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -136,6 +136,7 @@ django-mysql==3.9.0 django-redis==5.2.0 python-redis-lock==3.7.0 redis==4.3.3 +pyOpenSSL==22.0.0 # Debug ipython==8.4.0 ForgeryPy3==0.3.1 From 2a9613d90a1eed28eb690e470d24b83321f7a951 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 28 Sep 2022 12:10:39 +0800 Subject: [PATCH 160/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20asset=20se?= =?UTF-8?q?rializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 419beba2b..56dea63d5 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -from io import StringIO from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ @@ -9,7 +8,6 @@ from django.db.models import F from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.fields import LabeledChoiceField, ObjectRelatedField -from common.utils import validate_ssh_private_key, ssh_private_key_gen from ..account import AccountSerializer from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol from ...const import Category, AllTypes @@ -29,7 +27,7 @@ class AssetProtocolsSerializer(serializers.ModelSerializer): class AssetLabelSerializer(serializers.ModelSerializer): class Meta: model = Label - fields = ['name', 'value'] + fields = ['id', 'name', 'value'] extra_kwargs = { 'name': {'required': False}, 'value': {'required': False} @@ -62,7 +60,7 @@ class AssetAccountSerializer(AccountSerializer): class AssetSerializer(JMSWritableNestedModelSerializer): category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type')) - domain = ObjectRelatedField(required=False, queryset=Domain.objects, label=_('Domain')) + domain = ObjectRelatedField(required=False, queryset=Domain.objects, label=_('Domain'), allow_null=True) platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) From cf69caaade3a10aa69db99a757f2609da0bebd3d Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 28 Sep 2022 14:17:49 +0800 Subject: [PATCH 161/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20ansible=20?= =?UTF-8?q?runner=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index cc7df5f69..ea6c56b59 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,6 +1,6 @@ amqp==5.0.9 ansible==6.4.0 -ansible-runner==4.8.0 +ansible-runner==2.2.1 asn1crypto==0.24.0 bcrypt==3.1.4 billiard==3.6.4.0 From 351d3b297dbf9abf1a3513967ab31d193a4feae4 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 28 Sep 2022 18:40:33 +0800 Subject: [PATCH 162/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E8=B5=84=E4=BA=A7=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?API=E5=8F=8AModel=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 6 ++ apps/assets/models/asset/common.py | 3 +- apps/perms/api/user_permission/common.py | 22 ++-- apps/perms/models/asset_permission.py | 122 ++++++++++++---------- apps/perms/serializers/user_permission.py | 3 +- apps/perms/urls/asset_permission.py | 15 ++- 6 files changed, 98 insertions(+), 73 deletions(-) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index be568d9dd..4394f6583 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -1,4 +1,5 @@ from django.db import models +from django.db.models import Q from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords @@ -54,6 +55,11 @@ class Account(BaseAccount): """ @USER 动态用户的账号(self) """ return cls(name=cls.InnerAccount.USER.value, username=username) + @classmethod + def filter(cls, asset_ids, account_usernames): + queries = Q(asset_id__in=asset_ids) & Q(username__in=account_usernames) + return cls.objects.filter(queries) + class AccountTemplate(BaseAccount): class Meta: diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 163d1a873..ef64e5fb6 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -183,7 +183,8 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): return self.accounts.all() if AssetPermission.SpecialAccount.ALL in account_names: return self.accounts.all() - queries = Q(name__in=account_names) | Q(username__in=account_names) + # queries = Q(name__in=account_names) | Q(username__in=account_names) + queries = Q(username__in=account_names) accounts = self.accounts.filter(queries) return accounts diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 962d079bb..52d3a4f31 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -2,8 +2,8 @@ # import uuid import time +from collections import defaultdict -from django.db.models import Q from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator from rest_framework.views import APIView, Response @@ -21,7 +21,7 @@ from common.utils import get_logger, lazyproperty from perms.hands import User, Asset, Account from perms import serializers -from perms.models import AssetPermission +from perms.models import AssetPermission, Action logger = get_logger(__name__) @@ -162,15 +162,19 @@ class UserGrantedAssetAccounts(ListAPIView): return asset def get_queryset(self): - # 获取用户-资产的授权规则 - assetperms = AssetPermission.filter(self.user, self.asset) - account_names = AssetPermission.get_account_names(assetperms) - accounts = list(self.asset.filter_accounts(account_names)) + accounts = AssetPermission.get_user_perm_asset_accounts( + self.user, self.asset, with_actions=True + ) + return accounts # @INPUT @USER - inner_accounts = [Account.get_input_account(), Account.get_user_account(self.user.username)] - all_accounts = accounts + inner_accounts + # inner_accounts = [ + # Account.get_input_account(), Account.get_user_account(self.user.username) + # ] + # for inner_account in inner_accounts: + # inner_account.actions = Action.ALL + # accounts = accounts + inner_accounts # 构造默认包含的账号,如: @INPUT @USER - return all_accounts + # return accounts class MyGrantedAssetAccounts(UserGrantedAssetAccounts): diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index fe9d1b1b7..76da872f8 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -5,6 +5,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.db import models from django.db.models import F, Q, TextChoices +from collections import defaultdict from assets.models import Asset, Node, FamilyMixin, Account from orgs.mixins.models import OrgModelMixin @@ -76,6 +77,11 @@ class AssetPermissionQuerySet(models.QuerySet): q = (Q(is_active=False) | Q(date_start__gt=now) | Q(date_expired__lt=now)) return self.filter(q) + def filter_by_accounts(self, accounts): + q = Q(accounts__contains=accounts) | \ + Q(accounts__contains=AssetPermission.SpecialAccount.ALL.value) + return self.filter(q) + class AssetPermissionManager(OrgManager): def valid(self): @@ -212,94 +218,96 @@ class AssetPermission(OrgModelMixin): names = [node.full_value for node in self.nodes.all()] return names + # Related accounts def get_asset_accounts(self): asset_ids = self.get_all_assets(flat=True) - queries = Q(asset_id__in=asset_ids) \ - & (Q(username__in=self.accounts) | Q(name__in=self.accounts)) - accounts = Account.objects.filter(queries) + accounts = Account.filter(asset_ids, self.accounts) return accounts @classmethod - def get_account_names(cls, perms): + def get_user_perm_asset_accounts(cls, user, asset: Asset, with_actions=False): + perms = cls.filter(user, asset) + all_account_names = cls.retrieve_account_names(perms) + accounts = asset.filter_accounts(all_account_names) + if with_actions: + cls.set_accounts_actions(accounts, perms=perms) + return accounts + + @classmethod + def set_accounts_actions(cls, accounts, perms): + # set account actions + account_names = accounts.values_list('username', flat=True) + perms = perms.filter_by_accounts(account_names) + account_names_actions_map = defaultdict(set) + account_names_actions = perms.values_list('accounts', 'actions') + for account_names, actions in account_names_actions: + for account_name in account_names: + account_names_actions_map[account_name] |= actions + for account in accounts: + account.actions = account_names_actions_map.get(account.username) + return accounts + + @classmethod + def retrieve_account_names(cls, perms): account_names = set() for perm in perms: - perm: cls if not isinstance(perm.accounts, list): continue account_names.update(perm.accounts) return account_names @classmethod - def filter(cls, user=None, asset=None, account=None): + def filter(cls, user=None, asset=None, account_names=None): """ 获取同时包含 用户-资产-账号 的授权规则 """ - assetperm_ids = [] + perm_ids = [] if user: user_assetperm_ids = cls.filter_by_user(user, flat=True) - assetperm_ids.append(user_assetperm_ids) + perm_ids.append(user_assetperm_ids) if asset: asset_assetperm_ids = cls.filter_by_asset(asset, flat=True) - assetperm_ids.append(asset_assetperm_ids) - if account: - account_assetperm_ids = cls.filter_by_account(account, flat=True) - assetperm_ids.append(account_assetperm_ids) + perm_ids.append(asset_assetperm_ids) # & 是同时满足,比如有用户,但是用户的规则是空,那么返回也应该是空 - assetperm_ids = list(reduce(lambda x, y: set(x) & set(y), assetperm_ids)) - assetperms = cls.objects.filter(id__in=assetperm_ids).valid().order_by('-date_expired') - return assetperms + perm_ids = list(reduce(lambda x, y: set(x) & set(y), perm_ids)) + perms = cls.objects.filter(id__in=perm_ids) + if account_names: + perms = perms.filter_by_accounts(account_names) + return perms.valid().order_by('-date_expired') @classmethod def filter_by_user(cls, user, with_group=True, flat=False): - assetperm_ids = set() - user_assetperm_ids = AssetPermission.users.through.objects \ - .filter(user_id=user.id) \ - .values_list('assetpermission_id', flat=True) \ - .distinct() - assetperm_ids.update(user_assetperm_ids) - + perm_ids = set() + user_perm_ids = AssetPermission.users.through.objects.filter( + user_id=user.id + ).values_list('assetpermission_id', flat=True).distinct() + perm_ids.update(user_perm_ids) if with_group: usergroup_ids = user.get_groups(flat=True) - usergroups_assetperm_id = AssetPermission.user_groups.through.objects \ - .filter(usergroup_id__in=usergroup_ids) \ - .values_list('assetpermission_id', flat=True) \ - .distinct() - assetperm_ids.update(usergroups_assetperm_id) - + usergroups_perm_id = AssetPermission.user_groups.through.objects.filter( + usergroup_id__in=usergroup_ids + ).values_list('assetpermission_id', flat=True).distinct() + perm_ids.update(usergroups_perm_id) if flat: - return assetperm_ids - else: - assetperms = cls.objects.filter(id__in=assetperm_ids).valid() - return assetperms + return perm_ids + perms = cls.objects.filter(id__in=perm_ids).valid() + return perms @classmethod def filter_by_asset(cls, asset, with_node=True, flat=False): - assetperm_ids = set() - asset_assetperm_ids = AssetPermission.assets.through.objects \ - .filter(asset_id=asset.id) \ - .values_list('assetpermission_id', flat=True) - assetperm_ids.update(asset_assetperm_ids) - + perm_ids = set() + asset_perm_ids = AssetPermission.assets.through.objects.filter( + asset_id=asset.id + ).values_list('assetpermission_id', flat=True).distinct() + perm_ids.update(asset_perm_ids) if with_node: node_ids = asset.get_all_nodes(flat=True) - node_assetperm_ids = AssetPermission.nodes.through.objects \ - .filter(node_id__in=node_ids) \ - .values_list('assetpermission_id', flat=True) - assetperm_ids.update(node_assetperm_ids) - + node_perm_ids = AssetPermission.nodes.through.objects.filter( + node_id__in=node_ids + ).values_list('assetpermission_id', flat=True).distinct() + perm_ids.update(node_perm_ids) if flat: - return assetperm_ids - else: - assetperms = cls.objects.filter(id__in=assetperm_ids).valid() - return assetperms - - @classmethod - def filter_by_account(cls, account, flat=False): - queries = Q(accounts__contains=account) | Q(accounts__contains=cls.SpecialAccount.ALL.value) - assetperms = cls.objects.filter(queries).valid() - if flat: - assetperm_ids = assetperms.values_list('id', flat=True) - return assetperm_ids - else: - return assetperms + return perm_ids + perms = cls.objects.filter(id__in=perm_ids).valid() + return perms class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 40754b637..9b7ac091c 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -49,8 +49,9 @@ class AccountsGrantedSerializer(serializers.ModelSerializer): # Todo: 添加前端登录逻辑中需要的一些字段,比如:是否需要手动输入密码 # need_manual = serializers.BooleanField(label=_('Need manual input')) + actions = ActionsField(read_only=True) class Meta: model = Account - fields = ['id', 'name', 'username'] + fields = ['id', 'name', 'username', 'actions'] read_only_fields = fields diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 28af1a94d..5606ee639 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -58,14 +58,15 @@ user_permission_urlpatterns = [ # 收藏的资产 path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), + # v3 中上面的 API 基本不用动 - # Todo: 删除 + # Todo: v3 删除 # Asset System users path('/assets//system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'), path('assets//system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), - # Todo: 增加 - # 获取所有和资产相关联的账号列表 + # Todo: v3 增加 + # 获取所有和资产-用户关联的账号列表 path('/assets//accounts/', api.UserGrantedAssetAccounts.as_view(), name='user-asset-accounts'), path('assets//accounts/', api.MyGrantedAssetAccounts.as_view(), name='my-asset-accounts') ] @@ -77,17 +78,21 @@ user_group_permission_urlpatterns = [ path('/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'), path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'), path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), + + # Todo: v3 删除 path('/assets//system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'), + # Todo: v3 增加 + # 获取所有和资产-用户组关联的账号列表 + path('/assets//accounts/', api.UserGrantedAssetAccounts.as_view(), name='user-asset-accounts'), ] permission_urlpatterns = [ - # Todo: 获取规则中授权的所有账号列表 - # # 授权规则中授权的资产 path('/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'), path('/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'), # 验证用户是否有某个资产和系统用户的权限 + # Todo: API 需要修改,验证用户有某个账号的权限 path('user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), path('user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), From da35e931a2cbdafa69a571676aa846d215e451e3 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 29 Sep 2022 14:19:14 +0800 Subject: [PATCH 163/488] =?UTF-8?q?perf:=20=E6=9B=B4=E6=96=B0=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0username=20passwd=20submit=20selector=E5=8F=AF?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/platform.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index b1e6820cd..f5fe60376 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -6,7 +6,6 @@ from common.drf.serializers import JMSWritableNestedModelSerializer from ..models import Platform, PlatformProtocol, PlatformAutomation from ..const import Category, AllTypes - __all__ = ['PlatformSerializer', 'PlatformOpsMethodSerializer'] @@ -27,9 +26,9 @@ class ProtocolSettingSerializer(serializers.Serializer): # HTTP auto_fill = serializers.BooleanField(default=False, label=_("Auto fill")) - username_selector = serializers.CharField(default='', label=_("Username selector")) - password_selector = serializers.CharField(default='', label=_("Password selector")) - submit_selector = serializers.CharField(default='', label=_("Submit selector")) + username_selector = serializers.CharField(default='', allow_blank=True, label=_("Username selector")) + password_selector = serializers.CharField(default='', allow_blank=True, label=_("Password selector")) + submit_selector = serializers.CharField(default='', allow_blank=True, label=_("Submit selector")) class PlatformAutomationSerializer(serializers.ModelSerializer): From ec0c334acc78f533e0e8595ba3adf2f63f566800 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 29 Sep 2022 14:44:27 +0800 Subject: [PATCH 164/488] perf: account search --- apps/assets/api/account/account.py | 4 ++-- apps/assets/filters.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index bb60fdfd9..4b26c042d 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -18,8 +18,8 @@ __all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] class AccountViewSet(OrgBulkModelViewSet): model = Account - filterset_fields = ("username", "asset", 'address', 'name') - search_fields = ('username', 'address', 'name') + filterset_fields = ("username", "asset", 'name') + search_fields = ('username', 'asset__address', 'name') filterset_class = AccountFilterSet serializer_classes = { 'default': serializers.AccountSerializer, diff --git a/apps/assets/filters.py b/apps/assets/filters.py index e44378ef7..76aa648ce 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -161,6 +161,7 @@ class AccountFilterSet(BaseFilterSet): ip = filters.CharFilter(field_name='address', lookup_expr='exact') hostname = filters.CharFilter(field_name='name', lookup_expr='exact') username = filters.CharFilter(field_name="username", lookup_expr='exact') + address = filters.CharFilter(field_name="asset__address", lookup_expr='exact') assets = UUIDInFilter(field_name='asset_id', lookup_expr='in') nodes = UUIDInFilter(method='filter_nodes') From 4a1aeefb82be03ef5232f3abb512c7c83b0b0ea2 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 29 Sep 2022 16:18:12 +0800 Subject: [PATCH 165/488] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E3=80=81=E7=94=A8=E6=88=B7=E7=BB=84=E5=AF=B9=E4=BA=8E?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E8=B4=A6=E5=8F=B7=E7=9A=84API=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_group_permission.py | 15 +++++++ apps/perms/api/user_permission/common.py | 52 ++++++++++++++++-------- apps/perms/models/asset_permission.py | 35 +++++++++++----- apps/perms/urls/asset_permission.py | 11 +++-- 4 files changed, 83 insertions(+), 30 deletions(-) diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 6d45de865..0a88c4228 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -20,6 +20,7 @@ __all__ = [ 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodeChildrenAsTreeApi', 'UserGroupGrantedAssetSystemUsersApi', + 'UserGroupGrantedAssetAccountsApi', ] @@ -203,3 +204,17 @@ class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIVie class UserGroupGrantedAssetSystemUsersApi(UserGroupMixin, uapi.UserGrantedAssetSystemUsersForAdminApi): def get_asset_system_user_ids_with_actions(self, asset): return get_asset_system_user_ids_with_actions_by_group(self.group, asset) + + +class UserGroupGrantedAssetAccountsApi(uapi.UserGrantedAssetAccountsApi): + + @lazyproperty + def user_group(self): + group_id = self.kwargs.get('pk') + return UserGroup.objects.get(id=group_id) + + def get_queryset(self): + accounts = AssetPermission.get_perm_asset_accounts( + user_group=self.user_group, asset=self.asset + ) + return accounts diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 52d3a4f31..8d80a481c 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -30,8 +30,10 @@ __all__ = [ 'ValidateUserAssetPermissionApi', 'GetUserAssetPermissionActionsApi', 'MyGrantedAssetSystemUsersApi', - 'UserGrantedAssetAccounts', - 'MyGrantedAssetAccounts', + 'UserGrantedAssetAccountsApi', + 'MyGrantedAssetAccountsApi', + 'UserGrantedAssetSpecialAccountsApi', + 'MyGrantedAssetSpecialAccountsApi', ] @@ -143,7 +145,7 @@ class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi): return self.request.user -class UserGrantedAssetAccounts(ListAPIView): +class UserGrantedAssetAccountsApi(ListAPIView): serializer_class = serializers.AccountsGrantedSerializer rbac_perms = { 'list': 'perms.view_userassets' @@ -162,22 +164,40 @@ class UserGrantedAssetAccounts(ListAPIView): return asset def get_queryset(self): - accounts = AssetPermission.get_user_perm_asset_accounts( - self.user, self.asset, with_actions=True - ) + accounts = AssetPermission.get_perm_asset_accounts(user=self.user, asset=self.asset) return accounts - # @INPUT @USER - # inner_accounts = [ - # Account.get_input_account(), Account.get_user_account(self.user.username) - # ] - # for inner_account in inner_accounts: - # inner_account.actions = Action.ALL - # accounts = accounts + inner_accounts - # 构造默认包含的账号,如: @INPUT @USER - # return accounts -class MyGrantedAssetAccounts(UserGrantedAssetAccounts): +class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi): + permission_classes = (IsValidUser,) + + @lazyproperty + def user(self): + return self.request.user + + +class UserGrantedAssetSpecialAccountsApi(ListAPIView): + serializer_class = serializers.AccountsGrantedSerializer + rbac_perms = { + 'list': 'perms.view_userassets' + } + + @lazyproperty + def user(self): + return self.request.user + + def get_queryset(self): + # 构造默认包含的账号,如: @INPUT @USER + accounts = [ + Account.get_input_account(), + Account.get_user_account(self.user.username) + ] + for account in accounts: + account.actions = Action.ALL + return accounts + + +class MyGrantedAssetSpecialAccountsApi(UserGrantedAssetSpecialAccountsApi): permission_classes = (IsValidUser,) @lazyproperty diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 76da872f8..660b8cc5b 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -225,10 +225,10 @@ class AssetPermission(OrgModelMixin): return accounts @classmethod - def get_user_perm_asset_accounts(cls, user, asset: Asset, with_actions=False): - perms = cls.filter(user, asset) - all_account_names = cls.retrieve_account_names(perms) - accounts = asset.filter_accounts(all_account_names) + def get_perm_asset_accounts(cls, user=None, user_group=None, asset=None, with_actions=True): + perms = cls.filter(user=user, user_group=user_group, asset=asset) + account_names = cls.retrieve_account_names(perms) + accounts = asset.filter_accounts(account_names) if with_actions: cls.set_accounts_actions(accounts, perms=perms) return accounts @@ -257,15 +257,20 @@ class AssetPermission(OrgModelMixin): return account_names @classmethod - def filter(cls, user=None, asset=None, account_names=None): - """ 获取同时包含 用户-资产-账号 的授权规则 """ + def filter(cls, user=None, user_group=None, asset=None, account_names=None): + """ 获取同时包含 用户(组)-资产-账号 的授权规则 """ perm_ids = [] if user: - user_assetperm_ids = cls.filter_by_user(user, flat=True) - perm_ids.append(user_assetperm_ids) + user_perm_ids = cls.filter_by_user(user, flat=True) + perm_ids.append(user_perm_ids) + + if user_group: + user_group_perm_ids = cls.filter_by_user_group(user_group, flat=True) + perm_ids.append(user_group_perm_ids) + if asset: - asset_assetperm_ids = cls.filter_by_asset(asset, flat=True) - perm_ids.append(asset_assetperm_ids) + asset_perm_ids = cls.filter_by_asset(asset, flat=True) + perm_ids.append(asset_perm_ids) # & 是同时满足,比如有用户,但是用户的规则是空,那么返回也应该是空 perm_ids = list(reduce(lambda x, y: set(x) & set(y), perm_ids)) perms = cls.objects.filter(id__in=perm_ids) @@ -291,6 +296,16 @@ class AssetPermission(OrgModelMixin): perms = cls.objects.filter(id__in=perm_ids).valid() return perms + @classmethod + def filter_by_user_group(cls, user_group, flat=False): + perm_ids = AssetPermission.user_groups.through.objects.filter( + usergroup_id=user_group + ).values_list('assetpermission_id', flat=True) + if flat: + return set(perm_ids) + perms = cls.objects.filter(id__in=perm_ids).valid() + return perms + @classmethod def filter_by_asset(cls, asset, with_node=True, flat=False): perm_ids = set() diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 5606ee639..5973dd0ae 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -65,10 +65,13 @@ user_permission_urlpatterns = [ path('/assets//system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'), path('assets//system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), - # Todo: v3 增加 + # Todo: v3 增加 Done. # 获取所有和资产-用户关联的账号列表 - path('/assets//accounts/', api.UserGrantedAssetAccounts.as_view(), name='user-asset-accounts'), - path('assets//accounts/', api.MyGrantedAssetAccounts.as_view(), name='my-asset-accounts') + path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), + path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), + # 用户登录资产的特殊账号, @INPUT, @USER 等 + path('/assets/special-accounts/', api.UserGrantedAssetSpecialAccountsApi.as_view(), name='user-special-accounts'), + path('/assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'), ] user_group_permission_urlpatterns = [ @@ -83,7 +86,7 @@ user_group_permission_urlpatterns = [ path('/assets//system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'), # Todo: v3 增加 # 获取所有和资产-用户组关联的账号列表 - path('/assets//accounts/', api.UserGrantedAssetAccounts.as_view(), name='user-asset-accounts'), + path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), name='user-group-asset-accounts'), ] permission_urlpatterns = [ From fd0ce0d1c695c593d0ff37ff383dd62d83623b26 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 29 Sep 2022 16:36:28 +0800 Subject: [PATCH 166/488] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E6=B3=A8=E9=87=8A=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 9 +++++++-- apps/perms/urls/asset_permission.py | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 660b8cc5b..67df0bb26 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -258,8 +258,9 @@ class AssetPermission(OrgModelMixin): @classmethod def filter(cls, user=None, user_group=None, asset=None, account_names=None): - """ 获取同时包含 用户(组)-资产-账号 的授权规则 """ + """ 获取同时包含 用户(组)-资产-账号 的授权规则, 条件之间都是 & 的关系""" perm_ids = [] + if user: user_perm_ids = cls.filter_by_user(user, flat=True) perm_ids.append(user_perm_ids) @@ -271,12 +272,16 @@ class AssetPermission(OrgModelMixin): if asset: asset_perm_ids = cls.filter_by_asset(asset, flat=True) perm_ids.append(asset_perm_ids) + # & 是同时满足,比如有用户,但是用户的规则是空,那么返回也应该是空 perm_ids = list(reduce(lambda x, y: set(x) & set(y), perm_ids)) perms = cls.objects.filter(id__in=perm_ids) + if account_names: perms = perms.filter_by_accounts(account_names) - return perms.valid().order_by('-date_expired') + + perms = perms.valid().order_by('-date_expired') + return perms @classmethod def filter_by_user(cls, user, with_group=True, flat=False): diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 5973dd0ae..e82dd8686 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -84,7 +84,7 @@ user_group_permission_urlpatterns = [ # Todo: v3 删除 path('/assets//system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'), - # Todo: v3 增加 + # Todo: v3 增加 Done. # 获取所有和资产-用户组关联的账号列表 path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), name='user-group-asset-accounts'), ] @@ -95,7 +95,8 @@ permission_urlpatterns = [ path('/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'), # 验证用户是否有某个资产和系统用户的权限 - # Todo: API 需要修改,验证用户有某个账号的权限 + # Todo: v3 API 需要修改,验证用户有某个账号的权限 # 先不动, v3 中可能会修改连接资产时的逻辑, + # 直接获取认证信息,获取不到就时没有权限,就不需要校验了 path('user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), path('user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), From 76747642c4bb8524d1abb25f79ddab2756cc1bd3 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 29 Sep 2022 17:38:27 +0800 Subject: [PATCH 167/488] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E6=A8=A1=E5=9D=97=E4=B8=AD=E5=85=B3=E4=BA=8E=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=E7=9A=84API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_group_permission.py | 14 -------- apps/perms/api/user_permission/common.py | 46 ------------------------ apps/perms/urls/asset_permission.py | 12 +------ apps/perms/utils/permission.py | 9 +---- 4 files changed, 2 insertions(+), 79 deletions(-) diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 0a88c4228..e6d470681 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -11,7 +11,6 @@ from perms.models import AssetPermission from assets.models import Asset, Node from . import user_permission as uapi from perms import serializers -from perms.utils.permission import get_asset_system_user_ids_with_actions_by_group from assets.api.mixin import SerializeToTreeNodeMixin from users.models import UserGroup @@ -19,18 +18,10 @@ __all__ = [ 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodeChildrenAsTreeApi', - 'UserGroupGrantedAssetSystemUsersApi', 'UserGroupGrantedAssetAccountsApi', ] -class UserGroupMixin: - @lazyproperty - def group(self): - group_id = self.kwargs.get('pk') - return UserGroup.objects.get(id=group_id) - - class UserGroupGrantedAssetsApi(ListAPIView): serializer_class = serializers.AssetGrantedSerializer only_fields = serializers.AssetGrantedSerializer.Meta.only_fields @@ -201,11 +192,6 @@ class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIVie return Response(data=nodes) -class UserGroupGrantedAssetSystemUsersApi(UserGroupMixin, uapi.UserGrantedAssetSystemUsersForAdminApi): - def get_asset_system_user_ids_with_actions(self, asset): - return get_asset_system_user_ids_with_actions_by_group(self.group, asset) - - class UserGroupGrantedAssetAccountsApi(uapi.UserGrantedAssetAccountsApi): @lazyproperty diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 8d80a481c..3c77a1be5 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -26,10 +26,8 @@ from perms.models import AssetPermission, Action logger = get_logger(__name__) __all__ = [ - 'UserGrantedAssetSystemUsersForAdminApi', 'ValidateUserAssetPermissionApi', 'GetUserAssetPermissionActionsApi', - 'MyGrantedAssetSystemUsersApi', 'UserGrantedAssetAccountsApi', 'MyGrantedAssetAccountsApi', 'UserGrantedAssetSpecialAccountsApi', @@ -101,50 +99,6 @@ class ValidateUserAssetPermissionApi(APIView): return Response(data, status=status_code) -class UserGrantedAssetSystemUsersForAdminApi(ListAPIView): - rbac_perms = { - 'list': 'perms.view_userassets' - } - - @lazyproperty - def user(self): - user_id = self.kwargs.get('pk') - return User.objects.get(id=user_id) - - @lazyproperty - def system_users_with_actions(self): - asset_id = self.kwargs.get('asset_id') - asset = get_object_or_404(Asset, id=asset_id, is_active=True) - return self.get_asset_system_user_ids_with_actions(asset) - - def get_asset_system_user_ids_with_actions(self, asset): - return get_asset_system_user_ids_with_actions_by_user(self.user, asset) - - def paginate_queryset(self, queryset): - page = super().paginate_queryset(queryset) - - if page: - page = self.set_systemusers_action(page) - else: - self.set_systemusers_action(queryset) - return page - - def set_systemusers_action(self, queryset): - queryset_list = list(queryset) - for system_user in queryset_list: - actions = self.system_users_with_actions.get(system_user.id, 0) - system_user.actions = actions - return queryset_list - - -class MyGrantedAssetSystemUsersApi(UserGrantedAssetSystemUsersForAdminApi): - permission_classes = (IsValidUser,) - - @lazyproperty - def user(self): - return self.request.user - - class UserGrantedAssetAccountsApi(ListAPIView): serializer_class = serializers.AccountsGrantedSerializer rbac_perms = { diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index e82dd8686..0ef87f606 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -60,12 +60,6 @@ user_permission_urlpatterns = [ path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), # v3 中上面的 API 基本不用动 - # Todo: v3 删除 - # Asset System users - path('/assets//system-users/', api.UserGrantedAssetSystemUsersForAdminApi.as_view(), name='user-asset-system-users'), - path('assets//system-users/', api.MyGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), - - # Todo: v3 增加 Done. # 获取所有和资产-用户关联的账号列表 path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), @@ -82,9 +76,6 @@ user_group_permission_urlpatterns = [ path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'), path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), - # Todo: v3 删除 - path('/assets//system-users/', api.UserGroupGrantedAssetSystemUsersApi.as_view(), name='user-group-asset-system-users'), - # Todo: v3 增加 Done. # 获取所有和资产-用户组关联的账号列表 path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), name='user-group-asset-accounts'), ] @@ -95,8 +86,7 @@ permission_urlpatterns = [ path('/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'), # 验证用户是否有某个资产和系统用户的权限 - # Todo: v3 API 需要修改,验证用户有某个账号的权限 # 先不动, v3 中可能会修改连接资产时的逻辑, - # 直接获取认证信息,获取不到就时没有权限,就不需要校验了 + # Todo: v3 先不动, 可能会修改连接资产时的逻辑, 直接获取认证信息,获取不到就时没有权限,就不需要校验了 path('user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), path('user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index b9037c040..71bc2f07e 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -85,14 +85,7 @@ def get_asset_system_user_ids_with_actions_by_user(user: User, asset: Asset): def has_asset_system_permission(user: User, asset: Asset, account: str): systemuser_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset) - actions = systemuser_actions_mapper.get(system_user.id, 0) + actions = systemuser_actions_mapper.get(account, 0) if actions: return True return False - - -def get_asset_system_user_ids_with_actions_by_group(group: UserGroup, asset: Asset): - asset_perm_ids = AssetPermission.objects.filter( - user_groups=group - ).valid().values_list('id', flat=True).distinct() - return get_asset_system_user_ids_with_actions(asset_perm_ids, asset) From b2991362f1189ae0621ea68b55c83b3b65739710 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Thu, 29 Sep 2022 18:01:23 +0800 Subject: [PATCH 168/488] =?UTF-8?q?perf:=20=E6=9F=A5=E7=9C=8B=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/serializers/login_asset_acl.py | 2 +- apps/assets/serializers/asset/common.py | 1 - apps/perms/api/user_permission/assets/mixin.py | 7 ++++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index 884b75c52..7282bf1a9 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -57,7 +57,7 @@ class LoginAssetACLAccountsSerializer(serializers.Serializer): class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): users = LoginAssetACLUsersSerializer() assets = LoginAssetACLAssestsSerializer() - account = LoginAssetACLAccountsSerializer() + accounts = LoginAssetACLAccountsSerializer() reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count') action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 56dea63d5..4aef50f58 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -108,7 +108,6 @@ class AssetSerializer(JMSWritableNestedModelSerializer): instance.nodes.set(nodes_to_set) def validate_nodes(self, nodes): - print("Nodes: ", nodes) if nodes: return nodes request = self.context.get('request') diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index ad50c5b19..d82e39faa 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -32,8 +32,8 @@ class UserDirectGrantedAssetsQuerysetMixin: class UserAllGrantedAssetsQuerysetMixin: only_fields = serializers.AssetGrantedSerializer.Meta.only_fields pagination_class = AllGrantedAssetPagination - ordering_fields = ("hostname", "address", "port", "cpu_cores") - ordering = ('hostname', ) + ordering_fields = ("name", "address", "port", "cpu_cores") + ordering = ('name', ) user: User @@ -41,7 +41,8 @@ class UserAllGrantedAssetsQuerysetMixin: if getattr(self, 'swagger_fake_view', False): return Asset.objects.none() queryset = UserGrantedAssetsQueryUtils(self.user).get_all_granted_assets() - queryset = queryset.prefetch_related('platform').only(*self.only_fields) + only_fields = [i for i in self.only_fields if i not in ['protocols']] + queryset = queryset.prefetch_related('platform', 'protocols').only(*only_fields) return queryset From 14f48c459c68f5609cf618128fd01bd754e5fa87 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 29 Sep 2022 20:41:40 +0800 Subject: [PATCH 169/488] =?UTF-8?q?feat:=20=E6=8E=88=E6=9D=83=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/permission.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index 18fa96563..fd393f990 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -87,6 +87,18 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): 'is_valid': {'label': _('Is valid')}, } + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_actions_field() + + def set_actions_field(self): + actions = self.fields.get('actions') + if not actions: + return + choices = actions._choices + actions._choices = choices + actions.default = list(choices.keys()) + @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ @@ -110,7 +122,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): instance.user_groups.add(*user_groups_to_set) # 资产 assets_to_set = Asset.objects.filter( - Q(ip__in=kwargs.get('assets_display')) | + Q(address__in=kwargs.get('assets_display')) | Q(name__in=kwargs.get('assets_display')) ).distinct() instance.assets.add(*assets_to_set) From 881c0a604276363f74871de8cd90b62767ff3ece Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 30 Sep 2022 16:08:28 +0800 Subject: [PATCH 170/488] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=94=A8=E6=88=B7=E6=8E=88=E6=9D=83=E7=9A=84=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E8=B4=A6=E5=8F=B7=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 4 ++-- apps/perms/utils/user_permission.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 67df0bb26..f6203841d 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -78,7 +78,7 @@ class AssetPermissionQuerySet(models.QuerySet): return self.filter(q) def filter_by_accounts(self, accounts): - q = Q(accounts__contains=accounts) | \ + q = Q(accounts__contains=list(accounts)) | \ Q(accounts__contains=AssetPermission.SpecialAccount.ALL.value) return self.filter(q) @@ -238,7 +238,7 @@ class AssetPermission(OrgModelMixin): # set account actions account_names = accounts.values_list('username', flat=True) perms = perms.filter_by_accounts(account_names) - account_names_actions_map = defaultdict(set) + account_names_actions_map = defaultdict(int) account_names_actions = perms.values_list('accounts', 'actions') for account_names, actions in account_names_actions: for account_name in account_names: diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index 4546e81a0..81a2b9a10 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -647,7 +647,7 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase): def get_whole_tree_nodes(self, with_special=True): """ 这里的 granted nodes, 是整棵树需要的node,推算出来的也算 - :param user: + :param with_special: :return: """ nodes = PermNode.objects.filter( From 237e7b22fb9744c6a16ae899d8333fd8de0f5a85 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 30 Sep 2022 16:28:44 +0800 Subject: [PATCH 171/488] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7actions=E8=AE=BE=E7=BD=AE=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index f6203841d..9d444268f 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -235,17 +235,21 @@ class AssetPermission(OrgModelMixin): @classmethod def set_accounts_actions(cls, accounts, perms): - # set account actions + account_names_actions_map = cls.get_account_names_actions_map(accounts, perms) + for account in accounts: + account.actions = account_names_actions_map.get(account.username) + return accounts + + @classmethod + def get_account_names_actions_map(cls, accounts, perms): + account_names_actions_map = defaultdict(int) account_names = accounts.values_list('username', flat=True) perms = perms.filter_by_accounts(account_names) - account_names_actions_map = defaultdict(int) account_names_actions = perms.values_list('accounts', 'actions') for account_names, actions in account_names_actions: for account_name in account_names: account_names_actions_map[account_name] |= actions - for account in accounts: - account.actions = account_names_actions_map.get(account.username) - return accounts + return account_names_actions_map @classmethod def retrieve_account_names(cls, perms): From cd98ec4cacf6e6e3bfdda67834e94c679088573b Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Sun, 9 Oct 2022 17:43:13 +0800 Subject: [PATCH 172/488] perf: account history record only secret --- apps/assets/api/account/history.py | 2 +- .../assets/migrations/0107_account_history.py | 73 +++++++++++++++++++ apps/assets/models/account.py | 21 +++++- 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 apps/assets/migrations/0107_account_history.py diff --git a/apps/assets/api/account/history.py b/apps/assets/api/account/history.py index 0ddc659ad..cfcbfeb8e 100644 --- a/apps/assets/api/account/history.py +++ b/apps/assets/api/account/history.py @@ -10,7 +10,7 @@ __all__ = ['AccountHistoryViewSet', 'AccountHistorySecretsViewSet'] class AccountHistoryFilterSet(AccountFilterSet): class Meta: model = Account.history.model - fields = AccountFilterSet.Meta.fields + fields = ['id', 'secret_type'] class AccountHistoryViewSet(AccountViewSet): diff --git a/apps/assets/migrations/0107_account_history.py b/apps/assets/migrations/0107_account_history.py new file mode 100644 index 000000000..66ec1e36b --- /dev/null +++ b/apps/assets/migrations/0107_account_history.py @@ -0,0 +1,73 @@ +# Generated by Django 3.2.13 on 2022-10-09 08:50 + +from django.db import migrations + + +def migrate_create_history_account(apps, schema_editor): + db_alias = schema_editor.connection.alias + account_model = apps.get_model('assets', 'Account') + history_account_model = apps.get_model('assets', 'HistoricalAccount') + history_accounts = [] + for account in account_model.objects.using(db_alias).all(): + data = { + 'id': account.id, + 'secret': account.secret, + 'secret_type': account.secret_type, + 'history_date': account.date_created, + } + history_accounts.append(history_account_model(**data)) + history_account_model.objects.using(db_alias).bulk_create(history_accounts) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0106_auto_20220916_1556'), + ] + + operations = [ + migrations.RemoveField( + model_name='historicalaccount', + name='asset', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='comment', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='created_by', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='date_created', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='date_updated', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='name', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='org_id', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='privileged', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='su_from', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='username', + ), + migrations.RemoveField( + model_name='historicalaccount', + name='version', + ), + migrations.RunPython(migrate_create_history_account), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 4394f6583..faf503b91 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -9,6 +9,23 @@ from .base import BaseAccount __all__ = ['Account', 'AccountTemplate'] +class AccountHistoricalRecords(HistoricalRecords): + def __init__(self, *args, **kwargs): + self.included_fields = kwargs.pop('included_fields', None) + super().__init__(*args, **kwargs) + + def fields_included(self, model): + fields = [] + for field in model._meta.fields: + if self.included_fields is None: + if field.name not in self.excluded_fields: + fields.append(field) + else: + if field.name in self.included_fields: + fields.append(field) + return fields + + class Account(BaseAccount): class InnerAccount(models.TextChoices): INPUT = '@INPUT', '@INPUT' @@ -23,7 +40,9 @@ class Account(BaseAccount): on_delete=models.SET_NULL, verbose_name=_("Su from") ) version = models.IntegerField(default=0, verbose_name=_('Version')) - history = HistoricalRecords() + history = AccountHistoricalRecords( + included_fields=['id', 'secret_type', 'secret'] + ) class Meta: verbose_name = _('Account') From e330776ab13b94b9fc85a4fc60377a0f9ee797dd Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 10 Oct 2022 15:17:51 +0800 Subject: [PATCH 173/488] fix: ticket nothing comment bug --- apps/tickets/serializers/ticket/apply_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 2dcbb6eb6..a2cb94179 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -23,7 +23,7 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria model = ApplyAssetTicket writeable_fields = [ 'id', 'title', 'type', 'apply_nodes', 'apply_assets', - 'apply_accounts', 'apply_actions', 'org_id', + 'apply_accounts', 'apply_actions', 'org_id', 'comment', 'apply_date_start', 'apply_date_expired' ] fields = TicketApplySerializer.Meta.fields + writeable_fields + [ From 145814f1e8b39d4933ede8f1ed8235ac3544c2eb Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 10 Oct 2022 16:31:45 +0800 Subject: [PATCH 174/488] =?UTF-8?q?perf:=20=E7=BB=9F=E4=B8=80=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E7=9A=84crontab?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F,=20=E5=AF=B9=E4=BA=8E=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E5=AE=9A=E6=97=B6=E6=89=A7=E8=A1=8C=E7=9A=84=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=9B=B4=E5=8A=A0=E6=B8=85=E6=99=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks/nodes_amount.py | 4 +++- apps/common/const/crontab.py | 5 +++++ apps/perms/tasks.py | 3 ++- apps/users/tasks.py | 5 +++-- 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 apps/common/const/crontab.py diff --git a/apps/assets/tasks/nodes_amount.py b/apps/assets/tasks/nodes_amount.py index 2f8f0592d..c6ad2e8ba 100644 --- a/apps/assets/tasks/nodes_amount.py +++ b/apps/assets/tasks/nodes_amount.py @@ -8,6 +8,8 @@ from assets.utils import check_node_assets_amount from common.utils.lock import AcquireFailed from common.utils import get_logger +from common.const.crontab import CRONTAB_AT_AM_TWO + logger = get_logger(__file__) @@ -29,7 +31,7 @@ def check_node_assets_amount_task(org_id=None): logger.error(error) -@register_as_period_task(crontab='0 2 * * *') +@register_as_period_task(crontab=CRONTAB_AT_AM_TWO) @shared_task def check_node_assets_amount_period_task(): check_node_assets_amount_task() diff --git a/apps/common/const/crontab.py b/apps/common/const/crontab.py new file mode 100644 index 000000000..bd9809176 --- /dev/null +++ b/apps/common/const/crontab.py @@ -0,0 +1,5 @@ + +CRONTAB_AT_AM_TWO = '0 14 * * *' +CRONTAB_AT_AM_TEN = '0 10 * * *' +CRONTAB_AT_PM_TWO = '0 2 * * *' + diff --git a/apps/perms/tasks.py b/apps/perms/tasks.py index 30c9600d3..564e5657e 100644 --- a/apps/perms/tasks.py +++ b/apps/perms/tasks.py @@ -10,6 +10,7 @@ from celery import shared_task from orgs.utils import tmp_to_root_org from common.utils import get_logger from common.utils.timezone import local_now, dt_formatter, dt_parser +from common.const.crontab import CRONTAB_AT_AM_TEN from ops.celery.decorator import register_as_period_task from perms.notifications import ( PermedAssetsWillExpireUserMsg, AssetPermsWillExpireForOrgAdminMsg, @@ -54,7 +55,7 @@ def check_asset_permission_expired(): UserGrantedTreeRefreshController.add_need_refresh_by_asset_perm_ids_cross_orgs(asset_perm_ids) -@register_as_period_task(crontab='0 10 * * *') +@register_as_period_task(crontab=CRONTAB_AT_AM_TEN) @shared_task() @atomic() @tmp_to_root_org() diff --git a/apps/users/tasks.py b/apps/users/tasks.py index ea6426aa4..cddf0d2ec 100644 --- a/apps/users/tasks.py +++ b/apps/users/tasks.py @@ -15,6 +15,7 @@ from orgs.models import Organization from .models import User from users.notifications import UserExpirationReminderMsg from settings.utils import LDAPServerUtil, LDAPImportUtil +from common.const.crontab import CRONTAB_AT_AM_TEN, CRONTAB_AT_PM_TWO logger = get_logger(__file__) @@ -41,7 +42,7 @@ def check_password_expired_periodic(): 'check_password_expired_periodic': { 'task': check_password_expired.name, 'interval': None, - 'crontab': '0 10 * * *', + 'crontab': CRONTAB_AT_AM_TEN, 'enabled': True, } } @@ -72,7 +73,7 @@ def check_user_expired_periodic(): 'check_user_expired_periodic': { 'task': check_user_expired.name, 'interval': None, - 'crontab': '0 14 * * *', + 'crontab': CRONTAB_AT_PM_TWO, 'enabled': True, } } From 41589c5305d346203ffe7636cbfb6d19a6bf2e3c Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 29 Sep 2022 20:44:45 +0800 Subject: [PATCH 175/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20ansible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/account.py | 6 +- apps/assets/api/platform.py | 6 +- apps/assets/models/automation/__init__.py | 4 + .../models/automation/account_discovery.py} | 6 +- .../models/automation/account_reconcile.py} | 6 +- .../models/automation/account_verify.py} | 4 +- .../models/automation/base.py} | 6 +- .../models/automation/change_secret.py} | 35 +--- apps/assets/models/base.py | 22 +-- apps/assets/models/domain.py | 3 + apps/assets/tasks/account_connectivity.py | 5 +- apps/assets/tasks/asset_connectivity.py | 21 ++- .../tasks/gather_asset_hardware_info.py | 27 ++-- apps/assets/tasks/gather_asset_users.py | 3 +- apps/ops/ansible/callback.py | 9 ++ apps/ops/ansible/inventory.py | 150 +++++++++++++++++- apps/ops/ansible/new_callback.py | 65 ++++++++ apps/ops/ansible/new_runner.py | 41 ++++- .../ops/migrations/0023_auto_20220929_2025.py | 44 +++++ .../migrations/0023_automation_strategy.py | 123 -------------- apps/ops/models/__init__.py | 1 - apps/ops/models/automation/__init__.py | 5 - apps/ops/models/automation/base.py | 2 - apps/ops/models/celery.py | 24 +-- apps/ops/models/playbook.py | 16 ++ apps/ops/signal_handlers.py | 55 ++++++- apps/ops/tasks.py | 35 +--- apps/orgs/caches.py | 3 +- apps/orgs/tasks.py | 6 +- 29 files changed, 450 insertions(+), 283 deletions(-) create mode 100644 apps/assets/models/automation/__init__.py rename apps/{ops/models/automation/collect.py => assets/models/automation/account_discovery.py} (69%) rename apps/{ops/models/automation/push.py => assets/models/automation/account_reconcile.py} (70%) rename apps/{ops/models/automation/verify.py => assets/models/automation/account_verify.py} (80%) rename apps/{ops/models/automation/common.py => assets/models/automation/base.py} (93%) rename apps/{ops/models/automation/change_auth.py => assets/models/automation/change_secret.py} (57%) create mode 100644 apps/ops/ansible/new_callback.py create mode 100644 apps/ops/migrations/0023_auto_20220929_2025.py delete mode 100644 apps/ops/migrations/0023_automation_strategy.py delete mode 100644 apps/ops/models/automation/__init__.py delete mode 100644 apps/ops/models/automation/base.py create mode 100644 apps/ops/models/playbook.py diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 4b26c042d..bdf1de116 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -33,7 +33,7 @@ class AccountViewSet(OrgBulkModelViewSet): @action(methods=['post'], detail=True, url_path='verify') def verify_account(self, request, *args, **kwargs): account = super().get_object() - task = test_accounts_connectivity_manual.delay([account]) + task = test_accounts_connectivity_manual.delay([account.id]) return Response(data={'task': task.id}) @@ -67,8 +67,8 @@ class AccountTaskCreateAPI(CreateAPIView): return queryset def perform_create(self, serializer): - accounts = self.get_accounts() - task = test_accounts_connectivity_manual.delay(accounts) + account_ids = self.get_accounts().values_list('id', flat=True) + task = test_accounts_connectivity_manual.delay(account_ids) data = getattr(serializer, '_data', {}) data["task"] = task.id setattr(serializer, '_data', data) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 2c06d504a..31726fc02 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -1,12 +1,8 @@ -from rest_framework.decorators import action -from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.drf.serializers import GroupedChoiceSerializer from assets.models import Platform -from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer -from assets.const import AllTypes -from assets.playbooks import filter_platform_methods +from assets.serializers import PlatformSerializer __all__ = ['AssetPlatformViewSet'] diff --git a/apps/assets/models/automation/__init__.py b/apps/assets/models/automation/__init__.py new file mode 100644 index 000000000..4e46ff150 --- /dev/null +++ b/apps/assets/models/automation/__init__.py @@ -0,0 +1,4 @@ +from .change_secret import * +from .account_discovery import * +from .account_reconcile import * +from .account_verify import * diff --git a/apps/ops/models/automation/collect.py b/apps/assets/models/automation/account_discovery.py similarity index 69% rename from apps/ops/models/automation/collect.py rename to apps/assets/models/automation/account_discovery.py index 9710e5c52..bfe9d0d80 100644 --- a/apps/ops/models/automation/collect.py +++ b/apps/assets/models/automation/account_discovery.py @@ -1,12 +1,12 @@ from django.utils.translation import ugettext_lazy as _ from ops.const import StrategyChoice -from .common import AutomationStrategy +from .base import BaseAutomation -class CollectStrategy(AutomationStrategy): +class DiscoveryAutomation(BaseAutomation): class Meta: - verbose_name = _("Collect strategy") + verbose_name = _("Discovery strategy") def to_attr_json(self): attr_json = super().to_attr_json() diff --git a/apps/ops/models/automation/push.py b/apps/assets/models/automation/account_reconcile.py similarity index 70% rename from apps/ops/models/automation/push.py rename to apps/assets/models/automation/account_reconcile.py index f7a1bd4be..f69d1c82d 100644 --- a/apps/ops/models/automation/push.py +++ b/apps/assets/models/automation/account_reconcile.py @@ -1,12 +1,12 @@ from django.utils.translation import ugettext_lazy as _ from ops.const import StrategyChoice -from .common import AutomationStrategy +from .base import BaseAutomation -class PushStrategy(AutomationStrategy): +class ReconcileAutomation(BaseAutomation): class Meta: - verbose_name = _("Push strategy") + verbose_name = _("Reconcile strategy") def to_attr_json(self): attr_json = super().to_attr_json() diff --git a/apps/ops/models/automation/verify.py b/apps/assets/models/automation/account_verify.py similarity index 80% rename from apps/ops/models/automation/verify.py rename to apps/assets/models/automation/account_verify.py index 0726704f9..d05cb4a0d 100644 --- a/apps/ops/models/automation/verify.py +++ b/apps/assets/models/automation/account_verify.py @@ -1,10 +1,10 @@ from django.utils.translation import ugettext_lazy as _ from ops.const import StrategyChoice -from .common import AutomationStrategy +from .base import BaseAutomation -class VerifyStrategy(AutomationStrategy): +class VerifyAutomation(BaseAutomation): class Meta: verbose_name = _("Verify strategy") diff --git a/apps/ops/models/automation/common.py b/apps/assets/models/automation/base.py similarity index 93% rename from apps/ops/models/automation/common.py rename to apps/assets/models/automation/base.py index a586c85a9..27c971e0d 100644 --- a/apps/ops/models/automation/common.py +++ b/apps/assets/models/automation/base.py @@ -12,8 +12,7 @@ from ops.tasks import execute_automation_strategy from ops.task_handlers import ExecutionManager -class AutomationStrategy(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) +class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) nodes = models.ManyToManyField( 'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes") @@ -21,6 +20,7 @@ class AutomationStrategy(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): assets = models.ManyToManyField( 'assets.Asset', related_name='automation_strategy', blank=True, verbose_name=_("Assets") ) + type = models.CharField(max_length=16, verbose_name=_('Type')) comment = models.TextField(blank=True, verbose_name=_('Comment')) def __str__(self): @@ -67,7 +67,7 @@ class AutomationStrategyExecution(OrgModelMixin): default=dict, blank=True, null=True, verbose_name=_('Automation snapshot') ) strategy = models.ForeignKey( - 'AutomationStrategy', related_name='execution', on_delete=models.CASCADE, + 'assets.models.automation.base.BaseAutomation', related_name='execution', on_delete=models.CASCADE, verbose_name=_('Automation strategy') ) trigger = models.CharField( diff --git a/apps/ops/models/automation/change_auth.py b/apps/assets/models/automation/change_secret.py similarity index 57% rename from apps/ops/models/automation/change_auth.py rename to apps/assets/models/automation/change_secret.py index 71adb010f..d176f5c6a 100644 --- a/apps/ops/models/automation/change_auth.py +++ b/apps/assets/models/automation/change_secret.py @@ -1,38 +1,19 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.db import fields from ops.const import SSHKeyStrategy, PasswordStrategy, StrategyChoice from ops.utils import generate_random_password -from common.db.fields import ( - EncryptCharField, EncryptTextField, JsonDictCharField -) -from .common import AutomationStrategy +from .base import BaseAutomation -class ChangeAuthStrategy(AutomationStrategy): - is_password = models.BooleanField(default=True) - password_strategy = models.CharField( - max_length=128, blank=True, null=True, choices=PasswordStrategy.choices, - verbose_name=_('Password strategy') - ) - password_rules = JsonDictCharField( - max_length=2048, blank=True, null=True, verbose_name=_('Password rules') - ) - password = EncryptCharField( - max_length=256, blank=True, null=True, verbose_name=_('Password') - ) +class ChangePasswordAutomation(BaseAutomation): + class PasswordStrategy(models.TextChoices): + custom = 'specific', _('Specific') + random_one = 'random_one', _('All assets use the same random password') + random_all = 'random_all', _('All assets use different random password') - is_ssh_key = models.BooleanField(default=False) - ssh_key_strategy = models.CharField( - max_length=128, blank=True, null=True, choices=SSHKeyStrategy.choices, - verbose_name=_('SSH Key strategy') - ) - private_key = EncryptTextField( - max_length=4096, blank=True, null=True, verbose_name=_('SSH private key') - ) - public_key = EncryptTextField( - max_length=4096, blank=True, null=True, verbose_name=_('SSH public key') - ) + password = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) recipients = models.ManyToManyField( 'users.User', related_name='recipients_change_auth_strategy', blank=True, verbose_name=_("Recipient") diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 7f96a07e9..7b5b1d6f8 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -76,6 +76,15 @@ class BaseAccount(OrgModelMixin): def has_secret(self): return bool(self.secret) + @property + def password(self): + return self.secret + + @password.setter + def password(self, value): + self.secret = value + self.secret_type = 'password' + @property def private_key(self): if self.secret_type == self.SecretType.ssh_key: @@ -91,15 +100,6 @@ class BaseAccount(OrgModelMixin): self.secret = value self.secret_type = 'private_key' - @property - def password(self): - return self.secret - - @password.setter - def password(self, value): - self.secret = value - self.secret_type = 'password' - @property def ssh_key_fingerprint(self): if self.public_key: @@ -125,8 +125,8 @@ class BaseAccount(OrgModelMixin): return None @property - def private_key_file(self): - if not self.private_key_obj: + def private_key_path(self): + if not self.secret_type != 'ssh_key' or not self.secret: return None project_dir = settings.PROJECT_DIR tmp_dir = os.path.join(project_dir, 'tmp') diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 219595d2c..4abe8aa68 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -40,6 +40,9 @@ class Domain(OrgModelMixin): def gateways(self): return self.gateway_set.filter(is_active=True) + def select_gateway(self): + return self.random_gateway() + def random_gateway(self): gateways = [gw for gw in self.gateways if gw.is_connective] if gateways: diff --git a/apps/assets/tasks/account_connectivity.py b/apps/assets/tasks/account_connectivity.py index c28f93110..694a7fe3a 100644 --- a/apps/assets/tasks/account_connectivity.py +++ b/apps/assets/tasks/account_connectivity.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _, gettext_noop from common.utils import get_logger from orgs.utils import org_aware_func -from ..models import Connectivity +from ..models import Connectivity, Account from . import const from .utils import check_asset_can_run_ansible @@ -99,10 +99,11 @@ def test_account_connectivity_util(account, task_name): @shared_task(queue="ansible") -def test_accounts_connectivity_manual(accounts): +def test_accounts_connectivity_manual(account_ids): """ :param accounts: 对象 """ + accounts = Account.objects.filter(id__in=account_ids) for account in accounts: task_name = gettext_noop("Test account connectivity: ") + str(account) test_account_connectivity_util(account, task_name) diff --git a/apps/assets/tasks/asset_connectivity.py b/apps/assets/tasks/asset_connectivity.py index 8c76d8e2b..ce379832a 100644 --- a/apps/assets/tasks/asset_connectivity.py +++ b/apps/assets/tasks/asset_connectivity.py @@ -5,8 +5,8 @@ from celery import shared_task from django.utils.translation import gettext_noop from common.utils import get_logger -from orgs.utils import org_aware_func -from ..models import Asset, Connectivity, Account +from orgs.utils import org_aware_func, tmp_to_root_org +from ..models import Asset, Connectivity, Account, Node from . import const from .utils import clean_ansible_task_hosts, group_asset_by_platform @@ -41,8 +41,7 @@ def set_assets_accounts_connectivity(assets, results_summary): Account.bulk_set_connectivity(accounts_failed, Connectivity.failed) -@shared_task(queue="ansible") -@org_aware_func("assets") +@org_aware_func('assets') def test_asset_connectivity_util(assets, task_name=None): from ops.utils import update_or_create_ansible_task @@ -88,7 +87,10 @@ def test_asset_connectivity_util(assets, task_name=None): @shared_task(queue="ansible") -def test_asset_connectivity_manual(asset): +def test_asset_connectivity_manual(asset_id): + asset = Asset.objects.filter(id=asset_id).first() + if not asset: + return task_name = gettext_noop("Test assets connectivity: ") + str(asset) summary = test_asset_connectivity_util([asset], task_name=task_name) @@ -99,7 +101,9 @@ def test_asset_connectivity_manual(asset): @shared_task(queue="ansible") -def test_assets_connectivity_manual(assets): +def test_assets_connectivity_manual(asset_ids): + with tmp_to_root_org(): + assets = Asset.objects.filter(id__in=asset_ids) task_name = gettext_noop("Test assets connectivity: ") + str([asset.name for asset in assets]) summary = test_asset_connectivity_util(assets, task_name=task_name) @@ -110,7 +114,10 @@ def test_assets_connectivity_manual(assets): @shared_task(queue="ansible") -def test_node_assets_connectivity_manual(node): +def test_node_assets_connectivity_manual(node_id): + with tmp_to_root_org(): + node = Node.objects.get(id=node_id) + task_name = gettext_noop("Test if the assets under the node are connectable: ") + node.name assets = node.get_all_assets() result = test_asset_connectivity_util(assets, task_name=task_name) diff --git a/apps/assets/tasks/gather_asset_hardware_info.py b/apps/assets/tasks/gather_asset_hardware_info.py index 7678f1115..1973dd4ff 100644 --- a/apps/assets/tasks/gather_asset_hardware_info.py +++ b/apps/assets/tasks/gather_asset_hardware_info.py @@ -9,8 +9,9 @@ from django.utils.translation import ugettext as _, gettext_noop from common.utils import ( capacity_convert, sum_capacity, get_logger ) -from orgs.utils import org_aware_func +from orgs.utils import org_aware_func, tmp_to_root_org from . import const +from ..models import Asset, Node from .utils import clean_ansible_task_hosts @@ -27,7 +28,6 @@ def set_assets_hardware_info(assets, result, **kwargs): """ Using ops task run result, to update asset info - @shared_task must be exit, because we using it as a task callback, is must be a celery task also :param assets: :param result: @@ -83,15 +83,15 @@ def set_assets_hardware_info(assets, result, **kwargs): return assets_updated -@shared_task -@org_aware_func("assets") +@org_aware_func('assets') def update_assets_hardware_info_util(assets, task_name=None): """ Using ansible api to update asset hardware info - :param assets: asset seq + :param asset_ids: asset seq :param task_name: task_name running :return: result summary ['contacted': {}, 'dark': {}] """ + from ops.utils import update_or_create_ansible_task if task_name is None: task_name = gettext_noop("Update some assets hardware info. ") @@ -110,15 +110,19 @@ def update_assets_hardware_info_util(assets, task_name=None): @shared_task(queue="ansible") -def update_asset_hardware_info_manual(asset): +def update_asset_hardware_info_manual(asset_id): + with tmp_to_root_org(): + asset = Asset.objects.filter(id=asset_id).first() + if not asset: + return task_name = gettext_noop("Update asset hardware info: ") + str(asset.name) update_assets_hardware_info_util([asset], task_name=task_name) @shared_task(queue="ansible") -def update_assets_hardware_info_manual(assets): +def update_assets_hardware_info_manual(asset_ids): task_name = gettext_noop("Update assets hardware info: ") + str([asset.name for asset in assets]) - update_assets_hardware_info_util(assets, task_name=task_name) + update_assets_hardware_info_util(asset_ids, task_name=task_name) @shared_task(queue="ansible") @@ -133,7 +137,12 @@ def update_assets_hardware_info_period(): @shared_task(queue="ansible") -def update_node_assets_hardware_info_manual(node): +def update_node_assets_hardware_info_manual(node_id): + with tmp_to_root_org(): + node = Node.objects.filter(id=node_id).first() + if not node: + return + task_name = gettext_noop("Update node asset hardware information: ") + str(node.name) assets = node.get_all_assets() result = update_assets_hardware_info_util(assets, task_name=task_name) diff --git a/apps/assets/tasks/gather_asset_users.py b/apps/assets/tasks/gather_asset_users.py index acacbb33d..0ce6c7453 100644 --- a/apps/assets/tasks/gather_asset_users.py +++ b/apps/assets/tasks/gather_asset_users.py @@ -7,7 +7,7 @@ from celery import shared_task from django.utils.translation import gettext_noop from django.utils import timezone -from orgs.utils import tmp_to_org, org_aware_func +from orgs.utils import tmp_to_org, org_aware_func, tmp_to_root_org from common.utils import get_logger from ..models import GatheredUser, Node from .utils import clean_ansible_task_hosts @@ -103,7 +103,6 @@ def add_asset_users(assets, results): ) -@shared_task(queue="ansible") @org_aware_func("assets") def gather_asset_users(assets, task_name=None): from ops.utils import update_or_create_ansible_task diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 3fe1933ac..cc879cc4c 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -72,6 +72,15 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule): Task result Callback """ context = None + events = [ + 'runner_on_failed', 'runner_on_ok', + 'runner_on_skipped', 'runner_on_unreachable', + ] + + def event_handler(self, data): + event = data.get('event', None) + print("Event: ", event) + print("Event Data: ", json.dumps(data)) def clean_result(self, t, host, task_name, task_result): contacted = self.results_summary["contacted"] diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index e024bc45b..dabda5e4e 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -1,4 +1,7 @@ # ~*~ coding: utf-8 ~*~ +from collections import defaultdict +import json + from ansible.inventory.host import Host from ansible.vars.manager import VariableManager from ansible.inventory.manager import InventoryManager @@ -21,7 +24,7 @@ class BaseHost(Host): # behind is not must be required "username": "", "password": "", - "private_key": "", + "private_key_path": "", "become": { "method": "", "user": "", @@ -49,8 +52,8 @@ class BaseHost(Host): # 添加密码和密钥 if host_data.get('password'): self.set_variable('ansible_ssh_pass', host_data['password']) - if host_data.get('private_key'): - self.set_variable('ansible_ssh_private_key_file', host_data['private_key']) + if host_data.get('private_key_path'): + self.set_variable('ansible_ssh_private_key_file', host_data['private_key_path']) # 添加become支持 become = host_data.get("become", False) @@ -155,13 +158,144 @@ class BaseInventory(InventoryManager): class JMSInventory: - def __init__(self, assets, account=None, ansible_connection='ssh', - account_policy='smart', host_var_callback=None): + def __init__(self, assets, account_username=None, account_policy='smart', host_var_callback=None): """ :param assets: - :param account: account username name if not set use account_policy - :param ansible_connection: ssh, local, + :param account_username: account username name if not set use account_policy :param account_policy: :param host_var_callback: """ - pass + self.assets = self.clean_assets(assets) + self.account_username = account_username + self.account_policy = account_policy + self.host_var_callback = host_var_callback + + @staticmethod + def clean_assets(assets): + from assets.models import Asset + asset_ids = [asset.id for asset in assets] + assets = Asset.objects.filter(id__in=asset_ids)\ + .prefetch_related('platform', 'domain', 'accounts') + return assets + + @staticmethod + def group_by_platform(assets): + groups = defaultdict(list) + for asset in assets: + groups[asset.platform].append(asset) + return groups + + @staticmethod + def make_proxy_command(gateway): + proxy_command_list = [ + "ssh", "-o", "Port={}".format(gateway.port), + "-o", "StrictHostKeyChecking=no", + "{}@{}".format(gateway.username, gateway.address), + "-W", "%h:%p", "-q", + ] + + if gateway.password: + proxy_command_list.insert( + 0, "sshpass -p '{}'".format(gateway.password) + ) + if gateway.private_key: + proxy_command_list.append("-i {}".format(gateway.private_key_file)) + + proxy_command = "'-o ProxyCommand={}'".format( + " ".join(proxy_command_list) + ) + return {"ansible_ssh_common_args": proxy_command} + + def asset_to_host(self, asset, account, automation, protocols): + host = {'name': asset.name, 'vars': { + 'asset_id': str(asset.id), 'asset_name': asset.name, + 'asset_type': asset.type, 'asset_category': asset.category, + }} + ansible_connection = automation.ansible_config.get('ansible_connection', 'ssh') + gateway = None + if asset.domain: + gateway = asset.domain.select_gateway() + + ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) + ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None + if ansible_connection == 'local': + if gateway: + host['ansible_host'] = gateway.address + host['ansible_port'] = gateway.port + host['ansible_user'] = gateway.username + host['ansible_password'] = gateway.password + host['ansible_connection'] = 'smart' + else: + host['ansible_connection'] = 'local' + else: + host['ansible_host'] = asset.address + host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 + if account: + host['ansible_user'] = account.username + + if account.secret_type == 'password' and account.secret: + host['ansible_password'] = account.secret + elif account.secret_type == 'private_key' and account.secret: + host['ssh_private_key'] = account.private_key_file + + if gateway: + host['vars'].update(self.make_proxy_command(gateway)) + + if self.host_var_callback: + callback_var = self.host_var_callback(asset) + if isinstance(callback_var, dict): + host['vars'].update(callback_var) + return host + + def select_account(self, asset): + accounts = list(asset.accounts.all()) + if not accounts: + return None + + account_selected = None + account_username = self.account_username + + if isinstance(self.account_username, str): + account_username = [self.account_username] + if account_username: + for username in account_username: + account_matched = list(filter(lambda account: account.username == username, accounts)) + if account_matched: + account_selected = account_matched[0] + return account_selected + + if not account_selected: + if self.account_policy in ['privileged_must', 'privileged_first']: + account_selected = list(filter(lambda account: account.is_privileged, accounts)) + account_selected = account_selected[0] if account_selected else None + + if not account_selected and self.account_policy == 'privileged_first': + account_selected = accounts[0] + return account_selected + + def generate(self): + hosts = [] + platform_assets = self.group_by_platform(self.assets) + for platform, assets in platform_assets.items(): + automation = platform.automation + protocols = platform.protocols.all() + + if not automation.ansible_enabled: + continue + + for asset in self.assets: + account = self.select_account(asset) + host = self.asset_to_host(asset, account, automation, protocols) + hosts.append(host) + return hosts + + def write_to_file(self, path): + hosts = self.generate() + data = {'all': {'hosts': {}}} + for host in hosts: + name = host.pop('name') + var = host.pop('vars', {}) + host.update(var) + data['all']['hosts'][name] = host + with open(path, 'w') as f: + f.write(json.dumps(data, indent=4)) diff --git a/apps/ops/ansible/new_callback.py b/apps/ops/ansible/new_callback.py new file mode 100644 index 000000000..90f945f48 --- /dev/null +++ b/apps/ops/ansible/new_callback.py @@ -0,0 +1,65 @@ + +class JMSCallback: + def event_handler(self, data, runner_config): + event = data.get('event', None) + if not event: + return + event_data = data.get('event_data', {}) + pass + + def runner_on_ok(self, event_data): + pass + + def runer_on_failed(self, event_data): + pass + + def runner_on_skipped(self, event_data): + pass + + def runner_on_unreachable(self, event_data): + pass + + def runner_on_start(self, event_data): + pass + + def runer_retry(self, event_data): + pass + + def runner_on_file_diff(self, event_data): + pass + + def runner_item_on_failed(self, event_data): + pass + + def runner_item_on_skipped(self, event_data): + pass + + def playbook_on_play_start(self, event_data): + pass + + def playbook_on_stats(self, event_data): + pass + + def playbook_on_include(self, event_data): + pass + + def playbook_on_notify(self, event_data): + pass + + def playbook_on_vars_prompt(self, event_data): + pass + + def playbook_on_handler_task_start(self, event_data): + pass + + def playbook_on_no_hosts_matched(self, event_data): + pass + + def playbook_on_no_hosts_remaining(self, event_data): + pass + + def warning(self): + pass + + def status_handler(self): + pass diff --git a/apps/ops/ansible/new_runner.py b/apps/ops/ansible/new_runner.py index 7802c1a82..26cb121a7 100644 --- a/apps/ops/ansible/new_runner.py +++ b/apps/ops/ansible/new_runner.py @@ -1,14 +1,43 @@ +import uuid import ansible_runner - -class AnsibleInventory: - def __init__(self, assets, account=None, ansible_connection='ssh'): - self.assets = assets - self.account = account +from django.conf import settings class AdHocRunner: - pass + cmd_modules_choices = ('shell', 'raw', 'command', 'script', 'win_shell') + cmd_blacklist = [ + "reboot", 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top' + ] + + def __init__(self, inventory, module, module_args, pattern='*', project_dir='/tmp/'): + self.id = uuid.uuid4() + self.inventory = inventory + self.pattern = pattern + self.module = module + self.module_args = module_args + self.project_dir = project_dir + + def check_module(self): + if self.module not in self.cmd_modules_choices: + return + if self.module_args and self.module_args.split()[0] in self.cmd_blacklist: + raise Exception("command not allowed: {}".format(self.module_args[0])) + + def run(self, verbosity=0, **kwargs): + self.check_module() + if verbosity is None and settings.DEBUG: + verbosity = 1 + + return ansible_runner.run( + host_pattern=self.pattern, + private_data_dir=self.project_dir, + inventory=self.inventory, + module=self.module, + module_args=self.module_args, + verbosity=verbosity, + **kwargs + ) class PlaybookRunner: diff --git a/apps/ops/migrations/0023_auto_20220929_2025.py b/apps/ops/migrations/0023_auto_20220929_2025.py new file mode 100644 index 000000000..b5c7475f4 --- /dev/null +++ b/apps/ops/migrations/0023_auto_20220929_2025.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.14 on 2022-09-29 12:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0022_auto_20220817_1346'), + ] + + operations = [ + migrations.RemoveField( + model_name='celerytask', + name='log_path', + ), + migrations.RemoveField( + model_name='celerytask', + name='status', + ), + migrations.AddField( + model_name='celerytask', + name='args', + field=models.JSONField(default=[], verbose_name='Args'), + preserve_default=False, + ), + migrations.AddField( + model_name='celerytask', + name='is_finished', + field=models.BooleanField(default=False, verbose_name='Finished'), + ), + migrations.AddField( + model_name='celerytask', + name='kwargs', + field=models.JSONField(default={}, verbose_name='Kwargs'), + preserve_default=False, + ), + migrations.AddField( + model_name='celerytask', + name='state', + field=models.CharField(default='SUCCESS', max_length=16, verbose_name='State'), + preserve_default=False, + ), + ] diff --git a/apps/ops/migrations/0023_automation_strategy.py b/apps/ops/migrations/0023_automation_strategy.py deleted file mode 100644 index d2807023f..000000000 --- a/apps/ops/migrations/0023_automation_strategy.py +++ /dev/null @@ -1,123 +0,0 @@ -# Generated by Django 3.2.14 on 2022-09-08 11:58 - -import common.db.fields -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0105_auto_20220817_1544'), - ('ops', '0022_auto_20220817_1346'), - ] - - operations = [ - migrations.CreateModel( - name='AutomationStrategy', - fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('name', models.CharField(max_length=128, verbose_name='Name')), - ('is_periodic', models.BooleanField(default=False)), - ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), - ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('accounts', models.JSONField(default=list, verbose_name='Accounts')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), - ('assets', models.ManyToManyField(blank=True, related_name='automation_strategy', to='assets.Asset', verbose_name='Assets')), - ('nodes', models.ManyToManyField(blank=True, related_name='automation_strategy', to='assets.Node', verbose_name='Nodes')), - ], - options={ - 'verbose_name': 'Automation plan', - 'unique_together': {('org_id', 'name')}, - }, - ), - migrations.CreateModel( - name='AutomationStrategyExecution', - fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('date_created', models.DateTimeField(auto_now_add=True)), - ('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')), - ('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')), - ('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')), - ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), - ('strategy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='execution', to='ops.automationstrategy', verbose_name='Automation strategy')), - ], - options={ - 'verbose_name': 'Automation strategy execution', - }, - ), - migrations.CreateModel( - name='CollectStrategy', - fields=[ - ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), - ], - options={ - 'verbose_name': 'Collect strategy', - }, - bases=('ops.automationstrategy',), - ), - migrations.CreateModel( - name='PushStrategy', - fields=[ - ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), - ], - options={ - 'verbose_name': 'Push strategy', - }, - bases=('ops.automationstrategy',), - ), - migrations.CreateModel( - name='VerifyStrategy', - fields=[ - ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), - ], - options={ - 'verbose_name': 'Verify strategy', - }, - bases=('ops.automationstrategy',), - ), - migrations.CreateModel( - name='AutomationStrategyTask', - fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('is_success', models.BooleanField(default=False, verbose_name='Is success')), - ('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')), - ('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')), - ('reason', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Reason')), - ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.account', verbose_name='Account')), - ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')), - ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='task', to='ops.automationstrategyexecution', verbose_name='Automation strategy execution')), - ], - options={ - 'verbose_name': 'Automation strategy task', - }, - ), - migrations.CreateModel( - name='ChangeAuthStrategy', - fields=[ - ('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')), - ('is_password', models.BooleanField(default=True)), - ('password_strategy', models.CharField(blank=True, choices=[('custom', 'Custom password'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], max_length=128, null=True, verbose_name='Password strategy')), - ('password_rules', common.db.fields.JsonDictCharField(blank=True, max_length=2048, null=True, verbose_name='Password rules')), - ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), - ('is_ssh_key', models.BooleanField(default=False)), - ('ssh_key_strategy', models.CharField(blank=True, choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], max_length=128, null=True, verbose_name='SSH Key strategy')), - ('private_key', common.db.fields.EncryptTextField(blank=True, max_length=4096, null=True, verbose_name='SSH private key')), - ('public_key', common.db.fields.EncryptTextField(blank=True, max_length=4096, null=True, verbose_name='SSH public key')), - ('recipients', models.ManyToManyField(blank=True, related_name='recipients_change_auth_strategy', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), - ], - options={ - 'verbose_name': 'Change auth strategy', - }, - bases=('ops.automationstrategy',), - ), - ] diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index f925b14a5..0a9ed463c 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -4,4 +4,3 @@ from .adhoc import * from .celery import * from .command import * -from .automation import * diff --git a/apps/ops/models/automation/__init__.py b/apps/ops/models/automation/__init__.py deleted file mode 100644 index 83fe4a6f9..000000000 --- a/apps/ops/models/automation/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .change_auth import * -from .collect import * -from .push import * -from .verify import * -from .common import * diff --git a/apps/ops/models/automation/base.py b/apps/ops/models/automation/base.py deleted file mode 100644 index ec51c5a2b..000000000 --- a/apps/ops/models/automation/base.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -# diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 9ab5f49e1..2291eb6f1 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -9,32 +9,16 @@ from django.db import models class CeleryTask(models.Model): - WAITING = "waiting" - RUNNING = "running" - FINISHED = "finished" LOG_DIR = os.path.join(settings.PROJECT_DIR, 'data', 'celery') - - STATUS_CHOICES = ( - (WAITING, WAITING), - (RUNNING, RUNNING), - (FINISHED, FINISHED), - ) id = models.UUIDField(primary_key=True, default=uuid.uuid4) name = models.CharField(max_length=1024) - status = models.CharField(max_length=128, choices=STATUS_CHOICES, db_index=True) - log_path = models.CharField(max_length=256, blank=True, null=True) + args = models.JSONField(verbose_name=_("Args")) + kwargs = models.JSONField(verbose_name=_("Kwargs")) + state = models.CharField(max_length=16, verbose_name=_("State")) + is_finished = models.BooleanField(default=False, verbose_name=_("Finished")) date_published = models.DateTimeField(auto_now_add=True) date_start = models.DateTimeField(null=True) date_finished = models.DateTimeField(null=True) def __str__(self): return "{}: {}".format(self.name, self.id) - - def is_finished(self): - return self.status == self.FINISHED - - @property - def full_log_path(self): - if not self.log_path: - return None - return os.path.join(self.LOG_DIR, self.log_path) diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py new file mode 100644 index 000000000..aaec7a4ef --- /dev/null +++ b/apps/ops/models/playbook.py @@ -0,0 +1,16 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from orgs.mixins.models import JMSOrgBaseModel +from ..mixin import PeriodTaskModelMixin + + +class PlaybookTask(PeriodTaskModelMixin, JMSOrgBaseModel): + assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) + account = models.CharField(max_length=128, default='root', verbose_name=_('Account')) + playbook = models.FilePathField(max_length=1024, verbose_name=_("Playbook")) + owner = models.CharField(max_length=1024, verbose_name=_("Owner")) + comment = models.TextField(blank=True, verbose_name=_("Comment")) + + def get_register_task(self): + pass diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index dfd364845..e48802d84 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -1,15 +1,20 @@ -from django.utils import translation +import ast + +from django.utils import translation, timezone from django.core.cache import cache -from celery.signals import task_prerun, task_postrun, before_task_publish +from celery import signals -from common.db.utils import close_old_connections +from common.db.utils import close_old_connections, get_logger +from .models import CeleryTask +logger = get_logger(__name__) + TASK_LANG_CACHE_KEY = 'TASK_LANG_{}' TASK_LANG_CACHE_TTL = 1800 -@before_task_publish.connect() +@signals.before_task_publish.connect def before_task_publish(headers=None, **kwargs): task_id = headers.get('id') current_lang = translation.get_language() @@ -17,8 +22,10 @@ def before_task_publish(headers=None, **kwargs): cache.set(key, current_lang, 1800) -@task_prerun.connect() +@signals.task_prerun.connect def on_celery_task_pre_run(task_id='', **kwargs): + # 更新状态 + CeleryTask.objects.filter(id=task_id).update(state='RUNNING', date_start=timezone.now()) # 关闭之前的数据库连接 close_old_connections() @@ -29,6 +36,40 @@ def on_celery_task_pre_run(task_id='', **kwargs): translation.activate(task_lang) -@task_postrun.connect() -def on_celery_task_post_run(**kwargs): +@signals.task_postrun.connect +def on_celery_task_post_run(task_id='', state='', **kwargs): close_old_connections() + print("Task post run: ", task_id, state) + + CeleryTask.objects.filter(id=task_id).update( + state=state, date_finished=timezone.now(), is_finished=True + ) + + +@signals.after_task_publish.connect +def task_sent_handler(headers=None, body=None, **kwargs): + info = headers if 'task' in headers else body + task = info.get('task') + i = info.get('id') + if not i or not task: + logger.error("Not found task id or name: {}".format(info)) + return + + args = info.get('argsrepr', '()') + kwargs = info.get('kwargsrepr', '{}') + try: + args = list(ast.literal_eval(args)) + kwargs = ast.literal_eval(kwargs) + except (ValueError, SyntaxError): + args = [] + kwargs = {} + + data = { + 'id': i, + 'name': task, + 'state': 'PENDING', + 'is_finished': False, + 'args': args, + 'kwargs': kwargs + } + CeleryTask.objects.create(**data) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 2b739e3d6..cb21b5c3d 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,10 +1,10 @@ # coding: utf-8 import os import subprocess -import time from django.conf import settings from celery import shared_task, subtask +from celery import signals from celery.exceptions import SoftTimeLimitExceeded from django.utils import timezone @@ -30,7 +30,7 @@ def rerun_task(): pass -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_("Run ansible task")) def run_ansible_task(tid, callback=None, **kwargs): """ :param tid: is the tasks serialized data @@ -49,7 +49,7 @@ def run_ansible_task(tid, callback=None, **kwargs): return result -@shared_task(soft_time_limit=60, queue="ansible") +@shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible command")) def run_command_execution(cid, **kwargs): with tmp_to_root_org(): execution = get_object_or_none(CommandExecution, id=cid) @@ -136,7 +136,7 @@ def check_server_performance_period(): ServerPerformanceCheckUtil().check_and_publish() -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_("Hello")) def hello(name, callback=None): from users.models import User import time @@ -148,38 +148,12 @@ def hello(name, callback=None): return gettext("Hello") -@shared_task -# @after_app_shutdown_clean_periodic -# @register_as_period_task(interval=30) -def hello123(): - return None - - @shared_task def hello_callback(result): print(result) print("Hello callback") -@shared_task -def add(a, b): - time.sleep(5) - return a + b - - -@shared_task -def add_m(x): - from celery import chain - a = range(x) - b = [a[i:i + 10] for i in range(0, len(a), 10)] - s = list() - s.append(add.s(b[0], b[1])) - for i in b[1:]: - s.append(add.s(i)) - res = chain(*tuple(s))() - return res - - @shared_task def execute_automation_strategy(pid, trigger): from .models import AutomationStrategy @@ -191,3 +165,4 @@ def execute_automation_strategy(pid, trigger): with tmp_to_org(instance.org): instance.execute(trigger) + diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index c3a1cb86d..5df387c91 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -6,7 +6,7 @@ from orgs.utils import current_org, tmp_to_org from common.cache import Cache, IntegerField from common.utils import get_logger from users.models import UserGroup, User -from assets.models import Node, Domain, Gateway, Asset +from assets.models import Node, Domain, Gateway, Asset, Account from terminal.models import Session from perms.models import AssetPermission @@ -52,6 +52,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache): assets_amount = IntegerField() nodes_amount = IntegerField(queryset=Node.objects) + accounts_amount = IntegerField(queryset=Account.objects) domains_amount = IntegerField(queryset=Domain.objects) gateways_amount = IntegerField(queryset=Gateway.objects) asset_perms_amount = IntegerField(queryset=AssetPermission.objects) diff --git a/apps/orgs/tasks.py b/apps/orgs/tasks.py index a33456913..6b6ec9e0d 100644 --- a/apps/orgs/tasks.py +++ b/apps/orgs/tasks.py @@ -6,6 +6,6 @@ logger = get_logger(__file__) @shared_task -def refresh_org_cache_task(cache, *fields): - logger.info(f'CACHE: refresh {cache.key}.{fields}') - cache.refresh(*fields) +def refresh_org_cache_task(*fields): + from .caches import OrgResourceStatisticsCache + OrgResourceStatisticsCache.refresh(*fields) From df5e63b3be6489a3e9e96adcb41c11638206ebd0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 30 Sep 2022 18:49:45 +0800 Subject: [PATCH 176/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20ansible=20?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/callback.py | 398 +++++++---------------------- apps/ops/ansible/display.py | 69 ----- apps/ops/ansible/inventory.py | 151 +---------- apps/ops/ansible/new_callback.py | 65 ----- apps/ops/ansible/new_runner.py | 44 ---- apps/ops/ansible/runner.py | 301 ++++------------------ apps/ops/ansible/test_inventory.py | 63 ----- apps/ops/ansible/test_runner.py | 58 ----- 8 files changed, 151 insertions(+), 998 deletions(-) delete mode 100644 apps/ops/ansible/display.py delete mode 100644 apps/ops/ansible/new_callback.py delete mode 100644 apps/ops/ansible/new_runner.py delete mode 100644 apps/ops/ansible/test_inventory.py delete mode 100644 apps/ops/ansible/test_runner.py diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index cc879cc4c..8b6ad1f8f 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -1,334 +1,126 @@ -# ~*~ coding: utf-8 ~*~ - -import datetime -import json -import os from collections import defaultdict -import ansible.constants as C -from ansible.plugins.callback import CallbackBase -from ansible.plugins.callback.default import CallbackModule -from ansible.plugins.callback.minimal import CallbackModule as CMDCallBackModule -from common.utils.strings import safe_str - - -class CallbackMixin: - def __init__(self, display=None): - # result_raw example: { - # "ok": {"hostname": {"task_name": {},...},..}, - # "failed": {"hostname": {"task_name": {}..}, ..}, - # "unreachable: {"hostname": {"task_name": {}, ..}}, - # "skipped": {"hostname": {"task_name": {}, ..}, ..}, - # } - # results_summary example: { - # "contacted": {"hostname": {"task_name": {}}, "hostname": {}}, - # "dark": {"hostname": {"task_name": {}, "task_name": {}},...,}, - # "success": True - # } - self.results_raw = dict( +class DefaultCallback: + def __init__(self): + self.result = dict( ok=defaultdict(dict), - failed=defaultdict(dict), - unreachable=defaultdict(dict), - skippe=defaultdict(dict), - ) - self.results_summary = dict( - contacted=defaultdict(dict), + failures=defaultdict(dict), dark=defaultdict(dict), - success=True + skipped=defaultdict(dict), ) - self.results = { - 'raw': self.results_raw, - 'summary': self.results_summary, - } - super().__init__() - if display: - self._display = display + self.summary = dict( + ok=[], + failures={}, + dark={}, + skipped=[], + ) + self.status = 'starting' + self.finished = False - cols = os.environ.get("TERM_COLS", None) - self._display.columns = 79 - if cols and cols.isdigit(): - self._display.columns = int(cols) - 1 + def is_success(self): + return self.status != 'successful' - def display(self, msg): - self._display.display(msg) - - def gather_result(self, t, result): - self._clean_results(result._result, result._task.action) - host = result._host.get_name() - task_name = result.task_name - task_result = result._result - - self.results_raw[t][host][task_name] = task_result - self.clean_result(t, host, task_name, task_result) - - def close(self): - if hasattr(self._display, 'close'): - self._display.close() - - -class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule): - """ - Task result Callback - """ - context = None - events = [ - 'runner_on_failed', 'runner_on_ok', - 'runner_on_skipped', 'runner_on_unreachable', - ] - - def event_handler(self, data): + def event_handler(self, data, **kwargs): event = data.get('event', None) - print("Event: ", event) - print("Event Data: ", json.dumps(data)) + if not event: + return + event_data = data.get('event_data', {}) + host = event_data.get('remote_addr', '') + task = event_data.get('task', '') + res = event_data.get('res', {}) + handler = getattr(self, event, self.on_any) + handler(event_data, host=host, task=task, res=res) - def clean_result(self, t, host, task_name, task_result): - contacted = self.results_summary["contacted"] - dark = self.results_summary["dark"] - - if task_result.get('rc') is not None: - cmd = task_result.get('cmd') - if isinstance(cmd, list): - cmd = " ".join(cmd) - else: - cmd = str(cmd) - detail = { - 'cmd': cmd, - 'stderr': task_result.get('stderr'), - 'stdout': safe_str(str(task_result.get('stdout', ''))), - 'rc': task_result.get('rc'), - 'delta': task_result.get('delta'), - 'msg': task_result.get('msg', '') - } - else: - detail = { - "changed": task_result.get('changed', False), - "msg": task_result.get('msg', '') - } - - if t in ("ok", "skipped"): - contacted[host][task_name] = detail - else: - dark[host][task_name] = detail - - def v2_runner_on_failed(self, result, ignore_errors=False): - self.results_summary['success'] = False - self.gather_result("failed", result) - - if result._task.action in C.MODULE_NO_JSON: - CMDCallBackModule.v2_runner_on_failed(self, - result, ignore_errors=ignore_errors - ) - else: - super().v2_runner_on_failed( - result, ignore_errors=ignore_errors - ) - - def v2_runner_on_ok(self, result): - self.gather_result("ok", result) - if result._task.action in C.MODULE_NO_JSON: - CMDCallBackModule.v2_runner_on_ok(self, result) - else: - super().v2_runner_on_ok(result) - - def v2_runner_on_skipped(self, result): - self.gather_result("skipped", result) - super().v2_runner_on_skipped(result) - - def v2_runner_on_unreachable(self, result): - self.results_summary['success'] = False - self.gather_result("unreachable", result) - super().v2_runner_on_unreachable(result) - - def v2_runner_on_start(self, *args, **kwargs): - pass - - def display_skipped_hosts(self): - pass - - def display_ok_hosts(self): - pass - - def display_failed_stderr(self): - pass - - def set_play_context(self, context): - # for k, v in context._attributes.items(): - # print("{} ==> {}".format(k, v)) - if self.context and isinstance(self.context, dict): - for k, v in self.context.items(): - setattr(context, k, v) - - -class CommandResultCallback(AdHocResultCallback): - """ - Command result callback - - results_command: { - "cmd": "", - "stderr": "", - "stdout": "", - "rc": 0, - "delta": 0:0:0.123 - } - """ - def __init__(self, display=None, **kwargs): - - self.results_command = dict() - super().__init__(display) - - def gather_result(self, t, res): - super().gather_result(t, res) - self.gather_cmd(t, res) - - def v2_playbook_on_play_start(self, play): - now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - msg = '$ {} ({})'.format(play.name, now) - self._play = play - self._display.banner(msg) - - def v2_runner_on_unreachable(self, result): - self.results_summary['success'] = False - self.gather_result("unreachable", result) - msg = result._result.get("msg") - if not msg: - msg = json.dumps(result._result, indent=4) - self._display.display("%s | FAILED! => \n%s" % ( - result._host.get_name(), - msg, - ), color=C.COLOR_ERROR) - - def v2_runner_on_failed(self, result, ignore_errors=False): - self.results_summary['success'] = False - self.gather_result("failed", result) - msg = result._result.get("msg", '') - stderr = result._result.get("stderr") - if stderr: - msg += '\n' + stderr - module_stdout = result._result.get("module_stdout") - if module_stdout: - msg += '\n' + module_stdout - if not msg: - msg = json.dumps(result._result, indent=4) - self._display.display("%s | FAILED! => \n%s" % ( - result._host.get_name(), - msg, - ), color=C.COLOR_ERROR) - - def v2_playbook_on_stats(self, stats): - pass - - def _print_task_banner(self, task): - pass - - def gather_cmd(self, t, res): - host = res._host.get_name() - cmd = {} - if t == "ok": - cmd['cmd'] = res._result.get('cmd') - cmd['stderr'] = res._result.get('stderr') - cmd['stdout'] = safe_str(str(res._result.get('stdout', ''))) - cmd['rc'] = res._result.get('rc') - cmd['delta'] = res._result.get('delta') - else: - cmd['err'] = "Error: {}".format(res) - self.results_command[host] = cmd - - -class PlaybookResultCallBack(CallbackBase): - """ - Custom callback model for handlering the output data of - execute playbook file, - Base on the build-in callback plugins of ansible which named `json`. - """ - - CALLBACK_VERSION = 2.0 - CALLBACK_TYPE = 'stdout' - CALLBACK_NAME = 'Dict' - - def __init__(self, display=None): - super(PlaybookResultCallBack, self).__init__(display) - self.results = [] - self.output = "" - self.item_results = {} # {"host": []} - - def _new_play(self, play): - return { - 'play': { - 'name': play.name, - 'id': str(play._uuid) - }, - 'tasks': [] + def runner_on_ok(self, event_data, host=None, task=None, res=None): + detail = { + 'action': event_data.get('task_action', ''), + 'res': res, + 'rc': res.get('rc', 0), + 'stdout': res.get('stdout', ''), } + self.result['ok'][host][task] = detail - def _new_task(self, task): - return { - 'task': { - 'name': task.get_name(), - }, - 'hosts': {} + def runer_on_failed(self, event_data, host=None, task=None, res=None, **kwargs): + detail = { + 'action': event_data.get('task_action', ''), + 'res': res, + 'rc': res.get('rc', 0), + 'stdout': res.get('stdout', ''), + 'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';') } + self.result['failures'][host][task] = detail - def v2_playbook_on_no_hosts_matched(self): - self.output = "skipping: No match hosts." + def runner_on_skipped(self, event_data, host=None, task=None, **kwargs): + detail = { + 'action': event_data.get('task_action', ''), + 'res': {}, + 'rc': 0, + } + self.result['skipped'][host][task] = detail - def v2_playbook_on_no_hosts_remaining(self): + def runner_on_unreachable(self, event_data, host=None, task=None, res=None, **kwargs): + detail = { + 'action': event_data.get('task_action', ''), + 'res': res, + 'rc': 255, + 'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';') + } + self.result['dark'][host][task] = detail + + def runner_on_start(self, event_data, **kwargs): pass - def v2_playbook_on_task_start(self, task, is_conditional): - self.results[-1]['tasks'].append(self._new_task(task)) + def runer_retry(self, event_data, **kwargs): + pass - def v2_playbook_on_play_start(self, play): - self.results.append(self._new_play(play)) + def runner_on_file_diff(self, event_data, **kwargs): + pass - def v2_playbook_on_stats(self, stats): - hosts = sorted(stats.processed.keys()) - summary = {} - for h in hosts: - s = stats.summarize(h) - summary[h] = s + def runner_item_on_failed(self, event_data, **kwargs): + pass - if self.output: - pass - else: - self.output = { - 'plays': self.results, - 'stats': summary - } + def runner_item_on_skipped(self, event_data, **kwargs): + pass - def gather_result(self, res): - if res._task.loop and "results" in res._result and res._host.name in self.item_results: - res._result.update({"results": self.item_results[res._host.name]}) - del self.item_results[res._host.name] + def playbook_on_play_start(self, event_data, **kwargs): + pass - self.results[-1]['tasks'][-1]['hosts'][res._host.name] = res._result + def playbook_on_stats(self, event_data, **kwargs): + failed = [] + for i in ['dark', 'failures']: + for host, tasks in self.result[i].items(): + failed.append(host) + error = '' + for task, detail in tasks.items(): + error += f'{task}: {detail["stderr"]};' + self.summary[i][host] = error.strip(';') + self.summary['ok'] = list(set(self.result['ok'].keys()) - set(failed)) + self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(failed)) - def v2_runner_on_ok(self, res, **kwargs): - if "ansible_facts" in res._result: - del res._result["ansible_facts"] + def playbook_on_include(self, event_data, **kwargs): + pass - self.gather_result(res) + def playbook_on_notify(self, event_data, **kwargs): + pass - def v2_runner_on_failed(self, res, **kwargs): - self.gather_result(res) + def playbook_on_vars_prompt(self, event_data, **kwargs): + pass - def v2_runner_on_unreachable(self, res, **kwargs): - self.gather_result(res) + def playbook_on_handler_task_start(self, event_data, **kwargs): + pass - def v2_runner_on_skipped(self, res, **kwargs): - self.gather_result(res) + def playbook_on_no_hosts_matched(self, event_data, **kwargs): + pass - def gather_item_result(self, res): - self.item_results.setdefault(res._host.name, []).append(res._result) - - def v2_runner_item_on_ok(self, res): - self.gather_item_result(res) - - def v2_runner_item_on_failed(self, res): - self.gather_item_result(res) - - def v2_runner_item_on_skipped(self, res): - self.gather_item_result(res) + def playbook_on_no_hosts_remaining(self, event_data, **kwargs): + pass + def warning(self, event_data, **kwargs): + pass + def on_any(self, event_data, **kwargs): + pass + def status_handler(self, data, **kwargs): + self.status = data.get('status', 'unknown') diff --git a/apps/ops/ansible/display.py b/apps/ops/ansible/display.py deleted file mode 100644 index ab93892b2..000000000 --- a/apps/ops/ansible/display.py +++ /dev/null @@ -1,69 +0,0 @@ -import errno -import sys -import os - -from ansible.utils.display import Display -from ansible.utils.color import stringc -from ansible.utils.singleton import Singleton - -from .utils import get_ansible_task_log_path - - -class UnSingleton(Singleton): - def __init__(cls, name, bases, dct): - type.__init__(cls, name, bases, dct) - - def __call__(cls, *args, **kwargs): - return type.__call__(cls, *args, **kwargs) - - -class AdHocDisplay(Display, metaclass=UnSingleton): - def __init__(self, execution_id, verbosity=0): - super().__init__(verbosity=verbosity) - if execution_id: - log_path = get_ansible_task_log_path(execution_id) - else: - log_path = os.devnull - self.log_file = open(log_path, mode='a') - - def close(self): - self.log_file.close() - - def set_cowsay_info(self): - # 中断 cowsay 的测试,会频繁开启子进程 - return - - def _write_to_screen(self, msg, stderr): - if not stderr: - screen = sys.stdout - else: - screen = sys.stderr - - screen.write(msg) - - try: - screen.flush() - except IOError as e: - # Ignore EPIPE in case fileobj has been prematurely closed, eg. - # when piping to "head -n1" - if e.errno != errno.EPIPE: - raise - - def _write_to_log_file(self, msg): - # 这里先不 flush,log 文件不需要那么及时。 - self.log_file.write(msg) - - def display(self, msg, color=None, stderr=False, screen_only=False, log_only=False, newline=True): - if log_only: - return - - if color: - msg = stringc(msg, color) - - if not msg.endswith(u'\n'): - msg2 = msg + u'\n' - else: - msg2 = msg - - self._write_to_log_file(msg2) - self._write_to_screen(msg2, stderr) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index dabda5e4e..2382525ed 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -2,161 +2,12 @@ from collections import defaultdict import json -from ansible.inventory.host import Host -from ansible.vars.manager import VariableManager -from ansible.inventory.manager import InventoryManager -from ansible.parsing.dataloader import DataLoader - __all__ = [ - 'BaseHost', 'BaseInventory' + 'JMSInventory', ] -class BaseHost(Host): - def __init__(self, host_data): - """ - 初始化 - :param host_data: { - "name": "", - "ip": "", - "port": "", - # behind is not must be required - "username": "", - "password": "", - "private_key_path": "", - "become": { - "method": "", - "user": "", - "pass": "", - } - "groups": [], - "vars": {}, - } - """ - self.host_data = host_data - hostname = host_data.get('name') or host_data.get('ip') - port = host_data.get('port') or 22 - super().__init__(hostname, port) - self.__set_required_variables() - self.__set_extra_variables() - - def __set_required_variables(self): - host_data = self.host_data - self.set_variable('ansible_host', host_data['address']) - self.set_variable('ansible_port', host_data['port']) - - if host_data.get('username'): - self.set_variable('ansible_user', host_data['username']) - - # 添加密码和密钥 - if host_data.get('password'): - self.set_variable('ansible_ssh_pass', host_data['password']) - if host_data.get('private_key_path'): - self.set_variable('ansible_ssh_private_key_file', host_data['private_key_path']) - - # 添加become支持 - become = host_data.get("become", False) - if become: - self.set_variable("ansible_become", True) - self.set_variable("ansible_become_method", become.get('method', 'sudo')) - self.set_variable("ansible_become_user", become.get('user', 'root')) - self.set_variable("ansible_become_pass", become.get('pass', '')) - else: - self.set_variable("ansible_become", False) - - def __set_extra_variables(self): - for k, v in self.host_data.get('vars', {}).items(): - self.set_variable(k, v) - - def __repr__(self): - return self.name - - -class BaseInventory(InventoryManager): - """ - 提供生成Ansible inventory对象的方法 - """ - loader_class = DataLoader - variable_manager_class = VariableManager - host_manager_class = BaseHost - - def __init__(self, host_list=None, group_list=None): - """ - 用于生成动态构建Ansible Inventory. super().__init__ 会自动调用 - host_list: [{ - "name": "", - "address": "", - "port": "", - "username": "", - "password": "", - "private_key": "", - "become": { - "method": "", - "user": "", - "pass": "", - }, - "groups": [], - "vars": {}, - }, - ] - group_list: [ - {"name: "", children: [""]}, - ] - :param host_list: - :param group_list - """ - self.host_list = host_list or [] - self.group_list = group_list or [] - assert isinstance(host_list, list) - self.loader = self.loader_class() - self.variable_manager = self.variable_manager_class() - super().__init__(self.loader) - - def get_groups(self): - return self._inventory.groups - - def get_group(self, name): - return self._inventory.groups.get(name, None) - - def get_or_create_group(self, name): - group = self.get_group(name) - if not group: - self.add_group(name) - return self.get_or_create_group(name) - else: - return group - - def parse_groups(self): - for g in self.group_list: - parent = self.get_or_create_group(g.get("name")) - children = [self.get_or_create_group(n) for n in g.get('children', [])] - for child in children: - parent.add_child_group(child) - - def parse_hosts(self): - group_all = self.get_or_create_group('all') - ungrouped = self.get_or_create_group('ungrouped') - for host_data in self.host_list: - host = self.host_manager_class(host_data=host_data) - self.hosts[host_data['name']] = host - groups_data = host_data.get('groups') - if groups_data: - for group_name in groups_data: - group = self.get_or_create_group(group_name) - group.add_host(host) - else: - ungrouped.add_host(host) - group_all.add_host(host) - - def parse_sources(self, cache=False): - self.parse_groups() - self.parse_hosts() - - def get_matched_hosts(self, pattern): - return self.get_hosts(pattern) - - class JMSInventory: def __init__(self, assets, account_username=None, account_policy='smart', host_var_callback=None): """ diff --git a/apps/ops/ansible/new_callback.py b/apps/ops/ansible/new_callback.py deleted file mode 100644 index 90f945f48..000000000 --- a/apps/ops/ansible/new_callback.py +++ /dev/null @@ -1,65 +0,0 @@ - -class JMSCallback: - def event_handler(self, data, runner_config): - event = data.get('event', None) - if not event: - return - event_data = data.get('event_data', {}) - pass - - def runner_on_ok(self, event_data): - pass - - def runer_on_failed(self, event_data): - pass - - def runner_on_skipped(self, event_data): - pass - - def runner_on_unreachable(self, event_data): - pass - - def runner_on_start(self, event_data): - pass - - def runer_retry(self, event_data): - pass - - def runner_on_file_diff(self, event_data): - pass - - def runner_item_on_failed(self, event_data): - pass - - def runner_item_on_skipped(self, event_data): - pass - - def playbook_on_play_start(self, event_data): - pass - - def playbook_on_stats(self, event_data): - pass - - def playbook_on_include(self, event_data): - pass - - def playbook_on_notify(self, event_data): - pass - - def playbook_on_vars_prompt(self, event_data): - pass - - def playbook_on_handler_task_start(self, event_data): - pass - - def playbook_on_no_hosts_matched(self, event_data): - pass - - def playbook_on_no_hosts_remaining(self, event_data): - pass - - def warning(self): - pass - - def status_handler(self): - pass diff --git a/apps/ops/ansible/new_runner.py b/apps/ops/ansible/new_runner.py deleted file mode 100644 index 26cb121a7..000000000 --- a/apps/ops/ansible/new_runner.py +++ /dev/null @@ -1,44 +0,0 @@ -import uuid -import ansible_runner - -from django.conf import settings - - -class AdHocRunner: - cmd_modules_choices = ('shell', 'raw', 'command', 'script', 'win_shell') - cmd_blacklist = [ - "reboot", 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top' - ] - - def __init__(self, inventory, module, module_args, pattern='*', project_dir='/tmp/'): - self.id = uuid.uuid4() - self.inventory = inventory - self.pattern = pattern - self.module = module - self.module_args = module_args - self.project_dir = project_dir - - def check_module(self): - if self.module not in self.cmd_modules_choices: - return - if self.module_args and self.module_args.split()[0] in self.cmd_blacklist: - raise Exception("command not allowed: {}".format(self.module_args[0])) - - def run(self, verbosity=0, **kwargs): - self.check_module() - if verbosity is None and settings.DEBUG: - verbosity = 1 - - return ansible_runner.run( - host_pattern=self.pattern, - private_data_dir=self.project_dir, - inventory=self.inventory, - module=self.module, - module_args=self.module_args, - verbosity=verbosity, - **kwargs - ) - - -class PlaybookRunner: - pass diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index a25d681b9..6c339eba6 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -1,261 +1,70 @@ -# ~*~ coding: utf-8 ~*~ +import uuid +import ansible_runner -import os - -import shutil -from collections import namedtuple - -from ansible import context -from ansible.playbook import Playbook -from ansible.module_utils.common.collections import ImmutableDict -from ansible.executor.task_queue_manager import TaskQueueManager -from ansible.vars.manager import VariableManager -from ansible.parsing.dataloader import DataLoader -from ansible.executor.playbook_executor import PlaybookExecutor -from ansible.playbook.play import Play -import ansible.constants as C - -from .callback import ( - AdHocResultCallback, PlaybookResultCallBack, CommandResultCallback -) -from common.utils import get_logger -from .exceptions import AnsibleError -from .display import AdHocDisplay - - -__all__ = ["AdHocRunner", "PlayBookRunner", "CommandRunner"] -C.HOST_KEY_CHECKING = False -logger = get_logger(__name__) - - -Options = namedtuple('Options', [ - 'listtags', 'listtasks', 'listhosts', 'syntax', 'connection', - 'module_path', 'forks', 'remote_user', 'private_key_file', 'timeout', - 'ssh_common_args', 'ssh_extra_args', 'sftp_extra_args', - 'scp_extra_args', 'become', 'become_method', 'become_user', - 'verbosity', 'check', 'extra_vars', 'playbook_path', 'passwords', - 'diff', 'gathering', 'remote_tmp', -]) - - -def get_default_options(): - options = dict( - syntax=False, - timeout=30, - connection='ssh', - forks=10, - remote_user='root', - private_key_file=None, - become=None, - become_method=None, - become_user=None, - verbosity=1, - check=False, - diff=False, - gathering='implicit', - remote_tmp='/tmp/.ansible' - ) - return options - - -# JumpServer not use playbook -class PlayBookRunner: - """ - 用于执行AnsiblePlaybook的接口.简化Playbook对象的使用. - """ - - # Default results callback - results_callback_class = PlaybookResultCallBack - loader_class = DataLoader - variable_manager_class = VariableManager - options = get_default_options() - - def __init__(self, inventory=None, options=None): - """ - :param options: Ansible options like ansible.cfg - :param inventory: Ansible inventory - """ - if options: - self.options = options - C.RETRY_FILES_ENABLED = False - self.inventory = inventory - self.loader = self.loader_class() - self.results_callback = self.results_callback_class() - self.playbook_path = options.playbook_path - self.variable_manager = self.variable_manager_class( - loader=self.loader, inventory=self.inventory - ) - self.passwords = options.passwords - self.__check() - - def __check(self): - if self.options.playbook_path is None or \ - not os.path.exists(self.options.playbook_path): - raise AnsibleError( - "Not Found the playbook file: {}.".format(self.options.playbook_path) - ) - if not self.inventory.list_hosts('all'): - raise AnsibleError('Inventory is empty') - - def run(self): - executor = PlaybookExecutor( - playbooks=[self.playbook_path], - inventory=self.inventory, - variable_manager=self.variable_manager, - loader=self.loader, - passwords={"conn_pass": self.passwords} - ) - context.CLIARGS = ImmutableDict(self.options) - - if executor._tqm: - executor._tqm._stdout_callback = self.results_callback - executor.run() - executor._tqm.cleanup() - return self.results_callback.output +from django.conf import settings +from .callback import DefaultCallback class AdHocRunner: - """ - ADHoc Runner接口 - """ - results_callback_class = AdHocResultCallback - results_callback = None - loader_class = DataLoader - variable_manager_class = VariableManager - default_options = get_default_options() - command_modules_choices = ('shell', 'raw', 'command', 'script', 'win_shell') + cmd_modules_choices = ('shell', 'raw', 'command', 'script', 'win_shell') + cmd_blacklist = [ + "reboot", 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top' + ] - def __init__(self, inventory, options=None): - self.options = self.update_options(options) + def __init__(self, inventory, module, module_args, pattern='*', project_dir='/tmp/'): + self.id = uuid.uuid4() self.inventory = inventory - self.loader = DataLoader() - self.variable_manager = VariableManager( - loader=self.loader, inventory=self.inventory - ) + self.pattern = pattern + self.module = module + self.module_args = module_args + self.project_dir = project_dir + self.cb = DefaultCallback() + self.runner = None - def get_result_callback(self, execution_id=None): - return self.__class__.results_callback_class(display=AdHocDisplay(execution_id)) + def check_module(self): + if self.module not in self.cmd_modules_choices: + return + if self.module_args and self.module_args.split()[0] in self.cmd_blacklist: + raise Exception("command not allowed: {}".format(self.module_args[0])) - @staticmethod - def check_module_args(module_name, module_args=''): - if module_name in C.MODULE_REQUIRE_ARGS and not module_args: - err = "No argument passed to '%s' module." % module_name - raise AnsibleError(err) + def run(self, verbosity=0, **kwargs): + self.check_module() + if verbosity is None and settings.DEBUG: + verbosity = 1 - def check_pattern(self, pattern): - if not pattern: - raise AnsibleError("Pattern `{}` is not valid!".format(pattern)) - if not self.inventory.list_hosts("all"): - raise AnsibleError("Inventory is empty.") - if not self.inventory.list_hosts(pattern): - raise AnsibleError( - "pattern: %s dose not match any hosts." % pattern - ) - - def clean_args(self, module, args): - if not args: - return '' - if module not in self.command_modules_choices: - return args - if isinstance(args, str): - if args.startswith('executable='): - _args = args.split(' ') - executable, command = _args[0].split('=')[1], ' '.join(_args[1:]) - args = {'executable': executable, '_raw_params': command} - else: - args = {'_raw_params': args} - return args - else: - return args - - def clean_tasks(self, tasks): - cleaned_tasks = [] - for task in tasks: - module = task['action']['module'] - args = task['action'].get('args') - cleaned_args = self.clean_args(module, args) - task['action']['args'] = cleaned_args - self.check_module_args(module, cleaned_args) - cleaned_tasks.append(task) - return cleaned_tasks - - def update_options(self, options): - _options = {k: v for k, v in self.default_options.items()} - if options and isinstance(options, dict): - _options.update(options) - return _options - - def set_control_master_if_need(self, cleaned_tasks): - modules = [task.get('action', {}).get('module') for task in cleaned_tasks] - if {'ping', 'win_ping'} & set(modules): - self.results_callback.context = { - 'ssh_args': '-C -o ControlMaster=no' - } - - def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no', execution_id=None): - """ - :param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ] - :param pattern: all, *, or others - :param play_name: The play name - :param gather_facts: - :return: - """ - self.check_pattern(pattern) - self.results_callback = self.get_result_callback(execution_id) - cleaned_tasks = self.clean_tasks(tasks) - self.set_control_master_if_need(cleaned_tasks) - context.CLIARGS = ImmutableDict(self.options) - - play_source = dict( - name=play_name, - hosts=pattern, - gather_facts=gather_facts, - tasks=cleaned_tasks - ) - - play = Play().load( - play_source, - variable_manager=self.variable_manager, - loader=self.loader, - ) - loader = DataLoader() - # used in start callback - playbook = Playbook(loader) - playbook._entries.append(play) - playbook._file_name = '__adhoc_playbook__' - - tqm = TaskQueueManager( + ansible_runner.run( + host_pattern=self.pattern, + private_data_dir=self.project_dir, inventory=self.inventory, - variable_manager=self.variable_manager, - loader=self.loader, - stdout_callback=self.results_callback, - passwords={"conn_pass": self.options.get("password", "")} + module=self.module, + module_args=self.module_args, + verbosity=verbosity, + event_handler=self.cb.event_handler, + status_handler=self.cb.status_handler, + **kwargs ) - try: - tqm.send_callback('v2_playbook_on_start', playbook) - tqm.run(play) - tqm.send_callback('v2_playbook_on_stats', tqm._stats) - return self.results_callback - except Exception as e: - raise AnsibleError(e) - finally: - if tqm is not None: - tqm.cleanup() - shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) - - self.results_callback.close() + return self.cb -class CommandRunner(AdHocRunner): - results_callback_class = CommandResultCallback - modules_choices = ('shell', 'raw', 'command', 'script', 'win_shell') +class PlaybookRunner: + def __init__(self, inventory, playbook, project_dir='/tmp/'): + self.id = uuid.uuid4() + self.inventory = inventory + self.playbook = playbook + self.project_dir = project_dir + self.cb = DefaultCallback() - def execute(self, cmd, pattern, module='shell'): - if module and module not in self.modules_choices: - raise AnsibleError("Module should in {}".format(self.modules_choices)) - - tasks = [ - {"action": {"module": module, "args": cmd}} - ] - return self.run(tasks, pattern, play_name=cmd) + def run(self, verbosity=0, **kwargs): + if verbosity is None and settings.DEBUG: + verbosity = 1 + ansible_runner.run( + private_data_dir=self.project_dir, + inventory=self.inventory, + playbook=self.playbook, + verbosity=verbosity, + event_handler=self.cb.event_handler, + status_handler=self.cb.status_handler, + **kwargs + ) + return self.cb diff --git a/apps/ops/ansible/test_inventory.py b/apps/ops/ansible/test_inventory.py deleted file mode 100644 index a03faeaf5..000000000 --- a/apps/ops/ansible/test_inventory.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# - -import sys -import unittest - - -sys.path.insert(0, '../..') -from ops.ansible.inventory import BaseInventory - - -class TestJMSInventory(unittest.TestCase): - def setUp(self): - host_list = [{ - "name": "testserver1", - "ip": "102.1.1.1", - "port": 22, - "username": "root", - "password": "password", - "private_key": "/tmp/private_key", - "become": { - "method": "sudo", - "user": "root", - "pass": None, - }, - "groups": ["group1", "group2"], - "vars": {"sexy": "yes"}, - }, { - "name": "testserver2", - "ip": "8.8.8.8", - "port": 2222, - "username": "root", - "password": "password", - "private_key": "/tmp/private_key", - "become": { - "method": "su", - "user": "root", - "pass": "123", - }, - "groups": ["group3", "group4"], - "vars": {"love": "yes"}, - }] - - self.inventory = BaseInventory(host_list=host_list) - - def test_hosts(self): - print("#"*10 + "Hosts" + "#"*10) - for host in self.inventory.hosts: - print(host) - - def test_groups(self): - print("#" * 10 + "Groups" + "#" * 10) - for group in self.inventory.groups: - print(group) - - def test_group_all(self): - print("#" * 10 + "all group hosts" + "#" * 10) - group = self.inventory.get_group('all') - print(group.hosts) - - -if __name__ == '__main__': - unittest.main() diff --git a/apps/ops/ansible/test_runner.py b/apps/ops/ansible/test_runner.py deleted file mode 100644 index 6f56985a7..000000000 --- a/apps/ops/ansible/test_runner.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# - -import unittest -import sys - -sys.path.insert(0, "../..") - -from ops.ansible.runner import AdHocRunner, CommandRunner -from ops.ansible.inventory import BaseInventory - - -class TestAdHocRunner(unittest.TestCase): - def setUp(self): - host_data = [ - { - "name": "testserver", - "ip": "192.168.244.185", - "port": 22, - "username": "root", - "password": "redhat", - }, - ] - inventory = BaseInventory(host_data) - self.runner = AdHocRunner(inventory) - - def test_run(self): - tasks = [ - {"action": {"module": "shell", "args": "ls"}, "name": "run_cmd"}, - {"action": {"module": "shell", "args": "whoami"}, "name": "run_whoami"}, - ] - ret = self.runner.run(tasks, "all") - print(ret.results_summary) - print(ret.results_raw) - - -class TestCommandRunner(unittest.TestCase): - def setUp(self): - host_data = [ - { - "name": "testserver", - "ip": "192.168.244.168", - "port": 22, - "username": "root", - "password": "redhat", - }, - ] - inventory = BaseInventory(host_data) - self.runner = CommandRunner(inventory) - - def test_execute(self): - res = self.runner.execute('ls', 'all') - print(res.results_command) - print(res.results_raw) - - -if __name__ == "__main__": - unittest.main() From 0fb4b52232993f9ecf948ce6c8956ee3abb4decf Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 8 Oct 2022 16:55:14 +0800 Subject: [PATCH 177/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20ansible=20?= =?UTF-8?q?=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/automation/base.py | 7 +- apps/assets/models/label.py | 4 - apps/audits/api.py | 110 +++--- apps/audits/filters.py | 39 +-- apps/audits/serializers.py | 79 +++-- apps/audits/urls/api_urls.py | 4 +- apps/jumpserver/settings/base.py | 1 + apps/ops/ansible/callback.py | 2 +- apps/ops/ansible/inventory.py | 10 +- apps/ops/ansible/runner.py | 8 + apps/ops/api/__init__.py | 1 - apps/ops/api/adhoc.py | 50 +-- apps/ops/api/command.py | 76 ---- apps/ops/inventory.py | 149 -------- .../ops/migrations/0024_auto_20221008_1514.py | 58 ++++ .../ops/migrations/0025_auto_20221008_1631.py | 72 ++++ apps/ops/mixin.py | 41 --- apps/ops/models/__init__.py | 1 - apps/ops/models/adhoc.py | 328 +----------------- apps/ops/models/base.py | 106 ++++++ apps/ops/models/command.py | 160 --------- apps/ops/models/playbook.py | 31 +- apps/ops/serializers/adhoc.py | 77 ++-- apps/ops/tasks.py | 2 +- apps/ops/urls/api_urls.py | 5 +- 25 files changed, 438 insertions(+), 983 deletions(-) delete mode 100644 apps/ops/api/command.py delete mode 100644 apps/ops/inventory.py create mode 100644 apps/ops/migrations/0024_auto_20221008_1514.py create mode 100644 apps/ops/migrations/0025_auto_20221008_1631.py create mode 100644 apps/ops/models/base.py delete mode 100644 apps/ops/models/command.py diff --git a/apps/assets/models/automation/base.py b/apps/assets/models/automation/base.py index 27c971e0d..a9b8ab087 100644 --- a/apps/assets/models/automation/base.py +++ b/apps/assets/models/automation/base.py @@ -4,15 +4,14 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.const.choices import Trigger -from common.mixins.models import CommonModelMixin from common.db.fields import EncryptJsonDictTextField -from orgs.mixins.models import OrgModelMixin +from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel from ops.mixin import PeriodTaskModelMixin from ops.tasks import execute_automation_strategy from ops.task_handlers import ExecutionManager -class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): +class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) nodes = models.ManyToManyField( 'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes") @@ -67,7 +66,7 @@ class AutomationStrategyExecution(OrgModelMixin): default=dict, blank=True, null=True, verbose_name=_('Automation snapshot') ) strategy = models.ForeignKey( - 'assets.models.automation.base.BaseAutomation', related_name='execution', on_delete=models.CASCADE, + 'BaseAutomation', related_name='execution', on_delete=models.CASCADE, verbose_name=_('Automation strategy') ) trigger = models.CharField( diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index f7820ccb1..937d0d95c 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -14,16 +14,12 @@ class Label(OrgModelMixin): ("S", _("System")), ("U", _("User")) ) - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_("Name")) value = models.CharField(max_length=128, verbose_name=_("Value")) category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) - date_created = models.DateTimeField( - auto_now_add=True, null=True, blank=True, verbose_name=_('Date created') - ) @classmethod def get_queryset_group_by_name(cls): diff --git a/apps/audits/api.py b/apps/audits/api.py index 6cb2e1283..a61694e85 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -11,16 +11,14 @@ from common.drf.filters import DatetimeRangeFilter from common.api import CommonGenericViewSet from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin from orgs.utils import current_org -from ops.models import CommandExecution +# from ops.models import CommandExecution from . import filters from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog -from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer -from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer +from .serializers import FTPLogSerializer, UserLoginLogSerializer +from .serializers import OperateLogSerializer, PasswordChangeLogSerializer -class FTPLogViewSet(CreateModelMixin, - ListModelMixin, - OrgGenericViewSet): +class FTPLogViewSet(CreateModelMixin, ListModelMixin, OrgGenericViewSet): model = FTPLog serializer_class = FTPLogSerializer extra_filter_backends = [DatetimeRangeFilter] @@ -98,53 +96,53 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): ) return queryset - -class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): - model = CommandExecution - serializer_class = CommandExecutionSerializer - extra_filter_backends = [DatetimeRangeFilter] - date_range_filter_fields = [ - ('date_start', ('date_from', 'date_to')) - ] - filterset_fields = [ - 'user__name', 'user__username', 'command', - 'account', 'is_finished' - ] - search_fields = [ - 'command', 'user__name', 'user__username', - 'account__username', - ] - ordering = ['-date_created'] - - def get_queryset(self): - queryset = super().get_queryset() - if getattr(self, 'swagger_fake_view', False): - return queryset.model.objects.none() - if current_org.is_root(): - return queryset - # queryset = queryset.filter(run_as__org_id=current_org.org_id()) - return queryset - - -class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): - serializer_class = CommandExecutionHostsRelationSerializer - m2m_field = CommandExecution.hosts.field - filterset_fields = [ - 'id', 'asset', 'commandexecution' - ] - search_fields = ('asset__name', ) - http_method_names = ['options', 'get'] - rbac_perms = { - 'GET': 'ops.view_commandexecution', - 'list': 'ops.view_commandexecution', - } - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.annotate( - asset_display=Concat( - F('asset__name'), Value('('), - F('asset__address'), Value(')') - ) - ) - return queryset +# Todo: 看看怎么搞 +# class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): +# model = CommandExecution +# serializer_class = CommandExecutionSerializer +# extra_filter_backends = [DatetimeRangeFilter] +# date_range_filter_fields = [ +# ('date_start', ('date_from', 'date_to')) +# ] +# filterset_fields = [ +# 'user__name', 'user__username', 'command', +# 'account', 'is_finished' +# ] +# search_fields = [ +# 'command', 'user__name', 'user__username', +# 'account__username', +# ] +# ordering = ['-date_created'] +# +# def get_queryset(self): +# queryset = super().get_queryset() +# if getattr(self, 'swagger_fake_view', False): +# return queryset.model.objects.none() +# if current_org.is_root(): +# return queryset +# # queryset = queryset.filter(run_as__org_id=current_org.org_id()) +# return queryset +# +# +# class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): +# serializer_class = CommandExecutionHostsRelationSerializer +# m2m_field = CommandExecution.hosts.field +# filterset_fields = [ +# 'id', 'asset', 'commandexecution' +# ] +# search_fields = ('asset__name', ) +# http_method_names = ['options', 'get'] +# rbac_perms = { +# 'GET': 'ops.view_commandexecution', +# 'list': 'ops.view_commandexecution', +# } +# +# def get_queryset(self): +# queryset = super().get_queryset() +# queryset = queryset.annotate( +# asset_display=Concat( +# F('asset__name'), Value('('), +# F('asset__address'), Value(')') +# ) +# ) +# return queryset diff --git a/apps/audits/filters.py b/apps/audits/filters.py index a6c44b5c5..c15c22b56 100644 --- a/apps/audits/filters.py +++ b/apps/audits/filters.py @@ -5,10 +5,9 @@ from rest_framework import filters from rest_framework.compat import coreapi, coreschema from orgs.utils import current_org -from ops.models import CommandExecution from common.drf.filters import BaseFilterSet -__all__ = ['CurrentOrgMembersFilter', 'CommandExecutionFilter'] +__all__ = ['CurrentOrgMembersFilter'] class CurrentOrgMembersFilter(filters.BaseFilterBackend): @@ -35,21 +34,21 @@ class CurrentOrgMembersFilter(filters.BaseFilterBackend): queryset = queryset.filter(user__in=self._get_user_list()) return queryset - -class CommandExecutionFilter(BaseFilterSet): - hostname_ip = CharFilter(method='filter_hostname_ip') - - class Meta: - model = CommandExecution.hosts.through - fields = ( - 'id', 'asset', 'commandexecution', 'hostname_ip' - ) - - def filter_hostname_ip(self, queryset, name, value): - queryset = queryset.annotate( - hostname_ip=Concat( - F('asset__hostname'), Value('('), - F('asset__address'), Value(')') - ) - ).filter(hostname_ip__icontains=value) - return queryset +# +# class CommandExecutionFilter(BaseFilterSet): +# hostname_ip = CharFilter(method='filter_hostname_ip') +# +# class Meta: +# model = CommandExecution.hosts.through +# fields = ( +# 'id', 'asset', 'commandexecution', 'hostname_ip' +# ) +# +# def filter_hostname_ip(self, queryset, name, value): +# queryset = queryset.annotate( +# hostname_ip=Concat( +# F('asset__hostname'), Value('('), +# F('asset__address'), Value(')') +# ) +# ).filter(hostname_ip__icontains=value) +# return queryset diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 8b9d28005..0f595be25 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -5,7 +5,6 @@ from rest_framework import serializers from common.drf.serializers import BulkSerializerMixin from terminal.models import Session -from ops.models import CommandExecution from . import models @@ -76,42 +75,42 @@ class SessionAuditSerializer(serializers.ModelSerializer): model = Session fields = '__all__' - -class CommandExecutionSerializer(serializers.ModelSerializer): - is_success = serializers.BooleanField(read_only=True, label=_('Is success')) - hosts_display = serializers.ListSerializer( - child=serializers.CharField(), source='hosts', read_only=True, label=_('Hosts display') - ) - - class Meta: - model = CommandExecution - fields_mini = ['id'] - fields_small = fields_mini + [ - 'command', 'is_finished', 'user', - 'date_start', 'result', 'is_success', 'org_id' - ] - fields = fields_small + ['hosts', 'hosts_display', 'user_display'] - extra_kwargs = { - 'result': {'label': _('Result')}, # model 上的方法,只能在这修改 - 'is_success': {'label': _('Is success')}, - 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改 - 'user': {'label': _('User')}, - 'user_display': {'label': _('User display')}, - } - - @classmethod - def setup_eager_loading(cls, queryset): - """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related('user', 'hosts') - return queryset - - -class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer): - asset_display = serializers.ReadOnlyField() - commandexecution_display = serializers.ReadOnlyField() - - class Meta: - model = CommandExecution.hosts.through - fields = [ - 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display' - ] +# +# class CommandExecutionSerializer(serializers.ModelSerializer): +# is_success = serializers.BooleanField(read_only=True, label=_('Is success')) +# hosts_display = serializers.ListSerializer( +# child=serializers.CharField(), source='hosts', read_only=True, label=_('Hosts display') +# ) +# +# class Meta: +# model = CommandExecution +# fields_mini = ['id'] +# fields_small = fields_mini + [ +# 'command', 'is_finished', 'user', +# 'date_start', 'result', 'is_success', 'org_id' +# ] +# fields = fields_small + ['hosts', 'hosts_display', 'user_display'] +# extra_kwargs = { +# 'result': {'label': _('Result')}, # model 上的方法,只能在这修改 +# 'is_success': {'label': _('Is success')}, +# 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改 +# 'user': {'label': _('User')}, +# 'user_display': {'label': _('User display')}, +# } +# +# @classmethod +# def setup_eager_loading(cls, queryset): +# """ Perform necessary eager loading of data. """ +# queryset = queryset.prefetch_related('user', 'hosts') +# return queryset +# +# +# class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer): +# asset_display = serializers.ReadOnlyField() +# commandexecution_display = serializers.ReadOnlyField() +# +# class Meta: +# model = CommandExecution.hosts.through +# fields = [ +# 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display' +# ] diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py index 7301b67fb..902c65fbf 100644 --- a/apps/audits/urls/api_urls.py +++ b/apps/audits/urls/api_urls.py @@ -15,8 +15,8 @@ router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log') router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log') router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log') router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log') -router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log') -router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation') +# router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log') +# router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation') urlpatterns = [ diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index d009238cd..8456d9fa0 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -16,6 +16,7 @@ VERSION = const.VERSION BASE_DIR = const.BASE_DIR PROJECT_DIR = const.PROJECT_DIR DATA_DIR = os.path.join(PROJECT_DIR, 'data') +ANSIBLE_DIR = os.path.join(DATA_DIR, 'ansible') CERTS_DIR = os.path.join(DATA_DIR, 'certs') # Quick-start development settings - unsuitable for production diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 8b6ad1f8f..59734b07d 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -15,7 +15,7 @@ class DefaultCallback: dark={}, skipped=[], ) - self.status = 'starting' + self.status = 'running' self.finished = False def is_success(self): diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 2382525ed..4da027696 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -3,21 +3,19 @@ from collections import defaultdict import json -__all__ = [ - 'JMSInventory', -] +__all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, assets, account_username=None, account_policy='smart', host_var_callback=None): + def __init__(self, assets, account='', account_policy='smart', host_var_callback=None): """ :param assets: - :param account_username: account username name if not set use account_policy + :param account: account username name if not set use account_policy :param account_policy: :param host_var_callback: """ self.assets = self.clean_assets(assets) - self.account_username = account_username + self.account_username = account self.account_policy = account_policy self.host_var_callback = host_var_callback diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index 6c339eba6..a420fb8a4 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -68,3 +68,11 @@ class PlaybookRunner: **kwargs ) return self.cb + + +class CommandRunner(AdHocRunner): + def __init__(self, inventory, command, pattern='*', project_dir='/tmp/'): + super().__init__(inventory, 'shell', command, pattern, project_dir) + + def run(self, verbosity=0, **kwargs): + return super().run(verbosity, **kwargs) diff --git a/apps/ops/api/__init__.py b/apps/ops/api/__init__.py index e59889cd2..8eb5356e4 100644 --- a/apps/ops/api/__init__.py +++ b/apps/ops/api/__init__.py @@ -2,4 +2,3 @@ # from .adhoc import * from .celery import * -from .command import * diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 0cc7b6d55..8644ac5d2 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -6,52 +6,18 @@ from rest_framework import viewsets, generics from rest_framework.views import Response from common.drf.serializers import CeleryTaskSerializer -from ..models import Task, AdHoc, AdHocExecution +from ..models import AdHoc, AdHocExecution from ..serializers import ( - TaskSerializer, AdHocSerializer, AdHocExecutionSerializer, - TaskDetailSerializer, AdHocDetailSerializer, ) -from ..tasks import run_ansible_task -from orgs.mixins.api import OrgBulkModelViewSet __all__ = [ - 'TaskViewSet', 'TaskRun', 'AdHocViewSet', 'AdHocRunHistoryViewSet' + 'AdHocViewSet', 'AdHocExecutionViewSet' ] -class TaskViewSet(OrgBulkModelViewSet): - model = Task - filterset_fields = ("name",) - search_fields = filterset_fields - serializer_class = TaskSerializer - - def get_serializer_class(self): - if self.action == 'retrieve': - return TaskDetailSerializer - return super().get_serializer_class() - - def get_queryset(self): - queryset = super().get_queryset() - queryset = queryset.select_related('latest_execution') - return queryset - - -class TaskRun(generics.RetrieveAPIView): - queryset = Task.objects.all() - serializer_class = CeleryTaskSerializer - rbac_perms = { - 'retrieve': 'ops.add_adhoc' - } - - def retrieve(self, request, *args, **kwargs): - task = self.get_object() - t = run_ansible_task.delay(str(task.id)) - return Response({"task": t.id}) - - class AdHocViewSet(viewsets.ModelViewSet): queryset = AdHoc.objects.all() serializer_class = AdHocSerializer @@ -61,23 +27,17 @@ class AdHocViewSet(viewsets.ModelViewSet): return AdHocDetailSerializer return super().get_serializer_class() - def get_queryset(self): - task_id = self.request.query_params.get('task') - if task_id: - task = get_object_or_404(Task, id=task_id) - self.queryset = self.queryset.filter(task=task) - return self.queryset - -class AdHocRunHistoryViewSet(viewsets.ModelViewSet): +class AdHocExecutionViewSet(viewsets.ModelViewSet): queryset = AdHocExecution.objects.all() serializer_class = AdHocExecutionSerializer def get_queryset(self): task_id = self.request.query_params.get('task') adhoc_id = self.request.query_params.get('adhoc') + if task_id: - task = get_object_or_404(Task, id=task_id) + task = get_object_or_404(AdHoc, id=task_id) adhocs = task.adhoc.all() self.queryset = self.queryset.filter(adhoc__in=adhocs) diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py deleted file mode 100644 index 1cf7950a6..000000000 --- a/apps/ops/api/command.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# -from rest_framework import viewsets -from rest_framework.exceptions import ValidationError -from django.db import transaction -from django.db.models import Q -from django.utils.translation import ugettext as _ -from django.conf import settings - -from assets.models import Asset, Node -from orgs.mixins.api import RootOrgViewMixin -from rbac.permissions import RBACPermission -from ..models import CommandExecution -from ..serializers import CommandExecutionSerializer -from ..tasks import run_command_execution - - -class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet): - serializer_class = CommandExecutionSerializer - permission_classes = (RBACPermission,) - - def get_queryset(self): - return CommandExecution.objects.filter(user_id=str(self.request.user.id)) - - def check_hosts(self, serializer): - data = serializer.validated_data - assets = data["hosts"] - user = self.request.user - - # TOdo: - # Q(granted_by_permissions__system_users__id=system_user.id) & - q = ( - Q(granted_by_permissions__users=user) | - Q(granted_by_permissions__user_groups__users=user) - ) - - permed_assets = set() - permed_assets.update(Asset.objects.filter(id__in=[a.id for a in assets]).filter(q).distinct()) - node_keys = Node.objects.filter(q).distinct().values_list('key', flat=True) - - nodes_assets_q = Q() - for _key in node_keys: - nodes_assets_q |= Q(nodes__key__startswith=f'{_key}:') - nodes_assets_q |= Q(nodes__key=_key) - - permed_assets.update( - Asset.objects.filter( - id__in=[a.id for a in assets] - ).filter( - nodes_assets_q - ).distinct() - ) - - invalid_assets = set(assets) - set(permed_assets) - if invalid_assets: - msg = _("Not has host {} permission").format( - [str(a.id) for a in invalid_assets] - ) - raise ValidationError({"hosts": msg}) - - def check_permissions(self, request): - if not settings.SECURITY_COMMAND_EXECUTION: - return self.permission_denied(request, "Command execution disabled") - return super().check_permissions(request) - - def perform_create(self, serializer): - self.check_hosts(serializer) - instance = serializer.save() - instance.user = self.request.user - instance.save() - cols = self.request.query_params.get("cols", '80') - rows = self.request.query_params.get("rows", '24') - transaction.on_commit(lambda: run_command_execution.apply_async( - args=(instance.id,), kwargs={"cols": cols, "rows": rows}, - task_id=str(instance.id) - )) diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py deleted file mode 100644 index d6943f5c5..000000000 --- a/apps/ops/inventory.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.conf import settings -from .ansible.inventory import BaseInventory - -from common.utils import get_logger - -__all__ = [ - 'JMSInventory', 'JMSCustomInventory', -] - - -logger = get_logger(__file__) - - -class JMSBaseInventory(BaseInventory): - def convert_to_ansible(self, asset, run_as_admin=False): - info = { - 'id': asset.id, - 'name': asset.name, - 'ip': asset.address, - 'port': asset.ssh_port, - 'vars': dict(), - 'groups': [], - } - if asset.domain and asset.domain.has_gateway(): - info["vars"].update(self.make_proxy_command(asset)) - if run_as_admin: - info.update(asset.get_auth_info(with_become=True)) - if asset.is_windows(): - info["vars"].update({ - "ansible_connection": "ssh", - "ansible_shell_type": settings.WINDOWS_SSH_DEFAULT_SHELL, - }) - for label in asset.labels.all(): - info["vars"].update({ - label.name: label.value - }) - if asset.domain: - info["vars"].update({ - "domain": asset.domain.name, - }) - return info - - @staticmethod - def make_proxy_command(asset): - gateway = asset.domain.random_gateway() - proxy_command_list = [ - "ssh", "-o", "Port={}".format(gateway.port), - "-o", "StrictHostKeyChecking=no", - "{}@{}".format(gateway.username, gateway.address), - "-W", "%h:%p", "-q", - ] - - if gateway.password: - proxy_command_list.insert( - 0, "sshpass -p '{}'".format(gateway.password) - ) - if gateway.private_key: - proxy_command_list.append("-i {}".format(gateway.private_key_file)) - - proxy_command = "'-o ProxyCommand={}'".format( - " ".join(proxy_command_list) - ) - return {"ansible_ssh_common_args": proxy_command} - - -class JMSInventory(JMSBaseInventory): - """ - JMS Inventory is the inventory with jumpserver assets, so you can - write you own inventory, construct you inventory, - user_info is obtained from admin_user or asset_user - """ - def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None, system_user=None): - """ - :param assets: assets - :param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同 - :param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username) - :param become_info: 是否become成某个用户去执行 - """ - self.assets = assets - self.using_admin = run_as_admin - self.run_as = run_as - self.system_user = system_user - self.become_info = become_info - - host_list = [] - - for asset in assets: - host = self.convert_to_ansible(asset, run_as_admin=run_as_admin) - if run_as is not None: - run_user_info = self.get_run_user_info(host) - host.update(run_user_info) - if become_info and asset.is_unixlike(): - host.update(become_info) - host_list.append(host) - - super().__init__(host_list=host_list) - - def get_run_user_info(self, host): - if not self.run_as and not self.system_user: - return {} - - asset_id = host.get('id', '') - asset = self.assets.filter(id=asset_id).first() - if not asset: - logger.error('Host not found: ', asset_id) - return {} - - if self.system_user: - self.system_user.load_asset_special_auth(asset=asset, username=self.run_as) - return self.system_user._to_secret_json() - else: - return {} - - -class JMSCustomInventory(JMSBaseInventory): - """ - JMS Custom Inventory is the inventory with jumpserver assets, - user_info is obtained from custom parameter - """ - - def __init__(self, assets, username, password=None, public_key=None, private_key=None): - """ - """ - self.assets = assets - self.username = username - self.password = password - self.public_key = public_key - self.private_key = private_key - - host_list = [] - - for asset in assets: - host = self.convert_to_ansible(asset) - run_user_info = self.get_run_user_info() - host.update(run_user_info) - host_list.append(host) - - super().__init__(host_list=host_list) - - def get_run_user_info(self): - return { - 'username': self.username, - 'password': self.password, - 'public_key': self.public_key, - 'private_key': self.private_key - } diff --git a/apps/ops/migrations/0024_auto_20221008_1514.py b/apps/ops/migrations/0024_auto_20221008_1514.py new file mode 100644 index 000000000..e208af96e --- /dev/null +++ b/apps/ops/migrations/0024_auto_20221008_1514.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.14 on 2022-10-08 07:19 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0106_auto_20220916_1556'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ops', '0023_auto_20220929_2025'), + ] + + operations = [ + migrations.RemoveField( + model_name='adhocexecution', + name='adhoc', + ), + migrations.RemoveField( + model_name='adhocexecution', + name='task', + ), + migrations.RemoveField( + model_name='commandexecution', + name='hosts', + ), + migrations.RemoveField( + model_name='commandexecution', + name='user', + ), + migrations.AlterUniqueTogether( + name='task', + unique_together=None, + ), + migrations.RemoveField( + model_name='task', + name='latest_adhoc', + ), + migrations.RemoveField( + model_name='task', + name='latest_execution', + ), + migrations.DeleteModel( + name='AdHoc', + ), + migrations.DeleteModel( + name='AdHocExecution', + ), + migrations.DeleteModel( + name='CommandExecution', + ), + migrations.DeleteModel( + name='Task', + ), + ] diff --git a/apps/ops/migrations/0025_auto_20221008_1631.py b/apps/ops/migrations/0025_auto_20221008_1631.py new file mode 100644 index 000000000..7e814c3d1 --- /dev/null +++ b/apps/ops/migrations/0025_auto_20221008_1631.py @@ -0,0 +1,72 @@ +# Generated by Django 3.2.14 on 2022-10-08 08:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0106_auto_20220916_1556'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ops', '0024_auto_20221008_1514'), + ] + + operations = [ + migrations.CreateModel( + name='AdHoc', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('is_periodic', models.BooleanField(default=False)), + ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), + ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), + ('account', models.CharField(default='root', max_length=128, verbose_name='Account')), + ('account_policy', models.CharField(default='root', max_length=128, verbose_name='Account policy')), + ('date_last_run', models.DateTimeField(null=True, verbose_name='Date last run')), + ('pattern', models.CharField(default='all', max_length=1024, verbose_name='Pattern')), + ('module', models.CharField(default='shell', max_length=128, verbose_name='Module')), + ('args', models.CharField(default='', max_length=1024, verbose_name='Args')), + ('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='AdHocExecution', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(default='running', max_length=16, verbose_name='Status')), + ('result', models.JSONField(blank=True, null=True, verbose_name='Result')), + ('summary', models.JSONField(default=dict, verbose_name='Summary')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True)), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='ops.adhoc', verbose_name='Adhoc')), + ], + options={ + 'verbose_name': 'AdHoc execution', + 'db_table': 'ops_adhoc_execution', + 'get_latest_by': 'date_start', + }, + ), + migrations.AddField( + model_name='adhoc', + name='last_execution', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.adhocexecution', verbose_name='Last execution'), + ), + migrations.AddField( + model_name='adhoc', + name='owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator'), + ), + ] diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py index e64a763fc..4d2fd52a7 100644 --- a/apps/ops/mixin.py +++ b/apps/ops/mixin.py @@ -14,12 +14,10 @@ from .celery.utils import ( __all__ = [ 'PeriodTaskModelMixin', 'PeriodTaskSerializerMixin', - 'PeriodTaskFormMixin', ] class PeriodTaskModelMixin(models.Model): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField( max_length=128, unique=False, verbose_name=_("Name") ) @@ -140,42 +138,3 @@ class PeriodTaskSerializerMixin(serializers.Serializer): msg = _("Require periodic or regularly perform setting") raise serializers.ValidationError(msg) return ok - - -class PeriodTaskFormMixin(forms.Form): - is_periodic = forms.BooleanField( - initial=True, required=False, label=_('Periodic perform') - ) - crontab = forms.CharField( - max_length=128, required=False, label=_('Regularly perform'), - help_text=_("eg: Every Sunday 03:05 run <5 3 * * 0>
" - "Tips: " - "Using 5 digits linux crontab expressions " - " " - "(Online tools)
" - "Note: " - "If both Regularly perform and Cycle perform are set, " - "give priority to Regularly perform"), - ) - interval = forms.IntegerField( - required=False, initial=24, - help_text=_('Unit: hour'), label=_("Cycle perform"), - ) - - def get_initial_for_field(self, field, field_name): - """ - Return initial data for field on form. Use initial data from the form - or the field, in that order. Evaluate callable values. - """ - if field_name not in ['is_periodic', 'crontab', 'interval']: - return super().get_initial_for_field(field, field_name) - instance = getattr(self, 'instance', None) - if instance is None: - return super().get_initial_for_field(field, field_name) - init_attr_name = field_name + '_initial' - value = getattr(self, init_attr_name, None) - if value is None: - return super().get_initial_for_field(field, field_name) - return value - - diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index 0a9ed463c..fcd8bd8f7 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -3,4 +3,3 @@ from .adhoc import * from .celery import * -from .command import * diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 1d2920206..565df9f3e 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -1,337 +1,41 @@ # ~*~ coding: utf-8 ~*~ -import uuid -import os -import time -import datetime -from celery import current_task from django.db import models -from django.conf import settings -from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from common.utils import get_logger, lazyproperty -from common.utils.translate import translate_value -from common.db.fields import ( - JsonListTextField, JsonDictCharField, EncryptJsonDictCharField, - JsonDictTextField, -) -from orgs.mixins.models import OrgModelMixin -from ..ansible import AdHocRunner, AnsibleError -from ..inventory import JMSInventory -from ..mixin import PeriodTaskModelMixin +from common.utils import get_logger +from .base import BaseAnsibleTask, BaseAnsibleExecution +from ..ansible import AdHocRunner -__all__ = ["Task", "AdHoc", "AdHocExecution"] +__all__ = ["AdHoc", "AdHocExecution"] logger = get_logger(__file__) -class Task(PeriodTaskModelMixin, OrgModelMixin): - """ - This task is different ansible task, Task like 'push system user', 'get asset info' .. - One task can have some versions of adhoc, run a task only run the latest version adhoc - """ - callback = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Callback")) # Callback must be a registered celery task - is_deleted = models.BooleanField(default=False) - comment = models.TextField(blank=True, verbose_name=_("Comment")) - date_created = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_("Date created")) - date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) - latest_adhoc = models.ForeignKey('ops.AdHoc', on_delete=models.SET_NULL, - null=True, related_name='task_latest') - latest_execution = models.ForeignKey('ops.AdHocExecution', on_delete=models.SET_NULL, null=True, related_name='task_latest') - total_run_amount = models.IntegerField(default=0) - success_run_amount = models.IntegerField(default=0) - _ignore_auto_created_by = True - - @property - def short_id(self): - return str(self.id).split('-')[-1] - - @lazyproperty - def versions(self): - return self.adhoc.all().count() - - @property - def is_success(self): - if self.latest_execution: - return self.latest_execution.is_success - else: - return False - - @lazyproperty - def display_name(self): - value = translate_value(self.name) - return value - - @property - def timedelta(self): - if self.latest_execution: - return self.latest_execution.timedelta - else: - return 0 - - @property - def date_start(self): - if self.latest_execution: - return self.latest_execution.date_start - else: - return None - - @property - def assets_amount(self): - if self.latest_execution: - return self.latest_execution.hosts_amount - return 0 - - def get_latest_adhoc(self): - if self.latest_adhoc: - return self.latest_adhoc - try: - adhoc = self.adhoc.all().latest() - self.latest_adhoc = adhoc - self.save() - return adhoc - except AdHoc.DoesNotExist: - return None - - @property - def history_summary(self): - total = self.total_run_amount - success = self.success_run_amount - failed = total - success - return {'total': total, 'success': success, 'failed': failed} - - def get_run_execution(self): - return self.execution.all() - - def run(self): - latest_adhoc = self.get_latest_adhoc() - if latest_adhoc: - return latest_adhoc.run() - else: - return {'error': 'No adhoc'} - - @property - def period_key(self): - return self.__str__() - - def get_register_task(self): - from ..tasks import run_ansible_task - name = self.__str__() - task = run_ansible_task.name - args = (str(self.id),) - kwargs = {"callback": self.callback} - return name, task, args, kwargs +class AdHoc(BaseAnsibleTask): + pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all') + module = models.CharField(max_length=128, default='shell', verbose_name=_('Module')) + args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) + last_execution = models.ForeignKey('AdHocExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True, blank=True) def __str__(self): - return self.name + '@' + str(self.org_id) - - class Meta: - db_table = 'ops_task' - unique_together = ('name', 'org_id') - ordering = ('-date_updated',) - verbose_name = _("Task") - get_latest_by = 'date_created' - permissions = [ - ('view_taskmonitor', _('Can view task monitor')) - ] + return "{}: {}".format(self.module, self.args) -class AdHoc(OrgModelMixin): - """ - task: A task reference - _tasks: [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ] - _options: ansible options, more see ops.ansible.runner.Options - run_as_admin: if true, then need get every host admin user run it, because every host may be have different admin user, so we choise host level - run_as: username(Add the uniform AssetUserManager and change it to username) - _become: May be using become [sudo, su] options. {method: "sudo", user: "user", pass: "pass"] - pattern: Even if we set _hosts, We only use that to make inventory, We also can set `patter` to run task on match hosts - """ - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - task = models.ForeignKey(Task, related_name='adhoc', on_delete=models.CASCADE) - tasks = JsonListTextField(verbose_name=_('Tasks')) - pattern = models.CharField(max_length=64, default='{}', verbose_name=_('Pattern')) - options = JsonDictCharField(max_length=1024, default='', verbose_name=_('Options')) - hosts = models.ManyToManyField('assets.Asset', verbose_name=_("Host")) - run_as_admin = models.BooleanField(default=False, verbose_name=_('Run as admin')) - run_as = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Username')) - become = EncryptJsonDictCharField(max_length=1024, default='', blank=True, null=True, verbose_name=_("Become")) - created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by')) - date_created = models.DateTimeField(auto_now_add=True, db_index=True) - - @lazyproperty - def run_times(self): - return self.execution.count() - - @property - def inventory(self): - if self.become: - become_info = { - 'become': { - self.become - } - } - else: - become_info = None - - inventory = JMSInventory( - self.hosts.all(), run_as_admin=self.run_as_admin, - run_as=self.run_as, become_info=become_info, system_user=self.run_system_user - ) - return inventory - - @property - def become_display(self): - if self.become: - return self.become.get("user", "") - return "" - - def run(self): - try: - celery_task_id = current_task.request.id - except AttributeError: - celery_task_id = None - - execution = AdHocExecution( - celery_task_id=celery_task_id, - adhoc=self, task=self.task, - task_display=str(self.task)[:128], - date_start=timezone.now(), - hosts_amount=self.hosts.count(), - ) - execution.save() - return execution.start() - - @property - def short_id(self): - return str(self.id).split('-')[-1] - - @property - def latest_execution(self): - try: - return self.execution.all().latest() - except AdHocExecution.DoesNotExist: - return None - - def save(self, **kwargs): - instance = super().save(**kwargs) - self.task.latest_adhoc = instance - self.task.save() - return instance - - def __str__(self): - return "{} of {}".format(self.task.name, self.short_id) - - def same_with(self, other): - if not isinstance(other, self.__class__): - return False - fields_check = [] - for field in self.__class__._meta.fields: - if field.name not in ['id', 'date_created']: - fields_check.append(field) - for field in fields_check: - if getattr(self, field.name) != getattr(other, field.name): - return False - return True - - class Meta: - db_table = "ops_adhoc" - get_latest_by = 'date_created' - verbose_name = _('AdHoc') - - -class AdHocExecution(OrgModelMixin): +class AdHocExecution(BaseAnsibleExecution): """ AdHoc running history. """ - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - task = models.ForeignKey(Task, related_name='execution', on_delete=models.SET_NULL, null=True) - task_display = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Task display")) - celery_task_id = models.UUIDField(default=None, null=True) - hosts_amount = models.IntegerField(default=0, verbose_name=_("Host amount")) - adhoc = models.ForeignKey(AdHoc, related_name='execution', on_delete=models.SET_NULL, null=True) - date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time')) - date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time')) - timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True) - is_finished = models.BooleanField(default=False, verbose_name=_('Is finished')) - is_success = models.BooleanField(default=False, verbose_name=_('Is success')) - result = JsonDictTextField(blank=True, null=True, verbose_name=_('Adhoc raw result')) - summary = JsonDictTextField(blank=True, null=True, verbose_name=_('Adhoc result summary')) + task = models.ForeignKey('AdHoc', verbose_name=_("Adhoc"), related_name='executions', on_delete=models.CASCADE) - @property - def short_id(self): - return str(self.id).split('-')[-1] - - @property - def adhoc_short_id(self): - return str(self.adhoc_id).split('-')[-1] - - @property - def log_path(self): - dt = datetime.datetime.now().strftime('%Y-%m-%d') - log_dir = os.path.join(settings.PROJECT_DIR, 'data', 'ansible', dt) - if not os.path.exists(log_dir): - os.makedirs(log_dir) - return os.path.join(log_dir, str(self.id) + '.log') - - def start_runner(self): - runner = AdHocRunner(self.adhoc.inventory, options=self.adhoc.options) - try: - result = runner.run( - self.adhoc.tasks, - self.adhoc.pattern, - self.task.name, - execution_id=self.id - ) - return result.results_raw, result.results_summary - except AnsibleError as e: - logger.warn("Failed run adhoc {}, {}".format(self.task.name, e)) - return {}, {} - - def start(self): - self.task.latest_execution = self - self.task.save() - time_start = time.time() - summary = {} - raw = '' - - try: - raw, summary = self.start_runner() - except Exception as e: - logger.error(e, exc_info=True) - raw = {"dark": {"all": str(e)}, "contacted": []} - finally: - self.clean_up(summary, time_start) - return raw, summary - - def clean_up(self, summary, time_start): - is_success = summary.get('success', False) - task = Task.objects.get(id=self.task_id) - task.total_run_amount = models.F('total_run_amount') + 1 - if is_success: - task.success_run_amount = models.F('success_run_amount') + 1 - task.save() - AdHocExecution.objects.filter(id=self.id).update( - is_finished=True, - is_success=is_success, - date_finished=timezone.now(), - timedelta=time.time() - time_start, - summary=summary + def get_runner(self): + return AdHocRunner( + self.task.inventory, self.task.module, self.task.args, + pattern=self.task.pattern, project_dir=self.private_dir ) - @property - def success_hosts(self): - return self.summary.get('contacted', []) - - @property - def failed_hosts(self): - return self.summary.get('dark', {}) - - def __str__(self): - return self.short_id - class Meta: db_table = "ops_adhoc_execution" get_latest_by = 'date_start' diff --git a/apps/ops/models/base.py b/apps/ops/models/base.py new file mode 100644 index 000000000..2992173c3 --- /dev/null +++ b/apps/ops/models/base.py @@ -0,0 +1,106 @@ +import os.path +import uuid + +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from django.conf import settings + +from orgs.mixins.models import JMSOrgBaseModel +from ..ansible.inventory import JMSInventory +from ..mixin import PeriodTaskModelMixin + + +class BaseAnsibleTask(PeriodTaskModelMixin, JMSOrgBaseModel): + owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) + account = models.CharField(max_length=128, default='root', verbose_name=_('Account')) + account_policy = models.CharField(max_length=128, default='root', verbose_name=_('Account policy')) + last_execution = models.ForeignKey('BaseAnsibleExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True) + date_last_run = models.DateTimeField(null=True, verbose_name=_('Date last run')) + + class Meta: + abstract = True + + @property + def inventory(self): + inv = JMSInventory(self.assets.all(), self.account, self.account_policy) + return inv.generate() + + def get_register_task(self): + raise NotImplemented + + def to_json(self): + raise NotImplemented + + +class BaseAnsibleExecution(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4) + status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') + task = models.ForeignKey(BaseAnsibleTask, on_delete=models.CASCADE, null=True) + result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) + summary = models.JSONField(default=dict, verbose_name=_('Summary')) + creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) + date_finished = models.DateTimeField(null=True) + + class Meta: + abstract = True + ordering = ["-date_start"] + + def __str__(self): + return str(self.id) + + def private_dir(self): + uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id + return os.path.join(settings.ANSIBLE_DIR, self.task.name, uniq) + + def get_runner(self): + raise NotImplemented + + def update_task(self): + self.task.last_execution = self + self.task.date_last_run = timezone.now() + self.task.save(update_fields=['last_execution', 'date_last_run']) + + def start(self, **kwargs): + runner = self.get_runner() + try: + cb = runner.run(**kwargs) + self.status = cb.status + self.summary = cb.summary + self.result = cb.result + self.date_finished = timezone.now() + except Exception as e: + self.status = 'failed' + self.summary = {'error': str(e)} + finally: + self.save() + self.update_task() + + @property + def is_finished(self): + return self.status in ['succeeded', 'failed'] + + @property + def is_success(self): + return self.status == 'succeeded' + + @property + def time_cost(self): + if self.date_finished and self.date_start: + return (self.date_finished - self.date_start).total_seconds() + return None + + @property + def short_id(self): + return str(self.id).split('-')[-1] + + @property + def timedelta(self): + if self.date_start and self.date_finished: + return self.date_finished - self.date_start + return None + + diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py deleted file mode 100644 index cb6023564..000000000 --- a/apps/ops/models/command.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -# -import uuid -import json - -from celery.exceptions import SoftTimeLimitExceeded -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ugettext -from django.db import models - -from terminal.notifications import CommandExecutionAlert -from assets.models import Asset -from common.utils import lazyproperty -from orgs.models import Organization -from orgs.mixins.models import OrgModelMixin -from orgs.utils import tmp_to_org -from ..ansible.runner import CommandRunner -from ..inventory import JMSInventory - - -class CommandExecution(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - hosts = models.ManyToManyField('assets.Asset') - account = models.CharField(max_length=128, default='', verbose_name=_('account')) - command = models.TextField(verbose_name=_("Command")) - _result = models.TextField(blank=True, null=True, verbose_name=_('Result')) - user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True) - is_finished = models.BooleanField(default=False, verbose_name=_('Is finished')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) - date_start = models.DateTimeField(null=True, verbose_name=_('Date start')) - date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished')) - - def __str__(self): - return self.command[:10] - - def save(self, *args, **kwargs): - with tmp_to_org(self.run_as.org_id): - super().save(*args, **kwargs) - - @property - def inventory(self): - if self.run_as.username_same_with_user: - username = self.user.username - else: - username = self.run_as.username - inv = JMSInventory(self.allow_assets, run_as=username, system_user=self.run_as) - return inv - - @lazyproperty - def user_display(self): - return str(self.user) - - @lazyproperty - def hosts_display(self): - return ','.join(self.hosts.all().values_list('name', flat=True)) - - @property - def result(self): - if self._result: - return json.loads(self._result) - else: - return {} - - @result.setter - def result(self, item): - self._result = json.dumps(item) - - @property - def is_success(self): - if 'error' in self.result: - return False - return True - - def get_hosts_names(self): - return ','.join(self.hosts.all().values_list('name', flat=True)) - - def cmd_filter_rules(self, asset_id=None): - from assets.models import CommandFilterRule - user_id = self.user.id - system_user_id = self.run_as.id - rules = CommandFilterRule.get_queryset( - user_id=user_id, - system_user_id=system_user_id, - asset_id=asset_id, - ) - return rules - - def is_command_can_run(self, command, asset_id=None): - for rule in self.cmd_filter_rules(asset_id=asset_id): - action, matched_cmd = rule.match(command) - if action == rule.ActionChoices.allow: - return True, None - elif action == rule.ActionChoices.deny: - return False, matched_cmd - return True, None - - @property - def allow_assets(self): - allow_asset_ids = [] - for asset in self.hosts.all(): - ok, __ = self.is_command_can_run(self.command, asset_id=asset.id) - if ok: - allow_asset_ids.append(asset.id) - allow_assets = Asset.objects.filter(id__in=allow_asset_ids) - return allow_assets - - def run(self): - print('-' * 10 + ' ' + ugettext('Task start') + ' ' + '-' * 10) - org = Organization.get_instance(self.run_as.org_id) - org.change_to() - self.date_start = timezone.now() - ok, msg = self.is_command_can_run(self.command) - if ok: - allow_assets = self.allow_assets - deny_assets = set(list(self.hosts.all())) - set(list(allow_assets)) - for asset in deny_assets: - print(f'资产{asset}: 命令{self.command}不允许执行') - if not allow_assets: - self.result = { - "error": 'There are currently no assets that can be executed' - } - self.save() - return self.result - runner = CommandRunner(self.inventory) - try: - host = allow_assets.first() - if host and host.is_windows(): - shell = 'win_shell' - elif host and host.is_unixlike(): - shell = 'shell' - else: - shell = 'raw' - result = runner.execute(self.command, 'all', module=shell) - self.result = result.results_command - except SoftTimeLimitExceeded as e: - print("Run timeout than 60s") - self.result = {"error": str(e)} - except Exception as e: - print("Error occur: {}".format(e)) - self.result = {"error": str(e)} - else: - msg = _("Command `{}` is forbidden ........").format(self.command) - print('\033[31m' + msg + '\033[0m') - CommandExecutionAlert({ - 'input': self.command, - 'assets': self.hosts.all(), - 'user': str(self.user), - 'risk_level': 5, - }).publish_async() - self.result = {"error": msg} - self.org_id = self.run_as.org_id - self.is_finished = True - self.date_finished = timezone.now() - self.save() - print('-' * 10 + ' ' + ugettext('Task end') + ' ' + '-' * 10) - return self.result - - class Meta: - verbose_name = _("Command execution") diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index aaec7a4ef..aec59bfb0 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -2,15 +2,34 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from orgs.mixins.models import JMSOrgBaseModel -from ..mixin import PeriodTaskModelMixin +from .base import BaseAnsibleExecution, BaseAnsibleTask -class PlaybookTask(PeriodTaskModelMixin, JMSOrgBaseModel): - assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) - account = models.CharField(max_length=128, default='root', verbose_name=_('Account')) - playbook = models.FilePathField(max_length=1024, verbose_name=_("Playbook")) - owner = models.CharField(max_length=1024, verbose_name=_("Owner")) +class PlaybookTemplate(JMSOrgBaseModel): + name = models.CharField(max_length=128, verbose_name=_("Name")) + path = models.FilePathField(verbose_name=_("Path")) + comment = models.TextField(verbose_name=_("Comment"), blank=True) + + def __str__(self): + return self.name + + class Meta: + ordering = ['name'] + verbose_name = _("Playbook template") + unique_together = [('org_id', 'name')] + + +class Playbook(BaseAnsibleTask): + path = models.FilePathField(max_length=1024, verbose_name=_("Playbook")) + owner = models.ForeignKey('users.User', verbose_name=_("Owner"), on_delete=models.SET_NULL, null=True) comment = models.TextField(blank=True, verbose_name=_("Comment")) + template = models.ForeignKey('PlaybookTemplate', verbose_name=_("Template"), on_delete=models.SET_NULL, null=True) + last_execution = models.ForeignKey('PlaybookExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True, blank=True) def get_register_task(self): pass + + +class PlaybookExecution(BaseAnsibleExecution): + task = models.ForeignKey('Playbook', verbose_name=_("Task"), on_delete=models.CASCADE) + path = models.FilePathField(max_length=1024, verbose_name=_("Run dir")) diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 50b1faeba..b6522b85f 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -3,8 +3,7 @@ from __future__ import unicode_literals from rest_framework import serializers from django.shortcuts import reverse -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from ..models import Task, AdHoc, AdHocExecution, CommandExecution +from ..models import AdHoc, AdHocExecution class AdHocExecutionSerializer(serializers.ModelSerializer): @@ -50,36 +49,6 @@ class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer): ] -class TaskSerializer(BulkOrgResourceModelSerializer): - summary = serializers.ReadOnlyField(source='history_summary') - latest_execution = AdHocExecutionExcludeResultSerializer(read_only=True) - - class Meta: - model = Task - fields_mini = ['id', 'name', 'display_name'] - fields_small = fields_mini + [ - 'interval', 'crontab', - 'is_periodic', 'is_deleted', - 'date_created', 'date_updated', - 'comment', - ] - fields_fk = ['latest_execution'] - fields_custom = ['summary'] - fields = fields_small + fields_fk + fields_custom - read_only_fields = [ - 'is_deleted', 'date_created', 'date_updated', - 'latest_adhoc', 'latest_execution', 'total_run_amount', - 'success_run_amount', 'summary', - ] - - -class TaskDetailSerializer(TaskSerializer): - contents = serializers.ListField(source='latest_adhoc.tasks') - - class Meta(TaskSerializer.Meta): - fields = TaskSerializer.Meta.fields + ['contents'] - - class AdHocSerializer(serializers.ModelSerializer): become_display = serializers.ReadOnlyField() tasks = serializers.ListField() @@ -127,26 +96,26 @@ class AdHocDetailSerializer(AdHocSerializer): ] -class CommandExecutionSerializer(serializers.ModelSerializer): - result = serializers.JSONField(read_only=True) - log_url = serializers.SerializerMethodField() - - class Meta: - model = CommandExecution - fields_mini = ['id'] - fields_small = fields_mini + [ - 'command', 'result', 'log_url', - 'is_finished', 'date_created', 'date_finished' - ] - fields_m2m = ['hosts'] - fields = fields_small + fields_m2m - read_only_fields = [ - 'result', 'is_finished', 'log_url', 'date_created', - 'date_finished' - ] - ref_name = 'OpsCommandExecution' - - @staticmethod - def get_log_url(obj): - return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id}) +# class CommandExecutionSerializer(serializers.ModelSerializer): +# result = serializers.JSONField(read_only=True) +# log_url = serializers.SerializerMethodField() +# +# class Meta: +# model = CommandExecution +# fields_mini = ['id'] +# fields_small = fields_mini + [ +# 'command', 'result', 'log_url', +# 'is_finished', 'date_created', 'date_finished' +# ] +# fields_m2m = ['hosts'] +# fields = fields_small + fields_m2m +# read_only_fields = [ +# 'result', 'is_finished', 'log_url', 'date_created', +# 'date_finished' +# ] +# ref_name = 'OpsCommandExecution' +# +# @staticmethod +# def get_log_url(obj): +# return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id}) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index cb21b5c3d..0ef430d7a 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -20,7 +20,7 @@ from .celery.utils import ( create_or_update_celery_periodic_tasks, get_celery_periodic_task, disable_celery_periodic_task, delete_celery_periodic_task ) -from .models import Task, CommandExecution, CeleryTask +from .models import CommandExecution, CeleryTask from .notifications import ServerPerformanceCheckUtil logger = get_logger(__file__) diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index a5838073f..49038b9b1 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -12,14 +12,11 @@ app_name = "ops" router = DefaultRouter() bulk_router = BulkRouter() -bulk_router.register(r'tasks', api.TaskViewSet, 'task') router.register(r'adhoc', api.AdHocViewSet, 'adhoc') -router.register(r'adhoc-executions', api.AdHocRunHistoryViewSet, 'execution') -router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution') +router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution') router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task') urlpatterns = [ - path('tasks//run/', api.TaskRun.as_view(), name='task-run'), path('celery/task//log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'), path('celery/task//result/', api.CeleryResultApi.as_view(), name='celery-result'), From a543a2ee377ce4d37a8c63b5c5a54ab18211f49b Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 8 Oct 2022 19:12:04 +0800 Subject: [PATCH 178/488] =?UTF-8?q?perf:=20=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=20adhoc=20runner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 11 ++++++-- apps/ops/ansible/runner.py | 12 ++++++-- apps/ops/models/__init__.py | 1 + apps/ops/models/adhoc.py | 17 ++++++++--- apps/ops/models/base.py | 53 +++++++++++++++++++++++++++-------- apps/ops/models/playbook.py | 6 +++- apps/ops/tasks.py | 45 ++++++++++++++++------------- 7 files changed, 105 insertions(+), 40 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 4da027696..85a3d03ff 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ from collections import defaultdict import json +import os __all__ = ['JMSInventory'] @@ -136,15 +137,19 @@ class JMSInventory: account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols) hosts.append(host) - return hosts - def write_to_file(self, path): - hosts = self.generate() data = {'all': {'hosts': {}}} for host in hosts: name = host.pop('name') var = host.pop('vars', {}) host.update(var) data['all']['hosts'][name] = host + return data + + def write_to_file(self, path): + data = self.generate() + path_dir = os.path.dirname(path) + if not os.path.exists(path_dir): + os.makedirs(path_dir, 0o700, True) with open(path, 'w') as f: f.write(json.dumps(data, indent=4)) diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index a420fb8a4..36a0e7e8c 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -1,7 +1,9 @@ import uuid -import ansible_runner +import os +import ansible_runner from django.conf import settings + from .callback import DefaultCallback @@ -11,7 +13,7 @@ class AdHocRunner: "reboot", 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top' ] - def __init__(self, inventory, module, module_args, pattern='*', project_dir='/tmp/'): + def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/'): self.id = uuid.uuid4() self.inventory = inventory self.pattern = pattern @@ -32,6 +34,12 @@ class AdHocRunner: if verbosity is None and settings.DEBUG: verbosity = 1 + if not os.path.exists(self.project_dir): + os.mkdir(self.project_dir, 0o755) + + print("inventory: ") + print(self.inventory) + ansible_runner.run( host_pattern=self.pattern, private_data_dir=self.project_dir, diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index fcd8bd8f7..93b630dd6 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -3,3 +3,4 @@ from .adhoc import * from .celery import * +from .playbook import * diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 565df9f3e..c3a7822a9 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -1,5 +1,5 @@ # ~*~ coding: utf-8 ~*~ - +import os.path from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -18,7 +18,12 @@ class AdHoc(BaseAnsibleTask): pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all') module = models.CharField(max_length=128, default='shell', verbose_name=_('Module')) args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) - last_execution = models.ForeignKey('AdHocExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True, blank=True) + last_execution = models.ForeignKey('AdHocExecution', verbose_name=_("Last execution"), + on_delete=models.SET_NULL, null=True, blank=True) + + def get_register_task(self): + from ops.tasks import run_adhoc + return "run_adhoc_{}".format(self.id), run_adhoc, (str(self.id),), {} def __str__(self): return "{}: {}".format(self.module, self.args) @@ -31,10 +36,14 @@ class AdHocExecution(BaseAnsibleExecution): task = models.ForeignKey('AdHoc', verbose_name=_("Adhoc"), related_name='executions', on_delete=models.CASCADE) def get_runner(self): - return AdHocRunner( - self.task.inventory, self.task.module, self.task.args, + inv = self.task.inventory + inv.write_to_file(self.inventory_path) + + runner = AdHocRunner( + self.inventory_path, self.task.module, module_args=self.task.args, pattern=self.task.pattern, project_dir=self.private_dir ) + return runner class Meta: db_table = "ops_adhoc_execution" diff --git a/apps/ops/models/base.py b/apps/ops/models/base.py index 2992173c3..f43e0c584 100644 --- a/apps/ops/models/base.py +++ b/apps/ops/models/base.py @@ -1,5 +1,6 @@ import os.path import uuid +import logging from django.db import models from django.utils.translation import gettext_lazy as _ @@ -25,7 +26,7 @@ class BaseAnsibleTask(PeriodTaskModelMixin, JMSOrgBaseModel): @property def inventory(self): inv = JMSInventory(self.assets.all(), self.account, self.account_policy) - return inv.generate() + return inv def get_register_task(self): raise NotImplemented @@ -33,11 +34,19 @@ class BaseAnsibleTask(PeriodTaskModelMixin, JMSOrgBaseModel): def to_json(self): raise NotImplemented + def create_execution(self): + execution = self.executions.create() + return execution + + def run(self, *args, **kwargs): + execution = self.create_execution() + return execution.start() + class BaseAnsibleExecution(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') - task = models.ForeignKey(BaseAnsibleTask, on_delete=models.CASCADE, null=True) + task = models.ForeignKey(BaseAnsibleTask, on_delete=models.CASCADE, related_name='executions', null=True) result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) summary = models.JSONField(default=dict, verbose_name=_('Summary')) creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) @@ -52,13 +61,40 @@ class BaseAnsibleExecution(models.Model): def __str__(self): return str(self.id) + @property def private_dir(self): uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id return os.path.join(settings.ANSIBLE_DIR, self.task.name, uniq) + @property + def inventory_path(self): + return os.path.join(self.private_dir, 'inventory', 'hosts') + def get_runner(self): raise NotImplemented + def finish_task(self): + self.date_finished = timezone.now() + self.save(update_fields=['result', 'status', 'summary', 'date_finished']) + self.update_task() + + def set_error(self, error): + this = self.__class__.objects.get(id=self.id) # 重新获取一次,避免数据库超时连接超时 + this.status = 'failed' + this.summary['error'] = str(error) + this.finish_task() + + def set_result(self, cb): + status_mapper = { + 'successful': 'succeeded', + } + this = self.__class__.objects.get(id=self.id) + this.status = status_mapper.get(cb.status, cb.status) + this.summary = cb.summary + this.result = cb.result + this.finish_task() + print("Finished") + def update_task(self): self.task.last_execution = self self.task.date_last_run = timezone.now() @@ -68,16 +104,11 @@ class BaseAnsibleExecution(models.Model): runner = self.get_runner() try: cb = runner.run(**kwargs) - self.status = cb.status - self.summary = cb.summary - self.result = cb.result - self.date_finished = timezone.now() + self.set_result(cb) + return cb except Exception as e: - self.status = 'failed' - self.summary = {'error': str(e)} - finally: - self.save() - self.update_task() + logging.error(e, exc_info=True) + self.set_error(e) @property def is_finished(self): diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index aec59bfb0..0701ed13e 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -27,7 +27,11 @@ class Playbook(BaseAnsibleTask): last_execution = models.ForeignKey('PlaybookExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True, blank=True) def get_register_task(self): - pass + name = "automation_strategy_period_{}".format(str(self.id)[:8]) + task = execute_automation_strategy.name + args = (str(self.id), Trigger.timing) + kwargs = {} + return name, task, args, kwargs class PlaybookExecution(BaseAnsibleExecution): diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 0ef430d7a..e9ba28eb7 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -20,7 +20,7 @@ from .celery.utils import ( create_or_update_celery_periodic_tasks, get_celery_periodic_task, disable_celery_periodic_task, delete_celery_periodic_task ) -from .models import CommandExecution, CeleryTask +from .models import CeleryTask, AdHoc, Playbook from .notifications import ServerPerformanceCheckUtil logger = get_logger(__file__) @@ -30,41 +30,48 @@ def rerun_task(): pass -@shared_task(queue="ansible", verbose_name=_("Run ansible task")) -def run_ansible_task(tid, callback=None, **kwargs): +@shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) +def run_adhoc(tid, **kwargs): """ :param tid: is the tasks serialized data :param callback: callback function name :return: """ with tmp_to_root_org(): - task = get_object_or_none(Task, id=tid) + task = get_object_or_none(AdHoc, id=tid) if not task: logger.error("No task found") return with tmp_to_org(task.org): - result = task.run() - if callback is not None: - subtask(callback).delay(result, task_name=task.name) - return result + execution = task.create_execution() + try: + execution.start(**kwargs) + except SoftTimeLimitExceeded: + execution.set_error('Run timeout') + logger.error("Run adhoc timeout") + except Exception as e: + execution.set_error(e) + logger.error("Start adhoc execution error: {}".format(e)) @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible command")) -def run_command_execution(cid, **kwargs): +def run_playbook(pid, **kwargs): with tmp_to_root_org(): - execution = get_object_or_none(CommandExecution, id=cid) - if not execution: - logger.error("Not found the execution id: {}".format(cid)) + task = get_object_or_none(Playbook, id=pid) + if not task: + logger.error("No task found") return - with tmp_to_org(execution.run_as.org): + + with tmp_to_org(task.org): + execution = task.create_execution() try: - os.environ.update({ - "TERM_ROWS": kwargs.get("rows", ""), - "TERM_COLS": kwargs.get("cols", ""), - }) - execution.run() + execution.start(**kwargs) except SoftTimeLimitExceeded: - logger.error("Run time out") + execution.set_error('Run timeout') + logger.error("Run playbook timeout") + except Exception as e: + execution.set_error(e) + logger.error("Run playbook execution error: {}".format(e)) @shared_task From f921f12171e87ff88d5c71dad144692f68c0704b Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 8 Oct 2022 19:51:29 +0800 Subject: [PATCH 179/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20adhoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 29 +++++++++++++++++++---------- apps/ops/ansible/runner.py | 3 --- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 85a3d03ff..6a7e3c5aa 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -3,6 +3,8 @@ from collections import defaultdict import json import os +from django.utils.translation import gettext as _ + __all__ = ['JMSInventory'] @@ -60,6 +62,7 @@ class JMSInventory: host = {'name': asset.name, 'vars': { 'asset_id': str(asset.id), 'asset_name': asset.name, 'asset_type': asset.type, 'asset_category': asset.category, + 'exclude': '' }} ansible_connection = automation.ansible_config.get('ansible_connection', 'ssh') gateway = None @@ -87,6 +90,8 @@ class JMSInventory: host['ansible_password'] = account.secret elif account.secret_type == 'private_key' and account.secret: host['ssh_private_key'] = account.private_key_file + else: + host['vars']['exclude'] = _("No account found") if gateway: host['vars'].update(self.make_proxy_command(gateway)) @@ -99,28 +104,26 @@ class JMSInventory: def select_account(self, asset): accounts = list(asset.accounts.all()) - if not accounts: - return None - account_selected = None account_username = self.account_username if isinstance(self.account_username, str): account_username = [self.account_username] + if account_username: for username in account_username: account_matched = list(filter(lambda account: account.username == username, accounts)) if account_matched: account_selected = account_matched[0] - return account_selected + break if not account_selected: if self.account_policy in ['privileged_must', 'privileged_first']: - account_selected = list(filter(lambda account: account.is_privileged, accounts)) - account_selected = account_selected[0] if account_selected else None + account_matched = list(filter(lambda account: account.is_privileged, accounts)) + account_selected = account_matched[0] if account_matched else None if not account_selected and self.account_policy == 'privileged_first': - account_selected = accounts[0] + account_selected = accounts[0] if accounts else None return account_selected def generate(self): @@ -130,14 +133,20 @@ class JMSInventory: automation = platform.automation protocols = platform.protocols.all() - if not automation.ansible_enabled: - continue - for asset in self.assets: account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols) + if not automation.ansible_enabled: + host['vars']['exclude'] = _('Ansible disabled') hosts.append(host) + exclude_hosts = list(filter(lambda x: x.get('exclude'), hosts)) + if exclude_hosts: + print(_("Skip hosts below:")) + for host in exclude_hosts: + print(" {}:\t{}".format(host['name'], host['exclude'])) + + hosts = list(filter(lambda x: not x.get('exclude'), hosts)) data = {'all': {'hosts': {}}} for host in hosts: name = host.pop('name') diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index 36a0e7e8c..e8f232e74 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -37,9 +37,6 @@ class AdHocRunner: if not os.path.exists(self.project_dir): os.mkdir(self.project_dir, 0o755) - print("inventory: ") - print(self.inventory) - ansible_runner.run( host_pattern=self.pattern, private_data_dir=self.project_dir, From 4e5a7a0a252e4ba091dbb524390ba0edcb04e6ef Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 9 Oct 2022 20:54:11 +0800 Subject: [PATCH 180/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=94=B9?= =?UTF-8?q?=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/__init__.py | 1 + .../base => automations/backup}/__init__.py | 0 .../backup/handlers.py | 0 .../backup/manager.py | 0 .../base}/__init__.py | 0 .../automations/base/base_inventory.txt | 14 +++ apps/assets/automations/base/manager.py | 70 ++++++++++++++ .../change_password}/__init__.py | 0 .../database/change_password_mysql/main.yml | 29 ++++++ .../change_password_mysql/manifest.yml | 0 .../database/change_password_oracle/main.yml | 29 ++++++ .../change_password_oracle/manifest.yml | 0 .../change_password_postgresql}/main.yml | 0 .../change_password_postgresql/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../change_password_sqlserver}/main.yml | 0 .../change_password_sqlserver/manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../change_password/demo_inventory.txt | 2 + .../host/change_password_aix/main.yml | 29 ++++++ .../host/change_password_aix/manifest.yml | 0 .../host/change_password_linux/main.yml | 29 ++++++ .../host/change_password_linux/manifest.yml | 0 .../change_password_local_windows/main.yml | 29 ++++++ .../manifest.yml | 0 .../automations/change_password/manager.py | 91 +++++++++++++++++++ apps/assets/automations/endpoint.py | 7 ++ .../generate_playbook}/__init__.py | 0 .../generate_playbook/change_password.py | 0 .../generate_playbook/verify.py | 0 .../__init__.py => automations/methods.py} | 3 +- apps/assets/const/types.py | 2 +- apps/assets/models/__init__.py | 1 + apps/assets/models/asset/common.py | 23 +++++ .../{automation => automations}/__init__.py | 0 .../account_discovery.py | 1 + .../account_reconcile.py | 0 .../account_verify.py | 0 .../{automation => automations}/base.py | 73 +++++++-------- .../change_secret.py | 2 +- apps/assets/playbooks/base/generator.py | 33 ------- apps/assets/playbooks/base/runner.py | 47 ---------- .../change_password_postgresql/main.yml | 10 -- .../roles/change_password/tasks/main.yml | 27 ------ .../change_password_sqlserver/main.yml | 10 -- .../roles/change_password/tasks/main.yml | 27 ------ .../host/change_password_aix/main.yml | 10 -- .../roles/change_password/tasks/main.yml | 27 ------ .../host/change_password_linux/main.yml | 8 -- .../roles/change_password/tasks/main.yml | 23 ----- .../change_password_local_windows/main.yml | 10 -- .../roles/change_password/tasks/main.yml | 27 ------ .../host/ansible_posix_ping/main.yml | 13 --- .../host/ansible_posix_ping/manifest.yml | 10 -- .../playbooks/host/ansible_win_ping/main.yml | 13 --- .../host/ansible_win_ping/manifest.yml | 6 -- .../change_password/roles/linux/main.yml | 12 --- .../roles/linux/tasks/main.yml | 36 -------- .../strategy/verify/roles/linux/main.yml | 5 - .../verify/roles/linux/tasks/main.yml | 8 -- apps/assets/task_handlers/__init__.py | 1 - apps/assets/task_handlers/endpoint.py | 10 -- apps/ops/ansible/callback.py | 2 +- apps/ops/ansible/inventory.py | 33 ++++--- apps/ops/models/base.py | 2 +- apps/ops/task_handlers/__init__.py | 1 - apps/ops/task_handlers/base/__init__.py | 2 - apps/ops/task_handlers/base/handlers.py | 16 ---- apps/ops/task_handlers/base/manager.py | 78 ---------------- .../ops/task_handlers/change_auth/__init__.py | 2 - .../ops/task_handlers/change_auth/handlers.py | 10 -- apps/ops/task_handlers/change_auth/manager.py | 12 --- apps/ops/task_handlers/collect/__init__.py | 2 - apps/ops/task_handlers/collect/handlers.py | 10 -- apps/ops/task_handlers/collect/manager.py | 10 -- apps/ops/task_handlers/endpoint.py | 31 ------- apps/ops/task_handlers/push/__init__.py | 2 - apps/ops/task_handlers/push/handlers.py | 10 -- apps/ops/task_handlers/push/manager.py | 10 -- apps/ops/task_handlers/verify/__init__.py | 2 - apps/ops/task_handlers/verify/handlers.py | 10 -- apps/ops/task_handlers/verify/manager.py | 10 -- apps/ops/utils.py | 2 +- 83 files changed, 413 insertions(+), 652 deletions(-) create mode 100644 apps/assets/automations/__init__.py rename apps/assets/{playbooks/base => automations/backup}/__init__.py (100%) rename apps/assets/{task_handlers => automations}/backup/handlers.py (100%) rename apps/assets/{task_handlers => automations}/backup/manager.py (100%) rename apps/assets/{playbooks/change_password => automations/base}/__init__.py (100%) create mode 100644 apps/assets/automations/base/base_inventory.txt create mode 100644 apps/assets/automations/base/manager.py rename apps/assets/{playbooks/generate_playbook => automations/change_password}/__init__.py (100%) create mode 100644 apps/assets/automations/change_password/database/change_password_mysql/main.yml rename apps/assets/{playbooks => automations}/change_password/database/change_password_mysql/manifest.yml (100%) create mode 100644 apps/assets/automations/change_password/database/change_password_oracle/main.yml rename apps/assets/{playbooks => automations}/change_password/database/change_password_oracle/manifest.yml (100%) rename apps/assets/{playbooks/change_password/database/change_password_mysql => automations/change_password/database/change_password_postgresql}/main.yml (100%) rename apps/assets/{playbooks => automations}/change_password/database/change_password_postgresql/manifest.yml (100%) rename apps/assets/{playbooks/change_password/database/change_password_mysql => automations/change_password/database/change_password_postgresql}/roles/change_password/tasks/main.yml (100%) rename apps/assets/{playbooks/change_password/database/change_password_oracle => automations/change_password/database/change_password_sqlserver}/main.yml (100%) rename apps/assets/{playbooks => automations}/change_password/database/change_password_sqlserver/manifest.yml (100%) rename apps/assets/{playbooks/change_password/database/change_password_oracle => automations/change_password/database/change_password_sqlserver}/roles/change_password/tasks/main.yml (100%) create mode 100644 apps/assets/automations/change_password/demo_inventory.txt create mode 100644 apps/assets/automations/change_password/host/change_password_aix/main.yml rename apps/assets/{playbooks => automations}/change_password/host/change_password_aix/manifest.yml (100%) create mode 100644 apps/assets/automations/change_password/host/change_password_linux/main.yml rename apps/assets/{playbooks => automations}/change_password/host/change_password_linux/manifest.yml (100%) create mode 100644 apps/assets/automations/change_password/host/change_password_local_windows/main.yml rename apps/assets/{playbooks => automations}/change_password/host/change_password_local_windows/manifest.yml (100%) create mode 100644 apps/assets/automations/change_password/manager.py create mode 100644 apps/assets/automations/endpoint.py rename apps/assets/{task_handlers/backup => automations/generate_playbook}/__init__.py (100%) rename apps/assets/{playbooks => automations}/generate_playbook/change_password.py (100%) rename apps/assets/{playbooks => automations}/generate_playbook/verify.py (100%) rename apps/assets/{playbooks/__init__.py => automations/methods.py} (96%) rename apps/assets/models/{automation => automations}/__init__.py (100%) rename apps/assets/models/{automation => automations}/account_discovery.py (89%) rename apps/assets/models/{automation => automations}/account_reconcile.py (100%) rename apps/assets/models/{automation => automations}/account_verify.py (100%) rename apps/assets/models/{automation => automations}/base.py (54%) rename apps/assets/models/{automation => automations}/change_secret.py (96%) delete mode 100644 apps/assets/playbooks/base/generator.py delete mode 100644 apps/assets/playbooks/base/runner.py delete mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml delete mode 100644 apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml delete mode 100644 apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml delete mode 100644 apps/assets/playbooks/host/ansible_posix_ping/main.yml delete mode 100644 apps/assets/playbooks/host/ansible_posix_ping/manifest.yml delete mode 100644 apps/assets/playbooks/host/ansible_win_ping/main.yml delete mode 100644 apps/assets/playbooks/host/ansible_win_ping/manifest.yml delete mode 100644 apps/assets/playbooks/strategy/change_password/roles/linux/main.yml delete mode 100644 apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml delete mode 100644 apps/assets/playbooks/strategy/verify/roles/linux/main.yml delete mode 100644 apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml delete mode 100644 apps/assets/task_handlers/__init__.py delete mode 100644 apps/assets/task_handlers/endpoint.py delete mode 100644 apps/ops/task_handlers/__init__.py delete mode 100644 apps/ops/task_handlers/base/__init__.py delete mode 100644 apps/ops/task_handlers/base/handlers.py delete mode 100644 apps/ops/task_handlers/base/manager.py delete mode 100644 apps/ops/task_handlers/change_auth/__init__.py delete mode 100644 apps/ops/task_handlers/change_auth/handlers.py delete mode 100644 apps/ops/task_handlers/change_auth/manager.py delete mode 100644 apps/ops/task_handlers/collect/__init__.py delete mode 100644 apps/ops/task_handlers/collect/handlers.py delete mode 100644 apps/ops/task_handlers/collect/manager.py delete mode 100644 apps/ops/task_handlers/endpoint.py delete mode 100644 apps/ops/task_handlers/push/__init__.py delete mode 100644 apps/ops/task_handlers/push/handlers.py delete mode 100644 apps/ops/task_handlers/push/manager.py delete mode 100644 apps/ops/task_handlers/verify/__init__.py delete mode 100644 apps/ops/task_handlers/verify/handlers.py delete mode 100644 apps/ops/task_handlers/verify/manager.py diff --git a/apps/assets/automations/__init__.py b/apps/assets/automations/__init__.py new file mode 100644 index 000000000..478e9740d --- /dev/null +++ b/apps/assets/automations/__init__.py @@ -0,0 +1 @@ +from .methods import platform_automation_methods diff --git a/apps/assets/playbooks/base/__init__.py b/apps/assets/automations/backup/__init__.py similarity index 100% rename from apps/assets/playbooks/base/__init__.py rename to apps/assets/automations/backup/__init__.py diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/automations/backup/handlers.py similarity index 100% rename from apps/assets/task_handlers/backup/handlers.py rename to apps/assets/automations/backup/handlers.py diff --git a/apps/assets/task_handlers/backup/manager.py b/apps/assets/automations/backup/manager.py similarity index 100% rename from apps/assets/task_handlers/backup/manager.py rename to apps/assets/automations/backup/manager.py diff --git a/apps/assets/playbooks/change_password/__init__.py b/apps/assets/automations/base/__init__.py similarity index 100% rename from apps/assets/playbooks/change_password/__init__.py rename to apps/assets/automations/base/__init__.py diff --git a/apps/assets/automations/base/base_inventory.txt b/apps/assets/automations/base/base_inventory.txt new file mode 100644 index 000000000..a2b73db16 --- /dev/null +++ b/apps/assets/automations/base/base_inventory.txt @@ -0,0 +1,14 @@ +## all connection vars +hostname asset_name=name asset_type=type asset_primary_protocol=ssh asset_primary_port=22 asset_protocols=[] + +## local connection +hostname ansible_connection=local + +## local connection with gateway +hostname ansible_connection=ssh ansible_user=gateway.username ansible_port=gateway.port ansible_host=gateway.host ansible_ssh_private_key_file=gateway.key + +## ssh connection for windows +hostname ansible_connection=ssh ansible_shell_type=powershell/cmd ansible_user=windows.username ansible_port=windows.port ansible_host=windows.host ansible_ssh_private_key_file=windows.key + +## ssh connection +hostname ansible_user=user ansible_password=pass ansible_host=host ansible_port=port ansible_ssh_private_key_file=key ssh_args="-o StrictHostKeyChecking=no" diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py new file mode 100644 index 000000000..83097d0eb --- /dev/null +++ b/apps/assets/automations/base/manager.py @@ -0,0 +1,70 @@ +import os + +from django.conf import settings +from django.utils import timezone + +from ops.ansible import JMSInventory + + +class BasePlaybookManager: + ansible_account_policy = 'privileged_first' + + def __init__(self, execution): + self.execution = execution + self.automation = execution.automation + + def get_grouped_assets(self): + return self.automation.all_assets_group_by_platform() + + @property + def playbook_dir_path(self): + ansible_dir = settings.ANSIBLE_DIR + path = os.path.join( + ansible_dir, self.automation.type, self.automation.name, + timezone.now().strftime('%Y%m%d_%H%M%S') + ) + return path + + @property + def inventory_path(self): + return os.path.join(self.playbook_dir_path, 'inventory', 'hosts.json') + + @property + def playbook_path(self): + return os.path.join(self.playbook_dir_path, 'project', 'main.yml') + + def generate(self): + self.prepare_playbook_dir() + self.generate_inventory() + self.generate_playbook() + + def prepare_playbook_dir(self): + inventory_dir = os.path.dirname(self.inventory_path) + playbook_dir = os.path.dirname(self.playbook_path) + for d in [inventory_dir, playbook_dir]: + if not os.path.exists(d): + os.makedirs(d, exist_ok=True, mode=0o755) + + def inventory_kwargs(self): + raise NotImplemented + + def generate_inventory(self): + inventory = JMSInventory( + assets=self.automation.get_all_assets(), + account_policy=self.ansible_account_policy, + **self.inventory_kwargs() + ) + inventory.write_to_file(self.inventory_path) + print("Generate inventory done: {}".format(self.inventory_path)) + + def generate_playbook(self): + raise NotImplemented + + def get_runner(self): + raise NotImplemented + + def run(self, **kwargs): + self.generate() + runner = self.get_runner() + return runner.run(**kwargs) + diff --git a/apps/assets/playbooks/generate_playbook/__init__.py b/apps/assets/automations/change_password/__init__.py similarity index 100% rename from apps/assets/playbooks/generate_playbook/__init__.py rename to apps/assets/automations/change_password/__init__.py diff --git a/apps/assets/automations/change_password/database/change_password_mysql/main.yml b/apps/assets/automations/change_password/database/change_password_mysql/main.yml new file mode 100644 index 000000000..483554a1e --- /dev/null +++ b/apps/assets/automations/change_password/database/change_password_mysql/main.yml @@ -0,0 +1,29 @@ +- hosts: demo + tasks: + - name: ping + ping: + + #- name: print variables + # debug: + # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + + - name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + + - name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + + - name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml b/apps/assets/automations/change_password/database/change_password_mysql/manifest.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_mysql/manifest.yml rename to apps/assets/automations/change_password/database/change_password_mysql/manifest.yml diff --git a/apps/assets/automations/change_password/database/change_password_oracle/main.yml b/apps/assets/automations/change_password/database/change_password_oracle/main.yml new file mode 100644 index 000000000..483554a1e --- /dev/null +++ b/apps/assets/automations/change_password/database/change_password_oracle/main.yml @@ -0,0 +1,29 @@ +- hosts: demo + tasks: + - name: ping + ping: + + #- name: print variables + # debug: + # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + + - name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + + - name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + + - name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml b/apps/assets/automations/change_password/database/change_password_oracle/manifest.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_oracle/manifest.yml rename to apps/assets/automations/change_password/database/change_password_oracle/manifest.yml diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/main.yml b/apps/assets/automations/change_password/database/change_password_postgresql/main.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_mysql/main.yml rename to apps/assets/automations/change_password/database/change_password_postgresql/main.yml diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml b/apps/assets/automations/change_password/database/change_password_postgresql/manifest.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_postgresql/manifest.yml rename to apps/assets/automations/change_password/database/change_password_postgresql/manifest.yml diff --git a/apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml b/apps/assets/automations/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_mysql/roles/change_password/tasks/main.yml rename to apps/assets/automations/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/main.yml b/apps/assets/automations/change_password/database/change_password_sqlserver/main.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_oracle/main.yml rename to apps/assets/automations/change_password/database/change_password_sqlserver/main.yml diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml b/apps/assets/automations/change_password/database/change_password_sqlserver/manifest.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_sqlserver/manifest.yml rename to apps/assets/automations/change_password/database/change_password_sqlserver/manifest.yml diff --git a/apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml b/apps/assets/automations/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/playbooks/change_password/database/change_password_oracle/roles/change_password/tasks/main.yml rename to apps/assets/automations/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml diff --git a/apps/assets/automations/change_password/demo_inventory.txt b/apps/assets/automations/change_password/demo_inventory.txt new file mode 100644 index 000000000..dcc7d1b6d --- /dev/null +++ b/apps/assets/automations/change_password/demo_inventory.txt @@ -0,0 +1,2 @@ +# all base inventory in base/base_inventory.txt +asset_name(ip)_account_username account={"username": "", "password": "xxx"} ...base_inventory_vars diff --git a/apps/assets/automations/change_password/host/change_password_aix/main.yml b/apps/assets/automations/change_password/host/change_password_aix/main.yml new file mode 100644 index 000000000..483554a1e --- /dev/null +++ b/apps/assets/automations/change_password/host/change_password_aix/main.yml @@ -0,0 +1,29 @@ +- hosts: demo + tasks: + - name: ping + ping: + + #- name: print variables + # debug: + # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + + - name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + + - name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + + - name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml b/apps/assets/automations/change_password/host/change_password_aix/manifest.yml similarity index 100% rename from apps/assets/playbooks/change_password/host/change_password_aix/manifest.yml rename to apps/assets/automations/change_password/host/change_password_aix/manifest.yml diff --git a/apps/assets/automations/change_password/host/change_password_linux/main.yml b/apps/assets/automations/change_password/host/change_password_linux/main.yml new file mode 100644 index 000000000..1ad590cff --- /dev/null +++ b/apps/assets/automations/change_password/host/change_password_linux/main.yml @@ -0,0 +1,29 @@ +- hosts: demo + tasks: + - name: Test privileged account + ping: + + #- name: print variables + # debug: + # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + + - name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.secret_type == 'password' + + - name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + + - name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml b/apps/assets/automations/change_password/host/change_password_linux/manifest.yml similarity index 100% rename from apps/assets/playbooks/change_password/host/change_password_linux/manifest.yml rename to apps/assets/automations/change_password/host/change_password_linux/manifest.yml diff --git a/apps/assets/automations/change_password/host/change_password_local_windows/main.yml b/apps/assets/automations/change_password/host/change_password_local_windows/main.yml new file mode 100644 index 000000000..483554a1e --- /dev/null +++ b/apps/assets/automations/change_password/host/change_password_local_windows/main.yml @@ -0,0 +1,29 @@ +- hosts: demo + tasks: + - name: ping + ping: + + #- name: print variables + # debug: + # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + + - name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + + - name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + + - name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml b/apps/assets/automations/change_password/host/change_password_local_windows/manifest.yml similarity index 100% rename from apps/assets/playbooks/change_password/host/change_password_local_windows/manifest.yml rename to apps/assets/automations/change_password/host/change_password_local_windows/manifest.yml diff --git a/apps/assets/automations/change_password/manager.py b/apps/assets/automations/change_password/manager.py new file mode 100644 index 000000000..ea0bb9a26 --- /dev/null +++ b/apps/assets/automations/change_password/manager.py @@ -0,0 +1,91 @@ +import os +import shutil +from copy import deepcopy +from collections import defaultdict + +import yaml +from django.utils.translation import gettext as _ + +from ops.ansible import PlaybookRunner, JMSInventory +from ..base.manager import BasePlaybookManager +from assets.automations.methods import platform_automation_methods + + +class ChangePasswordManager(BasePlaybookManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.id_method_mapper = { + method['id']: method + for method in platform_automation_methods + } + self.method_hosts_mapper = defaultdict(list) + + def host_duplicator(self, host, asset=None, account=None, platform=None, **kwargs): + accounts = asset.accounts.all() + if account: + accounts = accounts.exclude(id=account.id) + if '*' not in self.automation.accounts: + accounts = accounts.filter(username__in=self.automation.accounts) + + automation = platform.automation + change_password_enabled = automation and \ + automation.change_password_enabled and \ + automation.change_password_method and \ + automation.change_password_method in self.id_method_mapper + + if not change_password_enabled: + host.exclude = _('Change password disabled') + return [host] + + hosts = [] + for account in accounts: + h = deepcopy(host) + h['name'] += '_' + account.username + h['account'] = { + 'name': account.name, + 'username': account.username, + 'secret_type': account.secret_type, + 'secret': account.secret, + } + hosts.append(h) + self.method_hosts_mapper[automation.change_password_method].append(h['name']) + return hosts + + def inventory_kwargs(self): + return { + 'host_duplicator': self.host_duplicator + } + + def generate_playbook(self): + playbook = [] + for method_id, host_names in self.method_hosts_mapper.items(): + method = self.id_method_mapper[method_id] + playbook_dir_path = method['dir'] + playbook_dir_name = os.path.dirname(playbook_dir_path) + shutil.copytree(playbook_dir_path, self.playbook_dir_path) + sub_playbook_path = os.path.join(self.playbook_dir_path, playbook_dir_name, 'main.yml') + + with open(sub_playbook_path, 'r') as f: + host_playbook_play = yaml.safe_load(f) + + plays = [] + for name in host_names: + play = deepcopy(host_playbook_play) + play['hosts'] = name + plays.append(play) + + with open(sub_playbook_path, 'w') as f: + yaml.safe_dump(plays, f, default_flow_style=False) + + playbook.append({ + 'name': method['name'], + 'import_playbook': playbook_dir_name + '/' + 'main.yml' + }) + + with open(self.playbook_path, 'w') as f: + yaml.safe_dump(playbook, f, default_flow_style=False) + + print("Generate playbook done: " + self.playbook_path) + + + diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py new file mode 100644 index 000000000..f99defdd8 --- /dev/null +++ b/apps/assets/automations/endpoint.py @@ -0,0 +1,7 @@ +# from .backup.manager import AccountBackupExecutionManager +# +# +class ExecutionManager: + manager_type = { + } + diff --git a/apps/assets/task_handlers/backup/__init__.py b/apps/assets/automations/generate_playbook/__init__.py similarity index 100% rename from apps/assets/task_handlers/backup/__init__.py rename to apps/assets/automations/generate_playbook/__init__.py diff --git a/apps/assets/playbooks/generate_playbook/change_password.py b/apps/assets/automations/generate_playbook/change_password.py similarity index 100% rename from apps/assets/playbooks/generate_playbook/change_password.py rename to apps/assets/automations/generate_playbook/change_password.py diff --git a/apps/assets/playbooks/generate_playbook/verify.py b/apps/assets/automations/generate_playbook/verify.py similarity index 100% rename from apps/assets/playbooks/generate_playbook/verify.py rename to apps/assets/automations/generate_playbook/verify.py diff --git a/apps/assets/playbooks/__init__.py b/apps/assets/automations/methods.py similarity index 96% rename from apps/assets/playbooks/__init__.py rename to apps/assets/automations/methods.py index 59f705fb9..811d0c77d 100644 --- a/apps/assets/playbooks/__init__.py +++ b/apps/assets/automations/methods.py @@ -1,5 +1,6 @@ import os import yaml +import json from functools import partial BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -62,4 +63,4 @@ platform_automation_methods = get_platform_automation_methods() if __name__ == '__main__': - print(get_platform_automation_methods()) + print(json.dumps(platform_automation_methods, indent=4)) diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 454a5d358..0d8b86afa 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -38,7 +38,7 @@ class AllTypes(ChoicesMixin): @classmethod def set_automation_methods(cls, category, tp, constraints): - from assets.playbooks import filter_platform_methods + from assets.automations import filter_platform_methods automation = constraints.get('automation', {}) automation_methods = {} for item, enabled in automation.items(): diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index e79b2fff5..376355657 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -10,6 +10,7 @@ from .gathered_user import * from .favorite_asset import * from .account import * from .backup import * +from .automations import * from ._user import * # 废弃以下 # from ._authbook import * diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index ef64e5fb6..5703c82dc 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -4,6 +4,7 @@ import logging import uuid +from collections import defaultdict from django.db import models from django.db.models import Q @@ -41,6 +42,12 @@ class AssetQuerySet(models.QuerySet): def has_protocol(self, name): return self.filter(protocols__contains=name) + def group_by_platform(self) -> dict: + groups = defaultdict(list) + for asset in self.all(): + groups[asset.platform].append(asset) + return groups + class NodesRelationMixin: NODES_CACHE_KEY = 'ASSET_NODES_{}' @@ -126,6 +133,22 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): names.append(n.name + ':' + n.value) return names + @lazyproperty + def primary_protocol(self): + return self.protocols.first() + + @lazyproperty + def protocol(self): + if not self.primary_protocol: + return 'none' + return self.primary_protocol.name + + @lazyproperty + def port(self): + if not self.primary_protocol: + return 0 + return self.primary_protocol.port + @property def protocols_as_list(self): return [{'name': p.name, 'port': p.port} for p in self.protocols.all()] diff --git a/apps/assets/models/automation/__init__.py b/apps/assets/models/automations/__init__.py similarity index 100% rename from apps/assets/models/automation/__init__.py rename to apps/assets/models/automations/__init__.py diff --git a/apps/assets/models/automation/account_discovery.py b/apps/assets/models/automations/account_discovery.py similarity index 89% rename from apps/assets/models/automation/account_discovery.py rename to apps/assets/models/automations/account_discovery.py index bfe9d0d80..9572986f3 100644 --- a/apps/assets/models/automation/account_discovery.py +++ b/apps/assets/models/automations/account_discovery.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from ops.const import StrategyChoice +from ops.ansible.runner import PlaybookRunner from .base import BaseAutomation diff --git a/apps/assets/models/automation/account_reconcile.py b/apps/assets/models/automations/account_reconcile.py similarity index 100% rename from apps/assets/models/automation/account_reconcile.py rename to apps/assets/models/automations/account_reconcile.py diff --git a/apps/assets/models/automation/account_verify.py b/apps/assets/models/automations/account_verify.py similarity index 100% rename from apps/assets/models/automation/account_verify.py rename to apps/assets/models/automations/account_verify.py diff --git a/apps/assets/models/automation/base.py b/apps/assets/models/automations/base.py similarity index 54% rename from apps/assets/models/automation/base.py rename to apps/assets/models/automations/base.py index a9b8ab087..ba0d393ea 100644 --- a/apps/assets/models/automation/base.py +++ b/apps/assets/models/automations/base.py @@ -8,16 +8,17 @@ from common.db.fields import EncryptJsonDictTextField from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel from ops.mixin import PeriodTaskModelMixin from ops.tasks import execute_automation_strategy -from ops.task_handlers import ExecutionManager +from assets.models import Node, Asset +from assets.automations.endpoint import ExecutionManager class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) nodes = models.ManyToManyField( - 'assets.Node', related_name='automation_strategy', blank=True, verbose_name=_("Nodes") + 'assets.Node', blank=True, verbose_name=_("Nodes") ) assets = models.ManyToManyField( - 'assets.Asset', related_name='automation_strategy', blank=True, verbose_name=_("Assets") + 'assets.Asset', blank=True, verbose_name=_("Assets") ) type = models.CharField(max_length=16, verbose_name=_('Type')) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -25,6 +26,17 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): def __str__(self): return self.name + '@' + str(self.created_by) + def get_all_assets(self): + nodes = self.nodes.all() + node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) + direct_asset_ids = self.assets.all().values_list('id', flat=True) + asset_ids = set(list(direct_asset_ids) + list(node_asset_ids)) + return Asset.objects.filter(id__in=asset_ids) + + def all_assets_group_by_platform(self): + assets = self.get_all_assets().prefetch_related('platform') + return assets.group_by_platform() + def get_register_task(self): name = "automation_strategy_period_{}".format(str(self.id)[:8]) task = execute_automation_strategy.name @@ -40,13 +52,15 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): 'nodes': list(self.assets.all().values_list('id', flat=True)), } - def execute(self, trigger): + def execute(self, trigger=Trigger.manual): try: eid = current_task.request.id except AttributeError: eid = str(uuid.uuid4()) - execution = AutomationStrategyExecution.objects.create( - id=eid, strategy=self, snapshot=self.to_attr_json(), trigger=trigger + + execution = AutomationExecution.objects.create( + id=eid, strategy=self, trigger=trigger, + snapshot=self.to_attr_json(), ) return execution.start() @@ -55,22 +69,22 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): verbose_name = _("Automation plan") -class AutomationStrategyExecution(OrgModelMixin): +class AutomationExecution(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - - date_created = models.DateTimeField(auto_now_add=True) - timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True) - date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) - + automation = models.ForeignKey( + 'BaseAutomation', related_name='executions', on_delete=models.CASCADE, + verbose_name=_('Automation strategy') + ) + status = models.CharField(max_length=16, default='pending') + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) + date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) snapshot = EncryptJsonDictTextField( default=dict, blank=True, null=True, verbose_name=_('Automation snapshot') ) - strategy = models.ForeignKey( - 'BaseAutomation', related_name='execution', on_delete=models.CASCADE, - verbose_name=_('Automation strategy') - ) trigger = models.CharField( - max_length=128, default=Trigger.manual, choices=Trigger.choices, verbose_name=_('Trigger mode') + max_length=128, default=Trigger.manual, choices=Trigger.choices, + verbose_name=_('Trigger mode') ) class Meta: @@ -83,28 +97,3 @@ class AutomationStrategyExecution(OrgModelMixin): def start(self): manager = ExecutionManager(execution=self) return manager.run() - - -class AutomationStrategyTask(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - asset = models.ForeignKey( - 'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset') - ) - account = models.ForeignKey( - 'assets.Account', on_delete=models.CASCADE, verbose_name=_('Account') - ) - is_success = models.BooleanField(default=False, verbose_name=_('Is success')) - timedelta = models.FloatField(default=0.0, null=True, verbose_name=_('Time')) - date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) - reason = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_('Reason')) - execution = models.ForeignKey( - 'AutomationStrategyExecution', related_name='task', on_delete=models.CASCADE, - verbose_name=_('Automation strategy execution') - ) - - class Meta: - verbose_name = _('Automation strategy task') - - @property - def handler_type(self): - return self.execution.snapshot['type'] diff --git a/apps/assets/models/automation/change_secret.py b/apps/assets/models/automations/change_secret.py similarity index 96% rename from apps/assets/models/automation/change_secret.py rename to apps/assets/models/automations/change_secret.py index d176f5c6a..4a2c304c2 100644 --- a/apps/assets/models/automation/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -2,7 +2,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.db import fields -from ops.const import SSHKeyStrategy, PasswordStrategy, StrategyChoice +from ops.const import PasswordStrategy, StrategyChoice from ops.utils import generate_random_password from .base import BaseAutomation diff --git a/apps/assets/playbooks/base/generator.py b/apps/assets/playbooks/base/generator.py deleted file mode 100644 index 972d12409..000000000 --- a/apps/assets/playbooks/base/generator.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -import time -import shutil -from typing import List - -from django.conf import settings - -from assets.models import Asset - - -class BaseRunner: - src_filepath: str - - def __init__(self, assets: List[Asset], strategy): - self.assets = assets - self.strategy = strategy - self.temp_folder = self.temp_folder_path() - - @staticmethod - def temp_folder_path(): - project_dir = settings.PROJECT_DIR - tmp_dir = os.path.join(project_dir, 'tmp') - filepath = os.path.join(tmp_dir, str(time.time())) - return filepath - - def del_temp_folder(self): - shutil.rmtree(self.temp_folder) - - def generate_temp_playbook(self): - src = self.src_filepath - dst = os.path.join(self.temp_folder, self.strategy) - shutil.copytree(src, dst) - return dst diff --git a/apps/assets/playbooks/base/runner.py b/apps/assets/playbooks/base/runner.py deleted file mode 100644 index 1370db6ba..000000000 --- a/apps/assets/playbooks/base/runner.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -import tempfile -import shutil -from typing import List - -from django.conf import settings - -from assets.models import Asset - - -class BasePlaybookGenerator: - def __init__(self, assets: list[Asset], strategy, ansible_connection='ssh'): - self.assets = assets - self.strategy = strategy - self.playbook_dir = self.temp_folder_path() - - def generate(self): - self.prepare_playbook_dir() - self.generate_inventory() - self.generate_playbook() - - def prepare_playbook_dir(self): - pass - - def generate_inventory(self): - pass - - def generate_playbook(self): - pass - - @property - def base_dir(self): - tmp_dir = os.path.join(settings.PROJECT_DIR, 'tmp') - path = os.path.join(tmp_dir, self.strategy) - return path - - def temp_folder_path(self): - return tempfile.mkdtemp(dir=self.base_dir) - - def del_temp_folder(self): - shutil.rmtree(self.playbook_dir) - - def generate_temp_playbook(self): - src = self.src_filepath - dst = os.path.join(self.temp_folder, self.strategy) - shutil.copytree(src, dst) - return dst diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_postgresql/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_sqlserver/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_aix/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_aix/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/main.yml deleted file mode 100644 index a7d0f9417..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_linux/main.yml +++ /dev/null @@ -1,8 +0,0 @@ -- hosts: all - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password diff --git a/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml deleted file mode 100644 index e0ba9c73f..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_linux/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,23 +0,0 @@ -- name: Check connection - ping: - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('sha512') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml deleted file mode 100644 index 402c7fa8d..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_local_windows/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} diff --git a/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml b/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/playbooks/change_password/host/change_password_local_windows/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/playbooks/host/ansible_posix_ping/main.yml b/apps/assets/playbooks/host/ansible_posix_ping/main.yml deleted file mode 100644 index 4ccdb3074..000000000 --- a/apps/assets/playbooks/host/ansible_posix_ping/main.yml +++ /dev/null @@ -1,13 +0,0 @@ -- hosts: centos - gather_facts: no - vars: - account: - username: web - password: test123 - - tasks: - - name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" diff --git a/apps/assets/playbooks/host/ansible_posix_ping/manifest.yml b/apps/assets/playbooks/host/ansible_posix_ping/manifest.yml deleted file mode 100644 index 6cd223f1c..000000000 --- a/apps/assets/playbooks/host/ansible_posix_ping/manifest.yml +++ /dev/null @@ -1,10 +0,0 @@ -id: ansible_posix_ping -name: Ansible posix ping -description: Ansible ping -category: host -type: - - linux - - unix - - macos - - bsd -method: verify_account diff --git a/apps/assets/playbooks/host/ansible_win_ping/main.yml b/apps/assets/playbooks/host/ansible_win_ping/main.yml deleted file mode 100644 index 726d04a53..000000000 --- a/apps/assets/playbooks/host/ansible_win_ping/main.yml +++ /dev/null @@ -1,13 +0,0 @@ -- hosts: centos - gather_facts: no - vars: - account: - username: web - password: test123 - - tasks: - - name: Verify password - win_ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" diff --git a/apps/assets/playbooks/host/ansible_win_ping/manifest.yml b/apps/assets/playbooks/host/ansible_win_ping/manifest.yml deleted file mode 100644 index fe881de3b..000000000 --- a/apps/assets/playbooks/host/ansible_win_ping/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: ansible_win_ping -name: Ansible win ping -category: host -type: - - windows -method: verify_account diff --git a/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml b/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml deleted file mode 100644 index 16f0d1037..000000000 --- a/apps/assets/playbooks/strategy/change_password/roles/linux/main.yml +++ /dev/null @@ -1,12 +0,0 @@ -- hosts: all - vars: - connection_type: ssh - password: - value: {{ password }} - public_key: - value: {{ jms_key }} - exclusive: {{ exclusive }} - key_strategy: {{ key_strategy }} - private_key_file: {{ private_key_file }} - roles: - - linux diff --git a/apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml b/apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml deleted file mode 100644 index cc9467ca1..000000000 --- a/apps/assets/playbooks/strategy/change_password/roles/linux/tasks/main.yml +++ /dev/null @@ -1,36 +0,0 @@ -- name: Check connection - ping: - -- name: Change password - user: - name: "{{ item }}" - password: "{{ password.value | password_hash('sha512') }}" - update_password: always - with_items: "{{ usernames }}" - when: "{{ password.value }}" - -- name: Change public key - authorized_key: - user: "{{ item }}" - key: "{{ lookup('file', id_rsa.pub) }}" - state: present - exclusive: "{{ public_key.exclusive }}" - with_items: "{{ usernames }}" - when: "{{ public_key.value and key_strategy != 'set_jms' }}" - -- name: Change public key - lineinfile: - user: "{{ item }}" - dest: /home/{{ item }}/.ssh/authorized_keys regexp='.*{{ public_key.value }}$ - state: absent - with_items: "{{ usernames }}" - when: "{{ public_key.value and key_strategy == 'set_jms' }}" - -- name: Verify user - ping: - vars: - ansible_user: "{{ item }}" - ansible_pass: "{{ password.value }}" - ansible_ssh_private_key_file: "{{ private_key_file }}" - ansible_connection: "{{ connection_type | default('ssh') }}" - with_items: "{{ usernames }}" diff --git a/apps/assets/playbooks/strategy/verify/roles/linux/main.yml b/apps/assets/playbooks/strategy/verify/roles/linux/main.yml deleted file mode 100644 index 03c666df7..000000000 --- a/apps/assets/playbooks/strategy/verify/roles/linux/main.yml +++ /dev/null @@ -1,5 +0,0 @@ -- hosts: all - vars: - connection_type: ssh - roles: - - linux diff --git a/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml b/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml deleted file mode 100644 index ff9e1eb99..000000000 --- a/apps/assets/playbooks/strategy/verify/roles/linux/tasks/main.yml +++ /dev/null @@ -1,8 +0,0 @@ -- name: Verify user - ping: - vars: - ansible_user: "{{ item.username }}" - ansible_pass: "{{ item.username }}" - ansible_connection: "{{ connection_type | default('ssh') }}" - ansible_ssh_private_key_file: "{{ item.private_key_file }}" - with_items: "{{ account_info }}" diff --git a/apps/assets/task_handlers/__init__.py b/apps/assets/task_handlers/__init__.py deleted file mode 100644 index d557c449e..000000000 --- a/apps/assets/task_handlers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .endpoint import * diff --git a/apps/assets/task_handlers/endpoint.py b/apps/assets/task_handlers/endpoint.py deleted file mode 100644 index 729fc8648..000000000 --- a/apps/assets/task_handlers/endpoint.py +++ /dev/null @@ -1,10 +0,0 @@ -from .backup.manager import AccountBackupExecutionManager - - -class ExecutionManager: - manager_type = { - 'backup': AccountBackupExecutionManager - } - - def __new__(cls, execution): - return AccountBackupExecutionManager(execution) diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 59734b07d..84e106e64 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -71,7 +71,7 @@ class DefaultCallback: def runner_on_start(self, event_data, **kwargs): pass - def runer_retry(self, event_data, **kwargs): + def runner_retry(self, event_data, **kwargs): pass def runner_on_file_diff(self, event_data, **kwargs): diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 6a7e3c5aa..5528f1a14 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -1,7 +1,7 @@ # ~*~ coding: utf-8 ~*~ -from collections import defaultdict import json import os +from collections import defaultdict from django.utils.translation import gettext as _ @@ -10,7 +10,7 @@ __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, assets, account='', account_policy='smart', host_var_callback=None): + def __init__(self, assets, account='', account_policy='smart', host_var_callback=None, host_duplicator=None): """ :param assets: :param account: account username name if not set use account_policy @@ -21,6 +21,7 @@ class JMSInventory: self.account_username = account self.account_policy = account_policy self.host_var_callback = host_var_callback + self.host_duplicator = host_duplicator @staticmethod def clean_assets(assets): @@ -59,11 +60,16 @@ class JMSInventory: return {"ansible_ssh_common_args": proxy_command} def asset_to_host(self, asset, account, automation, protocols): - host = {'name': asset.name, 'vars': { - 'asset_id': str(asset.id), 'asset_name': asset.name, - 'asset_type': asset.type, 'asset_category': asset.category, + host = { + 'name': asset.name, + 'asset': { + 'id': asset.id, 'name': asset.name, 'ip': asset.ip, + 'type': asset.type, 'category': asset.category, + 'protocol': asset.protocol, 'port': asset.port, + 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], + }, 'exclude': '' - }} + } ansible_connection = automation.ansible_config.get('ansible_connection', 'ssh') gateway = None if asset.domain: @@ -91,15 +97,15 @@ class JMSInventory: elif account.secret_type == 'private_key' and account.secret: host['ssh_private_key'] = account.private_key_file else: - host['vars']['exclude'] = _("No account found") + host['exclude'] = _("No account found") if gateway: - host['vars'].update(self.make_proxy_command(gateway)) + host.update(self.make_proxy_command(gateway)) if self.host_var_callback: callback_var = self.host_var_callback(asset) if isinstance(callback_var, dict): - host['vars'].update(callback_var) + host.update(callback_var) return host def select_account(self, asset): @@ -137,8 +143,11 @@ class JMSInventory: account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols) if not automation.ansible_enabled: - host['vars']['exclude'] = _('Ansible disabled') - hosts.append(host) + host['exclude'] = _('Ansible disabled') + if self.host_duplicator: + hosts.extend(self.host_duplicator(host, asset=asset, account=account, platform=platform)) + else: + hosts.append(host) exclude_hosts = list(filter(lambda x: x.get('exclude'), hosts)) if exclude_hosts: @@ -150,8 +159,6 @@ class JMSInventory: data = {'all': {'hosts': {}}} for host in hosts: name = host.pop('name') - var = host.pop('vars', {}) - host.update(var) data['all']['hosts'][name] = host return data diff --git a/apps/ops/models/base.py b/apps/ops/models/base.py index f43e0c584..e91af13a6 100644 --- a/apps/ops/models/base.py +++ b/apps/ops/models/base.py @@ -52,7 +52,7 @@ class BaseAnsibleExecution(models.Model): creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) - date_finished = models.DateTimeField(null=True) + date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) class Meta: abstract = True diff --git a/apps/ops/task_handlers/__init__.py b/apps/ops/task_handlers/__init__.py deleted file mode 100644 index d557c449e..000000000 --- a/apps/ops/task_handlers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .endpoint import * diff --git a/apps/ops/task_handlers/base/__init__.py b/apps/ops/task_handlers/base/__init__.py deleted file mode 100644 index 2dc0b1a17..000000000 --- a/apps/ops/task_handlers/base/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .manager import * -from .handlers import * diff --git a/apps/ops/task_handlers/base/handlers.py b/apps/ops/task_handlers/base/handlers.py deleted file mode 100644 index 1df6d946c..000000000 --- a/apps/ops/task_handlers/base/handlers.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -执行改密计划的基类 -""" -from common.utils import get_logger - -logger = get_logger(__file__) - - -class BaseHandler: - def __init__(self, task, show_step_info=True): - self.task = task - self.conn = None - self.retry_times = 3 - self.current_step = 0 - self.is_frozen = False # 任务状态冻结标志 - self.show_step_info = show_step_info diff --git a/apps/ops/task_handlers/base/manager.py b/apps/ops/task_handlers/base/manager.py deleted file mode 100644 index 080bf866c..000000000 --- a/apps/ops/task_handlers/base/manager.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# -import time -from openpyxl import Workbook -from django.utils import timezone - -from common.utils import get_logger -from common.utils.timezone import local_now_display - -logger = get_logger(__file__) - - -class BaseExecutionManager: - task_back_up_serializer: None - - def __init__(self, execution): - self.execution = execution - self.date_start = timezone.now() - self.time_start = time.time() - self.date_end = None - self.time_end = None - self.timedelta = 0 - self.total_tasks = [] - - def on_tasks_pre_run(self, tasks): - raise NotImplementedError - - def on_per_task_pre_run(self, task, total, index): - raise NotImplementedError - - def create_csv_file(self, tasks, file_name): - raise NotImplementedError - - def get_handler_cls(self): - raise NotImplemented - - def do_run(self): - tasks = self.total_tasks = self.execution.create_plan_tasks() - self.on_tasks_pre_run(tasks) - total = len(tasks) - - for index, task in enumerate(tasks, start=1): - self.on_per_task_pre_run(task, total, index) - task.start(show_step_info=False) - - def pre_run(self): - self.execution.date_start = self.date_start - self.execution.save() - self.show_execution_steps() - - def show_execution_steps(self): - pass - - def show_summary(self): - split_line = '#' * 40 - summary = self.execution.result_summary - logger.info(f'\n{split_line} 改密计划执行结果汇总 {split_line}') - logger.info( - '\n成功: {succeed}, 失败: {failed}, 总数: {total}\n' - ''.format(**summary) - ) - - def post_run(self): - self.time_end = time.time() - self.date_end = timezone.now() - - logger.info('\n\n' + '-' * 80) - logger.info('任务执行结束 {}\n'.format(local_now_display())) - self.timedelta = int(self.time_end - self.time_start) - logger.info('用时: {}s'.format(self.timedelta)) - self.execution.timedelta = self.timedelta - self.execution.save() - self.show_summary() - - def run(self): - self.pre_run() - self.do_run() - self.post_run() diff --git a/apps/ops/task_handlers/change_auth/__init__.py b/apps/ops/task_handlers/change_auth/__init__.py deleted file mode 100644 index 2dc0b1a17..000000000 --- a/apps/ops/task_handlers/change_auth/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .manager import * -from .handlers import * diff --git a/apps/ops/task_handlers/change_auth/handlers.py b/apps/ops/task_handlers/change_auth/handlers.py deleted file mode 100644 index c982a089d..000000000 --- a/apps/ops/task_handlers/change_auth/handlers.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseHandler - -logger = get_logger(__name__) - - -class ChangeAuthHandler(BaseHandler): - pass diff --git a/apps/ops/task_handlers/change_auth/manager.py b/apps/ops/task_handlers/change_auth/manager.py deleted file mode 100644 index a9f77927c..000000000 --- a/apps/ops/task_handlers/change_auth/manager.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseExecutionManager -from .handlers import ChangeAuthHandler - -logger = get_logger(__name__) - - -class ChangeAuthExecutionManager(BaseExecutionManager): - def get_handler_cls(self): - return ChangeAuthHandler diff --git a/apps/ops/task_handlers/collect/__init__.py b/apps/ops/task_handlers/collect/__init__.py deleted file mode 100644 index 2dc0b1a17..000000000 --- a/apps/ops/task_handlers/collect/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .manager import * -from .handlers import * diff --git a/apps/ops/task_handlers/collect/handlers.py b/apps/ops/task_handlers/collect/handlers.py deleted file mode 100644 index 81b1e748d..000000000 --- a/apps/ops/task_handlers/collect/handlers.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseHandler - -logger = get_logger(__name__) - - -class CollectHandler(BaseHandler): - pass diff --git a/apps/ops/task_handlers/collect/manager.py b/apps/ops/task_handlers/collect/manager.py deleted file mode 100644 index 18a685d7f..000000000 --- a/apps/ops/task_handlers/collect/manager.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseExecutionManager - -logger = get_logger(__name__) - - -class CollectExecutionManager(object): - pass diff --git a/apps/ops/task_handlers/endpoint.py b/apps/ops/task_handlers/endpoint.py deleted file mode 100644 index 2d95dcad5..000000000 --- a/apps/ops/task_handlers/endpoint.py +++ /dev/null @@ -1,31 +0,0 @@ -from ops.const import StrategyChoice -from .push import PushExecutionManager, PushHandler -from .verify import VerifyExecutionManager, VerifyHandler -from .collect import CollectExecutionManager, CollectHandler -from .change_auth import ChangeAuthExecutionManager, ChangeAuthHandler - - -class ExecutionManager: - manager_type = { - StrategyChoice.push: PushExecutionManager, - StrategyChoice.verify: VerifyExecutionManager, - StrategyChoice.collect: CollectExecutionManager, - StrategyChoice.change_password: ChangeAuthExecutionManager, - } - - def __new__(cls, execution): - manager = cls.manager_type[execution.manager_type] - return manager(execution) - - -class TaskHandler: - handler_type = { - StrategyChoice.push: PushHandler, - StrategyChoice.verify: VerifyHandler, - StrategyChoice.collect: CollectHandler, - StrategyChoice.change_password: ChangeAuthHandler, - } - - def __new__(cls, task, show_step_info): - handler = cls.handler_type[task.handler_type] - return handler(task, show_step_info) diff --git a/apps/ops/task_handlers/push/__init__.py b/apps/ops/task_handlers/push/__init__.py deleted file mode 100644 index 2dc0b1a17..000000000 --- a/apps/ops/task_handlers/push/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .manager import * -from .handlers import * diff --git a/apps/ops/task_handlers/push/handlers.py b/apps/ops/task_handlers/push/handlers.py deleted file mode 100644 index 891ac4bb6..000000000 --- a/apps/ops/task_handlers/push/handlers.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseHandler - -logger = get_logger(__name__) - - -class PushHandler(BaseHandler): - pass diff --git a/apps/ops/task_handlers/push/manager.py b/apps/ops/task_handlers/push/manager.py deleted file mode 100644 index 933f9a0cc..000000000 --- a/apps/ops/task_handlers/push/manager.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseExecutionManager - -logger = get_logger(__name__) - - -class PushExecutionManager(BaseExecutionManager): - pass diff --git a/apps/ops/task_handlers/verify/__init__.py b/apps/ops/task_handlers/verify/__init__.py deleted file mode 100644 index 2dc0b1a17..000000000 --- a/apps/ops/task_handlers/verify/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .manager import * -from .handlers import * diff --git a/apps/ops/task_handlers/verify/handlers.py b/apps/ops/task_handlers/verify/handlers.py deleted file mode 100644 index 7a7f69881..000000000 --- a/apps/ops/task_handlers/verify/handlers.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseHandler - -logger = get_logger(__name__) - - -class VerifyHandler(BaseHandler): - pass diff --git a/apps/ops/task_handlers/verify/manager.py b/apps/ops/task_handlers/verify/manager.py deleted file mode 100644 index a3727f1de..000000000 --- a/apps/ops/task_handlers/verify/manager.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# -from common.utils import get_logger -from ..base import BaseExecutionManager - -logger = get_logger(__name__) - - -class VerifyExecutionManager(BaseExecutionManager): - pass diff --git a/apps/ops/utils.py b/apps/ops/utils.py index ea9d74cbc..44c486ded 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -8,7 +8,7 @@ from common.utils import get_logger, get_object_or_none from orgs.utils import org_aware_func from jumpserver.const import PROJECT_DIR -from .models import Task, AdHoc +from .models import AdHoc from .const import DEFAULT_PASSWORD_RULES logger = get_logger(__file__) From 9a0bae5bfd75fb5396cbdb0ea3a55e522e85378e Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 10 Oct 2022 13:56:42 +0800 Subject: [PATCH 181/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20ansible=20?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 11 ++++---- .../host/change_password_linux/main.yml | 1 + .../change_password_local_windows/main.yml | 1 + .../automations/change_password/manager.py | 27 +++++++++++++------ apps/assets/automations/endpoint.py | 13 ++++++++- apps/assets/models/automations/base.py | 5 ++-- apps/assets/models/label.py | 6 ++--- apps/common/drf/parsers/base.py | 2 +- apps/ops/ansible/inventory.py | 8 +++--- apps/ops/models/base.py | 6 ++--- apps/orgs/signal_handlers/common.py | 8 +++--- 11 files changed, 57 insertions(+), 31 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 83097d0eb..e55a564bf 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -20,7 +20,7 @@ class BasePlaybookManager: def playbook_dir_path(self): ansible_dir = settings.ANSIBLE_DIR path = os.path.join( - ansible_dir, self.automation.type, self.automation.name, + ansible_dir, self.automation.type, self.automation.name.replace(' ', '_'), timezone.now().strftime('%Y%m%d_%H%M%S') ) return path @@ -41,12 +41,13 @@ class BasePlaybookManager: def prepare_playbook_dir(self): inventory_dir = os.path.dirname(self.inventory_path) playbook_dir = os.path.dirname(self.playbook_path) - for d in [inventory_dir, playbook_dir]: + for d in [inventory_dir, playbook_dir, self.playbook_dir_path]: + print("Create dir: {}".format(d)) if not os.path.exists(d): os.makedirs(d, exist_ok=True, mode=0o755) def inventory_kwargs(self): - raise NotImplemented + raise NotImplementedError def generate_inventory(self): inventory = JMSInventory( @@ -58,10 +59,10 @@ class BasePlaybookManager: print("Generate inventory done: {}".format(self.inventory_path)) def generate_playbook(self): - raise NotImplemented + raise NotImplementedError def get_runner(self): - raise NotImplemented + raise NotImplementedError def run(self, **kwargs): self.generate() diff --git a/apps/assets/automations/change_password/host/change_password_linux/main.yml b/apps/assets/automations/change_password/host/change_password_linux/main.yml index 1ad590cff..8bdaa5cdc 100644 --- a/apps/assets/automations/change_password/host/change_password_linux/main.yml +++ b/apps/assets/automations/change_password/host/change_password_linux/main.yml @@ -1,4 +1,5 @@ - hosts: demo + gather_facts: no tasks: - name: Test privileged account ping: diff --git a/apps/assets/automations/change_password/host/change_password_local_windows/main.yml b/apps/assets/automations/change_password/host/change_password_local_windows/main.yml index 483554a1e..d10c3681e 100644 --- a/apps/assets/automations/change_password/host/change_password_local_windows/main.yml +++ b/apps/assets/automations/change_password/host/change_password_local_windows/main.yml @@ -1,4 +1,5 @@ - hosts: demo + gather_facts: no tasks: - name: ping ping: diff --git a/apps/assets/automations/change_password/manager.py b/apps/assets/automations/change_password/manager.py index ea0bb9a26..aa86c6d08 100644 --- a/apps/assets/automations/change_password/manager.py +++ b/apps/assets/automations/change_password/manager.py @@ -34,7 +34,7 @@ class ChangePasswordManager(BasePlaybookManager): automation.change_password_method in self.id_method_mapper if not change_password_enabled: - host.exclude = _('Change password disabled') + host['exclude'] = _('Change password disabled') return [host] hosts = [] @@ -60,14 +60,18 @@ class ChangePasswordManager(BasePlaybookManager): playbook = [] for method_id, host_names in self.method_hosts_mapper.items(): method = self.id_method_mapper[method_id] - playbook_dir_path = method['dir'] - playbook_dir_name = os.path.dirname(playbook_dir_path) - shutil.copytree(playbook_dir_path, self.playbook_dir_path) - sub_playbook_path = os.path.join(self.playbook_dir_path, playbook_dir_name, 'main.yml') + method_playbook_dir_path = method['dir'] + method_playbook_dir_name = os.path.basename(method_playbook_dir_path) + sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) + shutil.copytree(method_playbook_dir_path, sub_playbook_dir) + sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') with open(sub_playbook_path, 'r') as f: host_playbook_play = yaml.safe_load(f) + if isinstance(host_playbook_play, list): + host_playbook_play = host_playbook_play[0] + plays = [] for name in host_names: play = deepcopy(host_playbook_play) @@ -75,17 +79,24 @@ class ChangePasswordManager(BasePlaybookManager): plays.append(play) with open(sub_playbook_path, 'w') as f: - yaml.safe_dump(plays, f, default_flow_style=False) + yaml.safe_dump(plays, f) playbook.append({ 'name': method['name'], - 'import_playbook': playbook_dir_name + '/' + 'main.yml' + 'import_playbook': os.path.join(method_playbook_dir_name, 'main.yml') }) with open(self.playbook_path, 'w') as f: - yaml.safe_dump(playbook, f, default_flow_style=False) + yaml.safe_dump(playbook, f) print("Generate playbook done: " + self.playbook_path) + def get_runner(self): + return PlaybookRunner( + self.inventory_path, + self.playbook_path, + self.playbook_dir_path + ) + diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index f99defdd8..8064ae58f 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -1,7 +1,18 @@ # from .backup.manager import AccountBackupExecutionManager # # +from .change_password.manager import ChangePasswordManager + + class ExecutionManager: - manager_type = { + manager_type_mapper = { + 'change_password': ChangePasswordManager, } + def __init__(self, execution): + self.execution = execution + self._runner = self.manager_type_mapper[execution.automation.type](execution) + + def run(self, **kwargs): + return self._runner.run(**kwargs) + diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index ba0d393ea..e440429f4 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -58,9 +58,8 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): except AttributeError: eid = str(uuid.uuid4()) - execution = AutomationExecution.objects.create( - id=eid, strategy=self, trigger=trigger, - snapshot=self.to_attr_json(), + execution = self.executions.create( + id=eid, trigger=trigger, ) return execution.start() diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py index 937d0d95c..afad1f069 100644 --- a/apps/assets/models/label.py +++ b/apps/assets/models/label.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- # -import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgModelMixin + +from orgs.mixins.models import JMSOrgBaseModel -class Label(OrgModelMixin): +class Label(JMSOrgBaseModel): SYSTEM_CATEGORY = "S" USER_CATEGORY = "U" CATEGORY_CHOICES = ( diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py index 1f15ea72d..ac75ff645 100644 --- a/apps/common/drf/parsers/base.py +++ b/apps/common/drf/parsers/base.py @@ -39,7 +39,7 @@ class BaseFileParser(BaseParser): @abc.abstractmethod def generate_rows(self, stream_data): - raise NotImplemented + raise NotImplementedError def get_column_titles(self, rows): return next(rows) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 5528f1a14..42cd7320c 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -63,7 +63,7 @@ class JMSInventory: host = { 'name': asset.name, 'asset': { - 'id': asset.id, 'name': asset.name, 'ip': asset.ip, + 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, 'protocol': asset.protocol, 'port': asset.port, 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], @@ -125,7 +125,7 @@ class JMSInventory: if not account_selected: if self.account_policy in ['privileged_must', 'privileged_first']: - account_matched = list(filter(lambda account: account.is_privileged, accounts)) + account_matched = list(filter(lambda account: account.privileged, accounts)) account_selected = account_matched[0] if account_matched else None if not account_selected and self.account_policy == 'privileged_first': @@ -152,8 +152,8 @@ class JMSInventory: exclude_hosts = list(filter(lambda x: x.get('exclude'), hosts)) if exclude_hosts: print(_("Skip hosts below:")) - for host in exclude_hosts: - print(" {}:\t{}".format(host['name'], host['exclude'])) + for i, host in enumerate(exclude_hosts, start=1): + print("{}: [{}] \t{}".format(i, host['name'], host['exclude'])) hosts = list(filter(lambda x: not x.get('exclude'), hosts)) data = {'all': {'hosts': {}}} diff --git a/apps/ops/models/base.py b/apps/ops/models/base.py index e91af13a6..4ff69277f 100644 --- a/apps/ops/models/base.py +++ b/apps/ops/models/base.py @@ -29,10 +29,10 @@ class BaseAnsibleTask(PeriodTaskModelMixin, JMSOrgBaseModel): return inv def get_register_task(self): - raise NotImplemented + raise NotImplementedError def to_json(self): - raise NotImplemented + raise NotImplementedError def create_execution(self): execution = self.executions.create() @@ -71,7 +71,7 @@ class BaseAnsibleExecution(models.Model): return os.path.join(self.private_dir, 'inventory', 'hosts') def get_runner(self): - raise NotImplemented + raise NotImplementedError def finish_task(self): self.date_finished = timezone.now() diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index 7b825b748..a4e1c7085 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -5,11 +5,11 @@ from collections import defaultdict from functools import partial from django.dispatch import receiver +from django.conf import settings from django.utils.functional import LazyObject -from django.db.models.signals import m2m_changed -from django.db.models.signals import post_save, pre_delete +from django.db.models.signals import post_save, pre_delete, m2m_changed -from orgs.utils import tmp_to_org +from orgs.utils import tmp_to_org, set_to_default_org from orgs.models import Organization from orgs.hands import set_current_org, Node, get_current_org from perms.models import AssetPermission @@ -44,6 +44,8 @@ def expire_orgs_mapping_for_memory(org_id): @receiver(django_ready) def subscribe_orgs_mapping_expire(sender, **kwargs): logger.debug("Start subscribe for expire orgs mapping from memory") + if settings.DEBUG: + set_to_default_org() def keep_subscribe_org_mapping(): orgs_mapping_for_memory_pub_sub.subscribe( From 1d757ec19a46cd47a0a2b06b58542a277bb22a90 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 10 Oct 2022 15:07:36 +0800 Subject: [PATCH 182/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=94=B9?= =?UTF-8?q?=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../automations/change_password/manager.py | 26 ++-- .../migrations/0107_auto_20221010_0959.py | 116 ++++++++++++++++++ .../ops/migrations/0026_auto_20221009_2050.py | 100 +++++++++++++++ 3 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 apps/assets/migrations/0107_auto_20221010_0959.py create mode 100644 apps/ops/migrations/0026_auto_20221009_2050.py diff --git a/apps/assets/automations/change_password/manager.py b/apps/assets/automations/change_password/manager.py index aa86c6d08..6c315cb82 100644 --- a/apps/assets/automations/change_password/manager.py +++ b/apps/assets/automations/change_password/manager.py @@ -6,7 +6,7 @@ from collections import defaultdict import yaml from django.utils.translation import gettext as _ -from ops.ansible import PlaybookRunner, JMSInventory +from ops.ansible import PlaybookRunner from ..base.manager import BasePlaybookManager from assets.automations.methods import platform_automation_methods @@ -19,6 +19,7 @@ class ChangePasswordManager(BasePlaybookManager): for method in platform_automation_methods } self.method_hosts_mapper = defaultdict(list) + self.playbooks = [] def host_duplicator(self, host, asset=None, account=None, platform=None, **kwargs): accounts = asset.accounts.all() @@ -72,19 +73,23 @@ class ChangePasswordManager(BasePlaybookManager): if isinstance(host_playbook_play, list): host_playbook_play = host_playbook_play[0] - plays = [] - for name in host_names: + step = 10 + hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)] + for i, hosts in enumerate(hosts_grouped): + plays = [] play = deepcopy(host_playbook_play) - play['hosts'] = name + play['hosts'] = ':'.join(hosts) plays.append(play) - with open(sub_playbook_path, 'w') as f: - yaml.safe_dump(plays, f) + playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) + with open(playbook_path, 'w') as f: + yaml.safe_dump(plays, f) + self.playbooks.append(playbook_path) - playbook.append({ - 'name': method['name'], - 'import_playbook': os.path.join(method_playbook_dir_name, 'main.yml') - }) + playbook.append({ + 'name': method['name'] + ' for part {}'.format(i), + 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) + }) with open(self.playbook_path, 'w') as f: yaml.safe_dump(playbook, f) @@ -99,4 +104,3 @@ class ChangePasswordManager(BasePlaybookManager): ) - diff --git a/apps/assets/migrations/0107_auto_20221010_0959.py b/apps/assets/migrations/0107_auto_20221010_0959.py new file mode 100644 index 000000000..f9cace60f --- /dev/null +++ b/apps/assets/migrations/0107_auto_20221010_0959.py @@ -0,0 +1,116 @@ +# Generated by Django 3.2.14 on 2022-10-10 01:59 + +import common.db.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0106_auto_20220916_1556'), + ] + + operations = [ + migrations.CreateModel( + name='BaseAutomation', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('is_periodic', models.BooleanField(default=False)), + ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), + ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), + ('accounts', models.JSONField(default=list, verbose_name='Accounts')), + ('type', models.CharField(max_length=16, verbose_name='Type')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('assets', models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets')), + ('nodes', models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes')), + ], + options={ + 'verbose_name': 'Automation plan', + 'unique_together': {('org_id', 'name')}, + }, + ), + migrations.AddField( + model_name='label', + name='created_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), + ), + migrations.AddField( + model_name='label', + name='date_updated', + field=models.DateTimeField(auto_now=True, verbose_name='Date updated'), + ), + migrations.AddField( + model_name='label', + name='updated_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), + ), + migrations.CreateModel( + name='DiscoveryAutomation', + fields=[ + ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ], + options={ + 'verbose_name': 'Discovery strategy', + }, + bases=('assets.baseautomation',), + ), + migrations.CreateModel( + name='ReconcileAutomation', + fields=[ + ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ], + options={ + 'verbose_name': 'Reconcile strategy', + }, + bases=('assets.baseautomation',), + ), + migrations.CreateModel( + name='VerifyAutomation', + fields=[ + ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ], + options={ + 'verbose_name': 'Verify strategy', + }, + bases=('assets.baseautomation',), + ), + migrations.CreateModel( + name='AutomationExecution', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(default='pending', max_length=16)), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), + ('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')), + ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), + ('automation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy')), + ], + options={ + 'verbose_name': 'Automation strategy execution', + }, + ), + migrations.CreateModel( + name='ChangePasswordAutomation', + fields=[ + ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ('password', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), + ('recipients', models.ManyToManyField(blank=True, related_name='recipients_change_auth_strategy', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), + ], + options={ + 'verbose_name': 'Change auth strategy', + }, + bases=('assets.baseautomation',), + ), + ] diff --git a/apps/ops/migrations/0026_auto_20221009_2050.py b/apps/ops/migrations/0026_auto_20221009_2050.py new file mode 100644 index 000000000..699246531 --- /dev/null +++ b/apps/ops/migrations/0026_auto_20221009_2050.py @@ -0,0 +1,100 @@ +# Generated by Django 3.2.14 on 2022-10-09 12:50 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0106_auto_20220916_1556'), + ('ops', '0025_auto_20221008_1631'), + ] + + operations = [ + migrations.CreateModel( + name='Playbook', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('is_periodic', models.BooleanField(default=False)), + ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), + ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), + ('account', models.CharField(default='root', max_length=128, verbose_name='Account')), + ('account_policy', models.CharField(default='root', max_length=128, verbose_name='Account policy')), + ('date_last_run', models.DateTimeField(null=True, verbose_name='Date last run')), + ('path', models.FilePathField(max_length=1024, verbose_name='Playbook')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterField( + model_name='adhocexecution', + name='date_finished', + field=models.DateTimeField(null=True, verbose_name='Date finished'), + ), + migrations.CreateModel( + name='PlaybookTemplate', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('path', models.FilePathField(verbose_name='Path')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ], + options={ + 'verbose_name': 'Playbook template', + 'ordering': ['name'], + 'unique_together': {('org_id', 'name')}, + }, + ), + migrations.CreateModel( + name='PlaybookExecution', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(default='running', max_length=16, verbose_name='Status')), + ('result', models.JSONField(blank=True, null=True, verbose_name='Result')), + ('summary', models.JSONField(default=dict, verbose_name='Summary')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), + ('path', models.FilePathField(max_length=1024, verbose_name='Run dir')), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ops.playbook', verbose_name='Task')), + ], + options={ + 'ordering': ['-date_start'], + 'abstract': False, + }, + ), + migrations.AddField( + model_name='playbook', + name='last_execution', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbookexecution', verbose_name='Last execution'), + ), + migrations.AddField( + model_name='playbook', + name='owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Owner'), + ), + migrations.AddField( + model_name='playbook', + name='template', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbooktemplate', verbose_name='Template'), + ), + ] From 6e0d211645df57ca696732b39a4713cb6f24c82e Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Mon, 10 Oct 2022 17:08:06 +0800 Subject: [PATCH 183/488] perf: automation migrate --- .../{0107_auto_20221010_0959.py => 0108_migrate_automation.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/assets/migrations/{0107_auto_20221010_0959.py => 0108_migrate_automation.py} (99%) diff --git a/apps/assets/migrations/0107_auto_20221010_0959.py b/apps/assets/migrations/0108_migrate_automation.py similarity index 99% rename from apps/assets/migrations/0107_auto_20221010_0959.py rename to apps/assets/migrations/0108_migrate_automation.py index f9cace60f..3bce64e4e 100644 --- a/apps/assets/migrations/0107_auto_20221010_0959.py +++ b/apps/assets/migrations/0108_migrate_automation.py @@ -11,7 +11,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0106_auto_20220916_1556'), + ('assets', '0107_account_history'), ] operations = [ From 9198c93fcf27d1cc64b39a22e31f6e8bd74b08d8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 10 Oct 2022 20:56:13 +0800 Subject: [PATCH 184/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20ansible=20?= =?UTF-8?q?change=20password?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/change_password_mysql/main.yml | 63 ++++++++++++------- .../change_password_postgresql/main.yml | 51 ++++++++++++--- .../roles/change_password/tasks/main.yml | 27 -------- apps/ops/ansible/inventory.py | 7 ++- apps/orgs/models.py | 4 +- apps/perms/urls/asset_permission.py | 2 +- 6 files changed, 92 insertions(+), 62 deletions(-) delete mode 100644 apps/assets/automations/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml diff --git a/apps/assets/automations/change_password/database/change_password_mysql/main.yml b/apps/assets/automations/change_password/database/change_password_mysql/main.yml index 483554a1e..b42251300 100644 --- a/apps/assets/automations/change_password/database/change_password_mysql/main.yml +++ b/apps/assets/automations/change_password/database/change_password_mysql/main.yml @@ -1,29 +1,46 @@ -- hosts: demo +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + jms_account: + username: root + password: redhat + jms_asset: + address: 127.0.0.1 + port: 3306 + account: + username: web1 + password: jumpserver + tasks: - - name: ping - ping: + - name: Test MySQL connection + community.mysql.mysql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version + register: db_info - #- name: print variables - # debug: - # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + - name: MySQL version + debug: + var: db_info.version.full - - name: Change password - user: + - name: Change MySQL password + community.mysql.mysql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - - - name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key + password: "{{ account.secret }}" + host: "%" + when: db_info is succeeded - name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko + community.mysql.mysql_info: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version diff --git a/apps/assets/automations/change_password/database/change_password_postgresql/main.yml b/apps/assets/automations/change_password/database/change_password_postgresql/main.yml index 402c7fa8d..0180c559c 100644 --- a/apps/assets/automations/change_password/database/change_password_postgresql/main.yml +++ b/apps/assets/automations/change_password/database/change_password_postgresql/main.yml @@ -1,10 +1,45 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} +- hosts: mysql + gather_facts: no vars: + ansible_python_interpreter: /usr/local/bin/python + jms_account: + username: postgre + password: postgre + jms_asset: + address: 127.0.0.1 + port: 5432 account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_password -{% endfor %} + username: web1 + secret: jumpserver + + tasks: + - name: Test PostgreSQL connection + community.postgresql.postgresql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + register: db_info + + - name: Display PostgreSQL version + debug: + var: db_info.version.full + + - name: Change PostgreSQL password + community.postgresql.postgresql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + comment: Updated by jumpserver + state: present + when: db_info is succeeded + + - name: Verify password + community.postgresql.postgresql_info: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" diff --git a/apps/assets/automations/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml b/apps/assets/automations/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml deleted file mode 100644 index 903cd9115..000000000 --- a/apps/assets/automations/change_password/database/change_password_postgresql/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 42cd7320c..9d4498515 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -62,13 +62,16 @@ class JMSInventory: def asset_to_host(self, asset, account, automation, protocols): host = { 'name': asset.name, - 'asset': { + 'jms_asset': { 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, 'protocol': asset.protocol, 'port': asset.port, 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, - 'exclude': '' + 'jms_account': { + 'id': str(account.id), 'username': account.username, + 'secret': account.secret, 'secret_type': account.secret_type + } if account else None } ansible_connection = automation.ansible_config.get('ansible_connection', 'ssh') gateway = None diff --git a/apps/orgs/models.py b/apps/orgs/models.py index d5e3ae617..7c7babd76 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -78,7 +78,9 @@ class Organization(OrgRoleMixin, models.Model): ROOT_ID = '00000000-0000-0000-0000-000000000000' ROOT_NAME = _('GLOBAL') DEFAULT_ID = '00000000-0000-0000-0000-000000000002' - DEFAULT_NAME = 'Default' + DEFAULT_NAME = _('DEFAULT') + SYSTEM_ID = '00000000-0000-0000-0000-000000000004' + SYSTEM_NAME = _('SYSTEM') orgs_mapping = None class Meta: diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 0ef87f606..cc8e10f36 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -65,7 +65,7 @@ user_permission_urlpatterns = [ path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), # 用户登录资产的特殊账号, @INPUT, @USER 等 path('/assets/special-accounts/', api.UserGrantedAssetSpecialAccountsApi.as_view(), name='user-special-accounts'), - path('/assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'), + path('assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'), ] user_group_permission_urlpatterns = [ From 22e211625ede30e945be013f4fe11d04d6d901f9 Mon Sep 17 00:00:00 2001 From: feng626 <1304903146@qq.com> Date: Tue, 11 Oct 2022 10:50:39 +0800 Subject: [PATCH 185/488] fix: platform 500 --- apps/assets/automations/__init__.py | 2 +- apps/perms/urls/asset_permission.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/automations/__init__.py b/apps/assets/automations/__init__.py index 478e9740d..ae4bc0377 100644 --- a/apps/assets/automations/__init__.py +++ b/apps/assets/automations/__init__.py @@ -1 +1 @@ -from .methods import platform_automation_methods +from .methods import * diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 0ef87f606..cc8e10f36 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -65,7 +65,7 @@ user_permission_urlpatterns = [ path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), # 用户登录资产的特殊账号, @INPUT, @USER 等 path('/assets/special-accounts/', api.UserGrantedAssetSpecialAccountsApi.as_view(), name='user-special-accounts'), - path('/assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'), + path('assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'), ] user_group_permission_urlpatterns = [ From 85a6f29a0aecd999cd71178f74571f74f031a13a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 12 Oct 2022 18:08:57 +0800 Subject: [PATCH 186/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20playbook?= =?UTF-8?q?=20=E4=BB=BB=E5=8A=A1=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/__init__.py | 2 +- apps/assets/automations/base/manager.py | 139 +++++++++++++++--- .../{change_password_mysql => mysql}/main.yml | 0 .../manifest.yml | 0 .../main.yml | 0 .../manifest.yml | 0 .../main.yml | 16 +- .../manifest.yml | 0 .../main.yml | 0 .../manifest.yml | 0 .../roles/change_password/tasks/main.yml | 0 .../{change_password_aix => aix}/main.yml | 0 .../{change_password_aix => aix}/manifest.yml | 0 .../{change_password_linux => linux}/main.yml | 0 .../manifest.yml | 0 .../main.yml | 0 .../manifest.yml | 0 .../automations/change_password/manager.py | 97 +++--------- .../__init__.py | 0 .../gather_facts/database/mysql/main.yml | 28 ++++ .../gather_facts/database/mysql/manifest.yml | 6 + .../gather_facts/database/postgresql/main.yml | 28 ++++ .../database/postgresql/manifest.yml | 6 + .../gather_facts/database/sqlserver/main.yml | 10 ++ .../database/sqlserver/manifest.yml | 8 + .../roles/change_password/tasks/main.yml | 27 ++++ .../gather_facts/demo_inventory.txt | 2 + .../gather_facts/host/posix/main.yml | 19 +++ .../gather_facts/host/posix/manifest.yml | 8 + .../gather_facts/host/windows/main.yml | 24 +++ .../gather_facts/host/windows/manifest.yml | 7 + .../automations/gather_facts/manager.py | 77 ++++++++++ .../generate_playbook/change_password.py | 106 ------------- .../automations/generate_playbook/verify.py | 86 ----------- apps/assets/automations/ping/__init__.py | 0 .../automations/ping/database/mysql/main.yml | 20 +++ .../ping/database/mysql/manifest.yml | 6 + .../ping/database/postgresql/main.yml | 23 +++ .../ping/database/postgresql/manifest.yml | 6 + .../automations/ping/demo_inventory.txt | 2 + .../automations/ping/host/posix/main.yml | 5 + .../automations/ping/host/posix/manifest.yml | 8 + .../automations/ping/host/windows/main.yml | 5 + .../ping/host/windows/manifest.yml | 7 + apps/assets/automations/ping/manager.py | 75 ++++++++++ .../models/automations/change_secret.py | 4 + apps/ops/ansible/inventory.py | 30 ++-- apps/ops/ansible/runner.py | 6 +- 48 files changed, 585 insertions(+), 308 deletions(-) rename apps/assets/automations/change_password/database/{change_password_mysql => mysql}/main.yml (100%) rename apps/assets/automations/change_password/database/{change_password_mysql => mysql}/manifest.yml (100%) rename apps/assets/automations/change_password/database/{change_password_oracle => oracle}/main.yml (100%) rename apps/assets/automations/change_password/database/{change_password_oracle => oracle}/manifest.yml (100%) rename apps/assets/automations/change_password/database/{change_password_postgresql => postgresql}/main.yml (79%) rename apps/assets/automations/change_password/database/{change_password_postgresql => postgresql}/manifest.yml (100%) rename apps/assets/automations/change_password/database/{change_password_sqlserver => sqlserver}/main.yml (100%) rename apps/assets/automations/change_password/database/{change_password_sqlserver => sqlserver}/manifest.yml (100%) rename apps/assets/automations/change_password/database/{change_password_sqlserver => sqlserver}/roles/change_password/tasks/main.yml (100%) rename apps/assets/automations/change_password/host/{change_password_aix => aix}/main.yml (100%) rename apps/assets/automations/change_password/host/{change_password_aix => aix}/manifest.yml (100%) rename apps/assets/automations/change_password/host/{change_password_linux => linux}/main.yml (100%) rename apps/assets/automations/change_password/host/{change_password_linux => linux}/manifest.yml (100%) rename apps/assets/automations/change_password/host/{change_password_local_windows => windows}/main.yml (100%) rename apps/assets/automations/change_password/host/{change_password_local_windows => windows}/manifest.yml (100%) rename apps/assets/automations/{generate_playbook => gather_facts}/__init__.py (100%) create mode 100644 apps/assets/automations/gather_facts/database/mysql/main.yml create mode 100644 apps/assets/automations/gather_facts/database/mysql/manifest.yml create mode 100644 apps/assets/automations/gather_facts/database/postgresql/main.yml create mode 100644 apps/assets/automations/gather_facts/database/postgresql/manifest.yml create mode 100644 apps/assets/automations/gather_facts/database/sqlserver/main.yml create mode 100644 apps/assets/automations/gather_facts/database/sqlserver/manifest.yml create mode 100644 apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml create mode 100644 apps/assets/automations/gather_facts/demo_inventory.txt create mode 100644 apps/assets/automations/gather_facts/host/posix/main.yml create mode 100644 apps/assets/automations/gather_facts/host/posix/manifest.yml create mode 100644 apps/assets/automations/gather_facts/host/windows/main.yml create mode 100644 apps/assets/automations/gather_facts/host/windows/manifest.yml create mode 100644 apps/assets/automations/gather_facts/manager.py delete mode 100644 apps/assets/automations/generate_playbook/change_password.py delete mode 100644 apps/assets/automations/generate_playbook/verify.py create mode 100644 apps/assets/automations/ping/__init__.py create mode 100644 apps/assets/automations/ping/database/mysql/main.yml create mode 100644 apps/assets/automations/ping/database/mysql/manifest.yml create mode 100644 apps/assets/automations/ping/database/postgresql/main.yml create mode 100644 apps/assets/automations/ping/database/postgresql/manifest.yml create mode 100644 apps/assets/automations/ping/demo_inventory.txt create mode 100644 apps/assets/automations/ping/host/posix/main.yml create mode 100644 apps/assets/automations/ping/host/posix/manifest.yml create mode 100644 apps/assets/automations/ping/host/windows/main.yml create mode 100644 apps/assets/automations/ping/host/windows/manifest.yml create mode 100644 apps/assets/automations/ping/manager.py diff --git a/apps/assets/automations/__init__.py b/apps/assets/automations/__init__.py index 478e9740d..7c63c916a 100644 --- a/apps/assets/automations/__init__.py +++ b/apps/assets/automations/__init__.py @@ -1 +1 @@ -from .methods import platform_automation_methods +from .methods import platform_automation_methods, filter_platform_methods diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index e55a564bf..626ced70a 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -1,37 +1,65 @@ import os +import shutil +import yaml +from copy import deepcopy +from collections import defaultdict from django.conf import settings from django.utils import timezone +from django.utils.translation import gettext as _ -from ops.ansible import JMSInventory +from common.utils import get_logger +from assets.automations.methods import platform_automation_methods +from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback + +logger = get_logger(__name__) + + +class PlaybookCallback(DefaultCallback): + def playbook_on_stats(self, event_data, **kwargs): + print("\n*** 分任务结果") + super().playbook_on_stats(event_data, **kwargs) class BasePlaybookManager: + bulk_size = 100 ansible_account_policy = 'privileged_first' def __init__(self, execution): self.execution = execution self.automation = execution.automation + self.method_id_meta_mapper = { + method['id']: method + for method in platform_automation_methods + if method['method'] == self.__class__.method_type() + } + # 根据执行方式就行分组, 不同资产的改密、推送等操作可能会使用不同的执行方式 + # 然后根据执行方式分组, 再根据 bulk_size 分组, 生成不同的 playbook + # 避免一个 playbook 中包含太多的主机 + self.method_hosts_mapper = defaultdict(list) + self.playbooks = [] - def get_grouped_assets(self): - return self.automation.all_assets_group_by_platform() + @classmethod + def method_type(cls): + raise NotImplementedError @property - def playbook_dir_path(self): + def runtime_dir(self): ansible_dir = settings.ANSIBLE_DIR path = os.path.join( - ansible_dir, self.automation.type, self.automation.name.replace(' ', '_'), + ansible_dir, self.automation.type, + self.automation.name.replace(' ', '_'), timezone.now().strftime('%Y%m%d_%H%M%S') ) return path @property def inventory_path(self): - return os.path.join(self.playbook_dir_path, 'inventory', 'hosts.json') + return os.path.join(self.runtime_dir, 'inventory', 'hosts.json') @property def playbook_path(self): - return os.path.join(self.playbook_dir_path, 'project', 'main.yml') + return os.path.join(self.runtime_dir, 'project', 'main.yml') def generate(self): self.prepare_playbook_dir() @@ -41,31 +69,108 @@ class BasePlaybookManager: def prepare_playbook_dir(self): inventory_dir = os.path.dirname(self.inventory_path) playbook_dir = os.path.dirname(self.playbook_path) - for d in [inventory_dir, playbook_dir, self.playbook_dir_path]: - print("Create dir: {}".format(d)) + for d in [inventory_dir, playbook_dir]: if not os.path.exists(d): os.makedirs(d, exist_ok=True, mode=0o755) - def inventory_kwargs(self): - raise NotImplementedError + def host_callback(self, host, automation=None, **kwargs): + enabled_attr = '{}_enabled'.format(self.__class__.method_type()) + method_attr = '{}_method'.format(self.__class__.method_type()) + + method_enabled = automation and \ + getattr(automation, enabled_attr) and \ + getattr(automation, method_attr) and \ + getattr(automation, method_attr) in self.method_id_meta_mapper + + if not method_enabled: + host['error'] = _('Change password disabled') + return host + + self.method_hosts_mapper[getattr(automation, method_attr)].append(host['name']) + return host def generate_inventory(self): inventory = JMSInventory( assets=self.automation.get_all_assets(), account_policy=self.ansible_account_policy, - **self.inventory_kwargs() + host_callback=self.host_callback ) inventory.write_to_file(self.inventory_path) - print("Generate inventory done: {}".format(self.inventory_path)) + logger.debug("Generate inventory done: {}".format(self.inventory_path)) def generate_playbook(self): + main_playbook = [] + for method_id, host_names in self.method_hosts_mapper.items(): + method = self.method_id_meta_mapper.get(method_id) + if not method: + logger.error("Method not found: {}".format(method_id)) + continue + method_playbook_dir_path = method['dir'] + method_playbook_dir_name = os.path.basename(method_playbook_dir_path) + sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) + sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') + shutil.copytree(method_playbook_dir_path, sub_playbook_dir) + + with open(sub_playbook_path, 'r') as f: + host_playbook_play = yaml.safe_load(f) + + if isinstance(host_playbook_play, list): + host_playbook_play = host_playbook_play[0] + + hosts_bulked = [host_names[i:i+self.bulk_size] for i in range(0, len(host_names), self.bulk_size)] + for i, hosts in enumerate(hosts_bulked): + plays = [] + play = deepcopy(host_playbook_play) + play['hosts'] = ':'.join(hosts) + plays.append(play) + + playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) + with open(playbook_path, 'w') as f: + yaml.safe_dump(plays, f) + self.playbooks.append(playbook_path) + + main_playbook.append({ + 'name': method['name'] + ' for part {}'.format(i), + 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) + }) + + with open(self.playbook_path, 'w') as f: + yaml.safe_dump(main_playbook, f) + + logger.debug("Generate playbook done: " + self.playbook_path) + + def get_runners(self): + runners = [] + for playbook_path in self.playbooks: + runer = PlaybookRunner( + self.inventory_path, + playbook_path, + self.runtime_dir, + callback=PlaybookCallback(), + ) + runners.append(runer) + return runners + + def on_runner_done(self, runner, cb): raise NotImplementedError - def get_runner(self): - raise NotImplementedError + def on_runner_failed(self, runner, e): + print("Runner failed: {} {}".format(e, self)) def run(self, **kwargs): self.generate() - runner = self.get_runner() - return runner.run(**kwargs) + runners = self.get_runners() + if len(runners) > 1: + print("### 分批次执行开始任务, 总共 {}\n".format(len(runners))) + else: + print(">>> 开始执行任务\n") + for i, runner in enumerate(runners, start=1): + if len(runners) > 1: + print(">>> 开始执行第 {} 批任务".format(i)) + try: + cb = runner.run(**kwargs) + self.on_runner_done(runner, cb) + except Exception as e: + self.on_runner_failed(runner, e) + print('\n\n') diff --git a/apps/assets/automations/change_password/database/change_password_mysql/main.yml b/apps/assets/automations/change_password/database/mysql/main.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_mysql/main.yml rename to apps/assets/automations/change_password/database/mysql/main.yml diff --git a/apps/assets/automations/change_password/database/change_password_mysql/manifest.yml b/apps/assets/automations/change_password/database/mysql/manifest.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_mysql/manifest.yml rename to apps/assets/automations/change_password/database/mysql/manifest.yml diff --git a/apps/assets/automations/change_password/database/change_password_oracle/main.yml b/apps/assets/automations/change_password/database/oracle/main.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_oracle/main.yml rename to apps/assets/automations/change_password/database/oracle/main.yml diff --git a/apps/assets/automations/change_password/database/change_password_oracle/manifest.yml b/apps/assets/automations/change_password/database/oracle/manifest.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_oracle/manifest.yml rename to apps/assets/automations/change_password/database/oracle/manifest.yml diff --git a/apps/assets/automations/change_password/database/change_password_postgresql/main.yml b/apps/assets/automations/change_password/database/postgresql/main.yml similarity index 79% rename from apps/assets/automations/change_password/database/change_password_postgresql/main.yml rename to apps/assets/automations/change_password/database/postgresql/main.yml index 0180c559c..ed4e60abf 100644 --- a/apps/assets/automations/change_password/database/change_password_postgresql/main.yml +++ b/apps/assets/automations/change_password/database/postgresql/main.yml @@ -1,24 +1,26 @@ -- hosts: mysql +- hosts: postgre gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python jms_account: username: postgre - password: postgre + secret: postgre jms_asset: address: 127.0.0.1 port: 5432 + database: testdb account: - username: web1 + username: test secret: jumpserver tasks: - name: Test PostgreSQL connection - community.postgresql.postgresql_info: + community.postgresql.postgresql_ping: login_user: "{{ jms_account.username }}" login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" + login_db: "{{ jms_asset.database }}" register: db_info - name: Display PostgreSQL version @@ -31,15 +33,15 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.database }}" name: "{{ account.username }}" password: "{{ account.secret }}" - comment: Updated by jumpserver - state: present when: db_info is succeeded - name: Verify password - community.postgresql.postgresql_info: + community.postgresql.postgresql_ping: login_user: "{{ account.username }}" login_password: "{{ account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.database }}" diff --git a/apps/assets/automations/change_password/database/change_password_postgresql/manifest.yml b/apps/assets/automations/change_password/database/postgresql/manifest.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_postgresql/manifest.yml rename to apps/assets/automations/change_password/database/postgresql/manifest.yml diff --git a/apps/assets/automations/change_password/database/change_password_sqlserver/main.yml b/apps/assets/automations/change_password/database/sqlserver/main.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_sqlserver/main.yml rename to apps/assets/automations/change_password/database/sqlserver/main.yml diff --git a/apps/assets/automations/change_password/database/change_password_sqlserver/manifest.yml b/apps/assets/automations/change_password/database/sqlserver/manifest.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_sqlserver/manifest.yml rename to apps/assets/automations/change_password/database/sqlserver/manifest.yml diff --git a/apps/assets/automations/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml b/apps/assets/automations/change_password/database/sqlserver/roles/change_password/tasks/main.yml similarity index 100% rename from apps/assets/automations/change_password/database/change_password_sqlserver/roles/change_password/tasks/main.yml rename to apps/assets/automations/change_password/database/sqlserver/roles/change_password/tasks/main.yml diff --git a/apps/assets/automations/change_password/host/change_password_aix/main.yml b/apps/assets/automations/change_password/host/aix/main.yml similarity index 100% rename from apps/assets/automations/change_password/host/change_password_aix/main.yml rename to apps/assets/automations/change_password/host/aix/main.yml diff --git a/apps/assets/automations/change_password/host/change_password_aix/manifest.yml b/apps/assets/automations/change_password/host/aix/manifest.yml similarity index 100% rename from apps/assets/automations/change_password/host/change_password_aix/manifest.yml rename to apps/assets/automations/change_password/host/aix/manifest.yml diff --git a/apps/assets/automations/change_password/host/change_password_linux/main.yml b/apps/assets/automations/change_password/host/linux/main.yml similarity index 100% rename from apps/assets/automations/change_password/host/change_password_linux/main.yml rename to apps/assets/automations/change_password/host/linux/main.yml diff --git a/apps/assets/automations/change_password/host/change_password_linux/manifest.yml b/apps/assets/automations/change_password/host/linux/manifest.yml similarity index 100% rename from apps/assets/automations/change_password/host/change_password_linux/manifest.yml rename to apps/assets/automations/change_password/host/linux/manifest.yml diff --git a/apps/assets/automations/change_password/host/change_password_local_windows/main.yml b/apps/assets/automations/change_password/host/windows/main.yml similarity index 100% rename from apps/assets/automations/change_password/host/change_password_local_windows/main.yml rename to apps/assets/automations/change_password/host/windows/main.yml diff --git a/apps/assets/automations/change_password/host/change_password_local_windows/manifest.yml b/apps/assets/automations/change_password/host/windows/manifest.yml similarity index 100% rename from apps/assets/automations/change_password/host/change_password_local_windows/manifest.yml rename to apps/assets/automations/change_password/host/windows/manifest.yml diff --git a/apps/assets/automations/change_password/manager.py b/apps/assets/automations/change_password/manager.py index 6c315cb82..1991d6854 100644 --- a/apps/assets/automations/change_password/manager.py +++ b/apps/assets/automations/change_password/manager.py @@ -1,44 +1,35 @@ -import os -import shutil from copy import deepcopy from collections import defaultdict -import yaml -from django.utils.translation import gettext as _ - -from ops.ansible import PlaybookRunner from ..base.manager import BasePlaybookManager -from assets.automations.methods import platform_automation_methods class ChangePasswordManager(BasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.id_method_mapper = { - method['id']: method - for method in platform_automation_methods - } self.method_hosts_mapper = defaultdict(list) self.playbooks = [] - def host_duplicator(self, host, asset=None, account=None, platform=None, **kwargs): + @classmethod + def method_type(cls): + return 'change_password' + + def host_callback(self, host, asset=None, account=None, automation=None, **kwargs): + host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs) + if host.get('exclude'): + return host + accounts = asset.accounts.all() if account: accounts = accounts.exclude(id=account.id) + if '*' not in self.automation.accounts: accounts = accounts.filter(username__in=self.automation.accounts) - automation = platform.automation - change_password_enabled = automation and \ - automation.change_password_enabled and \ - automation.change_password_method and \ - automation.change_password_method in self.id_method_mapper - - if not change_password_enabled: - host['exclude'] = _('Change password disabled') - return [host] - - hosts = [] + method_attr = getattr(automation, self.method_type() + '_method') + method_hosts = self.method_hosts_mapper[method_attr] + method_hosts = [h for h in method_hosts if h != host['name']] + inventory_hosts = [] for account in accounts: h = deepcopy(host) h['name'] += '_' + account.username @@ -48,59 +39,15 @@ class ChangePasswordManager(BasePlaybookManager): 'secret_type': account.secret_type, 'secret': account.secret, } - hosts.append(h) - self.method_hosts_mapper[automation.change_password_method].append(h['name']) - return hosts + inventory_hosts.append(h) + method_hosts.append(h['name']) + self.method_hosts_mapper[method_attr] = method_hosts + return inventory_hosts - def inventory_kwargs(self): - return { - 'host_duplicator': self.host_duplicator - } + def on_runner_done(self, runner, cb): + pass - def generate_playbook(self): - playbook = [] - for method_id, host_names in self.method_hosts_mapper.items(): - method = self.id_method_mapper[method_id] - method_playbook_dir_path = method['dir'] - method_playbook_dir_name = os.path.basename(method_playbook_dir_path) - sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) - shutil.copytree(method_playbook_dir_path, sub_playbook_dir) - sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') - - with open(sub_playbook_path, 'r') as f: - host_playbook_play = yaml.safe_load(f) - - if isinstance(host_playbook_play, list): - host_playbook_play = host_playbook_play[0] - - step = 10 - hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)] - for i, hosts in enumerate(hosts_grouped): - plays = [] - play = deepcopy(host_playbook_play) - play['hosts'] = ':'.join(hosts) - plays.append(play) - - playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) - with open(playbook_path, 'w') as f: - yaml.safe_dump(plays, f) - self.playbooks.append(playbook_path) - - playbook.append({ - 'name': method['name'] + ' for part {}'.format(i), - 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) - }) - - with open(self.playbook_path, 'w') as f: - yaml.safe_dump(playbook, f) - - print("Generate playbook done: " + self.playbook_path) - - def get_runner(self): - return PlaybookRunner( - self.inventory_path, - self.playbook_path, - self.playbook_dir_path - ) + def on_runner_failed(self, runner, e): + pass diff --git a/apps/assets/automations/generate_playbook/__init__.py b/apps/assets/automations/gather_facts/__init__.py similarity index 100% rename from apps/assets/automations/generate_playbook/__init__.py rename to apps/assets/automations/gather_facts/__init__.py diff --git a/apps/assets/automations/gather_facts/database/mysql/main.yml b/apps/assets/automations/gather_facts/database/mysql/main.yml new file mode 100644 index 000000000..e7ba00880 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/mysql/main.yml @@ -0,0 +1,28 @@ +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + jms_account: + username: root + secret: redhat + jms_asset: + address: 127.0.0.1 + port: 3306 + + tasks: + - name: Gather facts info + community.mysql.mysql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + register: db_info + + - name: Get info + set_fact: + info: + version: "{{ db_info.version.full }}" + + - debug: + var: db_info + diff --git a/apps/assets/automations/gather_facts/database/mysql/manifest.yml b/apps/assets/automations/gather_facts/database/mysql/manifest.yml new file mode 100644 index 000000000..33109b29b --- /dev/null +++ b/apps/assets/automations/gather_facts/database/mysql/manifest.yml @@ -0,0 +1,6 @@ +id: gather_facts_mysql +name: Gather facts from MySQL +category: database +type: + - mysql +method: gather_facts diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml new file mode 100644 index 000000000..6af98366b --- /dev/null +++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml @@ -0,0 +1,28 @@ +- hosts: postgre + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + jms_account: + username: postgre + secret: postgre + jms_asset: + address: 127.0.0.1 + port: 5432 + database: testdb + account: + username: test + secret: jumpserver + + tasks: + - name: Test PostgreSQL connection + community.postgresql.postgresql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_db: "{{ jms_asset.database }}" + register: db_info + + - name: Debug it + debug: + var: db_info diff --git a/apps/assets/automations/gather_facts/database/postgresql/manifest.yml b/apps/assets/automations/gather_facts/database/postgresql/manifest.yml new file mode 100644 index 000000000..19bf255de --- /dev/null +++ b/apps/assets/automations/gather_facts/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: gather_facts_postgresql +name: Gather facts for PostgreSQL +category: database +type: + - postgresql +method: gather_facts diff --git a/apps/assets/automations/gather_facts/database/sqlserver/main.yml b/apps/assets/automations/gather_facts/database/sqlserver/main.yml new file mode 100644 index 000000000..402c7fa8d --- /dev/null +++ b/apps/assets/automations/gather_facts/database/sqlserver/main.yml @@ -0,0 +1,10 @@ +{% for account in accounts %} +- hosts: {{ account.asset.name }} + vars: + account: + username: {{ account.username }} + password: {{ account.password }} + public_key: {{ account.public_key }} + roles: + - change_password +{% endfor %} diff --git a/apps/assets/automations/gather_facts/database/sqlserver/manifest.yml b/apps/assets/automations/gather_facts/database/sqlserver/manifest.yml new file mode 100644 index 000000000..3c4c82de4 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/sqlserver/manifest.yml @@ -0,0 +1,8 @@ +id: gather_facts_sqlserver +name: Change password for SQLServer +version: 1 +category: database +type: + - sqlserver +method: gather_facts + diff --git a/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml b/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml new file mode 100644 index 000000000..903cd9115 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml @@ -0,0 +1,27 @@ +- name: ping + ping: + +#- name: print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.password }}" + +- name: Change password + user: + name: "{{ account.username }}" + password: "{{ account.password | password_hash('des') }}" + update_password: always + when: account.password + +- name: Change public key + authorized_key: + user: "{{ account.username }}" + key: "{{ account.public_key }}" + state: present + when: account.public_key + +- name: Verify password + ping: + vars: + ansible_user: "{{ account.username }}" + ansible_pass: "{{ account.password }}" + ansible_ssh_connection: paramiko diff --git a/apps/assets/automations/gather_facts/demo_inventory.txt b/apps/assets/automations/gather_facts/demo_inventory.txt new file mode 100644 index 000000000..ed011eae2 --- /dev/null +++ b/apps/assets/automations/gather_facts/demo_inventory.txt @@ -0,0 +1,2 @@ +# all base inventory in base/base_inventory.txt +asset_name(ip) ...base_inventory_vars diff --git a/apps/assets/automations/gather_facts/host/posix/main.yml b/apps/assets/automations/gather_facts/host/posix/main.yml new file mode 100644 index 000000000..6e900fccb --- /dev/null +++ b/apps/assets/automations/gather_facts/host/posix/main.yml @@ -0,0 +1,19 @@ +- hosts: website + gather_facts: yes + tasks: + - name: Get info + set_fact: + info: + arch: "{{ ansible_architecture }}" + distribution: "{{ ansible_distribution }}" + distribution_version: "{{ ansible_distribution_version }}" + kernel: "{{ ansible_kernel }}" + vendor: "{{ ansible_system_vendor }}" + model: "{{ ansible_product_name }}" + sn: "{{ ansible_product_serial }}" + cpu_vcpus: "{{ ansible_processor_vcpus }}" + memory: "{{ ansible_memtotal_mb }}" + disk_total: "{{ (ansible_mounts | map(attribute='size_total') | sum / 1024 / 1024 / 1024) | round(2) }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_facts/host/posix/manifest.yml b/apps/assets/automations/gather_facts/host/posix/manifest.yml new file mode 100644 index 000000000..e5622cf28 --- /dev/null +++ b/apps/assets/automations/gather_facts/host/posix/manifest.yml @@ -0,0 +1,8 @@ +id: gather_facts_posix +name: Gather posix facts +category: host +type: + - linux + - windows + - unix +method: gather_facts diff --git a/apps/assets/automations/gather_facts/host/windows/main.yml b/apps/assets/automations/gather_facts/host/windows/main.yml new file mode 100644 index 000000000..723aa7720 --- /dev/null +++ b/apps/assets/automations/gather_facts/host/windows/main.yml @@ -0,0 +1,24 @@ +- hosts: windows + gather_facts: yes + tasks: +# - name: Gather facts windows +# setup: +# register: facts +# +# - debug: +# var: facts + - name: Get info + set_fact: + info: + arch: "{{ ansible_architecture2 }}" + distribution: "{{ ansible_distribution }}" + distribution_version: "{{ ansible_distribution_version }}" + kernel: "{{ ansible_kernel }}" + vendor: "{{ ansible_system_vendor }}" + model: "{{ ansible_product_name }}" + sn: "{{ ansible_product_serial }}" + cpu_vcpus: "{{ ansible_processor_vcpus }}" + memory: "{{ ansible_memtotal_mb }}" +t + - debug: + var: info diff --git a/apps/assets/automations/gather_facts/host/windows/manifest.yml b/apps/assets/automations/gather_facts/host/windows/manifest.yml new file mode 100644 index 000000000..929a6626f --- /dev/null +++ b/apps/assets/automations/gather_facts/host/windows/manifest.yml @@ -0,0 +1,7 @@ +id: gather_facts_windows +name: Gather facts windows +version: 1 +method: gather_facts +category: host +type: + - windows diff --git a/apps/assets/automations/gather_facts/manager.py b/apps/assets/automations/gather_facts/manager.py new file mode 100644 index 000000000..7b56d728e --- /dev/null +++ b/apps/assets/automations/gather_facts/manager.py @@ -0,0 +1,77 @@ +import os +import shutil +from copy import deepcopy +from collections import defaultdict + +import yaml +from django.utils.translation import gettext as _ + +from ops.ansible import PlaybookRunner +from ..base.manager import BasePlaybookManager +from assets.automations.methods import platform_automation_methods + + +class GatherFactsManager(BasePlaybookManager): + method_name = 'gather_facts' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.id_method_mapper = { + method['id']: method + for method in platform_automation_methods + if method['method'] == self.method_name + } + self.method_hosts_mapper = defaultdict(list) + self.playbooks = [] + + def inventory_kwargs(self): + return { + } + + def generate_playbook(self): + playbook = [] + for method_id, host_names in self.method_hosts_mapper.items(): + method = self.id_method_mapper[method_id] + method_playbook_dir_path = method['dir'] + method_playbook_dir_name = os.path.basename(method_playbook_dir_path) + sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) + shutil.copytree(method_playbook_dir_path, sub_playbook_dir) + sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') + + with open(sub_playbook_path, 'r') as f: + host_playbook_play = yaml.safe_load(f) + + if isinstance(host_playbook_play, list): + host_playbook_play = host_playbook_play[0] + + step = 10 + hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)] + for i, hosts in enumerate(hosts_grouped): + plays = [] + play = deepcopy(host_playbook_play) + play['hosts'] = ':'.join(hosts) + plays.append(play) + + playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) + with open(playbook_path, 'w') as f: + yaml.safe_dump(plays, f) + self.playbooks.append(playbook_path) + + playbook.append({ + 'name': method['name'] + ' for part {}'.format(i), + 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) + }) + + with open(self.playbook_path, 'w') as f: + yaml.safe_dump(playbook, f) + + print("Generate playbook done: " + self.playbook_path) + + def get_runner(self): + return PlaybookRunner( + self.inventory_path, + self.playbook_path, + self.runtime_dir + ) + + diff --git a/apps/assets/automations/generate_playbook/change_password.py b/apps/assets/automations/generate_playbook/change_password.py deleted file mode 100644 index 20c3f0889..000000000 --- a/apps/assets/automations/generate_playbook/change_password.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import yaml -import jinja2 -from typing import List - -from django.conf import settings -from assets.models import Asset -from .base import BaseGeneratePlaybook - - -class GenerateChangePasswordPlaybook(BaseGeneratePlaybook): - - def __init__( - self, assets: List[Asset], strategy, usernames, password='', - private_key='', public_key='', key_strategy='' - ): - super().__init__(assets, strategy) - self.password = password - self.public_key = public_key - self.private_key = private_key - self.key_strategy = key_strategy - self.relation_asset_map = self.get_username_relation_asset_map(usernames) - - def get_username_relation_asset_map(self, usernames): - # TODO 没特权用户的资产 要考虑网关 - - complete_map = { - asset: list(asset.accounts.value_list('username', flat=True)) - for asset in self.assets - } - - if '*' in usernames: - return complete_map - - relation_map = {} - for asset, usernames in complete_map.items(): - usernames = list(set(usernames) & set(usernames)) - if not usernames: - continue - relation_map[asset] = list(set(usernames) & set(usernames)) - return relation_map - - @property - def src_filepath(self): - return os.path.join( - settings.BASE_DIR, 'assets', 'playbooks', 'strategy', - 'change_password', 'roles', self.strategy - ) - - def generate_hosts(self): - host_pathname = os.path.join(self.temp_folder, 'hosts') - with open(host_pathname, 'w', encoding='utf8') as f: - for asset in self.relation_asset_map.keys(): - f.write(f'{asset.name}\n') - - def generate_host_vars(self): - host_vars_pathname = os.path.join(self.temp_folder, 'hosts', 'host_vars') - os.makedirs(host_vars_pathname, exist_ok=True) - for asset, usernames in self.relation_asset_map.items(): - host_vars = { - 'ansible_host': asset.get_target_ip(), - 'ansible_port': asset.get_target_ssh_port(), # TODO 需要根绝协议取端口号 - 'ansible_user': asset.admin_user.username, - 'ansible_pass': asset.admin_user.username, - 'usernames': usernames, - } - pathname = os.path.join(host_vars_pathname, f'{asset.name}.yml') - with open(pathname, 'w', encoding='utf8') as f: - f.write(yaml.dump(host_vars, allow_unicode=True)) - - def generate_secret_key_files(self): - if not self.private_key and not self.public_key: - return - - file_pathname = os.path.join(self.temp_folder, self.strategy, 'files') - public_pathname = os.path.join(file_pathname, 'id_rsa.pub') - private_pathname = os.path.join(file_pathname, 'id_rsa') - - os.makedirs(file_pathname, exist_ok=True) - with open(public_pathname, 'w', encoding='utf8') as f: - f.write(self.public_key) - with open(private_pathname, 'w', encoding='utf8') as f: - f.write(self.private_key) - - def generate_role_main(self): - task_main_pathname = os.path.join(self.temp_folder, 'main.yaml') - context = { - 'password': self.password, - 'key_strategy': self.key_strategy, - 'private_key_file': 'id_rsa' if self.private_key else '', - 'exclusive': 'no' if self.key_strategy == 'all' else 'yes', - 'jms_key': self.public_key.split()[2].strip() if self.public_key else '', - } - with open(task_main_pathname, 'r+', encoding='utf8') as f: - string_var = f.read() - f.seek(0, 0) - response = jinja2.Template(string_var).render(context) - results = yaml.safe_load(response) - f.write(yaml.dump(results, allow_unicode=True)) - - def execute(self): - self.generate_temp_playbook() - self.generate_hosts() - self.generate_host_vars() - self.generate_secret_key_files() - self.generate_role_main() diff --git a/apps/assets/automations/generate_playbook/verify.py b/apps/assets/automations/generate_playbook/verify.py deleted file mode 100644 index 88695b814..000000000 --- a/apps/assets/automations/generate_playbook/verify.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -import yaml -from typing import List - -from django.conf import settings -from assets.models import Asset -from .base import BaseGeneratePlaybook - - -class GenerateVerifyPlaybook(BaseGeneratePlaybook): - - def __init__( - self, assets: List[Asset], strategy, usernames - ): - super().__init__(assets, strategy) - self.relation_asset_map = self.get_account_relation_asset_map(usernames) - - def get_account_relation_asset_map(self, usernames): - # TODO 没特权用户的资产 要考虑网关 - complete_map = { - asset: list(asset.accounts.all()) - for asset in self.assets - } - - if '*' in usernames: - return complete_map - - relation_map = {} - for asset, accounts in complete_map.items(): - account_map = {account.username: account for account in accounts} - accounts = [account_map[i] for i in (set(usernames) & set(account_map))] - if not accounts: - continue - relation_map[asset] = accounts - return relation_map - - @property - def src_filepath(self): - return os.path.join( - settings.BASE_DIR, 'assets', 'playbooks', 'strategy', - 'verify', 'roles', self.strategy - ) - - def generate_hosts(self): - host_pathname = os.path.join(self.temp_folder, 'hosts') - with open(host_pathname, 'w', encoding='utf8') as f: - for asset in self.relation_asset_map.keys(): - f.write(f'{asset.name}\n') - - def generate_host_vars(self): - host_vars_pathname = os.path.join(self.temp_folder, 'hosts', 'host_vars') - os.makedirs(host_vars_pathname, exist_ok=True) - for asset, accounts in self.relation_asset_map.items(): - account_info = [] - for account in accounts: - private_key_filename = f'{asset.name}_{account.username}' if account.private_key else '' - account_info.append({ - 'username': account.username, - 'password': account.password, - 'private_key_filename': private_key_filename, - }) - host_vars = { - 'ansible_host': asset.get_target_ip(), - 'ansible_port': asset.get_target_ssh_port(), # TODO 需要根绝协议取端口号 - 'account_info': account_info, - } - pathname = os.path.join(host_vars_pathname, f'{asset.name}.yml') - with open(pathname, 'w', encoding='utf8') as f: - f.write(yaml.dump(host_vars, allow_unicode=True)) - - def generate_secret_key_files(self): - file_pathname = os.path.join(self.temp_folder, self.strategy, 'files') - os.makedirs(file_pathname, exist_ok=True) - for asset, accounts in self.relation_asset_map.items(): - for account in accounts: - if account.private_key: - path_name = os.path.join(file_pathname, f'{asset.name}_{account.username}') - with open(path_name, 'w', encoding='utf8') as f: - f.write(account.private_key) - - def execute(self): - self.generate_temp_playbook() - self.generate_hosts() - self.generate_host_vars() - self.generate_secret_key_files() - # self.generate_role_main() # TODO Linux 暂时不需要 diff --git a/apps/assets/automations/ping/__init__.py b/apps/assets/automations/ping/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/automations/ping/database/mysql/main.yml b/apps/assets/automations/ping/database/mysql/main.yml new file mode 100644 index 000000000..fab498c76 --- /dev/null +++ b/apps/assets/automations/ping/database/mysql/main.yml @@ -0,0 +1,20 @@ +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + jms_account: + username: root + password: redhat + jms_asset: + address: 127.0.0.1 + port: 3306 + + tasks: + - name: Test MySQL connection + community.mysql.mysql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version + register: db_info diff --git a/apps/assets/automations/ping/database/mysql/manifest.yml b/apps/assets/automations/ping/database/mysql/manifest.yml new file mode 100644 index 000000000..aded00b1f --- /dev/null +++ b/apps/assets/automations/ping/database/mysql/manifest.yml @@ -0,0 +1,6 @@ +id: mysql_ping +name: Ping MySQL +category: database +type: + - mysql +method: ping diff --git a/apps/assets/automations/ping/database/postgresql/main.yml b/apps/assets/automations/ping/database/postgresql/main.yml new file mode 100644 index 000000000..3bc2f7957 --- /dev/null +++ b/apps/assets/automations/ping/database/postgresql/main.yml @@ -0,0 +1,23 @@ +- hosts: postgre + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + jms_account: + username: postgre + secret: postgre + jms_asset: + address: 127.0.0.1 + port: 5432 + database: testdb + account: + username: test + secret: jumpserver + + tasks: + - name: Test PostgreSQL connection + community.postgresql.postgresql_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_db: "{{ jms_asset.database }}" diff --git a/apps/assets/automations/ping/database/postgresql/manifest.yml b/apps/assets/automations/ping/database/postgresql/manifest.yml new file mode 100644 index 000000000..337b2b50d --- /dev/null +++ b/apps/assets/automations/ping/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: ping_postgresql +name: Ping PostgreSQL +category: database +type: + - postgresql +method: ping diff --git a/apps/assets/automations/ping/demo_inventory.txt b/apps/assets/automations/ping/demo_inventory.txt new file mode 100644 index 000000000..dcc7d1b6d --- /dev/null +++ b/apps/assets/automations/ping/demo_inventory.txt @@ -0,0 +1,2 @@ +# all base inventory in base/base_inventory.txt +asset_name(ip)_account_username account={"username": "", "password": "xxx"} ...base_inventory_vars diff --git a/apps/assets/automations/ping/host/posix/main.yml b/apps/assets/automations/ping/host/posix/main.yml new file mode 100644 index 000000000..c4c740367 --- /dev/null +++ b/apps/assets/automations/ping/host/posix/main.yml @@ -0,0 +1,5 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Posix ping + ping: diff --git a/apps/assets/automations/ping/host/posix/manifest.yml b/apps/assets/automations/ping/host/posix/manifest.yml new file mode 100644 index 000000000..b07caa7f8 --- /dev/null +++ b/apps/assets/automations/ping/host/posix/manifest.yml @@ -0,0 +1,8 @@ +id: posix_ping +name: Posix ping +category: host +type: + - linux + - windows + - unix +method: ping diff --git a/apps/assets/automations/ping/host/windows/main.yml b/apps/assets/automations/ping/host/windows/main.yml new file mode 100644 index 000000000..495b82a3d --- /dev/null +++ b/apps/assets/automations/ping/host/windows/main.yml @@ -0,0 +1,5 @@ +- hosts: windows + gather_facts: no + tasks: + - name: Windows ping + win_ping: diff --git a/apps/assets/automations/ping/host/windows/manifest.yml b/apps/assets/automations/ping/host/windows/manifest.yml new file mode 100644 index 000000000..55e336f19 --- /dev/null +++ b/apps/assets/automations/ping/host/windows/manifest.yml @@ -0,0 +1,7 @@ +id: win_ping +name: Windows ping +version: 1 +method: change_password +category: host +type: + - windows diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py new file mode 100644 index 000000000..36017438b --- /dev/null +++ b/apps/assets/automations/ping/manager.py @@ -0,0 +1,75 @@ +import os +import shutil +from copy import deepcopy +from collections import defaultdict + +import yaml +from django.utils.translation import gettext as _ + +from ops.ansible import PlaybookRunner +from ..base.manager import BasePlaybookManager +from assets.automations.methods import platform_automation_methods + + +class ChangePasswordManager(BasePlaybookManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.id_method_mapper = { + method['id']: method + for method in platform_automation_methods + } + self.method_hosts_mapper = defaultdict(list) + self.playbooks = [] + + def inventory_kwargs(self): + return { + 'host_callback': self.host_duplicator + } + + def generate_playbook(self): + playbook = [] + for method_id, host_names in self.method_hosts_mapper.items(): + method = self.id_method_mapper[method_id] + method_playbook_dir_path = method['dir'] + method_playbook_dir_name = os.path.basename(method_playbook_dir_path) + sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) + shutil.copytree(method_playbook_dir_path, sub_playbook_dir) + sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') + + with open(sub_playbook_path, 'r') as f: + host_playbook_play = yaml.safe_load(f) + + if isinstance(host_playbook_play, list): + host_playbook_play = host_playbook_play[0] + + step = 10 + hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)] + for i, hosts in enumerate(hosts_grouped): + plays = [] + play = deepcopy(host_playbook_play) + play['hosts'] = ':'.join(hosts) + plays.append(play) + + playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) + with open(playbook_path, 'w') as f: + yaml.safe_dump(plays, f) + self.playbooks.append(playbook_path) + + playbook.append({ + 'name': method['name'] + ' for part {}'.format(i), + 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) + }) + + with open(self.playbook_path, 'w') as f: + yaml.safe_dump(playbook, f) + + print("Generate playbook done: " + self.playbook_path) + + def get_runner(self): + return PlaybookRunner( + self.inventory_path, + self.playbook_path, + self.runtime_dir + ) + + diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index 4a2c304c2..dc0403a12 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -19,6 +19,10 @@ class ChangePasswordAutomation(BaseAutomation): verbose_name=_("Recipient") ) + def save(self, *args, **kwargs): + self.type = 'change_password' + super().save(*args, **kwargs) + class Meta: verbose_name = _("Change auth strategy") diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 9d4498515..a91126b5f 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -10,18 +10,17 @@ __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, assets, account='', account_policy='smart', host_var_callback=None, host_duplicator=None): + def __init__(self, assets, account='', account_policy='smart', host_callback=None): """ :param assets: :param account: account username name if not set use account_policy :param account_policy: - :param host_var_callback: + :param host_callback: after generate host, call this callback to modify host """ self.assets = self.clean_assets(assets) self.account_username = account self.account_policy = account_policy - self.host_var_callback = host_var_callback - self.host_duplicator = host_duplicator + self.host_callback = host_callback @staticmethod def clean_assets(assets): @@ -100,15 +99,10 @@ class JMSInventory: elif account.secret_type == 'private_key' and account.secret: host['ssh_private_key'] = account.private_key_file else: - host['exclude'] = _("No account found") + host['error'] = _("No account found") if gateway: host.update(self.make_proxy_command(gateway)) - - if self.host_var_callback: - callback_var = self.host_var_callback(asset) - if isinstance(callback_var, dict): - host.update(callback_var) return host def select_account(self, asset): @@ -145,10 +139,18 @@ class JMSInventory: for asset in self.assets: account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols) + if not automation.ansible_enabled: - host['exclude'] = _('Ansible disabled') - if self.host_duplicator: - hosts.extend(self.host_duplicator(host, asset=asset, account=account, platform=platform)) + host['error'] = _('Ansible disabled') + + if self.host_callback is not None: + host = self.host_callback( + host, asset=asset, account=account, + platform=platform, automation=automation + ) + + if isinstance(host, list): + hosts.extend(host) else: hosts.append(host) @@ -156,7 +158,7 @@ class JMSInventory: if exclude_hosts: print(_("Skip hosts below:")) for i, host in enumerate(exclude_hosts, start=1): - print("{}: [{}] \t{}".format(i, host['name'], host['exclude'])) + print("{}: [{}] \t{}".format(i, host['name'], host['error'])) hosts = list(filter(lambda x: not x.get('exclude'), hosts)) data = {'all': {'hosts': {}}} diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index e8f232e74..fbf3245ae 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -52,12 +52,14 @@ class AdHocRunner: class PlaybookRunner: - def __init__(self, inventory, playbook, project_dir='/tmp/'): + def __init__(self, inventory, playbook, project_dir='/tmp/', callback=None): self.id = uuid.uuid4() self.inventory = inventory self.playbook = playbook self.project_dir = project_dir - self.cb = DefaultCallback() + if not callback: + callback = DefaultCallback() + self.cb = callback def run(self, verbosity=0, **kwargs): if verbosity is None and settings.DEBUG: From 52fb55e806ec4ae14576c215ce118fd2e867250b Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 13 Oct 2022 17:52:25 +0800 Subject: [PATCH 187/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=94=B9?= =?UTF-8?q?=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...013_1429.py => 0108_migrate_automation.py} | 57 +++---------- .../migrations/0109_auto_20221013_1751.py | 83 +++++++++++++++++++ 2 files changed, 96 insertions(+), 44 deletions(-) rename apps/assets/migrations/{0108_auto_20221013_1429.py => 0108_migrate_automation.py} (64%) create mode 100644 apps/assets/migrations/0109_auto_20221013_1751.py diff --git a/apps/assets/migrations/0108_auto_20221013_1429.py b/apps/assets/migrations/0108_migrate_automation.py similarity index 64% rename from apps/assets/migrations/0108_auto_20221013_1429.py rename to apps/assets/migrations/0108_migrate_automation.py index df19a59d5..86a29c193 100644 --- a/apps/assets/migrations/0108_auto_20221013_1429.py +++ b/apps/assets/migrations/0108_migrate_automation.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.14 on 2022-10-13 06:29 +# Generated by Django 3.2.14 on 2022-10-10 01:59 import common.db.fields from django.conf import settings @@ -15,22 +15,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name='AutomationExecution', - fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('status', models.CharField(default='pending', max_length=16)), - ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), - ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), - ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), - ('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')), - ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), - ], - options={ - 'verbose_name': 'Automation strategy execution', - }, - ), migrations.CreateModel( name='BaseAutomation', fields=[ @@ -46,7 +30,6 @@ class Migration(migrations.Migration): ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), ('accounts', models.JSONField(default=list, verbose_name='Accounts')), ('type', models.CharField(max_length=16, verbose_name='Type')), - ('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('assets', models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets')), ('nodes', models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes')), @@ -102,47 +85,33 @@ class Migration(migrations.Migration): bases=('assets.baseautomation',), ), migrations.CreateModel( - name='ChangeSecretRecord', + name='AutomationExecution', fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')), - ('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')), - ('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')), ('status', models.CharField(default='pending', max_length=16)), - ('error', models.TextField(blank=True, null=True, verbose_name='Error')), - ('account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.account')), - ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.automationexecution')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), + ('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')), + ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), + ('automation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy')), ], options={ - 'verbose_name': 'Change secret', + 'verbose_name': 'Automation strategy execution', }, ), - migrations.AddField( - model_name='automationexecution', - name='automation', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy'), - ), migrations.CreateModel( - name='ChangeSecretAutomation', + name='ChangePasswordAutomation', fields=[ ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), - ('secret_types', models.JSONField(default=list, verbose_name='Secret types')), - ('password_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16, verbose_name='Password strategy')), ('password', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('password_rules', models.JSONField(default=dict, verbose_name='Password rules')), - ('ssh_key_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16)), - ('ssh_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH key')), - ('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key strategy')), - ('recipients', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), + ('recipients', models.ManyToManyField(blank=True, related_name='recipients_change_auth_strategy', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), ], options={ 'verbose_name': 'Change auth strategy', }, bases=('assets.baseautomation',), ), + ] diff --git a/apps/assets/migrations/0109_auto_20221013_1751.py b/apps/assets/migrations/0109_auto_20221013_1751.py new file mode 100644 index 000000000..c00a11b34 --- /dev/null +++ b/apps/assets/migrations/0109_auto_20221013_1751.py @@ -0,0 +1,83 @@ +# Generated by Django 3.2.14 on 2022-10-13 09:51 + +import common.db.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0108_migrate_automation'), + ] + + operations = [ + migrations.RenameModel( + old_name='ChangePasswordAutomation', + new_name='ChangeSecretAutomation', + ), + migrations.AddField( + model_name='baseautomation', + name='is_active', + field=models.BooleanField(default=True, verbose_name='Is active'), + ), + migrations.AddField( + model_name='changesecretautomation', + name='password_rules', + field=models.JSONField(default=dict, verbose_name='Password rules'), + ), + migrations.AddField( + model_name='changesecretautomation', + name='password_strategy', + field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Password strategy'), + ), + migrations.AddField( + model_name='changesecretautomation', + name='secret_types', + field=models.JSONField(default=list, verbose_name='Secret types'), + ), + migrations.AddField( + model_name='changesecretautomation', + name='ssh_key', + field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH key'), + ), + migrations.AddField( + model_name='changesecretautomation', + name='ssh_key_change_strategy', + field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key strategy'), + ), + migrations.AddField( + model_name='changesecretautomation', + name='ssh_key_strategy', + field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16), + ), + migrations.AlterField( + model_name='changesecretautomation', + name='recipients', + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient'), + ), + migrations.CreateModel( + name='ChangeSecretRecord', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')), + ('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), + ('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')), + ('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')), + ('status', models.CharField(default='pending', max_length=16)), + ('error', models.TextField(blank=True, null=True, verbose_name='Error')), + ('account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.account')), + ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.automationexecution')), + ], + options={ + 'verbose_name': 'Change secret', + }, + ), + ] From b74ec483939d32acfa0b364304dbae2c655530ee Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 13 Oct 2022 18:19:18 +0800 Subject: [PATCH 188/488] =?UTF-8?q?refacotr:=20=E6=8B=86=E5=88=86=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E6=A8=A1=E5=9D=97=E7=9A=84=E7=9B=AE=E5=BD=95=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/__init__.py | 1 + apps/perms/models/asset_permission.py | 46 ++----------------------- apps/perms/models/const.py | 48 +++++++++++++++++++++++++++ apps/perms/tests.py | 3 -- apps/perms/utils/permission.py | 4 +++ 5 files changed, 56 insertions(+), 46 deletions(-) create mode 100644 apps/perms/models/const.py diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py index 0c7e25c70..9cb0efc76 100644 --- a/apps/perms/models/__init__.py +++ b/apps/perms/models/__init__.py @@ -2,3 +2,4 @@ # from .asset_permission import * +from .const import * diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 9d444268f..91b894105 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -11,7 +11,8 @@ from assets.models import Asset, Node, FamilyMixin, Account from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgManager from common.utils import lazyproperty, date_expired_default -from common.db.models import BaseCreateUpdateModel, BitOperationChoice, UnionQuerySet +from common.db.models import BaseCreateUpdateModel, UnionQuerySet +from .const import Action, SpecialAccount __all__ = [ 'AssetPermission', 'PermNode', @@ -23,44 +24,6 @@ __all__ = [ logger = logging.getLogger(__name__) -class Action(BitOperationChoice): - ALL = 0xff - CONNECT = 0b1 - UPLOAD = 0b1 << 1 - DOWNLOAD = 0b1 << 2 - CLIPBOARD_COPY = 0b1 << 3 - CLIPBOARD_PASTE = 0b1 << 4 - UPDOWNLOAD = UPLOAD | DOWNLOAD - CLIPBOARD_COPY_PASTE = CLIPBOARD_COPY | CLIPBOARD_PASTE - - DB_CHOICES = ( - (ALL, _('All')), - (CONNECT, _('Connect')), - (UPLOAD, _('Upload file')), - (DOWNLOAD, _('Download file')), - (UPDOWNLOAD, _("Upload download")), - (CLIPBOARD_COPY, _('Clipboard copy')), - (CLIPBOARD_PASTE, _('Clipboard paste')), - (CLIPBOARD_COPY_PASTE, _('Clipboard copy paste')) - ) - - NAME_MAP = { - ALL: "all", - CONNECT: "connect", - UPLOAD: "upload_file", - DOWNLOAD: "download_file", - UPDOWNLOAD: "updownload", - CLIPBOARD_COPY: 'clipboard_copy', - CLIPBOARD_PASTE: 'clipboard_paste', - CLIPBOARD_COPY_PASTE: 'clipboard_copy_paste' - } - - NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} - CHOICES = [] - for i, j in DB_CHOICES: - CHOICES.append((NAME_MAP[i], j)) - - class AssetPermissionQuerySet(models.QuerySet): def active(self): return self.filter(is_active=True) @@ -79,7 +42,7 @@ class AssetPermissionQuerySet(models.QuerySet): def filter_by_accounts(self, accounts): q = Q(accounts__contains=list(accounts)) | \ - Q(accounts__contains=AssetPermission.SpecialAccount.ALL.value) + Q(accounts__contains=SpecialAccount.ALL.value) return self.filter(q) @@ -89,9 +52,6 @@ class AssetPermissionManager(OrgManager): class AssetPermission(OrgModelMixin): - class SpecialAccount(models.TextChoices): - ALL = '@ALL', 'All' - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), diff --git a/apps/perms/models/const.py b/apps/perms/models/const.py new file mode 100644 index 000000000..6128418b0 --- /dev/null +++ b/apps/perms/models/const.py @@ -0,0 +1,48 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from common.db.models import BitOperationChoice + + +__all__ = ['Action', 'SpecialAccount'] + + +class Action(BitOperationChoice): + ALL = 0xff + CONNECT = 0b1 + UPLOAD = 0b1 << 1 + DOWNLOAD = 0b1 << 2 + CLIPBOARD_COPY = 0b1 << 3 + CLIPBOARD_PASTE = 0b1 << 4 + UPDOWNLOAD = UPLOAD | DOWNLOAD + CLIPBOARD_COPY_PASTE = CLIPBOARD_COPY | CLIPBOARD_PASTE + + DB_CHOICES = ( + (ALL, _('All')), + (CONNECT, _('Connect')), + (UPLOAD, _('Upload file')), + (DOWNLOAD, _('Download file')), + (UPDOWNLOAD, _("Upload download")), + (CLIPBOARD_COPY, _('Clipboard copy')), + (CLIPBOARD_PASTE, _('Clipboard paste')), + (CLIPBOARD_COPY_PASTE, _('Clipboard copy paste')) + ) + + NAME_MAP = { + ALL: "all", + CONNECT: "connect", + UPLOAD: "upload_file", + DOWNLOAD: "download_file", + UPDOWNLOAD: "updownload", + CLIPBOARD_COPY: 'clipboard_copy', + CLIPBOARD_PASTE: 'clipboard_paste', + CLIPBOARD_COPY_PASTE: 'clipboard_copy_paste' + } + + NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} + CHOICES = [] + for i, j in DB_CHOICES: + CHOICES.append((NAME_MAP[i], j)) + + +class SpecialAccount(models.TextChoices): + ALL = '@ALL', 'All' diff --git a/apps/perms/tests.py b/apps/perms/tests.py index 344266b19..e69de29bb 100644 --- a/apps/perms/tests.py +++ b/apps/perms/tests.py @@ -1,3 +0,0 @@ -from django.test import TestCase - -from django.contrib.sessions.backends import file, db, cache diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 71bc2f07e..908c6016f 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -11,6 +11,10 @@ from perms.utils.user_permission import get_user_all_asset_perm_ids logger = get_logger(__file__) +class AssetPermissionUtil(object): + pass + + def validate_permission(user, asset, account, action='connect'): asset_perm_ids = get_user_all_asset_perm_ids(user) From 0f8668fee9d198831fd9a5a4b61d7c1a45d20c70 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 13 Oct 2022 20:14:04 +0800 Subject: [PATCH 189/488] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83Model=E4=B8=AD=E4=B8=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 88 +++++++++++---------------- apps/perms/utils/permission.py | 13 ++-- 2 files changed, 46 insertions(+), 55 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 91b894105..7f6bf02ef 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -54,27 +54,30 @@ class AssetPermissionManager(OrgManager): class AssetPermission(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) - users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User"), - related_name='%(class)ss') - user_groups = models.ManyToManyField('users.UserGroup', blank=True, - verbose_name=_("User group"), related_name='%(class)ss') - assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', - blank=True, verbose_name=_("Asset")) - nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, - verbose_name=_("Nodes")) - # 只保存 @ALL (@INPUT @USER 默认包含,将来在全局设置中进行控制) - # 特殊的账号描述 - # ['@ALL',] - # 指定账号授权 - # ['web', 'root',] + users = models.ManyToManyField( + 'users.User', related_name='%(class)ss', blank=True, verbose_name=_("User") + ) + user_groups = models.ManyToManyField( + 'users.UserGroup', related_name='%(class)ss', blank=True, verbose_name=_("User group") + ) + assets = models.ManyToManyField( + 'assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset") + ) + nodes = models.ManyToManyField( + 'assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes") + ) + # 特殊的账号: @ALL, @INPUT @USER 默认包含,将来在全局设置中进行控制. accounts = models.JSONField(default=list, verbose_name=_("Accounts")) - actions = models.IntegerField(choices=Action.DB_CHOICES, default=Action.ALL, - verbose_name=_("Actions")) + actions = models.IntegerField( + choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions") + ) is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_start = models.DateTimeField(default=timezone.now, db_index=True, - verbose_name=_("Date start")) - date_expired = models.DateTimeField(default=date_expired_default, db_index=True, - verbose_name=_('Date expired')) + date_start = models.DateTimeField( + default=timezone.now, db_index=True, verbose_name=_("Date start") + ) + date_expired = models.DateTimeField( + default=date_expired_default, db_index=True, verbose_name=_('Date expired') + ) created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket')) @@ -91,10 +94,6 @@ class AssetPermission(OrgModelMixin): def __str__(self): return self.name - @property - def id_str(self): - return str(self.id) - @property def is_expired(self): if self.date_expired > timezone.now() > self.date_start: @@ -107,15 +106,6 @@ class AssetPermission(OrgModelMixin): return True return False - @property - def all_users(self): - from users.models import User - users_query = self._meta.get_field('users').related_query_name() - user_groups_query = self._meta.get_field('user_groups').related_query_name() - users_q = Q(**{f'{users_query}': self}) - user_groups_q = Q(**{f'groups__{user_groups_query}': self}) - return User.objects.filter(users_q | user_groups_q).distinct() - def get_all_users(self): from users.models import User user_ids = self.users.all().values_list('id', flat=True) @@ -127,6 +117,21 @@ class AssetPermission(OrgModelMixin): qs = UnionQuerySet(qs1, qs2) return qs + def get_all_assets(self, flat=False): + from assets.models import Node + nodes_keys = self.nodes.all().values_list('key', flat=True) + asset_ids = set(self.assets.all().values_list('id', flat=True)) + nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys) + asset_ids.update(nodes_asset_ids) + if flat: + return asset_ids + assets = Asset.objects.filter(id__in=asset_ids) + return assets + + def get_all_accounts(self): + """ TODO: 获取所有账号 (Account 对象) """ + pass + @lazyproperty def users_amount(self): return self.users.count() @@ -143,25 +148,6 @@ class AssetPermission(OrgModelMixin): def nodes_amount(self): return self.nodes.count() - @classmethod - def get_queryset_with_prefetch(cls): - return cls.objects.all().valid().prefetch_related( - models.Prefetch('nodes', queryset=Node.objects.all().only('key')), - models.Prefetch('assets', queryset=Asset.objects.all().only('id')), - ).order_by() - - def get_all_assets(self, flat=False): - from assets.models import Node - nodes_keys = self.nodes.all().values_list('key', flat=True) - asset_ids = set(self.assets.all().values_list('id', flat=True)) - nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys) - asset_ids.update(nodes_asset_ids) - if flat: - return asset_ids - else: - assets = Asset.objects.filter(id__in=asset_ids) - return assets - def users_display(self): names = [user.username for user in self.users.all()] return names diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 908c6016f..8b9067613 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -11,10 +11,6 @@ from perms.utils.user_permission import get_user_all_asset_perm_ids logger = get_logger(__file__) -class AssetPermissionUtil(object): - pass - - def validate_permission(user, asset, account, action='connect'): asset_perm_ids = get_user_all_asset_perm_ids(user) @@ -93,3 +89,12 @@ def has_asset_system_permission(user: User, asset: Asset, account: str): if actions: return True return False + + +class AssetPermissionUtil(object): + + def get_permed_accounts(self, user=None, asset=None): + pass + + def get_related_permissions(self, user=None, asset=None): + pass From 0a65b9de8eef538c7ab8949b5945b5328a076ed4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 13 Oct 2022 20:28:18 +0800 Subject: [PATCH 190/488] =?UTF-8?q?perf:=20=E9=87=8D=E6=9E=84=20playbook?= =?UTF-8?q?=20base=20runner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 130 +++++++----------- .../automations/change_secret/manager.py | 6 +- 2 files changed, 56 insertions(+), 80 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 85366785d..81f4c3227 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -1,7 +1,6 @@ import os import shutil import yaml -from copy import deepcopy from collections import defaultdict from django.conf import settings @@ -42,33 +41,23 @@ class BasePlaybookManager: def method_type(cls): raise NotImplementedError + def get_assets_group_by_platform(self): + return self.automation.all_assets_group_by_platform() + @property def runtime_dir(self): ansible_dir = settings.ANSIBLE_DIR + dir_name = '{}_{}'.format(self.automation.name.replace(' ', '_'), self.execution.id) path = os.path.join( - ansible_dir, self.automation.type, - self.automation.name.replace(' ', '_'), - timezone.now().strftime('%Y%m%d_%H%M%S') + ansible_dir, 'automations', self.automation.type, + dir_name, timezone.now().strftime('%Y%m%d_%H%M%S') ) + if not os.path.exists(path): + os.makedirs(path, exist_ok=True, mode=0o755) return path - @property - def inventory_path(self): - return os.path.join(self.runtime_dir, 'inventory', 'hosts.json') - - @property - def playbook_path(self): - return os.path.join(self.runtime_dir, 'project', 'main.yml') - - def generate(self): - self.prepare_playbook_dir() - self.generate_inventory() - self.generate_playbook() - def prepare_playbook_dir(self): - inventory_dir = os.path.dirname(self.inventory_path) - playbook_dir = os.path.dirname(self.playbook_path) - for d in [inventory_dir, playbook_dir]: + for d in [self.runtime_dir]: if not os.path.exists(d): os.makedirs(d, exist_ok=True, mode=0o755) @@ -82,85 +71,70 @@ class BasePlaybookManager: getattr(automation, method_attr) in self.method_id_meta_mapper if not method_enabled: - host['error'] = _('Change password disabled') + host['error'] = _('{} disabled'.format(self.__class__.method_type())) return host - - self.method_hosts_mapper[getattr(automation, method_attr)].append(host['name']) return host - def generate_inventory(self): + def generate_inventory(self, platformed_assets, inventory_path): inventory = JMSInventory( - assets=self.automation.get_all_assets(), + assets=platformed_assets, account_policy=self.ansible_account_policy, host_callback=self.host_callback ) - inventory.write_to_file(self.inventory_path) - logger.debug("Generate inventory done: {}".format(self.inventory_path)) + inventory.write_to_file(inventory_path) - def generate_playbook(self): - main_playbook = [] - for method_id, host_names in self.method_hosts_mapper.items(): - method = self.method_id_meta_mapper.get(method_id) - if not method: - logger.error("Method not found: {}".format(method_id)) - continue - method_playbook_dir_path = method['dir'] - method_playbook_dir_name = os.path.basename(method_playbook_dir_path) - sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) - sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') - shutil.copytree(method_playbook_dir_path, sub_playbook_dir) + def generate_playbook(self, platformed_assets, platform, sub_playbook_dir): + method_id = getattr(platform.automation, '{}_method'.format(self.__class__.method_type())) + method = self.method_id_meta_mapper.get(method_id) + if not method: + logger.error("Method not found: {}".format(method_id)) + return method + method_playbook_dir_path = method['dir'] + sub_playbook_path = os.path.join(sub_playbook_dir, 'project', 'main.yml') + shutil.copytree(method_playbook_dir_path, os.path.dirname(sub_playbook_path)) - with open(sub_playbook_path, 'r') as f: - host_playbook_play = yaml.safe_load(f) + with open(sub_playbook_path, 'r') as f: + plays = yaml.safe_load(f) + for play in plays: + play['hosts'] = 'all' - if isinstance(host_playbook_play, list): - host_playbook_play = host_playbook_play[0] - - hosts_bulked = [host_names[i:i+self.bulk_size] for i in range(0, len(host_names), self.bulk_size)] - for i, hosts in enumerate(hosts_bulked): - plays = [] - play = deepcopy(host_playbook_play) - play['hosts'] = ':'.join(hosts) - plays.append(play) - - playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) - with open(playbook_path, 'w') as f: - yaml.safe_dump(plays, f) - self.playbooks.append([playbook_path, hosts]) - - main_playbook.append({ - 'name': method['name'] + ' for part {}'.format(i), - 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) - }) - - with open(self.playbook_path, 'w') as f: - yaml.safe_dump(main_playbook, f) - - logger.debug("Generate playbook done: " + self.playbook_path) + with open(sub_playbook_path, 'w') as f: + yaml.safe_dump(plays, f) + return sub_playbook_path def get_runners(self): runners = [] - for playbook_path in self.playbooks: - runer = PlaybookRunner( - self.inventory_path, - playbook_path, - self.runtime_dir, - callback=PlaybookCallback(), - ) - runners.append(runer) + for platform, assets in self.get_assets_group_by_platform().items(): + assets_bulked = [assets[i:i+self.bulk_size] for i in range(0, len(assets), self.bulk_size)] + + for i, _assets in enumerate(assets_bulked, start=1): + sub_dir = '{}_{}'.format(platform.name, i) + playbook_dir = os.path.join(self.runtime_dir, sub_dir) + inventory_path = os.path.join(self.runtime_dir, sub_dir, 'hosts.json') + self.generate_inventory(_assets, inventory_path) + playbook_path = self.generate_playbook(_assets, platform, playbook_dir) + + runer = PlaybookRunner( + inventory_path, + playbook_path, + self.runtime_dir, + callback=PlaybookCallback(), + ) + runners.append(runer) return runners - def on_runner_done(self, runner, cb): + def on_runner_success(self, runner, cb): raise NotImplementedError def on_runner_failed(self, runner, e): print("Runner failed: {} {}".format(e, self)) def before_runner_start(self, runner): - pass + print("Start run task: ") + print(" inventory: {}".format(runner.inventory)) + print(" playbook: {}".format(runner.playbook)) def run(self, **kwargs): - self.generate() runners = self.get_runners() if len(runners) > 1: print("### 分批次执行开始任务, 总共 {}\n".format(len(runners))) @@ -173,7 +147,7 @@ class BasePlaybookManager: self.before_runner_start(runner) try: cb = runner.run(**kwargs) - self.on_runner_done(runner, cb) + self.on_runner_success(runner, cb) except Exception as e: self.on_runner_failed(runner, e) - print('\n\n') + print('\n') diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 43cc2a350..3fabe4792 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -121,8 +121,10 @@ class ChangeSecretManager(BasePlaybookManager): ChangeSecretRecord.objects.bulk_create(records) return inventory_hosts - def on_runner_done(self, runner, cb): - summary = runner.summary + def on_runner_success(self, runner, cb): + summary = cb.summary + print("Summary: ") + print(summary) def on_runner_failed(self, runner, e): pass From 2d893c4a6ac03e26701b9757a970ff700a873d49 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 14 Oct 2022 14:56:38 +0800 Subject: [PATCH 191/488] =?UTF-8?q?refactor:=20=E8=8E=B7=E5=8F=96=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E8=A7=84=E5=88=99=E6=8E=88=E6=9D=83=E7=9A=84=E6=89=80?= =?UTF-8?q?=E6=9C=89=E8=B4=A6=E5=8F=B7=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 7f6bf02ef..d7b2495e2 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -129,8 +129,19 @@ class AssetPermission(OrgModelMixin): return assets def get_all_accounts(self): - """ TODO: 获取所有账号 (Account 对象) """ - pass + """ + :return: 返回授权的所有账号对象 Account + """ + asset_ids = self.get_all_assets(flat=True) + q = Q(asset_id__in=asset_ids) + if not self.is_perm_all_accounts: + q &= Q(username__in=self.accounts) + accounts = Account.objects.filter(q) + return accounts + + @property + def is_perm_all_accounts(self): + return SpecialAccount.ALL in self.accounts @lazyproperty def users_amount(self): From 4e2aebde6cdc4eaf6d181933ee84840b0401d14a Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 14 Oct 2022 16:33:24 +0800 Subject: [PATCH 192/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=94=B9?= =?UTF-8?q?=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 16 ++++- .../change_secret/host/aix/main.yml | 4 +- .../change_secret/host/linux/main.yml | 28 ++++---- .../change_secret/host/windows/main.yml | 27 +++---- .../automations/change_secret/manager.py | 32 +++++++-- .../roles/change_password/tasks/main.yml | 4 +- .../gather_facts/host/posix/manifest.yml | 1 - .../automations/ping/host/posix/main.yml | 2 +- .../automations/ping/host/posix/manifest.yml | 1 - .../ping/host/windows/manifest.yml | 2 +- apps/assets/const/host.py | 3 + apps/ops/ansible/inventory.py | 71 +++++++++++++------ utils/playbooks/change_password/main.yml | 4 +- 13 files changed, 126 insertions(+), 69 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 81f4c3227..59a5bf75c 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -123,8 +123,22 @@ class BasePlaybookManager: runners.append(runer) return runners + def on_host_success(self, host, result): + pass + + def on_host_error(self, host, error, result): + pass + def on_runner_success(self, runner, cb): - raise NotImplementedError + summary = cb.summary + for state, hosts in summary.items(): + for host in hosts: + result = cb.result.get(host) + if state == 'ok': + self.on_host_success(host, result) + else: + error = hosts.get(host) + self.on_host_error(host, error, result) def on_runner_failed(self, runner, e): print("Runner failed: {} {}".format(e, self)) diff --git a/apps/assets/automations/change_secret/host/aix/main.yml b/apps/assets/automations/change_secret/host/aix/main.yml index 483554a1e..41e093df8 100644 --- a/apps/assets/automations/change_secret/host/aix/main.yml +++ b/apps/assets/automations/change_secret/host/aix/main.yml @@ -1,7 +1,7 @@ - hosts: demo tasks: - name: ping - ping: + ansible.builtin.ping: #- name: print variables # debug: @@ -22,7 +22,7 @@ when: account.public_key - name: Verify password - ping: + ansible.builtin.ping: vars: ansible_user: "{{ account.username }}" ansible_pass: "{{ account.password }}" diff --git a/apps/assets/automations/change_secret/host/linux/main.yml b/apps/assets/automations/change_secret/host/linux/main.yml index a7f5d8c18..39c5d0996 100644 --- a/apps/assets/automations/change_secret/host/linux/main.yml +++ b/apps/assets/automations/change_secret/host/linux/main.yml @@ -2,29 +2,33 @@ gather_facts: no tasks: - name: Test privileged account - ping: - - #- name: print variables - # debug: - # msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ account.secret_type }}" + ansible.builtin.ping: +# +# - name: print variables +# debug: +# msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ account.secret_type }}" - name: Change password - user: + ansible.builtin.user: name: "{{ account.username }}" password: "{{ account.secret | password_hash('sha512') }}" update_password: always - when: account.secret_type == 'password' + when: account.secret_type == "password" - name: Change public key - authorized_key: + ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.public_key }}" state: present - when: account.public_key + when: account.secret_type == "public_key" + + - name: Refresh connection + ansible.builtin.meta: reset_connection - name: Verify password - ping: + ansible.builtin.ping: + become: no vars: ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.secret }}" - ansible_ssh_connection: paramiko + ansible_password: "{{ account.secret }}" + ansible_become: no diff --git a/apps/assets/automations/change_secret/host/windows/main.yml b/apps/assets/automations/change_secret/host/windows/main.yml index d10c3681e..bc66486dc 100644 --- a/apps/assets/automations/change_secret/host/windows/main.yml +++ b/apps/assets/automations/change_secret/host/windows/main.yml @@ -2,29 +2,24 @@ gather_facts: no tasks: - name: ping - ping: + ansible.windows.win_ping: - #- name: print variables - # debug: - # msg: "Username: {{ account.username }}, Password: {{ account.password }}" +# - name: Print variables +# debug: +# msg: "Username: {{ account.username }}, Password: {{ account.secret }}" - name: Change password - user: + ansible.windows.win_user: name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" + password: "{{ account.secret }}" update_password: always - when: account.password + when: account.secret_type == "password" - - name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key + - name: Refresh connection + ansible.builtin.meta: reset_connection - name: Verify password - ping: + ansible.windows.win_ping: vars: ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko + ansible_password: "{{ account.secret }}" diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 3fabe4792..379323b69 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -1,11 +1,13 @@ -from copy import deepcopy -from collections import defaultdict import random import string +from copy import deepcopy +from collections import defaultdict + +from django.utils import timezone from common.utils import lazyproperty, gen_key_pair -from ..base.manager import BasePlaybookManager from assets.models import ChangeSecretRecord, SecretStrategy +from ..base.manager import BasePlaybookManager string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~' DEFAULT_PASSWORD_LENGTH = 30 @@ -121,10 +123,26 @@ class ChangeSecretManager(BasePlaybookManager): ChangeSecretRecord.objects.bulk_create(records) return inventory_hosts - def on_runner_success(self, runner, cb): - summary = cb.summary - print("Summary: ") - print(summary) + def on_host_success(self, host, result): + recorder = self.name_recorder_mapper.get(host) + if not recorder: + return + recorder.status = 'succeed' + recorder.date_finished = timezone.now() + recorder.save() + + account = recorder.account + account.secret = recorder.new_secret + account.save(update_fields=['secret']) + + def on_host_error(self, host, error, result): + recorder = self.name_recorder_mapper.get(host) + if not recorder: + return + recorder.status = 'failed' + recorder.date_finished = timezone.now() + recorder.error = error + recorder.save() def on_runner_failed(self, runner, e): pass diff --git a/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml b/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml index 903cd9115..cb6480235 100644 --- a/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml +++ b/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml @@ -1,5 +1,5 @@ - name: ping - ping: + ansible.builtin.ping: #- name: print variables # debug: @@ -20,7 +20,7 @@ when: account.public_key - name: Verify password - ping: + ansible.builtin.ping: vars: ansible_user: "{{ account.username }}" ansible_pass: "{{ account.password }}" diff --git a/apps/assets/automations/gather_facts/host/posix/manifest.yml b/apps/assets/automations/gather_facts/host/posix/manifest.yml index e5622cf28..b59b701aa 100644 --- a/apps/assets/automations/gather_facts/host/posix/manifest.yml +++ b/apps/assets/automations/gather_facts/host/posix/manifest.yml @@ -3,6 +3,5 @@ name: Gather posix facts category: host type: - linux - - windows - unix method: gather_facts diff --git a/apps/assets/automations/ping/host/posix/main.yml b/apps/assets/automations/ping/host/posix/main.yml index c4c740367..8e5f375dd 100644 --- a/apps/assets/automations/ping/host/posix/main.yml +++ b/apps/assets/automations/ping/host/posix/main.yml @@ -2,4 +2,4 @@ gather_facts: no tasks: - name: Posix ping - ping: + ansible.builtin.ping: diff --git a/apps/assets/automations/ping/host/posix/manifest.yml b/apps/assets/automations/ping/host/posix/manifest.yml index b07caa7f8..4b9afde37 100644 --- a/apps/assets/automations/ping/host/posix/manifest.yml +++ b/apps/assets/automations/ping/host/posix/manifest.yml @@ -3,6 +3,5 @@ name: Posix ping category: host type: - linux - - windows - unix method: ping diff --git a/apps/assets/automations/ping/host/windows/manifest.yml b/apps/assets/automations/ping/host/windows/manifest.yml index fd8cb27a9..6218e5978 100644 --- a/apps/assets/automations/ping/host/windows/manifest.yml +++ b/apps/assets/automations/ping/host/windows/manifest.yml @@ -1,7 +1,7 @@ id: win_ping name: Windows ping version: 1 -method: change_secret +method: ping category: host type: - windows diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index a26663220..a8db5f022 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -33,6 +33,9 @@ class HostTypes(BaseType): return { '*': { 'choices': ['ssh', 'telnet', 'vnc', 'rdp'] + }, + cls.WINDOWS: { + 'choices': ['rdp', 'ssh', 'vnc'] } } diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 4408b38b6..ce2c69a46 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -10,15 +10,15 @@ __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, assets, account='', account_policy='smart', host_callback=None): + def __init__(self, assets, account_policy='smart', account_prefer='root,administrator', host_callback=None): """ :param assets: - :param account: account username name if not set use account_policy + :param account_prefer: account username name if not set use account_policy :param account_policy: :param host_callback: after generate host, call this callback to modify host """ self.assets = self.clean_assets(assets) - self.account_username = account + self.account_prefer = account_prefer self.account_policy = account_policy self.host_callback = host_callback @@ -58,6 +58,46 @@ class JMSInventory: ) return {"ansible_ssh_common_args": proxy_command} + @staticmethod + def make_account_ansible_vars(account): + var = { + 'ansible_user': account.username, + } + if not account.secret: + return var + if account.secret_type == 'password': + var['ansible_password'] = account.secret + elif account.secret_type == 'ssh_key': + var['ansible_ssh_private_key_file'] = account.private_key_file + return var + + def make_ssh_account_vars(self, host, asset, account, automation, protocols, platform, gateway): + if not account: + host['error'] = _("No account available") + return host + + ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) + ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None + host['ansible_host'] = asset.address + host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 + + su_from = account.su_from + if platform.su_enabled and su_from: + host.update(self.make_account_ansible_vars(su_from)) + become_method = 'sudo' if platform.su_method != 'su' else 'su' + host['ansible_become'] = True + host['ansible_become_method'] = 'sudo' + host['ansible_become_user'] = account.username + if become_method == 'sudo': + host['ansible_become_password'] = su_from.secret + else: + host['ansible_become_password'] = account.secret + else: + host.update(self.make_account_ansible_vars(account)) + + if gateway: + host.update(self.make_proxy_command(gateway)) + def asset_to_host(self, asset, account, automation, protocols, platform): host = { 'name': '{}'.format(asset.name), @@ -73,14 +113,12 @@ class JMSInventory: } if account else None } ansible_config = dict(automation.ansible_config) - ansible_connection = ansible_config.pop('ansible_connection', 'ssh') + ansible_connection = ansible_config.get('ansible_connection', 'ssh') host.update(ansible_config) gateway = None if asset.domain: gateway = asset.domain.select_gateway() - ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) - ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None if ansible_connection == 'local': if gateway: host['ansible_host'] = gateway.address @@ -91,29 +129,16 @@ class JMSInventory: else: host['ansible_connection'] = 'local' else: - host['ansible_host'] = asset.address - host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 - if account: - host['ansible_user'] = account.username - - if account.secret_type == 'password' and account.secret: - host['ansible_password'] = account.secret - elif account.secret_type == 'private_key' and account.secret: - host['ssh_private_key'] = account.private_key_file - else: - host['error'] = _("No account found") - - if gateway: - host.update(self.make_proxy_command(gateway)) + self.make_ssh_account_vars(host, asset, account, automation, protocols, platform, gateway) return host def select_account(self, asset): accounts = list(asset.accounts.all()) account_selected = None - account_username = self.account_username + account_username = self.account_prefer - if isinstance(self.account_username, str): - account_username = [self.account_username] + if isinstance(self.account_prefer, str): + account_username = self.account_prefer.split(',') if account_username: for username in account_username: diff --git a/utils/playbooks/change_password/main.yml b/utils/playbooks/change_password/main.yml index 3a981fdb2..31748fe1c 100644 --- a/utils/playbooks/change_password/main.yml +++ b/utils/playbooks/change_password/main.yml @@ -7,7 +7,7 @@ tasks: - name: 监测特权用户密码 - ping: + ansible.builtin.ping: - name: 更改用户密码 user: @@ -19,5 +19,5 @@ vars: - ansible_user: '{{ user1 }}' ansible_ssh_password: '{{ user1password }}' - ping: + ansible.builtin.ping: From 0e6773917393362322e2d2ddb18721e31ee62daa Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 14 Oct 2022 17:01:36 +0800 Subject: [PATCH 193/488] =?UTF-8?q?refactor:=20=E6=8E=88=E6=9D=83=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=8E=88=E6=9D=83=E8=B4=A6=E5=8F=B7=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0=E8=8E=B7=E5=8F=96=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9F=90=E4=B8=AA=E8=B5=84=E4=BA=A7=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 5 --- apps/perms/models/asset_permission.py | 13 +++---- apps/perms/utils/__init__.py | 1 + apps/perms/utils/account.py | 51 +++++++++++++++++++++++++++ apps/perms/utils/permission.py | 8 ----- 5 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 apps/perms/utils/account.py diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index faf503b91..391a58e9b 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -74,11 +74,6 @@ class Account(BaseAccount): """ @USER 动态用户的账号(self) """ return cls(name=cls.InnerAccount.USER.value, username=username) - @classmethod - def filter(cls, asset_ids, account_usernames): - queries = Q(asset_id__in=asset_ids) & Q(username__in=account_usernames) - return cls.objects.filter(queries) - class AccountTemplate(BaseAccount): class Meta: diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index d7b2495e2..acd654528 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -128,7 +128,7 @@ class AssetPermission(OrgModelMixin): assets = Asset.objects.filter(id__in=asset_ids) return assets - def get_all_accounts(self): + def get_all_accounts(self, flat=False): """ :return: 返回授权的所有账号对象 Account """ @@ -137,7 +137,9 @@ class AssetPermission(OrgModelMixin): if not self.is_perm_all_accounts: q &= Q(username__in=self.accounts) accounts = Account.objects.filter(q) - return accounts + if not flat: + return accounts + return accounts.values_list('id', flat=True) @property def is_perm_all_accounts(self): @@ -175,12 +177,7 @@ class AssetPermission(OrgModelMixin): names = [node.full_value for node in self.nodes.all()] return names - # Related accounts - def get_asset_accounts(self): - asset_ids = self.get_all_assets(flat=True) - accounts = Account.filter(asset_ids, self.accounts) - return accounts - + # Accounts @classmethod def get_perm_asset_accounts(cls, user=None, user_group=None, asset=None, with_actions=True): perms = cls.filter(user=user, user_group=user_group, asset=asset) diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py index ea3cb14de..fc2a94e88 100644 --- a/apps/perms/utils/__init__.py +++ b/apps/perms/utils/__init__.py @@ -1,2 +1,3 @@ from .permission import * from .user_permission import * +from .account import * diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py new file mode 100644 index 000000000..2734c1423 --- /dev/null +++ b/apps/perms/utils/account.py @@ -0,0 +1,51 @@ +from collections import defaultdict +from assets.models import Account +from perms.models import AssetPermission + + +class PermAccountUtil(object): + """ 授权账号查询工具 """ + + # Accounts + + def get_user_perm_asset_accounts(self, user, asset, with_actions=False): + """ 获取授权给用户某个资产的账号 """ + aid_actions_map = defaultdict(int) + perms = self.get_user_asset_permissions(user, asset) + for perm in perms: + account_ids = perm.get_all_accounts(flat=True) + actions = perm.actions + for aid in account_ids: + aid_actions_map[str(aid)] |= actions + account_ids = list(aid_actions_map.keys()) + accounts = Account.objects.filter(id__in=account_ids) + if with_actions: + for account in accounts: + account.actions = aid_actions_map.get(str(account.id)) + return accounts + + def get_user_perm_accounts(self, user): + """ 获取授权给用户的所有账号 """ + pass + + # Permissions + + def get_user_asset_permissions(self, user, asset): + """ 获取同时包含用户、资产的授权规则 """ + return AssetPermission.objects.all() + + def get_user_permissions(self): + """ 获取用户的授权规则 """ + pass + + def get_asset_permissions(self): + """ 获取资产的授权规则""" + pass + + def get_node_permissions(self): + """ 获取节点的授权规则 """ + pass + + def get_user_group_permissions(self): + """ 获取用户组的授权规则 """ + pass diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 8b9067613..c3a515514 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -90,11 +90,3 @@ def has_asset_system_permission(user: User, asset: Asset, account: str): return True return False - -class AssetPermissionUtil(object): - - def get_permed_accounts(self, user=None, asset=None): - pass - - def get_related_permissions(self, user=None, asset=None): - pass From 6a32ac4699206fd1b7a4ba78dad47fa63fee023d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 14 Oct 2022 17:53:54 +0800 Subject: [PATCH 194/488] =?UTF-8?q?refactor:=20=E7=BB=A7=E7=BB=AD=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E6=B7=BB=E5=8A=A0=E6=8E=88=E6=9D=83=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/account.py | 60 ++++++++++++++++++++++++++++--------- apps/users/models/user.py | 15 ---------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 2734c1423..9db8a175f 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -10,9 +10,20 @@ class PermAccountUtil(object): def get_user_perm_asset_accounts(self, user, asset, with_actions=False): """ 获取授权给用户某个资产的账号 """ - aid_actions_map = defaultdict(int) perms = self.get_user_asset_permissions(user, asset) - for perm in perms: + accounts = self.get_permissions_accounts(perms, with_actions=with_actions) + return accounts + + def get_user_perm_accounts(self, user, with_actions=False): + """ 获取授权给用户的所有账号 """ + perms = self.get_user_permissions(user) + accounts = self.get_permissions_accounts(perms, with_actions=with_actions) + return accounts + + @staticmethod + def get_permissions_accounts(permissions, with_actions=False): + aid_actions_map = defaultdict(int) + for perm in permissions: account_ids = perm.get_all_accounts(flat=True) actions = perm.actions for aid in account_ids: @@ -24,28 +35,49 @@ class PermAccountUtil(object): account.actions = aid_actions_map.get(str(account.id)) return accounts - def get_user_perm_accounts(self, user): - """ 获取授权给用户的所有账号 """ - pass - # Permissions def get_user_asset_permissions(self, user, asset): """ 获取同时包含用户、资产的授权规则 """ - return AssetPermission.objects.all() + user_perm_ids = self.get_user_permissions(user, flat=True) + asset_perm_ids = self.get_asset_permissions(asset, flat=True) + perm_ids = set(user_perm_ids) & set(asset_perm_ids) + perms = AssetPermission.objects.filter(id__in=perm_ids) + return perms - def get_user_permissions(self): + def get_user_permissions(self, user, with_group=True, flat=False): """ 获取用户的授权规则 """ - pass + perm_ids = set() + # user + user_perm_ids = AssetPermission.users.through.objects.filter(user_id=user.id)\ + .values_list('assetpermission_id', flat=True).distinct() + perm_ids.update(user_perm_ids) + # group + if with_group: + groups = user.groups.all() + group_perm_ids = self.get_user_groups_permissions(groups, flat=True) + perm_ids.update(group_perm_ids) + if flat: + return perm_ids + perms = AssetPermission.objects.filter(id__in=perm_ids) + return perms - def get_asset_permissions(self): + @staticmethod + def get_user_groups_permissions(user_groups, flat=False): + """ 获取用户组的授权规则 """ + group_ids = user_groups.values_list('id', flat=True).distinct() + perm_ids = AssetPermission.user_groups.through.objects.filter(usergroup_id__in=group_ids) \ + .values_list('assetpermission_id', flat=True).distinct() + if flat: + return perm_ids + perms = AssetPermission.objects.filter(id__in=perm_ids) + return perms + + def get_asset_permissions(self, asset, flat=False): """ 获取资产的授权规则""" - pass + return AssetPermission.objects.all() def get_node_permissions(self): """ 获取节点的授权规则 """ pass - def get_user_group_permissions(self): - """ 获取用户组的授权规则 """ - pass diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 659a894a9..78d5eb540 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -918,21 +918,6 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): return True return False - def get_groups(self, flat=False): - from users.models import UserGroup - usergroup_ids = self.groups.through.objects\ - .filter(user_id=self.id)\ - .distinct()\ - .values_list('usergroup_id', flat=True) - usergroups = UserGroup.objects.filter(id__in=usergroup_ids) - if flat: - usergroup_ids = usergroups.values_list('id', flat=True) - return usergroup_ids - else: - return usergroups - - - class UserPasswordHistory(models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) From 75ec9d4173569384089d10c5e2ef0efd14d04f10 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 14 Oct 2022 18:59:28 +0800 Subject: [PATCH 195/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20gather=20f?= =?UTF-8?q?acts=20automation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../automations/change_secret/manager.py | 1 - .../automations/gather_facts/manager.py | 78 +++---------------- .../models/automations/account_verify.py | 9 +-- apps/assets/models/automations/base.py | 9 +++ .../assets/models/automations/gather_facts.py | 13 ++++ apps/common/hashers/sm3.py | 13 +++- 6 files changed, 49 insertions(+), 74 deletions(-) create mode 100644 apps/assets/models/automations/gather_facts.py diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 379323b69..072278d0d 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -21,7 +21,6 @@ class ChangeSecretManager(BasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.method_hosts_mapper = defaultdict(list) - self.playbooks = [] self.password_strategy = self.execution.automation.password_strategy self.ssh_key_strategy = self.execution.automation.ssh_key_strategy self._password_generated = None diff --git a/apps/assets/automations/gather_facts/manager.py b/apps/assets/automations/gather_facts/manager.py index 7b56d728e..4b782ae31 100644 --- a/apps/assets/automations/gather_facts/manager.py +++ b/apps/assets/automations/gather_facts/manager.py @@ -1,77 +1,23 @@ -import os -import shutil -from copy import deepcopy -from collections import defaultdict - -import yaml -from django.utils.translation import gettext as _ - -from ops.ansible import PlaybookRunner from ..base.manager import BasePlaybookManager -from assets.automations.methods import platform_automation_methods class GatherFactsManager(BasePlaybookManager): - method_name = 'gather_facts' - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.id_method_mapper = { - method['id']: method - for method in platform_automation_methods - if method['method'] == self.method_name - } - self.method_hosts_mapper = defaultdict(list) - self.playbooks = [] + self.host_asset_mapper = {} - def inventory_kwargs(self): - return { - } + @classmethod + def method_type(cls): + return 'gather_facts' + + def host_callback(self, host, asset=None, **kwargs): + super().host_callback(host, asset=asset, **kwargs) + self.host_asset_mapper[host['name']] = asset + + def on_host_success(self, host, result): + print("Host: {}".format(host)) + print("Result: {}".format(result)) - def generate_playbook(self): - playbook = [] - for method_id, host_names in self.method_hosts_mapper.items(): - method = self.id_method_mapper[method_id] - method_playbook_dir_path = method['dir'] - method_playbook_dir_name = os.path.basename(method_playbook_dir_path) - sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) - shutil.copytree(method_playbook_dir_path, sub_playbook_dir) - sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') - with open(sub_playbook_path, 'r') as f: - host_playbook_play = yaml.safe_load(f) - - if isinstance(host_playbook_play, list): - host_playbook_play = host_playbook_play[0] - - step = 10 - hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)] - for i, hosts in enumerate(hosts_grouped): - plays = [] - play = deepcopy(host_playbook_play) - play['hosts'] = ':'.join(hosts) - plays.append(play) - - playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) - with open(playbook_path, 'w') as f: - yaml.safe_dump(plays, f) - self.playbooks.append(playbook_path) - - playbook.append({ - 'name': method['name'] + ' for part {}'.format(i), - 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) - }) - - with open(self.playbook_path, 'w') as f: - yaml.safe_dump(playbook, f) - - print("Generate playbook done: " + self.playbook_path) - - def get_runner(self): - return PlaybookRunner( - self.inventory_path, - self.playbook_path, - self.runtime_dir - ) diff --git a/apps/assets/models/automations/account_verify.py b/apps/assets/models/automations/account_verify.py index d05cb4a0d..15551f75c 100644 --- a/apps/assets/models/automations/account_verify.py +++ b/apps/assets/models/automations/account_verify.py @@ -8,9 +8,6 @@ class VerifyAutomation(BaseAutomation): class Meta: verbose_name = _("Verify strategy") - def to_attr_json(self): - attr_json = super().to_attr_json() - attr_json.update({ - 'type': StrategyChoice.verify - }) - return attr_json + def save(self, *args, **kwargs): + self.type = 'verify' + super().save(*args, **kwargs) diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 1f9b6631f..a0eef8df7 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -11,6 +11,15 @@ from ops.tasks import execute_automation_strategy from assets.models import Node, Asset +class AutomationTypes(models.TextChoices): + ping = 'ping', _('Ping') + gather_facts = 'gather_facts', _('Gather facts') + create_account = 'create_account', _('Create account') + change_secret = 'change_secret', _('Change secret') + verify_account = 'verify_account', _('Verify account') + gather_accounts = 'gather_accounts', _('Gather accounts') + + class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) nodes = models.ManyToManyField( diff --git a/apps/assets/models/automations/gather_facts.py b/apps/assets/models/automations/gather_facts.py new file mode 100644 index 000000000..d86ead8e8 --- /dev/null +++ b/apps/assets/models/automations/gather_facts.py @@ -0,0 +1,13 @@ +from django.utils.translation import ugettext_lazy as _ + +from .base import BaseAutomation + + +class GatherFactsAutomation(BaseAutomation): + class Meta: + verbose_name = _("Gather asset facts") + + def save(self, *args, **kwargs): + self.type = 'gather_facts' + super().save(*args, **kwargs) + diff --git a/apps/common/hashers/sm3.py b/apps/common/hashers/sm3.py index 62f811e94..a675dc9df 100644 --- a/apps/common/hashers/sm3.py +++ b/apps/common/hashers/sm3.py @@ -5,6 +5,8 @@ from django.contrib.auth.hashers import PBKDF2PasswordHasher class Hasher: name = 'sm3' + block_size = 64 + digest_size = 32 def __init__(self, key): self.key = key @@ -12,10 +14,19 @@ class Hasher: def hexdigest(self): return sm3.sm3_hash(func.bytes_to_list(self.key)) + def digest(self): + return bytes.fromhex(self.hexdigest()) + @staticmethod - def hash(msg): + def hash(msg=b''): return Hasher(msg) + def update(self, msg): + self.key += msg + + def copy(self): + return Hasher(self.key) + class PBKDF2SM3PasswordHasher(PBKDF2PasswordHasher): algorithm = "pbkdf2_sm3" From 4e8e4e4bb76df2db5c181b10791be26e35342f47 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 14 Oct 2022 19:40:51 +0800 Subject: [PATCH 196/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20gather=20facts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0110_gatherfactsautomation.py | 24 +++++++++++++++++++ apps/assets/models/automations/__init__.py | 1 + .../assets/models/automations/gather_facts.py | 3 +++ apps/common/hashers/sm3.py | 12 +++++----- 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 apps/assets/migrations/0110_gatherfactsautomation.py diff --git a/apps/assets/migrations/0110_gatherfactsautomation.py b/apps/assets/migrations/0110_gatherfactsautomation.py new file mode 100644 index 000000000..8c26d5cf8 --- /dev/null +++ b/apps/assets/migrations/0110_gatherfactsautomation.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-10-14 11:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0109_auto_20221013_1751'), + ] + + operations = [ + migrations.CreateModel( + name='GatherFactsAutomation', + fields=[ + ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ], + options={ + 'verbose_name': 'Gather asset facts', + }, + bases=('assets.baseautomation',), + ), + ] diff --git a/apps/assets/models/automations/__init__.py b/apps/assets/models/automations/__init__.py index 4e46ff150..5aa130c6d 100644 --- a/apps/assets/models/automations/__init__.py +++ b/apps/assets/models/automations/__init__.py @@ -2,3 +2,4 @@ from .change_secret import * from .account_discovery import * from .account_reconcile import * from .account_verify import * +from .gather_facts import * diff --git a/apps/assets/models/automations/gather_facts.py b/apps/assets/models/automations/gather_facts.py index d86ead8e8..251e63944 100644 --- a/apps/assets/models/automations/gather_facts.py +++ b/apps/assets/models/automations/gather_facts.py @@ -3,6 +3,9 @@ from django.utils.translation import ugettext_lazy as _ from .base import BaseAutomation +__all__ = ['GatherFactsAutomation'] + + class GatherFactsAutomation(BaseAutomation): class Meta: verbose_name = _("Gather asset facts") diff --git a/apps/common/hashers/sm3.py b/apps/common/hashers/sm3.py index a675dc9df..da360ea30 100644 --- a/apps/common/hashers/sm3.py +++ b/apps/common/hashers/sm3.py @@ -8,11 +8,11 @@ class Hasher: block_size = 64 digest_size = 32 - def __init__(self, key): - self.key = key + def __init__(self, data): + self.__data = data def hexdigest(self): - return sm3.sm3_hash(func.bytes_to_list(self.key)) + return sm3.sm3_hash(func.bytes_to_list(self.__data)) def digest(self): return bytes.fromhex(self.hexdigest()) @@ -21,11 +21,11 @@ class Hasher: def hash(msg=b''): return Hasher(msg) - def update(self, msg): - self.key += msg + def update(self, data): + self.__data += data def copy(self): - return Hasher(self.key) + return Hasher(self.__data) class PBKDF2SM3PasswordHasher(PBKDF2PasswordHasher): From 6a33129349344c3563dc0f714e3d3a161ea78e35 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 17 Oct 2022 11:22:21 +0800 Subject: [PATCH 197/488] =?UTF-8?q?pref:=20=20=E5=AE=8C=E6=88=90=E6=94=B6?= =?UTF-8?q?=E9=9B=86=E8=B5=84=E4=BA=A7=E4=BF=A1=E6=81=AF=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 2 +- apps/assets/automations/endpoint.py | 2 ++ apps/assets/automations/gather_facts/README.md | 0 .../automations/gather_facts/demo_inventory.txt | 2 +- .../automations/gather_facts/host/windows/main.yml | 8 +------- apps/assets/automations/gather_facts/manager.py | 13 +++++++++++-- apps/assets/signal_handlers/asset.py | 6 ++++-- apps/ops/ansible/callback.py | 8 ++++++++ apps/ops/ansible/inventory.py | 4 ++-- 9 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 apps/assets/automations/gather_facts/README.md diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 59a5bf75c..b1d140492 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -133,7 +133,7 @@ class BasePlaybookManager: summary = cb.summary for state, hosts in summary.items(): for host in hosts: - result = cb.result.get(host) + result = cb.host_results.get(host) if state == 'ok': self.on_host_success(host, result) else: diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index 472089ca1..3e69d2953 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -2,11 +2,13 @@ # # from .change_secret.manager import ChangeSecretManager +from .gather_facts.manager import GatherFactsManager class ExecutionManager: manager_type_mapper = { 'change_secret': ChangeSecretManager, + 'gather_facts': GatherFactsManager, } def __init__(self, execution): diff --git a/apps/assets/automations/gather_facts/README.md b/apps/assets/automations/gather_facts/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/automations/gather_facts/demo_inventory.txt b/apps/assets/automations/gather_facts/demo_inventory.txt index ed011eae2..529a74e67 100644 --- a/apps/assets/automations/gather_facts/demo_inventory.txt +++ b/apps/assets/automations/gather_facts/demo_inventory.txt @@ -1,2 +1,2 @@ # all base inventory in base/base_inventory.txt -asset_name(ip) ...base_inventory_vars +asset_name ...base_inventory_vars diff --git a/apps/assets/automations/gather_facts/host/windows/main.yml b/apps/assets/automations/gather_facts/host/windows/main.yml index 723aa7720..377ffd10a 100644 --- a/apps/assets/automations/gather_facts/host/windows/main.yml +++ b/apps/assets/automations/gather_facts/host/windows/main.yml @@ -1,12 +1,6 @@ - hosts: windows gather_facts: yes tasks: -# - name: Gather facts windows -# setup: -# register: facts -# -# - debug: -# var: facts - name: Get info set_fact: info: @@ -19,6 +13,6 @@ sn: "{{ ansible_product_serial }}" cpu_vcpus: "{{ ansible_processor_vcpus }}" memory: "{{ ansible_memtotal_mb }}" -t + - debug: var: info diff --git a/apps/assets/automations/gather_facts/manager.py b/apps/assets/automations/gather_facts/manager.py index 4b782ae31..a2072b138 100644 --- a/apps/assets/automations/gather_facts/manager.py +++ b/apps/assets/automations/gather_facts/manager.py @@ -1,5 +1,8 @@ +from common.utils import get_logger from ..base.manager import BasePlaybookManager +logger = get_logger(__name__) + class GatherFactsManager(BasePlaybookManager): def __init__(self, *args, **kwargs): @@ -13,10 +16,16 @@ class GatherFactsManager(BasePlaybookManager): def host_callback(self, host, asset=None, **kwargs): super().host_callback(host, asset=asset, **kwargs) self.host_asset_mapper[host['name']] = asset + return host def on_host_success(self, host, result): - print("Host: {}".format(host)) - print("Result: {}".format(result)) + info = result.get('Get info', {}).get('res', {}).get('ansible_facts', {}).get('info', {}) + asset = self.host_asset_mapper.get(host) + if asset and info: + asset.info = info + asset.save() + else: + logger.error("Not found info, task name must be 'Get info': {}".format(host)) diff --git a/apps/assets/signal_handlers/asset.py b/apps/assets/signal_handlers/asset.py index 5aac26319..6caef6385 100644 --- a/apps/assets/signal_handlers/asset.py +++ b/apps/assets/signal_handlers/asset.py @@ -19,12 +19,14 @@ logger = get_logger(__file__) def update_asset_hardware_info_on_created(asset): logger.debug("Update asset `{}` hardware info".format(asset)) - update_assets_hardware_info_util.delay([asset]) + # Todo: + # update_assets_hardware_info_util.delay([asset]) def test_asset_conn_on_created(asset): logger.debug("Test asset `{}` connectivity".format(asset)) - test_asset_connectivity_util.delay([asset]) + # Todo: + # test_asset_connectivity_util.delay([asset]) @receiver(pre_save, sender=Node) diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 84e106e64..344605a18 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -18,6 +18,14 @@ class DefaultCallback: self.status = 'running' self.finished = False + @property + def host_results(self): + results = {} + for state, hosts in self.result.items(): + for host, items in hosts.items(): + results[host] = items + return results + def is_success(self): return self.status != 'successful' diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index ce2c69a46..f6c19b7a2 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -181,13 +181,13 @@ class JMSInventory: else: hosts.append(host) - exclude_hosts = list(filter(lambda x: x.get('exclude'), hosts)) + exclude_hosts = list(filter(lambda x: x.get('error'), hosts)) if exclude_hosts: print(_("Skip hosts below:")) for i, host in enumerate(exclude_hosts, start=1): print("{}: [{}] \t{}".format(i, host['name'], host['error'])) - hosts = list(filter(lambda x: not x.get('exclude'), hosts)) + hosts = list(filter(lambda x: not x.get('error'), hosts)) data = {'all': {'hosts': {}}} for host in hosts: name = host.pop('name') From 4f16c1f92c14e2762178a40699f14fc241ee71f3 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 17 Oct 2022 17:56:19 +0800 Subject: [PATCH 198/488] fix: account init 500 --- apps/assets/api/account/account.py | 2 +- apps/assets/serializers/account/account.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index af1575daa..31ec9dc32 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -44,7 +44,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): 'default': serializers.AccountSecretSerializer } http_method_names = ['get'] - permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] + # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] rbac_perms = { 'list': 'assets.view_assetaccountsecret', 'retrieve': 'assets.view_assetaccountsecret', diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index ecf8e8490..f4a3b495a 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -60,8 +60,8 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): class Meta(BaseAccountSerializer.Meta): model = Account fields = BaseAccountSerializer.Meta.fields \ - + ['su_from', 'version', 'asset'] \ - + ['template', 'push_now'] + + ['su_from', 'version', 'asset'] \ + + ['template', 'push_now'] extra_kwargs = { **BaseAccountSerializer.Meta.extra_kwargs, 'name': {'required': False, 'allow_null': True}, @@ -71,6 +71,9 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): super().__init__(*args, data=data, **kwargs) if data and 'name' not in data: data['name'] = data.get('username') + if hasattr(self, 'initial_data') and \ + not getattr(self, 'initial_data', None): + delattr(self, 'initial_data') @classmethod def setup_eager_loading(cls, queryset): From 1b795791de552101acf6ded3b9b6bcb3a07a4e8b Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 18 Oct 2022 10:43:51 +0800 Subject: [PATCH 199/488] fix: swagger 500 --- apps/ops/models/adhoc.py | 3 +++ apps/ops/serializers/adhoc.py | 16 ++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index c3a7822a9..2273a21b6 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -45,6 +45,9 @@ class AdHocExecution(BaseAnsibleExecution): ) return runner + def task_display(self): + return str(self.task) + class Meta: db_table = "ops_adhoc_execution" get_latest_by = 'date_start' diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index b6522b85f..5df047bfa 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -15,11 +15,11 @@ class AdHocExecutionSerializer(serializers.ModelSerializer): model = AdHocExecution fields_mini = ['id'] fields_small = fields_mini + [ - 'hosts_amount', 'timedelta', 'result', 'summary', 'short_id', + 'timedelta', 'result', 'summary', 'short_id', 'is_finished', 'is_success', 'date_start', 'date_finished', ] - fields_fk = ['task', 'task_display', 'adhoc', 'adhoc_short_id',] + fields_fk = ['task', 'task_display'] fields_custom = ['stat', 'last_success', 'last_failure'] fields = fields_small + fields_fk + fields_custom @@ -50,20 +50,16 @@ class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer): class AdHocSerializer(serializers.ModelSerializer): - become_display = serializers.ReadOnlyField() tasks = serializers.ListField() class Meta: model = AdHoc fields_mini = ['id'] fields_small = fields_mini + [ - 'tasks', "pattern", "options", "run_as", - "become", "become_display", "short_id", - "run_as_admin", - "date_created", + 'tasks', "pattern", "args", "date_created", ] - fields_fk = ["task"] - fields_m2m = ["hosts"] + fields_fk = ["last_execution"] + fields_m2m = ["assets"] fields = fields_small + fields_fk + fields_m2m read_only_fields = [ 'date_created' @@ -92,7 +88,7 @@ class AdHocDetailSerializer(AdHocSerializer): class Meta(AdHocSerializer.Meta): fields = AdHocSerializer.Meta.fields + [ - 'latest_execution', 'created_by', 'run_times', 'task_name' + 'latest_execution', 'created_by', 'task_name' ] From c41e0148d9d2a40257967cc437ad89b624b875c4 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 18 Oct 2022 15:21:44 +0800 Subject: [PATCH 200/488] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83=E5=B7=A5=E5=85=B7=E3=80=81=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83=E8=B4=A6=E5=8F=B7=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/account.py | 58 +++------------------------- apps/perms/utils/permission.py | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 9db8a175f..948f60a84 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -1,22 +1,20 @@ from collections import defaultdict from assets.models import Account -from perms.models import AssetPermission +from .permission import AssetPermissionUtil -class PermAccountUtil(object): - """ 授权账号查询工具 """ - - # Accounts +class PermAccountUtil(AssetPermissionUtil): + """ 资产授权账号相关的工具 """ def get_user_perm_asset_accounts(self, user, asset, with_actions=False): """ 获取授权给用户某个资产的账号 """ - perms = self.get_user_asset_permissions(user, asset) + perms = self.get_permissions_for_user_asset(user, asset) accounts = self.get_permissions_accounts(perms, with_actions=with_actions) return accounts def get_user_perm_accounts(self, user, with_actions=False): """ 获取授权给用户的所有账号 """ - perms = self.get_user_permissions(user) + perms = self.get_permissions_for_user(user) accounts = self.get_permissions_accounts(perms, with_actions=with_actions) return accounts @@ -35,49 +33,3 @@ class PermAccountUtil(object): account.actions = aid_actions_map.get(str(account.id)) return accounts - # Permissions - - def get_user_asset_permissions(self, user, asset): - """ 获取同时包含用户、资产的授权规则 """ - user_perm_ids = self.get_user_permissions(user, flat=True) - asset_perm_ids = self.get_asset_permissions(asset, flat=True) - perm_ids = set(user_perm_ids) & set(asset_perm_ids) - perms = AssetPermission.objects.filter(id__in=perm_ids) - return perms - - def get_user_permissions(self, user, with_group=True, flat=False): - """ 获取用户的授权规则 """ - perm_ids = set() - # user - user_perm_ids = AssetPermission.users.through.objects.filter(user_id=user.id)\ - .values_list('assetpermission_id', flat=True).distinct() - perm_ids.update(user_perm_ids) - # group - if with_group: - groups = user.groups.all() - group_perm_ids = self.get_user_groups_permissions(groups, flat=True) - perm_ids.update(group_perm_ids) - if flat: - return perm_ids - perms = AssetPermission.objects.filter(id__in=perm_ids) - return perms - - @staticmethod - def get_user_groups_permissions(user_groups, flat=False): - """ 获取用户组的授权规则 """ - group_ids = user_groups.values_list('id', flat=True).distinct() - perm_ids = AssetPermission.user_groups.through.objects.filter(usergroup_id__in=group_ids) \ - .values_list('assetpermission_id', flat=True).distinct() - if flat: - return perm_ids - perms = AssetPermission.objects.filter(id__in=perm_ids) - return perms - - def get_asset_permissions(self, asset, flat=False): - """ 获取资产的授权规则""" - return AssetPermission.objects.all() - - def get_node_permissions(self): - """ 获取节点的授权规则 """ - pass - diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index c3a515514..7906cf1d3 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -11,6 +11,75 @@ from perms.utils.user_permission import get_user_all_asset_perm_ids logger = get_logger(__file__) +class AssetPermissionUtil(object): + """ 资产授权相关的方法工具 """ + + def get_permissions_for_user_asset(self, user, asset): + """ 获取同时包含用户、资产的授权规则 """ + user_perm_ids = self.get_permissions_for_user(user, flat=True) + asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) + perm_ids = set(user_perm_ids) & set(asset_perm_ids) + perms = AssetPermission.objects.filter(id__in=perm_ids) + return perms + + def get_permissions_for_user(self, user, with_group=True, flat=False): + """ 获取用户的授权规则 """ + perm_ids = set() + # user + user_perm_ids = AssetPermission.users.through.objects.filter(user_id=user.id) \ + .values_list('assetpermission_id', flat=True).distinct() + perm_ids.update(user_perm_ids) + # group + if with_group: + groups = user.groups.all() + group_perm_ids = self.get_permissions_for_user_groups(groups, flat=True) + perm_ids.update(group_perm_ids) + if flat: + return perm_ids + perms = AssetPermission.objects.filter(id__in=perm_ids) + return perms + + @staticmethod + def get_permissions_for_user_groups(user_groups, flat=False): + """ 获取用户组的授权规则 """ + group_ids = user_groups.values_list('id', flat=True).distinct() + group_perm_ids = AssetPermission.user_groups.through.objects.filter(usergroup_id__in=group_ids) \ + .values_list('assetpermission_id', flat=True).distinct() + if flat: + return group_perm_ids + perms = AssetPermission.objects.filter(id__in=group_perm_ids) + return perms + + def get_permissions_for_asset(self, asset, with_node=True, flat=False): + """ 获取资产的授权规则""" + perm_ids = set() + asset_perm_ids = AssetPermission.assets.through.objects.filter(asset_id=asset.id) \ + .values_list('assetpermission_id', flat=True).distinct() + perm_ids.update(asset_perm_ids) + if with_node: + nodes = asset.get_all_nodes(flat=True) + node_perm_ids = self.get_permissions_for_nodes(nodes, flat=True) + perm_ids.update(node_perm_ids) + if flat: + return perm_ids + perms = AssetPermission.objects.filter(id__in=perm_ids) + return perms + + @staticmethod + def get_permissions_for_nodes(nodes, flat=False): + """ 获取节点的授权规则 """ + node_ids = nodes.values_list('id', flat=True).distinct() + node_perm_ids = AssetPermission.nodes.through.objects.filter(node_id__in=node_ids) \ + .values_list('assetpermission_id', flat=True).distinct() + if flat: + return node_perm_ids + perms = AssetPermission.objects.filter(id__in=node_perm_ids) + return perms + + +# TODO: 下面的方法放到类中进行实现 + + def validate_permission(user, asset, account, action='connect'): asset_perm_ids = get_user_all_asset_perm_ids(user) From 2c04ad64652fc174e9f4b9aa19c5188f3f5ea43d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 18 Oct 2022 16:04:45 +0800 Subject: [PATCH 201/488] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83=E5=B7=A5=E5=85=B7=E3=80=81=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83=E8=B4=A6=E5=8F=B7=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/permission.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 7906cf1d3..3096a87db 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -43,7 +43,8 @@ class AssetPermissionUtil(object): def get_permissions_for_user_groups(user_groups, flat=False): """ 获取用户组的授权规则 """ group_ids = user_groups.values_list('id', flat=True).distinct() - group_perm_ids = AssetPermission.user_groups.through.objects.filter(usergroup_id__in=group_ids) \ + group_perm_ids = AssetPermission.user_groups.through.objects\ + .filter(usergroup_id__in=group_ids)\ .values_list('assetpermission_id', flat=True).distinct() if flat: return group_perm_ids @@ -66,9 +67,16 @@ class AssetPermissionUtil(object): return perms @staticmethod - def get_permissions_for_nodes(nodes, flat=False): + def get_permissions_for_nodes(nodes, with_ancestor=False, flat=False): """ 获取节点的授权规则 """ - node_ids = nodes.values_list('id', flat=True).distinct() + if with_ancestor: + node_ids = set() + for node in nodes: + _nodes = node.get_ancestors(with_self=True) + _node_ids = _nodes.values_list('id', flat=True).distinct() + node_ids.update(_node_ids) + else: + node_ids = nodes.values_list('id', flat=True).distinct() node_perm_ids = AssetPermission.nodes.through.objects.filter(node_id__in=node_ids) \ .values_list('assetpermission_id', flat=True).distinct() if flat: From 152749c872c64bbbad4b1a585ec698266a76c87c Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 18 Oct 2022 16:42:32 +0800 Subject: [PATCH 202/488] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83=E5=B7=A5=E5=85=B7=E3=80=81=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83=E8=B4=A6=E5=8F=B7=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB;=E5=88=A0=E9=99=A4Model=E4=B8=AD=E7=9A=84=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91;=E5=A2=9E=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=BB=84=E3=80=81=E8=B5=84=E4=BA=A7=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E7=9A=84=E8=8E=B7=E5=8F=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_group_permission.py | 5 +- apps/perms/api/user_permission/common.py | 5 +- apps/perms/models/asset_permission.py | 110 ----------------------- apps/perms/utils/account.py | 17 ++-- apps/perms/utils/permission.py | 12 ++- 5 files changed, 30 insertions(+), 119 deletions(-) diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index e6d470681..dedd90a3c 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -11,6 +11,7 @@ from perms.models import AssetPermission from assets.models import Asset, Node from . import user_permission as uapi from perms import serializers +from perms.utils import PermAccountUtil from assets.api.mixin import SerializeToTreeNodeMixin from users.models import UserGroup @@ -200,7 +201,7 @@ class UserGroupGrantedAssetAccountsApi(uapi.UserGrantedAssetAccountsApi): return UserGroup.objects.get(id=group_id) def get_queryset(self): - accounts = AssetPermission.get_perm_asset_accounts( - user_group=self.user_group, asset=self.asset + accounts = PermAccountUtil().get_perm_accounts_for_user_group_asset( + self.user_group, self.asset, with_actions=True ) return accounts diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 3c77a1be5..ebcbcf3e6 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -22,6 +22,7 @@ from common.utils import get_logger, lazyproperty from perms.hands import User, Asset, Account from perms import serializers from perms.models import AssetPermission, Action +from perms.utils import PermAccountUtil logger = get_logger(__name__) @@ -118,7 +119,9 @@ class UserGrantedAssetAccountsApi(ListAPIView): return asset def get_queryset(self): - accounts = AssetPermission.get_perm_asset_accounts(user=self.user, asset=self.asset) + accounts = PermAccountUtil().get_perm_accounts_for_user_asset( + self.user, self.asset, with_actions=True + ) return accounts diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index acd654528..cc071065d 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -177,116 +177,6 @@ class AssetPermission(OrgModelMixin): names = [node.full_value for node in self.nodes.all()] return names - # Accounts - @classmethod - def get_perm_asset_accounts(cls, user=None, user_group=None, asset=None, with_actions=True): - perms = cls.filter(user=user, user_group=user_group, asset=asset) - account_names = cls.retrieve_account_names(perms) - accounts = asset.filter_accounts(account_names) - if with_actions: - cls.set_accounts_actions(accounts, perms=perms) - return accounts - - @classmethod - def set_accounts_actions(cls, accounts, perms): - account_names_actions_map = cls.get_account_names_actions_map(accounts, perms) - for account in accounts: - account.actions = account_names_actions_map.get(account.username) - return accounts - - @classmethod - def get_account_names_actions_map(cls, accounts, perms): - account_names_actions_map = defaultdict(int) - account_names = accounts.values_list('username', flat=True) - perms = perms.filter_by_accounts(account_names) - account_names_actions = perms.values_list('accounts', 'actions') - for account_names, actions in account_names_actions: - for account_name in account_names: - account_names_actions_map[account_name] |= actions - return account_names_actions_map - - @classmethod - def retrieve_account_names(cls, perms): - account_names = set() - for perm in perms: - if not isinstance(perm.accounts, list): - continue - account_names.update(perm.accounts) - return account_names - - @classmethod - def filter(cls, user=None, user_group=None, asset=None, account_names=None): - """ 获取同时包含 用户(组)-资产-账号 的授权规则, 条件之间都是 & 的关系""" - perm_ids = [] - - if user: - user_perm_ids = cls.filter_by_user(user, flat=True) - perm_ids.append(user_perm_ids) - - if user_group: - user_group_perm_ids = cls.filter_by_user_group(user_group, flat=True) - perm_ids.append(user_group_perm_ids) - - if asset: - asset_perm_ids = cls.filter_by_asset(asset, flat=True) - perm_ids.append(asset_perm_ids) - - # & 是同时满足,比如有用户,但是用户的规则是空,那么返回也应该是空 - perm_ids = list(reduce(lambda x, y: set(x) & set(y), perm_ids)) - perms = cls.objects.filter(id__in=perm_ids) - - if account_names: - perms = perms.filter_by_accounts(account_names) - - perms = perms.valid().order_by('-date_expired') - return perms - - @classmethod - def filter_by_user(cls, user, with_group=True, flat=False): - perm_ids = set() - user_perm_ids = AssetPermission.users.through.objects.filter( - user_id=user.id - ).values_list('assetpermission_id', flat=True).distinct() - perm_ids.update(user_perm_ids) - if with_group: - usergroup_ids = user.get_groups(flat=True) - usergroups_perm_id = AssetPermission.user_groups.through.objects.filter( - usergroup_id__in=usergroup_ids - ).values_list('assetpermission_id', flat=True).distinct() - perm_ids.update(usergroups_perm_id) - if flat: - return perm_ids - perms = cls.objects.filter(id__in=perm_ids).valid() - return perms - - @classmethod - def filter_by_user_group(cls, user_group, flat=False): - perm_ids = AssetPermission.user_groups.through.objects.filter( - usergroup_id=user_group - ).values_list('assetpermission_id', flat=True) - if flat: - return set(perm_ids) - perms = cls.objects.filter(id__in=perm_ids).valid() - return perms - - @classmethod - def filter_by_asset(cls, asset, with_node=True, flat=False): - perm_ids = set() - asset_perm_ids = AssetPermission.assets.through.objects.filter( - asset_id=asset.id - ).values_list('assetpermission_id', flat=True).distinct() - perm_ids.update(asset_perm_ids) - if with_node: - node_ids = asset.get_all_nodes(flat=True) - node_perm_ids = AssetPermission.nodes.through.objects.filter( - node_id__in=node_ids - ).values_list('assetpermission_id', flat=True).distinct() - perm_ids.update(node_perm_ids) - if flat: - return perm_ids - perms = cls.objects.filter(id__in=perm_ids).valid() - return perms - class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): class NodeFrom(TextChoices): diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 948f60a84..a5fdadc6b 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -2,24 +2,31 @@ from collections import defaultdict from assets.models import Account from .permission import AssetPermissionUtil +__all__ = ['PermAccountUtil'] + class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ - def get_user_perm_asset_accounts(self, user, asset, with_actions=False): + def get_perm_accounts_for_user_asset(self, user, asset, with_actions=False): """ 获取授权给用户某个资产的账号 """ perms = self.get_permissions_for_user_asset(user, asset) - accounts = self.get_permissions_accounts(perms, with_actions=with_actions) + accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) return accounts - def get_user_perm_accounts(self, user, with_actions=False): + def get_perm_accounts_for_user(self, user, with_actions=False): """ 获取授权给用户的所有账号 """ perms = self.get_permissions_for_user(user) - accounts = self.get_permissions_accounts(perms, with_actions=with_actions) + accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) + return accounts + + def get_perm_accounts_for_user_group_asset(self, user_group, asset, with_actions=False): + perms = self.get_permissions_for_user_group_asset(user_group, asset) + accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) return accounts @staticmethod - def get_permissions_accounts(permissions, with_actions=False): + def get_perm_accounts_for_permissions(permissions, with_actions=False): aid_actions_map = defaultdict(int) for perm in permissions: account_ids = perm.get_all_accounts(flat=True) diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 3096a87db..b16c69fa0 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -22,6 +22,13 @@ class AssetPermissionUtil(object): perms = AssetPermission.objects.filter(id__in=perm_ids) return perms + def get_permissions_for_user_group_asset(self, user_group, asset): + user_perm_ids = self.get_permissions_for_user_groups([user_group], flat=True) + asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) + perm_ids = set(user_perm_ids) & set(asset_perm_ids) + perms = AssetPermission.objects.filter(id__in=perm_ids) + return perms + def get_permissions_for_user(self, user, with_group=True, flat=False): """ 获取用户的授权规则 """ perm_ids = set() @@ -42,7 +49,10 @@ class AssetPermissionUtil(object): @staticmethod def get_permissions_for_user_groups(user_groups, flat=False): """ 获取用户组的授权规则 """ - group_ids = user_groups.values_list('id', flat=True).distinct() + if isinstance(user_groups, list): + group_ids = [g.id for g in user_groups] + else: + group_ids = user_groups.values_list('id', flat=True).distinct() group_perm_ids = AssetPermission.user_groups.through.objects\ .filter(usergroup_id__in=group_ids)\ .values_list('assetpermission_id', flat=True).distinct() From 9b44ed55c297c1eca82e71fbb7e2d6ce44a3347c Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 18 Oct 2022 20:37:17 +0800 Subject: [PATCH 203/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20secret=20e?= =?UTF-8?q?ncrypt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/account.py | 5 ++++ apps/assets/const/base.py | 1 + apps/assets/const/types.py | 11 +++++++- apps/assets/filters.py | 9 ++++++ .../migrations/0111_auto_20221017_1441.py | 23 +++++++++++++++ apps/assets/models/asset/common.py | 5 +++- apps/assets/models/base.py | 11 ++------ apps/assets/models/platform.py | 16 +++++++++-- apps/assets/serializers/account/account.py | 4 +-- apps/assets/serializers/asset/common.py | 28 +++++++++++++++++++ apps/assets/serializers/platform.py | 12 +++++--- apps/common/db/fields.py | 6 ++-- apps/common/drf/serializers/common.py | 2 +- 13 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 apps/assets/migrations/0111_auto_20221017_1441.py diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index af1575daa..d750fa4a0 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -34,6 +34,11 @@ class AccountViewSet(OrgBulkModelViewSet): account = super().get_object() task = test_accounts_connectivity_manual.delay([account.id]) return Response(data={'task': task.id}) + # + # @action(methods=['get'], detail=True, url_path='secret') + # def get_secret(self, request, *args, **kwargs): + # account = super().get_object() + # return Response(data={'secret': account.secret}) class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): diff --git a/apps/assets/const/base.py b/apps/assets/const/base.py index e39f8c09c..35ad76757 100644 --- a/apps/assets/const/base.py +++ b/apps/assets/const/base.py @@ -35,6 +35,7 @@ class BaseType(TextChoices): if choices == '__self__': choices = [tp] protocols = [{'name': name, **settings.get(name, {})} for name in choices] + protocols[0]['primary'] = True return protocols @classmethod diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 0d8b86afa..5cdf5d28e 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -36,6 +36,13 @@ class AllTypes(ChoicesMixin): cls.set_automation_methods(category, tp, constraints) return constraints + @classmethod + def get_primary_protocol_name(cls, category, tp): + constraints = cls.get_constraints(category, tp) + if not constraints: + return None + return constraints.get('protocols')[0]['name'] + @classmethod def set_automation_methods(cls, category, tp, constraints): from assets.automations import filter_platform_methods @@ -189,7 +196,9 @@ class AllTypes(ChoicesMixin): automation.save() platform.protocols.all().delete() - [PlatformProtocol.objects.create(**p, platform=platform) for p in protocols_data] + for p in protocols_data: + p.pop('primary', None) + PlatformProtocol.objects.create(**p, platform=platform) @classmethod def create_or_update_internal_platforms(cls): diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 8e60ac37c..db29f4393 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -165,6 +165,15 @@ class AccountFilterSet(BaseFilterSet): asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact') assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='in') nodes = drf_filters.CharFilter(method='filter_nodes') + has_secret = drf_filters.BooleanFilter(method='filter_has_secret') + + @staticmethod + def filter_has_secret(queryset, name, has_secret): + q = Q(secret__isnull=True) | Q(secret='') + if has_secret: + return queryset.exclude(q) + else: + return queryset.filter(q) @staticmethod def filter_nodes(queryset, name, value): diff --git a/apps/assets/migrations/0111_auto_20221017_1441.py b/apps/assets/migrations/0111_auto_20221017_1441.py new file mode 100644 index 000000000..d534cc125 --- /dev/null +++ b/apps/assets/migrations/0111_auto_20221017_1441.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-10-17 06:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_gatherfactsautomation'), + ] + + operations = [ + migrations.AddField( + model_name='platformprotocol', + name='default', + field=models.BooleanField(default=True, verbose_name='Default'), + ), + migrations.AddField( + model_name='platformprotocol', + name='required', + field=models.BooleanField(default=False, verbose_name='Required'), + ), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 5703c82dc..573091959 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -135,7 +135,10 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): @lazyproperty def primary_protocol(self): - return self.protocols.first() + from assets.const.types import AllTypes + primary_protocol_name = AllTypes.get_primary_protocol_name(self.category, self.type) + protocol = self.protocols.get(name=primary_protocol_name) + return protocol @lazyproperty def protocol(self): diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 7b5b1d6f8..56de5fbac 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -72,18 +72,13 @@ class BaseAccount(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - @property - def has_secret(self): - return bool(self.secret) - @property def password(self): return self.secret - @password.setter - def password(self, value): - self.secret = value - self.secret_type = 'password' + @property + def has_secret(self): + return bool(self.secret) @property def private_key(self): diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 3c32fbcb1..f89221448 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -17,6 +17,8 @@ class PlatformProtocol(models.Model): 'sftp_enabled': True, 'sftp_home': '/tmp' } + default = models.BooleanField(default=False, verbose_name=_('Default')) + required = models.BooleanField(default=False, verbose_name=_('Required')) name = models.CharField(max_length=32, verbose_name=_('Name')) port = models.IntegerField(verbose_name=_('Port')) setting = models.JSONField(verbose_name=_('Setting'), default=dict) @@ -25,6 +27,13 @@ class PlatformProtocol(models.Model): def __str__(self): return '{}/{}'.format(self.name, self.port) + @property + def primary(self): + primary_protocol_name = AllTypes.get_primary_protocol_name( + self.platform.category, self.platform.type + ) + return self.name == primary_protocol_name + @property def secret_types(self): return Protocol.settings().get(self.name, {}).get('secret_types') @@ -83,9 +92,10 @@ class Platform(models.Model): ) return linux.id - @staticmethod - def set_default_platforms_ops(platform_model): - pass + @property + def primary_protocol(self): + primary_protocol_name = AllTypes.get_primary_protocol_name(self.category, self.type) + return self.protocols.filter(name=primary_protocol_name).first() def __str__(self): return self.name diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index ecf8e8490..77658c0fc 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -82,9 +82,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class Meta(AccountSerializer.Meta): extra_kwargs = { - 'password': {'write_only': False}, - 'private_key': {'write_only': False}, - 'public_key': {'write_only': False}, + 'secret': {'write_only': False}, } diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 4aef50f58..45b02f8ac 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -117,6 +117,34 @@ class AssetSerializer(JMSWritableNestedModelSerializer): if not node_id: return [] + def validate_protocols(self, protocols_data): + if not protocols_data: + protocols_data = [] + platform_id = self.initial_data.get('platform') + if isinstance(platform_id, dict): + platform_id = platform_id.get('id') or platform_id.get('pk') + platform = Platform.objects.filter(id=platform_id).first() + if not platform: + raise serializers.ValidationError({'platform': _("Platform not exist")}) + + protocols_data_map = {p['name']: p for p in protocols_data} + platform_protocols = platform.protocols.all() + protocols_default = [p for p in platform_protocols if p.default] + protocols_required = [p for p in platform_protocols if p.required or p.primary] + + if not protocols_data_map: + protocols_data_map = { + p.name: {'name': p.name, 'port': p.port} + for p in protocols_required + protocols_default + } + + protocols_not_found = [p.name for p in protocols_required if p.name not in protocols_data_map] + if protocols_not_found: + raise serializers.ValidationError({ + 'protocols': _("Protocol is required: {}").format(', '.join(protocols_not_found)) + }) + return protocols_data_map.values() + @atomic def create(self, validated_data): nodes_display = validated_data.pop('nodes_display', '') diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 0baeceb8d..06b70be05 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -50,9 +50,9 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): 'gather_facts_method': {'label': '收集信息方式'}, 'verify_account_enabled': {'label': '启用校验账号'}, 'verify_account_method': {'label': '校验账号方式'}, - 'create_account_enabled': {'label': '启用创建账号'}, - 'create_account_method': {'label': '创建账号方式'}, - 'change_secret_enabled': {'label': '启用账号创建改密'}, + 'create_account_enabled': {'label': '启用推送账号'}, + 'create_account_method': {'label': '推送账号方式'}, + 'change_secret_enabled': {'label': '启用账号改密'}, 'change_secret_method': {'label': '账号创建改密方式'}, 'gather_accounts_enabled': {'label': '启用账号收集'}, 'gather_accounts_method': {'label': '收集账号方式'}, @@ -61,10 +61,14 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): class PlatformProtocolsSerializer(serializers.ModelSerializer): setting = ProtocolSettingSerializer(required=False, allow_null=True) + primary = serializers.BooleanField(read_only=True, label=_("Primary")) class Meta: model = PlatformProtocol - fields = ['id', 'name', 'port', 'secret_types', 'setting'] + fields = [ + 'id', 'name', 'port', 'primary', 'default', + 'required', 'secret_types', 'setting', + ] class PlatformSerializer(JMSWritableNestedModelSerializer): diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 0757bc068..72c4df898 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -117,10 +117,10 @@ class EncryptMixin: return signer.unsign(value) or '' def from_db_value(self, value, expression, connection, context=None): - if value is None: + if not value: return value - value = force_text(value) + value = force_text(value) plain_value = crypto.decrypt(value) # 如果没有解开,使用原来的signer解密 @@ -134,7 +134,7 @@ class EncryptMixin: return plain_value def get_prep_value(self, value): - if value is None: + if not value: return value # 先 json 再解密 diff --git a/apps/common/drf/serializers/common.py b/apps/common/drf/serializers/common.py index 836914a60..cfd81d476 100644 --- a/apps/common/drf/serializers/common.py +++ b/apps/common/drf/serializers/common.py @@ -86,5 +86,5 @@ class GroupedChoiceSerializer(ChoiceSerializer): children = ChoiceSerializer(many=True, label=_("Children")) -class JMSWritableNestedModelSerializer(WritableNestedModelSerializer): +class JMSWritableNestedModelSerializer(ModelSerializer): pass From d6e36c873d66acd5a90d24c70f415f6231b16d63 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 19 Oct 2022 10:21:05 +0800 Subject: [PATCH 204/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/account.py | 6 +----- apps/assets/filters.py | 4 ++-- apps/assets/serializers/asset/common.py | 5 +++-- apps/assets/serializers/platform.py | 4 ++-- apps/common/drf/serializers/common.py | 6 +++--- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 83bbc4d13..eaba34571 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -34,11 +34,6 @@ class AccountViewSet(OrgBulkModelViewSet): account = super().get_object() task = test_accounts_connectivity_manual.delay([account.id]) return Response(data={'task': task.id}) - # - # @action(methods=['get'], detail=True, url_path='secret') - # def get_secret(self, request, *args, **kwargs): - # account = super().get_object() - # return Response(data={'secret': account.secret}) class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): @@ -49,6 +44,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): 'default': serializers.AccountSecretSerializer } http_method_names = ['get'] + # Todo: 记得打开 # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] rbac_perms = { 'list': 'assets.view_assetaccountsecret', diff --git a/apps/assets/filters.py b/apps/assets/filters.py index db29f4393..2af062718 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -163,7 +163,7 @@ class AccountFilterSet(BaseFilterSet): username = drf_filters.CharFilter(field_name="username", lookup_expr='exact') address = drf_filters.CharFilter(field_name="asset__address", lookup_expr='exact') asset = drf_filters.CharFilter(field_name="asset_id", lookup_expr='exact') - assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='in') + assets = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact') nodes = drf_filters.CharFilter(method='filter_nodes') has_secret = drf_filters.BooleanFilter(method='filter_has_secret') @@ -177,7 +177,7 @@ class AccountFilterSet(BaseFilterSet): @staticmethod def filter_nodes(queryset, name, value): - nodes = Node.objects.filter(id__in=value) + nodes = Node.objects.filter(id=value) if not nodes: return queryset diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 45b02f8ac..059b7ffe3 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -6,8 +6,9 @@ from django.utils.translation import ugettext_lazy as _ from django.db.transaction import atomic from django.db.models import F -from common.drf.serializers import JMSWritableNestedModelSerializer +from common.drf.serializers import WritableNestedModelSerializer from common.drf.fields import LabeledChoiceField, ObjectRelatedField +from orgs.mixins.serializers import OrgResourceSerializerMixin from ..account import AccountSerializer from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol from ...const import Category, AllTypes @@ -57,7 +58,7 @@ class AssetAccountSerializer(AccountSerializer): fields = fields_mini + fields_write_only -class AssetSerializer(JMSWritableNestedModelSerializer): +class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer): category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type')) domain = ObjectRelatedField(required=False, queryset=Domain.objects, label=_('Domain'), allow_null=True) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 06b70be05..cb3ade099 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -2,7 +2,7 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.drf.fields import LabeledChoiceField -from common.drf.serializers import JMSWritableNestedModelSerializer +from common.drf.serializers import WritableNestedModelSerializer from ..models import Platform, PlatformProtocol, PlatformAutomation from ..const import Category, AllTypes @@ -71,7 +71,7 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer): ] -class PlatformSerializer(JMSWritableNestedModelSerializer): +class PlatformSerializer(WritableNestedModelSerializer): type = LabeledChoiceField(choices=AllTypes.choices(), label=_("Type")) category = LabeledChoiceField(choices=Category.choices, label=_("Category")) protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) diff --git a/apps/common/drf/serializers/common.py b/apps/common/drf/serializers/common.py index cfd81d476..688e1bfcc 100644 --- a/apps/common/drf/serializers/common.py +++ b/apps/common/drf/serializers/common.py @@ -5,7 +5,7 @@ from rest_framework.serializers import ModelSerializer from rest_framework_bulk.serializers import BulkListSerializer from django.utils.translation import gettext_lazy as _ from django.utils.functional import cached_property -from drf_writable_nested.serializers import WritableNestedModelSerializer +from drf_writable_nested.serializers import WritableNestedModelSerializer as NestedModelSerializer from .mixin import BulkListSerializerMixin, BulkSerializerMixin @@ -13,7 +13,7 @@ from .mixin import BulkListSerializerMixin, BulkSerializerMixin __all__ = [ 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer', - 'JMSWritableNestedModelSerializer', + 'WritableNestedModelSerializer', 'GroupedChoiceSerializer', ] @@ -86,5 +86,5 @@ class GroupedChoiceSerializer(ChoiceSerializer): children = ChoiceSerializer(many=True, label=_("Children")) -class JMSWritableNestedModelSerializer(ModelSerializer): +class WritableNestedModelSerializer(NestedModelSerializer): pass From 076afb2b8b5f71374adb89e9d24e8046c367716d Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 19 Oct 2022 11:39:11 +0800 Subject: [PATCH 205/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20automation?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/cloud.py | 2 +- apps/assets/const/database.py | 2 +- apps/assets/const/device.py | 2 +- apps/assets/const/host.py | 6 +- apps/assets/const/types.py | 8 +- apps/assets/const/web.py | 2 +- .../migrations/0096_auto_20220426_1550.py | 6 +- .../migrations/0098_auto_20220430_2126.py | 6 +- .../migrations/0099_auto_20220711_1409.py | 15 - .../migrations/0100_auto_20220711_1413.py | 4 +- .../migrations/0102_auto_20220803_1859.py | 4 +- .../migrations/0104_auto_20220816_1022.py | 4 +- .../assets/migrations/0107_account_history.py | 73 - ...tomation.py => 0107_auto_20221019_1115.py} | 98 +- .../migrations/0109_auto_20221013_1751.py | 83 - .../migrations/0110_gatherfactsautomation.py | 24 - .../migrations/0111_auto_20221017_1441.py | 23 - apps/assets/models/automations/__init__.py | 6 +- .../models/automations/change_secret.py | 2 +- ...ount_reconcile.py => discovery_account.py} | 7 +- .../{account_discovery.py => push_account.py} | 8 +- .../{account_verify.py => verify_secret.py} | 6 +- apps/assets/models/platform.py | 4 +- apps/assets/models/utils.py | 2 +- apps/assets/serializers/platform.py | 6 +- apps/locale/ja/LC_MESSAGES/django.po | 3423 +++++++++-------- apps/locale/zh/LC_MESSAGES/django.po | 3405 ++++++++-------- .../migrations/0002_auto_20210909_1946.py | 2 +- apps/rbac/builtin.py | 2 +- .../migrations/0004_auto_20211201_1901.py | 4 +- apps/rbac/signal_handlers.py | 2 +- .../migrations/0013_ticket_serial_num.py | 2 +- .../migrations/0020_auto_20220817_1346.py | 2 +- apps/users/models/user.py | 2 +- utils/test_run_migrations.py | 2 +- 35 files changed, 3837 insertions(+), 3412 deletions(-) delete mode 100644 apps/assets/migrations/0107_account_history.py rename apps/assets/migrations/{0108_migrate_automation.py => 0107_auto_20221019_1115.py} (53%) delete mode 100644 apps/assets/migrations/0109_auto_20221013_1751.py delete mode 100644 apps/assets/migrations/0110_gatherfactsautomation.py delete mode 100644 apps/assets/migrations/0111_auto_20221017_1441.py rename apps/assets/models/automations/{account_reconcile.py => discovery_account.py} (59%) rename apps/assets/models/automations/{account_discovery.py => push_account.py} (52%) rename apps/assets/models/automations/{account_verify.py => verify_secret.py} (62%) diff --git a/apps/assets/const/cloud.py b/apps/assets/const/cloud.py index d6e6e2599..7bc1864f1 100644 --- a/apps/assets/const/cloud.py +++ b/apps/assets/const/cloud.py @@ -25,7 +25,7 @@ class CloudTypes(BaseType): 'gather_facts_enabled': False, 'verify_account_enabled': False, 'change_secret_enabled': False, - 'create_account_enabled': False, + 'push_account_enabled': False, 'gather_accounts_enabled': False, } } diff --git a/apps/assets/const/database.py b/apps/assets/const/database.py index 43c887bd9..47e45dc71 100644 --- a/apps/assets/const/database.py +++ b/apps/assets/const/database.py @@ -33,7 +33,7 @@ class DatabaseTypes(BaseType): 'gather_accounts_enabled': True, 'verify_account_enabled': True, 'change_secret_enabled': True, - 'create_account_enabled': True, + 'push_account_enabled': True, } } return constrains diff --git a/apps/assets/const/device.py b/apps/assets/const/device.py index fb1879ea3..1e2a5b717 100644 --- a/apps/assets/const/device.py +++ b/apps/assets/const/device.py @@ -40,7 +40,7 @@ class DeviceTypes(BaseType): 'gather_accounts_enabled': False, 'verify_account_enabled': False, 'change_secret_enabled': False, - 'create_account_enabled': False, + 'push_account_enabled': False, } } diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index a8db5f022..6236379a5 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -52,7 +52,7 @@ class HostTypes(BaseType): 'gather_accounts_enabled': True, 'verify_account_enabled': True, 'change_secret_enabled': True, - 'create_account_enabled': True, + 'push_account_enabled': True, }, cls.WINDOWS: { 'ansible_config': { @@ -73,8 +73,8 @@ class HostTypes(BaseType): {'name': 'macOS'}, {'name': 'BSD'}, {'name': 'AIX', 'automation': { - 'create_account_method': 'create_account_aix', - 'change_secret_method': 'change_secret_aix' + 'push_account_method': 'push_account_aix', + 'change_secret_method': 'push_secret_aix' }}, ], cls.WINDOWS: [ diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 5cdf5d28e..939a00ea8 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -204,18 +204,18 @@ class AllTypes(ChoicesMixin): def create_or_update_internal_platforms(cls): print("Create internal platforms") for category, type_cls in cls.category_types(): - print("## Category: {}".format(category.label)) + print("\t## Category: {}".format(category.label)) data = type_cls.internal_platforms() for tp, platform_datas in data.items(): - print(" >> Type: {}".format(tp.label)) + print("\t >> Type: {}".format(tp.label)) default_platform_data = cls.get_type_default_platform(category, tp) default_automation = default_platform_data.pop('automation', {}) default_protocols = default_platform_data.pop('protocols', []) for d in platform_datas: name = d['name'] - print(" - Platform: {}".format(name)) + print("\t - Platform: {}".format(name)) _automation = d.pop('automation', {}) _protocols = d.pop('_protocols', []) _protocols_setting = d.pop('protocols_setting', {}) @@ -246,7 +246,7 @@ class AllTypes(ChoicesMixin): user_platforms.update(internal=False) for platform in user_platforms: - print("Update platform: {}".format(platform.name)) + print("\t- Update platform: {}".format(platform.name)) platform_data = cls.get_type_default_platform(platform.category, platform.type) cls.create_or_update_by_platform_data(platform.name, platform_data) diff --git a/apps/assets/const/web.py b/apps/assets/const/web.py index cf54fa6b4..20c35b3a1 100644 --- a/apps/assets/const/web.py +++ b/apps/assets/const/web.py @@ -23,7 +23,7 @@ class WebTypes(BaseType): 'gather_facts_enabled': False, 'verify_account_enabled': False, 'change_secret_enabled': False, - 'create_account_enabled': False, + 'push_account_enabled': False, 'gather_accounts_enabled': False, } } diff --git a/apps/assets/migrations/0096_auto_20220426_1550.py b/apps/assets/migrations/0096_auto_20220426_1550.py index c3121b18c..8d7e6be27 100644 --- a/apps/assets/migrations/0096_auto_20220426_1550.py +++ b/apps/assets/migrations/0096_auto_20220426_1550.py @@ -19,6 +19,8 @@ class Migration(migrations.Migration): ('port', models.IntegerField(verbose_name='Port')), ('setting', models.JSONField(default=dict, verbose_name='Setting')), ('platform', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.platform'),), + ('default', models.BooleanField(default=True, verbose_name='Default')), + ('required', models.BooleanField(default=False, verbose_name='Required')), ], ), migrations.CreateModel( @@ -31,8 +33,8 @@ class Migration(migrations.Migration): ('ping_method', models.CharField(blank=True, max_length=32, null=True, verbose_name='Ping method')), ('gather_facts_enabled', models.BooleanField(default=False, verbose_name='Gather facts enabled')), ('gather_facts_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Gather facts method')), - ('create_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), - ('create_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')), + ('push_account_enabled', models.BooleanField(default=False, verbose_name='Create account enabled')), + ('push_account_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method')), ('change_secret_enabled', models.BooleanField(default=False, verbose_name='Change password enabled')), ('change_secret_method', models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method')), ('verify_account_enabled', models.BooleanField(default=False, verbose_name='Verify account enabled')), diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index 7dc157977..3ac99b577 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -41,7 +41,7 @@ def migrate_database_to_asset(apps, *args): org_id=app.org_id ) try: - print("Create database: ", app.name) + print("\t- Create database: ", app.name) db.save() except: failed_apps.append(app) @@ -59,7 +59,7 @@ def migrate_cloud_to_asset(apps, *args): for app in applications: attrs = app.attrs - print("Create cloud: {}".format(app.name)) + print("\t- Create cloud: {}".format(app.name)) cloud = cloud_model( id=app.id, name=app.name, address=attrs.get('cluster', ''), @@ -115,7 +115,7 @@ def migrate_to_nodes(apps, *args): ) if not node: continue - print("Set node asset: ", node) + print("\t- Set node asset: ", node) node.assets_amount = len(assets) node.save() node.assets.set(assets) diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index e5ead66c8..cf04f8b66 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -19,23 +19,13 @@ class Migration(migrations.Migration): migrations.CreateModel( name='HistoricalAccount', fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), - ('name', models.CharField(max_length=128, verbose_name='Name')), - ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('comment', models.TextField(blank=True, verbose_name='Comment')), - ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), - ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), - ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), - ('privileged', models.BooleanField(default=False, verbose_name='Privileged')), - ('version', models.IntegerField(default=0, verbose_name='Version')), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), ('history_change_reason', models.CharField(max_length=100, null=True)), ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('asset', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.asset', verbose_name='Asset')), ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), ], options={ @@ -74,11 +64,6 @@ class Migration(migrations.Migration): name='su_from', field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='assets.account', verbose_name='Su from'), ), - migrations.AddField( - model_name='historicalaccount', - name='su_from', - field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.account', verbose_name='Su from'), - ), migrations.CreateModel( name='AccountTemplate', fields=[ diff --git a/apps/assets/migrations/0100_auto_20220711_1413.py b/apps/assets/migrations/0100_auto_20220711_1413.py index 9f7ec8f73..f45ff27da 100644 --- a/apps/assets/migrations/0100_auto_20220711_1413.py +++ b/apps/assets/migrations/0100_auto_20220711_1413.py @@ -11,7 +11,7 @@ def migrate_accounts(apps, schema_editor): count = 0 bulk_size = 1000 - print("\nStart migrate accounts") + print("\n\tStart migrate accounts") while True: start = time.time() auth_books = auth_book_model.objects \ @@ -71,7 +71,7 @@ def migrate_accounts(apps, schema_editor): accounts.append(account) account_model.objects.bulk_create(accounts, ignore_conflicts=True) - print("Create accounts: {}-{} using: {:.2f}s".format( + print("\t - Create accounts: {}-{} using: {:.2f}s".format( count - len(auth_books), count, time.time()-start )) diff --git a/apps/assets/migrations/0102_auto_20220803_1859.py b/apps/assets/migrations/0102_auto_20220803_1859.py index f03332127..afaa63d41 100644 --- a/apps/assets/migrations/0102_auto_20220803_1859.py +++ b/apps/assets/migrations/0102_auto_20220803_1859.py @@ -9,7 +9,7 @@ def migrate_asset_protocols(apps, schema_editor): count = 0 bulk_size = 1000 - print("\nStart migrate asset protocols") + print("\n\tStart migrate asset protocols") while True: start = time.time() assets = asset_model.objects.all()[count:count+bulk_size] @@ -36,7 +36,7 @@ def migrate_asset_protocols(apps, schema_editor): assets_protocols.append(protocol) protocol_model.objects.bulk_create(assets_protocols, ignore_conflicts=True) - print("Create asset protocols: {}-{} using: {:.2f}s".format( + print("\t - Create asset protocols: {}-{} using: {:.2f}s".format( count - len(assets), count, time.time()-start )) diff --git a/apps/assets/migrations/0104_auto_20220816_1022.py b/apps/assets/migrations/0104_auto_20220816_1022.py index 45523495e..111e4a479 100644 --- a/apps/assets/migrations/0104_auto_20220816_1022.py +++ b/apps/assets/migrations/0104_auto_20220816_1022.py @@ -9,7 +9,7 @@ def migrate_command_filter_to_assets(apps, schema_editor): count = 0 bulk_size = 1000 - print("\nStart migrate command filters to assets") + print("\n\tStart migrate command filters to assets") while True: start = time.time() command_filters = command_filter_model.objects.all() \ @@ -23,7 +23,7 @@ def migrate_command_filter_to_assets(apps, schema_editor): updated.append(command_filter) command_filter_model.objects.bulk_update(updated, ['accounts']) - print("Create assets: {}-{} using: {:.2f}s".format( + print("\tCreate assets: {}-{} using: {:.2f}s".format( count - len(command_filters), count, time.time() - start )) diff --git a/apps/assets/migrations/0107_account_history.py b/apps/assets/migrations/0107_account_history.py deleted file mode 100644 index 66ec1e36b..000000000 --- a/apps/assets/migrations/0107_account_history.py +++ /dev/null @@ -1,73 +0,0 @@ -# Generated by Django 3.2.13 on 2022-10-09 08:50 - -from django.db import migrations - - -def migrate_create_history_account(apps, schema_editor): - db_alias = schema_editor.connection.alias - account_model = apps.get_model('assets', 'Account') - history_account_model = apps.get_model('assets', 'HistoricalAccount') - history_accounts = [] - for account in account_model.objects.using(db_alias).all(): - data = { - 'id': account.id, - 'secret': account.secret, - 'secret_type': account.secret_type, - 'history_date': account.date_created, - } - history_accounts.append(history_account_model(**data)) - history_account_model.objects.using(db_alias).bulk_create(history_accounts) - - -class Migration(migrations.Migration): - dependencies = [ - ('assets', '0106_auto_20220916_1556'), - ] - - operations = [ - migrations.RemoveField( - model_name='historicalaccount', - name='asset', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='comment', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='created_by', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='date_created', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='date_updated', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='name', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='org_id', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='privileged', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='su_from', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='username', - ), - migrations.RemoveField( - model_name='historicalaccount', - name='version', - ), - migrations.RunPython(migrate_create_history_account), - ] diff --git a/apps/assets/migrations/0108_migrate_automation.py b/apps/assets/migrations/0107_auto_20221019_1115.py similarity index 53% rename from apps/assets/migrations/0108_migrate_automation.py rename to apps/assets/migrations/0107_auto_20221019_1115.py index 86a29c193..4790068d0 100644 --- a/apps/assets/migrations/0108_migrate_automation.py +++ b/apps/assets/migrations/0107_auto_20221019_1115.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.14 on 2022-10-10 01:59 +# Generated by Django 3.2.14 on 2022-10-19 03:15 import common.db.fields from django.conf import settings @@ -11,10 +11,26 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0107_account_history'), + ('assets', '0106_auto_20220916_1556'), ] operations = [ + migrations.CreateModel( + name='AutomationExecution', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(default='pending', max_length=16)), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), + ('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')), + ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), + ], + options={ + 'verbose_name': 'Automation strategy execution', + }, + ), migrations.CreateModel( name='BaseAutomation', fields=[ @@ -30,6 +46,7 @@ class Migration(migrations.Migration): ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), ('accounts', models.JSONField(default=list, verbose_name='Accounts')), ('type', models.CharField(max_length=16, verbose_name='Type')), + ('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('comment', models.TextField(blank=True, verbose_name='Comment')), ('assets', models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets')), ('nodes', models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes')), @@ -54,64 +71,103 @@ class Migration(migrations.Migration): name='updated_by', field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), ), + migrations.AlterField( + model_name='platformautomation', + name='push_account_enabled', + field=models.BooleanField(default=False, verbose_name='Push account enabled'), + ), + migrations.AlterField( + model_name='platformautomation', + name='push_account_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Push account method'), + ), + migrations.AlterField( + model_name='platformprotocol', + name='default', + field=models.BooleanField(default=False, verbose_name='Default'), + ), migrations.CreateModel( - name='DiscoveryAutomation', + name='DiscoveryAccountAutomation', fields=[ ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), ], options={ - 'verbose_name': 'Discovery strategy', + 'verbose_name': 'Discovery account automation', }, bases=('assets.baseautomation',), ), migrations.CreateModel( - name='ReconcileAutomation', + name='GatherFactsAutomation', fields=[ ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), ], options={ - 'verbose_name': 'Reconcile strategy', + 'verbose_name': 'Gather asset facts', }, bases=('assets.baseautomation',), ), migrations.CreateModel( - name='VerifyAutomation', + name='PushAccountAutomation', fields=[ ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), ], options={ - 'verbose_name': 'Verify strategy', + 'verbose_name': 'Push automation', }, bases=('assets.baseautomation',), ), migrations.CreateModel( - name='AutomationExecution', + name='VerifySecretAutomation', fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ], + options={ + 'verbose_name': 'Verify secret automation', + }, + bases=('assets.baseautomation',), + ), + migrations.CreateModel( + name='ChangeSecretRecord', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')), + ('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), + ('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')), + ('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')), ('status', models.CharField(default='pending', max_length=16)), - ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), - ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), - ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), - ('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')), - ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), - ('automation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy')), + ('error', models.TextField(blank=True, null=True, verbose_name='Error')), + ('account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.account')), + ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.automationexecution')), ], options={ - 'verbose_name': 'Automation strategy execution', + 'verbose_name': 'Change secret', }, ), + migrations.AddField( + model_name='automationexecution', + name='automation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy'), + ), migrations.CreateModel( - name='ChangePasswordAutomation', + name='ChangeSecretAutomation', fields=[ ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ('secret_types', models.JSONField(default=list, verbose_name='Secret types')), + ('password_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Password strategy')), ('password', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('recipients', models.ManyToManyField(blank=True, related_name='recipients_change_auth_strategy', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), + ('password_rules', models.JSONField(default=dict, verbose_name='Password rules')), + ('ssh_key_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16)), + ('ssh_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH key')), + ('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key strategy')), + ('recipients', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), ], options={ - 'verbose_name': 'Change auth strategy', + 'verbose_name': 'Change secret automation', }, bases=('assets.baseautomation',), ), - ] diff --git a/apps/assets/migrations/0109_auto_20221013_1751.py b/apps/assets/migrations/0109_auto_20221013_1751.py deleted file mode 100644 index c00a11b34..000000000 --- a/apps/assets/migrations/0109_auto_20221013_1751.py +++ /dev/null @@ -1,83 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-13 09:51 - -import common.db.fields -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0108_migrate_automation'), - ] - - operations = [ - migrations.RenameModel( - old_name='ChangePasswordAutomation', - new_name='ChangeSecretAutomation', - ), - migrations.AddField( - model_name='baseautomation', - name='is_active', - field=models.BooleanField(default=True, verbose_name='Is active'), - ), - migrations.AddField( - model_name='changesecretautomation', - name='password_rules', - field=models.JSONField(default=dict, verbose_name='Password rules'), - ), - migrations.AddField( - model_name='changesecretautomation', - name='password_strategy', - field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Password strategy'), - ), - migrations.AddField( - model_name='changesecretautomation', - name='secret_types', - field=models.JSONField(default=list, verbose_name='Secret types'), - ), - migrations.AddField( - model_name='changesecretautomation', - name='ssh_key', - field=common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH key'), - ), - migrations.AddField( - model_name='changesecretautomation', - name='ssh_key_change_strategy', - field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key strategy'), - ), - migrations.AddField( - model_name='changesecretautomation', - name='ssh_key_strategy', - field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16), - ), - migrations.AlterField( - model_name='changesecretautomation', - name='recipients', - field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient'), - ), - migrations.CreateModel( - name='ChangeSecretRecord', - fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('old_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Old secret')), - ('new_secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), - ('date_started', models.DateTimeField(blank=True, null=True, verbose_name='Date started')), - ('date_finished', models.DateTimeField(blank=True, null=True, verbose_name='Date finished')), - ('status', models.CharField(default='pending', max_length=16)), - ('error', models.TextField(blank=True, null=True, verbose_name='Error')), - ('account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.account')), - ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.automationexecution')), - ], - options={ - 'verbose_name': 'Change secret', - }, - ), - ] diff --git a/apps/assets/migrations/0110_gatherfactsautomation.py b/apps/assets/migrations/0110_gatherfactsautomation.py deleted file mode 100644 index 8c26d5cf8..000000000 --- a/apps/assets/migrations/0110_gatherfactsautomation.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-14 11:40 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0109_auto_20221013_1751'), - ] - - operations = [ - migrations.CreateModel( - name='GatherFactsAutomation', - fields=[ - ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), - ], - options={ - 'verbose_name': 'Gather asset facts', - }, - bases=('assets.baseautomation',), - ), - ] diff --git a/apps/assets/migrations/0111_auto_20221017_1441.py b/apps/assets/migrations/0111_auto_20221017_1441.py deleted file mode 100644 index d534cc125..000000000 --- a/apps/assets/migrations/0111_auto_20221017_1441.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-17 06:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0110_gatherfactsautomation'), - ] - - operations = [ - migrations.AddField( - model_name='platformprotocol', - name='default', - field=models.BooleanField(default=True, verbose_name='Default'), - ), - migrations.AddField( - model_name='platformprotocol', - name='required', - field=models.BooleanField(default=False, verbose_name='Required'), - ), - ] diff --git a/apps/assets/models/automations/__init__.py b/apps/assets/models/automations/__init__.py index 5aa130c6d..77a885b1b 100644 --- a/apps/assets/models/automations/__init__.py +++ b/apps/assets/models/automations/__init__.py @@ -1,5 +1,5 @@ from .change_secret import * -from .account_discovery import * -from .account_reconcile import * -from .account_verify import * +from .discovery_account import * +from .push_account import * +from .verify_secret import * from .gather_facts import * diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index c7efb88d0..0d34a4840 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -39,7 +39,7 @@ class ChangeSecretAutomation(BaseAutomation): super().save(*args, **kwargs) class Meta: - verbose_name = _("Change auth strategy") + verbose_name = _("Change secret automation") class ChangeSecretRecord(JMSBaseModel): diff --git a/apps/assets/models/automations/account_reconcile.py b/apps/assets/models/automations/discovery_account.py similarity index 59% rename from apps/assets/models/automations/account_reconcile.py rename to apps/assets/models/automations/discovery_account.py index f69d1c82d..9e2adf610 100644 --- a/apps/assets/models/automations/account_reconcile.py +++ b/apps/assets/models/automations/discovery_account.py @@ -1,16 +1,15 @@ from django.utils.translation import ugettext_lazy as _ -from ops.const import StrategyChoice from .base import BaseAutomation -class ReconcileAutomation(BaseAutomation): +class DiscoveryAccountAutomation(BaseAutomation): class Meta: - verbose_name = _("Reconcile strategy") + verbose_name = _("Discovery account automation") def to_attr_json(self): attr_json = super().to_attr_json() attr_json.update({ - 'type': StrategyChoice.push + 'type': 'discover_account' }) return attr_json diff --git a/apps/assets/models/automations/account_discovery.py b/apps/assets/models/automations/push_account.py similarity index 52% rename from apps/assets/models/automations/account_discovery.py rename to apps/assets/models/automations/push_account.py index 9572986f3..c36d89b43 100644 --- a/apps/assets/models/automations/account_discovery.py +++ b/apps/assets/models/automations/push_account.py @@ -1,17 +1,15 @@ from django.utils.translation import ugettext_lazy as _ -from ops.const import StrategyChoice -from ops.ansible.runner import PlaybookRunner from .base import BaseAutomation -class DiscoveryAutomation(BaseAutomation): +class PushAccountAutomation(BaseAutomation): class Meta: - verbose_name = _("Discovery strategy") + verbose_name = _("Push automation") def to_attr_json(self): attr_json = super().to_attr_json() attr_json.update({ - 'type': StrategyChoice.collect + 'type': 'push_account' }) return attr_json diff --git a/apps/assets/models/automations/account_verify.py b/apps/assets/models/automations/verify_secret.py similarity index 62% rename from apps/assets/models/automations/account_verify.py rename to apps/assets/models/automations/verify_secret.py index 15551f75c..f2a1d5bdb 100644 --- a/apps/assets/models/automations/account_verify.py +++ b/apps/assets/models/automations/verify_secret.py @@ -4,10 +4,10 @@ from ops.const import StrategyChoice from .base import BaseAutomation -class VerifyAutomation(BaseAutomation): +class VerifySecretAutomation(BaseAutomation): class Meta: - verbose_name = _("Verify strategy") + verbose_name = _("Verify secret automation") def save(self, *args, **kwargs): - self.type = 'verify' + self.type = 'verify_secret' super().save(*args, **kwargs) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index f89221448..2abcf3652 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -46,8 +46,8 @@ class PlatformAutomation(models.Model): ping_method = models.CharField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) gather_facts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) - create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) - create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) + push_account_enabled = models.BooleanField(default=False, verbose_name=_("Push account enabled")) + push_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Push account method")) change_secret_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) change_secret_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) diff --git a/apps/assets/models/utils.py b/apps/assets/models/utils.py index aad8b9c37..d60c5a28c 100644 --- a/apps/assets/models/utils.py +++ b/apps/assets/models/utils.py @@ -32,7 +32,7 @@ def update_internal_platforms(platform_model): {'name': 'Windows', 'category': 'host', 'type': 'unix'}, { 'name': 'AIX', 'category': 'host', 'type': 'unix', - 'create_account_method': 'create_account_aix', + 'push_account_method': 'create_account_aix', 'change_secret_method': 'change_secret_aix', }, {'name': 'Windows', 'category': 'host', 'type': 'windows'}, diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index cb3ade099..3bc02732f 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -38,7 +38,7 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): 'id', 'ansible_enabled', 'ansible_config', 'ping_enabled', 'ping_method', 'gather_facts_enabled', 'gather_facts_method', - 'create_account_enabled', 'create_account_method', + 'push_account_enabled', 'push_account_method', 'change_secret_enabled', 'change_secret_method', 'verify_account_enabled', 'verify_account_method', 'gather_accounts_enabled', 'gather_accounts_method', @@ -50,8 +50,8 @@ class PlatformAutomationSerializer(serializers.ModelSerializer): 'gather_facts_method': {'label': '收集信息方式'}, 'verify_account_enabled': {'label': '启用校验账号'}, 'verify_account_method': {'label': '校验账号方式'}, - 'create_account_enabled': {'label': '启用推送账号'}, - 'create_account_method': {'label': '推送账号方式'}, + 'push_account_enabled': {'label': '启用推送账号'}, + 'push_account_method': {'label': '推送账号方式'}, 'change_secret_enabled': {'label': '启用账号改密'}, 'change_secret_method': {'label': '账号创建改密方式'}, 'gather_accounts_enabled': {'label': '启用账号收集'}, diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index a5cb194ad..d29f4e311 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-17 16:28+0800\n" +"POT-Creation-Date: 2022-10-19 10:41+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,61 +22,66 @@ msgstr "" msgid "Acls" msgstr "Acls" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 -#: applications/models/application.py:220 assets/models/asset.py:138 -#: assets/models/base.py:175 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 -#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 -#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 +#: applications/models.py:10 assets/models/_user.py:33 +#: assets/models/asset/common.py:81 assets/models/asset/common.py:91 +#: assets/models/base.py:65 assets/models/cmd_filter.py:25 +#: assets/models/domain.py:24 assets/models/group.py:20 +#: assets/models/label.py:17 assets/models/platform.py:22 +#: assets/models/platform.py:68 assets/serializers/asset/common.py:85 +#: assets/serializers/platform.py:104 ops/mixin.py:22 ops/models/playbook.py:9 +#: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86 #: terminal/models/storage.py:26 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 -#: xpack/plugins/cloud/models.py:28 +#: xpack/plugins/cloud/models.py:30 msgid "Name" msgstr "名前" -#: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:89 +#: acls/models/base.py:27 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:89 msgid "Priority" msgstr "優先順位" -#: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:90 +#: acls/models/base.py:28 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:90 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" -#: acls/models/base.py:31 authentication/models.py:21 +#: acls/models/base.py:31 authentication/models.py:22 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39 +#: perms/models/asset_permission.py:74 terminal/models/sharing.py:28 +#: tickets/const.py:38 msgid "Active" msgstr "アクティブ" -#: acls/models/base.py:32 applications/models/application.py:233 -#: assets/models/asset.py:143 assets/models/asset.py:231 -#: assets/models/backup.py:54 assets/models/base.py:180 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 -#: assets/models/cmd_filter.py:96 assets/models/domain.py:24 -#: assets/models/domain.py:65 assets/models/group.py:23 -#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 -#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/endpoint.py:23 terminal/models/endpoint.py:96 -#: terminal/models/storage.py:29 terminal/models/terminal.py:114 -#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 -#: users/models/group.py:16 users/models/user.py:702 -#: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 +#: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 +#: assets/models/asset/common.py:101 assets/models/automations/base.py:33 +#: assets/models/backup.py:30 assets/models/base.py:70 +#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 +#: assets/models/domain.py:25 assets/models/domain.py:69 +#: assets/models/group.py:23 assets/models/label.py:22 +#: assets/models/platform.py:73 ops/models/playbook.py:11 +#: ops/models/playbook.py:25 orgs/models.py:73 +#: perms/models/asset_permission.py:84 rbac/models/role.py:37 +#: settings/models.py:38 terminal/models/endpoint.py:23 +#: terminal/models/endpoint.py:96 terminal/models/storage.py:29 +#: terminal/models/terminal.py:114 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:288 users/models/group.py:16 +#: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 +#: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "コメント" -#: acls/models/login_acl.py:18 tickets/const.py:47 +#: acls/models/login_acl.py:18 tickets/const.py:46 #: tickets/templates/tickets/approve_check_password.html:49 msgid "Reject" msgstr "拒否" -#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:75 +#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:67 msgid "Allow" msgstr "許可" @@ -86,15 +91,15 @@ msgid "Login confirm" msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 -#: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:62 audits/models.py:87 audits/serializers.py:100 -#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:220 -#: perms/models/base.py:84 rbac/builtin.py:120 rbac/models/rolebinding.py:41 +#: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 +#: audits/models.py:62 audits/models.py:87 authentication/models.py:55 +#: authentication/models.py:79 perms/models/asset_permission.py:58 +#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:30 #: terminal/models/sharing.py:33 terminal/notifications.py:91 #: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14 -#: users/models/user.py:894 users/models/user.py:925 +#: users/models/user.py:895 users/models/user.py:926 #: users/serializers/group.py:19 msgid "User" msgstr "ユーザー" @@ -104,14 +109,14 @@ msgid "Rule" msgstr "ルール" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 -#: assets/models/cmd_filter.py:89 audits/models.py:63 audits/serializers.py:51 +#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:62 +#: assets/models/cmd_filter.py:81 audits/models.py:63 audits/serializers.py:49 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "アクション" #: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32 -#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:94 +#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:86 msgid "Reviewers" msgstr "レビュー担当者" @@ -119,25 +124,24 @@ msgstr "レビュー担当者" msgid "Login acl" msgstr "ログインacl" -#: acls/models/login_asset_acl.py:21 -#: applications/serializers/application.py:122 -#: applications/serializers/application.py:167 -msgid "System User" -msgstr "システムユーザー" +#: acls/models/login_asset_acl.py:21 assets/models/account.py:48 +#: authentication/models.py:88 ops/models/base.py:18 +#: terminal/models/session.py:34 xpack/plugins/cloud/models.py:87 +#: xpack/plugins/cloud/serializers/task.py:65 +msgid "Account" +msgstr "アカウント" -#: acls/models/login_asset_acl.py:22 -#: applications/serializers/attrs/application_category/remote_app.py:36 -#: assets/models/asset.py:386 assets/models/authbook.py:19 -#: assets/models/backup.py:31 assets/models/cmd_filter.py:38 -#: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:268 audits/models.py:39 -#: authentication/models.py:66 authentication/models.py:90 -#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:36 +#: assets/models/asset/common.py:83 assets/models/asset/common.py:219 +#: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 +#: assets/serializers/account/account.py:57 assets/serializers/label.py:30 +#: audits/models.py:39 authentication/models.py:67 authentication/models.py:84 +#: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:32 #: terminal/notifications.py:90 -#: xpack/plugins/change_auth_plan/models/asset.py:199 -#: xpack/plugins/change_auth_plan/serializers/asset.py:181 -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/change_auth_plan/models/asset.py:200 +#: xpack/plugins/change_auth_plan/serializers/asset.py:172 +#: xpack/plugins/cloud/models.py:219 msgid "Asset" msgstr "資産" @@ -145,30 +149,30 @@ msgstr "資産" msgid "Login asset acl" msgstr "ログインasset acl" -#: acls/models/login_asset_acl.py:89 tickets/const.py:12 +#: acls/models/login_asset_acl.py:86 tickets/const.py:11 msgid "Login asset confirm" msgstr "ログイン資産の確認" -#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:12 +#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:13 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" -#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 -#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 -#: assets/models/gathered_user.py:15 audits/models.py:121 -#: authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:260 +#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 +#: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 +#: assets/models/base.py:66 assets/models/gathered_user.py:15 +#: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 +#: authentication/models.py:248 #: 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:32 users/models/user.py:663 +#: users/forms/profile.py:32 users/models/user.py:663 #: users/templates/users/_msg_user_created.html:12 -#: xpack/plugins/change_auth_plan/models/asset.py:34 -#: xpack/plugins/change_auth_plan/models/asset.py:195 +#: xpack/plugins/change_auth_plan/models/asset.py:35 +#: xpack/plugins/change_auth_plan/models/asset.py:196 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Username" msgstr "ユーザー名" -#: acls/serializers/login_asset_acl.py:24 +#: acls/serializers/login_asset_acl.py:25 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -178,10 +182,8 @@ msgstr "" "192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" "db8:1a:1110:::/64 (ドメイン名サポート)" -#: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 -#: applications/serializers/attrs/application_type/mysql_workbench.py:18 -#: assets/models/asset.py:210 assets/models/domain.py:61 -#: assets/serializers/account.py:13 +#: acls/serializers/login_asset_acl.py:32 acls/serializers/rules/rules.py:33 +#: assets/models/asset/common.py:92 assets/models/domain.py:65 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -189,13 +191,12 @@ msgstr "" msgid "IP" msgstr "IP" -#: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 -#: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 -#: settings/serializers/terminal.py:7 +#: acls/serializers/login_asset_acl.py:36 +#: assets/serializers/gathered_user.py:22 settings/serializers/terminal.py:7 msgid "Hostname" msgstr "ホスト名" -#: acls/serializers/login_asset_acl.py:42 +#: acls/serializers/login_asset_acl.py:43 msgid "" "Format for comma-delimited string, with * indicating a match all. Protocol " "options: {}" @@ -203,27 +204,17 @@ msgstr "" "コンマ区切り文字列の形式。* はすべて一致することを示します。プロトコルオプ" "ション: {}" -#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:63 assets/models/user.py:252 -#: terminal/serializers/session.py:31 terminal/serializers/storage.py:68 -msgid "Protocol" -msgstr "プロトコル" - -#: acls/serializers/login_asset_acl.py:65 -msgid "Unsupported protocols: {}" -msgstr "サポートされていないプロトコル: {}" - -#: acls/serializers/login_asset_acl.py:98 +#: acls/serializers/login_asset_acl.py:84 #: tickets/serializers/ticket/ticket.py:85 msgid "The organization `{}` does not exist" msgstr "組織 '{}'は存在しません" -#: acls/serializers/login_asset_acl.py:103 +#: acls/serializers/login_asset_acl.py:89 msgid "None of the reviewers belong to Organization `{}`" msgstr "いずれのレビューアも組織 '{}' に属していません" #: acls/serializers/rules/rules.py:20 -#: xpack/plugins/cloud/serializers/task.py:23 +#: xpack/plugins/cloud/serializers/task.py:22 msgid "IP address invalid: `{}`" msgstr "IPアドレスが無効: '{}'" @@ -241,267 +232,58 @@ msgstr "" msgid "Time Period" msgstr "期間" -#: applications/apps.py:9 applications/models/application.py:64 +#: applications/apps.py:9 msgid "Applications" msgstr "アプリケーション" -#: applications/const.py:8 -#: applications/serializers/attrs/application_category/db.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:26 -#: xpack/plugins/change_auth_plan/models/app.py:32 -msgid "Database" -msgstr "データベース" - -#: applications/const.py:9 -msgid "Remote app" -msgstr "リモートアプリ" - -#: applications/const.py:35 -msgid "Custom" -msgstr "カスタム" - -#: applications/const.py:91 rbac/tree.py:29 -msgid "Other" -msgstr "その他" - -#: applications/models/account.py:12 applications/models/application.py:237 -#: assets/models/backup.py:32 assets/models/cmd_filter.py:45 -#: authentication/models.py:67 authentication/models.py:95 -#: perms/models/application_permission.py:28 -msgid "Application" -msgstr "アプリケーション" - -#: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:42 assets/models/user.py:342 audits/models.py:40 -#: authentication/models.py:83 perms/models/application_permission.py:33 -#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 -#: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 -#: xpack/plugins/change_auth_plan/models/app.py:36 -#: xpack/plugins/change_auth_plan/models/app.py:147 -#: xpack/plugins/change_auth_plan/serializers/app.py:65 -msgid "System user" -msgstr "システムユーザー" - -#: applications/models/account.py:17 -#: applications/serializers/attrs/application_type/oracle.py:13 -#: assets/models/authbook.py:21 settings/serializers/auth/cas.py:18 -msgid "Version" -msgstr "バージョン" - -#: applications/models/account.py:23 -msgid "Application account" -msgstr "アプリケーションアカウント" - -#: applications/models/account.py:26 -msgid "Can view application account secret" -msgstr "アプリケーションアカウントの秘密を表示できます" - -#: applications/models/account.py:27 -msgid "Can change application account secret" -msgstr "アプリケーションアカウントの秘密を変更できます" - -#: applications/models/application.py:222 -#: applications/serializers/application.py:99 assets/models/label.py:21 -#: perms/models/application_permission.py:21 -#: perms/serializers/application/user_permission.py:33 -#: tickets/models/ticket/apply_application.py:15 -#: xpack/plugins/change_auth_plan/models/app.py:25 +#: applications/models.py:12 assets/models/label.py:20 +#: assets/models/platform.py:69 assets/serializers/asset/common.py:62 +#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:76 +#: assets/serializers/platform.py:105 +#: tickets/models/ticket/apply_application.py:14 +#: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" msgstr "カテゴリ" -#: applications/models/application.py:225 -#: applications/serializers/application.py:101 assets/models/backup.py:49 -#: assets/models/cmd_filter.py:82 assets/models/user.py:250 -#: authentication/models.py:70 perms/models/application_permission.py:24 -#: perms/serializers/application/user_permission.py:34 +#: applications/models.py:15 assets/models/_user.py:46 +#: assets/models/automations/base.py:31 assets/models/cmd_filter.py:74 +#: assets/models/platform.py:70 assets/serializers/asset/common.py:63 +#: assets/serializers/platform.py:75 authentication/models.py:71 #: terminal/models/storage.py:58 terminal/models/storage.py:143 #: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:18 +#: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 -#: xpack/plugins/change_auth_plan/models/app.py:28 -#: xpack/plugins/change_auth_plan/models/app.py:153 +#: xpack/plugins/change_auth_plan/models/app.py:27 +#: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" msgstr "タイプ" -#: applications/models/application.py:229 assets/models/asset.py:217 -#: assets/models/domain.py:29 assets/models/domain.py:64 -msgid "Domain" -msgstr "ドメイン" - -#: applications/models/application.py:231 xpack/plugins/cloud/models.py:33 +#: applications/models.py:17 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/serializers/account.py:61 msgid "Attrs" msgstr "ツールバーの" -#: applications/models/application.py:241 +#: applications/models.py:23 authentication/models.py:68 +msgid "Application" +msgstr "アプリケーション" + +#: applications/models.py:27 msgid "Can match application" msgstr "アプリケーションを一致させることができます" -#: applications/models/application.py:320 -msgid "Application user" -msgstr "アプリケーションユーザー" - -#: applications/serializers/application.py:70 -#: applications/serializers/application.py:100 assets/serializers/label.py:13 -#: perms/serializers/application/permission.py:18 -msgid "Category display" -msgstr "カテゴリ表示" - -#: applications/serializers/application.py:71 -#: applications/serializers/application.py:102 -#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 -#: audits/serializers.py:29 authentication/serializers/connection_token.py:22 -#: perms/serializers/application/permission.py:19 -#: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 -msgid "Type display" -msgstr "タイプ表示" - -#: applications/serializers/application.py:103 assets/models/asset.py:230 -#: assets/models/base.py:181 assets/models/cluster.py:26 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 -#: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 -#: assets/serializers/cmd_filter.py:48 common/db/models.py:114 -#: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 -#: orgs/models.py:72 orgs/models.py:223 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:926 -#: xpack/plugins/cloud/models.py:125 -msgid "Date created" -msgstr "作成された日付" - -#: applications/serializers/application.py:104 assets/models/base.py:182 -#: assets/models/gathered_user.py:20 assets/serializers/account.py:21 -#: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 -#: common/db/models.py:115 common/mixins/models.py:51 ops/models/adhoc.py:40 -#: orgs/models.py:224 -msgid "Date updated" -msgstr "更新日" - -#: applications/serializers/application.py:121 -#: applications/serializers/application.py:166 authentication/models.py:99 -msgid "Application display" -msgstr "アプリケーション表示" - -#: applications/serializers/application.py:123 -msgid "account" -msgstr "アカウント" - -#: applications/serializers/attrs/application_category/cloud.py:8 -#: assets/models/cluster.py:40 -msgid "Cluster" -msgstr "クラスター" - -#: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 -#: xpack/plugins/cloud/serializers/account_attrs.py:72 -msgid "Host" -msgstr "ホスト" - -#: applications/serializers/attrs/application_category/db.py:12 -#: applications/serializers/attrs/application_type/mongodb.py:10 -#: applications/serializers/attrs/application_type/mysql.py:10 -#: applications/serializers/attrs/application_type/mysql_workbench.py:22 -#: applications/serializers/attrs/application_type/oracle.py:16 -#: applications/serializers/attrs/application_type/pgsql.py:10 -#: applications/serializers/attrs/application_type/redis.py:10 -#: applications/serializers/attrs/application_type/sqlserver.py:10 -#: assets/models/asset.py:214 assets/models/domain.py:62 -#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:57 -#: xpack/plugins/cloud/serializers/account_attrs.py:73 -msgid "Port" -msgstr "ポート" - -#: applications/serializers/attrs/application_category/remote_app.py:34 -msgid "Asset Info" -msgstr "資産情報" - -#: applications/serializers/attrs/application_category/remote_app.py:39 -#: applications/serializers/attrs/application_type/chrome.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:14 -#: applications/serializers/attrs/application_type/vmware_client.py:18 -msgid "Application path" -msgstr "アプリケーションパス" - -#: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:167 -#: tickets/serializers/ticket/apply_application.py:38 -#: tickets/serializers/ticket/common.py:59 -#: xpack/plugins/change_auth_plan/serializers/asset.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:70 -#: xpack/plugins/change_auth_plan/serializers/asset.py:73 -#: xpack/plugins/change_auth_plan/serializers/asset.py:104 -#: xpack/plugins/cloud/serializers/account_attrs.py:56 -msgid "This field is required." -msgstr "このフィールドは必須です。" - -#: applications/serializers/attrs/application_type/chrome.py:18 -#: applications/serializers/attrs/application_type/vmware_client.py:22 -msgid "Target URL" -msgstr "ターゲットURL" - -#: applications/serializers/attrs/application_type/chrome.py:22 -msgid "Chrome username" -msgstr "Chromeユーザー名" - -#: applications/serializers/attrs/application_type/chrome.py:26 -#: applications/serializers/attrs/application_type/chrome.py:33 -msgid "Chrome password" -msgstr "Chromeパスワード" - -#: applications/serializers/attrs/application_type/custom.py:12 -msgid "Operating parameter" -msgstr "操作パラメータ" - -#: applications/serializers/attrs/application_type/custom.py:16 -msgid "Target url" -msgstr "ターゲットURL" - -#: applications/serializers/attrs/application_type/custom.py:20 -msgid "Custom Username" -msgstr "カスタムユーザー名" - -#: applications/serializers/attrs/application_type/custom.py:25 -#: applications/serializers/attrs/application_type/custom.py:32 -#: xpack/plugins/change_auth_plan/models/base.py:27 -msgid "Custom password" -msgstr "カスタムパスワード" - -#: applications/serializers/attrs/application_type/mysql_workbench.py:30 -msgid "Mysql workbench username" -msgstr "Mysql workbench のユーザー名" - -#: applications/serializers/attrs/application_type/mysql_workbench.py:35 -#: applications/serializers/attrs/application_type/mysql_workbench.py:42 -msgid "Mysql workbench password" -msgstr "Mysql workbench パスワード" - -#: applications/serializers/attrs/application_type/oracle.py:14 -msgid "Magnus currently supports only 11g and 12c connections" -msgstr "現在、Magnusは11gおよび12cバージョンへの接続のみをサポートしています" - -#: applications/serializers/attrs/application_type/vmware_client.py:26 -msgid "Vmware username" -msgstr "Vmware ユーザー名" - -#: applications/serializers/attrs/application_type/vmware_client.py:31 -#: applications/serializers/attrs/application_type/vmware_client.py:38 -msgid "Vmware password" -msgstr "Vmware パスワード" - #: assets/api/domain.py:52 msgid "Number required" msgstr "必要な数" -#: assets/api/node.py:61 +#: assets/api/node.py:62 msgid "You can't update the root node name" msgstr "ルートノード名を更新できません" -#: assets/api/node.py:68 +#: assets/api/node.py:69 msgid "You can't delete the root node ({})" msgstr "ルートノード ({}) を削除できません。" -#: assets/api/node.py:71 +#: assets/api/node.py:72 msgid "Deletion failed and the node contains assets" msgstr "削除に失敗し、ノードにアセットが含まれています。" @@ -509,285 +291,80 @@ msgstr "削除に失敗し、ノードにアセットが含まれています。 msgid "App assets" msgstr "アプリ資産" -#: assets/models/asset.py:139 -msgid "Base" -msgstr "ベース" +#: assets/automations/base/manager.py:74 +#, fuzzy +#| msgid "Disabled" +msgid "{} disabled" +msgstr "無効" -#: assets/models/asset.py:140 -msgid "Charset" -msgstr "シャーセット" +#: assets/const/category.py:11 settings/serializers/auth/radius.py:14 +#: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 +#: xpack/plugins/cloud/serializers/account_attrs.py:72 +msgid "Host" +msgstr "ホスト" -#: assets/models/asset.py:141 assets/serializers/asset.py:176 -#: tickets/models/ticket/general.py:298 -msgid "Meta" -msgstr "メタ" +#: assets/const/category.py:12 +msgid "Device" +msgstr "" -#: assets/models/asset.py:142 -msgid "Internal" -msgstr "内部" +#: assets/const/category.py:13 assets/models/asset/database.py:8 +#: assets/models/asset/database.py:18 +#: xpack/plugins/change_auth_plan/models/app.py:31 +msgid "Database" +msgstr "データベース" -#: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:15 assets/serializers/asset.py:63 -#: perms/serializers/asset/user_permission.py:43 -#: xpack/plugins/cloud/serializers/account_attrs.py:162 -msgid "Platform" -msgstr "プラットフォーム" +#: assets/const/category.py:14 +#, fuzzy +#| msgid "Cloud center" +msgid "Cloud service" +msgstr "クラウドセンター" -#: assets/models/asset.py:168 -msgid "Vendor" -msgstr "ベンダー" +#: assets/const/category.py:15 +msgid "Web" +msgstr "" -#: assets/models/asset.py:169 -msgid "Model" -msgstr "モデル" +#: assets/const/device.py:7 tickets/const.py:8 +msgid "General" +msgstr "一般" -#: assets/models/asset.py:170 tickets/models/ticket/general.py:296 -msgid "Serial number" -msgstr "シリアル番号" +#: assets/const/device.py:8 +#, fuzzy +#| msgid "Switch from" +msgid "Switch" +msgstr "から切り替え" -#: assets/models/asset.py:172 -msgid "CPU model" -msgstr "CPU モデル" +#: assets/const/device.py:9 +msgid "Router" +msgstr "" -#: assets/models/asset.py:173 -msgid "CPU count" -msgstr "CPU カウント" +#: assets/const/device.py:10 +msgid "Firewall" +msgstr "" -#: assets/models/asset.py:174 -msgid "CPU cores" -msgstr "CPU カラー" +#: assets/const/web.py:7 +#, fuzzy +#| msgid "Website icon" +msgid "Website" +msgstr "ウェブサイトのアイコン" -#: assets/models/asset.py:175 -msgid "CPU vcpus" -msgstr "CPU 合計" +#: assets/models/_user.py:24 +msgid "Automatic managed" +msgstr "自動管理" -#: assets/models/asset.py:176 -msgid "Memory" -msgstr "メモリ" +#: assets/models/_user.py:25 +msgid "Manually input" +msgstr "手動入力" -#: assets/models/asset.py:177 -msgid "Disk total" -msgstr "ディスクの合計" +#: assets/models/_user.py:29 +msgid "Common user" +msgstr "共通ユーザー" -#: assets/models/asset.py:178 -msgid "Disk info" -msgstr "ディスク情報" - -#: assets/models/asset.py:180 -msgid "OS" -msgstr "OS" - -#: assets/models/asset.py:181 -msgid "OS version" -msgstr "システムバージョン" - -#: assets/models/asset.py:182 -msgid "OS arch" -msgstr "システムアーキテクチャ" - -#: assets/models/asset.py:183 -msgid "Hostname raw" -msgstr "ホスト名生" - -#: assets/models/asset.py:215 assets/serializers/account.py:16 -#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:43 -msgid "Protocols" -msgstr "プロトコル" - -#: assets/models/asset.py:218 assets/models/user.py:242 -#: perms/models/asset_permission.py:24 -#: xpack/plugins/change_auth_plan/models/asset.py:43 -#: xpack/plugins/gathered_user/models.py:24 -msgid "Nodes" -msgstr "ノード" - -#: assets/models/asset.py:219 assets/models/cmd_filter.py:47 -#: assets/models/domain.py:66 assets/models/label.py:22 -#: users/serializers/user.py:147 -msgid "Is active" -msgstr "アクティブです。" - -#: assets/models/asset.py:222 assets/models/cluster.py:19 -#: assets/models/user.py:239 assets/models/user.py:394 +#: assets/models/_user.py:30 msgid "Admin user" msgstr "管理ユーザー" -#: assets/models/asset.py:225 -msgid "Public IP" -msgstr "パブリックIP" - -#: assets/models/asset.py:226 -msgid "Asset number" -msgstr "資産番号" - -#: assets/models/asset.py:228 -msgid "Labels" -msgstr "ラベル" - -#: assets/models/asset.py:229 assets/models/base.py:183 -#: 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:112 common/mixins/models.py:49 orgs/models.py:71 -#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:710 -#: users/serializers/group.py:33 -#: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 -msgid "Created by" -msgstr "によって作成された" - -#: assets/models/asset.py:389 -msgid "Can refresh asset hardware info" -msgstr "資産ハードウェア情報を更新できます" - -#: assets/models/asset.py:390 -msgid "Can test asset connectivity" -msgstr "資産接続をテストできます" - -#: assets/models/asset.py:391 -msgid "Can push system user to asset" -msgstr "システムユーザーを資産にプッシュできます" - -#: assets/models/asset.py:392 -msgid "Can match asset" -msgstr "アセットを一致させることができます" - -#: assets/models/asset.py:393 -msgid "Add asset to node" -msgstr "ノードにアセットを追加する" - -#: assets/models/asset.py:394 -msgid "Move asset to node" -msgstr "アセットをノードに移動する" - -#: assets/models/authbook.py:27 -msgid "AuthBook" -msgstr "資産アカウント" - -#: assets/models/authbook.py:30 -msgid "Can test asset account connectivity" -msgstr "アセットアカウントの接続性をテストできます" - -#: assets/models/authbook.py:31 -msgid "Can view asset account secret" -msgstr "資産アカウントの秘密を表示できます" - -#: assets/models/authbook.py:32 -msgid "Can change asset account secret" -msgstr "資産口座の秘密を変更できます" - -#: assets/models/authbook.py:33 -msgid "Can view asset history account" -msgstr "資産履歴アカウントを表示できます" - -#: assets/models/authbook.py:34 -msgid "Can view asset history account secret" -msgstr "資産履歴アカウントパスワードを表示できます" - -#: assets/models/backup.py:30 perms/models/base.py:54 -#: settings/serializers/terminal.py:12 -msgid "All" -msgstr "すべて" - -#: assets/models/backup.py:52 assets/serializers/backup.py:32 -#: xpack/plugins/change_auth_plan/models/app.py:41 -#: xpack/plugins/change_auth_plan/models/asset.py:62 -#: xpack/plugins/change_auth_plan/serializers/base.py:45 -msgid "Recipient" -msgstr "受信者" - -#: assets/models/backup.py:62 assets/models/backup.py:124 -msgid "Account backup plan" -msgstr "アカウントバックアップ計画" - -#: assets/models/backup.py:100 -#: xpack/plugins/change_auth_plan/models/base.py:107 -msgid "Manual trigger" -msgstr "手動トリガー" - -#: assets/models/backup.py:101 -#: xpack/plugins/change_auth_plan/models/base.py:108 -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:58 -#: tickets/models/ticket/apply_application.py:29 -#: tickets/models/ticket/apply_asset.py:23 -#: xpack/plugins/change_auth_plan/models/base.py:112 -#: xpack/plugins/change_auth_plan/models/base.py:203 -#: xpack/plugins/gathered_user/models.py:76 -msgid "Date start" -msgstr "開始日" - -#: assets/models/backup.py:108 -#: authentication/templates/authentication/_msg_oauth_bind.html:11 -#: notifications/notifications.py:187 ops/models/adhoc.py:258 -#: xpack/plugins/change_auth_plan/models/base.py:115 -#: xpack/plugins/change_auth_plan/models/base.py:204 -#: xpack/plugins/gathered_user/models.py:79 -msgid "Time" -msgstr "時間" - -#: assets/models/backup.py:112 -msgid "Account backup snapshot" -msgstr "アカウントのバックアップスナップショット" - -#: assets/models/backup.py:116 assets/serializers/backup.py:40 -#: xpack/plugins/change_auth_plan/models/base.py:125 -#: xpack/plugins/change_auth_plan/serializers/base.py:78 -msgid "Trigger mode" -msgstr "トリガーモード" - -#: assets/models/backup.py:119 audits/models.py:127 -#: terminal/models/sharing.py:108 -#: xpack/plugins/change_auth_plan/models/base.py:201 -#: xpack/plugins/change_auth_plan/serializers/app.py:66 -#: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:179 -msgid "Reason" -msgstr "理由" - -#: assets/models/backup.py:121 audits/serializers.py:82 -#: audits/serializers.py:97 ops/models/adhoc.py:260 -#: terminal/serializers/session.py:36 -#: xpack/plugins/change_auth_plan/models/base.py:202 -#: xpack/plugins/change_auth_plan/serializers/app.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:182 -msgid "Is success" -msgstr "成功は" - -#: assets/models/backup.py:128 -msgid "Account backup execution" -msgstr "アカウントバックアップの実行" - -#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 -#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 -#: common/utils/ip/utils.py:84 -msgid "Unknown" -msgstr "不明" - -#: assets/models/base.py:31 -msgid "Ok" -msgstr "OK" - -#: assets/models/base.py:32 audits/models.py:118 -#: xpack/plugins/change_auth_plan/serializers/app.py:88 -#: xpack/plugins/change_auth_plan/serializers/asset.py:199 -#: xpack/plugins/cloud/const.py:33 -msgid "Failed" -msgstr "失敗しました" - -#: assets/models/base.py:38 assets/serializers/domain.py:47 -msgid "Connectivity" -msgstr "接続性" - -#: assets/models/base.py:40 authentication/models.py:263 -msgid "Date verified" -msgstr "確認済みの日付" - -#: assets/models/base.py:177 assets/serializers/base.py:15 -#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 +#: assets/models/_user.py:35 assets/models/base.py:59 +#: assets/models/domain.py:71 assets/serializers/base.py:15 #: audits/signal_handlers.py:50 authentication/confirm/password.py:9 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 @@ -796,145 +373,576 @@ msgstr "確認済みの日付" #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 -#: xpack/plugins/change_auth_plan/models/base.py:121 -#: xpack/plugins/change_auth_plan/models/base.py:196 +#: xpack/plugins/change_auth_plan/models/base.py:117 +#: xpack/plugins/change_auth_plan/models/base.py:192 #: xpack/plugins/change_auth_plan/serializers/base.py:21 #: xpack/plugins/change_auth_plan/serializers/base.py:73 #: xpack/plugins/cloud/serializers/account_attrs.py:28 msgid "Password" msgstr "パスワード" -#: assets/models/base.py:178 assets/serializers/base.py:41 -#: xpack/plugins/change_auth_plan/models/asset.py:53 -#: xpack/plugins/change_auth_plan/models/asset.py:130 -#: xpack/plugins/change_auth_plan/models/asset.py:206 +#: assets/models/_user.py:36 assets/models/domain.py:72 +#: assets/serializers/base.py:19 +#: xpack/plugins/change_auth_plan/models/asset.py:54 +#: xpack/plugins/change_auth_plan/models/asset.py:131 +#: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" msgstr "SSH秘密鍵" -#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56 -#: xpack/plugins/change_auth_plan/models/asset.py:126 -#: xpack/plugins/change_auth_plan/models/asset.py:202 +#: assets/models/_user.py:37 assets/models/domain.py:73 +#: xpack/plugins/change_auth_plan/models/asset.py:57 +#: xpack/plugins/change_auth_plan/models/asset.py:127 +#: xpack/plugins/change_auth_plan/models/asset.py:203 msgid "SSH public key" msgstr "SSHパブリックキー" -#: assets/models/cluster.py:20 -msgid "Bandwidth" -msgstr "帯域幅" +#: assets/models/_user.py:38 assets/models/base.py:62 +#: authentication/models.py:53 +msgid "Token" +msgstr "トークン" -#: assets/models/cluster.py:21 -msgid "Contact" -msgstr "連絡先" +#: assets/models/_user.py:41 assets/models/automations/base.py:87 +#: assets/models/base.py:71 assets/models/domain.py:26 +#: assets/models/gathered_user.py:19 assets/models/group.py:22 +#: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 +#: orgs/models.py:72 perms/models/asset_permission.py:82 +#: users/models/group.py:18 users/models/user.py:927 +msgid "Date created" +msgstr "作成された日付" -#: assets/models/cluster.py:22 users/models/user.py:685 -msgid "Phone" -msgstr "電話" +#: assets/models/_user.py:42 assets/models/base.py:72 +#: assets/models/gathered_user.py:20 common/db/models.py:77 +#: common/mixins/models.py:51 +msgid "Date updated" +msgstr "更新日" -#: assets/models/cluster.py:23 -msgid "Address" -msgstr "アドレス" +#: assets/models/_user.py:43 assets/models/base.py:73 +#: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 +#: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 +#: orgs/models.py:71 perms/models/asset_permission.py:81 +#: users/models/user.py:710 users/serializers/group.py:33 +#: xpack/plugins/change_auth_plan/models/base.py:48 +msgid "Created by" +msgstr "によって作成された" -#: assets/models/cluster.py:24 -msgid "Intranet" -msgstr "イントラネット" +#: assets/models/_user.py:45 +msgid "Username same with user" +msgstr "ユーザーと同じユーザー名" -#: assets/models/cluster.py:25 -msgid "Extranet" -msgstr "エクストラネット" +#: assets/models/_user.py:48 assets/models/domain.py:67 +#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 +#: terminal/serializers/storage.py:68 +msgid "Protocol" +msgstr "プロトコル" -#: assets/models/cluster.py:27 -msgid "Operator" -msgstr "オペレーター" +#: assets/models/_user.py:49 +msgid "Auto push" +msgstr "オートプッシュ" -#: assets/models/cluster.py:36 assets/models/group.py:34 -#: xpack/plugins/cloud/providers/nutanix.py:30 -msgid "Default" -msgstr "デフォルト" +#: assets/models/_user.py:50 +msgid "Sudo" +msgstr "すど" -#: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:911 -msgid "System" -msgstr "システム" +#: assets/models/_user.py:51 +msgid "Shell" +msgstr "シェル" -#: assets/models/cluster.py:36 -msgid "Default Cluster" -msgstr "デフォルトクラスター" +#: assets/models/_user.py:52 +msgid "Login mode" +msgstr "ログインモード" -#: assets/models/cmd_filter.py:34 perms/models/base.py:86 +#: assets/models/_user.py:53 +msgid "SFTP Root" +msgstr "SFTPルート" + +#: assets/models/_user.py:54 +msgid "Home" +msgstr "ホーム" + +#: assets/models/_user.py:55 +msgid "System groups" +msgstr "システムグループ" + +#: assets/models/_user.py:58 +msgid "User switch" +msgstr "ユーザースイッチ" + +#: assets/models/_user.py:59 +msgid "Switch from" +msgstr "から切り替え" + +#: assets/models/_user.py:65 audits/models.py:40 +#: terminal/backends/command/models.py:22 +#: terminal/backends/command/serializers.py:36 +#: xpack/plugins/change_auth_plan/models/app.py:35 +#: xpack/plugins/change_auth_plan/models/app.py:146 +msgid "System user" +msgstr "システムユーザー" + +#: assets/models/_user.py:67 +msgid "Can match system user" +msgstr "システムユーザーに一致できます" + +#: assets/models/account.py:40 +#, fuzzy +#| msgid "Switch from" +msgid "Su from" +msgstr "から切り替え" + +#: assets/models/account.py:42 settings/serializers/auth/cas.py:18 +msgid "Version" +msgstr "バージョン" + +#: assets/models/account.py:54 +msgid "Can view asset account secret" +msgstr "資産アカウントの秘密を表示できます" + +#: assets/models/account.py:55 +msgid "Can change asset account secret" +msgstr "資産口座の秘密を変更できます" + +#: assets/models/account.py:56 +msgid "Can view asset history account" +msgstr "資産履歴アカウントを表示できます" + +#: assets/models/account.py:57 +msgid "Can view asset history account secret" +msgstr "資産履歴アカウントパスワードを表示できます" + +#: assets/models/account.py:80 assets/serializers/account/account.py:13 +#, fuzzy +#| msgid "Account name" +msgid "Account template" +msgstr "アカウント名" + +#: assets/models/asset/common.py:82 assets/models/domain.py:66 +#: assets/models/platform.py:23 settings/serializers/auth/radius.py:15 +#: settings/serializers/auth/sms.py:57 +#: xpack/plugins/cloud/serializers/account_attrs.py:73 +msgid "Port" +msgstr "ポート" + +#: assets/models/asset/common.py:94 assets/models/platform.py:104 +#: assets/serializers/asset/common.py:65 +#: perms/serializers/user_permission.py:21 +#: xpack/plugins/cloud/serializers/account_attrs.py:172 +msgid "Platform" +msgstr "プラットフォーム" + +#: assets/models/asset/common.py:96 assets/models/domain.py:29 +#: assets/models/domain.py:68 assets/serializers/asset/common.py:64 +msgid "Domain" +msgstr "ドメイン" + +#: assets/models/asset/common.py:98 assets/models/automations/base.py:26 +#: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 +#: xpack/plugins/change_auth_plan/models/asset.py:44 +#: xpack/plugins/gathered_user/models.py:24 +msgid "Nodes" +msgstr "ノード" + +#: assets/models/asset/common.py:99 assets/models/automations/base.py:32 +#: assets/models/cmd_filter.py:39 assets/models/domain.py:70 +#: assets/models/label.py:21 users/serializers/user.py:147 +msgid "Is active" +msgstr "アクティブです。" + +#: assets/models/asset/common.py:100 assets/serializers/asset/common.py:67 +msgid "Labels" +msgstr "ラベル" + +#: assets/models/asset/common.py:222 +msgid "Can refresh asset hardware info" +msgstr "資産ハードウェア情報を更新できます" + +#: assets/models/asset/common.py:223 +msgid "Can test asset connectivity" +msgstr "資産接続をテストできます" + +#: assets/models/asset/common.py:224 +msgid "Can push system user to asset" +msgstr "システムユーザーを資産にプッシュできます" + +#: assets/models/asset/common.py:225 +msgid "Can match asset" +msgstr "アセットを一致させることができます" + +#: assets/models/asset/common.py:226 +msgid "Add asset to node" +msgstr "ノードにアセットを追加する" + +#: assets/models/asset/common.py:227 +msgid "Move asset to node" +msgstr "アセットをノードに移動する" + +#: assets/models/automations/account_discovery.py:10 +#, fuzzy +#| msgid "Approve strategy" +msgid "Discovery strategy" +msgstr "戦略を承認する" + +#: assets/models/automations/account_reconcile.py:9 +#, fuzzy +#| msgid "Hostname strategy" +msgid "Reconcile strategy" +msgstr "ホスト名戦略" + +#: assets/models/automations/account_verify.py:9 +#, fuzzy +#| msgid "SSH Key strategy" +msgid "Verify strategy" +msgstr "SSHキー戦略" + +#: assets/models/automations/base.py:15 +msgid "Ping" +msgstr "" + +#: assets/models/automations/base.py:16 +#, fuzzy +#| msgid "Gather account" +msgid "Gather facts" +msgstr "アカウントを集める" + +#: assets/models/automations/base.py:17 +#, fuzzy +#| msgid "Gather account" +msgid "Create account" +msgstr "アカウントを集める" + +#: assets/models/automations/base.py:18 +#: assets/models/automations/change_secret.py:56 +#, fuzzy +#| msgid "Change auth" +msgid "Change secret" +msgstr "秘密を改める" + +#: assets/models/automations/base.py:19 +#, fuzzy +#| msgid "Verify auth" +msgid "Verify account" +msgstr "パスワード/キーの確認" + +#: assets/models/automations/base.py:20 +#, fuzzy +#| msgid "Gather account" +msgid "Gather accounts" +msgstr "アカウントを集める" + +#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:38 +#: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 +#: rbac/tree.py:37 +msgid "Accounts" +msgstr "アカウント" + +#: assets/models/automations/base.py:29 assets/serializers/domain.py:29 +#: ops/models/base.py:17 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 +#: xpack/plugins/change_auth_plan/models/asset.py:40 +msgid "Assets" +msgstr "資産" + +#: assets/models/automations/base.py:77 +#, fuzzy +#| msgid "Automatic managed" +msgid "Automation plan" +msgstr "自動管理" + +#: assets/models/automations/base.py:84 +#, fuzzy +#| msgid "Hostname strategy" +msgid "Automation strategy" +msgstr "ホスト名戦略" + +#: assets/models/automations/base.py:88 assets/models/backup.py:77 +#: audits/models.py:44 ops/models/base.py:54 +#: perms/models/asset_permission.py:76 terminal/models/session.py:43 +#: tickets/models/ticket/apply_application.py:28 +#: tickets/models/ticket/apply_asset.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:199 +#: xpack/plugins/gathered_user/models.py:71 +msgid "Date start" +msgstr "開始日" + +#: assets/models/automations/base.py:89 +#: assets/models/automations/change_secret.py:51 ops/models/base.py:55 +msgid "Date finished" +msgstr "終了日" + +#: assets/models/automations/base.py:91 +#, fuzzy +#| msgid "Relation snapshot" +msgid "Automation snapshot" +msgstr "製造オーダスナップショット" + +#: assets/models/automations/base.py:95 assets/models/backup.py:88 +#: assets/serializers/account/backup.py:36 +#: xpack/plugins/change_auth_plan/models/base.py:121 +#: xpack/plugins/change_auth_plan/serializers/base.py:78 +msgid "Trigger mode" +msgstr "トリガーモード" + +#: assets/models/automations/base.py:99 +#, fuzzy +#| msgid "Command execution" +msgid "Automation strategy execution" +msgstr "コマンド実行" + +#: assets/models/automations/change_secret.py:13 +msgid "Specific" +msgstr "" + +#: assets/models/automations/change_secret.py:14 ops/const.py:20 +#: xpack/plugins/change_auth_plan/models/base.py:28 +msgid "All assets use the same random password" +msgstr "すべての資産は同じランダムパスワードを使用します" + +#: assets/models/automations/change_secret.py:15 ops/const.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:29 +msgid "All assets use different random password" +msgstr "すべての資産は異なるランダムパスワードを使用します" + +#: assets/models/automations/change_secret.py:19 ops/const.py:13 +#: xpack/plugins/change_auth_plan/models/asset.py:30 +msgid "Append SSH KEY" +msgstr "追加" + +#: assets/models/automations/change_secret.py:20 ops/const.py:14 +#: xpack/plugins/change_auth_plan/models/asset.py:31 +msgid "Empty and append SSH KEY" +msgstr "すべてクリアして追加" + +#: assets/models/automations/change_secret.py:21 ops/const.py:15 +#: xpack/plugins/change_auth_plan/models/asset.py:32 +msgid "Replace (The key generated by JumpServer) " +msgstr "置換(JumpServerによって生成された鍵)" + +#: assets/models/automations/change_secret.py:25 +#, fuzzy +#| msgid "Secret key" +msgid "Secret types" +msgstr "秘密キー" + +#: assets/models/automations/change_secret.py:27 users/serializers/user.py:81 +#: xpack/plugins/change_auth_plan/models/base.py:35 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 +msgid "Password strategy" +msgstr "パスワード戦略" + +#: assets/models/automations/change_secret.py:28 +#: assets/models/automations/change_secret.py:49 assets/models/base.py:68 +#: assets/serializers/account/base.py:17 authentication/models.py:73 +#: authentication/models.py:249 +#: authentication/templates/authentication/_access_key_modal.html:31 +#: settings/serializers/auth/radius.py:17 +msgid "Secret" +msgstr "ひみつ" + +#: assets/models/automations/change_secret.py:29 +#: xpack/plugins/change_auth_plan/models/base.py:39 +msgid "Password rules" +msgstr "パスワードルール" + +#: assets/models/automations/change_secret.py:32 assets/models/base.py:60 +#, fuzzy +#| msgid "SSH Key" +msgid "SSH key" +msgstr "SSHキー" + +#: assets/models/automations/change_secret.py:34 +#, fuzzy +#| msgid "SSH Key strategy" +msgid "SSH key strategy" +msgstr "SSHキー戦略" + +#: assets/models/automations/change_secret.py:35 assets/models/backup.py:28 +#: assets/serializers/account/backup.py:28 +#: xpack/plugins/change_auth_plan/models/app.py:40 +#: xpack/plugins/change_auth_plan/models/asset.py:63 +#: xpack/plugins/change_auth_plan/serializers/base.py:45 +msgid "Recipient" +msgstr "受信者" + +#: assets/models/automations/change_secret.py:42 +#, fuzzy +#| msgid "Can change auth setting" +msgid "Change auth strategy" +msgstr "資格認定の設定" + +#: assets/models/automations/change_secret.py:48 +#, fuzzy +#| msgid "Secret" +msgid "Old secret" +msgstr "ひみつ" + +#: assets/models/automations/change_secret.py:50 +#, fuzzy +#| msgid "Date start" +msgid "Date started" +msgstr "開始日" + +#: assets/models/automations/change_secret.py:53 +#, fuzzy +#| msgid "WeCom Error" +msgid "Error" +msgstr "企業微信エラー" + +#: assets/models/automations/gather_facts.py:11 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather asset facts" +msgstr "資産ユーザーの収集" + +#: assets/models/backup.py:38 assets/models/backup.py:96 +msgid "Account backup plan" +msgstr "アカウントバックアップ計画" + +#: assets/models/backup.py:80 +#: authentication/templates/authentication/_msg_oauth_bind.html:11 +#: notifications/notifications.py:187 +#: xpack/plugins/change_auth_plan/models/base.py:111 +#: xpack/plugins/change_auth_plan/models/base.py:200 +#: xpack/plugins/gathered_user/models.py:74 +msgid "Time" +msgstr "時間" + +#: assets/models/backup.py:84 +msgid "Account backup snapshot" +msgstr "アカウントのバックアップスナップショット" + +#: assets/models/backup.py:91 audits/models.py:127 +#: terminal/models/sharing.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:197 +#: xpack/plugins/change_auth_plan/serializers/asset.py:171 +#: xpack/plugins/cloud/models.py:175 +msgid "Reason" +msgstr "理由" + +#: assets/models/backup.py:93 terminal/serializers/session.py:36 +#: xpack/plugins/change_auth_plan/models/base.py:198 +#: xpack/plugins/change_auth_plan/serializers/asset.py:173 +msgid "Is success" +msgstr "成功は" + +#: assets/models/backup.py:100 +msgid "Account backup execution" +msgstr "アカウントバックアップの実行" + +#: assets/models/base.py:28 assets/tasks/const.py:51 audits/const.py:5 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 +msgid "Unknown" +msgstr "不明" + +#: assets/models/base.py:29 +msgid "Ok" +msgstr "OK" + +#: assets/models/base.py:30 audits/models.py:118 +#: xpack/plugins/change_auth_plan/serializers/asset.py:190 +#: xpack/plugins/cloud/const.py:33 +msgid "Failed" +msgstr "失敗しました" + +#: assets/models/base.py:36 assets/serializers/domain.py:42 +msgid "Connectivity" +msgstr "接続性" + +#: assets/models/base.py:38 authentication/models.py:251 +msgid "Date verified" +msgstr "確認済みの日付" + +#: assets/models/base.py:61 authentication/models.py:38 +msgid "Access key" +msgstr "アクセスキー" + +#: assets/models/base.py:67 +#, fuzzy +#| msgid "Secret key" +msgid "Secret type" +msgstr "秘密キー" + +#: assets/models/base.py:69 +msgid "Privileged" +msgstr "" + +#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:61 #: users/models/group.py:31 users/models/user.py:671 msgid "User group" msgstr "ユーザーグループ" -#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:59 +#: assets/models/cmd_filter.py:52 msgid "Command filter" msgstr "コマンドフィルター" -#: assets/models/cmd_filter.py:67 +#: assets/models/cmd_filter.py:59 msgid "Regex" msgstr "正規情報" -#: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:55 +#: assets/models/cmd_filter.py:60 terminal/backends/command/serializers.py:15 +#: terminal/models/session.py:41 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" msgstr "コマンド" -#: assets/models/cmd_filter.py:74 +#: assets/models/cmd_filter.py:66 msgid "Deny" msgstr "拒否" -#: assets/models/cmd_filter.py:76 +#: assets/models/cmd_filter.py:68 msgid "Reconfirm" msgstr "再確認" -#: assets/models/cmd_filter.py:80 +#: assets/models/cmd_filter.py:72 msgid "Filter" msgstr "フィルター" -#: assets/models/cmd_filter.py:87 settings/serializers/basic.py:10 +#: assets/models/cmd_filter.py:79 settings/serializers/basic.py:10 #: xpack/plugins/license/models.py:29 msgid "Content" msgstr "コンテンツ" -#: assets/models/cmd_filter.py:87 +#: assets/models/cmd_filter.py:79 msgid "One line one command" msgstr "1行1コマンド" -#: assets/models/cmd_filter.py:88 +#: assets/models/cmd_filter.py:80 msgid "Ignore case" msgstr "家を無視する" -#: assets/models/cmd_filter.py:103 +#: assets/models/cmd_filter.py:95 msgid "Command filter rule" msgstr "コマンドフィルタルール" -#: assets/models/cmd_filter.py:147 +#: assets/models/cmd_filter.py:138 msgid "The generated regular expression is incorrect: {}" msgstr "生成された正規表現が正しくありません: {}" -#: assets/models/cmd_filter.py:173 tickets/const.py:13 +#: assets/models/cmd_filter.py:164 tickets/const.py:12 msgid "Command confirm" msgstr "コマンドの確認" -#: assets/models/domain.py:73 +#: assets/models/domain.py:84 msgid "Gateway" msgstr "ゲートウェイ" -#: assets/models/domain.py:75 +#: assets/models/domain.py:86 msgid "Test gateway" msgstr "テストゲートウェイ" -#: assets/models/domain.py:131 -#, python-brace-format -msgid "Unable to connect to port {port} on {ip}" +#: assets/models/domain.py:142 +#, fuzzy, python-brace-format +#| msgid "Unable to connect to port {port} on {ip}" +msgid "Unable to connect to port {port} on {address}" msgstr "{ip} でポート {port} に接続できません" -#: assets/models/domain.py:134 authentication/middleware.py:75 +#: assets/models/domain.py:145 authentication/middleware.py:75 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "認証に失敗しました" -#: assets/models/domain.py:136 assets/models/domain.py:158 +#: assets/models/domain.py:147 assets/models/domain.py:169 msgid "Connect failed" msgstr "接続に失敗しました" @@ -958,15 +966,28 @@ msgstr "収集ユーザー" msgid "Asset group" msgstr "資産グループ" +#: assets/models/group.py:34 assets/models/platform.py:20 +#: xpack/plugins/cloud/providers/nutanix.py:30 +msgid "Default" +msgstr "デフォルト" + #: assets/models/group.py:34 msgid "Default asset group" msgstr "デフォルトアセットグループ" -#: assets/models/label.py:19 assets/models/node.py:553 settings/models.py:34 +#: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:912 +msgid "System" +msgstr "システム" + +#: assets/models/label.py:18 assets/models/node.py:553 +#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 +#: common/drf/serializers/common.py:82 settings/models.py:34 msgid "Value" msgstr "値" -#: assets/models/label.py:40 settings/serializers/sms.py:7 +#: assets/models/label.py:36 assets/serializers/cagegory.py:6 +#: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 +#: settings/serializers/sms.py:7 msgid "Label" msgstr "ラベル" @@ -978,7 +999,7 @@ msgstr "新しいノード" msgid "empty" msgstr "空" -#: assets/models/node.py:552 perms/models/asset_permission.py:101 +#: assets/models/node.py:552 perms/models/asset_permission.py:190 msgid "Key" msgstr "キー" @@ -986,12 +1007,12 @@ msgstr "キー" msgid "Full value" msgstr "フルバリュー" -#: assets/models/node.py:557 perms/models/asset_permission.py:102 +#: assets/models/node.py:557 perms/models/asset_permission.py:191 msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:566 assets/serializers/system_user.py:267 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:70 +#: assets/models/node.py:566 xpack/plugins/cloud/models.py:98 +#: xpack/plugins/cloud/serializers/task.py:68 msgid "Node" msgstr "ノード" @@ -999,82 +1020,123 @@ msgstr "ノード" msgid "Can match node" msgstr "ノードを一致させることができます" -#: assets/models/user.py:233 -msgid "Automatic managed" +#: assets/models/platform.py:21 +#, fuzzy +#| msgid "MFA required" +msgid "Required" +msgstr "MFAが必要" + +#: assets/models/platform.py:24 users/templates/users/reset_password.html:29 +msgid "Setting" +msgstr "設定" + +#: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37 +msgid "Enabled" +msgstr "有効化" + +#: assets/models/platform.py:44 +msgid "Ansible config" +msgstr "" + +#: assets/models/platform.py:45 +#, fuzzy +#| msgid "MFA enabled" +msgid "Ping enabled" +msgstr "MFA有効化" + +#: assets/models/platform.py:46 +msgid "Ping method" +msgstr "" + +#: assets/models/platform.py:47 assets/models/platform.py:55 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather facts enabled" +msgstr "資産ユーザーの収集" + +#: assets/models/platform.py:48 assets/models/platform.py:56 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather facts method" +msgstr "資産ユーザーの収集" + +#: assets/models/platform.py:49 +#, fuzzy +#| msgid "Create account successfully" +msgid "Create account enabled" +msgstr "アカウントを正常に作成" + +#: assets/models/platform.py:50 +#, fuzzy +#| msgid "Create account successfully" +msgid "Create account method" +msgstr "アカウントを正常に作成" + +#: assets/models/platform.py:51 +#, fuzzy +#| msgid "Change Password" +msgid "Change password enabled" +msgstr "パスワードの変更" + +#: assets/models/platform.py:52 +#, fuzzy +#| msgid "Change Password" +msgid "Change password method" +msgstr "パスワードの変更" + +#: assets/models/platform.py:53 +#, fuzzy +#| msgid "Service account key" +msgid "Verify account enabled" +msgstr "サービスアカウントキー" + +#: assets/models/platform.py:54 +#, fuzzy +#| msgid "Verify auth" +msgid "Verify account method" +msgstr "パスワード/キーの確認" + +#: assets/models/platform.py:71 tickets/models/ticket/general.py:298 +msgid "Meta" +msgstr "メタ" + +#: assets/models/platform.py:72 +msgid "Internal" +msgstr "内部" + +#: assets/models/platform.py:75 +msgid "Charset" +msgstr "シャーセット" + +#: assets/models/platform.py:76 +#, fuzzy +#| msgid "Domain name" +msgid "Domain enabled" +msgstr "ドメイン名" + +#: assets/models/platform.py:77 +#, fuzzy +#| msgid "Protocols" +msgid "Protocols enabled" +msgstr "プロトコル" + +#: assets/models/platform.py:79 +#, fuzzy +#| msgid "MFA enabled" +msgid "Su enabled" +msgstr "MFA有効化" + +#: assets/models/platform.py:80 +msgid "SU method" +msgstr "" + +#: assets/models/platform.py:82 assets/serializers/platform.py:78 +#, fuzzy +#| msgid "Automatic managed" +msgid "Automation" msgstr "自動管理" -#: assets/models/user.py:234 -msgid "Manually input" -msgstr "手動入力" - -#: assets/models/user.py:238 -msgid "Common user" -msgstr "共通ユーザー" - -#: assets/models/user.py:241 -msgid "Username same with user" -msgstr "ユーザーと同じユーザー名" - -#: assets/models/user.py:244 assets/serializers/domain.py:30 -#: terminal/templates/terminal/_msg_command_execute_alert.html:16 -#: xpack/plugins/change_auth_plan/models/asset.py:39 -msgid "Assets" -msgstr "資産" - -#: assets/models/user.py:248 users/apps.py:9 -msgid "Users" -msgstr "ユーザー" - -#: assets/models/user.py:249 -msgid "User groups" -msgstr "ユーザーグループ" - -#: assets/models/user.py:253 -msgid "Auto push" -msgstr "オートプッシュ" - -#: assets/models/user.py:254 -msgid "Sudo" -msgstr "すど" - -#: assets/models/user.py:255 -msgid "Shell" -msgstr "シェル" - -#: assets/models/user.py:256 -msgid "Login mode" -msgstr "ログインモード" - -#: assets/models/user.py:257 -msgid "SFTP Root" -msgstr "SFTPルート" - -#: assets/models/user.py:258 assets/serializers/system_user.py:37 -#: authentication/models.py:52 -msgid "Token" -msgstr "トークン" - -#: assets/models/user.py:259 -msgid "Home" -msgstr "ホーム" - -#: assets/models/user.py:260 -msgid "System groups" -msgstr "システムグループ" - -#: assets/models/user.py:263 -msgid "User switch" -msgstr "ユーザースイッチ" - -#: assets/models/user.py:264 -msgid "Switch from" -msgstr "から切り替え" - -#: assets/models/user.py:344 -msgid "Can match system user" -msgstr "システムユーザーに一致できます" - -#: assets/models/utils.py:35 +#: assets/models/utils.py:19 #, python-format msgid "%(value)s is not an even number" msgstr "%(value)s は偶数ではありません" @@ -1101,98 +1163,153 @@ msgstr "" "されていません-個人情報にアクセスしてください-> ファイル暗号化パスワードを設" "定してください暗号化パスワード" -#: assets/serializers/account.py:36 assets/serializers/account.py:87 -#: assets/serializers/account_history.py:10 authentication/models.py:87 -msgid "System user display" -msgstr "システムユーザー表示" +#: assets/serializers/account/account.py:16 +msgid "Push now" +msgstr "" -#: assets/serializers/asset.py:20 -msgid "Protocol format should {}/{}" -msgstr "プロトコル形式は {}/{}" +#: assets/serializers/account/account.py:24 +msgid "Account template not found" +msgstr "" -#: assets/serializers/asset.py:37 -msgid "Protocol duplicate: {}" -msgstr "プロトコル重複: {}" - -#: assets/serializers/asset.py:66 -msgid "Domain name" -msgstr "ドメイン名" - -#: assets/serializers/asset.py:68 -msgid "Nodes name" -msgstr "ノード名" - -#: assets/serializers/asset.py:71 -msgid "Labels name" -msgstr "ラベル名" - -#: assets/serializers/asset.py:105 -msgid "Hardware info" -msgstr "ハードウェア情報" - -#: assets/serializers/asset.py:106 -msgid "Admin user display" -msgstr "管理者ユーザー表示" - -#: assets/serializers/asset.py:107 -msgid "CPU info" -msgstr "CPU情報" - -#: assets/serializers/backup.py:20 perms/models/base.py:87 -#: perms/serializers/application/permission.py:17 -#: perms/serializers/application/permission.py:42 -#: perms/serializers/asset/permission.py:18 -#: perms/serializers/asset/permission.py:46 -#: tickets/models/ticket/apply_application.py:27 -#: tickets/models/ticket/apply_asset.py:21 -msgid "Actions" -msgstr "アクション" - -#: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 +#: assets/serializers/account/backup.py:27 ops/mixin.py:104 #: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定期的なパフォーマンス" -#: assets/serializers/backup.py:33 +#: assets/serializers/account/backup.py:29 #: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/base.py:16 users/models/user.py:693 -msgid "Private key" -msgstr "ssh秘密鍵" - -#: assets/serializers/base.py:45 -msgid "Key password" -msgstr "キーパスワード" - -#: assets/serializers/base.py:58 +#: assets/serializers/account/base.py:39 assets/serializers/base.py:34 msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" -#: assets/serializers/cmd_filter.py:35 assets/serializers/cmd_filter.py:50 -msgid "Action display" -msgstr "アクション表示" +#: assets/serializers/account/template.py:16 common/drf/fields.py:69 +#: tickets/serializers/ticket/common.py:58 +#: xpack/plugins/change_auth_plan/serializers/asset.py:64 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:101 +#: xpack/plugins/cloud/serializers/account_attrs.py:56 +msgid "This field is required." +msgstr "このフィールドは必須です。" -#: assets/serializers/cmd_filter.py:51 ops/models/adhoc.py:155 -msgid "Pattern" -msgstr "パターン" +#: assets/serializers/asset/common.py:69 assets/serializers/platform.py:77 +#: xpack/plugins/cloud/models.py:109 +msgid "Protocols" +msgstr "プロトコル" + +#: assets/serializers/asset/common.py:86 +msgid "Address" +msgstr "アドレス" + +#: assets/serializers/asset/common.py:129 +#, fuzzy +#| msgid "Application not exists" +msgid "Platform not exist" +msgstr "アプリが存在しません" + +#: assets/serializers/asset/common.py:145 +#, fuzzy +#| msgid "Protocol duplicate: {}" +msgid "Protocol is required: {}" +msgstr "プロトコル重複: {}" + +#: assets/serializers/asset/host.py:12 +msgid "Vendor" +msgstr "ベンダー" + +#: assets/serializers/asset/host.py:13 +msgid "Model" +msgstr "モデル" + +#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:296 +msgid "Serial number" +msgstr "シリアル番号" + +#: assets/serializers/asset/host.py:16 +msgid "CPU model" +msgstr "CPU モデル" + +#: assets/serializers/asset/host.py:17 +msgid "CPU count" +msgstr "CPU カウント" + +#: assets/serializers/asset/host.py:18 +msgid "CPU cores" +msgstr "CPU カラー" + +#: assets/serializers/asset/host.py:19 +msgid "CPU vcpus" +msgstr "CPU 合計" + +#: assets/serializers/asset/host.py:20 +msgid "Memory" +msgstr "メモリ" + +#: assets/serializers/asset/host.py:21 +msgid "Disk total" +msgstr "ディスクの合計" + +#: assets/serializers/asset/host.py:22 +msgid "Disk info" +msgstr "ディスク情報" + +#: assets/serializers/asset/host.py:24 +msgid "OS" +msgstr "OS" + +#: assets/serializers/asset/host.py:25 +msgid "OS version" +msgstr "システムバージョン" + +#: assets/serializers/asset/host.py:26 +msgid "OS arch" +msgstr "システムアーキテクチャ" + +#: assets/serializers/asset/host.py:27 +msgid "Hostname raw" +msgstr "ホスト名生" + +#: assets/serializers/asset/host.py:28 +msgid "Asset number" +msgstr "資産番号" + +#: assets/serializers/base.py:24 +msgid "Key password" +msgstr "キーパスワード" + +#: assets/serializers/cagegory.py:9 +msgid "Constraints" +msgstr "" + +#: assets/serializers/cagegory.py:15 +#, fuzzy +#| msgid "Type" +msgid "Types" +msgstr "タイプ" #: assets/serializers/domain.py:14 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:63 -#: perms/serializers/asset/permission.py:49 +#: perms/serializers/permission.py:83 msgid "Assets amount" msgstr "資産額" #: assets/serializers/domain.py:15 -msgid "Applications amount" -msgstr "申し込み金額" - -#: assets/serializers/domain.py:16 msgid "Gateways count" msgstr "ゲートウェイ数" +#: assets/serializers/label.py:13 assets/serializers/mixin.py:7 +msgid "Category display" +msgstr "カテゴリ表示" + +#: assets/serializers/mixin.py:10 audits/serializers.py:27 +#: authentication/serializers/connection_token.py:20 +#: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 +msgid "Type display" +msgstr "タイプ表示" + #: assets/serializers/node.py:17 msgid "value" msgstr "値" @@ -1205,80 +1322,43 @@ msgstr "含まれない:/" msgid "The same level node name cannot be the same" msgstr "同じレベルのノード名を同じにすることはできません。" -#: assets/serializers/system_user.py:35 -msgid "SSH key fingerprint" -msgstr "SSHキー指紋" +#: assets/serializers/platform.py:24 +#, fuzzy +#| msgid "MFA enabled" +msgid "SFTP enabled" +msgstr "MFA有効化" -#: assets/serializers/system_user.py:40 -#: perms/serializers/application/permission.py:46 -msgid "Apps amount" -msgstr "アプリの量" +#: assets/serializers/platform.py:25 +#, fuzzy +#| msgid "SFTP Root" +msgid "SFTP home" +msgstr "SFTPルート" -#: assets/serializers/system_user.py:62 -#: perms/serializers/asset/permission.py:50 -msgid "Nodes amount" -msgstr "ノード量" +#: assets/serializers/platform.py:28 +#, fuzzy +#| msgid "Auto" +msgid "Auto fill" +msgstr "自動" -#: assets/serializers/system_user.py:64 assets/serializers/system_user.py:269 -msgid "Login mode display" -msgstr "ログインモード表示" +#: assets/serializers/platform.py:29 +#, fuzzy +#| msgid "Username attr" +msgid "Username selector" +msgstr "ユーザー名のプロパティ" -#: assets/serializers/system_user.py:66 -msgid "Ad domain" -msgstr "広告ドメイン" +#: assets/serializers/platform.py:30 +#, fuzzy +#| msgid "Password rules" +msgid "Password selector" +msgstr "パスワードルール" -#: assets/serializers/system_user.py:67 -msgid "Is asset protocol" -msgstr "資産プロトコルです" +#: assets/serializers/platform.py:31 +msgid "Submit selector" +msgstr "" -#: assets/serializers/system_user.py:68 -msgid "Only ssh and automatic login system users are supported" -msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" - -#: assets/serializers/system_user.py:108 -msgid "Username same with user with protocol {} only allow 1" -msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" - -#: assets/serializers/system_user.py:121 common/validators.py:14 -msgid "Special char not allowed" -msgstr "特別なcharは許可されていません" - -#: assets/serializers/system_user.py:131 -msgid "* Automatic login mode must fill in the username." -msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" - -#: assets/serializers/system_user.py:146 -msgid "Path should starts with /" -msgstr "パスは/で始まる必要があります" - -#: assets/serializers/system_user.py:158 -msgid "Password or private key required" -msgstr "パスワードまたは秘密鍵が必要" - -#: assets/serializers/system_user.py:172 -msgid "Only ssh protocol system users are allowed" -msgstr "Sshプロトコルシステムユーザーのみが許可されています" - -#: assets/serializers/system_user.py:176 -msgid "The protocol must be consistent with the current user: {}" -msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" - -#: assets/serializers/system_user.py:180 -msgid "Only system users with automatic login are allowed" -msgstr "自動ログインを持つシステムユーザーのみが許可されます" - -#: assets/serializers/system_user.py:288 -msgid "System user name" -msgstr "システムユーザー名" - -#: assets/serializers/system_user.py:289 orgs/mixins/serializers.py:26 -#: rbac/serializers/rolebinding.py:23 -msgid "Org name" -msgstr "組織名" - -#: assets/serializers/system_user.py:298 -msgid "Asset hostname" -msgstr "資産ホスト名" +#: assets/serializers/platform.py:64 +msgid "Primary" +msgstr "" #: assets/serializers/utils.py:11 msgid "Password can not contains `{{` " @@ -1298,7 +1378,7 @@ msgstr "" "資産 {} システムプラットフォーム {} はAnsibleタスクの実行をサポートしていませ" "ん。" -#: assets/tasks/account_connectivity.py:107 +#: assets/tasks/account_connectivity.py:108 msgid "Test account connectivity: " msgstr "テストアカウント接続:" @@ -1306,11 +1386,11 @@ msgstr "テストアカウント接続:" msgid "Test assets connectivity. " msgstr "資産の接続性をテストします。" -#: assets/tasks/asset_connectivity.py:91 assets/tasks/asset_connectivity.py:102 +#: assets/tasks/asset_connectivity.py:94 assets/tasks/asset_connectivity.py:107 msgid "Test assets connectivity: " msgstr "資産の接続性のテスト:" -#: assets/tasks/asset_connectivity.py:113 +#: assets/tasks/asset_connectivity.py:121 msgid "Test if the assets under the node are connectable: " msgstr "ノードの下のアセットが接続可能かどうかをテストします。" @@ -1330,67 +1410,29 @@ msgstr "資産情報の取得に失敗しました: {}" msgid "Update some assets hardware info. " msgstr "一部の資産ハードウェア情報を更新します。" -#: assets/tasks/gather_asset_hardware_info.py:114 +#: assets/tasks/gather_asset_hardware_info.py:118 msgid "Update asset hardware info: " msgstr "資産ハードウェア情報の更新:" -#: assets/tasks/gather_asset_hardware_info.py:120 +#: assets/tasks/gather_asset_hardware_info.py:124 msgid "Update assets hardware info: " msgstr "資産のハードウェア情報を更新する:" -#: assets/tasks/gather_asset_hardware_info.py:137 +#: assets/tasks/gather_asset_hardware_info.py:146 msgid "Update node asset hardware information: " msgstr "ノード資産のハードウェア情報を更新します。" -#: assets/tasks/gather_asset_users.py:111 +#: assets/tasks/gather_asset_users.py:110 msgid "Gather assets users" msgstr "資産ユーザーの収集" -#: assets/tasks/nodes_amount.py:27 +#: assets/tasks/nodes_amount.py:29 msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "" "セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ" "ん" -#: assets/tasks/push_system_user.py:201 -msgid "System user is dynamic: {}" -msgstr "システムユーザーは動的です: {}" - -#: assets/tasks/push_system_user.py:242 -msgid "Start push system user for platform: [{}]" -msgstr "プラットフォームのプッシュシステムユーザーを開始: [{}]" - -#: assets/tasks/push_system_user.py:243 -#: assets/tasks/system_user_connectivity.py:106 -msgid "Hosts count: {}" -msgstr "ホスト数: {}" - -#: assets/tasks/push_system_user.py:264 assets/tasks/push_system_user.py:297 -msgid "Push system users to assets: " -msgstr "システムユーザーを資産にプッシュする:" - -#: assets/tasks/push_system_user.py:276 -msgid "Push system users to asset: " -msgstr "システムユーザーをアセットにプッシュする:" - -#: assets/tasks/system_user_connectivity.py:56 -msgid "Dynamic system user not support test" -msgstr "動的システムユーザーがテストをサポートしていない" - -#: assets/tasks/system_user_connectivity.py:105 -msgid "Start test system user connectivity for platform: [{}]" -msgstr "プラットフォームのテストシステムのユーザー接続を開始: [{}]" - -#: assets/tasks/system_user_connectivity.py:118 -#: assets/tasks/system_user_connectivity.py:129 -msgid "Test system user connectivity: " -msgstr "テストシステムユーザー接続:" - -#: assets/tasks/system_user_connectivity.py:148 -msgid "Test system user connectivity period: " -msgstr "テストシステムユーザー接続期间:" - #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" msgstr "資産が無効化されました。スキップ: {}" @@ -1442,7 +1484,7 @@ msgid "Symlink" msgstr "Symlink" #: audits/models.py:38 audits/models.py:66 audits/models.py:89 -#: terminal/models/session.py:51 terminal/models/sharing.py:96 +#: terminal/models/session.py:37 terminal/models/sharing.py:96 msgid "Remote addr" msgstr "リモートaddr" @@ -1455,9 +1497,8 @@ msgid "Filename" msgstr "ファイル名" #: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:104 -#: tickets/views/approve.py:115 -#: xpack/plugins/change_auth_plan/serializers/app.py:87 -#: xpack/plugins/change_auth_plan/serializers/asset.py:198 +#: tickets/views/approve.py:114 +#: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" msgstr "成功" @@ -1480,7 +1521,7 @@ msgstr "表示" msgid "Update" msgstr "更新" -#: audits/models.py:64 audits/serializers.py:63 +#: audits/models.py:64 audits/serializers.py:61 msgid "Resource Type" msgstr "リソースタイプ" @@ -1509,10 +1550,6 @@ msgstr "パスワード変更ログ" msgid "Disabled" msgstr "無効" -#: audits/models.py:112 settings/models.py:37 -msgid "Enabled" -msgstr "有効化" - #: audits/models.py:113 msgid "-" msgstr "-" @@ -1531,7 +1568,7 @@ msgstr "ログインIP" msgid "Login city" msgstr "ログイン都市" -#: audits/models.py:125 audits/serializers.py:44 +#: audits/models.py:125 audits/serializers.py:42 msgid "User agent" msgstr "ユーザーエージェント" @@ -1542,9 +1579,9 @@ msgstr "ユーザーエージェント" msgid "MFA" msgstr "MFA" -#: audits/models.py:128 terminal/models/status.py:33 -#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175 -#: xpack/plugins/cloud/models.py:227 +#: audits/models.py:128 ops/models/base.py:48 terminal/models/status.py:33 +#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:223 msgid "Status" msgstr "ステータス" @@ -1552,7 +1589,7 @@ msgstr "ステータス" msgid "Date login" msgstr "日付ログイン" -#: audits/models.py:130 audits/serializers.py:46 +#: audits/models.py:130 audits/serializers.py:44 msgid "Authentication backend" msgstr "認証バックエンド" @@ -1560,48 +1597,22 @@ msgstr "認証バックエンド" msgid "User login log" msgstr "ユーザーログインログ" -#: audits/serializers.py:14 +#: audits/serializers.py:12 msgid "Operate display" msgstr "ディスプレイを操作する" -#: audits/serializers.py:30 tickets/serializers/ticket/ticket.py:18 +#: audits/serializers.py:28 tickets/serializers/ticket/ticket.py:18 msgid "Status display" msgstr "ステータス表示" -#: audits/serializers.py:31 +#: audits/serializers.py:29 msgid "MFA display" msgstr "MFAディスプレイ" -#: audits/serializers.py:45 +#: audits/serializers.py:43 msgid "Reason display" msgstr "理由表示" -#: audits/serializers.py:84 -msgid "Hosts display" -msgstr "ホスト表示" - -#: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:173 -msgid "Result" -msgstr "結果" - -#: audits/serializers.py:98 terminal/serializers/storage.py:157 -msgid "Hosts" -msgstr "ホスト" - -#: audits/serializers.py:99 -msgid "Run as" -msgstr "として実行" - -#: audits/serializers.py:101 -msgid "Run as display" -msgstr "ディスプレイとして実行する" - -#: audits/serializers.py:102 authentication/models.py:81 -#: rbac/serializers/rolebinding.py:21 -msgid "User display" -msgstr "ユーザー表示" - #: audits/signal_handlers.py:49 msgid "SSH Key" msgstr "SSHキー" @@ -1632,7 +1643,7 @@ msgstr "本を飛ばす" msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:56 authentication/models.py:267 +#: audits/signal_handlers.py:56 authentication/models.py:255 msgid "Temporary token" msgstr "仮パスワード" @@ -1651,159 +1662,75 @@ msgid "{User} LEFT {UserGroup}" msgstr "{User} のそばを通る {UserGroup}" #: audits/signal_handlers.py:73 -msgid "Asset and SystemUser" -msgstr "資産およびシステム・ユーザー" - -#: audits/signal_handlers.py:74 -#, python-brace-format -msgid "{Asset} ADD {SystemUser}" -msgstr "{Asset} 追加 {SystemUser}" - -#: audits/signal_handlers.py:75 -#, python-brace-format -msgid "{Asset} REMOVE {SystemUser}" -msgstr "{Asset} 削除 {SystemUser}" - -#: audits/signal_handlers.py:78 msgid "Node and Asset" msgstr "ノードと資産" -#: audits/signal_handlers.py:79 +#: audits/signal_handlers.py:74 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 追加 {Asset}" -#: audits/signal_handlers.py:80 +#: audits/signal_handlers.py:75 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 削除 {Asset}" -#: audits/signal_handlers.py:83 +#: audits/signal_handlers.py:78 msgid "User asset permissions" msgstr "ユーザー資産の権限" -#: audits/signal_handlers.py:84 +#: audits/signal_handlers.py:79 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 追加 {User}" -#: audits/signal_handlers.py:85 +#: audits/signal_handlers.py:80 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 削除 {User}" -#: audits/signal_handlers.py:88 +#: audits/signal_handlers.py:83 msgid "User group asset permissions" msgstr "ユーザーグループの資産権限" -#: audits/signal_handlers.py:89 +#: audits/signal_handlers.py:84 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:90 +#: audits/signal_handlers.py:85 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:93 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:88 perms/models/asset_permission.py:90 msgid "Asset permission" msgstr "資産権限" -#: audits/signal_handlers.py:94 +#: audits/signal_handlers.py:89 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 追加 {Asset}" -#: audits/signal_handlers.py:95 +#: audits/signal_handlers.py:90 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 削除 {Asset}" -#: audits/signal_handlers.py:98 +#: audits/signal_handlers.py:93 msgid "Node permission" msgstr "ノード権限" -#: audits/signal_handlers.py:99 +#: audits/signal_handlers.py:94 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 追加 {Node}" -#: audits/signal_handlers.py:100 +#: audits/signal_handlers.py:95 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 削除 {Node}" -#: audits/signal_handlers.py:103 -msgid "Asset permission and SystemUser" -msgstr "資産権限とSystemUser" - -#: audits/signal_handlers.py:104 -#, python-brace-format -msgid "{AssetPermission} ADD {SystemUser}" -msgstr "{AssetPermission} 追加 {SystemUser}" - -#: audits/signal_handlers.py:105 -#, python-brace-format -msgid "{AssetPermission} REMOVE {SystemUser}" -msgstr "{AssetPermission} 削除 {SystemUser}" - -#: audits/signal_handlers.py:108 -msgid "User application permissions" -msgstr "ユーザーアプリケーションの権限" - -#: audits/signal_handlers.py:109 -#, python-brace-format -msgid "{ApplicationPermission} ADD {User}" -msgstr "{ApplicationPermission} 追加 {User}" - -#: audits/signal_handlers.py:110 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {User}" -msgstr "{ApplicationPermission} 削除 {User}" - -#: audits/signal_handlers.py:113 -msgid "User group application permissions" -msgstr "ユーザーグループアプリケーションの権限" - -#: audits/signal_handlers.py:114 -#, python-brace-format -msgid "{ApplicationPermission} ADD {UserGroup}" -msgstr "{ApplicationPermission} 追加 {UserGroup}" - -#: audits/signal_handlers.py:115 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {UserGroup}" -msgstr "{ApplicationPermission} 削除 {UserGroup}" - -#: audits/signal_handlers.py:118 perms/models/application_permission.py:38 -msgid "Application permission" -msgstr "申請許可" - -#: audits/signal_handlers.py:119 -#, python-brace-format -msgid "{ApplicationPermission} ADD {Application}" -msgstr "{ApplicationPermission} 追加 {Application}" - -#: audits/signal_handlers.py:120 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {Application}" -msgstr "{ApplicationPermission} 削除 {Application}" - -#: audits/signal_handlers.py:123 -msgid "Application permission and SystemUser" -msgstr "アプリケーション権限とSystemUser" - -#: audits/signal_handlers.py:124 -#, python-brace-format -msgid "{ApplicationPermission} ADD {SystemUser}" -msgstr "{ApplicationPermission} 追加 {SystemUser}" - -#: audits/signal_handlers.py:125 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {SystemUser}" -msgstr "{ApplicationPermission} 削除 {SystemUser}" - #: authentication/api/confirm.py:40 msgid "This action require verify your MFA" msgstr "この操作には、MFAを検証する必要があります" @@ -1869,7 +1796,7 @@ msgstr "" msgid "Invalid token or cache refreshed." msgstr "無効なトークンまたはキャッシュの更新。" -#: authentication/backends/oauth2/backends.py:155 authentication/models.py:158 +#: authentication/backends/oauth2/backends.py:155 authentication/models.py:146 msgid "User invalid, disabled or expired" msgstr "ユーザーが無効、無効、または期限切れです" @@ -1953,9 +1880,13 @@ msgstr "" "にもう一度お試しください)" #: authentication/errors/const.py:51 +#, fuzzy +#| msgid "" +#| "The ip has been locked (please contact admin to unlock it or try again " +#| "after {} minutes)" msgid "" -"The ip has been locked (please contact admin to unlock it or try again after " -"{} minutes)" +"The address has been locked (please contact admin to unlock it or try again " +"after {} minutes)" msgstr "" "IPがロックされています (管理者に連絡してロックを解除するか、 {} 分後にもう一" "度お試しください)" @@ -2128,87 +2059,82 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:37 -msgid "Access key" -msgstr "アクセスキー" - -#: authentication/models.py:44 +#: authentication/models.py:45 msgid "Private Token" msgstr "プライベートトークン" -#: authentication/models.py:53 +#: authentication/models.py:54 msgid "Expired" msgstr "期限切れ" -#: authentication/models.py:57 +#: authentication/models.py:58 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:72 authentication/models.py:261 -#: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 -msgid "Secret" -msgstr "ひみつ" - -#: authentication/models.py:74 authentication/models.py:264 -#: perms/models/base.py:90 tickets/models/ticket/apply_application.py:30 -#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:707 +#: authentication/models.py:75 authentication/models.py:252 +#: perms/models/asset_permission.py:79 +#: tickets/models/ticket/apply_application.py:29 +#: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 msgid "Date expired" msgstr "期限切れの日付" -#: authentication/models.py:93 +#: authentication/models.py:82 rbac/serializers/rolebinding.py:21 +msgid "User display" +msgstr "ユーザー表示" + +#: authentication/models.py:87 msgid "Asset display" msgstr "アセット名" -#: authentication/models.py:104 +#: authentication/models.py:92 msgid "Connection token" msgstr "接続トークン" -#: authentication/models.py:106 +#: authentication/models.py:94 msgid "Can view connection token secret" msgstr "接続トークンの秘密を表示できます" -#: authentication/models.py:149 +#: authentication/models.py:137 msgid "Connection token expired at: {}" msgstr "接続トークンの有効期限: {}" -#: authentication/models.py:154 +#: authentication/models.py:142 msgid "User not exists" msgstr "ユーザーは存在しません" -#: authentication/models.py:163 +#: authentication/models.py:151 msgid "System user not exists" msgstr "システムユーザーが存在しません" -#: authentication/models.py:169 +#: authentication/models.py:157 msgid "Asset not exists" msgstr "アセットが存在しません" -#: authentication/models.py:173 +#: authentication/models.py:161 msgid "Asset inactive" msgstr "アセットがアクティブ化されていません" -#: authentication/models.py:180 +#: authentication/models.py:168 msgid "User has no permission to access asset or permission expired" msgstr "" "ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて" "います" -#: authentication/models.py:188 +#: authentication/models.py:176 msgid "Application not exists" msgstr "アプリが存在しません" -#: authentication/models.py:195 +#: authentication/models.py:183 msgid "User has no permission to access application or permission expired" msgstr "" "ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れてい" "ます" -#: authentication/models.py:262 +#: authentication/models.py:250 msgid "Verified" msgstr "確認済み" -#: authentication/models.py:283 +#: authentication/models.py:271 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2220,24 +2146,21 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" -#: authentication/serializers/connection_token.py:23 -#: xpack/plugins/cloud/models.py:34 +#: authentication/serializers/connection_token.py:21 +#: xpack/plugins/cloud/models.py:36 msgid "Validity" msgstr "有効性" -#: authentication/serializers/connection_token.py:24 +#: authentication/serializers/connection_token.py:22 msgid "Expired time" msgstr "期限切れ時間" -#: authentication/serializers/connection_token.py:73 +#: authentication/serializers/connection_token.py:68 msgid "Asset or application required" msgstr "アセットまたはアプリが必要" -#: authentication/serializers/token.py:79 -#: perms/serializers/application/permission.py:20 -#: perms/serializers/application/permission.py:41 -#: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:148 +#: authentication/serializers/token.py:79 perms/serializers/permission.py:60 +#: perms/serializers/permission.py:87 users/serializers/user.py:148 msgid "Is valid" msgstr "有効です" @@ -2287,7 +2210,7 @@ msgstr "削除成功" #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:45 +#: templates/_modal.html:22 tickets/const.py:44 msgid "Close" msgstr "閉じる" @@ -2324,7 +2247,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:390 ops/tasks.py:146 ops/tasks.py:152 ops/tasks.py:155 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2416,7 +2339,7 @@ msgstr "" "能性があります" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:20 +#: templates/flash_message_standalone.html:28 tickets/const.py:19 msgid "Cancel" msgstr "キャンセル" @@ -2617,6 +2540,14 @@ msgstr "%(name)s が正常に作成されました" msgid "%(name)s was updated successfully" msgstr "%(name)s は正常に更新されました" +#: common/const/choices.py:10 +msgid "Manual trigger" +msgstr "手動トリガー" + +#: common/const/choices.py:11 +msgid "Timing trigger" +msgstr "タイミングトリガー" + #: common/db/encoder.py:11 msgid "ugettext_lazy" msgstr "ugettext_lazy" @@ -2649,7 +2580,7 @@ msgstr "テキストフィールドへのマーシャルデータ" msgid "Encrypt field using Secret Key" msgstr "Secret Keyを使用したフィールドの暗号化" -#: common/db/models.py:113 +#: common/db/models.py:75 msgid "Updated by" msgstr "によって更新" @@ -2657,6 +2588,17 @@ msgstr "によって更新" msgid "Object" msgstr "オブジェクト" +#: common/drf/fields.py:70 +#, fuzzy, python-brace-format +#| msgid "%s object does not exist." +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "%s オブジェクトは存在しません。" + +#: common/drf/fields.py:71 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "" + #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" msgstr "ファイルの内容がオーバーフローしました (最大長 '{}' バイト)" @@ -2665,6 +2607,10 @@ msgstr "ファイルの内容がオーバーフローしました (最大長 '{} msgid "Parse file error: {}" msgstr "解析ファイルエラー: {}" +#: common/drf/serializers/common.py:86 +msgid "Children" +msgstr "" + #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." @@ -2771,10 +2717,20 @@ msgstr "確認コードが正しくありません" msgid "Please wait {} seconds before sending" msgstr "{} 秒待ってから送信してください" -#: common/utils/ip/geoip/utils.py:26 common/utils/ip/utils.py:78 +#: common/utils/ip/geoip/utils.py:26 msgid "Invalid ip" msgstr "無効なIP" +#: common/utils/ip/utils.py:78 +#, fuzzy +#| msgid "Invalid signature." +msgid "Invalid address" +msgstr "署名が無効です。" + +#: common/validators.py:14 +msgid "Special char not allowed" +msgstr "特別なcharは許可されていません" + #: common/validators.py:32 msgid "This field must be unique." msgstr "このフィールドは一意である必要があります。" @@ -2844,148 +2800,173 @@ msgstr "メール" msgid "Site message" msgstr "サイトメッセージ" +#: ops/ansible/inventory.py:76 +#, fuzzy +#| msgid "Account unavailable" +msgid "No account available" +msgstr "利用できないアカウント" + +#: ops/ansible/inventory.py:171 +#, fuzzy +#| msgid "User disabled." +msgid "Ansible disabled" +msgstr "ユーザーが無効になりました。" + +#: ops/ansible/inventory.py:186 +msgid "Skip hosts below:" +msgstr "" + #: ops/api/celery.py:61 ops/api/celery.py:76 msgid "Waiting task start" msgstr "タスク開始待ち" -#: ops/api/command.py:56 -msgid "Not has host {} permission" -msgstr "ホスト {} 権限がありません" - #: ops/apps.py:9 ops/notifications.py:16 msgid "App ops" msgstr "アプリ操作" -#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:72 +#: ops/const.py:6 +msgid "Push" +msgstr "" + +#: ops/const.py:7 +#, fuzzy +#| msgid "Verified" +msgid "Verify" +msgstr "確認済み" + +#: ops/const.py:8 +msgid "Collect" +msgstr "" + +#: ops/const.py:9 +#, fuzzy +#| msgid "Change Password" +msgid "Change password" +msgstr "パスワードの変更" + +#: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 +msgid "Custom password" +msgstr "カスタムパスワード" + +#: ops/mixin.py:27 ops/mixin.py:90 settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "サイクル実行" -#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 +#: ops/mixin.py:31 ops/mixin.py:88 ops/mixin.py:107 #: settings/serializers/auth/ldap.py:69 msgid "Regularly perform" msgstr "定期的に実行する" -#: ops/mixin.py:112 +#: ops/mixin.py:110 msgid "Interval" msgstr "間隔" -#: ops/mixin.py:122 +#: ops/mixin.py:120 msgid "* Please enter a valid crontab expression" msgstr "* 有効なcrontab式を入力してください" -#: ops/mixin.py:129 +#: ops/mixin.py:127 msgid "Range {} to {}" msgstr "{} から {} までの範囲" -#: ops/mixin.py:140 +#: ops/mixin.py:138 msgid "Require periodic or regularly perform setting" msgstr "定期的または定期的に設定を行う必要があります" -#: ops/mixin.py:151 -msgid "" -"eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " -"crontab expressions (Online tools)
Note: If both Regularly " -"perform and Cycle perform are set, give priority to Regularly perform" -msgstr "" -"eg:毎週日03:05<5 3**0>
ヒント:5ビットLinux crontab式<分時日月曜日>(オンラインワーク)
注" -"意:定期実行と周期実行を同時に設定した場合は、定期実行を優先します。" +#: ops/models/adhoc.py:18 +msgid "Pattern" +msgstr "パターン" -#: ops/mixin.py:162 -msgid "Unit: hour" -msgstr "単位: 時間" +#: ops/models/adhoc.py:19 +msgid "Module" +msgstr "" + +#: ops/models/adhoc.py:20 ops/models/celery.py:15 terminal/models/task.py:17 +msgid "Args" +msgstr "アルグ" + +#: ops/models/adhoc.py:21 ops/models/base.py:20 ops/models/playbook.py:27 +#, fuzzy +#| msgid "Command execution" +msgid "Last execution" +msgstr "コマンド実行" #: ops/models/adhoc.py:36 -msgid "Callback" -msgstr "コールバック" +msgid "Adhoc" +msgstr "" -#: ops/models/adhoc.py:135 terminal/models/task.py:26 -#: xpack/plugins/gathered_user/models.py:73 -msgid "Task" -msgstr "タスク" - -#: ops/models/adhoc.py:138 -msgid "Can view task monitor" -msgstr "タスクモニターを表示できます" - -#: ops/models/adhoc.py:154 -msgid "Tasks" -msgstr "タスク" - -#: ops/models/adhoc.py:156 -msgid "Options" -msgstr "オプション" - -#: ops/models/adhoc.py:158 -msgid "Run as admin" -msgstr "再実行" - -#: ops/models/adhoc.py:161 -msgid "Become" -msgstr "になる" - -#: ops/models/adhoc.py:162 -msgid "Create by" -msgstr "による作成" - -#: ops/models/adhoc.py:243 -msgid "AdHoc" -msgstr "タスクの各バージョン" - -#: ops/models/adhoc.py:252 -msgid "Task display" -msgstr "タスク表示" - -#: ops/models/adhoc.py:254 -msgid "Host amount" -msgstr "ホスト量" - -#: ops/models/adhoc.py:256 -msgid "Start time" -msgstr "開始時間" - -#: ops/models/adhoc.py:257 -msgid "End time" -msgstr "終了時間" - -#: ops/models/adhoc.py:259 ops/models/command.py:29 -#: terminal/serializers/session.py:40 -msgid "Is finished" -msgstr "終了しました" - -#: ops/models/adhoc.py:261 -msgid "Adhoc raw result" -msgstr "アドホック生の結果" - -#: ops/models/adhoc.py:262 -msgid "Adhoc result summary" -msgstr "アドホック結果の概要" - -#: ops/models/adhoc.py:339 +#: ops/models/adhoc.py:54 msgid "AdHoc execution" msgstr "アドホックエキューション" -#: ops/models/command.py:32 -msgid "Date finished" -msgstr "終了日" +#: ops/models/base.py:16 ops/models/base.py:52 terminal/models/sharing.py:24 +msgid "Creator" +msgstr "作成者" -#: ops/models/command.py:113 -msgid "Task start" -msgstr "タスクの開始" +#: ops/models/base.py:19 +#, fuzzy +#| msgid "Account key" +msgid "Account policy" +msgstr "アカウントキー" -#: ops/models/command.py:147 -msgid "Command `{}` is forbidden ........" -msgstr "コマンド '{}' は禁止されています ........" +#: ops/models/base.py:21 +#, fuzzy +#| msgid "Date last sync" +msgid "Date last run" +msgstr "最終同期日" -#: ops/models/command.py:160 -msgid "Task end" +#: ops/models/base.py:50 xpack/plugins/cloud/models.py:169 +msgid "Result" +msgstr "結果" + +#: ops/models/base.py:51 +msgid "Summary" +msgstr "" + +#: ops/models/celery.py:16 terminal/models/task.py:18 +msgid "Kwargs" +msgstr "クワーグ" + +#: ops/models/celery.py:17 tickets/models/comment.py:13 +#: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 +msgid "State" +msgstr "状態" + +#: ops/models/celery.py:18 terminal/models/sharing.py:111 tickets/const.py:25 +#: xpack/plugins/change_auth_plan/models/base.py:188 +msgid "Finished" +msgstr "終了" + +#: ops/models/playbook.py:10 +msgid "Path" +msgstr "" + +#: ops/models/playbook.py:18 +msgid "Playbook template" +msgstr "" + +#: ops/models/playbook.py:23 +msgid "Playbook" +msgstr "" + +#: ops/models/playbook.py:24 +msgid "Owner" +msgstr "" + +#: ops/models/playbook.py:26 settings/serializers/auth/sms.py:64 +msgid "Template" +msgstr "テンプレート" + +#: ops/models/playbook.py:38 terminal/models/task.py:26 +#: xpack/plugins/gathered_user/models.py:68 +msgid "Task" msgstr "タスク" -#: ops/models/command.py:164 -msgid "Command execution" -msgstr "コマンド実行" +#: ops/models/playbook.py:39 +#, fuzzy +#| msgid "Run user" +msgid "Run dir" +msgstr "ユーザーの実行" #: ops/notifications.py:17 msgid "Server performance" @@ -3015,11 +2996,23 @@ msgstr "{max_threshold}%: => {value} を超える使用メモリ" msgid "CPU load more than {max_threshold}: => {value}" msgstr "{max_threshold} を超えるCPUロード: => {value}" -#: ops/tasks.py:72 +#: ops/tasks.py:33 +#, fuzzy +#| msgid "Run asset" +msgid "Run ansible task" +msgstr "アセットの実行" + +#: ops/tasks.py:57 +#, fuzzy +#| msgid "Run command" +msgid "Run ansible command" +msgstr "実行コマンド" + +#: ops/tasks.py:79 msgid "Clean task history period" msgstr "クリーンなタスク履歴期間" -#: ops/tasks.py:85 +#: ops/tasks.py:92 msgid "Clean celery log period" msgstr "きれいなセロリログ期間" @@ -3031,18 +3024,18 @@ msgstr "タスクログ" msgid "Update task content: {}" msgstr "タスク内容の更新: {}" -#: orgs/api.py:69 +#: orgs/api.py:67 msgid "The current organization ({}) cannot be deleted" msgstr "現在の組織 ({}) は削除できません" -#: orgs/api.py:74 +#: orgs/api.py:72 msgid "" "LDAP synchronization is set to the current organization. Please switch to " "another organization before deleting" msgstr "" "LDAP 同期は現在の組織に設定されます。削除する前に別の組織に切り替えてください" -#: orgs/api.py:83 +#: orgs/api.py:81 msgid "The organization have resource ({}) cannot be deleted" msgstr "組織のリソース ({}) は削除できません" @@ -3050,116 +3043,109 @@ msgstr "組織のリソース ({}) は削除できません" msgid "App organizations" msgstr "アプリ組織" -#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:85 -#: orgs/models.py:217 rbac/const.py:7 rbac/models/rolebinding.py:48 +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:87 +#: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 #: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 msgid "Organization" msgstr "組織" +#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 +msgid "Org name" +msgstr "組織名" + #: orgs/models.py:79 msgid "GLOBAL" msgstr "グローバル組織" -#: orgs/models.py:87 +#: orgs/models.py:81 +msgid "DEFAULT" +msgstr "" + +#: orgs/models.py:83 +msgid "SYSTEM" +msgstr "" + +#: orgs/models.py:89 msgid "Can view root org" msgstr "グローバル組織を表示できます" -#: orgs/models.py:88 +#: orgs/models.py:90 msgid "Can view all joined org" msgstr "参加しているすべての組織を表示できます" -#: orgs/models.py:222 rbac/models/role.py:46 rbac/models/rolebinding.py:44 -#: users/models/user.py:675 -msgid "Role" -msgstr "ロール" - #: perms/apps.py:9 msgid "App permissions" msgstr "アプリの権限" -#: perms/exceptions.py:9 -msgid "The administrator is modifying permissions. Please wait" -msgstr "管理者は権限を変更しています。お待ちください" +#: perms/models/asset_permission.py:72 perms/serializers/permission.py:59 +#: perms/serializers/permission.py:85 +#: tickets/models/ticket/apply_application.py:26 +#: tickets/models/ticket/apply_asset.py:19 +msgid "Actions" +msgstr "アクション" -#: perms/exceptions.py:14 -msgid "The authorization cannot be revoked for the time being" -msgstr "当分の間、承認を取り消すことはできません。" - -#: perms/models/application_permission.py:111 -msgid "Permed application" -msgstr "許可されたアプリケーション" - -#: perms/models/application_permission.py:114 -msgid "Can view my apps" -msgstr "自分のアプリを表示できます" - -#: perms/models/application_permission.py:115 -msgid "Can view user apps" -msgstr "ユーザーアプリを表示できます" - -#: perms/models/application_permission.py:116 -msgid "Can view usergroup apps" -msgstr "ユーザー・グループ認可の適用を表示できます" - -#: perms/models/asset_permission.py:134 -msgid "Ungrouped" -msgstr "グループ化されていません" - -#: perms/models/asset_permission.py:136 -msgid "Favorite" -msgstr "お気に入り" - -#: perms/models/asset_permission.py:183 -msgid "Permed asset" -msgstr "許可された資産" - -#: perms/models/asset_permission.py:185 -msgid "Can view my assets" -msgstr "私の資産を見ることができます" - -#: perms/models/asset_permission.py:186 -msgid "Can view user assets" -msgstr "ユーザー資産を表示できます" - -#: perms/models/asset_permission.py:187 -msgid "Can view usergroup assets" -msgstr "ユーザーグループの資産を表示できます" - -#: perms/models/base.py:55 -msgid "Connect" -msgstr "接続" - -#: perms/models/base.py:56 -msgid "Upload file" -msgstr "ファイルのアップロード" - -#: perms/models/base.py:57 -msgid "Download file" -msgstr "ファイルのダウンロード" - -#: perms/models/base.py:58 -msgid "Upload download" -msgstr "ダウンロードのアップロード" - -#: perms/models/base.py:59 -msgid "Clipboard copy" -msgstr "クリップボードのコピー" - -#: perms/models/base.py:60 -msgid "Clipboard paste" -msgstr "クリップボードペースト" - -#: perms/models/base.py:61 -msgid "Clipboard copy paste" -msgstr "クリップボードコピーペースト" - -#: perms/models/base.py:94 +#: perms/models/asset_permission.py:83 msgid "From ticket" msgstr "チケットから" +#: perms/models/asset_permission.py:224 +msgid "Ungrouped" +msgstr "グループ化されていません" + +#: perms/models/asset_permission.py:226 +msgid "Favorite" +msgstr "お気に入り" + +#: perms/models/asset_permission.py:273 +msgid "Permed asset" +msgstr "許可された資産" + +#: perms/models/asset_permission.py:275 +msgid "Can view my assets" +msgstr "私の資産を見ることができます" + +#: perms/models/asset_permission.py:276 +msgid "Can view user assets" +msgstr "ユーザー資産を表示できます" + +#: perms/models/asset_permission.py:277 +msgid "Can view usergroup assets" +msgstr "ユーザーグループの資産を表示できます" + +#: perms/models/const.py:20 settings/serializers/terminal.py:12 +msgid "All" +msgstr "すべて" + +#: perms/models/const.py:21 +msgid "Connect" +msgstr "接続" + +#: perms/models/const.py:22 +msgid "Upload file" +msgstr "ファイルのアップロード" + +#: perms/models/const.py:23 +msgid "Download file" +msgstr "ファイルのダウンロード" + +#: perms/models/const.py:24 +msgid "Upload download" +msgstr "ダウンロードのアップロード" + +#: perms/models/const.py:25 +msgid "Clipboard copy" +msgstr "クリップボードのコピー" + +#: perms/models/const.py:26 +msgid "Clipboard paste" +msgstr "クリップボードペースト" + +#: perms/models/const.py:27 +msgid "Clipboard copy paste" +msgstr "クリップボードコピーペースト" + #: perms/notifications.py:12 perms/notifications.py:44 -#: perms/notifications.py:88 perms/notifications.py:119 msgid "today" msgstr "今" @@ -3179,73 +3165,39 @@ msgstr "資産権限の有効期限が近づいています" msgid "asset permissions of organization {}" msgstr "組織 {} の資産権限" -#: perms/notifications.py:91 -msgid "Your permed applications is about to expire" -msgstr "パーマアプリケーションの有効期限が近づいています" +#: perms/serializers/permission.py:48 +msgid "Users display" +msgstr "ユーザー表示" -#: perms/notifications.py:95 -msgid "permed applications" -msgstr "Permedアプリケーション" +#: perms/serializers/permission.py:51 +msgid "User groups display" +msgstr "ユーザーグループの表示" -#: perms/notifications.py:134 -msgid "Application permissions is about to expire" -msgstr "アプリケーション権限の有効期限が近づいています" +#: perms/serializers/permission.py:54 +msgid "Assets display" +msgstr "資産表示" -#: perms/notifications.py:138 -msgid "application permissions of organization {}" -msgstr "Organization {} のアプリケーション権限" +#: perms/serializers/permission.py:57 +msgid "Nodes display" +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:89 -#: users/serializers/user.py:150 +#: perms/serializers/permission.py:61 perms/serializers/permission.py:86 +#: users/serializers/user.py:89 users/serializers/user.py:150 msgid "Is expired" msgstr "期限切れです" -#: perms/serializers/application/permission.py:43 -#: perms/serializers/asset/permission.py:47 rbac/serializers/role.py:26 +#: perms/serializers/permission.py:81 rbac/serializers/role.py:26 #: users/serializers/group.py:34 msgid "Users amount" msgstr "ユーザー数" -#: perms/serializers/application/permission.py:44 -#: perms/serializers/asset/permission.py:48 +#: perms/serializers/permission.py:82 msgid "User groups amount" msgstr "ユーザーグループの量" -#: perms/serializers/application/permission.py:45 -#: perms/serializers/asset/permission.py:51 -msgid "System users amount" -msgstr "システムユーザー数" - -#: perms/serializers/application/permission.py:79 -msgid "" -"The application list contains applications that are different from the " -"permission type. ({})" -msgstr "" -"アプリケーションリストには、権限タイプとは異なるアプリケーションが含まれてい" -"ます。({})" - -#: perms/serializers/asset/permission.py:21 -msgid "Users display" -msgstr "ユーザー表示" - -#: perms/serializers/asset/permission.py:22 -msgid "User groups display" -msgstr "ユーザーグループの表示" - -#: perms/serializers/asset/permission.py:23 -msgid "Assets display" -msgstr "資産表示" - -#: perms/serializers/asset/permission.py:24 -msgid "Nodes display" -msgstr "ノード表示" - -#: perms/serializers/asset/permission.py:25 -msgid "System users display" -msgstr "システムユーザーの表示" +#: perms/serializers/permission.py:84 +msgid "Nodes amount" +msgstr "ノード量" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 @@ -3263,15 +3215,7 @@ msgstr "" msgid "If you have any question, please contact the administrator" msgstr "質問があったら、管理者に連絡して下さい" -#: perms/tree/app.py:24 -msgid "My applications" -msgstr "私のアプリケーション" - -#: perms/tree/app.py:41 -msgid "Empty" -msgstr "空" - -#: perms/utils/asset/user_permission.py:620 rbac/tree.py:57 +#: perms/utils/user_permission.py:623 rbac/tree.py:57 msgid "My assets" msgstr "私の資産" @@ -3360,6 +3304,11 @@ msgstr "権限" msgid "Built-in" msgstr "内蔵" +#: rbac/models/role.py:46 rbac/models/rolebinding.py:44 +#: users/models/user.py:675 +msgid "Role" +msgstr "ロール" + #: rbac/models/role.py:144 msgid "System role" msgstr "システムの役割" @@ -3431,9 +3380,9 @@ msgstr "監査ビュー" msgid "System setting" msgstr "システム設定" -#: rbac/tree.py:37 -msgid "Accounts" -msgstr "アカウント" +#: rbac/tree.py:29 +msgid "Other" +msgstr "その他" #: rbac/tree.py:41 msgid "Session audits" @@ -3887,10 +3836,6 @@ msgstr "元の番号(Src id)" msgid "Business type(Service id)" msgstr "ビジネス・タイプ(Service id)" -#: settings/serializers/auth/sms.py:64 -msgid "Template" -msgstr "テンプレート" - #: settings/serializers/auth/sms.py:65 #, python-brace-format msgid "" @@ -3925,7 +3870,7 @@ msgid "SSO auth key TTL" msgstr "Token有効期間" #: settings/serializers/auth/sso.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:159 +#: xpack/plugins/cloud/serializers/account_attrs.py:169 msgid "Unit: second" msgstr "単位: 秒" @@ -4784,7 +4729,7 @@ msgstr "" msgid "Offline video player" msgstr "オフラインビデオプレーヤー" -#: terminal/api/endpoint.py:34 +#: terminal/api/endpoint.py:33 msgid "Not found protocol query params" msgstr "プロトコルクエリパラメータが見つかりません" @@ -4872,7 +4817,7 @@ msgstr "出力" #: terminal/backends/command/models.py:25 terminal/models/replay.py:9 #: terminal/models/sharing.py:19 terminal/models/sharing.py:78 #: terminal/templates/terminal/_msg_command_alert.html:10 -#: tickets/models/ticket/command_confirm.py:20 +#: tickets/models/ticket/command_confirm.py:17 msgid "Session" msgstr "セッション" @@ -4992,42 +4937,38 @@ msgstr "セッションのリプレイをアップロードできます" msgid "Can download session replay" msgstr "セッション再生をダウンロードできます" -#: terminal/models/session.py:50 terminal/models/sharing.py:101 +#: terminal/models/session.py:36 terminal/models/sharing.py:101 msgid "Login from" msgstr "ログイン元" -#: terminal/models/session.py:54 +#: terminal/models/session.py:40 msgid "Replay" msgstr "リプレイ" -#: terminal/models/session.py:59 +#: terminal/models/session.py:44 msgid "Date end" msgstr "終了日" -#: terminal/models/session.py:260 +#: terminal/models/session.py:236 msgid "Session record" msgstr "セッション記録" -#: terminal/models/session.py:262 +#: terminal/models/session.py:238 msgid "Can monitor session" msgstr "セッションを監視できます" -#: terminal/models/session.py:263 +#: terminal/models/session.py:239 msgid "Can share session" msgstr "セッションを共有できます" -#: terminal/models/session.py:264 +#: terminal/models/session.py:240 msgid "Can terminate session" msgstr "セッションを終了できます" -#: terminal/models/session.py:265 +#: terminal/models/session.py:241 msgid "Can validate session action perm" msgstr "セッションアクションのパーマを検証できます" -#: terminal/models/sharing.py:24 -msgid "Creator" -msgstr "作成者" - #: terminal/models/sharing.py:26 terminal/models/sharing.py:80 msgid "Verify code" msgstr "コードの確認" @@ -5068,11 +5009,6 @@ msgstr "参加日" msgid "Date left" msgstr "日付が残っています" -#: terminal/models/sharing.py:111 tickets/const.py:26 -#: xpack/plugins/change_auth_plan/models/base.py:192 -msgid "Finished" -msgstr "終了" - #: terminal/models/sharing.py:116 msgid "Session join record" msgstr "セッション参加記録" @@ -5121,14 +5057,6 @@ msgstr "コマンドストレージ" msgid "Replay storage" msgstr "再生ストレージ" -#: terminal/models/task.py:17 -msgid "Args" -msgstr "アルグ" - -#: terminal/models/task.py:18 -msgid "Kwargs" -msgstr "クワーグ" - #: terminal/models/terminal.py:103 msgid "type" msgstr "タイプ" @@ -5168,22 +5096,18 @@ msgstr "" "異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使用" "して実装します" -#: terminal/serializers/session.py:15 terminal/serializers/session.py:42 +#: terminal/serializers/session.py:17 terminal/serializers/session.py:42 msgid "Terminal display" msgstr "ターミナルディスプレイ" -#: terminal/serializers/session.py:32 +#: terminal/serializers/session.py:33 msgid "User ID" msgstr "ユーザーID" -#: terminal/serializers/session.py:33 +#: terminal/serializers/session.py:34 msgid "Asset ID" msgstr "資産ID" -#: terminal/serializers/session.py:34 -msgid "System user ID" -msgstr "システムユーザーID" - #: terminal/serializers/session.py:35 msgid "Login from display" msgstr "表示からのログイン" @@ -5200,6 +5124,10 @@ msgstr "参加できます" msgid "Terminal ID" msgstr "ターミナル ID" +#: terminal/serializers/session.py:40 +msgid "Is finished" +msgstr "終了しました" + #: terminal/serializers/session.py:41 msgid "Can terminate" msgstr "終了できます" @@ -5226,7 +5154,7 @@ msgstr "アクセスキー" msgid "Access key secret" msgstr "アクセスキーシークレット" -#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:216 msgid "Region" msgstr "リージョン" @@ -5258,6 +5186,10 @@ msgstr "ホスト無効" msgid "Port invalid" msgstr "ポートが無効" +#: terminal/serializers/storage.py:157 +msgid "Hosts" +msgstr "ホスト" + #: terminal/serializers/storage.py:160 msgid "Index by date" msgstr "日付による索引付け" @@ -5294,67 +5226,59 @@ msgstr "表示" msgid "Tickets" msgstr "チケット" -#: tickets/const.py:8 -msgid "General" -msgstr "一般" - #: tickets/const.py:10 msgid "Apply for asset" msgstr "資産の申請" -#: tickets/const.py:11 -msgid "Apply for application" -msgstr "申し込み" - -#: tickets/const.py:17 tickets/const.py:25 tickets/const.py:44 +#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 msgid "Open" msgstr "オープン" -#: tickets/const.py:18 tickets/const.py:31 +#: tickets/const.py:17 tickets/const.py:30 msgid "Approved" msgstr "承認済み" -#: tickets/const.py:19 tickets/const.py:32 +#: tickets/const.py:18 tickets/const.py:31 msgid "Rejected" msgstr "拒否" -#: tickets/const.py:21 tickets/const.py:34 +#: tickets/const.py:20 tickets/const.py:33 msgid "Reopen" msgstr "" -#: tickets/const.py:30 tickets/const.py:38 +#: tickets/const.py:29 tickets/const.py:37 msgid "Pending" msgstr "未定" -#: tickets/const.py:33 tickets/const.py:40 +#: tickets/const.py:32 tickets/const.py:39 msgid "Closed" msgstr "クローズ" -#: tickets/const.py:46 +#: tickets/const.py:45 msgid "Approve" msgstr "承認" -#: tickets/const.py:51 +#: tickets/const.py:50 msgid "One level" msgstr "1つのレベル" -#: tickets/const.py:52 +#: tickets/const.py:51 msgid "Two level" msgstr "2つのレベル" -#: tickets/const.py:56 +#: tickets/const.py:55 msgid "Super admin" msgstr "スーパー管理者" -#: tickets/const.py:57 +#: tickets/const.py:56 msgid "Org admin" msgstr "Org admin" -#: tickets/const.py:58 +#: tickets/const.py:57 msgid "Super admin and org admin" msgstr "スーパーadminとorg admin" -#: tickets/const.py:59 +#: tickets/const.py:58 msgid "Custom user" msgstr "カスタムユーザー" @@ -5362,15 +5286,7 @@ msgstr "カスタムユーザー" msgid "Ticket already closed" msgstr "チケットはすでに閉じています" -#: tickets/handlers/apply_application.py:38 -msgid "" -"Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " -"processor: {}, ticket ID: {}" -msgstr "" -"チケットによって作成されたチケットタイトル: {}、チケット申請者: {}、チケット" -"処理者: {}、チケットID: {}" - -#: tickets/handlers/apply_asset.py:37 +#: tickets/handlers/apply_asset.py:36 msgid "" "Created by the ticket ticket title: {} ticket applicant: {} ticket " "processor: {} ticket ID: {}" @@ -5406,11 +5322,6 @@ msgstr "応用ログイン都市" msgid "Applied login datetime" msgstr "適用されたログインの日付時間" -#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41 -#: tickets/models/ticket/general.py:277 -msgid "State" -msgstr "状態" - #: tickets/models/comment.py:14 msgid "common" msgstr "" @@ -5448,17 +5359,16 @@ msgstr "チケットの流れ" msgid "Ticket session relation" msgstr "チケットセッションの関係" -#: tickets/models/ticket/apply_application.py:12 +#: tickets/models/ticket/apply_application.py:11 #: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" msgstr "認可ルール名" -#: tickets/models/ticket/apply_application.py:21 +#: tickets/models/ticket/apply_application.py:20 msgid "Apply applications" msgstr "アプリケーションの適用" -#: tickets/models/ticket/apply_application.py:24 -#: tickets/models/ticket/apply_asset.py:18 +#: tickets/models/ticket/apply_application.py:23 msgid "Apply system users" msgstr "システムユーザーの適用" @@ -5475,6 +5385,12 @@ msgstr "ノードの適用" msgid "Apply assets" msgstr "資産の適用" +#: tickets/models/ticket/apply_asset.py:17 +#, fuzzy +#| msgid "Application account" +msgid "Apply accounts" +msgstr "アプリケーションアカウント" + #: tickets/models/ticket/command_confirm.py:10 msgid "Run user" msgstr "ユーザーの実行" @@ -5483,19 +5399,21 @@ msgstr "ユーザーの実行" msgid "Run asset" msgstr "アセットの実行" -#: tickets/models/ticket/command_confirm.py:15 -msgid "Run system user" -msgstr "システムユーザーの実行" +#: tickets/models/ticket/command_confirm.py:13 +#, fuzzy +#| msgid "account" +msgid "Run account" +msgstr "アカウント" -#: tickets/models/ticket/command_confirm.py:17 +#: tickets/models/ticket/command_confirm.py:14 msgid "Run command" msgstr "実行コマンド" -#: tickets/models/ticket/command_confirm.py:24 +#: tickets/models/ticket/command_confirm.py:21 msgid "From cmd filter" msgstr "コマンドフィルタ規則から" -#: tickets/models/ticket/command_confirm.py:28 +#: tickets/models/ticket/command_confirm.py:25 msgid "From cmd filter rule" msgstr "コマンドフィルタ規則から" @@ -5543,9 +5461,11 @@ msgstr "ログインユーザー" msgid "Login asset" msgstr "ログイン資産" -#: tickets/models/ticket/login_asset_confirm.py:20 -msgid "Login system user" -msgstr "ログインシステムユーザー" +#: tickets/models/ticket/login_asset_confirm.py:19 +#, fuzzy +#| msgid "Login acl" +msgid "Login account" +msgstr "ログインacl" #: tickets/models/ticket/login_confirm.py:12 msgid "Login datetime" @@ -5591,16 +5511,16 @@ msgstr "現在の組織タイプは既に存在します。" msgid "Processor" msgstr "プロセッサ" -#: tickets/serializers/ticket/common.py:16 -#: tickets/serializers/ticket/common.py:79 +#: tickets/serializers/ticket/common.py:15 +#: tickets/serializers/ticket/common.py:77 msgid "Created by ticket ({}-{})" msgstr "チケットで作成 ({}-{})" -#: tickets/serializers/ticket/common.py:69 +#: tickets/serializers/ticket/common.py:67 msgid "The expiration date should be greater than the start date" msgstr "有効期限は開始日より大きくする必要があります" -#: tickets/serializers/ticket/common.py:85 +#: tickets/serializers/ticket/common.py:83 msgid "Permission named `{}` already exists" msgstr "'{}'という名前の権限は既に存在します" @@ -5621,7 +5541,7 @@ msgid "Ticket information" msgstr "作業指示情報" #: tickets/templates/tickets/approve_check_password.html:29 -#: tickets/views/approve.py:39 +#: tickets/views/approve.py:38 msgid "Ticket approval" msgstr "作業指示の承認" @@ -5633,26 +5553,26 @@ msgstr "承認" msgid "Go Login" msgstr "ログイン" -#: tickets/views/approve.py:40 +#: tickets/views/approve.py:39 msgid "" "This ticket does not exist, the process has ended, or this link has expired" msgstr "" "このワークシートが存在しないか、ワークシートが終了したか、このリンクが無効に" "なっています" -#: tickets/views/approve.py:69 +#: tickets/views/approve.py:68 msgid "Click the button below to approve or reject" msgstr "下のボタンをクリックして同意または拒否。" -#: tickets/views/approve.py:71 +#: tickets/views/approve.py:70 msgid "After successful authentication, this ticket can be approved directly" msgstr "認証に成功した後、作業指示書は直接承認することができる。" -#: tickets/views/approve.py:93 +#: tickets/views/approve.py:92 msgid "Illegal approval action" msgstr "無効な承認アクション" -#: tickets/views/approve.py:106 +#: tickets/views/approve.py:105 msgid "This user is not authorized to approve this ticket" msgstr "このユーザーはこの作業指示を承認する権限がありません" @@ -5660,6 +5580,10 @@ msgstr "このユーザーはこの作業指示を承認する権限がありま msgid "Could not reset self otp, use profile reset instead" msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用" +#: users/apps.py:9 +msgid "Users" +msgstr "ユーザー" + #: users/const.py:10 msgid "System administrator" msgstr "システム管理者" @@ -5787,6 +5711,14 @@ msgstr "アバター" msgid "Wechat" msgstr "微信" +#: users/models/user.py:685 +msgid "Phone" +msgstr "電話" + +#: users/models/user.py:693 +msgid "Private key" +msgstr "ssh秘密鍵" + #: users/models/user.py:699 msgid "Secret key" msgstr "秘密キー" @@ -5803,27 +5735,27 @@ msgstr "最終更新日パスワード" msgid "Need update password" msgstr "更新パスワードが必要" -#: users/models/user.py:896 +#: users/models/user.py:897 msgid "Can invite user" msgstr "ユーザーを招待できます" -#: users/models/user.py:897 +#: users/models/user.py:898 msgid "Can remove user" msgstr "ユーザーを削除できます" -#: users/models/user.py:898 +#: users/models/user.py:899 msgid "Can match user" msgstr "ユーザーに一致できます" -#: users/models/user.py:907 +#: users/models/user.py:908 msgid "Administrator" msgstr "管理者" -#: users/models/user.py:910 +#: users/models/user.py:911 msgid "Administrator is the super user of system" msgstr "管理者はシステムのスーパーユーザーです" -#: users/models/user.py:935 +#: users/models/user.py:936 msgid "User password history" msgstr "ユーザーパスワード履歴" @@ -5894,12 +5826,6 @@ msgstr "システムロール表示" msgid "Org roles display" msgstr "組織ロール表示" -#: users/serializers/user.py:81 -#: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:27 -msgid "Password strategy" -msgstr "パスワード戦略" - #: users/serializers/user.py:83 msgid "MFA enabled" msgstr "MFA有効化" @@ -6058,10 +5984,6 @@ msgstr "パスワードを満たす必要があります" msgid "Password strength" msgstr "パスワードの強さ" -#: users/templates/users/reset_password.html:29 -msgid "Setting" -msgstr "設定" - #: users/templates/users/reset_password.html:48 msgid "Very weak" msgstr "非常に弱い" @@ -6230,123 +6152,97 @@ msgstr "パスワードの成功をリセットし、ログインページに戻 msgid "XPACK" msgstr "XPack" -#: xpack/plugins/change_auth_plan/api/app.py:112 -#: xpack/plugins/change_auth_plan/api/asset.py:95 +#: xpack/plugins/change_auth_plan/api/asset.py:94 msgid "The parameter 'action' must be [{}]" msgstr "パラメータ 'action' は [{}] でなければなりません。" #: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models/asset.py:123 +#: xpack/plugins/change_auth_plan/models/asset.py:124 msgid "Change auth plan" msgstr "密かな計画" -#: xpack/plugins/change_auth_plan/models/app.py:46 -#: xpack/plugins/change_auth_plan/models/app.py:95 +#: xpack/plugins/change_auth_plan/models/app.py:45 +#: xpack/plugins/change_auth_plan/models/app.py:94 msgid "Application change auth plan" msgstr "改密計画の適用" -#: xpack/plugins/change_auth_plan/models/app.py:99 -#: xpack/plugins/change_auth_plan/models/app.py:151 +#: xpack/plugins/change_auth_plan/models/app.py:98 +#: xpack/plugins/change_auth_plan/models/app.py:150 msgid "Application change auth plan execution" msgstr "改密計画実行の適用" -#: xpack/plugins/change_auth_plan/models/app.py:144 -#: xpack/plugins/change_auth_plan/serializers/app.py:64 +#: xpack/plugins/change_auth_plan/models/app.py:143 msgid "App" msgstr "適用" -#: xpack/plugins/change_auth_plan/models/app.py:156 +#: xpack/plugins/change_auth_plan/models/app.py:155 msgid "Application change auth plan task" msgstr "改密計画タスクの適用" -#: xpack/plugins/change_auth_plan/models/app.py:180 -#: xpack/plugins/change_auth_plan/models/asset.py:263 +#: xpack/plugins/change_auth_plan/models/app.py:179 +#: xpack/plugins/change_auth_plan/models/asset.py:264 msgid "Password cannot be set to blank, exit. " msgstr "パスワードを空白に設定することはできません。" -#: xpack/plugins/change_auth_plan/models/asset.py:29 -msgid "Append SSH KEY" -msgstr "追加" - -#: xpack/plugins/change_auth_plan/models/asset.py:30 -msgid "Empty and append SSH KEY" -msgstr "すべてクリアして追加" - -#: xpack/plugins/change_auth_plan/models/asset.py:31 -msgid "Replace (The key generated by JumpServer) " -msgstr "置換(JumpServerによって生成された鍵)" - -#: xpack/plugins/change_auth_plan/models/asset.py:49 -#: xpack/plugins/change_auth_plan/serializers/asset.py:36 +#: xpack/plugins/change_auth_plan/models/asset.py:50 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "SSH Key strategy" msgstr "SSHキー戦略" -#: xpack/plugins/change_auth_plan/models/asset.py:67 +#: xpack/plugins/change_auth_plan/models/asset.py:68 msgid "Asset change auth plan" msgstr "資産変更のオースプラン" -#: xpack/plugins/change_auth_plan/models/asset.py:134 +#: xpack/plugins/change_auth_plan/models/asset.py:135 msgid "Asset change auth plan execution" msgstr "資産変更のオースプランの実行" -#: xpack/plugins/change_auth_plan/models/asset.py:210 +#: xpack/plugins/change_auth_plan/models/asset.py:211 msgid "Change auth plan execution" msgstr "改密計画の実行" -#: xpack/plugins/change_auth_plan/models/asset.py:217 +#: xpack/plugins/change_auth_plan/models/asset.py:218 msgid "Asset change auth plan task" msgstr "資産改密計画タスク" -#: xpack/plugins/change_auth_plan/models/asset.py:252 +#: xpack/plugins/change_auth_plan/models/asset.py:253 msgid "This asset does not have a privileged user set: " msgstr "このアセットには特権ユーザーセットがありません。" -#: xpack/plugins/change_auth_plan/models/asset.py:258 +#: xpack/plugins/change_auth_plan/models/asset.py:259 msgid "" "The password and key of the current asset privileged user cannot be changed: " msgstr "現在のアセット特権ユーザーのパスワードとキーは変更できません。" -#: xpack/plugins/change_auth_plan/models/asset.py:269 +#: xpack/plugins/change_auth_plan/models/asset.py:270 msgid "Public key cannot be set to null, exit. " msgstr "公開鍵をnull、exitに設定することはできません。" -#: xpack/plugins/change_auth_plan/models/base.py:28 -msgid "All assets use the same random password" -msgstr "すべての資産は同じランダムパスワードを使用します" - -#: xpack/plugins/change_auth_plan/models/base.py:29 -msgid "All assets use different random password" -msgstr "すべての資産は異なるランダムパスワードを使用します" - -#: xpack/plugins/change_auth_plan/models/base.py:39 -msgid "Password rules" -msgstr "パスワードルール" - -#: xpack/plugins/change_auth_plan/models/base.py:118 +#: xpack/plugins/change_auth_plan/models/base.py:114 msgid "Change auth plan snapshot" msgstr "計画スナップショットの暗号化" -#: xpack/plugins/change_auth_plan/models/base.py:187 +#: xpack/plugins/change_auth_plan/models/base.py:183 msgid "Ready" msgstr "の準備を" -#: xpack/plugins/change_auth_plan/models/base.py:188 +#: xpack/plugins/change_auth_plan/models/base.py:184 msgid "Preflight check" msgstr "プリフライトチェック" -#: xpack/plugins/change_auth_plan/models/base.py:189 +#: xpack/plugins/change_auth_plan/models/base.py:185 msgid "Change auth" msgstr "秘密を改める" -#: xpack/plugins/change_auth_plan/models/base.py:190 +#: xpack/plugins/change_auth_plan/models/base.py:186 msgid "Verify auth" msgstr "パスワード/キーの確認" -#: xpack/plugins/change_auth_plan/models/base.py:191 +#: xpack/plugins/change_auth_plan/models/base.py:187 msgid "Keep auth" msgstr "パスワード/キーの保存" -#: xpack/plugins/change_auth_plan/models/base.py:199 +#: xpack/plugins/change_auth_plan/models/base.py:195 msgid "Step" msgstr "ステップ" @@ -6369,11 +6265,11 @@ msgstr "" "{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" "情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +#: xpack/plugins/change_auth_plan/serializers/asset.py:30 msgid "Change Password" msgstr "パスワードの変更" -#: xpack/plugins/change_auth_plan/serializers/asset.py:34 +#: xpack/plugins/change_auth_plan/serializers/asset.py:31 msgid "Change SSH Key" msgstr "SSHキーの変更" @@ -6509,75 +6405,71 @@ msgstr "リリース済み" msgid "Cloud center" msgstr "クラウドセンター" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:32 msgid "Provider" msgstr "プロバイダー" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:41 msgid "Cloud account" msgstr "クラウドアカウント" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:43 msgid "Test cloud account" msgstr "クラウドアカウントのテスト" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:67 -msgid "Account" -msgstr "アカウント" - -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:38 +#: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "リージョン" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:93 msgid "Hostname strategy" msgstr "ホスト名戦略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:66 msgid "Unix admin user" msgstr "Unix adminユーザー" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:67 msgid "Windows admin user" msgstr "Windows管理者" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:46 +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:44 msgid "IP network segment group" msgstr "IPネットワークセグメントグループ" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:72 +#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:70 msgid "Always update" msgstr "常に更新" -#: xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/models.py:121 msgid "Date last sync" msgstr "最終同期日" -#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:126 xpack/plugins/cloud/models.py:167 msgid "Sync instance task" msgstr "インスタンスの同期タスク" -#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 +#: xpack/plugins/cloud/models.py:178 xpack/plugins/cloud/models.py:226 msgid "Date sync" msgstr "日付の同期" -#: xpack/plugins/cloud/models.py:186 +#: xpack/plugins/cloud/models.py:182 msgid "Sync instance task execution" msgstr "インスタンスタスクの同期実行" -#: xpack/plugins/cloud/models.py:210 +#: xpack/plugins/cloud/models.py:206 msgid "Sync task" msgstr "同期タスク" -#: xpack/plugins/cloud/models.py:214 +#: xpack/plugins/cloud/models.py:210 msgid "Sync instance task history" msgstr "インスタンスタスク履歴の同期" -#: xpack/plugins/cloud/models.py:217 +#: xpack/plugins/cloud/models.py:213 msgid "Instance" msgstr "インスタンス" -#: xpack/plugins/cloud/models.py:234 +#: xpack/plugins/cloud/models.py:230 msgid "Sync instance detail" msgstr "同期インスタンスの詳細" @@ -6793,7 +6685,7 @@ msgstr "サブスクリプションID" #: xpack/plugins/cloud/serializers/account_attrs.py:95 #: xpack/plugins/cloud/serializers/account_attrs.py:100 -#: xpack/plugins/cloud/serializers/account_attrs.py:124 +#: xpack/plugins/cloud/serializers/account_attrs.py:134 msgid "API Endpoint" msgstr "APIエンドポイント" @@ -6809,25 +6701,25 @@ msgstr "例えば: http://openstack.example.com:5000/v3" msgid "User domain" msgstr "ユーザードメイン" -#: xpack/plugins/cloud/serializers/account_attrs.py:117 +#: xpack/plugins/cloud/serializers/account_attrs.py:127 msgid "Service account key" msgstr "サービスアカウントキー" -#: xpack/plugins/cloud/serializers/account_attrs.py:118 +#: xpack/plugins/cloud/serializers/account_attrs.py:128 msgid "The file is in JSON format" msgstr "ファイルはJSON形式です。" -#: xpack/plugins/cloud/serializers/account_attrs.py:131 +#: xpack/plugins/cloud/serializers/account_attrs.py:141 msgid "IP address invalid `{}`, {}" msgstr "IPアドレスが無効: '{}', {}" -#: xpack/plugins/cloud/serializers/account_attrs.py:137 +#: xpack/plugins/cloud/serializers/account_attrs.py:147 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" msgstr "形式はコンマ区切りの文字列です,例:192.168.1.0/24,10.0.0.0-10.0.0.255" -#: xpack/plugins/cloud/serializers/account_attrs.py:141 +#: xpack/plugins/cloud/serializers/account_attrs.py:151 msgid "" "The port is used to detect the validity of the IP address. When the " "synchronization task is executed, only the valid IP address will be " @@ -6837,23 +6729,23 @@ msgstr "" "実行されると、有効な IP アドレスのみが同期されます。
ポートが0の場合、す" "べてのIPアドレスが有効です。" -#: xpack/plugins/cloud/serializers/account_attrs.py:149 +#: xpack/plugins/cloud/serializers/account_attrs.py:159 msgid "Hostname prefix" msgstr "ホスト名プレフィックス" -#: xpack/plugins/cloud/serializers/account_attrs.py:152 +#: xpack/plugins/cloud/serializers/account_attrs.py:162 msgid "IP segment" msgstr "IP セグメント" -#: xpack/plugins/cloud/serializers/account_attrs.py:156 +#: xpack/plugins/cloud/serializers/account_attrs.py:166 msgid "Test port" msgstr "テストポート" -#: xpack/plugins/cloud/serializers/account_attrs.py:159 +#: xpack/plugins/cloud/serializers/account_attrs.py:169 msgid "Test timeout" msgstr "テストタイムアウト" -#: xpack/plugins/cloud/serializers/task.py:29 +#: xpack/plugins/cloud/serializers/task.py:28 msgid "" "Only instances matching the IP range will be synced.
If the instance " "contains multiple IP addresses, the first IP address that matches will be " @@ -6867,19 +6759,19 @@ msgstr "" "ドレスをランダムに一致させることを意味します。
形式はコンマ区切りの文字列" "です。例:192.168.1.0/24,10.1.1.1-10.1.1.20" -#: xpack/plugins/cloud/serializers/task.py:36 +#: xpack/plugins/cloud/serializers/task.py:35 msgid "History count" msgstr "実行回数" -#: xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/serializers/task.py:36 msgid "Instance count" msgstr "インスタンス数" -#: xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/serializers/task.py:64 msgid "Linux admin user" msgstr "Linux管理者" -#: xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/serializers/task.py:69 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定期的な表示" @@ -6892,15 +6784,15 @@ msgstr "利用できないアカウント" msgid "Gathered user" msgstr "収集されたユーザー" -#: xpack/plugins/gathered_user/models.py:39 +#: xpack/plugins/gathered_user/models.py:34 msgid "Gather user task" msgstr "ユーザータスクの収集" -#: xpack/plugins/gathered_user/models.py:85 +#: xpack/plugins/gathered_user/models.py:80 msgid "gather user task execution" msgstr "ユーザータスクの実行を収集" -#: xpack/plugins/gathered_user/models.py:91 +#: xpack/plugins/gathered_user/models.py:86 msgid "Assets is empty, please change nodes" msgstr "資産は空です。ノードを変更してください" @@ -6971,3 +6863,412 @@ msgstr "究極のエディション" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "コミュニティ版" + +#~ msgid "System User" +#~ msgstr "システムユーザー" + +#~ msgid "Unsupported protocols: {}" +#~ msgstr "サポートされていないプロトコル: {}" + +#~ msgid "Remote app" +#~ msgstr "リモートアプリ" + +#~ msgid "Custom" +#~ msgstr "カスタム" + +#~ msgid "Can view application account secret" +#~ msgstr "アプリケーションアカウントの秘密を表示できます" + +#~ msgid "Can change application account secret" +#~ msgstr "アプリケーションアカウントの秘密を変更できます" + +#~ msgid "Application user" +#~ msgstr "アプリケーションユーザー" + +#~ msgid "Application display" +#~ msgstr "アプリケーション表示" + +#~ msgid "Cluster" +#~ msgstr "クラスター" + +#~ msgid "Asset Info" +#~ msgstr "資産情報" + +#~ msgid "Application path" +#~ msgstr "アプリケーションパス" + +#~ msgid "Target URL" +#~ msgstr "ターゲットURL" + +#~ msgid "Chrome username" +#~ msgstr "Chromeユーザー名" + +#~ msgid "Chrome password" +#~ msgstr "Chromeパスワード" + +#~ msgid "Operating parameter" +#~ msgstr "操作パラメータ" + +#~ msgid "Target url" +#~ msgstr "ターゲットURL" + +#~ msgid "Custom Username" +#~ msgstr "カスタムユーザー名" + +#~ msgid "Mysql workbench username" +#~ msgstr "Mysql workbench のユーザー名" + +#~ msgid "Mysql workbench password" +#~ msgstr "Mysql workbench パスワード" + +#~ msgid "Magnus currently supports only 11g and 12c connections" +#~ msgstr "" +#~ "現在、Magnusは11gおよび12cバージョンへの接続のみをサポートしています" + +#~ msgid "Vmware username" +#~ msgstr "Vmware ユーザー名" + +#~ msgid "Vmware password" +#~ msgstr "Vmware パスワード" + +#~ msgid "Base" +#~ msgstr "ベース" + +#~ msgid "Public IP" +#~ msgstr "パブリックIP" + +#~ msgid "AuthBook" +#~ msgstr "資産アカウント" + +#~ msgid "Can test asset account connectivity" +#~ msgstr "アセットアカウントの接続性をテストできます" + +#~ msgid "Bandwidth" +#~ msgstr "帯域幅" + +#~ msgid "Contact" +#~ msgstr "連絡先" + +#~ msgid "Intranet" +#~ msgstr "イントラネット" + +#~ msgid "Extranet" +#~ msgstr "エクストラネット" + +#~ msgid "Operator" +#~ msgstr "オペレーター" + +#~ msgid "Default Cluster" +#~ msgstr "デフォルトクラスター" + +#~ msgid "User groups" +#~ msgstr "ユーザーグループ" + +#~ msgid "System user display" +#~ msgstr "システムユーザー表示" + +#~ msgid "Protocol format should {}/{}" +#~ msgstr "プロトコル形式は {}/{}" + +#~ msgid "Nodes name" +#~ msgstr "ノード名" + +#~ msgid "Labels name" +#~ msgstr "ラベル名" + +#~ msgid "Hardware info" +#~ msgstr "ハードウェア情報" + +#~ msgid "Admin user display" +#~ msgstr "管理者ユーザー表示" + +#~ msgid "CPU info" +#~ msgstr "CPU情報" + +#~ msgid "Action display" +#~ msgstr "アクション表示" + +#~ msgid "Applications amount" +#~ msgstr "申し込み金額" + +#~ msgid "SSH key fingerprint" +#~ msgstr "SSHキー指紋" + +#~ msgid "Apps amount" +#~ msgstr "アプリの量" + +#~ msgid "Login mode display" +#~ msgstr "ログインモード表示" + +#~ msgid "Ad domain" +#~ msgstr "広告ドメイン" + +#~ msgid "Is asset protocol" +#~ msgstr "資産プロトコルです" + +#~ msgid "Only ssh and automatic login system users are supported" +#~ msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" + +#~ msgid "Username same with user with protocol {} only allow 1" +#~ msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" + +#~ msgid "* Automatic login mode must fill in the username." +#~ msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" + +#~ msgid "Path should starts with /" +#~ msgstr "パスは/で始まる必要があります" + +#~ msgid "Password or private key required" +#~ msgstr "パスワードまたは秘密鍵が必要" + +#~ msgid "Only ssh protocol system users are allowed" +#~ msgstr "Sshプロトコルシステムユーザーのみが許可されています" + +#~ msgid "The protocol must be consistent with the current user: {}" +#~ msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" + +#~ msgid "Only system users with automatic login are allowed" +#~ msgstr "自動ログインを持つシステムユーザーのみが許可されます" + +#~ msgid "System user name" +#~ msgstr "システムユーザー名" + +#~ msgid "Asset hostname" +#~ msgstr "資産ホスト名" + +#~ msgid "System user is dynamic: {}" +#~ msgstr "システムユーザーは動的です: {}" + +#~ msgid "Start push system user for platform: [{}]" +#~ msgstr "プラットフォームのプッシュシステムユーザーを開始: [{}]" + +#~ msgid "Hosts count: {}" +#~ msgstr "ホスト数: {}" + +#~ msgid "Push system users to assets: " +#~ msgstr "システムユーザーを資産にプッシュする:" + +#~ msgid "Push system users to asset: " +#~ msgstr "システムユーザーをアセットにプッシュする:" + +#~ msgid "Dynamic system user not support test" +#~ msgstr "動的システムユーザーがテストをサポートしていない" + +#~ msgid "Start test system user connectivity for platform: [{}]" +#~ msgstr "プラットフォームのテストシステムのユーザー接続を開始: [{}]" + +#~ msgid "Test system user connectivity: " +#~ msgstr "テストシステムユーザー接続:" + +#~ msgid "Test system user connectivity period: " +#~ msgstr "テストシステムユーザー接続期间:" + +#~ msgid "Hosts display" +#~ msgstr "ホスト表示" + +#~ msgid "Run as" +#~ msgstr "として実行" + +#~ msgid "Run as display" +#~ msgstr "ディスプレイとして実行する" + +#~ msgid "Asset and SystemUser" +#~ msgstr "資産およびシステム・ユーザー" + +#, python-brace-format +#~ msgid "{Asset} ADD {SystemUser}" +#~ msgstr "{Asset} 追加 {SystemUser}" + +#, python-brace-format +#~ msgid "{Asset} REMOVE {SystemUser}" +#~ msgstr "{Asset} 削除 {SystemUser}" + +#~ msgid "Asset permission and SystemUser" +#~ msgstr "資産権限とSystemUser" + +#, python-brace-format +#~ msgid "{AssetPermission} ADD {SystemUser}" +#~ msgstr "{AssetPermission} 追加 {SystemUser}" + +#, python-brace-format +#~ msgid "{AssetPermission} REMOVE {SystemUser}" +#~ msgstr "{AssetPermission} 削除 {SystemUser}" + +#~ msgid "User application permissions" +#~ msgstr "ユーザーアプリケーションの権限" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {User}" +#~ msgstr "{ApplicationPermission} 追加 {User}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {User}" +#~ msgstr "{ApplicationPermission} 削除 {User}" + +#~ msgid "User group application permissions" +#~ msgstr "ユーザーグループアプリケーションの権限" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {UserGroup}" +#~ msgstr "{ApplicationPermission} 追加 {UserGroup}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {UserGroup}" +#~ msgstr "{ApplicationPermission} 削除 {UserGroup}" + +#~ msgid "Application permission" +#~ msgstr "申請許可" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {Application}" +#~ msgstr "{ApplicationPermission} 追加 {Application}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {Application}" +#~ msgstr "{ApplicationPermission} 削除 {Application}" + +#~ msgid "Application permission and SystemUser" +#~ msgstr "アプリケーション権限とSystemUser" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {SystemUser}" +#~ msgstr "{ApplicationPermission} 追加 {SystemUser}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {SystemUser}" +#~ msgstr "{ApplicationPermission} 削除 {SystemUser}" + +#~ msgid "Not has host {} permission" +#~ msgstr "ホスト {} 権限がありません" + +#~ msgid "" +#~ "eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " +#~ "crontab expressions (Online tools)
Note: If both Regularly " +#~ "perform and Cycle perform are set, give priority to Regularly perform" +#~ msgstr "" +#~ "eg:毎週日03:05<5 3**0>
ヒント:5ビットLinux crontab式<分時日月曜日>(オンラインワーク)
" +#~ "注意:定期実行と周期実行を同時に設定した場合は、定期実行を優先します。" + +#~ msgid "Unit: hour" +#~ msgstr "単位: 時間" + +#~ msgid "Callback" +#~ msgstr "コールバック" + +#~ msgid "Can view task monitor" +#~ msgstr "タスクモニターを表示できます" + +#~ msgid "Tasks" +#~ msgstr "タスク" + +#~ msgid "Options" +#~ msgstr "オプション" + +#~ msgid "Run as admin" +#~ msgstr "再実行" + +#~ msgid "Become" +#~ msgstr "になる" + +#~ msgid "Create by" +#~ msgstr "による作成" + +#~ msgid "AdHoc" +#~ msgstr "タスクの各バージョン" + +#~ msgid "Task display" +#~ msgstr "タスク表示" + +#~ msgid "Host amount" +#~ msgstr "ホスト量" + +#~ msgid "Start time" +#~ msgstr "開始時間" + +#~ msgid "End time" +#~ msgstr "終了時間" + +#~ msgid "Adhoc raw result" +#~ msgstr "アドホック生の結果" + +#~ msgid "Adhoc result summary" +#~ msgstr "アドホック結果の概要" + +#~ msgid "Task start" +#~ msgstr "タスクの開始" + +#~ msgid "Command `{}` is forbidden ........" +#~ msgstr "コマンド '{}' は禁止されています ........" + +#~ msgid "Task end" +#~ msgstr "タスク" + +#~ msgid "The administrator is modifying permissions. Please wait" +#~ msgstr "管理者は権限を変更しています。お待ちください" + +#~ msgid "The authorization cannot be revoked for the time being" +#~ msgstr "当分の間、承認を取り消すことはできません。" + +#~ msgid "Permed application" +#~ msgstr "許可されたアプリケーション" + +#~ msgid "Can view my apps" +#~ msgstr "自分のアプリを表示できます" + +#~ msgid "Can view user apps" +#~ msgstr "ユーザーアプリを表示できます" + +#~ msgid "Can view usergroup apps" +#~ msgstr "ユーザー・グループ認可の適用を表示できます" + +#~ msgid "Your permed applications is about to expire" +#~ msgstr "パーマアプリケーションの有効期限が近づいています" + +#~ msgid "permed applications" +#~ msgstr "Permedアプリケーション" + +#~ msgid "Application permissions is about to expire" +#~ msgstr "アプリケーション権限の有効期限が近づいています" + +#~ msgid "application permissions of organization {}" +#~ msgstr "Organization {} のアプリケーション権限" + +#~ msgid "System users amount" +#~ msgstr "システムユーザー数" + +#~ msgid "" +#~ "The application list contains applications that are different from the " +#~ "permission type. ({})" +#~ msgstr "" +#~ "アプリケーションリストには、権限タイプとは異なるアプリケーションが含まれて" +#~ "います。({})" + +#~ msgid "System users display" +#~ msgstr "システムユーザーの表示" + +#~ msgid "My applications" +#~ msgstr "私のアプリケーション" + +#~ msgid "Empty" +#~ msgstr "空" + +#~ msgid "System user ID" +#~ msgstr "システムユーザーID" + +#~ msgid "Apply for application" +#~ msgstr "申し込み" + +#~ msgid "" +#~ "Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " +#~ "processor: {}, ticket ID: {}" +#~ msgstr "" +#~ "チケットによって作成されたチケットタイトル: {}、チケット申請者: {}、チケッ" +#~ "ト処理者: {}、チケットID: {}" + +#~ msgid "Run system user" +#~ msgstr "システムユーザーの実行" + +#~ msgid "Login system user" +#~ msgstr "ログインシステムユーザー" diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index da00d3ee0..f8d2feba7 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-08-17 16:28+0800\n" +"POT-Creation-Date: 2022-10-19 10:41+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -21,61 +21,66 @@ msgstr "" msgid "Acls" msgstr "访问控制" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 -#: applications/models/application.py:220 assets/models/asset.py:138 -#: assets/models/base.py:175 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:27 assets/models/domain.py:23 -#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 -#: orgs/models.py:70 perms/models/base.py:83 rbac/models/role.py:29 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 +#: applications/models.py:10 assets/models/_user.py:33 +#: assets/models/asset/common.py:81 assets/models/asset/common.py:91 +#: assets/models/base.py:65 assets/models/cmd_filter.py:25 +#: assets/models/domain.py:24 assets/models/group.py:20 +#: assets/models/label.py:17 assets/models/platform.py:22 +#: assets/models/platform.py:68 assets/serializers/asset/common.py:85 +#: assets/serializers/platform.py:104 ops/mixin.py:22 ops/models/playbook.py:9 +#: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86 #: terminal/models/storage.py:26 terminal/models/task.py:16 #: terminal/models/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 -#: xpack/plugins/cloud/models.py:28 +#: xpack/plugins/cloud/models.py:30 msgid "Name" msgstr "名称" -#: acls/models/base.py:27 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:89 +#: acls/models/base.py:27 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:89 msgid "Priority" msgstr "优先级" -#: acls/models/base.py:28 assets/models/cmd_filter.py:84 -#: assets/models/user.py:251 terminal/models/endpoint.py:90 +#: acls/models/base.py:28 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:90 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" -#: acls/models/base.py:31 authentication/models.py:21 +#: acls/models/base.py:31 authentication/models.py:22 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/base.py:88 terminal/models/sharing.py:28 tickets/const.py:39 +#: perms/models/asset_permission.py:74 terminal/models/sharing.py:28 +#: tickets/const.py:38 msgid "Active" msgstr "激活中" -#: acls/models/base.py:32 applications/models/application.py:233 -#: assets/models/asset.py:143 assets/models/asset.py:231 -#: assets/models/backup.py:54 assets/models/base.py:180 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:48 -#: assets/models/cmd_filter.py:96 assets/models/domain.py:24 -#: assets/models/domain.py:65 assets/models/group.py:23 -#: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:73 -#: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:38 -#: terminal/models/endpoint.py:23 terminal/models/endpoint.py:96 -#: terminal/models/storage.py:29 terminal/models/terminal.py:114 -#: tickets/models/comment.py:32 tickets/models/ticket/general.py:288 -#: users/models/group.py:16 users/models/user.py:702 -#: xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:116 +#: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 +#: assets/models/asset/common.py:101 assets/models/automations/base.py:33 +#: assets/models/backup.py:30 assets/models/base.py:70 +#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 +#: assets/models/domain.py:25 assets/models/domain.py:69 +#: assets/models/group.py:23 assets/models/label.py:22 +#: assets/models/platform.py:73 ops/models/playbook.py:11 +#: ops/models/playbook.py:25 orgs/models.py:73 +#: perms/models/asset_permission.py:84 rbac/models/role.py:37 +#: settings/models.py:38 terminal/models/endpoint.py:23 +#: terminal/models/endpoint.py:96 terminal/models/storage.py:29 +#: terminal/models/terminal.py:114 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:288 users/models/group.py:16 +#: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 +#: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" -#: acls/models/login_acl.py:18 tickets/const.py:47 +#: acls/models/login_acl.py:18 tickets/const.py:46 #: tickets/templates/tickets/approve_check_password.html:49 msgid "Reject" msgstr "拒绝" -#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:75 +#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:67 msgid "Allow" msgstr "允许" @@ -85,15 +90,15 @@ msgid "Login confirm" msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 -#: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:62 audits/models.py:87 audits/serializers.py:100 -#: authentication/models.py:54 authentication/models.py:78 orgs/models.py:220 -#: perms/models/base.py:84 rbac/builtin.py:120 rbac/models/rolebinding.py:41 +#: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 +#: audits/models.py:62 audits/models.py:87 authentication/models.py:55 +#: authentication/models.py:79 perms/models/asset_permission.py:58 +#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:30 #: terminal/models/sharing.py:33 terminal/notifications.py:91 #: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14 -#: users/models/user.py:894 users/models/user.py:925 +#: users/models/user.py:895 users/models/user.py:926 #: users/serializers/group.py:19 msgid "User" msgstr "用户" @@ -103,14 +108,14 @@ msgid "Rule" msgstr "规则" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:75 -#: assets/models/cmd_filter.py:89 audits/models.py:63 audits/serializers.py:51 +#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:62 +#: assets/models/cmd_filter.py:81 audits/models.py:63 audits/serializers.py:49 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "动作" #: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32 -#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:94 +#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:86 msgid "Reviewers" msgstr "审批人" @@ -118,25 +123,24 @@ msgstr "审批人" msgid "Login acl" msgstr "登录访问控制" -#: acls/models/login_asset_acl.py:21 -#: applications/serializers/application.py:122 -#: applications/serializers/application.py:167 -msgid "System User" -msgstr "系统用户" +#: acls/models/login_asset_acl.py:21 assets/models/account.py:48 +#: authentication/models.py:88 ops/models/base.py:18 +#: terminal/models/session.py:34 xpack/plugins/cloud/models.py:87 +#: xpack/plugins/cloud/serializers/task.py:65 +msgid "Account" +msgstr "账号" -#: acls/models/login_asset_acl.py:22 -#: applications/serializers/attrs/application_category/remote_app.py:36 -#: assets/models/asset.py:386 assets/models/authbook.py:19 -#: assets/models/backup.py:31 assets/models/cmd_filter.py:38 -#: assets/models/gathered_user.py:14 assets/serializers/label.py:30 -#: assets/serializers/system_user.py:268 audits/models.py:39 -#: authentication/models.py:66 authentication/models.py:90 -#: perms/models/asset_permission.py:23 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:36 +#: assets/models/asset/common.py:83 assets/models/asset/common.py:219 +#: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 +#: assets/serializers/account/account.py:57 assets/serializers/label.py:30 +#: audits/models.py:39 authentication/models.py:67 authentication/models.py:84 +#: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:32 #: terminal/notifications.py:90 -#: xpack/plugins/change_auth_plan/models/asset.py:199 -#: xpack/plugins/change_auth_plan/serializers/asset.py:181 -#: xpack/plugins/cloud/models.py:223 +#: xpack/plugins/change_auth_plan/models/asset.py:200 +#: xpack/plugins/change_auth_plan/serializers/asset.py:172 +#: xpack/plugins/cloud/models.py:219 msgid "Asset" msgstr "资产" @@ -144,30 +148,30 @@ msgstr "资产" msgid "Login asset acl" msgstr "登录资产访问控制" -#: acls/models/login_asset_acl.py:89 tickets/const.py:12 +#: acls/models/login_asset_acl.py:86 tickets/const.py:11 msgid "Login asset confirm" msgstr "登录资产复核" -#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:12 +#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:13 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " -#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 -#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 -#: assets/models/gathered_user.py:15 audits/models.py:121 -#: authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:260 +#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 +#: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 +#: assets/models/base.py:66 assets/models/gathered_user.py:15 +#: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 +#: authentication/models.py:248 #: 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:32 users/models/user.py:663 +#: users/forms/profile.py:32 users/models/user.py:663 #: users/templates/users/_msg_user_created.html:12 -#: xpack/plugins/change_auth_plan/models/asset.py:34 -#: xpack/plugins/change_auth_plan/models/asset.py:195 +#: xpack/plugins/change_auth_plan/models/asset.py:35 +#: xpack/plugins/change_auth_plan/models/asset.py:196 #: xpack/plugins/cloud/serializers/account_attrs.py:26 msgid "Username" msgstr "用户名" -#: acls/serializers/login_asset_acl.py:24 +#: acls/serializers/login_asset_acl.py:25 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -176,10 +180,8 @@ msgstr "" "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" -#: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 -#: applications/serializers/attrs/application_type/mysql_workbench.py:18 -#: assets/models/asset.py:210 assets/models/domain.py:61 -#: assets/serializers/account.py:13 +#: acls/serializers/login_asset_acl.py:32 acls/serializers/rules/rules.py:33 +#: assets/models/asset/common.py:92 assets/models/domain.py:65 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -187,39 +189,28 @@ msgstr "" msgid "IP" msgstr "IP" -#: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:211 -#: assets/serializers/account.py:14 assets/serializers/gathered_user.py:23 -#: settings/serializers/terminal.py:7 +#: acls/serializers/login_asset_acl.py:36 +#: assets/serializers/gathered_user.py:22 settings/serializers/terminal.py:7 msgid "Hostname" msgstr "主机名" -#: acls/serializers/login_asset_acl.py:42 +#: acls/serializers/login_asset_acl.py:43 msgid "" "Format for comma-delimited string, with * indicating a match all. Protocol " "options: {}" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" -#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:213 -#: assets/models/domain.py:63 assets/models/user.py:252 -#: terminal/serializers/session.py:31 terminal/serializers/storage.py:68 -msgid "Protocol" -msgstr "协议" - -#: acls/serializers/login_asset_acl.py:65 -msgid "Unsupported protocols: {}" -msgstr "不支持的协议: {}" - -#: acls/serializers/login_asset_acl.py:98 +#: acls/serializers/login_asset_acl.py:84 #: tickets/serializers/ticket/ticket.py:85 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" -#: acls/serializers/login_asset_acl.py:103 +#: acls/serializers/login_asset_acl.py:89 msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" #: acls/serializers/rules/rules.py:20 -#: xpack/plugins/cloud/serializers/task.py:23 +#: xpack/plugins/cloud/serializers/task.py:22 msgid "IP address invalid: `{}`" msgstr "IP 地址无效: `{}`" @@ -236,267 +227,58 @@ msgstr "" msgid "Time Period" msgstr "时段" -#: applications/apps.py:9 applications/models/application.py:64 +#: applications/apps.py:9 msgid "Applications" msgstr "应用管理" -#: applications/const.py:8 -#: applications/serializers/attrs/application_category/db.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:26 -#: xpack/plugins/change_auth_plan/models/app.py:32 -msgid "Database" -msgstr "数据库" - -#: applications/const.py:9 -msgid "Remote app" -msgstr "远程应用" - -#: applications/const.py:35 -msgid "Custom" -msgstr "自定义" - -#: applications/const.py:91 rbac/tree.py:29 -msgid "Other" -msgstr "其它" - -#: applications/models/account.py:12 applications/models/application.py:237 -#: assets/models/backup.py:32 assets/models/cmd_filter.py:45 -#: authentication/models.py:67 authentication/models.py:95 -#: perms/models/application_permission.py:28 -msgid "Application" -msgstr "应用程序" - -#: applications/models/account.py:15 assets/models/authbook.py:20 -#: assets/models/cmd_filter.py:42 assets/models/user.py:342 audits/models.py:40 -#: authentication/models.py:83 perms/models/application_permission.py:33 -#: perms/models/asset_permission.py:25 terminal/backends/command/models.py:22 -#: terminal/backends/command/serializers.py:36 terminal/models/session.py:48 -#: xpack/plugins/change_auth_plan/models/app.py:36 -#: xpack/plugins/change_auth_plan/models/app.py:147 -#: xpack/plugins/change_auth_plan/serializers/app.py:65 -msgid "System user" -msgstr "系统用户" - -#: applications/models/account.py:17 -#: applications/serializers/attrs/application_type/oracle.py:13 -#: assets/models/authbook.py:21 settings/serializers/auth/cas.py:18 -msgid "Version" -msgstr "版本" - -#: applications/models/account.py:23 -msgid "Application account" -msgstr "应用账号" - -#: applications/models/account.py:26 -msgid "Can view application account secret" -msgstr "可以查看应用账号密码" - -#: applications/models/account.py:27 -msgid "Can change application account secret" -msgstr "可以查看应用账号密码" - -#: applications/models/application.py:222 -#: applications/serializers/application.py:99 assets/models/label.py:21 -#: perms/models/application_permission.py:21 -#: perms/serializers/application/user_permission.py:33 -#: tickets/models/ticket/apply_application.py:15 -#: xpack/plugins/change_auth_plan/models/app.py:25 +#: applications/models.py:12 assets/models/label.py:20 +#: assets/models/platform.py:69 assets/serializers/asset/common.py:62 +#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:76 +#: assets/serializers/platform.py:105 +#: tickets/models/ticket/apply_application.py:14 +#: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" msgstr "类别" -#: applications/models/application.py:225 -#: applications/serializers/application.py:101 assets/models/backup.py:49 -#: assets/models/cmd_filter.py:82 assets/models/user.py:250 -#: authentication/models.py:70 perms/models/application_permission.py:24 -#: perms/serializers/application/user_permission.py:34 +#: applications/models.py:15 assets/models/_user.py:46 +#: assets/models/automations/base.py:31 assets/models/cmd_filter.py:74 +#: assets/models/platform.py:70 assets/serializers/asset/common.py:63 +#: assets/serializers/platform.py:75 authentication/models.py:71 #: terminal/models/storage.py:58 terminal/models/storage.py:143 #: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:18 +#: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 -#: xpack/plugins/change_auth_plan/models/app.py:28 -#: xpack/plugins/change_auth_plan/models/app.py:153 +#: xpack/plugins/change_auth_plan/models/app.py:27 +#: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" msgstr "类型" -#: applications/models/application.py:229 assets/models/asset.py:217 -#: assets/models/domain.py:29 assets/models/domain.py:64 -msgid "Domain" -msgstr "网域" - -#: applications/models/application.py:231 xpack/plugins/cloud/models.py:33 +#: applications/models.py:17 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/serializers/account.py:61 msgid "Attrs" msgstr "属性" -#: applications/models/application.py:241 +#: applications/models.py:23 authentication/models.py:68 +msgid "Application" +msgstr "应用程序" + +#: applications/models.py:27 msgid "Can match application" msgstr "匹配应用" -#: applications/models/application.py:320 -msgid "Application user" -msgstr "应用用户" - -#: applications/serializers/application.py:70 -#: applications/serializers/application.py:100 assets/serializers/label.py:13 -#: perms/serializers/application/permission.py:18 -msgid "Category display" -msgstr "类别名称" - -#: applications/serializers/application.py:71 -#: applications/serializers/application.py:102 -#: assets/serializers/cmd_filter.py:34 assets/serializers/system_user.py:34 -#: audits/serializers.py:29 authentication/serializers/connection_token.py:22 -#: perms/serializers/application/permission.py:19 -#: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 -msgid "Type display" -msgstr "类型名称" - -#: applications/serializers/application.py:103 assets/models/asset.py:230 -#: assets/models/base.py:181 assets/models/cluster.py:26 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 -#: assets/models/group.py:22 assets/models/label.py:25 -#: assets/serializers/account.py:18 assets/serializers/cmd_filter.py:28 -#: assets/serializers/cmd_filter.py:48 common/db/models.py:114 -#: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30 -#: orgs/models.py:72 orgs/models.py:223 perms/models/base.py:92 -#: users/models/group.py:18 users/models/user.py:926 -#: xpack/plugins/cloud/models.py:125 -msgid "Date created" -msgstr "创建日期" - -#: applications/serializers/application.py:104 assets/models/base.py:182 -#: assets/models/gathered_user.py:20 assets/serializers/account.py:21 -#: assets/serializers/cmd_filter.py:29 assets/serializers/cmd_filter.py:49 -#: common/db/models.py:115 common/mixins/models.py:51 ops/models/adhoc.py:40 -#: orgs/models.py:224 -msgid "Date updated" -msgstr "更新日期" - -#: applications/serializers/application.py:121 -#: applications/serializers/application.py:166 authentication/models.py:99 -msgid "Application display" -msgstr "应用名称" - -#: applications/serializers/application.py:123 -msgid "account" -msgstr "账号" - -#: applications/serializers/attrs/application_category/cloud.py:8 -#: assets/models/cluster.py:40 -msgid "Cluster" -msgstr "集群" - -#: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 -#: xpack/plugins/cloud/serializers/account_attrs.py:72 -msgid "Host" -msgstr "主机" - -#: applications/serializers/attrs/application_category/db.py:12 -#: applications/serializers/attrs/application_type/mongodb.py:10 -#: applications/serializers/attrs/application_type/mysql.py:10 -#: applications/serializers/attrs/application_type/mysql_workbench.py:22 -#: applications/serializers/attrs/application_type/oracle.py:16 -#: applications/serializers/attrs/application_type/pgsql.py:10 -#: applications/serializers/attrs/application_type/redis.py:10 -#: applications/serializers/attrs/application_type/sqlserver.py:10 -#: assets/models/asset.py:214 assets/models/domain.py:62 -#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:57 -#: xpack/plugins/cloud/serializers/account_attrs.py:73 -msgid "Port" -msgstr "端口" - -#: applications/serializers/attrs/application_category/remote_app.py:34 -msgid "Asset Info" -msgstr "资产信息" - -#: applications/serializers/attrs/application_category/remote_app.py:39 -#: applications/serializers/attrs/application_type/chrome.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:14 -#: applications/serializers/attrs/application_type/vmware_client.py:18 -msgid "Application path" -msgstr "应用路径" - -#: applications/serializers/attrs/application_category/remote_app.py:44 -#: assets/serializers/system_user.py:167 -#: tickets/serializers/ticket/apply_application.py:38 -#: tickets/serializers/ticket/common.py:59 -#: xpack/plugins/change_auth_plan/serializers/asset.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:70 -#: xpack/plugins/change_auth_plan/serializers/asset.py:73 -#: xpack/plugins/change_auth_plan/serializers/asset.py:104 -#: xpack/plugins/cloud/serializers/account_attrs.py:56 -msgid "This field is required." -msgstr "该字段是必填项。" - -#: applications/serializers/attrs/application_type/chrome.py:18 -#: applications/serializers/attrs/application_type/vmware_client.py:22 -msgid "Target URL" -msgstr "目标URL" - -#: applications/serializers/attrs/application_type/chrome.py:22 -msgid "Chrome username" -msgstr "Chrome 用户名" - -#: applications/serializers/attrs/application_type/chrome.py:26 -#: applications/serializers/attrs/application_type/chrome.py:33 -msgid "Chrome password" -msgstr "Chrome 密码" - -#: applications/serializers/attrs/application_type/custom.py:12 -msgid "Operating parameter" -msgstr "运行参数" - -#: applications/serializers/attrs/application_type/custom.py:16 -msgid "Target url" -msgstr "目标URL" - -#: applications/serializers/attrs/application_type/custom.py:20 -msgid "Custom Username" -msgstr "自定义用户名" - -#: applications/serializers/attrs/application_type/custom.py:25 -#: applications/serializers/attrs/application_type/custom.py:32 -#: xpack/plugins/change_auth_plan/models/base.py:27 -msgid "Custom password" -msgstr "自定义密码" - -#: applications/serializers/attrs/application_type/mysql_workbench.py:30 -msgid "Mysql workbench username" -msgstr "Mysql 工作台 用户名" - -#: applications/serializers/attrs/application_type/mysql_workbench.py:35 -#: applications/serializers/attrs/application_type/mysql_workbench.py:42 -msgid "Mysql workbench password" -msgstr "Mysql 工作台 密码" - -#: applications/serializers/attrs/application_type/oracle.py:14 -msgid "Magnus currently supports only 11g and 12c connections" -msgstr "目前 Magnus 只支持连接 11g、12c 版本" - -#: applications/serializers/attrs/application_type/vmware_client.py:26 -msgid "Vmware username" -msgstr "Vmware 用户名" - -#: applications/serializers/attrs/application_type/vmware_client.py:31 -#: applications/serializers/attrs/application_type/vmware_client.py:38 -msgid "Vmware password" -msgstr "Vmware 密码" - #: assets/api/domain.py:52 msgid "Number required" msgstr "需要为数字" -#: assets/api/node.py:61 +#: assets/api/node.py:62 msgid "You can't update the root node name" msgstr "不能修改根节点名称" -#: assets/api/node.py:68 +#: assets/api/node.py:69 msgid "You can't delete the root node ({})" msgstr "不能删除根节点 ({})" -#: assets/api/node.py:71 +#: assets/api/node.py:72 msgid "Deletion failed and the node contains assets" msgstr "删除失败,节点包含资产" @@ -504,285 +286,74 @@ msgstr "删除失败,节点包含资产" msgid "App assets" msgstr "资产管理" -#: assets/models/asset.py:139 -msgid "Base" -msgstr "基础" +#: assets/automations/base/manager.py:74 +#, fuzzy +#| msgid "Disabled" +msgid "{} disabled" +msgstr "禁用" -#: assets/models/asset.py:140 -msgid "Charset" -msgstr "编码" +#: assets/const/category.py:11 settings/serializers/auth/radius.py:14 +#: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 +#: xpack/plugins/cloud/serializers/account_attrs.py:72 +msgid "Host" +msgstr "主机" -#: assets/models/asset.py:141 assets/serializers/asset.py:176 -#: tickets/models/ticket/general.py:298 -msgid "Meta" -msgstr "元数据" +#: assets/const/category.py:12 +msgid "Device" +msgstr "" -#: assets/models/asset.py:142 -msgid "Internal" -msgstr "内部的" +#: assets/const/category.py:13 assets/models/asset/database.py:8 +#: assets/models/asset/database.py:18 +#: xpack/plugins/change_auth_plan/models/app.py:31 +msgid "Database" +msgstr "数据库" -#: assets/models/asset.py:162 assets/models/asset.py:216 -#: assets/serializers/account.py:15 assets/serializers/asset.py:63 -#: perms/serializers/asset/user_permission.py:43 -#: xpack/plugins/cloud/serializers/account_attrs.py:162 -msgid "Platform" -msgstr "资产平台" +#: assets/const/category.py:14 +msgid "Cloud service" +msgstr "云服务" -#: assets/models/asset.py:168 -msgid "Vendor" -msgstr "制造商" +#: assets/const/category.py:15 +msgid "Web" +msgstr "Web" -#: assets/models/asset.py:169 -msgid "Model" -msgstr "型号" +#: assets/const/device.py:7 tickets/const.py:8 +msgid "General" +msgstr "一般" -#: assets/models/asset.py:170 tickets/models/ticket/general.py:296 -msgid "Serial number" -msgstr "序列号" +#: assets/const/device.py:8 +msgid "Switch" +msgstr "交换机" -#: assets/models/asset.py:172 -msgid "CPU model" -msgstr "CPU型号" +#: assets/const/device.py:9 +msgid "Router" +msgstr "路由器" -#: assets/models/asset.py:173 -msgid "CPU count" -msgstr "CPU数量" +#: assets/const/device.py:10 +msgid "Firewall" +msgstr "防火墙" -#: assets/models/asset.py:174 -msgid "CPU cores" -msgstr "CPU核数" +#: assets/const/web.py:7 +msgid "Website" +msgstr "网站" -#: assets/models/asset.py:175 -msgid "CPU vcpus" -msgstr "CPU总数" +#: assets/models/_user.py:24 +msgid "Automatic managed" +msgstr "托管的" -#: assets/models/asset.py:176 -msgid "Memory" -msgstr "内存" +#: assets/models/_user.py:25 +msgid "Manually input" +msgstr "手动输入" -#: assets/models/asset.py:177 -msgid "Disk total" -msgstr "硬盘大小" +#: assets/models/_user.py:29 +msgid "Common user" +msgstr "普通用户" -#: assets/models/asset.py:178 -msgid "Disk info" -msgstr "硬盘信息" - -#: assets/models/asset.py:180 -msgid "OS" -msgstr "操作系统" - -#: assets/models/asset.py:181 -msgid "OS version" -msgstr "系统版本" - -#: assets/models/asset.py:182 -msgid "OS arch" -msgstr "系统架构" - -#: assets/models/asset.py:183 -msgid "Hostname raw" -msgstr "主机名原始" - -#: assets/models/asset.py:215 assets/serializers/account.py:16 -#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 -#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:43 -msgid "Protocols" -msgstr "协议组" - -#: assets/models/asset.py:218 assets/models/user.py:242 -#: perms/models/asset_permission.py:24 -#: xpack/plugins/change_auth_plan/models/asset.py:43 -#: xpack/plugins/gathered_user/models.py:24 -msgid "Nodes" -msgstr "节点" - -#: assets/models/asset.py:219 assets/models/cmd_filter.py:47 -#: assets/models/domain.py:66 assets/models/label.py:22 -#: users/serializers/user.py:147 -msgid "Is active" -msgstr "激活" - -#: assets/models/asset.py:222 assets/models/cluster.py:19 -#: assets/models/user.py:239 assets/models/user.py:394 +#: assets/models/_user.py:30 msgid "Admin user" msgstr "特权用户" -#: assets/models/asset.py:225 -msgid "Public IP" -msgstr "公网IP" - -#: assets/models/asset.py:226 -msgid "Asset number" -msgstr "资产编号" - -#: assets/models/asset.py:228 -msgid "Labels" -msgstr "标签管理" - -#: assets/models/asset.py:229 assets/models/base.py:183 -#: 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:112 common/mixins/models.py:49 orgs/models.py:71 -#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:710 -#: users/serializers/group.py:33 -#: xpack/plugins/change_auth_plan/models/base.py:48 -#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30 -msgid "Created by" -msgstr "创建者" - -#: assets/models/asset.py:389 -msgid "Can refresh asset hardware info" -msgstr "可以更新资产硬件信息" - -#: assets/models/asset.py:390 -msgid "Can test asset connectivity" -msgstr "可以测试资产连接性" - -#: assets/models/asset.py:391 -msgid "Can push system user to asset" -msgstr "可以推送系统用户到资产" - -#: assets/models/asset.py:392 -msgid "Can match asset" -msgstr "可以匹配资产" - -#: assets/models/asset.py:393 -msgid "Add asset to node" -msgstr "添加资产到节点" - -#: assets/models/asset.py:394 -msgid "Move asset to node" -msgstr "移动资产到节点" - -#: assets/models/authbook.py:27 -msgid "AuthBook" -msgstr "资产账号" - -#: assets/models/authbook.py:30 -msgid "Can test asset account connectivity" -msgstr "可以测试资产账号连接性" - -#: assets/models/authbook.py:31 -msgid "Can view asset account secret" -msgstr "可以查看资产账号密码" - -#: assets/models/authbook.py:32 -msgid "Can change asset account secret" -msgstr "可以更改资产账号密码" - -#: assets/models/authbook.py:33 -msgid "Can view asset history account" -msgstr "可以查看资产历史账号" - -#: assets/models/authbook.py:34 -msgid "Can view asset history account secret" -msgstr "可以查看资产历史账号密码" - -#: assets/models/backup.py:30 perms/models/base.py:54 -#: settings/serializers/terminal.py:12 -msgid "All" -msgstr "全部" - -#: assets/models/backup.py:52 assets/serializers/backup.py:32 -#: xpack/plugins/change_auth_plan/models/app.py:41 -#: xpack/plugins/change_auth_plan/models/asset.py:62 -#: xpack/plugins/change_auth_plan/serializers/base.py:45 -msgid "Recipient" -msgstr "收件人" - -#: assets/models/backup.py:62 assets/models/backup.py:124 -msgid "Account backup plan" -msgstr "账号备份计划" - -#: assets/models/backup.py:100 -#: xpack/plugins/change_auth_plan/models/base.py:107 -msgid "Manual trigger" -msgstr "手动触发" - -#: assets/models/backup.py:101 -#: xpack/plugins/change_auth_plan/models/base.py:108 -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:58 -#: tickets/models/ticket/apply_application.py:29 -#: tickets/models/ticket/apply_asset.py:23 -#: xpack/plugins/change_auth_plan/models/base.py:112 -#: xpack/plugins/change_auth_plan/models/base.py:203 -#: xpack/plugins/gathered_user/models.py:76 -msgid "Date start" -msgstr "开始日期" - -#: assets/models/backup.py:108 -#: authentication/templates/authentication/_msg_oauth_bind.html:11 -#: notifications/notifications.py:187 ops/models/adhoc.py:258 -#: xpack/plugins/change_auth_plan/models/base.py:115 -#: xpack/plugins/change_auth_plan/models/base.py:204 -#: xpack/plugins/gathered_user/models.py:79 -msgid "Time" -msgstr "时间" - -#: assets/models/backup.py:112 -msgid "Account backup snapshot" -msgstr "账号备份快照" - -#: assets/models/backup.py:116 assets/serializers/backup.py:40 -#: xpack/plugins/change_auth_plan/models/base.py:125 -#: xpack/plugins/change_auth_plan/serializers/base.py:78 -msgid "Trigger mode" -msgstr "触发模式" - -#: assets/models/backup.py:119 audits/models.py:127 -#: terminal/models/sharing.py:108 -#: xpack/plugins/change_auth_plan/models/base.py:201 -#: xpack/plugins/change_auth_plan/serializers/app.py:66 -#: xpack/plugins/change_auth_plan/serializers/asset.py:180 -#: xpack/plugins/cloud/models.py:179 -msgid "Reason" -msgstr "原因" - -#: assets/models/backup.py:121 audits/serializers.py:82 -#: audits/serializers.py:97 ops/models/adhoc.py:260 -#: terminal/serializers/session.py:36 -#: xpack/plugins/change_auth_plan/models/base.py:202 -#: xpack/plugins/change_auth_plan/serializers/app.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:182 -msgid "Is success" -msgstr "是否成功" - -#: assets/models/backup.py:128 -msgid "Account backup execution" -msgstr "账号备份执行" - -#: assets/models/base.py:30 assets/tasks/const.py:51 audits/const.py:5 -#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 -#: common/utils/ip/utils.py:84 -msgid "Unknown" -msgstr "未知" - -#: assets/models/base.py:31 -msgid "Ok" -msgstr "成功" - -#: assets/models/base.py:32 audits/models.py:118 -#: xpack/plugins/change_auth_plan/serializers/app.py:88 -#: xpack/plugins/change_auth_plan/serializers/asset.py:199 -#: xpack/plugins/cloud/const.py:33 -msgid "Failed" -msgstr "失败" - -#: assets/models/base.py:38 assets/serializers/domain.py:47 -msgid "Connectivity" -msgstr "可连接性" - -#: assets/models/base.py:40 authentication/models.py:263 -msgid "Date verified" -msgstr "校验日期" - -#: assets/models/base.py:177 assets/serializers/base.py:15 -#: assets/serializers/base.py:37 assets/serializers/system_user.py:29 +#: assets/models/_user.py:35 assets/models/base.py:59 +#: assets/models/domain.py:71 assets/serializers/base.py:15 #: audits/signal_handlers.py:50 authentication/confirm/password.py:9 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 @@ -791,145 +362,568 @@ msgstr "校验日期" #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 -#: xpack/plugins/change_auth_plan/models/base.py:121 -#: xpack/plugins/change_auth_plan/models/base.py:196 +#: xpack/plugins/change_auth_plan/models/base.py:117 +#: xpack/plugins/change_auth_plan/models/base.py:192 #: xpack/plugins/change_auth_plan/serializers/base.py:21 #: xpack/plugins/change_auth_plan/serializers/base.py:73 #: xpack/plugins/cloud/serializers/account_attrs.py:28 msgid "Password" msgstr "密码" -#: assets/models/base.py:178 assets/serializers/base.py:41 -#: xpack/plugins/change_auth_plan/models/asset.py:53 -#: xpack/plugins/change_auth_plan/models/asset.py:130 -#: xpack/plugins/change_auth_plan/models/asset.py:206 +#: assets/models/_user.py:36 assets/models/domain.py:72 +#: assets/serializers/base.py:19 +#: xpack/plugins/change_auth_plan/models/asset.py:54 +#: xpack/plugins/change_auth_plan/models/asset.py:131 +#: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" msgstr "SSH 密钥" -#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:56 -#: xpack/plugins/change_auth_plan/models/asset.py:126 -#: xpack/plugins/change_auth_plan/models/asset.py:202 +#: assets/models/_user.py:37 assets/models/domain.py:73 +#: xpack/plugins/change_auth_plan/models/asset.py:57 +#: xpack/plugins/change_auth_plan/models/asset.py:127 +#: xpack/plugins/change_auth_plan/models/asset.py:203 msgid "SSH public key" msgstr "SSH 公钥" -#: assets/models/cluster.py:20 -msgid "Bandwidth" -msgstr "带宽" +#: assets/models/_user.py:38 assets/models/base.py:62 +#: authentication/models.py:53 +msgid "Token" +msgstr "Token" -#: assets/models/cluster.py:21 -msgid "Contact" -msgstr "联系人" +#: assets/models/_user.py:41 assets/models/automations/base.py:87 +#: assets/models/base.py:71 assets/models/domain.py:26 +#: assets/models/gathered_user.py:19 assets/models/group.py:22 +#: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 +#: orgs/models.py:72 perms/models/asset_permission.py:82 +#: users/models/group.py:18 users/models/user.py:927 +msgid "Date created" +msgstr "创建日期" -#: assets/models/cluster.py:22 users/models/user.py:685 -msgid "Phone" -msgstr "手机" +#: assets/models/_user.py:42 assets/models/base.py:72 +#: assets/models/gathered_user.py:20 common/db/models.py:77 +#: common/mixins/models.py:51 +msgid "Date updated" +msgstr "更新日期" -#: assets/models/cluster.py:23 -msgid "Address" -msgstr "地址" +#: assets/models/_user.py:43 assets/models/base.py:73 +#: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 +#: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 +#: orgs/models.py:71 perms/models/asset_permission.py:81 +#: users/models/user.py:710 users/serializers/group.py:33 +#: xpack/plugins/change_auth_plan/models/base.py:48 +msgid "Created by" +msgstr "创建者" -#: assets/models/cluster.py:24 -msgid "Intranet" -msgstr "内网" +#: assets/models/_user.py:45 +msgid "Username same with user" +msgstr "用户名与用户相同" -#: assets/models/cluster.py:25 -msgid "Extranet" -msgstr "外网" +#: assets/models/_user.py:48 assets/models/domain.py:67 +#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 +#: terminal/serializers/storage.py:68 +msgid "Protocol" +msgstr "协议" -#: assets/models/cluster.py:27 -msgid "Operator" -msgstr "运营商" +#: assets/models/_user.py:49 +msgid "Auto push" +msgstr "自动推送" -#: assets/models/cluster.py:36 assets/models/group.py:34 -#: xpack/plugins/cloud/providers/nutanix.py:30 -msgid "Default" -msgstr "默认" +#: assets/models/_user.py:50 +msgid "Sudo" +msgstr "Sudo" -#: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6 -#: users/models/user.py:911 -msgid "System" -msgstr "系统" +#: assets/models/_user.py:51 +msgid "Shell" +msgstr "Shell" -#: assets/models/cluster.py:36 -msgid "Default Cluster" -msgstr "默认Cluster" +#: assets/models/_user.py:52 +msgid "Login mode" +msgstr "认证方式" -#: assets/models/cmd_filter.py:34 perms/models/base.py:86 +#: assets/models/_user.py:53 +msgid "SFTP Root" +msgstr "SFTP根路径" + +#: assets/models/_user.py:54 +msgid "Home" +msgstr "家目录" + +#: assets/models/_user.py:55 +msgid "System groups" +msgstr "用户组" + +#: assets/models/_user.py:58 +msgid "User switch" +msgstr "用户切换" + +#: assets/models/_user.py:59 +msgid "Switch from" +msgstr "切换自" + +#: assets/models/_user.py:65 audits/models.py:40 +#: terminal/backends/command/models.py:22 +#: terminal/backends/command/serializers.py:36 +#: xpack/plugins/change_auth_plan/models/app.py:35 +#: xpack/plugins/change_auth_plan/models/app.py:146 +msgid "System user" +msgstr "系统用户" + +#: assets/models/_user.py:67 +msgid "Can match system user" +msgstr "可以匹配系统用户" + +#: assets/models/account.py:40 +msgid "Su from" +msgstr "切换自" + +#: assets/models/account.py:42 settings/serializers/auth/cas.py:18 +msgid "Version" +msgstr "版本" + +#: assets/models/account.py:54 +msgid "Can view asset account secret" +msgstr "可以查看资产账号密码" + +#: assets/models/account.py:55 +msgid "Can change asset account secret" +msgstr "可以更改资产账号密码" + +#: assets/models/account.py:56 +msgid "Can view asset history account" +msgstr "可以查看资产历史账号" + +#: assets/models/account.py:57 +msgid "Can view asset history account secret" +msgstr "可以查看资产历史账号密码" + +#: assets/models/account.py:80 assets/serializers/account/account.py:13 +msgid "Account template" +msgstr "账号模版" + +#: assets/models/asset/common.py:82 assets/models/domain.py:66 +#: assets/models/platform.py:23 settings/serializers/auth/radius.py:15 +#: settings/serializers/auth/sms.py:57 +#: xpack/plugins/cloud/serializers/account_attrs.py:73 +msgid "Port" +msgstr "端口" + +#: assets/models/asset/common.py:94 assets/models/platform.py:104 +#: assets/serializers/asset/common.py:65 +#: perms/serializers/user_permission.py:21 +#: xpack/plugins/cloud/serializers/account_attrs.py:172 +msgid "Platform" +msgstr "资产平台" + +#: assets/models/asset/common.py:96 assets/models/domain.py:29 +#: assets/models/domain.py:68 assets/serializers/asset/common.py:64 +msgid "Domain" +msgstr "网域" + +#: assets/models/asset/common.py:98 assets/models/automations/base.py:26 +#: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 +#: xpack/plugins/change_auth_plan/models/asset.py:44 +#: xpack/plugins/gathered_user/models.py:24 +msgid "Nodes" +msgstr "节点" + +#: assets/models/asset/common.py:99 assets/models/automations/base.py:32 +#: assets/models/cmd_filter.py:39 assets/models/domain.py:70 +#: assets/models/label.py:21 users/serializers/user.py:147 +msgid "Is active" +msgstr "激活" + +#: assets/models/asset/common.py:100 assets/serializers/asset/common.py:67 +msgid "Labels" +msgstr "标签管理" + +#: assets/models/asset/common.py:222 +msgid "Can refresh asset hardware info" +msgstr "可以更新资产硬件信息" + +#: assets/models/asset/common.py:223 +msgid "Can test asset connectivity" +msgstr "可以测试资产连接性" + +#: assets/models/asset/common.py:224 +msgid "Can push system user to asset" +msgstr "可以推送系统用户到资产" + +#: assets/models/asset/common.py:225 +msgid "Can match asset" +msgstr "可以匹配资产" + +#: assets/models/asset/common.py:226 +msgid "Add asset to node" +msgstr "添加资产到节点" + +#: assets/models/asset/common.py:227 +msgid "Move asset to node" +msgstr "移动资产到节点" + +#: assets/models/automations/account_discovery.py:10 +msgid "Discovery strategy" +msgstr "自动发现策略" + +#: assets/models/automations/account_reconcile.py:9 +msgid "Reconcile strategy" +msgstr "主机名策略" + +#: assets/models/automations/account_verify.py:9 +#, fuzzy +#| msgid "SSH Key strategy" +msgid "Verify strategy" +msgstr "SSH 密钥策略" + +#: assets/models/automations/base.py:15 +msgid "Ping" +msgstr "" + +#: assets/models/automations/base.py:16 +#, fuzzy +#| msgid "Gather account" +msgid "Gather facts" +msgstr "收集账号" + +#: assets/models/automations/base.py:17 +#, fuzzy +#| msgid "Gather account" +msgid "Create account" +msgstr "收集账号" + +#: assets/models/automations/base.py:18 +#: assets/models/automations/change_secret.py:56 +#, fuzzy +#| msgid "Change auth" +msgid "Change secret" +msgstr "执行改密" + +#: assets/models/automations/base.py:19 +#, fuzzy +#| msgid "Verify auth" +msgid "Verify account" +msgstr "验证密码/密钥" + +#: assets/models/automations/base.py:20 +#, fuzzy +#| msgid "Gather account" +msgid "Gather accounts" +msgstr "收集账号" + +#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:38 +#: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 +#: rbac/tree.py:37 +msgid "Accounts" +msgstr "账号管理" + +#: assets/models/automations/base.py:29 assets/serializers/domain.py:29 +#: ops/models/base.py:17 +#: terminal/templates/terminal/_msg_command_execute_alert.html:16 +#: xpack/plugins/change_auth_plan/models/asset.py:40 +msgid "Assets" +msgstr "资产" + +#: assets/models/automations/base.py:77 +#, fuzzy +#| msgid "Automatic managed" +msgid "Automation plan" +msgstr "托管密码" + +#: assets/models/automations/base.py:84 +#, fuzzy +#| msgid "Hostname strategy" +msgid "Automation strategy" +msgstr "主机名策略" + +#: assets/models/automations/base.py:88 assets/models/backup.py:77 +#: audits/models.py:44 ops/models/base.py:54 +#: perms/models/asset_permission.py:76 terminal/models/session.py:43 +#: tickets/models/ticket/apply_application.py:28 +#: tickets/models/ticket/apply_asset.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:199 +#: xpack/plugins/gathered_user/models.py:71 +msgid "Date start" +msgstr "开始日期" + +#: assets/models/automations/base.py:89 +#: assets/models/automations/change_secret.py:51 ops/models/base.py:55 +msgid "Date finished" +msgstr "结束日期" + +#: assets/models/automations/base.py:91 +#, fuzzy +#| msgid "Relation snapshot" +msgid "Automation snapshot" +msgstr "工单快照" + +#: assets/models/automations/base.py:95 assets/models/backup.py:88 +#: assets/serializers/account/backup.py:36 +#: xpack/plugins/change_auth_plan/models/base.py:121 +#: xpack/plugins/change_auth_plan/serializers/base.py:78 +msgid "Trigger mode" +msgstr "触发模式" + +#: assets/models/automations/base.py:99 +#, fuzzy +#| msgid "Command execution" +msgid "Automation strategy execution" +msgstr "命令执行" + +#: assets/models/automations/change_secret.py:13 +msgid "Specific" +msgstr "" + +#: assets/models/automations/change_secret.py:14 ops/const.py:20 +#: xpack/plugins/change_auth_plan/models/base.py:28 +msgid "All assets use the same random password" +msgstr "使用相同的随机密码" + +#: assets/models/automations/change_secret.py:15 ops/const.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:29 +msgid "All assets use different random password" +msgstr "使用不同的随机密码" + +#: assets/models/automations/change_secret.py:19 ops/const.py:13 +#: xpack/plugins/change_auth_plan/models/asset.py:30 +msgid "Append SSH KEY" +msgstr "追加" + +#: assets/models/automations/change_secret.py:20 ops/const.py:14 +#: xpack/plugins/change_auth_plan/models/asset.py:31 +msgid "Empty and append SSH KEY" +msgstr "清空所有并添加" + +#: assets/models/automations/change_secret.py:21 ops/const.py:15 +#: xpack/plugins/change_auth_plan/models/asset.py:32 +msgid "Replace (The key generated by JumpServer) " +msgstr "替换 (由 JumpServer 生成的密钥)" + +#: assets/models/automations/change_secret.py:25 +#, fuzzy +#| msgid "Secret key" +msgid "Secret types" +msgstr "Secret key" + +#: assets/models/automations/change_secret.py:27 users/serializers/user.py:81 +#: xpack/plugins/change_auth_plan/models/base.py:35 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 +msgid "Password strategy" +msgstr "密码策略" + +#: assets/models/automations/change_secret.py:28 +#: assets/models/automations/change_secret.py:49 assets/models/base.py:68 +#: assets/serializers/account/base.py:17 authentication/models.py:73 +#: authentication/models.py:249 +#: authentication/templates/authentication/_access_key_modal.html:31 +#: settings/serializers/auth/radius.py:17 +msgid "Secret" +msgstr "密钥" + +#: assets/models/automations/change_secret.py:29 +#: xpack/plugins/change_auth_plan/models/base.py:39 +msgid "Password rules" +msgstr "密码规则" + +#: assets/models/automations/change_secret.py:32 assets/models/base.py:60 +#, fuzzy +#| msgid "SSH Key" +msgid "SSH key" +msgstr "SSH 密钥" + +#: assets/models/automations/change_secret.py:34 +#, fuzzy +#| msgid "SSH Key strategy" +msgid "SSH key strategy" +msgstr "SSH 密钥策略" + +#: assets/models/automations/change_secret.py:35 assets/models/backup.py:28 +#: assets/serializers/account/backup.py:28 +#: xpack/plugins/change_auth_plan/models/app.py:40 +#: xpack/plugins/change_auth_plan/models/asset.py:63 +#: xpack/plugins/change_auth_plan/serializers/base.py:45 +msgid "Recipient" +msgstr "收件人" + +#: assets/models/automations/change_secret.py:42 +#, fuzzy +#| msgid "Can change auth setting" +msgid "Change auth strategy" +msgstr "认证设置" + +#: assets/models/automations/change_secret.py:48 +#, fuzzy +#| msgid "Secret" +msgid "Old secret" +msgstr "密钥" + +#: assets/models/automations/change_secret.py:50 +#, fuzzy +#| msgid "Date start" +msgid "Date started" +msgstr "开始日期" + +#: assets/models/automations/change_secret.py:53 +#, fuzzy +#| msgid "WeCom Error" +msgid "Error" +msgstr "企业微信错误" + +#: assets/models/automations/gather_facts.py:11 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather asset facts" +msgstr "收集资产上的用户" + +#: assets/models/backup.py:38 assets/models/backup.py:96 +msgid "Account backup plan" +msgstr "账号备份计划" + +#: assets/models/backup.py:80 +#: authentication/templates/authentication/_msg_oauth_bind.html:11 +#: notifications/notifications.py:187 +#: xpack/plugins/change_auth_plan/models/base.py:111 +#: xpack/plugins/change_auth_plan/models/base.py:200 +#: xpack/plugins/gathered_user/models.py:74 +msgid "Time" +msgstr "时间" + +#: assets/models/backup.py:84 +msgid "Account backup snapshot" +msgstr "账号备份快照" + +#: assets/models/backup.py:91 audits/models.py:127 +#: terminal/models/sharing.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:197 +#: xpack/plugins/change_auth_plan/serializers/asset.py:171 +#: xpack/plugins/cloud/models.py:175 +msgid "Reason" +msgstr "原因" + +#: assets/models/backup.py:93 terminal/serializers/session.py:36 +#: xpack/plugins/change_auth_plan/models/base.py:198 +#: xpack/plugins/change_auth_plan/serializers/asset.py:173 +msgid "Is success" +msgstr "是否成功" + +#: assets/models/backup.py:100 +msgid "Account backup execution" +msgstr "账号备份执行" + +#: assets/models/base.py:28 assets/tasks/const.py:51 audits/const.py:5 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 +msgid "Unknown" +msgstr "未知" + +#: assets/models/base.py:29 +msgid "Ok" +msgstr "成功" + +#: assets/models/base.py:30 audits/models.py:118 +#: xpack/plugins/change_auth_plan/serializers/asset.py:190 +#: xpack/plugins/cloud/const.py:33 +msgid "Failed" +msgstr "失败" + +#: assets/models/base.py:36 assets/serializers/domain.py:42 +msgid "Connectivity" +msgstr "可连接性" + +#: assets/models/base.py:38 authentication/models.py:251 +msgid "Date verified" +msgstr "校验日期" + +#: assets/models/base.py:61 authentication/models.py:38 +msgid "Access key" +msgstr "Access key" + +#: assets/models/base.py:67 +#, fuzzy +#| msgid "Secret key" +msgid "Secret type" +msgstr "Secret key" + +#: assets/models/base.py:69 +msgid "Privileged" +msgstr "" + +#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:61 #: users/models/group.py:31 users/models/user.py:671 msgid "User group" msgstr "用户组" -#: assets/models/cmd_filter.py:60 assets/serializers/system_user.py:59 +#: assets/models/cmd_filter.py:52 msgid "Command filter" msgstr "命令过滤器" -#: assets/models/cmd_filter.py:67 +#: assets/models/cmd_filter.py:59 msgid "Regex" msgstr "正则表达式" -#: assets/models/cmd_filter.py:68 ops/models/command.py:26 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:55 +#: assets/models/cmd_filter.py:60 terminal/backends/command/serializers.py:15 +#: terminal/models/session.py:41 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" msgstr "命令" -#: assets/models/cmd_filter.py:74 +#: assets/models/cmd_filter.py:66 msgid "Deny" msgstr "拒绝" -#: assets/models/cmd_filter.py:76 +#: assets/models/cmd_filter.py:68 msgid "Reconfirm" msgstr "复核" -#: assets/models/cmd_filter.py:80 +#: assets/models/cmd_filter.py:72 msgid "Filter" msgstr "过滤器" -#: assets/models/cmd_filter.py:87 settings/serializers/basic.py:10 +#: assets/models/cmd_filter.py:79 settings/serializers/basic.py:10 #: xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" -#: assets/models/cmd_filter.py:87 +#: assets/models/cmd_filter.py:79 msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:88 +#: assets/models/cmd_filter.py:80 msgid "Ignore case" msgstr "忽略大小写" -#: assets/models/cmd_filter.py:103 +#: assets/models/cmd_filter.py:95 msgid "Command filter rule" msgstr "命令过滤规则" -#: assets/models/cmd_filter.py:147 +#: assets/models/cmd_filter.py:138 msgid "The generated regular expression is incorrect: {}" msgstr "生成的正则表达式有误" -#: assets/models/cmd_filter.py:173 tickets/const.py:13 +#: assets/models/cmd_filter.py:164 tickets/const.py:12 msgid "Command confirm" msgstr "命令复核" -#: assets/models/domain.py:73 +#: assets/models/domain.py:84 msgid "Gateway" msgstr "网关" -#: assets/models/domain.py:75 +#: assets/models/domain.py:86 msgid "Test gateway" msgstr "测试网关" -#: assets/models/domain.py:131 -#, python-brace-format -msgid "Unable to connect to port {port} on {ip}" +#: assets/models/domain.py:142 +#, fuzzy, python-brace-format +#| msgid "Unable to connect to port {port} on {ip}" +msgid "Unable to connect to port {port} on {address}" msgstr "无法连接到 {ip} 上的端口 {port}" -#: assets/models/domain.py:134 authentication/middleware.py:75 +#: assets/models/domain.py:145 authentication/middleware.py:75 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "认证失败" -#: assets/models/domain.py:136 assets/models/domain.py:158 +#: assets/models/domain.py:147 assets/models/domain.py:169 msgid "Connect failed" msgstr "连接失败" @@ -953,15 +947,28 @@ msgstr "收集用户" msgid "Asset group" msgstr "资产组" +#: assets/models/group.py:34 assets/models/platform.py:20 +#: xpack/plugins/cloud/providers/nutanix.py:30 +msgid "Default" +msgstr "默认" + #: assets/models/group.py:34 msgid "Default asset group" msgstr "默认资产组" -#: assets/models/label.py:19 assets/models/node.py:553 settings/models.py:34 +#: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:912 +msgid "System" +msgstr "系统" + +#: assets/models/label.py:18 assets/models/node.py:553 +#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 +#: common/drf/serializers/common.py:82 settings/models.py:34 msgid "Value" msgstr "值" -#: assets/models/label.py:40 settings/serializers/sms.py:7 +#: assets/models/label.py:36 assets/serializers/cagegory.py:6 +#: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 +#: settings/serializers/sms.py:7 msgid "Label" msgstr "标签" @@ -973,7 +980,7 @@ msgstr "新节点" msgid "empty" msgstr "空" -#: assets/models/node.py:552 perms/models/asset_permission.py:101 +#: assets/models/node.py:552 perms/models/asset_permission.py:190 msgid "Key" msgstr "键" @@ -981,12 +988,12 @@ msgstr "键" msgid "Full value" msgstr "全称" -#: assets/models/node.py:557 perms/models/asset_permission.py:102 +#: assets/models/node.py:557 perms/models/asset_permission.py:191 msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:566 assets/serializers/system_user.py:267 -#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:70 +#: assets/models/node.py:566 xpack/plugins/cloud/models.py:98 +#: xpack/plugins/cloud/serializers/task.py:68 msgid "Node" msgstr "节点" @@ -994,82 +1001,123 @@ msgstr "节点" msgid "Can match node" msgstr "可以匹配节点" -#: assets/models/user.py:233 -msgid "Automatic managed" +#: assets/models/platform.py:21 +#, fuzzy +#| msgid "MFA required" +msgid "Required" +msgstr "需要 MFA 认证" + +#: assets/models/platform.py:24 users/templates/users/reset_password.html:29 +msgid "Setting" +msgstr "设置" + +#: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37 +msgid "Enabled" +msgstr "启用" + +#: assets/models/platform.py:44 +msgid "Ansible config" +msgstr "" + +#: assets/models/platform.py:45 +#, fuzzy +#| msgid "MFA enabled" +msgid "Ping enabled" +msgstr "MFA 已启用" + +#: assets/models/platform.py:46 +msgid "Ping method" +msgstr "" + +#: assets/models/platform.py:47 assets/models/platform.py:55 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather facts enabled" +msgstr "收集资产上的用户" + +#: assets/models/platform.py:48 assets/models/platform.py:56 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather facts method" +msgstr "收集资产上的用户" + +#: assets/models/platform.py:49 +#, fuzzy +#| msgid "Create account successfully" +msgid "Create account enabled" +msgstr "创建账号成功" + +#: assets/models/platform.py:50 +#, fuzzy +#| msgid "Create account successfully" +msgid "Create account method" +msgstr "创建账号成功" + +#: assets/models/platform.py:51 +#, fuzzy +#| msgid "Change Password" +msgid "Change password enabled" +msgstr "更改密码" + +#: assets/models/platform.py:52 +#, fuzzy +#| msgid "Change Password" +msgid "Change password method" +msgstr "更改密码" + +#: assets/models/platform.py:53 +#, fuzzy +#| msgid "Service account key" +msgid "Verify account enabled" +msgstr "服务账号密钥" + +#: assets/models/platform.py:54 +#, fuzzy +#| msgid "Verify auth" +msgid "Verify account method" +msgstr "验证密码/密钥" + +#: assets/models/platform.py:71 tickets/models/ticket/general.py:298 +msgid "Meta" +msgstr "元数据" + +#: assets/models/platform.py:72 +msgid "Internal" +msgstr "内部的" + +#: assets/models/platform.py:75 +msgid "Charset" +msgstr "编码" + +#: assets/models/platform.py:76 +#, fuzzy +#| msgid "Domain name" +msgid "Domain enabled" +msgstr "网域名称" + +#: assets/models/platform.py:77 +#, fuzzy +#| msgid "Protocols" +msgid "Protocols enabled" +msgstr "协议组" + +#: assets/models/platform.py:79 +#, fuzzy +#| msgid "MFA enabled" +msgid "Su enabled" +msgstr "MFA 已启用" + +#: assets/models/platform.py:80 +msgid "SU method" +msgstr "" + +#: assets/models/platform.py:82 assets/serializers/platform.py:78 +#, fuzzy +#| msgid "Automatic managed" +msgid "Automation" msgstr "托管密码" -#: assets/models/user.py:234 -msgid "Manually input" -msgstr "手动输入" - -#: assets/models/user.py:238 -msgid "Common user" -msgstr "普通用户" - -#: assets/models/user.py:241 -msgid "Username same with user" -msgstr "用户名与用户相同" - -#: assets/models/user.py:244 assets/serializers/domain.py:30 -#: terminal/templates/terminal/_msg_command_execute_alert.html:16 -#: xpack/plugins/change_auth_plan/models/asset.py:39 -msgid "Assets" -msgstr "资产" - -#: assets/models/user.py:248 users/apps.py:9 -msgid "Users" -msgstr "用户管理" - -#: assets/models/user.py:249 -msgid "User groups" -msgstr "用户组" - -#: assets/models/user.py:253 -msgid "Auto push" -msgstr "自动推送" - -#: assets/models/user.py:254 -msgid "Sudo" -msgstr "Sudo" - -#: assets/models/user.py:255 -msgid "Shell" -msgstr "Shell" - -#: assets/models/user.py:256 -msgid "Login mode" -msgstr "认证方式" - -#: assets/models/user.py:257 -msgid "SFTP Root" -msgstr "SFTP根路径" - -#: assets/models/user.py:258 assets/serializers/system_user.py:37 -#: authentication/models.py:52 -msgid "Token" -msgstr "Token" - -#: assets/models/user.py:259 -msgid "Home" -msgstr "家目录" - -#: assets/models/user.py:260 -msgid "System groups" -msgstr "用户组" - -#: assets/models/user.py:263 -msgid "User switch" -msgstr "用户切换" - -#: assets/models/user.py:264 -msgid "Switch from" -msgstr "切换自" - -#: assets/models/user.py:344 -msgid "Can match system user" -msgstr "可以匹配系统用户" - -#: assets/models/utils.py:35 +#: assets/models/utils.py:19 #, python-format msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" @@ -1093,98 +1141,153 @@ msgstr "" "{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" "置加密密码" -#: assets/serializers/account.py:36 assets/serializers/account.py:87 -#: assets/serializers/account_history.py:10 authentication/models.py:87 -msgid "System user display" -msgstr "系统用户名称" +#: assets/serializers/account/account.py:16 +msgid "Push now" +msgstr "" -#: assets/serializers/asset.py:20 -msgid "Protocol format should {}/{}" -msgstr "协议格式 {}/{}" +#: assets/serializers/account/account.py:24 +msgid "Account template not found" +msgstr "" -#: assets/serializers/asset.py:37 -msgid "Protocol duplicate: {}" -msgstr "协议重复: {}" - -#: assets/serializers/asset.py:66 -msgid "Domain name" -msgstr "网域名称" - -#: assets/serializers/asset.py:68 -msgid "Nodes name" -msgstr "节点名称" - -#: assets/serializers/asset.py:71 -msgid "Labels name" -msgstr "标签名称" - -#: assets/serializers/asset.py:105 -msgid "Hardware info" -msgstr "硬件信息" - -#: assets/serializers/asset.py:106 -msgid "Admin user display" -msgstr "特权用户名称" - -#: assets/serializers/asset.py:107 -msgid "CPU info" -msgstr "CPU信息" - -#: assets/serializers/backup.py:20 perms/models/base.py:87 -#: perms/serializers/application/permission.py:17 -#: perms/serializers/application/permission.py:42 -#: perms/serializers/asset/permission.py:18 -#: perms/serializers/asset/permission.py:46 -#: tickets/models/ticket/apply_application.py:27 -#: tickets/models/ticket/apply_asset.py:21 -msgid "Actions" -msgstr "动作" - -#: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147 +#: assets/serializers/account/backup.py:27 ops/mixin.py:104 #: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定时执行" -#: assets/serializers/backup.py:33 +#: assets/serializers/account/backup.py:29 #: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/base.py:16 users/models/user.py:693 -msgid "Private key" -msgstr "ssh私钥" - -#: assets/serializers/base.py:45 -msgid "Key password" -msgstr "密钥密码" - -#: assets/serializers/base.py:58 +#: assets/serializers/account/base.py:39 assets/serializers/base.py:34 msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" -#: assets/serializers/cmd_filter.py:35 assets/serializers/cmd_filter.py:50 -msgid "Action display" -msgstr "动作名称" +#: assets/serializers/account/template.py:16 common/drf/fields.py:69 +#: tickets/serializers/ticket/common.py:58 +#: xpack/plugins/change_auth_plan/serializers/asset.py:64 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:101 +#: xpack/plugins/cloud/serializers/account_attrs.py:56 +msgid "This field is required." +msgstr "该字段是必填项。" -#: assets/serializers/cmd_filter.py:51 ops/models/adhoc.py:155 -msgid "Pattern" -msgstr "模式" +#: assets/serializers/asset/common.py:69 assets/serializers/platform.py:77 +#: xpack/plugins/cloud/models.py:109 +msgid "Protocols" +msgstr "协议组" + +#: assets/serializers/asset/common.py:86 +msgid "Address" +msgstr "地址" + +#: assets/serializers/asset/common.py:129 +#, fuzzy +#| msgid "Application not exists" +msgid "Platform not exist" +msgstr "应用不存在" + +#: assets/serializers/asset/common.py:145 +#, fuzzy +#| msgid "Protocol duplicate: {}" +msgid "Protocol is required: {}" +msgstr "协议重复: {}" + +#: assets/serializers/asset/host.py:12 +msgid "Vendor" +msgstr "制造商" + +#: assets/serializers/asset/host.py:13 +msgid "Model" +msgstr "型号" + +#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:296 +msgid "Serial number" +msgstr "序列号" + +#: assets/serializers/asset/host.py:16 +msgid "CPU model" +msgstr "CPU型号" + +#: assets/serializers/asset/host.py:17 +msgid "CPU count" +msgstr "CPU数量" + +#: assets/serializers/asset/host.py:18 +msgid "CPU cores" +msgstr "CPU核数" + +#: assets/serializers/asset/host.py:19 +msgid "CPU vcpus" +msgstr "CPU总数" + +#: assets/serializers/asset/host.py:20 +msgid "Memory" +msgstr "内存" + +#: assets/serializers/asset/host.py:21 +msgid "Disk total" +msgstr "硬盘大小" + +#: assets/serializers/asset/host.py:22 +msgid "Disk info" +msgstr "硬盘信息" + +#: assets/serializers/asset/host.py:24 +msgid "OS" +msgstr "操作系统" + +#: assets/serializers/asset/host.py:25 +msgid "OS version" +msgstr "系统版本" + +#: assets/serializers/asset/host.py:26 +msgid "OS arch" +msgstr "系统架构" + +#: assets/serializers/asset/host.py:27 +msgid "Hostname raw" +msgstr "主机名原始" + +#: assets/serializers/asset/host.py:28 +msgid "Asset number" +msgstr "资产编号" + +#: assets/serializers/base.py:24 +msgid "Key password" +msgstr "密钥密码" + +#: assets/serializers/cagegory.py:9 +msgid "Constraints" +msgstr "" + +#: assets/serializers/cagegory.py:15 +#, fuzzy +#| msgid "Type" +msgid "Types" +msgstr "类型" #: assets/serializers/domain.py:14 assets/serializers/label.py:12 -#: assets/serializers/system_user.py:63 -#: perms/serializers/asset/permission.py:49 +#: perms/serializers/permission.py:83 msgid "Assets amount" msgstr "资产数量" #: assets/serializers/domain.py:15 -msgid "Applications amount" -msgstr "应用数量" - -#: assets/serializers/domain.py:16 msgid "Gateways count" msgstr "网关数量" +#: assets/serializers/label.py:13 assets/serializers/mixin.py:7 +msgid "Category display" +msgstr "类别名称" + +#: assets/serializers/mixin.py:10 audits/serializers.py:27 +#: authentication/serializers/connection_token.py:20 +#: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 +msgid "Type display" +msgstr "类型名称" + #: assets/serializers/node.py:17 msgid "value" msgstr "值" @@ -1197,80 +1300,43 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:35 -msgid "SSH key fingerprint" -msgstr "密钥指纹" +#: assets/serializers/platform.py:24 +#, fuzzy +#| msgid "MFA enabled" +msgid "SFTP enabled" +msgstr "MFA 已启用" -#: assets/serializers/system_user.py:40 -#: perms/serializers/application/permission.py:46 -msgid "Apps amount" -msgstr "应用数量" +#: assets/serializers/platform.py:25 +#, fuzzy +#| msgid "SFTP Root" +msgid "SFTP home" +msgstr "SFTP根路径" -#: assets/serializers/system_user.py:62 -#: perms/serializers/asset/permission.py:50 -msgid "Nodes amount" -msgstr "节点数量" +#: assets/serializers/platform.py:28 +#, fuzzy +#| msgid "Auto" +msgid "Auto fill" +msgstr "自动" -#: assets/serializers/system_user.py:64 assets/serializers/system_user.py:269 -msgid "Login mode display" -msgstr "认证方式名称" +#: assets/serializers/platform.py:29 +#, fuzzy +#| msgid "Username attr" +msgid "Username selector" +msgstr "用户名属性" -#: assets/serializers/system_user.py:66 -msgid "Ad domain" -msgstr "Ad 网域" +#: assets/serializers/platform.py:30 +#, fuzzy +#| msgid "Password rules" +msgid "Password selector" +msgstr "密码规则" -#: assets/serializers/system_user.py:67 -msgid "Is asset protocol" -msgstr "资产协议" +#: assets/serializers/platform.py:31 +msgid "Submit selector" +msgstr "" -#: assets/serializers/system_user.py:68 -msgid "Only ssh and automatic login system users are supported" -msgstr "仅支持ssh协议和自动登录的系统用户" - -#: assets/serializers/system_user.py:108 -msgid "Username same with user with protocol {} only allow 1" -msgstr "用户名和用户相同的一种协议只允许存在一个" - -#: assets/serializers/system_user.py:121 common/validators.py:14 -msgid "Special char not allowed" -msgstr "不能包含特殊字符" - -#: assets/serializers/system_user.py:131 -msgid "* Automatic login mode must fill in the username." -msgstr "自动登录模式,必须填写用户名" - -#: assets/serializers/system_user.py:146 -msgid "Path should starts with /" -msgstr "路径应该以 / 开头" - -#: assets/serializers/system_user.py:158 -msgid "Password or private key required" -msgstr "密码或密钥密码需要一个" - -#: assets/serializers/system_user.py:172 -msgid "Only ssh protocol system users are allowed" -msgstr "仅允许ssh协议的系统用户" - -#: assets/serializers/system_user.py:176 -msgid "The protocol must be consistent with the current user: {}" -msgstr "协议必须和当前用户保持一致: {}" - -#: assets/serializers/system_user.py:180 -msgid "Only system users with automatic login are allowed" -msgstr "仅允许自动登录的系统用户" - -#: assets/serializers/system_user.py:288 -msgid "System user name" -msgstr "系统用户名称" - -#: assets/serializers/system_user.py:289 orgs/mixins/serializers.py:26 -#: rbac/serializers/rolebinding.py:23 -msgid "Org name" -msgstr "组织名称" - -#: assets/serializers/system_user.py:298 -msgid "Asset hostname" -msgstr "资产主机名" +#: assets/serializers/platform.py:64 +msgid "Primary" +msgstr "" #: assets/serializers/utils.py:11 msgid "Password can not contains `{{` " @@ -1288,7 +1354,7 @@ msgstr "密码不能包含 `\"` 字符" msgid "The asset {} system platform {} does not support run Ansible tasks" msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" -#: assets/tasks/account_connectivity.py:107 +#: assets/tasks/account_connectivity.py:108 msgid "Test account connectivity: " msgstr "测试账号可连接性: " @@ -1296,11 +1362,11 @@ msgstr "测试账号可连接性: " msgid "Test assets connectivity. " msgstr "测试资产可连接性. " -#: assets/tasks/asset_connectivity.py:91 assets/tasks/asset_connectivity.py:102 +#: assets/tasks/asset_connectivity.py:94 assets/tasks/asset_connectivity.py:107 msgid "Test assets connectivity: " msgstr "测试资产可连接性: " -#: assets/tasks/asset_connectivity.py:113 +#: assets/tasks/asset_connectivity.py:121 msgid "Test if the assets under the node are connectable: " msgstr "测试节点下资产是否可连接: " @@ -1320,65 +1386,27 @@ msgstr "获取资产信息失败:{}" msgid "Update some assets hardware info. " msgstr "更新资产硬件信息. " -#: assets/tasks/gather_asset_hardware_info.py:114 +#: assets/tasks/gather_asset_hardware_info.py:118 msgid "Update asset hardware info: " msgstr "更新资产硬件信息: " -#: assets/tasks/gather_asset_hardware_info.py:120 +#: assets/tasks/gather_asset_hardware_info.py:124 msgid "Update assets hardware info: " msgstr "更新资产硬件信息: " -#: assets/tasks/gather_asset_hardware_info.py:137 +#: assets/tasks/gather_asset_hardware_info.py:146 msgid "Update node asset hardware information: " msgstr "更新节点资产硬件信息: " -#: assets/tasks/gather_asset_users.py:111 +#: assets/tasks/gather_asset_users.py:110 msgid "Gather assets users" msgstr "收集资产上的用户" -#: assets/tasks/nodes_amount.py:27 +#: assets/tasks/nodes_amount.py:29 msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "自检程序已经在运行,不能重复启动" -#: assets/tasks/push_system_user.py:201 -msgid "System user is dynamic: {}" -msgstr "系统用户是动态的: {}" - -#: assets/tasks/push_system_user.py:242 -msgid "Start push system user for platform: [{}]" -msgstr "推送系统用户到平台: [{}]" - -#: assets/tasks/push_system_user.py:243 -#: assets/tasks/system_user_connectivity.py:106 -msgid "Hosts count: {}" -msgstr "主机数量: {}" - -#: assets/tasks/push_system_user.py:264 assets/tasks/push_system_user.py:297 -msgid "Push system users to assets: " -msgstr "推送系统用户到入资产: " - -#: assets/tasks/push_system_user.py:276 -msgid "Push system users to asset: " -msgstr "推送系统用户到入资产: " - -#: assets/tasks/system_user_connectivity.py:56 -msgid "Dynamic system user not support test" -msgstr "动态系统用户不支持测试" - -#: assets/tasks/system_user_connectivity.py:105 -msgid "Start test system user connectivity for platform: [{}]" -msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" - -#: assets/tasks/system_user_connectivity.py:118 -#: assets/tasks/system_user_connectivity.py:129 -msgid "Test system user connectivity: " -msgstr "测试系统用户可连接性: " - -#: assets/tasks/system_user_connectivity.py:148 -msgid "Test system user connectivity period: " -msgstr "定期测试系统用户可连接性: " - #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" msgstr "资产已经被禁用, 跳过: {}" @@ -1430,7 +1458,7 @@ msgid "Symlink" msgstr "建立软链接" #: audits/models.py:38 audits/models.py:66 audits/models.py:89 -#: terminal/models/session.py:51 terminal/models/sharing.py:96 +#: terminal/models/session.py:37 terminal/models/sharing.py:96 msgid "Remote addr" msgstr "远端地址" @@ -1443,9 +1471,8 @@ msgid "Filename" msgstr "文件名" #: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:104 -#: tickets/views/approve.py:115 -#: xpack/plugins/change_auth_plan/serializers/app.py:87 -#: xpack/plugins/change_auth_plan/serializers/asset.py:198 +#: tickets/views/approve.py:114 +#: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" msgstr "成功" @@ -1468,7 +1495,7 @@ msgstr "查看" msgid "Update" msgstr "更新" -#: audits/models.py:64 audits/serializers.py:63 +#: audits/models.py:64 audits/serializers.py:61 msgid "Resource Type" msgstr "资源类型" @@ -1497,10 +1524,6 @@ msgstr "改密日志" msgid "Disabled" msgstr "禁用" -#: audits/models.py:112 settings/models.py:37 -msgid "Enabled" -msgstr "启用" - #: audits/models.py:113 msgid "-" msgstr "-" @@ -1519,7 +1542,7 @@ msgstr "登录IP" msgid "Login city" msgstr "登录城市" -#: audits/models.py:125 audits/serializers.py:44 +#: audits/models.py:125 audits/serializers.py:42 msgid "User agent" msgstr "用户代理" @@ -1530,9 +1553,9 @@ msgstr "用户代理" msgid "MFA" msgstr "MFA" -#: audits/models.py:128 terminal/models/status.py:33 -#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175 -#: xpack/plugins/cloud/models.py:227 +#: audits/models.py:128 ops/models/base.py:48 terminal/models/status.py:33 +#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:223 msgid "Status" msgstr "状态" @@ -1540,7 +1563,7 @@ msgstr "状态" msgid "Date login" msgstr "登录日期" -#: audits/models.py:130 audits/serializers.py:46 +#: audits/models.py:130 audits/serializers.py:44 msgid "Authentication backend" msgstr "认证方式" @@ -1548,48 +1571,22 @@ msgstr "认证方式" msgid "User login log" msgstr "用户登录日志" -#: audits/serializers.py:14 +#: audits/serializers.py:12 msgid "Operate display" msgstr "操作名称" -#: audits/serializers.py:30 tickets/serializers/ticket/ticket.py:18 +#: audits/serializers.py:28 tickets/serializers/ticket/ticket.py:18 msgid "Status display" msgstr "状态名称" -#: audits/serializers.py:31 +#: audits/serializers.py:29 msgid "MFA display" msgstr "MFA名称" -#: audits/serializers.py:45 +#: audits/serializers.py:43 msgid "Reason display" msgstr "原因描述" -#: audits/serializers.py:84 -msgid "Hosts display" -msgstr "主机名称" - -#: audits/serializers.py:96 ops/models/command.py:27 -#: xpack/plugins/cloud/models.py:173 -msgid "Result" -msgstr "结果" - -#: audits/serializers.py:98 terminal/serializers/storage.py:157 -msgid "Hosts" -msgstr "主机" - -#: audits/serializers.py:99 -msgid "Run as" -msgstr "运行用户" - -#: audits/serializers.py:101 -msgid "Run as display" -msgstr "运行用户名称" - -#: audits/serializers.py:102 authentication/models.py:81 -#: rbac/serializers/rolebinding.py:21 -msgid "User display" -msgstr "用户名称" - #: audits/signal_handlers.py:49 msgid "SSH Key" msgstr "SSH 密钥" @@ -1620,7 +1617,7 @@ msgstr "飞书" msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:56 authentication/models.py:267 +#: audits/signal_handlers.py:56 authentication/models.py:255 msgid "Temporary token" msgstr "临时密码" @@ -1639,159 +1636,75 @@ msgid "{User} LEFT {UserGroup}" msgstr "{User} 离开 {UserGroup}" #: audits/signal_handlers.py:73 -msgid "Asset and SystemUser" -msgstr "资产与系统用户" - -#: audits/signal_handlers.py:74 -#, python-brace-format -msgid "{Asset} ADD {SystemUser}" -msgstr "{Asset} 添加 {SystemUser}" - -#: audits/signal_handlers.py:75 -#, python-brace-format -msgid "{Asset} REMOVE {SystemUser}" -msgstr "{Asset} 移除 {SystemUser}" - -#: audits/signal_handlers.py:78 msgid "Node and Asset" msgstr "节点与资产" -#: audits/signal_handlers.py:79 +#: audits/signal_handlers.py:74 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 添加 {Asset}" -#: audits/signal_handlers.py:80 +#: audits/signal_handlers.py:75 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 移除 {Asset}" -#: audits/signal_handlers.py:83 +#: audits/signal_handlers.py:78 msgid "User asset permissions" msgstr "用户资产授权" -#: audits/signal_handlers.py:84 +#: audits/signal_handlers.py:79 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 添加 {User}" -#: audits/signal_handlers.py:85 +#: audits/signal_handlers.py:80 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 移除 {User}" -#: audits/signal_handlers.py:88 +#: audits/signal_handlers.py:83 msgid "User group asset permissions" msgstr "用户组资产授权" -#: audits/signal_handlers.py:89 +#: audits/signal_handlers.py:84 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:90 +#: audits/signal_handlers.py:85 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:93 perms/models/asset_permission.py:29 +#: audits/signal_handlers.py:88 perms/models/asset_permission.py:90 msgid "Asset permission" msgstr "资产授权" -#: audits/signal_handlers.py:94 +#: audits/signal_handlers.py:89 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 添加 {Asset}" -#: audits/signal_handlers.py:95 +#: audits/signal_handlers.py:90 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 移除 {Asset}" -#: audits/signal_handlers.py:98 +#: audits/signal_handlers.py:93 msgid "Node permission" msgstr "节点授权" -#: audits/signal_handlers.py:99 +#: audits/signal_handlers.py:94 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 添加 {Node}" -#: audits/signal_handlers.py:100 +#: audits/signal_handlers.py:95 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 移除 {Node}" -#: audits/signal_handlers.py:103 -msgid "Asset permission and SystemUser" -msgstr "资产授权与系统用户" - -#: audits/signal_handlers.py:104 -#, python-brace-format -msgid "{AssetPermission} ADD {SystemUser}" -msgstr "{AssetPermission} 添加 {SystemUser}" - -#: audits/signal_handlers.py:105 -#, python-brace-format -msgid "{AssetPermission} REMOVE {SystemUser}" -msgstr "{AssetPermission} 移除 {SystemUser}" - -#: audits/signal_handlers.py:108 -msgid "User application permissions" -msgstr "用户应用授权" - -#: audits/signal_handlers.py:109 -#, python-brace-format -msgid "{ApplicationPermission} ADD {User}" -msgstr "{ApplicationPermission} 添加 {User}" - -#: audits/signal_handlers.py:110 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {User}" -msgstr "{ApplicationPermission} 移除 {User}" - -#: audits/signal_handlers.py:113 -msgid "User group application permissions" -msgstr "用户组应用授权" - -#: audits/signal_handlers.py:114 -#, python-brace-format -msgid "{ApplicationPermission} ADD {UserGroup}" -msgstr "{ApplicationPermission} 添加 {UserGroup}" - -#: audits/signal_handlers.py:115 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {UserGroup}" -msgstr "{ApplicationPermission} 移除 {UserGroup}" - -#: audits/signal_handlers.py:118 perms/models/application_permission.py:38 -msgid "Application permission" -msgstr "应用授权" - -#: audits/signal_handlers.py:119 -#, python-brace-format -msgid "{ApplicationPermission} ADD {Application}" -msgstr "{ApplicationPermission} 添加 {Application}" - -#: audits/signal_handlers.py:120 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {Application}" -msgstr "{ApplicationPermission} 移除 {Application}" - -#: audits/signal_handlers.py:123 -msgid "Application permission and SystemUser" -msgstr "应用授权与系统用户" - -#: audits/signal_handlers.py:124 -#, python-brace-format -msgid "{ApplicationPermission} ADD {SystemUser}" -msgstr "{ApplicationPermission} 添加 {SystemUser}" - -#: audits/signal_handlers.py:125 -#, python-brace-format -msgid "{ApplicationPermission} REMOVE {SystemUser}" -msgstr "{ApplicationPermission} 移除 {SystemUser}" - #: authentication/api/confirm.py:40 msgid "This action require verify your MFA" msgstr "此操作需要验证您的 MFA" @@ -1855,7 +1768,7 @@ msgstr "无效的令牌头。符号字符串不应包含无效字符。" msgid "Invalid token or cache refreshed." msgstr "刷新的令牌或缓存无效。" -#: authentication/backends/oauth2/backends.py:155 authentication/models.py:158 +#: authentication/backends/oauth2/backends.py:155 authentication/models.py:146 msgid "User invalid, disabled or expired" msgstr "用户无效,已禁用或已过期" @@ -1936,9 +1849,13 @@ msgid "" msgstr "账号已被锁定(请联系管理员解锁或{}分钟后重试)" #: authentication/errors/const.py:51 +#, fuzzy +#| msgid "" +#| "The ip has been locked (please contact admin to unlock it or try again " +#| "after {} minutes)" msgid "" -"The ip has been locked (please contact admin to unlock it or try again after " -"{} minutes)" +"The address has been locked (please contact admin to unlock it or try again " +"after {} minutes)" msgstr "IP 已被锁定(请联系管理员解锁或{}分钟后重试)" #: authentication/errors/const.py:59 @@ -2107,83 +2024,78 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:37 -msgid "Access key" -msgstr "Access key" - -#: authentication/models.py:44 +#: authentication/models.py:45 msgid "Private Token" msgstr "SSH 密钥" -#: authentication/models.py:53 +#: authentication/models.py:54 msgid "Expired" msgstr "过期时间" -#: authentication/models.py:57 +#: authentication/models.py:58 msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:72 authentication/models.py:261 -#: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 -msgid "Secret" -msgstr "密钥" - -#: authentication/models.py:74 authentication/models.py:264 -#: perms/models/base.py:90 tickets/models/ticket/apply_application.py:30 -#: tickets/models/ticket/apply_asset.py:24 users/models/user.py:707 +#: authentication/models.py:75 authentication/models.py:252 +#: perms/models/asset_permission.py:79 +#: tickets/models/ticket/apply_application.py:29 +#: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 msgid "Date expired" msgstr "失效日期" -#: authentication/models.py:93 +#: authentication/models.py:82 rbac/serializers/rolebinding.py:21 +msgid "User display" +msgstr "用户名称" + +#: authentication/models.py:87 msgid "Asset display" msgstr "资产名称" -#: authentication/models.py:104 +#: authentication/models.py:92 msgid "Connection token" msgstr "连接令牌" -#: authentication/models.py:106 +#: authentication/models.py:94 msgid "Can view connection token secret" msgstr "可以查看连接令牌密文" -#: authentication/models.py:149 +#: authentication/models.py:137 msgid "Connection token expired at: {}" msgstr "连接令牌过期: {}" -#: authentication/models.py:154 +#: authentication/models.py:142 msgid "User not exists" msgstr "用户不存在" -#: authentication/models.py:163 +#: authentication/models.py:151 msgid "System user not exists" msgstr "系统用户不存在" -#: authentication/models.py:169 +#: authentication/models.py:157 msgid "Asset not exists" msgstr "资产不存在" -#: authentication/models.py:173 +#: authentication/models.py:161 msgid "Asset inactive" msgstr "资产未激活" -#: authentication/models.py:180 +#: authentication/models.py:168 msgid "User has no permission to access asset or permission expired" msgstr "用户没有权限访问资产或权限已过期" -#: authentication/models.py:188 +#: authentication/models.py:176 msgid "Application not exists" msgstr "应用不存在" -#: authentication/models.py:195 +#: authentication/models.py:183 msgid "User has no permission to access application or permission expired" msgstr "用户没有权限访问应用或权限已过期" -#: authentication/models.py:262 +#: authentication/models.py:250 msgid "Verified" msgstr "已校验" -#: authentication/models.py:283 +#: authentication/models.py:271 msgid "Super connection token" msgstr "超级连接令牌" @@ -2195,24 +2107,21 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" -#: authentication/serializers/connection_token.py:23 -#: xpack/plugins/cloud/models.py:34 +#: authentication/serializers/connection_token.py:21 +#: xpack/plugins/cloud/models.py:36 msgid "Validity" msgstr "有效" -#: authentication/serializers/connection_token.py:24 +#: authentication/serializers/connection_token.py:22 msgid "Expired time" msgstr "过期时间" -#: authentication/serializers/connection_token.py:73 +#: authentication/serializers/connection_token.py:68 msgid "Asset or application required" msgstr "资产或应用必填" -#: authentication/serializers/token.py:79 -#: perms/serializers/application/permission.py:20 -#: perms/serializers/application/permission.py:41 -#: perms/serializers/asset/permission.py:19 -#: perms/serializers/asset/permission.py:45 users/serializers/user.py:148 +#: authentication/serializers/token.py:79 perms/serializers/permission.py:60 +#: perms/serializers/permission.py:87 users/serializers/user.py:148 msgid "Is valid" msgstr "账号是否有效" @@ -2262,7 +2171,7 @@ msgstr "删除成功" #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:45 +#: templates/_modal.html:22 tickets/const.py:44 msgid "Close" msgstr "关闭" @@ -2299,7 +2208,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 ops/tasks.py:145 ops/tasks.py:148 +#: jumpserver/conf.py:390 ops/tasks.py:146 ops/tasks.py:152 ops/tasks.py:155 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2383,7 +2292,7 @@ msgid "" msgstr "如果这次公钥更新不是由你发起的,那么你的账号可能存在安全问题" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:20 +#: templates/flash_message_standalone.html:28 tickets/const.py:19 msgid "Cancel" msgstr "取消" @@ -2583,6 +2492,14 @@ msgstr "%(name)s 创建成功" msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" +#: common/const/choices.py:10 +msgid "Manual trigger" +msgstr "手动触发" + +#: common/const/choices.py:11 +msgid "Timing trigger" +msgstr "定时触发" + #: common/db/encoder.py:11 msgid "ugettext_lazy" msgstr "ugettext_lazy" @@ -2615,7 +2532,7 @@ msgstr "编码数据为 text" msgid "Encrypt field using Secret Key" msgstr "加密的字段" -#: common/db/models.py:113 +#: common/db/models.py:75 msgid "Updated by" msgstr "更新人" @@ -2623,6 +2540,17 @@ msgstr "更新人" msgid "Object" msgstr "对象" +#: common/drf/fields.py:70 +#, fuzzy, python-brace-format +#| msgid "%s object does not exist." +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "%s对象不存在" + +#: common/drf/fields.py:71 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "" + #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" msgstr "文件内容太大 (最大长度 `{}` 字节)" @@ -2631,6 +2559,10 @@ msgstr "文件内容太大 (最大长度 `{}` 字节)" msgid "Parse file error: {}" msgstr "解析文件错误: {}" +#: common/drf/serializers/common.py:86 +msgid "Children" +msgstr "" + #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." @@ -2737,10 +2669,20 @@ msgstr "验证码错误" msgid "Please wait {} seconds before sending" msgstr "请在 {} 秒后发送" -#: common/utils/ip/geoip/utils.py:26 common/utils/ip/utils.py:78 +#: common/utils/ip/geoip/utils.py:26 msgid "Invalid ip" msgstr "无效IP" +#: common/utils/ip/utils.py:78 +#, fuzzy +#| msgid "Invalid signature." +msgid "Invalid address" +msgstr "签名无效" + +#: common/validators.py:14 +msgid "Special char not allowed" +msgstr "不能包含特殊字符" + #: common/validators.py:32 msgid "This field must be unique." msgstr "字段必须唯一" @@ -2805,148 +2747,173 @@ msgstr "邮件" msgid "Site message" msgstr "站内信" +#: ops/ansible/inventory.py:76 +#, fuzzy +#| msgid "Account unavailable" +msgid "No account available" +msgstr "账号无效" + +#: ops/ansible/inventory.py:171 +#, fuzzy +#| msgid "User disabled." +msgid "Ansible disabled" +msgstr "用户已禁用" + +#: ops/ansible/inventory.py:186 +msgid "Skip hosts below:" +msgstr "" + #: ops/api/celery.py:61 ops/api/celery.py:76 msgid "Waiting task start" msgstr "等待任务开始" -#: ops/api/command.py:56 -msgid "Not has host {} permission" -msgstr "没有该主机 {} 权限" - #: ops/apps.py:9 ops/notifications.py:16 msgid "App ops" msgstr "作业中心" -#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: settings/serializers/auth/ldap.py:72 +#: ops/const.py:6 +msgid "Push" +msgstr "" + +#: ops/const.py:7 +#, fuzzy +#| msgid "Verified" +msgid "Verify" +msgstr "已校验" + +#: ops/const.py:8 +msgid "Collect" +msgstr "" + +#: ops/const.py:9 +#, fuzzy +#| msgid "Change Password" +msgid "Change password" +msgstr "更改密码" + +#: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 +msgid "Custom password" +msgstr "自定义密码" + +#: ops/mixin.py:27 ops/mixin.py:90 settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "周期执行" -#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 +#: ops/mixin.py:31 ops/mixin.py:88 ops/mixin.py:107 #: settings/serializers/auth/ldap.py:69 msgid "Regularly perform" msgstr "定期执行" -#: ops/mixin.py:112 +#: ops/mixin.py:110 msgid "Interval" msgstr "间隔" -#: ops/mixin.py:122 +#: ops/mixin.py:120 msgid "* Please enter a valid crontab expression" msgstr "* 请输入有效的 crontab 表达式" -#: ops/mixin.py:129 +#: ops/mixin.py:127 msgid "Range {} to {}" msgstr "输入在 {} - {} 范围之间" -#: ops/mixin.py:140 +#: ops/mixin.py:138 msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" -#: ops/mixin.py:151 -msgid "" -"eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " -"crontab expressions (Online tools)
Note: If both Regularly " -"perform and Cycle perform are set, give priority to Regularly perform" -msgstr "" -"eg:每周日 03:05 执行 <5 3 * * 0>
提示: 使用5位 Linux crontab 表达式 <" -"分 时 日 月 星期> (在线工" -"具
注意: 如果同时设置了定期执行和周期执行,优先使用定期执行" +#: ops/models/adhoc.py:18 +msgid "Pattern" +msgstr "模式" -#: ops/mixin.py:162 -msgid "Unit: hour" -msgstr "单位: 时" +#: ops/models/adhoc.py:19 +msgid "Module" +msgstr "" + +#: ops/models/adhoc.py:20 ops/models/celery.py:15 terminal/models/task.py:17 +msgid "Args" +msgstr "参数" + +#: ops/models/adhoc.py:21 ops/models/base.py:20 ops/models/playbook.py:27 +#, fuzzy +#| msgid "Command execution" +msgid "Last execution" +msgstr "命令执行" #: ops/models/adhoc.py:36 -msgid "Callback" -msgstr "回调" +msgid "Adhoc" +msgstr "" -#: ops/models/adhoc.py:135 terminal/models/task.py:26 -#: xpack/plugins/gathered_user/models.py:73 -msgid "Task" -msgstr "任务" - -#: ops/models/adhoc.py:138 -msgid "Can view task monitor" -msgstr "可以查看任务监控" - -#: ops/models/adhoc.py:154 -msgid "Tasks" -msgstr "任务" - -#: ops/models/adhoc.py:156 -msgid "Options" -msgstr "选项" - -#: ops/models/adhoc.py:158 -msgid "Run as admin" -msgstr "再次执行" - -#: ops/models/adhoc.py:161 -msgid "Become" -msgstr "Become" - -#: ops/models/adhoc.py:162 -msgid "Create by" -msgstr "创建者" - -#: ops/models/adhoc.py:243 -msgid "AdHoc" -msgstr "任务各版本" - -#: ops/models/adhoc.py:252 -msgid "Task display" -msgstr "任务名称" - -#: ops/models/adhoc.py:254 -msgid "Host amount" -msgstr "主机数量" - -#: ops/models/adhoc.py:256 -msgid "Start time" -msgstr "开始时间" - -#: ops/models/adhoc.py:257 -msgid "End time" -msgstr "完成时间" - -#: ops/models/adhoc.py:259 ops/models/command.py:29 -#: terminal/serializers/session.py:40 -msgid "Is finished" -msgstr "是否完成" - -#: ops/models/adhoc.py:261 -msgid "Adhoc raw result" -msgstr "结果" - -#: ops/models/adhoc.py:262 -msgid "Adhoc result summary" -msgstr "汇总" - -#: ops/models/adhoc.py:339 +#: ops/models/adhoc.py:54 msgid "AdHoc execution" msgstr "任务执行" -#: ops/models/command.py:32 -msgid "Date finished" -msgstr "结束日期" +#: ops/models/base.py:16 ops/models/base.py:52 terminal/models/sharing.py:24 +msgid "Creator" +msgstr "创建者" -#: ops/models/command.py:113 -msgid "Task start" -msgstr "任务开始" +#: ops/models/base.py:19 +#, fuzzy +#| msgid "Account key" +msgid "Account policy" +msgstr "账号密钥" -#: ops/models/command.py:147 -msgid "Command `{}` is forbidden ........" -msgstr "命令 `{}` 不允许被执行 ......." +#: ops/models/base.py:21 +#, fuzzy +#| msgid "Date last sync" +msgid "Date last run" +msgstr "最后同步日期" -#: ops/models/command.py:160 -msgid "Task end" -msgstr "任务结束" +#: ops/models/base.py:50 xpack/plugins/cloud/models.py:169 +msgid "Result" +msgstr "结果" -#: ops/models/command.py:164 -msgid "Command execution" -msgstr "命令执行" +#: ops/models/base.py:51 +msgid "Summary" +msgstr "" + +#: ops/models/celery.py:16 terminal/models/task.py:18 +msgid "Kwargs" +msgstr "其它参数" + +#: ops/models/celery.py:17 tickets/models/comment.py:13 +#: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 +msgid "State" +msgstr "状态" + +#: ops/models/celery.py:18 terminal/models/sharing.py:111 tickets/const.py:25 +#: xpack/plugins/change_auth_plan/models/base.py:188 +msgid "Finished" +msgstr "结束" + +#: ops/models/playbook.py:10 +msgid "Path" +msgstr "" + +#: ops/models/playbook.py:18 +msgid "Playbook template" +msgstr "" + +#: ops/models/playbook.py:23 +msgid "Playbook" +msgstr "" + +#: ops/models/playbook.py:24 +msgid "Owner" +msgstr "" + +#: ops/models/playbook.py:26 settings/serializers/auth/sms.py:64 +msgid "Template" +msgstr "模板" + +#: ops/models/playbook.py:38 terminal/models/task.py:26 +#: xpack/plugins/gathered_user/models.py:68 +msgid "Task" +msgstr "任务" + +#: ops/models/playbook.py:39 +#, fuzzy +#| msgid "Run user" +msgid "Run dir" +msgstr "运行的用户" #: ops/notifications.py:17 msgid "Server performance" @@ -2976,11 +2943,23 @@ msgstr "内存使用率超过 {max_threshold}%: => {value}" msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" -#: ops/tasks.py:72 +#: ops/tasks.py:33 +#, fuzzy +#| msgid "Run asset" +msgid "Run ansible task" +msgstr "运行的资产" + +#: ops/tasks.py:57 +#, fuzzy +#| msgid "Run command" +msgid "Run ansible command" +msgstr "运行的命令" + +#: ops/tasks.py:79 msgid "Clean task history period" msgstr "定期清除任务历史" -#: ops/tasks.py:85 +#: ops/tasks.py:92 msgid "Clean celery log period" msgstr "定期清除Celery日志" @@ -2992,17 +2971,17 @@ msgstr "任务列表" msgid "Update task content: {}" msgstr "更新任务内容: {}" -#: orgs/api.py:69 +#: orgs/api.py:67 msgid "The current organization ({}) cannot be deleted" msgstr "当前组织 ({}) 不能被删除" -#: orgs/api.py:74 +#: orgs/api.py:72 msgid "" "LDAP synchronization is set to the current organization. Please switch to " "another organization before deleting" msgstr "LDAP 同步设置组织为当前组织,请切换其他组织后再进行删除操作" -#: orgs/api.py:83 +#: orgs/api.py:81 msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" @@ -3010,116 +2989,109 @@ msgstr "组织存在资源 ({}) 不能被删除" msgid "App organizations" msgstr "组织管理" -#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:85 -#: orgs/models.py:217 rbac/const.py:7 rbac/models/rolebinding.py:48 +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:87 +#: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 #: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 msgid "Organization" msgstr "组织" +#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 +msgid "Org name" +msgstr "组织名称" + #: orgs/models.py:79 msgid "GLOBAL" msgstr "全局组织" -#: orgs/models.py:87 +#: orgs/models.py:81 +msgid "DEFAULT" +msgstr "" + +#: orgs/models.py:83 +msgid "SYSTEM" +msgstr "" + +#: orgs/models.py:89 msgid "Can view root org" msgstr "可以查看全局组织" -#: orgs/models.py:88 +#: orgs/models.py:90 msgid "Can view all joined org" msgstr "可以查看所有加入的组织" -#: orgs/models.py:222 rbac/models/role.py:46 rbac/models/rolebinding.py:44 -#: users/models/user.py:675 -msgid "Role" -msgstr "角色" - #: perms/apps.py:9 msgid "App permissions" msgstr "授权管理" -#: perms/exceptions.py:9 -msgid "The administrator is modifying permissions. Please wait" -msgstr "管理员正在修改授权,请稍等" +#: perms/models/asset_permission.py:72 perms/serializers/permission.py:59 +#: perms/serializers/permission.py:85 +#: tickets/models/ticket/apply_application.py:26 +#: tickets/models/ticket/apply_asset.py:19 +msgid "Actions" +msgstr "动作" -#: perms/exceptions.py:14 -msgid "The authorization cannot be revoked for the time being" -msgstr "该授权暂时不能撤销" - -#: perms/models/application_permission.py:111 -msgid "Permed application" -msgstr "授权的应用" - -#: perms/models/application_permission.py:114 -msgid "Can view my apps" -msgstr "可以查看我的应用" - -#: perms/models/application_permission.py:115 -msgid "Can view user apps" -msgstr "可以查看用户授权的应用" - -#: perms/models/application_permission.py:116 -msgid "Can view usergroup apps" -msgstr "可以查看用户组授权的应用" - -#: perms/models/asset_permission.py:134 -msgid "Ungrouped" -msgstr "未分组" - -#: perms/models/asset_permission.py:136 -msgid "Favorite" -msgstr "收藏夹" - -#: perms/models/asset_permission.py:183 -msgid "Permed asset" -msgstr "授权的资产" - -#: perms/models/asset_permission.py:185 -msgid "Can view my assets" -msgstr "可以查看我的资产" - -#: perms/models/asset_permission.py:186 -msgid "Can view user assets" -msgstr "可以查看用户授权的资产" - -#: perms/models/asset_permission.py:187 -msgid "Can view usergroup assets" -msgstr "可以查看用户组授权的资产" - -#: perms/models/base.py:55 -msgid "Connect" -msgstr "连接" - -#: perms/models/base.py:56 -msgid "Upload file" -msgstr "上传文件" - -#: perms/models/base.py:57 -msgid "Download file" -msgstr "下载文件" - -#: perms/models/base.py:58 -msgid "Upload download" -msgstr "上传下载" - -#: perms/models/base.py:59 -msgid "Clipboard copy" -msgstr "剪贴板复制" - -#: perms/models/base.py:60 -msgid "Clipboard paste" -msgstr "剪贴板粘贴" - -#: perms/models/base.py:61 -msgid "Clipboard copy paste" -msgstr "剪贴板复制粘贴" - -#: perms/models/base.py:94 +#: perms/models/asset_permission.py:83 msgid "From ticket" msgstr "来自工单" +#: perms/models/asset_permission.py:224 +msgid "Ungrouped" +msgstr "未分组" + +#: perms/models/asset_permission.py:226 +msgid "Favorite" +msgstr "收藏夹" + +#: perms/models/asset_permission.py:273 +msgid "Permed asset" +msgstr "授权的资产" + +#: perms/models/asset_permission.py:275 +msgid "Can view my assets" +msgstr "可以查看我的资产" + +#: perms/models/asset_permission.py:276 +msgid "Can view user assets" +msgstr "可以查看用户授权的资产" + +#: perms/models/asset_permission.py:277 +msgid "Can view usergroup assets" +msgstr "可以查看用户组授权的资产" + +#: perms/models/const.py:20 settings/serializers/terminal.py:12 +msgid "All" +msgstr "全部" + +#: perms/models/const.py:21 +msgid "Connect" +msgstr "连接" + +#: perms/models/const.py:22 +msgid "Upload file" +msgstr "上传文件" + +#: perms/models/const.py:23 +msgid "Download file" +msgstr "下载文件" + +#: perms/models/const.py:24 +msgid "Upload download" +msgstr "上传下载" + +#: perms/models/const.py:25 +msgid "Clipboard copy" +msgstr "剪贴板复制" + +#: perms/models/const.py:26 +msgid "Clipboard paste" +msgstr "剪贴板粘贴" + +#: perms/models/const.py:27 +msgid "Clipboard copy paste" +msgstr "剪贴板复制粘贴" + #: perms/notifications.py:12 perms/notifications.py:44 -#: perms/notifications.py:88 perms/notifications.py:119 msgid "today" msgstr "今" @@ -3139,71 +3111,39 @@ msgstr "资产授权规则将要过期" msgid "asset permissions of organization {}" msgstr "组织 ({}) 的资产授权" -#: perms/notifications.py:91 -msgid "Your permed applications is about to expire" -msgstr "你授权的应用即将过期" +#: perms/serializers/permission.py:48 +msgid "Users display" +msgstr "用户名称" -#: perms/notifications.py:95 -msgid "permed applications" -msgstr "授权的应用" +#: perms/serializers/permission.py:51 +msgid "User groups display" +msgstr "用户组名称" -#: perms/notifications.py:134 -msgid "Application permissions is about to expire" -msgstr "应用授权规则即将过期" +#: perms/serializers/permission.py:54 +msgid "Assets display" +msgstr "资产名称" -#: perms/notifications.py:138 -msgid "application permissions of organization {}" -msgstr "组织 ({}) 的应用授权" +#: perms/serializers/permission.py:57 +msgid "Nodes display" +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:89 -#: users/serializers/user.py:150 +#: perms/serializers/permission.py:61 perms/serializers/permission.py:86 +#: users/serializers/user.py:89 users/serializers/user.py:150 msgid "Is expired" msgstr "已过期" -#: perms/serializers/application/permission.py:43 -#: perms/serializers/asset/permission.py:47 rbac/serializers/role.py:26 +#: perms/serializers/permission.py:81 rbac/serializers/role.py:26 #: users/serializers/group.py:34 msgid "Users amount" msgstr "用户数量" -#: perms/serializers/application/permission.py:44 -#: perms/serializers/asset/permission.py:48 +#: perms/serializers/permission.py:82 msgid "User groups amount" msgstr "用户组数量" -#: perms/serializers/application/permission.py:45 -#: perms/serializers/asset/permission.py:51 -msgid "System users amount" -msgstr "系统用户数量" - -#: perms/serializers/application/permission.py:79 -msgid "" -"The application list contains applications that are different from the " -"permission type. ({})" -msgstr "应用列表中包含与授权类型不同的应用。({})" - -#: perms/serializers/asset/permission.py:21 -msgid "Users display" -msgstr "用户名称" - -#: perms/serializers/asset/permission.py:22 -msgid "User groups display" -msgstr "用户组名称" - -#: perms/serializers/asset/permission.py:23 -msgid "Assets display" -msgstr "资产名称" - -#: perms/serializers/asset/permission.py:24 -msgid "Nodes display" -msgstr "节点名称" - -#: perms/serializers/asset/permission.py:25 -msgid "System users display" -msgstr "系统用户名称" +#: perms/serializers/permission.py:84 +msgid "Nodes amount" +msgstr "节点数量" #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 @@ -3221,15 +3161,7 @@ msgstr "" msgid "If you have any question, please contact the administrator" msgstr "如果有疑问或需求,请联系系统管理员" -#: perms/tree/app.py:24 -msgid "My applications" -msgstr "我的应用" - -#: perms/tree/app.py:41 -msgid "Empty" -msgstr "空" - -#: perms/utils/asset/user_permission.py:620 rbac/tree.py:57 +#: perms/utils/user_permission.py:623 rbac/tree.py:57 msgid "My assets" msgstr "我的资产" @@ -3318,6 +3250,11 @@ msgstr "授权" msgid "Built-in" msgstr "内置" +#: rbac/models/role.py:46 rbac/models/rolebinding.py:44 +#: users/models/user.py:675 +msgid "Role" +msgstr "角色" + #: rbac/models/role.py:144 msgid "System role" msgstr "系统角色" @@ -3388,9 +3325,9 @@ msgstr "审计台" msgid "System setting" msgstr "系统设置" -#: rbac/tree.py:37 -msgid "Accounts" -msgstr "账号管理" +#: rbac/tree.py:29 +msgid "Other" +msgstr "其它" #: rbac/tree.py:41 msgid "Session audits" @@ -3844,10 +3781,6 @@ msgstr "原始号码(Src id)" msgid "Business type(Service id)" msgstr "业务类型(Service id)" -#: settings/serializers/auth/sms.py:64 -msgid "Template" -msgstr "模板" - #: settings/serializers/auth/sms.py:65 #, python-brace-format msgid "" @@ -3880,7 +3813,7 @@ msgid "SSO auth key TTL" msgstr "Token 有效期" #: settings/serializers/auth/sso.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:159 +#: xpack/plugins/cloud/serializers/account_attrs.py:169 msgid "Unit: second" msgstr "单位: 秒" @@ -4707,7 +4640,7 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远 msgid "Offline video player" msgstr "离线录像播放器" -#: terminal/api/endpoint.py:34 +#: terminal/api/endpoint.py:33 msgid "Not found protocol query params" msgstr "" @@ -4795,7 +4728,7 @@ msgstr "输出" #: terminal/backends/command/models.py:25 terminal/models/replay.py:9 #: terminal/models/sharing.py:19 terminal/models/sharing.py:78 #: terminal/templates/terminal/_msg_command_alert.html:10 -#: tickets/models/ticket/command_confirm.py:20 +#: tickets/models/ticket/command_confirm.py:17 msgid "Session" msgstr "会话" @@ -4915,42 +4848,38 @@ msgstr "可以上传会话录像" msgid "Can download session replay" msgstr "可以下载会话录像" -#: terminal/models/session.py:50 terminal/models/sharing.py:101 +#: terminal/models/session.py:36 terminal/models/sharing.py:101 msgid "Login from" msgstr "登录来源" -#: terminal/models/session.py:54 +#: terminal/models/session.py:40 msgid "Replay" msgstr "回放" -#: terminal/models/session.py:59 +#: terminal/models/session.py:44 msgid "Date end" msgstr "结束日期" -#: terminal/models/session.py:260 +#: terminal/models/session.py:236 msgid "Session record" msgstr "会话记录" -#: terminal/models/session.py:262 +#: terminal/models/session.py:238 msgid "Can monitor session" msgstr "可以监控会话" -#: terminal/models/session.py:263 +#: terminal/models/session.py:239 msgid "Can share session" msgstr "可以分享会话" -#: terminal/models/session.py:264 +#: terminal/models/session.py:240 msgid "Can terminate session" msgstr "可以终断会话" -#: terminal/models/session.py:265 +#: terminal/models/session.py:241 msgid "Can validate session action perm" msgstr "可以验证会话动作权限" -#: terminal/models/sharing.py:24 -msgid "Creator" -msgstr "创建者" - #: terminal/models/sharing.py:26 terminal/models/sharing.py:80 msgid "Verify code" msgstr "验证码" @@ -4991,11 +4920,6 @@ msgstr "加入日期" msgid "Date left" msgstr "结束日期" -#: terminal/models/sharing.py:111 tickets/const.py:26 -#: xpack/plugins/change_auth_plan/models/base.py:192 -msgid "Finished" -msgstr "结束" - #: terminal/models/sharing.py:116 msgid "Session join record" msgstr "会话加入记录" @@ -5044,14 +4968,6 @@ msgstr "命令存储" msgid "Replay storage" msgstr "录像存储" -#: terminal/models/task.py:17 -msgid "Args" -msgstr "参数" - -#: terminal/models/task.py:18 -msgid "Kwargs" -msgstr "其它参数" - #: terminal/models/terminal.py:103 msgid "type" msgstr "类型" @@ -5089,22 +5005,18 @@ msgid "" "If asset IP addresses under different endpoints conflict, use asset labels" msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" -#: terminal/serializers/session.py:15 terminal/serializers/session.py:42 +#: terminal/serializers/session.py:17 terminal/serializers/session.py:42 msgid "Terminal display" msgstr "终端显示" -#: terminal/serializers/session.py:32 +#: terminal/serializers/session.py:33 msgid "User ID" msgstr "用户 ID" -#: terminal/serializers/session.py:33 +#: terminal/serializers/session.py:34 msgid "Asset ID" msgstr "资产 ID" -#: terminal/serializers/session.py:34 -msgid "System user ID" -msgstr "系统用户 ID" - #: terminal/serializers/session.py:35 msgid "Login from display" msgstr "登录来源名称" @@ -5121,6 +5033,10 @@ msgstr "是否可加入" msgid "Terminal ID" msgstr "终端 ID" +#: terminal/serializers/session.py:40 +msgid "Is finished" +msgstr "是否完成" + #: terminal/serializers/session.py:41 msgid "Can terminate" msgstr "是否可中断" @@ -5147,7 +5063,7 @@ msgstr "访问密钥 ID(AK)" msgid "Access key secret" msgstr "访问密钥密文(SK)" -#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:220 +#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:216 msgid "Region" msgstr "地域" @@ -5179,6 +5095,10 @@ msgstr "主机无效" msgid "Port invalid" msgstr "端口无效" +#: terminal/serializers/storage.py:157 +msgid "Hosts" +msgstr "主机" + #: terminal/serializers/storage.py:160 msgid "Index by date" msgstr "按日期建索引" @@ -5215,67 +5135,59 @@ msgstr "查看" msgid "Tickets" msgstr "工单管理" -#: tickets/const.py:8 -msgid "General" -msgstr "一般" - #: tickets/const.py:10 msgid "Apply for asset" msgstr "申请资产" -#: tickets/const.py:11 -msgid "Apply for application" -msgstr "申请应用" - -#: tickets/const.py:17 tickets/const.py:25 tickets/const.py:44 +#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 msgid "Open" msgstr "打开" -#: tickets/const.py:18 tickets/const.py:31 +#: tickets/const.py:17 tickets/const.py:30 msgid "Approved" msgstr "已同意" -#: tickets/const.py:19 tickets/const.py:32 +#: tickets/const.py:18 tickets/const.py:31 msgid "Rejected" msgstr "已拒绝" -#: tickets/const.py:21 tickets/const.py:34 +#: tickets/const.py:20 tickets/const.py:33 msgid "Reopen" msgstr "" -#: tickets/const.py:30 tickets/const.py:38 +#: tickets/const.py:29 tickets/const.py:37 msgid "Pending" msgstr "待定的" -#: tickets/const.py:33 tickets/const.py:40 +#: tickets/const.py:32 tickets/const.py:39 msgid "Closed" msgstr "关闭的" -#: tickets/const.py:46 +#: tickets/const.py:45 msgid "Approve" msgstr "同意" -#: tickets/const.py:51 +#: tickets/const.py:50 msgid "One level" msgstr "1 级" -#: tickets/const.py:52 +#: tickets/const.py:51 msgid "Two level" msgstr "2 级" -#: tickets/const.py:56 +#: tickets/const.py:55 msgid "Super admin" msgstr "超级管理员" -#: tickets/const.py:57 +#: tickets/const.py:56 msgid "Org admin" msgstr "组织管理员" -#: tickets/const.py:58 +#: tickets/const.py:57 msgid "Super admin and org admin" msgstr "组织管理员或超级管理员" -#: tickets/const.py:59 +#: tickets/const.py:58 msgid "Custom user" msgstr "自定义用户" @@ -5283,14 +5195,7 @@ msgstr "自定义用户" msgid "Ticket already closed" msgstr "工单已经关闭" -#: tickets/handlers/apply_application.py:38 -msgid "" -"Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " -"processor: {}, ticket ID: {}" -msgstr "" -"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" - -#: tickets/handlers/apply_asset.py:37 +#: tickets/handlers/apply_asset.py:36 msgid "" "Created by the ticket ticket title: {} ticket applicant: {} ticket " "processor: {} ticket ID: {}" @@ -5325,11 +5230,6 @@ msgstr "申请登录的城市" msgid "Applied login datetime" msgstr "申请登录的日期" -#: tickets/models/comment.py:13 tickets/models/ticket/general.py:41 -#: tickets/models/ticket/general.py:277 -msgid "State" -msgstr "状态" - #: tickets/models/comment.py:14 msgid "common" msgstr "" @@ -5367,17 +5267,16 @@ msgstr "工单流程" msgid "Ticket session relation" msgstr "工单会话" -#: tickets/models/ticket/apply_application.py:12 +#: tickets/models/ticket/apply_application.py:11 #: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" msgstr "授权规则名称" -#: tickets/models/ticket/apply_application.py:21 +#: tickets/models/ticket/apply_application.py:20 msgid "Apply applications" msgstr "申请应用" -#: tickets/models/ticket/apply_application.py:24 -#: tickets/models/ticket/apply_asset.py:18 +#: tickets/models/ticket/apply_application.py:23 msgid "Apply system users" msgstr "申请的系统用户" @@ -5394,6 +5293,12 @@ msgstr "申请节点" msgid "Apply assets" msgstr "申请资产" +#: tickets/models/ticket/apply_asset.py:17 +#, fuzzy +#| msgid "Application account" +msgid "Apply accounts" +msgstr "应用账号" + #: tickets/models/ticket/command_confirm.py:10 msgid "Run user" msgstr "运行的用户" @@ -5402,19 +5307,21 @@ msgstr "运行的用户" msgid "Run asset" msgstr "运行的资产" -#: tickets/models/ticket/command_confirm.py:15 -msgid "Run system user" -msgstr "运行的系统用户" +#: tickets/models/ticket/command_confirm.py:13 +#, fuzzy +#| msgid "account" +msgid "Run account" +msgstr "账号" -#: tickets/models/ticket/command_confirm.py:17 +#: tickets/models/ticket/command_confirm.py:14 msgid "Run command" msgstr "运行的命令" -#: tickets/models/ticket/command_confirm.py:24 +#: tickets/models/ticket/command_confirm.py:21 msgid "From cmd filter" msgstr "来自命令过滤规则" -#: tickets/models/ticket/command_confirm.py:28 +#: tickets/models/ticket/command_confirm.py:25 msgid "From cmd filter rule" msgstr "来自命令过滤规则" @@ -5462,9 +5369,11 @@ msgstr "登录用户" msgid "Login asset" msgstr "登录资产" -#: tickets/models/ticket/login_asset_confirm.py:20 -msgid "Login system user" -msgstr "登录系统用户" +#: tickets/models/ticket/login_asset_confirm.py:19 +#, fuzzy +#| msgid "Login acl" +msgid "Login account" +msgstr "登录访问控制" #: tickets/models/ticket/login_confirm.py:12 msgid "Login datetime" @@ -5510,16 +5419,16 @@ msgstr "当前组织已存在该类型" msgid "Processor" msgstr "处理人" -#: tickets/serializers/ticket/common.py:16 -#: tickets/serializers/ticket/common.py:79 +#: tickets/serializers/ticket/common.py:15 +#: tickets/serializers/ticket/common.py:77 msgid "Created by ticket ({}-{})" msgstr "通过工单创建 ({}-{})" -#: tickets/serializers/ticket/common.py:69 +#: tickets/serializers/ticket/common.py:67 msgid "The expiration date should be greater than the start date" msgstr "过期时间要大于开始时间" -#: tickets/serializers/ticket/common.py:85 +#: tickets/serializers/ticket/common.py:83 msgid "Permission named `{}` already exists" msgstr "授权名称 `{}` 已存在" @@ -5540,7 +5449,7 @@ msgid "Ticket information" msgstr "工单信息" #: tickets/templates/tickets/approve_check_password.html:29 -#: tickets/views/approve.py:39 +#: tickets/views/approve.py:38 msgid "Ticket approval" msgstr "工单审批" @@ -5552,24 +5461,24 @@ msgstr "同意" msgid "Go Login" msgstr "去登录" -#: tickets/views/approve.py:40 +#: tickets/views/approve.py:39 msgid "" "This ticket does not exist, the process has ended, or this link has expired" msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期" -#: tickets/views/approve.py:69 +#: tickets/views/approve.py:68 msgid "Click the button below to approve or reject" msgstr "点击下方按钮同意或者拒绝" -#: tickets/views/approve.py:71 +#: tickets/views/approve.py:70 msgid "After successful authentication, this ticket can be approved directly" msgstr "认证成功后,工单可直接审批" -#: tickets/views/approve.py:93 +#: tickets/views/approve.py:92 msgid "Illegal approval action" msgstr "无效的审批动作" -#: tickets/views/approve.py:106 +#: tickets/views/approve.py:105 msgid "This user is not authorized to approve this ticket" msgstr "此用户无权审批此工单" @@ -5577,6 +5486,10 @@ msgstr "此用户无权审批此工单" msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置" +#: users/apps.py:9 +msgid "Users" +msgstr "用户管理" + #: users/const.py:10 msgid "System administrator" msgstr "系统管理员" @@ -5704,6 +5617,14 @@ msgstr "头像" msgid "Wechat" msgstr "微信" +#: users/models/user.py:685 +msgid "Phone" +msgstr "手机" + +#: users/models/user.py:693 +msgid "Private key" +msgstr "ssh私钥" + #: users/models/user.py:699 msgid "Secret key" msgstr "Secret key" @@ -5720,27 +5641,27 @@ msgstr "最后更新密码日期" msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:896 +#: users/models/user.py:897 msgid "Can invite user" msgstr "可以邀请用户" -#: users/models/user.py:897 +#: users/models/user.py:898 msgid "Can remove user" msgstr "可以移除用户" -#: users/models/user.py:898 +#: users/models/user.py:899 msgid "Can match user" msgstr "可以匹配用户" -#: users/models/user.py:907 +#: users/models/user.py:908 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:910 +#: users/models/user.py:911 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/models/user.py:935 +#: users/models/user.py:936 msgid "User password history" msgstr "用户密码历史" @@ -5811,12 +5732,6 @@ msgstr "系统角色显示" msgid "Org roles display" msgstr "组织角色显示" -#: users/serializers/user.py:81 -#: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:27 -msgid "Password strategy" -msgstr "密码策略" - #: users/serializers/user.py:83 msgid "MFA enabled" msgstr "MFA 已启用" @@ -5973,10 +5888,6 @@ msgstr "您的密码必须满足:" msgid "Password strength" msgstr "密码强度:" -#: users/templates/users/reset_password.html:29 -msgid "Setting" -msgstr "设置" - #: users/templates/users/reset_password.html:48 msgid "Very weak" msgstr "很弱" @@ -6134,123 +6045,97 @@ msgstr "重置密码成功,返回到登录页面" msgid "XPACK" msgstr "XPack" -#: xpack/plugins/change_auth_plan/api/app.py:112 -#: xpack/plugins/change_auth_plan/api/asset.py:95 +#: xpack/plugins/change_auth_plan/api/asset.py:94 msgid "The parameter 'action' must be [{}]" msgstr "参数 'action' 必须是 [{}]" #: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models/asset.py:123 +#: xpack/plugins/change_auth_plan/models/asset.py:124 msgid "Change auth plan" msgstr "改密计划" -#: xpack/plugins/change_auth_plan/models/app.py:46 -#: xpack/plugins/change_auth_plan/models/app.py:95 +#: xpack/plugins/change_auth_plan/models/app.py:45 +#: xpack/plugins/change_auth_plan/models/app.py:94 msgid "Application change auth plan" msgstr "应用改密计划" -#: xpack/plugins/change_auth_plan/models/app.py:99 -#: xpack/plugins/change_auth_plan/models/app.py:151 +#: xpack/plugins/change_auth_plan/models/app.py:98 +#: xpack/plugins/change_auth_plan/models/app.py:150 msgid "Application change auth plan execution" msgstr "应用改密计划执行" -#: xpack/plugins/change_auth_plan/models/app.py:144 -#: xpack/plugins/change_auth_plan/serializers/app.py:64 +#: xpack/plugins/change_auth_plan/models/app.py:143 msgid "App" msgstr "应用" -#: xpack/plugins/change_auth_plan/models/app.py:156 +#: xpack/plugins/change_auth_plan/models/app.py:155 msgid "Application change auth plan task" msgstr "应用改密计划任务" -#: xpack/plugins/change_auth_plan/models/app.py:180 -#: xpack/plugins/change_auth_plan/models/asset.py:263 +#: xpack/plugins/change_auth_plan/models/app.py:179 +#: xpack/plugins/change_auth_plan/models/asset.py:264 msgid "Password cannot be set to blank, exit. " msgstr "密码不能设置为空, 退出. " -#: xpack/plugins/change_auth_plan/models/asset.py:29 -msgid "Append SSH KEY" -msgstr "追加" - -#: xpack/plugins/change_auth_plan/models/asset.py:30 -msgid "Empty and append SSH KEY" -msgstr "清空所有并添加" - -#: xpack/plugins/change_auth_plan/models/asset.py:31 -msgid "Replace (The key generated by JumpServer) " -msgstr "替换 (由 JumpServer 生成的密钥)" - -#: xpack/plugins/change_auth_plan/models/asset.py:49 -#: xpack/plugins/change_auth_plan/serializers/asset.py:36 +#: xpack/plugins/change_auth_plan/models/asset.py:50 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "SSH Key strategy" msgstr "SSH 密钥策略" -#: xpack/plugins/change_auth_plan/models/asset.py:67 +#: xpack/plugins/change_auth_plan/models/asset.py:68 msgid "Asset change auth plan" msgstr "资产改密计划" -#: xpack/plugins/change_auth_plan/models/asset.py:134 +#: xpack/plugins/change_auth_plan/models/asset.py:135 msgid "Asset change auth plan execution" msgstr "资产改密计划执行" -#: xpack/plugins/change_auth_plan/models/asset.py:210 +#: xpack/plugins/change_auth_plan/models/asset.py:211 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models/asset.py:217 +#: xpack/plugins/change_auth_plan/models/asset.py:218 msgid "Asset change auth plan task" msgstr "资产改密计划任务" -#: xpack/plugins/change_auth_plan/models/asset.py:252 +#: xpack/plugins/change_auth_plan/models/asset.py:253 msgid "This asset does not have a privileged user set: " msgstr "该资产没有设置特权用户: " -#: xpack/plugins/change_auth_plan/models/asset.py:258 +#: xpack/plugins/change_auth_plan/models/asset.py:259 msgid "" "The password and key of the current asset privileged user cannot be changed: " msgstr "不能更改当前资产特权用户的密码及密钥: " -#: xpack/plugins/change_auth_plan/models/asset.py:269 +#: xpack/plugins/change_auth_plan/models/asset.py:270 msgid "Public key cannot be set to null, exit. " msgstr "公钥不能设置为空, 退出. " -#: xpack/plugins/change_auth_plan/models/base.py:28 -msgid "All assets use the same random password" -msgstr "使用相同的随机密码" - -#: xpack/plugins/change_auth_plan/models/base.py:29 -msgid "All assets use different random password" -msgstr "使用不同的随机密码" - -#: xpack/plugins/change_auth_plan/models/base.py:39 -msgid "Password rules" -msgstr "密码规则" - -#: xpack/plugins/change_auth_plan/models/base.py:118 +#: xpack/plugins/change_auth_plan/models/base.py:114 msgid "Change auth plan snapshot" msgstr "改密计划快照" -#: xpack/plugins/change_auth_plan/models/base.py:187 +#: xpack/plugins/change_auth_plan/models/base.py:183 msgid "Ready" msgstr "准备" -#: xpack/plugins/change_auth_plan/models/base.py:188 +#: xpack/plugins/change_auth_plan/models/base.py:184 msgid "Preflight check" msgstr "改密前的校验" -#: xpack/plugins/change_auth_plan/models/base.py:189 +#: xpack/plugins/change_auth_plan/models/base.py:185 msgid "Change auth" msgstr "执行改密" -#: xpack/plugins/change_auth_plan/models/base.py:190 +#: xpack/plugins/change_auth_plan/models/base.py:186 msgid "Verify auth" msgstr "验证密码/密钥" -#: xpack/plugins/change_auth_plan/models/base.py:191 +#: xpack/plugins/change_auth_plan/models/base.py:187 msgid "Keep auth" msgstr "保存密码/密钥" -#: xpack/plugins/change_auth_plan/models/base.py:199 +#: xpack/plugins/change_auth_plan/models/base.py:195 msgid "Step" msgstr "步骤" @@ -6273,11 +6158,11 @@ msgstr "" "{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" "密密码" -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +#: xpack/plugins/change_auth_plan/serializers/asset.py:30 msgid "Change Password" msgstr "更改密码" -#: xpack/plugins/change_auth_plan/serializers/asset.py:34 +#: xpack/plugins/change_auth_plan/serializers/asset.py:31 msgid "Change SSH Key" msgstr "修改 SSH Key" @@ -6413,75 +6298,71 @@ msgstr "已释放" msgid "Cloud center" msgstr "云管中心" -#: xpack/plugins/cloud/models.py:30 +#: xpack/plugins/cloud/models.py:32 msgid "Provider" msgstr "云服务商" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:41 msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:41 +#: xpack/plugins/cloud/models.py:43 msgid "Test cloud account" msgstr "测试云账号" -#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:67 -msgid "Account" -msgstr "账号" - -#: xpack/plugins/cloud/models.py:88 xpack/plugins/cloud/serializers/task.py:38 +#: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:37 msgid "Regions" msgstr "地域" -#: xpack/plugins/cloud/models.py:91 +#: xpack/plugins/cloud/models.py:93 msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:100 xpack/plugins/cloud/serializers/task.py:68 +#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:66 msgid "Unix admin user" msgstr "Unix 管理员" -#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:69 +#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:67 msgid "Windows admin user" msgstr "Windows 管理员" -#: xpack/plugins/cloud/models.py:110 xpack/plugins/cloud/serializers/task.py:46 +#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:44 msgid "IP network segment group" msgstr "IP网段组" -#: xpack/plugins/cloud/models.py:113 xpack/plugins/cloud/serializers/task.py:72 +#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:70 msgid "Always update" msgstr "总是更新" -#: xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/models.py:121 msgid "Date last sync" msgstr "最后同步日期" -#: xpack/plugins/cloud/models.py:130 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:126 xpack/plugins/cloud/models.py:167 msgid "Sync instance task" msgstr "同步实例任务" -#: xpack/plugins/cloud/models.py:182 xpack/plugins/cloud/models.py:230 +#: xpack/plugins/cloud/models.py:178 xpack/plugins/cloud/models.py:226 msgid "Date sync" msgstr "同步日期" -#: xpack/plugins/cloud/models.py:186 +#: xpack/plugins/cloud/models.py:182 msgid "Sync instance task execution" msgstr "同步实例任务执行" -#: xpack/plugins/cloud/models.py:210 +#: xpack/plugins/cloud/models.py:206 msgid "Sync task" msgstr "同步任务" -#: xpack/plugins/cloud/models.py:214 +#: xpack/plugins/cloud/models.py:210 msgid "Sync instance task history" msgstr "同步实例任务历史" -#: xpack/plugins/cloud/models.py:217 +#: xpack/plugins/cloud/models.py:213 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/models.py:234 +#: xpack/plugins/cloud/models.py:230 msgid "Sync instance detail" msgstr "同步实例详情" @@ -6697,7 +6578,7 @@ msgstr "订阅 ID" #: xpack/plugins/cloud/serializers/account_attrs.py:95 #: xpack/plugins/cloud/serializers/account_attrs.py:100 -#: xpack/plugins/cloud/serializers/account_attrs.py:124 +#: xpack/plugins/cloud/serializers/account_attrs.py:134 msgid "API Endpoint" msgstr "API 端点" @@ -6713,25 +6594,25 @@ msgstr "如: http://openstack.example.com:5000/v3" msgid "User domain" msgstr "用户域" -#: xpack/plugins/cloud/serializers/account_attrs.py:117 +#: xpack/plugins/cloud/serializers/account_attrs.py:127 msgid "Service account key" msgstr "服务账号密钥" -#: xpack/plugins/cloud/serializers/account_attrs.py:118 +#: xpack/plugins/cloud/serializers/account_attrs.py:128 msgid "The file is in JSON format" msgstr "JSON 格式的文件" -#: xpack/plugins/cloud/serializers/account_attrs.py:131 +#: xpack/plugins/cloud/serializers/account_attrs.py:141 msgid "IP address invalid `{}`, {}" msgstr "IP 地址无效: `{}`, {}" -#: xpack/plugins/cloud/serializers/account_attrs.py:137 +#: xpack/plugins/cloud/serializers/account_attrs.py:147 msgid "" "Format for comma-delimited string,Such as: 192.168.1.0/24, " "10.0.0.0-10.0.0.255" msgstr "格式为逗号分隔的字符串,如:192.168.1.0/24,10.0.0.0-10.0.0.255" -#: xpack/plugins/cloud/serializers/account_attrs.py:141 +#: xpack/plugins/cloud/serializers/account_attrs.py:151 msgid "" "The port is used to detect the validity of the IP address. When the " "synchronization task is executed, only the valid IP address will be " @@ -6740,23 +6621,23 @@ msgstr "" "端口用来检测 IP 地址的有效性,在同步任务执行时,只会同步有效的 IP 地址。
" "如果端口为 0,则表示所有 IP 地址均有效。" -#: xpack/plugins/cloud/serializers/account_attrs.py:149 +#: xpack/plugins/cloud/serializers/account_attrs.py:159 msgid "Hostname prefix" msgstr "主机名前缀" -#: xpack/plugins/cloud/serializers/account_attrs.py:152 +#: xpack/plugins/cloud/serializers/account_attrs.py:162 msgid "IP segment" msgstr "IP 网段" -#: xpack/plugins/cloud/serializers/account_attrs.py:156 +#: xpack/plugins/cloud/serializers/account_attrs.py:166 msgid "Test port" msgstr "测试端口" -#: xpack/plugins/cloud/serializers/account_attrs.py:159 +#: xpack/plugins/cloud/serializers/account_attrs.py:169 msgid "Test timeout" msgstr "测试超时时间" -#: xpack/plugins/cloud/serializers/task.py:29 +#: xpack/plugins/cloud/serializers/task.py:28 msgid "" "Only instances matching the IP range will be synced.
If the instance " "contains multiple IP addresses, the first IP address that matches will be " @@ -6768,19 +6649,19 @@ msgstr "" "到的 IP 地址将被用作创建的资产的 IP。
默认值 * 表示同步所有实例和随机匹配 " "IP 地址。
格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20" -#: xpack/plugins/cloud/serializers/task.py:36 +#: xpack/plugins/cloud/serializers/task.py:35 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers/task.py:37 +#: xpack/plugins/cloud/serializers/task.py:36 msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/serializers/task.py:66 +#: xpack/plugins/cloud/serializers/task.py:64 msgid "Linux admin user" msgstr "Linux 管理员" -#: xpack/plugins/cloud/serializers/task.py:71 +#: xpack/plugins/cloud/serializers/task.py:69 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" @@ -6793,15 +6674,15 @@ msgstr "账号无效" msgid "Gathered user" msgstr "收集用户" -#: xpack/plugins/gathered_user/models.py:39 +#: xpack/plugins/gathered_user/models.py:34 msgid "Gather user task" msgstr "收集用户任务" -#: xpack/plugins/gathered_user/models.py:85 +#: xpack/plugins/gathered_user/models.py:80 msgid "gather user task execution" msgstr "收集用户执行" -#: xpack/plugins/gathered_user/models.py:91 +#: xpack/plugins/gathered_user/models.py:86 msgid "Assets is empty, please change nodes" msgstr "资产为空,请更改节点" @@ -6872,3 +6753,409 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" + +#~ msgid "System User" +#~ msgstr "系统用户" + +#~ msgid "Unsupported protocols: {}" +#~ msgstr "不支持的协议: {}" + +#~ msgid "Remote app" +#~ msgstr "远程应用" + +#~ msgid "Custom" +#~ msgstr "自定义" + +#~ msgid "Can view application account secret" +#~ msgstr "可以查看应用账号密码" + +#~ msgid "Can change application account secret" +#~ msgstr "可以查看应用账号密码" + +#~ msgid "Application user" +#~ msgstr "应用用户" + +#~ msgid "Application display" +#~ msgstr "应用名称" + +#~ msgid "Cluster" +#~ msgstr "集群" + +#~ msgid "Asset Info" +#~ msgstr "资产信息" + +#~ msgid "Application path" +#~ msgstr "应用路径" + +#~ msgid "Target URL" +#~ msgstr "目标URL" + +#~ msgid "Chrome username" +#~ msgstr "Chrome 用户名" + +#~ msgid "Chrome password" +#~ msgstr "Chrome 密码" + +#~ msgid "Operating parameter" +#~ msgstr "运行参数" + +#~ msgid "Target url" +#~ msgstr "目标URL" + +#~ msgid "Custom Username" +#~ msgstr "自定义用户名" + +#~ msgid "Mysql workbench username" +#~ msgstr "Mysql 工作台 用户名" + +#~ msgid "Mysql workbench password" +#~ msgstr "Mysql 工作台 密码" + +#~ msgid "Magnus currently supports only 11g and 12c connections" +#~ msgstr "目前 Magnus 只支持连接 11g、12c 版本" + +#~ msgid "Vmware username" +#~ msgstr "Vmware 用户名" + +#~ msgid "Vmware password" +#~ msgstr "Vmware 密码" + +#~ msgid "Base" +#~ msgstr "基础" + +#~ msgid "Public IP" +#~ msgstr "公网IP" + +#~ msgid "AuthBook" +#~ msgstr "资产账号" + +#~ msgid "Can test asset account connectivity" +#~ msgstr "可以测试资产账号连接性" + +#~ msgid "Bandwidth" +#~ msgstr "带宽" + +#~ msgid "Contact" +#~ msgstr "联系人" + +#~ msgid "Intranet" +#~ msgstr "内网" + +#~ msgid "Extranet" +#~ msgstr "外网" + +#~ msgid "Operator" +#~ msgstr "运营商" + +#~ msgid "Default Cluster" +#~ msgstr "默认Cluster" + +#~ msgid "User groups" +#~ msgstr "用户组" + +#~ msgid "System user display" +#~ msgstr "系统用户名称" + +#~ msgid "Protocol format should {}/{}" +#~ msgstr "协议格式 {}/{}" + +#~ msgid "Nodes name" +#~ msgstr "节点名称" + +#~ msgid "Labels name" +#~ msgstr "标签名称" + +#~ msgid "Hardware info" +#~ msgstr "硬件信息" + +#~ msgid "Admin user display" +#~ msgstr "特权用户名称" + +#~ msgid "CPU info" +#~ msgstr "CPU信息" + +#~ msgid "Action display" +#~ msgstr "动作名称" + +#~ msgid "Applications amount" +#~ msgstr "应用数量" + +#~ msgid "SSH key fingerprint" +#~ msgstr "密钥指纹" + +#~ msgid "Apps amount" +#~ msgstr "应用数量" + +#~ msgid "Login mode display" +#~ msgstr "认证方式名称" + +#~ msgid "Ad domain" +#~ msgstr "Ad 网域" + +#~ msgid "Is asset protocol" +#~ msgstr "资产协议" + +#~ msgid "Only ssh and automatic login system users are supported" +#~ msgstr "仅支持ssh协议和自动登录的系统用户" + +#~ msgid "Username same with user with protocol {} only allow 1" +#~ msgstr "用户名和用户相同的一种协议只允许存在一个" + +#~ msgid "* Automatic login mode must fill in the username." +#~ msgstr "自动登录模式,必须填写用户名" + +#~ msgid "Path should starts with /" +#~ msgstr "路径应该以 / 开头" + +#~ msgid "Password or private key required" +#~ msgstr "密码或密钥密码需要一个" + +#~ msgid "Only ssh protocol system users are allowed" +#~ msgstr "仅允许ssh协议的系统用户" + +#~ msgid "The protocol must be consistent with the current user: {}" +#~ msgstr "协议必须和当前用户保持一致: {}" + +#~ msgid "Only system users with automatic login are allowed" +#~ msgstr "仅允许自动登录的系统用户" + +#~ msgid "System user name" +#~ msgstr "系统用户名称" + +#~ msgid "Asset hostname" +#~ msgstr "资产主机名" + +#~ msgid "System user is dynamic: {}" +#~ msgstr "系统用户是动态的: {}" + +#~ msgid "Start push system user for platform: [{}]" +#~ msgstr "推送系统用户到平台: [{}]" + +#~ msgid "Hosts count: {}" +#~ msgstr "主机数量: {}" + +#~ msgid "Push system users to assets: " +#~ msgstr "推送系统用户到入资产: " + +#~ msgid "Push system users to asset: " +#~ msgstr "推送系统用户到入资产: " + +#~ msgid "Dynamic system user not support test" +#~ msgstr "动态系统用户不支持测试" + +#~ msgid "Start test system user connectivity for platform: [{}]" +#~ msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" + +#~ msgid "Test system user connectivity: " +#~ msgstr "测试系统用户可连接性: " + +#~ msgid "Test system user connectivity period: " +#~ msgstr "定期测试系统用户可连接性: " + +#~ msgid "Hosts display" +#~ msgstr "主机名称" + +#~ msgid "Run as" +#~ msgstr "运行用户" + +#~ msgid "Run as display" +#~ msgstr "运行用户名称" + +#~ msgid "Asset and SystemUser" +#~ msgstr "资产与系统用户" + +#, python-brace-format +#~ msgid "{Asset} ADD {SystemUser}" +#~ msgstr "{Asset} 添加 {SystemUser}" + +#, python-brace-format +#~ msgid "{Asset} REMOVE {SystemUser}" +#~ msgstr "{Asset} 移除 {SystemUser}" + +#~ msgid "Asset permission and SystemUser" +#~ msgstr "资产授权与系统用户" + +#, python-brace-format +#~ msgid "{AssetPermission} ADD {SystemUser}" +#~ msgstr "{AssetPermission} 添加 {SystemUser}" + +#, python-brace-format +#~ msgid "{AssetPermission} REMOVE {SystemUser}" +#~ msgstr "{AssetPermission} 移除 {SystemUser}" + +#~ msgid "User application permissions" +#~ msgstr "用户应用授权" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {User}" +#~ msgstr "{ApplicationPermission} 添加 {User}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {User}" +#~ msgstr "{ApplicationPermission} 移除 {User}" + +#~ msgid "User group application permissions" +#~ msgstr "用户组应用授权" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {UserGroup}" +#~ msgstr "{ApplicationPermission} 添加 {UserGroup}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {UserGroup}" +#~ msgstr "{ApplicationPermission} 移除 {UserGroup}" + +#~ msgid "Application permission" +#~ msgstr "应用授权" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {Application}" +#~ msgstr "{ApplicationPermission} 添加 {Application}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {Application}" +#~ msgstr "{ApplicationPermission} 移除 {Application}" + +#~ msgid "Application permission and SystemUser" +#~ msgstr "应用授权与系统用户" + +#, python-brace-format +#~ msgid "{ApplicationPermission} ADD {SystemUser}" +#~ msgstr "{ApplicationPermission} 添加 {SystemUser}" + +#, python-brace-format +#~ msgid "{ApplicationPermission} REMOVE {SystemUser}" +#~ msgstr "{ApplicationPermission} 移除 {SystemUser}" + +#~ msgid "Not has host {} permission" +#~ msgstr "没有该主机 {} 权限" + +#~ msgid "" +#~ "eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " +#~ "crontab expressions (Online tools)
Note: If both Regularly " +#~ "perform and Cycle perform are set, give priority to Regularly perform" +#~ msgstr "" +#~ "eg:每周日 03:05 执行 <5 3 * * 0>
提示: 使用5位 Linux crontab 表达" +#~ "式 <分 时 日 月 星期> (在线工具
注意: 如果同时设置了定期执行和周期执" +#~ "行,优先使用定期执行" + +#~ msgid "Unit: hour" +#~ msgstr "单位: 时" + +#~ msgid "Callback" +#~ msgstr "回调" + +#~ msgid "Can view task monitor" +#~ msgstr "可以查看任务监控" + +#~ msgid "Tasks" +#~ msgstr "任务" + +#~ msgid "Options" +#~ msgstr "选项" + +#~ msgid "Run as admin" +#~ msgstr "再次执行" + +#~ msgid "Become" +#~ msgstr "Become" + +#~ msgid "Create by" +#~ msgstr "创建者" + +#~ msgid "AdHoc" +#~ msgstr "任务各版本" + +#~ msgid "Task display" +#~ msgstr "任务名称" + +#~ msgid "Host amount" +#~ msgstr "主机数量" + +#~ msgid "Start time" +#~ msgstr "开始时间" + +#~ msgid "End time" +#~ msgstr "完成时间" + +#~ msgid "Adhoc raw result" +#~ msgstr "结果" + +#~ msgid "Adhoc result summary" +#~ msgstr "汇总" + +#~ msgid "Task start" +#~ msgstr "任务开始" + +#~ msgid "Command `{}` is forbidden ........" +#~ msgstr "命令 `{}` 不允许被执行 ......." + +#~ msgid "Task end" +#~ msgstr "任务结束" + +#~ msgid "The administrator is modifying permissions. Please wait" +#~ msgstr "管理员正在修改授权,请稍等" + +#~ msgid "The authorization cannot be revoked for the time being" +#~ msgstr "该授权暂时不能撤销" + +#~ msgid "Permed application" +#~ msgstr "授权的应用" + +#~ msgid "Can view my apps" +#~ msgstr "可以查看我的应用" + +#~ msgid "Can view user apps" +#~ msgstr "可以查看用户授权的应用" + +#~ msgid "Can view usergroup apps" +#~ msgstr "可以查看用户组授权的应用" + +#~ msgid "Your permed applications is about to expire" +#~ msgstr "你授权的应用即将过期" + +#~ msgid "permed applications" +#~ msgstr "授权的应用" + +#~ msgid "Application permissions is about to expire" +#~ msgstr "应用授权规则即将过期" + +#~ msgid "application permissions of organization {}" +#~ msgstr "组织 ({}) 的应用授权" + +#~ msgid "System users amount" +#~ msgstr "系统用户数量" + +#~ msgid "" +#~ "The application list contains applications that are different from the " +#~ "permission type. ({})" +#~ msgstr "应用列表中包含与授权类型不同的应用。({})" + +#~ msgid "System users display" +#~ msgstr "系统用户名称" + +#~ msgid "My applications" +#~ msgstr "我的应用" + +#~ msgid "Empty" +#~ msgstr "空" + +#~ msgid "System user ID" +#~ msgstr "系统用户 ID" + +#~ msgid "Apply for application" +#~ msgstr "申请应用" + +#~ msgid "" +#~ "Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " +#~ "processor: {}, ticket ID: {}" +#~ msgstr "" +#~ "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" + +#~ msgid "Run system user" +#~ msgstr "运行的系统用户" + +#~ msgid "Login system user" +#~ msgstr "登录系统用户" diff --git a/apps/notifications/migrations/0002_auto_20210909_1946.py b/apps/notifications/migrations/0002_auto_20210909_1946.py index 75f71fad7..e0f8ac073 100644 --- a/apps/notifications/migrations/0002_auto_20210909_1946.py +++ b/apps/notifications/migrations/0002_auto_20210909_1946.py @@ -30,7 +30,7 @@ def init_user_msg_subscription(apps, schema_editor): to_create.append(UserMsgSubscription(user=user, receive_backends=receive_backends)) UserMsgSubscription.objects.bulk_create(to_create) - print(f'\n Init user message subscription: {len(to_create)}') + print(f'\n\tInit user message subscription: {len(to_create)}') class Migration(migrations.Migration): diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index 3bca6138f..77ec14cf1 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -164,7 +164,7 @@ class BuiltinRole: @classmethod def sync_to_db(cls, show_msg=False): roles = cls.get_roles() - print("\n Update builtin roles") + print("\n\tUpdate builtin roles") for pre_role in roles.values(): role, created = pre_role.update_or_create_role() diff --git a/apps/rbac/migrations/0004_auto_20211201_1901.py b/apps/rbac/migrations/0004_auto_20211201_1901.py index 811876f58..669af02cf 100644 --- a/apps/rbac/migrations/0004_auto_20211201_1901.py +++ b/apps/rbac/migrations/0004_auto_20211201_1901.py @@ -29,7 +29,7 @@ def migrate_system_role_binding(apps, schema_editor): role_bindings.append(role_binding) role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) - print("Create role binding: {}-{} using: {:.2f}s".format( + print("\tCreate role binding: {}-{} using: {:.2f}s".format( count, count + len(users), time.time()-start )) count += len(users) @@ -62,7 +62,7 @@ def migrate_org_role_binding(apps, schema_editor): ) role_bindings.append(role_binding) role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) - print("Create role binding: {}-{} using: {:.2f}s".format( + print("\tCreate role binding: {}-{} using: {:.2f}s".format( count, count + len(members), time.time()-start )) count += len(members) diff --git a/apps/rbac/signal_handlers.py b/apps/rbac/signal_handlers.py index 86eefb180..a158b7e6a 100644 --- a/apps/rbac/signal_handlers.py +++ b/apps/rbac/signal_handlers.py @@ -11,7 +11,7 @@ def after_migrate_update_builtin_role_permissions(sender, app_config, **kwargs): # 最后一个 app migrations 后执行, 更新内置角色的权限 last_app = list(apps.get_app_configs())[-1] if app_config.name == last_app.name: - print("After migration, update builtin role permissions") + print("\tAfter migration, update builtin role permissions") BuiltinRole.sync_to_db() diff --git a/apps/tickets/migrations/0013_ticket_serial_num.py b/apps/tickets/migrations/0013_ticket_serial_num.py index 4c457d21f..6a6568175 100644 --- a/apps/tickets/migrations/0013_ticket_serial_num.py +++ b/apps/tickets/migrations/0013_ticket_serial_num.py @@ -12,7 +12,7 @@ def fill_ticket_serial_number(apps, schema_editor): curr_day = '00000000' curr_num = 1 - print(f'\nFill ticket serial number ... ', end='') + print(f'\n Fill ticket serial number ... ') for ticket in tickets: # 跑这个脚本的时候,所有 ticket.serial_num == null date_created = as_current_tz(ticket.date_created) diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index 91748dd76..7bdd129f9 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -15,7 +15,7 @@ def migrate_system_to_account(apps, schema_editor): (apply_login_asset_ticket_model, 'apply_login_system_user', 'apply_login_account', False), ) - print("\nStart migrate system user to account") + print("\n Start migrate system user to account") for model, old_field, new_field, m2m in model_system_user_account: print(" - migrate '{}'".format(model.__name__)) count = 0 diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 78d5eb540..50abee411 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -254,7 +254,7 @@ class RoleManager(models.Manager): self.user.expire_users_rbac_perms_cache() return result except Exception as e: - logger.error('Create role binding error: {}'.format(e)) + logger.error('\tCreate role binding error: {}'.format(e)) def set(self, roles, clear=False): if clear: diff --git a/utils/test_run_migrations.py b/utils/test_run_migrations.py index 33c7c3c5c..70162ab68 100644 --- a/utils/test_run_migrations.py +++ b/utils/test_run_migrations.py @@ -42,7 +42,7 @@ def migrate_system_role_binding(apps, schema_editor): role_bindings.append(role_binding) role_binding_model.objects.bulk_create(role_bindings, ignore_conflicts=True) - print("Create role binding: {}-{} using: {:.2f}s".format( + print("\tCreate role binding: {}-{} using: {:.2f}s".format( count, count + len(users), time.time()-start )) count += len(users) From eb16e3c7cbdeb4dad8e95322d2fe4db467713145 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 19 Oct 2022 14:56:27 +0800 Subject: [PATCH 206/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/types.py | 2 +- .../assets/migrations/0060_node_full_value.py | 8 +- .../migrations/0098_auto_20220430_2126.py | 1 + .../migrations/0107_auto_20221019_1115.py | 2 +- apps/assets/models/automations/base.py | 10 +- .../models/automations/verify_secret.py | 7 +- apps/assets/serializers/asset/web.py | 9 + apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 109 +++++--- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 264 +++++++----------- .../migrations/0010_auto_20210219_1241.py | 10 +- .../migrations/0013_ticket_serial_num.py | 3 +- .../migrations/0020_auto_20220817_1346.py | 4 +- 14 files changed, 198 insertions(+), 239 deletions(-) diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 939a00ea8..620527bfd 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -202,7 +202,7 @@ class AllTypes(ChoicesMixin): @classmethod def create_or_update_internal_platforms(cls): - print("Create internal platforms") + print("\n\tCreate internal platforms") for category, type_cls in cls.category_types(): print("\t## Category: {}".format(category.label)) data = type_cls.internal_platforms() diff --git a/apps/assets/migrations/0060_node_full_value.py b/apps/assets/migrations/0060_node_full_value.py index f5e86030d..0f633deac 100644 --- a/apps/assets/migrations/0060_node_full_value.py +++ b/apps/assets/migrations/0060_node_full_value.py @@ -19,10 +19,10 @@ def migrate_nodes_value_with_slash(apps, schema_editor): db_alias = schema_editor.connection.alias nodes = model.objects.using(db_alias).filter(value__contains='/') print('') - print("- Start migrate node value if has /") + print("\t- Start migrate node value if has /") for i, node in enumerate(list(nodes)): new_value = node.value.replace('/', '|') - print("{} start migrate node value: {} => {}".format(i, node.value, new_value)) + print("\t - {} start migrate node value: {} => {}".format(i, node.value, new_value)) node.value = new_value node.save() @@ -31,9 +31,9 @@ def migrate_nodes_full_value(apps, schema_editor): model = apps.get_model("assets", "Node") db_alias = schema_editor.connection.alias nodes = model.objects.using(db_alias).all() - print("\n- Start migrate node full value") + print("\n\t- Start migrate node full value") for i, node in enumerate(list(nodes)): - print("{} start migrate {} node full value".format(i, node.value)) + print("\t - {} start migrate {} node full value".format(i, node.value)) ancestor_keys = get_node_ancestor_keys(node.key, True) values = model.objects.filter(key__in=ancestor_keys).values_list('key', 'value') values = [v for k, v in sorted(values, key=lambda x: len(x[0]))] diff --git a/apps/assets/migrations/0098_auto_20220430_2126.py b/apps/assets/migrations/0098_auto_20220430_2126.py index 3ac99b577..d3eab6855 100644 --- a/apps/assets/migrations/0098_auto_20220430_2126.py +++ b/apps/assets/migrations/0098_auto_20220430_2126.py @@ -27,6 +27,7 @@ def migrate_database_to_asset(apps, *args): applications = app_model.objects.filter(category='db') platforms = platform_model.objects.all().filter(internal=True) platforms_map = {p.type: p for p in platforms} + print() for app in applications: attrs = {'host': '', 'port': 0, 'database': ''} diff --git a/apps/assets/migrations/0107_auto_20221019_1115.py b/apps/assets/migrations/0107_auto_20221019_1115.py index 4790068d0..0c0fb772f 100644 --- a/apps/assets/migrations/0107_auto_20221019_1115.py +++ b/apps/assets/migrations/0107_auto_20221019_1115.py @@ -117,7 +117,7 @@ class Migration(migrations.Migration): bases=('assets.baseautomation',), ), migrations.CreateModel( - name='VerifySecretAutomation', + name='VerifyAccountAutomation', fields=[ ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), ], diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index a0eef8df7..17d681477 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -14,10 +14,10 @@ from assets.models import Node, Asset class AutomationTypes(models.TextChoices): ping = 'ping', _('Ping') gather_facts = 'gather_facts', _('Gather facts') - create_account = 'create_account', _('Create account') + push_account = 'push_account', _('Create account') change_secret = 'change_secret', _('Change secret') verify_account = 'verify_account', _('Verify account') - gather_accounts = 'gather_accounts', _('Gather accounts') + gather_account = 'gather_account', _('Gather account') class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): @@ -74,14 +74,14 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): class Meta: unique_together = [('org_id', 'name')] - verbose_name = _("Automation plan") + verbose_name = _("Automation task") class AutomationExecution(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) automation = models.ForeignKey( 'BaseAutomation', related_name='executions', on_delete=models.CASCADE, - verbose_name=_('Automation strategy') + verbose_name=_('Automation task') ) status = models.CharField(max_length=16, default='pending') date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) @@ -96,7 +96,7 @@ class AutomationExecution(OrgModelMixin): ) class Meta: - verbose_name = _('Automation strategy execution') + verbose_name = _('Automation task execution') @property def manager_type(self): diff --git a/apps/assets/models/automations/verify_secret.py b/apps/assets/models/automations/verify_secret.py index f2a1d5bdb..62326c8bb 100644 --- a/apps/assets/models/automations/verify_secret.py +++ b/apps/assets/models/automations/verify_secret.py @@ -1,13 +1,12 @@ from django.utils.translation import ugettext_lazy as _ -from ops.const import StrategyChoice from .base import BaseAutomation -class VerifySecretAutomation(BaseAutomation): +class VerifyAccountAutomation(BaseAutomation): class Meta: - verbose_name = _("Verify secret automation") + verbose_name = _("Verify account automation") def save(self, *args, **kwargs): - self.type = 'verify_secret' + self.type = 'verify_account' super().save(*args, **kwargs) diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py index b98360022..a6bd89435 100644 --- a/apps/assets/serializers/asset/web.py +++ b/apps/assets/serializers/asset/web.py @@ -16,5 +16,14 @@ class WebSerializer(AssetSerializer): **AssetSerializer.Meta.extra_kwargs, 'address': { 'label': 'URL' + }, + 'username_selector': { + 'default': 'input[type=text]' + }, + 'password_selector': { + 'default': 'input[type=password]' + }, + 'submit_selector': { + 'default': 'button[type=submit]', } } diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 4378759fd..cbc8ff533 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7d4ace7d7ec976b0321bde41789f994f02e3ab6f034828cbfb7e675a313611f -size 131531 +oid sha256:e0070188de11b8ace3a23cdf03ac803b50bf98beccef49b94bcfd0131fea8604 +size 119875 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index d29f4e311..92f434e6e 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-19 10:41+0800\n" +"POT-Creation-Date: 2022-10-19 14:41+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -575,24 +575,6 @@ msgstr "ノードにアセットを追加する" msgid "Move asset to node" msgstr "アセットをノードに移動する" -#: assets/models/automations/account_discovery.py:10 -#, fuzzy -#| msgid "Approve strategy" -msgid "Discovery strategy" -msgstr "戦略を承認する" - -#: assets/models/automations/account_reconcile.py:9 -#, fuzzy -#| msgid "Hostname strategy" -msgid "Reconcile strategy" -msgstr "ホスト名戦略" - -#: assets/models/automations/account_verify.py:9 -#, fuzzy -#| msgid "SSH Key strategy" -msgid "Verify strategy" -msgstr "SSHキー戦略" - #: assets/models/automations/base.py:15 msgid "Ping" msgstr "" @@ -618,14 +600,12 @@ msgstr "秘密を改める" #: assets/models/automations/base.py:19 #, fuzzy -#| msgid "Verify auth" -msgid "Verify account" -msgstr "パスワード/キーの確認" +#| msgid "Verify code" +msgid "Verify secret" +msgstr "コードの確認" -#: assets/models/automations/base.py:20 -#, fuzzy -#| msgid "Gather account" -msgid "Gather accounts" +#: assets/models/automations/base.py:20 rbac/tree.py:53 +msgid "Gather account" msgstr "アカウントを集める" #: assets/models/automations/base.py:24 assets/models/cmd_filter.py:38 @@ -641,18 +621,12 @@ msgstr "アカウント" msgid "Assets" msgstr "資産" -#: assets/models/automations/base.py:77 +#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 #, fuzzy #| msgid "Automatic managed" -msgid "Automation plan" +msgid "Automation task" msgstr "自動管理" -#: assets/models/automations/base.py:84 -#, fuzzy -#| msgid "Hostname strategy" -msgid "Automation strategy" -msgstr "ホスト名戦略" - #: assets/models/automations/base.py:88 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 #: perms/models/asset_permission.py:76 terminal/models/session.py:43 @@ -685,7 +659,7 @@ msgstr "トリガーモード" #: assets/models/automations/base.py:99 #, fuzzy #| msgid "Command execution" -msgid "Automation strategy execution" +msgid "Automation task execution" msgstr "コマンド実行" #: assets/models/automations/change_secret.py:13 @@ -765,9 +739,9 @@ msgstr "受信者" #: assets/models/automations/change_secret.py:42 #, fuzzy -#| msgid "Can change auth setting" -msgid "Change auth strategy" -msgstr "資格認定の設定" +#| msgid "Change auth" +msgid "Change secret automation" +msgstr "秘密を改める" #: assets/models/automations/change_secret.py:48 #, fuzzy @@ -787,12 +761,28 @@ msgstr "開始日" msgid "Error" msgstr "企業微信エラー" +#: assets/models/automations/discovery_account.py:8 +#, fuzzy +#| msgid "Verify auth" +msgid "Discovery account automation" +msgstr "パスワード/キーの確認" + #: assets/models/automations/gather_facts.py:11 #, fuzzy #| msgid "Gather assets users" msgid "Gather asset facts" msgstr "資産ユーザーの収集" +#: assets/models/automations/push_account.py:8 +#, fuzzy +#| msgid "Automatic managed" +msgid "Push automation" +msgstr "自動管理" + +#: assets/models/automations/verify_secret.py:8 +msgid "Verify secret automation" +msgstr "" + #: assets/models/backup.py:38 assets/models/backup.py:96 msgid "Account backup plan" msgstr "アカウントバックアップ計画" @@ -1063,13 +1053,13 @@ msgstr "資産ユーザーの収集" #: assets/models/platform.py:49 #, fuzzy #| msgid "Create account successfully" -msgid "Create account enabled" +msgid "Push account enabled" msgstr "アカウントを正常に作成" #: assets/models/platform.py:50 #, fuzzy #| msgid "Create account successfully" -msgid "Create account method" +msgid "Push account method" msgstr "アカウントを正常に作成" #: assets/models/platform.py:51 @@ -3396,10 +3386,6 @@ msgstr "クラウドインポート" msgid "Backup account" msgstr "バックアップアカウント" -#: rbac/tree.py:53 -msgid "Gather account" -msgstr "アカウントを集める" - #: rbac/tree.py:54 msgid "App change auth" msgstr "応用改密" @@ -6864,6 +6850,41 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#, fuzzy +#~| msgid "Verify auth" +#~ msgid "Verify account" +#~ msgstr "パスワード/キーの確認" + +#, fuzzy +#~| msgid "Gather account" +#~ msgid "Gather accounts" +#~ msgstr "アカウントを集める" + +#, fuzzy +#~| msgid "Hostname strategy" +#~ msgid "Automation strategy" +#~ msgstr "ホスト名戦略" + +#, fuzzy +#~| msgid "Approve strategy" +#~ msgid "Discovery strategy" +#~ msgstr "戦略を承認する" + +#, fuzzy +#~| msgid "Hostname strategy" +#~ msgid "Reconcile strategy" +#~ msgstr "ホスト名戦略" + +#, fuzzy +#~| msgid "SSH Key strategy" +#~ msgid "Verify strategy" +#~ msgstr "SSHキー戦略" + +#, fuzzy +#~| msgid "Can change auth setting" +#~ msgid "Change auth strategy" +#~ msgstr "資格認定の設定" + #~ msgid "System User" #~ msgstr "システムユーザー" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 1ad9097dd..5cf5d1d57 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:a89e824cdc4abeea54ffba79270406eefe3a260b764acb79cd42e6a11d4c03a2 -size 108405 +oid sha256:7e313c2d3223de35efb1e9449ab3f6ef33a9d1d565b41ff97b8601c50cf4f70b +size 101988 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index f8d2feba7..2248ab02c 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-10-19 10:41+0800\n" +"POT-Creation-Date: 2022-10-19 14:41+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -287,10 +287,8 @@ msgid "App assets" msgstr "资产管理" #: assets/automations/base/manager.py:74 -#, fuzzy -#| msgid "Disabled" msgid "{} disabled" -msgstr "禁用" +msgstr "{} 已禁用" #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 #: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 @@ -560,33 +558,15 @@ msgstr "添加资产到节点" msgid "Move asset to node" msgstr "移动资产到节点" -#: assets/models/automations/account_discovery.py:10 -msgid "Discovery strategy" -msgstr "自动发现策略" - -#: assets/models/automations/account_reconcile.py:9 -msgid "Reconcile strategy" -msgstr "主机名策略" - -#: assets/models/automations/account_verify.py:9 -#, fuzzy -#| msgid "SSH Key strategy" -msgid "Verify strategy" -msgstr "SSH 密钥策略" - #: assets/models/automations/base.py:15 msgid "Ping" msgstr "" #: assets/models/automations/base.py:16 -#, fuzzy -#| msgid "Gather account" msgid "Gather facts" -msgstr "收集账号" +msgstr "收集信息" #: assets/models/automations/base.py:17 -#, fuzzy -#| msgid "Gather account" msgid "Create account" msgstr "收集账号" @@ -599,14 +579,12 @@ msgstr "执行改密" #: assets/models/automations/base.py:19 #, fuzzy -#| msgid "Verify auth" -msgid "Verify account" -msgstr "验证密码/密钥" +#| msgid "Verify code" +msgid "Verify secret" +msgstr "验证码" -#: assets/models/automations/base.py:20 -#, fuzzy -#| msgid "Gather account" -msgid "Gather accounts" +#: assets/models/automations/base.py:20 rbac/tree.py:53 +msgid "Gather account" msgstr "收集账号" #: assets/models/automations/base.py:24 assets/models/cmd_filter.py:38 @@ -622,17 +600,9 @@ msgstr "账号管理" msgid "Assets" msgstr "资产" -#: assets/models/automations/base.py:77 -#, fuzzy -#| msgid "Automatic managed" -msgid "Automation plan" -msgstr "托管密码" - -#: assets/models/automations/base.py:84 -#, fuzzy -#| msgid "Hostname strategy" -msgid "Automation strategy" -msgstr "主机名策略" +#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 +msgid "Automation task" +msgstr "自动化任务" #: assets/models/automations/base.py:88 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 @@ -664,14 +634,12 @@ msgid "Trigger mode" msgstr "触发模式" #: assets/models/automations/base.py:99 -#, fuzzy -#| msgid "Command execution" -msgid "Automation strategy execution" -msgstr "命令执行" +msgid "Automation task execution" +msgstr "自动化任务执行" #: assets/models/automations/change_secret.py:13 msgid "Specific" -msgstr "" +msgstr "指定" #: assets/models/automations/change_secret.py:14 ops/const.py:20 #: xpack/plugins/change_auth_plan/models/base.py:28 @@ -725,14 +693,10 @@ msgid "Password rules" msgstr "密码规则" #: assets/models/automations/change_secret.py:32 assets/models/base.py:60 -#, fuzzy -#| msgid "SSH Key" msgid "SSH key" msgstr "SSH 密钥" #: assets/models/automations/change_secret.py:34 -#, fuzzy -#| msgid "SSH Key strategy" msgid "SSH key strategy" msgstr "SSH 密钥策略" @@ -745,34 +709,36 @@ msgid "Recipient" msgstr "收件人" #: assets/models/automations/change_secret.py:42 -#, fuzzy -#| msgid "Can change auth setting" -msgid "Change auth strategy" -msgstr "认证设置" +msgid "Change secret automation" +msgstr "自动化改密" #: assets/models/automations/change_secret.py:48 -#, fuzzy -#| msgid "Secret" msgid "Old secret" -msgstr "密钥" +msgstr "原来密码" #: assets/models/automations/change_secret.py:50 -#, fuzzy -#| msgid "Date start" msgid "Date started" msgstr "开始日期" #: assets/models/automations/change_secret.py:53 -#, fuzzy -#| msgid "WeCom Error" msgid "Error" -msgstr "企业微信错误" +msgstr "错误" + +#: assets/models/automations/discovery_account.py:8 +msgid "Discovery account automation" +msgstr "自动化账号发现" #: assets/models/automations/gather_facts.py:11 -#, fuzzy -#| msgid "Gather assets users" msgid "Gather asset facts" -msgstr "收集资产上的用户" +msgstr "收集资产信息" + +#: assets/models/automations/push_account.py:8 +msgid "Push automation" +msgstr "自动化推送" + +#: assets/models/automations/verify_secret.py:8 +msgid "Verify secret automation" +msgstr "自动化验证" #: assets/models/backup.py:38 assets/models/backup.py:96 msgid "Account backup plan" @@ -838,14 +804,12 @@ msgid "Access key" msgstr "Access key" #: assets/models/base.py:67 -#, fuzzy -#| msgid "Secret key" msgid "Secret type" -msgstr "Secret key" +msgstr "密文类型" #: assets/models/base.py:69 msgid "Privileged" -msgstr "" +msgstr "特权的" #: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:61 #: users/models/group.py:31 users/models/user.py:671 @@ -914,9 +878,8 @@ msgstr "测试网关" #: assets/models/domain.py:142 #, fuzzy, python-brace-format -#| msgid "Unable to connect to port {port} on {ip}" msgid "Unable to connect to port {port} on {address}" -msgstr "无法连接到 {ip} 上的端口 {port}" +msgstr "无法连接到 {address} 上的端口 {port}" #: assets/models/domain.py:145 authentication/middleware.py:75 #: xpack/plugins/cloud/providers/fc.py:48 @@ -1002,10 +965,8 @@ msgid "Can match node" msgstr "可以匹配节点" #: assets/models/platform.py:21 -#, fuzzy -#| msgid "MFA required" msgid "Required" -msgstr "需要 MFA 认证" +msgstr "必需的" #: assets/models/platform.py:24 users/templates/users/reset_password.html:29 msgid "Setting" @@ -1017,65 +978,47 @@ msgstr "启用" #: assets/models/platform.py:44 msgid "Ansible config" -msgstr "" +msgstr "Ansible 配置" #: assets/models/platform.py:45 -#, fuzzy -#| msgid "MFA enabled" msgid "Ping enabled" -msgstr "MFA 已启用" +msgstr "探测资产" #: assets/models/platform.py:46 msgid "Ping method" -msgstr "" +msgstr "探测方式" #: assets/models/platform.py:47 assets/models/platform.py:55 -#, fuzzy -#| msgid "Gather assets users" msgid "Gather facts enabled" -msgstr "收集资产上的用户" +msgstr "收集资产信息" #: assets/models/platform.py:48 assets/models/platform.py:56 -#, fuzzy -#| msgid "Gather assets users" msgid "Gather facts method" -msgstr "收集资产上的用户" +msgstr "收集资产信息方式" #: assets/models/platform.py:49 -#, fuzzy -#| msgid "Create account successfully" -msgid "Create account enabled" -msgstr "创建账号成功" +msgid "Push account enabled" +msgstr "推送账号" #: assets/models/platform.py:50 -#, fuzzy -#| msgid "Create account successfully" -msgid "Create account method" -msgstr "创建账号成功" +msgid "Push account method" +msgstr "推送方式" #: assets/models/platform.py:51 -#, fuzzy -#| msgid "Change Password" msgid "Change password enabled" msgstr "更改密码" #: assets/models/platform.py:52 -#, fuzzy -#| msgid "Change Password" msgid "Change password method" -msgstr "更改密码" +msgstr "改密方式" #: assets/models/platform.py:53 -#, fuzzy -#| msgid "Service account key" msgid "Verify account enabled" -msgstr "服务账号密钥" +msgstr "校验账号" #: assets/models/platform.py:54 -#, fuzzy -#| msgid "Verify auth" msgid "Verify account method" -msgstr "验证密码/密钥" +msgstr "验证z" #: assets/models/platform.py:71 tickets/models/ticket/general.py:298 msgid "Meta" @@ -1313,30 +1256,24 @@ msgid "SFTP home" msgstr "SFTP根路径" #: assets/serializers/platform.py:28 -#, fuzzy -#| msgid "Auto" msgid "Auto fill" -msgstr "自动" +msgstr "自动填充" #: assets/serializers/platform.py:29 -#, fuzzy -#| msgid "Username attr" msgid "Username selector" -msgstr "用户名属性" +msgstr "用户名选择器" #: assets/serializers/platform.py:30 -#, fuzzy -#| msgid "Password rules" msgid "Password selector" -msgstr "密码规则" +msgstr "密码选择器" #: assets/serializers/platform.py:31 msgid "Submit selector" -msgstr "" +msgstr "提交按钮选择器" #: assets/serializers/platform.py:64 msgid "Primary" -msgstr "" +msgstr "主要的" #: assets/serializers/utils.py:11 msgid "Password can not contains `{{` " @@ -2549,7 +2486,7 @@ msgstr "%s对象不存在" #: common/drf/fields.py:71 #, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." -msgstr "" +msgstr "不正确的类型。期望 pk 值,收到 {data_type} 类型。" #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" @@ -2674,10 +2611,8 @@ msgid "Invalid ip" msgstr "无效IP" #: common/utils/ip/utils.py:78 -#, fuzzy -#| msgid "Invalid signature." msgid "Invalid address" -msgstr "签名无效" +msgstr "不合理的地址" #: common/validators.py:14 msgid "Special char not allowed" @@ -2748,20 +2683,16 @@ msgid "Site message" msgstr "站内信" #: ops/ansible/inventory.py:76 -#, fuzzy -#| msgid "Account unavailable" msgid "No account available" -msgstr "账号无效" +msgstr "没有账号可以使用" #: ops/ansible/inventory.py:171 -#, fuzzy -#| msgid "User disabled." msgid "Ansible disabled" -msgstr "用户已禁用" +msgstr "Ansible 已禁用" #: ops/ansible/inventory.py:186 msgid "Skip hosts below:" -msgstr "" +msgstr "跳过一下主机:" #: ops/api/celery.py:61 ops/api/celery.py:76 msgid "Waiting task start" @@ -2773,21 +2704,17 @@ msgstr "作业中心" #: ops/const.py:6 msgid "Push" -msgstr "" +msgstr "推送" #: ops/const.py:7 -#, fuzzy -#| msgid "Verified" msgid "Verify" -msgstr "已校验" +msgstr "校验" #: ops/const.py:8 msgid "Collect" msgstr "" #: ops/const.py:9 -#, fuzzy -#| msgid "Change Password" msgid "Change password" msgstr "更改密码" @@ -2833,10 +2760,8 @@ msgid "Args" msgstr "参数" #: ops/models/adhoc.py:21 ops/models/base.py:20 ops/models/playbook.py:27 -#, fuzzy -#| msgid "Command execution" msgid "Last execution" -msgstr "命令执行" +msgstr "最后执行" #: ops/models/adhoc.py:36 msgid "Adhoc" @@ -2851,16 +2776,12 @@ msgid "Creator" msgstr "创建者" #: ops/models/base.py:19 -#, fuzzy -#| msgid "Account key" msgid "Account policy" -msgstr "账号密钥" +msgstr "账号策略" #: ops/models/base.py:21 -#, fuzzy -#| msgid "Date last sync" msgid "Date last run" -msgstr "最后同步日期" +msgstr "最后执行日期" #: ops/models/base.py:50 xpack/plugins/cloud/models.py:169 msgid "Result" @@ -2868,7 +2789,7 @@ msgstr "结果" #: ops/models/base.py:51 msgid "Summary" -msgstr "" +msgstr "汇总" #: ops/models/celery.py:16 terminal/models/task.py:18 msgid "Kwargs" @@ -2886,19 +2807,19 @@ msgstr "结束" #: ops/models/playbook.py:10 msgid "Path" -msgstr "" +msgstr "路径" #: ops/models/playbook.py:18 msgid "Playbook template" -msgstr "" +msgstr "Playbook 模版" #: ops/models/playbook.py:23 msgid "Playbook" -msgstr "" +msgstr "Playbook" #: ops/models/playbook.py:24 msgid "Owner" -msgstr "" +msgstr "Owner" #: ops/models/playbook.py:26 settings/serializers/auth/sms.py:64 msgid "Template" @@ -2910,10 +2831,8 @@ msgid "Task" msgstr "任务" #: ops/models/playbook.py:39 -#, fuzzy -#| msgid "Run user" msgid "Run dir" -msgstr "运行的用户" +msgstr "运行目录" #: ops/notifications.py:17 msgid "Server performance" @@ -2944,16 +2863,12 @@ msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" #: ops/tasks.py:33 -#, fuzzy -#| msgid "Run asset" msgid "Run ansible task" -msgstr "运行的资产" +msgstr "运行 ansible 任务" #: ops/tasks.py:57 -#, fuzzy -#| msgid "Run command" msgid "Run ansible command" -msgstr "运行的命令" +msgstr "运行 ansible 命令" #: ops/tasks.py:79 msgid "Clean task history period" @@ -3341,10 +3256,6 @@ msgstr "云同步" msgid "Backup account" msgstr "备份账号" -#: rbac/tree.py:53 -msgid "Gather account" -msgstr "收集账号" - #: rbac/tree.py:54 msgid "App change auth" msgstr "应用改密" @@ -5294,10 +5205,8 @@ msgid "Apply assets" msgstr "申请资产" #: tickets/models/ticket/apply_asset.py:17 -#, fuzzy -#| msgid "Application account" msgid "Apply accounts" -msgstr "应用账号" +msgstr "申请账号" #: tickets/models/ticket/command_confirm.py:10 msgid "Run user" @@ -5308,8 +5217,6 @@ msgid "Run asset" msgstr "运行的资产" #: tickets/models/ticket/command_confirm.py:13 -#, fuzzy -#| msgid "account" msgid "Run account" msgstr "账号" @@ -5370,10 +5277,8 @@ msgid "Login asset" msgstr "登录资产" #: tickets/models/ticket/login_asset_confirm.py:19 -#, fuzzy -#| msgid "Login acl" msgid "Login account" -msgstr "登录访问控制" +msgstr "登录账号" #: tickets/models/ticket/login_confirm.py:12 msgid "Login datetime" @@ -6754,6 +6659,31 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Verify account" +#~ msgstr "验证密钥" + +#~ msgid "Gather accounts" +#~ msgstr "收集账号" + +#, fuzzy +#~| msgid "Hostname strategy" +#~ msgid "Automation strategy" +#~ msgstr "主机名策略" + +#~ msgid "Discovery strategy" +#~ msgstr "自动发现策略" + +#~ msgid "Reconcile strategy" +#~ msgstr "主机名策略" + +#~ msgid "Verify strategy" +#~ msgstr "SSH 密钥策略" + +#, fuzzy +#~| msgid "Can change auth setting" +#~ msgid "Change auth strategy" +#~ msgstr "认证设置" + #~ msgid "System User" #~ msgstr "系统用户" diff --git a/apps/orgs/migrations/0010_auto_20210219_1241.py b/apps/orgs/migrations/0010_auto_20210219_1241.py index facc6a654..6fc3b0577 100644 --- a/apps/orgs/migrations/0010_auto_20210219_1241.py +++ b/apps/orgs/migrations/0010_auto_20210219_1241.py @@ -34,13 +34,13 @@ def migrate_default_org_id(apps, schema_editor): for app, models_name in org_app_models: for model_name in models_name: t_start = time.time() - print("Migrate model org id: {}".format(model_name), end='') + print("\tMigrate model org id: {}".format(model_name), end='') sys.stdout.flush() model_cls = apps.get_model(app, model_name) model_cls.objects.filter(org_id='').update(org_id=default_id) interval = round((time.time() - t_start) * 1000, 2) - print(" done, use {} ms".format(interval)) + print("\tdone, use {} ms".format(interval)) def add_all_user_to_default_org(apps, schema_editor): @@ -53,16 +53,16 @@ def add_all_user_to_default_org(apps, schema_editor): t_start = time.time() count = users_qs.count() - print(f'Will add users to default org: {count}') + print(f'\tWill add users to default org: {count}') batch_size = 1000 for i in range(0, count, batch_size): users = list(users_qs[i:i + batch_size]) members = [org_members_model(user=user, org=default_org) for user in users] org_members_model.objects.bulk_create(members, ignore_conflicts=True) - print(f'Add users to default org: {i+1}-{i+len(users)}') + print(f'\t Add users to default org: {i+1}-{i+len(users)}') interval = round((time.time() - t_start) * 1000, 2) - print(f'done, use {interval} ms') + print(f'\tdone, use {interval} ms') class Migration(migrations.Migration): diff --git a/apps/tickets/migrations/0013_ticket_serial_num.py b/apps/tickets/migrations/0013_ticket_serial_num.py index 6a6568175..96d0cbc0d 100644 --- a/apps/tickets/migrations/0013_ticket_serial_num.py +++ b/apps/tickets/migrations/0013_ticket_serial_num.py @@ -12,7 +12,7 @@ def fill_ticket_serial_number(apps, schema_editor): curr_day = '00000000' curr_num = 1 - print(f'\n Fill ticket serial number ... ') + print(f'\n\tFill ticket serial number ... ') for ticket in tickets: # 跑这个脚本的时候,所有 ticket.serial_num == null date_created = as_current_tz(ticket.date_created) @@ -25,7 +25,6 @@ def fill_ticket_serial_number(apps, schema_editor): curr_num += 1 Ticket.objects.bulk_update(tickets, fields=('serial_num',)) - print(len(tickets), end='') class Migration(migrations.Migration): diff --git a/apps/tickets/migrations/0020_auto_20220817_1346.py b/apps/tickets/migrations/0020_auto_20220817_1346.py index 7bdd129f9..783021dc8 100644 --- a/apps/tickets/migrations/0020_auto_20220817_1346.py +++ b/apps/tickets/migrations/0020_auto_20220817_1346.py @@ -15,9 +15,9 @@ def migrate_system_to_account(apps, schema_editor): (apply_login_asset_ticket_model, 'apply_login_system_user', 'apply_login_account', False), ) - print("\n Start migrate system user to account") + print("\n\tStart migrate system user to account") for model, old_field, new_field, m2m in model_system_user_account: - print(" - migrate '{}'".format(model.__name__)) + print("\t - migrate '{}'".format(model.__name__)) count = 0 bulk_size = 1000 From 1a2193d091379273a709bc329b298bd6244e1207 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 19 Oct 2022 18:56:46 +0800 Subject: [PATCH 207/488] =?UTF-8?q?perf:=20=E8=B5=84=E4=BA=A7=20api=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=89=B9=E6=9C=89=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0092_add_host.py | 6 +++--- apps/assets/models/asset/common.py | 8 ++++++++ apps/assets/models/asset/database.py | 6 ++++++ apps/assets/models/asset/web.py | 7 ++++--- apps/assets/serializers/asset/common.py | 9 ++++++++- apps/assets/serializers/asset/host.py | 7 +++++++ 6 files changed, 36 insertions(+), 7 deletions(-) diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index a6138c30a..10a5c8367 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -93,9 +93,9 @@ class Migration(migrations.Migration): fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), ('autofill', models.CharField(default='basic', max_length=16)), - ('password_selector', models.CharField(blank=True, default='', max_length=128)), - ('submit_selector', models.CharField(blank=True, default='', max_length=128)), - ('username_selector', models.CharField(blank=True, default='', max_length=128)) + ('password_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Password selector')), + ('submit_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Submit selector')), + ('username_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Username selector')), ], options={ 'abstract': False, diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 573091959..ae3c99b60 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -105,6 +105,14 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): def __str__(self): return '{0.name}({0.address})'.format(self) + @property + def category_property(self): + if not hasattr(self, self.category): + return {} + instance = getattr(self, self.category) + private_fields = [i.name for i in instance._meta.local_fields if i.name != 'asset_ptr'] + return {i: getattr(instance, i) for i in private_fields} + def get_target_ip(self): return self.address diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index a8f0daf15..de6b6a758 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -14,5 +14,11 @@ class Database(Asset): def ip(self): return self.address + @property + def category_property(self): + return { + 'db_name': self.db_name, + } + class Meta: verbose_name = _("Database") diff --git a/apps/assets/models/asset/web.py b/apps/assets/models/asset/web.py index afe7bed72..1bcd3a766 100644 --- a/apps/assets/models/asset/web.py +++ b/apps/assets/models/asset/web.py @@ -1,10 +1,11 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ from .common import Asset class Web(Asset): autofill = models.CharField(max_length=16, default='basic') - username_selector = models.CharField(max_length=128, blank=True, default='') - password_selector = models.CharField(max_length=128, blank=True, default='') - submit_selector = models.CharField(max_length=128, blank=True, default='') + username_selector = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Username selector")) + password_selector = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Password selector")) + submit_selector = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Submit selector")) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 059b7ffe3..8690b6c10 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -77,7 +77,8 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) 'nodes', 'labels', 'accounts', 'protocols', 'nodes_display', ] read_only_fields = [ - 'category', 'type', 'connectivity', 'date_verified', + 'category', 'type', 'category_property', + 'connectivity', 'date_verified', 'created_by', 'date_created', ] fields = fields_small + fields_fk + fields_m2m + read_only_fields @@ -86,6 +87,12 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) 'address': {'label': _('Address')}, } + def get_field_names(self, declared_fields, info): + names = super().get_field_names(declared_fields, info) + if self.__class__.__name__ != 'AssetSerializer': + names.remove('category_property') + return names + @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ diff --git a/apps/assets/serializers/asset/host.py b/apps/assets/serializers/asset/host.py index 35f1ca00a..e01e1bab9 100644 --- a/apps/assets/serializers/asset/host.py +++ b/apps/assets/serializers/asset/host.py @@ -34,4 +34,11 @@ class HostSerializer(AssetSerializer): class Meta(AssetSerializer.Meta): model = Host fields = AssetSerializer.Meta.fields + ['info'] + extra_kwargs = { + **AssetSerializer.Meta.extra_kwargs, + 'address': { + 'label': _("IP/Host") + }, + } + From 26278cc9e06ff84137b757f14c9f98c2ea0e9270 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 19 Oct 2022 17:05:21 +0800 Subject: [PATCH 208/488] perf: change secret automation --- apps/assets/automations/__init__.py | 1 + apps/assets/automations/base/manager.py | 2 +- .../automations/change_secret/manager.py | 59 ++++++++------- apps/assets/automations/endpoint.py | 11 +-- apps/assets/const/__init__.py | 4 +- apps/assets/const/account.py | 15 ++++ apps/assets/const/automation.py | 30 ++++++++ .../migrations/0108_auto_20221019_1706.py | 75 +++++++++++++++++++ apps/assets/models/account.py | 1 - apps/assets/models/automations/base.py | 31 +++----- .../models/automations/change_secret.py | 66 +++++++++------- apps/assets/models/base.py | 15 +--- apps/assets/tasks/__init__.py | 8 +- apps/assets/tasks/automation.py | 18 +++++ apps/assets/utils.py | 4 +- apps/ops/mixin.py | 2 +- 16 files changed, 244 insertions(+), 98 deletions(-) create mode 100644 apps/assets/const/account.py create mode 100644 apps/assets/const/automation.py create mode 100644 apps/assets/migrations/0108_auto_20221019_1706.py create mode 100644 apps/assets/tasks/automation.py diff --git a/apps/assets/automations/__init__.py b/apps/assets/automations/__init__.py index 7c63c916a..30fb03cda 100644 --- a/apps/assets/automations/__init__.py +++ b/apps/assets/automations/__init__.py @@ -1 +1,2 @@ +from .endpoint import ExecutionManager from .methods import platform_automation_methods, filter_platform_methods diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index b1d140492..ad8104127 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -148,7 +148,7 @@ class BasePlaybookManager: print(" inventory: {}".format(runner.inventory)) print(" playbook: {}".format(runner.playbook)) - def run(self, **kwargs): + def run(self, *args, **kwargs): runners = self.get_runners() if len(runners) > 1: print("### 分批次执行开始任务, 总共 {}\n".format(len(runners))) diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 072278d0d..fcb663ae8 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -6,30 +6,26 @@ from collections import defaultdict from django.utils import timezone from common.utils import lazyproperty, gen_key_pair -from assets.models import ChangeSecretRecord, SecretStrategy +from assets.models import ChangeSecretRecord +from assets.const import ( + AutomationTypes, SecretType, SecretStrategy, DEFAULT_PASSWORD_RULES +) from ..base.manager import BasePlaybookManager -string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~' -DEFAULT_PASSWORD_LENGTH = 30 -DEFAULT_PASSWORD_RULES = { - 'length': DEFAULT_PASSWORD_LENGTH, - 'symbol_set': string_punctuation -} - class ChangeSecretManager(BasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.method_hosts_mapper = defaultdict(list) - self.password_strategy = self.execution.automation.password_strategy - self.ssh_key_strategy = self.execution.automation.ssh_key_strategy + self.secret_strategy = self.execution.plan_snapshot['secret_strategy'] + self.ssh_key_change_strategy = self.execution.plan_snapshot['ssh_key_change_strategy'] self._password_generated = None self._ssh_key_generated = None self.name_recorder_mapper = {} # 做个映射,方便后面处理 @classmethod def method_type(cls): - return 'change_secret' + return AutomationTypes.change_secret @lazyproperty def related_accounts(self): @@ -41,43 +37,52 @@ class ChangeSecretManager(BasePlaybookManager): return private_key def generate_password(self): - kwargs = self.automation.password_rules or {} + kwargs = self.automation.plan_snapshot['password_rules'] or {} length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length'])) symbol_set = kwargs.get('symbol_set') if symbol_set is None: symbol_set = DEFAULT_PASSWORD_RULES['symbol_set'] - chars = string.ascii_letters + string.digits + symbol_set - password = ''.join([random.choice(chars) for _ in range(length)]) + + no_special_chars = string.ascii_letters + string.digits + chars = no_special_chars + symbol_set + + first_char = random.choice(no_special_chars) + password = ''.join([random.choice(chars) for _ in range(length - 1)]) + password = first_char + password return password def get_ssh_key(self): - if self.ssh_key_strategy == SecretStrategy.custom: - return self.automation.ssh_key - elif self.ssh_key_strategy == SecretStrategy.random_one: + if self.secret_strategy == SecretStrategy.custom: + ssh_key = self.automation.plan_snapshot['ssh_key'] + if not ssh_key: + raise ValueError("Automation SSH key must be set") + return ssh_key + elif self.secret_strategy == SecretStrategy.random_one: if not self._ssh_key_generated: self._ssh_key_generated = self.generate_ssh_key() return self._ssh_key_generated else: - self.generate_ssh_key() + return self.generate_ssh_key() def get_password(self): - if self.password_strategy == SecretStrategy.custom: - if not self.automation.password: + if self.secret_strategy == SecretStrategy.custom: + password = self.automation.plan_snapshot['password'] + if not password: raise ValueError("Automation Password must be set") - return self.automation.password - elif self.password_strategy == SecretStrategy.random_one: + return password + elif self.secret_strategy == SecretStrategy.random_one: if not self._password_generated: self._password_generated = self.generate_password() return self._password_generated else: - self.generate_password() + return self.generate_password() def get_secret(self, account): - if account.secret_type == 'ssh-key': + if account.secret_type == SecretType.ssh_key: secret = self.get_ssh_key() - else: + elif account.secret_type == SecretType.password: secret = self.get_password() - if not secret: + else: raise ValueError("Secret must be set") return secret @@ -145,5 +150,3 @@ class ChangeSecretManager(BasePlaybookManager): def on_runner_failed(self, runner, e): pass - - diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index 3e69d2953..0efbc55ea 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -3,18 +3,19 @@ # from .change_secret.manager import ChangeSecretManager from .gather_facts.manager import GatherFactsManager +from ..const import AutomationTypes class ExecutionManager: manager_type_mapper = { - 'change_secret': ChangeSecretManager, - 'gather_facts': GatherFactsManager, + AutomationTypes.change_secret: ChangeSecretManager, + AutomationTypes.gather_facts: GatherFactsManager, } def __init__(self, execution): self.execution = execution - self._runner = self.manager_type_mapper[execution.automation.type](execution) + self._runner = self.manager_type_mapper[execution.manager_type](execution) - def run(self, **kwargs): - return self._runner.run(**kwargs) + def run(self, *args, **kwargs): + return self._runner.run(*args, **kwargs) diff --git a/apps/assets/const/__init__.py b/apps/assets/const/__init__.py index e3e822fb3..81115b412 100644 --- a/apps/assets/const/__init__.py +++ b/apps/assets/const/__init__.py @@ -1,3 +1,5 @@ +from .types import * +from .account import * from .protocol import * from .category import * -from .types import * +from .automation import * diff --git a/apps/assets/const/account.py b/apps/assets/const/account.py new file mode 100644 index 000000000..5ec872134 --- /dev/null +++ b/apps/assets/const/account.py @@ -0,0 +1,15 @@ +from django.db.models import TextChoices +from django.utils.translation import ugettext_lazy as _ + + +class Connectivity(TextChoices): + unknown = 'unknown', _('Unknown') + ok = 'ok', _('Ok') + failed = 'failed', _('Failed') + + +class SecretType(TextChoices): + password = 'password', _('Password') + ssh_key = 'ssh_key', _('SSH key') + access_key = 'access_key', _('Access key') + token = 'token', _('Token') diff --git a/apps/assets/const/automation.py b/apps/assets/const/automation.py new file mode 100644 index 000000000..54b3cc019 --- /dev/null +++ b/apps/assets/const/automation.py @@ -0,0 +1,30 @@ +from django.db.models import TextChoices +from django.utils.translation import ugettext_lazy as _ + +string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~' +DEFAULT_PASSWORD_LENGTH = 30 +DEFAULT_PASSWORD_RULES = { + 'length': DEFAULT_PASSWORD_LENGTH, + 'symbol_set': string_punctuation +} + + +class AutomationTypes(TextChoices): + ping = 'ping', _('Ping') + gather_facts = 'gather_facts', _('Gather facts') + push_account = 'push_account', _('Create account') + change_secret = 'change_secret', _('Change secret') + verify_account = 'verify_account', _('Verify account') + gather_account = 'gather_account', _('Gather account') + + +class SecretStrategy(TextChoices): + custom = 'specific', _('Specific') + random_one = 'random_one', _('All assets use the same random password') + random_all = 'random_all', _('All assets use different random password') + + +class SSHKeyStrategy(TextChoices): + add = 'add', _('Append SSH KEY') + set = 'set', _('Empty and append SSH KEY') + set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ') diff --git a/apps/assets/migrations/0108_auto_20221019_1706.py b/apps/assets/migrations/0108_auto_20221019_1706.py new file mode 100644 index 000000000..f59a5b7a9 --- /dev/null +++ b/apps/assets/migrations/0108_auto_20221019_1706.py @@ -0,0 +1,75 @@ +# Generated by Django 3.2.14 on 2022-10-19 09:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_auto_20221019_1115'), + ] + + operations = [ + migrations.AlterModelOptions( + name='automationexecution', + options={'verbose_name': 'Automation task execution'}, + ), + migrations.AlterModelOptions( + name='baseautomation', + options={'verbose_name': 'Automation task'}, + ), + migrations.AlterModelOptions( + name='changesecretrecord', + options={'verbose_name': 'Change secret record'}, + ), + migrations.AlterModelOptions( + name='verifyaccountautomation', + options={'verbose_name': 'Verify account automation'}, + ), + migrations.RenameField( + model_name='changesecretautomation', + old_name='password', + new_name='secret', + ), + migrations.RemoveField( + model_name='baseautomation', + name='updated_by', + ), + migrations.RemoveField( + model_name='changesecretautomation', + name='password_strategy', + ), + migrations.RemoveField( + model_name='changesecretautomation', + name='secret_types', + ), + migrations.RemoveField( + model_name='changesecretautomation', + name='ssh_key', + ), + migrations.RemoveField( + model_name='changesecretautomation', + name='ssh_key_strategy', + ), + migrations.AddField( + model_name='changesecretautomation', + name='secret_strategy', + field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Secret strategy'), + ), + migrations.AddField( + model_name='changesecretautomation', + name='secret_type', + field=models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'), + ), + migrations.AlterField( + model_name='automationexecution', + name='automation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation task'), + ), + migrations.AlterField( + model_name='changesecretautomation', + name='ssh_key_change_strategy', + field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 391a58e9b..0870e2f6b 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -1,5 +1,4 @@ from django.db import models -from django.db.models import Q from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 17d681477..b0fbd80a2 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -4,23 +4,14 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.const.choices import Trigger +from common.mixins.models import CommonModelMixin from common.db.fields import EncryptJsonDictTextField -from orgs.mixins.models import OrgModelMixin, JMSOrgBaseModel +from orgs.mixins.models import OrgModelMixin from ops.mixin import PeriodTaskModelMixin -from ops.tasks import execute_automation_strategy from assets.models import Node, Asset -class AutomationTypes(models.TextChoices): - ping = 'ping', _('Ping') - gather_facts = 'gather_facts', _('Gather facts') - push_account = 'push_account', _('Create account') - change_secret = 'change_secret', _('Change secret') - verify_account = 'verify_account', _('Verify account') - gather_account = 'gather_account', _('Gather account') - - -class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): +class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) nodes = models.ManyToManyField( 'assets.Node', blank=True, verbose_name=_("Nodes") @@ -47,18 +38,17 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): return assets.group_by_platform() def get_register_task(self): - name = "automation_strategy_period_{}".format(str(self.id)[:8]) - task = execute_automation_strategy.name - args = (str(self.id), Trigger.timing) - kwargs = {} - return name, task, args, kwargs + raise NotImplementedError def to_attr_json(self): return { 'name': self.name, + 'type': self.type, + 'org_id': self.org_id, + 'comment': self.comment, 'accounts': self.accounts, + 'nodes': list(self.nodes.all().values_list('id', flat=True)), 'assets': list(self.assets.all().values_list('id', flat=True)), - 'nodes': list(self.assets.all().values_list('id', flat=True)), } def execute(self, trigger=Trigger.manual): @@ -67,8 +57,9 @@ class BaseAutomation(JMSOrgBaseModel, PeriodTaskModelMixin): except AttributeError: eid = str(uuid.uuid4()) - execution = self.executions.create( - id=eid, trigger=trigger, + execution = self.executions.model.objects.create( + id=eid, trigger=trigger, automation=self, + plan_snapshot=self.to_attr_json(), ) return execution.start() diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index 0d34a4840..47462320d 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -2,45 +2,61 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.db import fields +from common.const.choices import Trigger from common.db.models import JMSBaseModel +from assets.tasks import execute_change_secret_automation +from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy from .base import BaseAutomation - -__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'SecretStrategy'] - - -class SecretStrategy(models.TextChoices): - custom = 'specific', _('Specific') - random_one = 'random_one', _('All assets use the same random password') - random_all = 'random_all', _('All assets use different random password') - - -class SSHKeyStrategy(models.TextChoices): - add = 'add', _('Append SSH KEY') - set = 'set', _('Empty and append SSH KEY') - set_jms = 'set_jms', _('Replace (The key generated by JumpServer) ') +__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord'] class ChangeSecretAutomation(BaseAutomation): - secret_types = models.JSONField(default=list, verbose_name=_('Secret types')) - password_strategy = models.CharField(choices=SecretStrategy.choices, max_length=16, - default=SecretStrategy.random_one, verbose_name=_('Password strategy')) - password = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) + secret_type = models.CharField( + choices=SecretType.choices, max_length=16, + default=SecretType.password, verbose_name=_('Secret type') + ) + secret_strategy = models.CharField( + choices=SecretStrategy.choices, max_length=16, + default=SecretStrategy.random_one, verbose_name=_('Secret strategy') + ) + secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) password_rules = models.JSONField(default=dict, verbose_name=_('Password rules')) - - ssh_key_strategy = models.CharField(choices=SecretStrategy.choices, default=SecretStrategy.random_one, max_length=16) - ssh_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH key')) - ssh_key_change_strategy = models.CharField(choices=SSHKeyStrategy.choices, max_length=16, - default=SSHKeyStrategy.add, verbose_name=_('SSH key strategy')) + ssh_key_change_strategy = models.CharField( + choices=SSHKeyStrategy.choices, max_length=16, + default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy') + ) recipients = models.ManyToManyField('users.User', blank=True, verbose_name=_("Recipient")) def save(self, *args, **kwargs): - self.type = 'change_secret' + self.type = AutomationTypes.change_secret super().save(*args, **kwargs) class Meta: verbose_name = _("Change secret automation") + def get_register_task(self): + name = "automation_change_secret_strategy_period_{}".format(str(self.id)[:8]) + task = execute_change_secret_automation.name + args = (str(self.id), Trigger.timing) + kwargs = {} + return name, task, args, kwargs + + def to_attr_json(self): + attr_json = super().to_attr_json() + attr_json.update({ + 'secret': self.secret, + 'secret_type': self.secret_type, + 'secret_strategy': self.secret_strategy, + 'password_rules': self.password_rules, + 'ssh_key_change_strategy': self.ssh_key_change_strategy, + 'recipients': { + str(recipient.id): (str(recipient), bool(recipient.secret_key)) + for recipient in self.recipients.all() + } + }) + return attr_json + class ChangeSecretRecord(JMSBaseModel): execution = models.ForeignKey('assets.AutomationExecution', on_delete=models.CASCADE) @@ -53,7 +69,7 @@ class ChangeSecretRecord(JMSBaseModel): error = models.TextField(blank=True, null=True, verbose_name=_('Error')) class Meta: - verbose_name = _("Change secret") + verbose_name = _("Change secret record") def __str__(self): return self.account.__str__() diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 56de5fbac..f293b9a93 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -18,18 +18,12 @@ from common.utils import ( random_string, ssh_pubkey_gen, ) from common.db import fields +from assets.const import Connectivity from orgs.mixins.models import OrgModelMixin - logger = get_logger(__file__) -class Connectivity(models.TextChoices): - unknown = 'unknown', _('Unknown') - ok = 'ok', _('Ok') - failed = 'failed', _('Failed') - - class AbsConnectivity(models.Model): connectivity = models.CharField( choices=Connectivity.choices, default=Connectivity.unknown, @@ -64,7 +58,9 @@ class BaseAccount(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) - secret_type = models.CharField(max_length=16, choices=SecretType.choices, default='password', verbose_name=_('Secret type')) + secret_type = models.CharField( + max_length=16, choices=SecretType.choices, default=SecretType.password, verbose_name=_('Secret type') + ) secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -165,10 +161,7 @@ class BaseAccount(OrgModelMixin): 'username': self.username, 'password': self.password, 'public_key': self.public_key, - 'private_key': self.private_key_file, - 'token': self.token } class Meta: abstract = True - diff --git a/apps/assets/tasks/__init__.py b/apps/assets/tasks/__init__.py index fcd5fbe46..1f26cde3f 100644 --- a/apps/assets/tasks/__init__.py +++ b/apps/assets/tasks/__init__.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- # + from .utils import * from .common import * +from .backup import * +from .automation import * +from .nodes_amount import * +from .gather_asset_users import * from .asset_connectivity import * from .account_connectivity import * -from .gather_asset_users import * from .gather_asset_hardware_info import * -from .nodes_amount import * -from .backup import * diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py new file mode 100644 index 000000000..0859381a8 --- /dev/null +++ b/apps/assets/tasks/automation.py @@ -0,0 +1,18 @@ +from celery import shared_task + +from orgs.utils import tmp_to_root_org, tmp_to_org +from common.utils import get_logger, get_object_or_none + +logger = get_logger(__file__) + + +@shared_task +def execute_change_secret_automation(pid, trigger): + from assets.models import ChangeSecretAutomation + with tmp_to_root_org(): + instance = get_object_or_none(ChangeSecretAutomation, pk=pid) + if not instance: + logger.error("No automation plan found: {}".format(pid)) + return + with tmp_to_org(instance.org): + instance.execute(trigger) diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 478b2187d..6c39fffa5 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -126,8 +126,8 @@ class NodeAssetsUtil: from assets.models import Node, Asset nodes = list(Node.objects.all()) - nodes_assets = Asset.nodes.through.objects.all()\ - .annotate(aid=output_as_string('asset_id'))\ + nodes_assets = Asset.nodes.through.objects.all() \ + .annotate(aid=output_as_string('asset_id')) \ .values_list('node__key', 'aid') mapping = defaultdict(set) diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py index 4d2fd52a7..7c0b473ec 100644 --- a/apps/ops/mixin.py +++ b/apps/ops/mixin.py @@ -71,7 +71,7 @@ class PeriodTaskModelMixin(models.Model): } create_or_update_celery_periodic_tasks(tasks) - def save(self, **kwargs): + def save(self, *args, **kwargs): instance = super().save(**kwargs) self.set_period_schedule() return instance From c55d3c0b6cd1902980fb966a821b96a55ca115e9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 20 Oct 2022 16:39:11 +0800 Subject: [PATCH 209/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=20histories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/account/__init__.py | 1 - apps/assets/api/account/account.py | 12 +++++- apps/assets/api/account/history.py | 38 ------------------- apps/assets/migrations/0092_add_host.py | 2 +- .../migrations/0107_auto_20221019_1115.py | 8 ++-- .../migrations/0108_auto_20221019_2040.py | 24 ++++++++++++ apps/assets/models/account.py | 2 +- apps/assets/models/asset/web.py | 8 +++- apps/assets/serializers/account/__init__.py | 1 - apps/assets/serializers/account/account.py | 11 +++++- apps/assets/serializers/account/history.py | 26 ------------- apps/assets/serializers/asset/web.py | 5 ++- apps/assets/urls/api_urls.py | 2 - apps/rbac/const.py | 4 -- 14 files changed, 59 insertions(+), 85 deletions(-) delete mode 100644 apps/assets/api/account/history.py create mode 100644 apps/assets/migrations/0108_auto_20221019_2040.py delete mode 100644 apps/assets/serializers/account/history.py diff --git a/apps/assets/api/account/__init__.py b/apps/assets/api/account/__init__.py index 6e402a550..19f1af93d 100644 --- a/apps/assets/api/account/__init__.py +++ b/apps/assets/api/account/__init__.py @@ -1,4 +1,3 @@ from .account import * from .backup import * -from .history import * from .template import * diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index eaba34571..8e8e320bd 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -47,10 +47,18 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): # Todo: 记得打开 # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] rbac_perms = { - 'list': 'assets.view_assetaccountsecret', - 'retrieve': 'assets.view_assetaccountsecret', + 'list': 'assets.view_accountsecret', + 'retrieve': 'assets.view_accountsecret', + 'histories': ['assets.view_accountsecret'], } + @action(methods=['get'], detail=True, url_path='histories', serializer_class=serializers.AccountHistorySerializer) + def histories(self, request, *args, **kwargs): + account = self.get_object() + histories = account.history.all() + serializer = serializers.AccountHistorySerializer(histories, many=True) + return Response(serializer.data) + class AccountTaskCreateAPI(CreateAPIView): serializer_class = serializers.AccountTaskSerializer diff --git a/apps/assets/api/account/history.py b/apps/assets/api/account/history.py deleted file mode 100644 index cfcbfeb8e..000000000 --- a/apps/assets/api/account/history.py +++ /dev/null @@ -1,38 +0,0 @@ -from assets import serializers -from assets.models import Account -from assets.filters import AccountFilterSet -from common.mixins import RecordViewLogMixin -from .account import AccountViewSet, AccountSecretsViewSet - -__all__ = ['AccountHistoryViewSet', 'AccountHistorySecretsViewSet'] - - -class AccountHistoryFilterSet(AccountFilterSet): - class Meta: - model = Account.history.model - fields = ['id', 'secret_type'] - - -class AccountHistoryViewSet(AccountViewSet): - model = Account.history.model - filterset_class = AccountHistoryFilterSet - serializer_classes = { - 'default': serializers.AccountHistorySerializer, - } - rbac_perms = { - 'list': 'assets.view_assethistoryaccount', - 'retrieve': 'assets.view_assethistoryaccount', - } - http_method_names = ['get', 'options'] - - -class AccountHistorySecretsViewSet(RecordViewLogMixin, AccountHistoryViewSet): - serializer_classes = { - 'default': serializers.AccountHistorySecretSerializer - } - http_method_names = ['get'] - permission_classes = AccountSecretsViewSet.permission_classes - rbac_perms = { - 'list': 'assets.view_assethistoryaccountsecret', - 'retrieve': 'assets.view_assethistoryaccountsecret', - } diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index 10a5c8367..72ad9f7d8 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -92,7 +92,7 @@ class Migration(migrations.Migration): name='Web', fields=[ ('asset_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset')), - ('autofill', models.CharField(default='basic', max_length=16)), + ('autofill', models.CharField(choices=[('no', 'Disabled'), ('basic', 'Basic'), ('script', 'Script')], default='basic', max_length=16, verbose_name='Autofill')), ('password_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Password selector')), ('submit_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Submit selector')), ('username_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Username selector')), diff --git a/apps/assets/migrations/0107_auto_20221019_1115.py b/apps/assets/migrations/0107_auto_20221019_1115.py index 0c0fb772f..6c812b708 100644 --- a/apps/assets/migrations/0107_auto_20221019_1115.py +++ b/apps/assets/migrations/0107_auto_20221019_1115.py @@ -28,7 +28,7 @@ class Migration(migrations.Migration): ('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')), ], options={ - 'verbose_name': 'Automation strategy execution', + 'verbose_name': 'Automation task execution', }, ), migrations.CreateModel( @@ -52,7 +52,7 @@ class Migration(migrations.Migration): ('nodes', models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes')), ], options={ - 'verbose_name': 'Automation plan', + 'verbose_name': 'Automation task', 'unique_together': {('org_id', 'name')}, }, ), @@ -122,7 +122,7 @@ class Migration(migrations.Migration): ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), ], options={ - 'verbose_name': 'Verify secret automation', + 'verbose_name': 'Verify account automation', }, bases=('assets.baseautomation',), ), @@ -150,7 +150,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='automationexecution', name='automation', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation strategy'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation task'), ), migrations.CreateModel( name='ChangeSecretAutomation', diff --git a/apps/assets/migrations/0108_auto_20221019_2040.py b/apps/assets/migrations/0108_auto_20221019_2040.py new file mode 100644 index 000000000..c2f47fc2f --- /dev/null +++ b/apps/assets/migrations/0108_auto_20221019_2040.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-10-19 12:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0107_auto_20221019_1115'), + ] + + operations = [ + migrations.AddField( + model_name='web', + name='script', + field=models.JSONField(blank=True, default=list, verbose_name='Script'), + ), + migrations.AddField( + model_name='historicalaccount', + name='version', + field=models.IntegerField(default=0, verbose_name='Version'), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 391a58e9b..8bb19bb39 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -41,7 +41,7 @@ class Account(BaseAccount): ) version = models.IntegerField(default=0, verbose_name=_('Version')) history = AccountHistoricalRecords( - included_fields=['id', 'secret_type', 'secret'] + included_fields=['id', 'secret_type', 'secret', 'version'] ) class Meta: diff --git a/apps/assets/models/asset/web.py b/apps/assets/models/asset/web.py index 1bcd3a766..15445b3d6 100644 --- a/apps/assets/models/asset/web.py +++ b/apps/assets/models/asset/web.py @@ -5,7 +5,13 @@ from .common import Asset class Web(Asset): - autofill = models.CharField(max_length=16, default='basic') + class FillType(models.TextChoices): + no = 'no', _('Disabled') + basic = 'basic', _('Basic') + script = 'script', _('Script') + + autofill = models.CharField(max_length=16, choices=FillType.choices, default='basic', verbose_name=_("Autofill")) username_selector = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Username selector")) password_selector = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Password selector")) submit_selector = models.CharField(max_length=128, blank=True, default='', verbose_name=_("Submit selector")) + script = models.JSONField(blank=True, default=list, verbose_name=_("Script")) diff --git a/apps/assets/serializers/account/__init__.py b/apps/assets/serializers/account/__init__.py index 70c013231..062b7064c 100644 --- a/apps/assets/serializers/account/__init__.py +++ b/apps/assets/serializers/account/__init__.py @@ -1,3 +1,2 @@ from .account import * -from .history import * from .template import * diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 5ef2bfde3..6faf2c946 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -60,8 +60,8 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): class Meta(BaseAccountSerializer.Meta): model = Account fields = BaseAccountSerializer.Meta.fields \ - + ['su_from', 'version', 'asset'] \ - + ['template', 'push_now'] + + ['su_from', 'version', 'asset'] \ + + ['template', 'push_now'] extra_kwargs = { **BaseAccountSerializer.Meta.extra_kwargs, 'name': {'required': False, 'allow_null': True}, @@ -89,6 +89,13 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): } +class AccountHistorySerializer(serializers.ModelSerializer): + class Meta: + model = Account.history.model + fields = ['id', 'secret', 'secret_type', 'version', 'history_date', 'history_user'] + read_only_fields = fields + + class AccountTaskSerializer(serializers.Serializer): ACTION_CHOICES = ( ('test', 'test'), diff --git a/apps/assets/serializers/account/history.py b/apps/assets/serializers/account/history.py deleted file mode 100644 index a49636334..000000000 --- a/apps/assets/serializers/account/history.py +++ /dev/null @@ -1,26 +0,0 @@ - -from assets.models import Account -from common.drf.serializers import SecretReadableMixin -from .base import BaseAccountSerializer -from .account import AccountSerializer, AccountSecretSerializer - - -class AccountHistorySerializer(AccountSerializer): - class Meta: - model = Account.history.model - fields = BaseAccountSerializer.Meta.fields_mini - read_only_fields = fields - ref_name = 'AccountHistorySerializer' - - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields = list(set(fields) - {'org_name'}) - return fields - - def to_representation(self, instance): - return super(AccountSerializer, self).to_representation(instance) - - -class AccountHistorySecretSerializer(SecretReadableMixin, AccountHistorySerializer): - class Meta(AccountHistorySerializer.Meta): - extra_kwargs = AccountSecretSerializer.Meta.extra_kwargs diff --git a/apps/assets/serializers/asset/web.py b/apps/assets/serializers/asset/web.py index a6bd89435..333795473 100644 --- a/apps/assets/serializers/asset/web.py +++ b/apps/assets/serializers/asset/web.py @@ -10,7 +10,8 @@ class WebSerializer(AssetSerializer): model = Web fields = AssetSerializer.Meta.fields + [ 'autofill', 'username_selector', - 'password_selector', 'submit_selector' + 'password_selector', 'submit_selector', + 'script' ] extra_kwargs = { **AssetSerializer.Meta.extra_kwargs, @@ -25,5 +26,5 @@ class WebSerializer(AssetSerializer): }, 'submit_selector': { 'default': 'button[type=submit]', - } + }, } diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 491d83163..688319ef6 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -17,8 +17,6 @@ router.register(r'clouds', api.CloudViewSet, 'cloud') router.register(r'accounts', api.AccountViewSet, 'account') router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') -router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history') -router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') router.register(r'labels', api.LabelViewSet, 'label') router.register(r'nodes', api.NodeViewSet, 'node') diff --git a/apps/rbac/const.py b/apps/rbac/const.py index 037358be0..bf79168a3 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -39,10 +39,6 @@ exclude_permissions = ( ('assets', 'assetuser', '*', '*'), ('assets', 'gathereduser', 'add,delete,change', 'gathereduser'), ('assets', 'accountbackupplanexecution', 'delete,change', 'accountbackupplanexecution'), - ('assets', 'account', 'change', 'account'), - # TODO 暂时去掉历史账号的权限 - ('assets', 'account', '*', 'assethistoryaccount'), - ('assets', 'account', '*', 'assethistoryaccountsecret'), ('perms', 'userassetgrantedtreenoderelation', '*', '*'), ('perms', 'usergrantedmappingnode', '*', '*'), From a450ceee91bf72d310492ebab2d0e957ed1812c0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 20 Oct 2022 16:44:15 +0800 Subject: [PATCH 210/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20migrations?= =?UTF-8?q?=20=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{0108_auto_20221019_2040.py => 0109_auto_20221019_2040.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/assets/migrations/{0108_auto_20221019_2040.py => 0109_auto_20221019_2040.py} (92%) diff --git a/apps/assets/migrations/0108_auto_20221019_2040.py b/apps/assets/migrations/0109_auto_20221019_2040.py similarity index 92% rename from apps/assets/migrations/0108_auto_20221019_2040.py rename to apps/assets/migrations/0109_auto_20221019_2040.py index c2f47fc2f..958ca4bea 100644 --- a/apps/assets/migrations/0108_auto_20221019_2040.py +++ b/apps/assets/migrations/0109_auto_20221019_2040.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('assets', '0107_auto_20221019_1115'), + ('assets', '0108_auto_20221019_1706'), ] operations = [ From 168de45da503a59cd14d35e1779441950649e8a8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 20 Oct 2022 20:06:58 +0800 Subject: [PATCH 211/488] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=20device=20a?= =?UTF-8?q?pi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/__init__.py | 2 +- .../api/asset/{network.py => device.py} | 4 +-- apps/assets/models/account.py | 34 ++++++++++++------- apps/assets/serializers/account/account.py | 1 + apps/locale/zh/LC_MESSAGES/django.po | 2 +- 5 files changed, 27 insertions(+), 16 deletions(-) rename apps/assets/api/asset/{network.py => device.py} (73%) diff --git a/apps/assets/api/asset/__init__.py b/apps/assets/api/asset/__init__.py index bca1dec50..c20e44573 100644 --- a/apps/assets/api/asset/__init__.py +++ b/apps/assets/api/asset/__init__.py @@ -3,5 +3,5 @@ from .host import * from .database import * from .web import * from .cloud import * -from .network import * +from .device import * from .permission import * diff --git a/apps/assets/api/asset/network.py b/apps/assets/api/asset/device.py similarity index 73% rename from apps/assets/api/asset/network.py rename to apps/assets/api/asset/device.py index e64f69e1f..f6a457fe4 100644 --- a/apps/assets/api/asset/network.py +++ b/apps/assets/api/asset/device.py @@ -1,5 +1,5 @@ -from assets.serializers import HostSerializer +from assets.serializers import DeviceSerializer from assets.models import Device from .asset import AssetViewSet @@ -11,5 +11,5 @@ class DeviceViewSet(AssetViewSet): def get_serializer_classes(self): serializer_classes = super().get_serializer_classes() - serializer_classes['default'] = HostSerializer + serializer_classes['default'] = DeviceSerializer return serializer_classes diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index b0ff6c482..70351a726 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -13,16 +13,28 @@ class AccountHistoricalRecords(HistoricalRecords): self.included_fields = kwargs.pop('included_fields', None) super().__init__(*args, **kwargs) + def post_save(self, instance, created, using=None, **kwargs): + if not self.included_fields: + return super().post_save(instance, created, using=using, **kwargs) + + check_fields = set(self.included_fields) - {'version'} + history_attrs = instance.history.all().values(*check_fields).first() + if history_attrs is None: + return super().post_save(instance, created, using=using, **kwargs) + + attrs = {field: getattr(instance, field) for field in check_fields} + history_attrs = set(history_attrs.items()) + attrs = set(attrs.items()) + diff = attrs - history_attrs + if not diff: + return + super().post_save(instance, created, using=using, **kwargs) + def fields_included(self, model): - fields = [] - for field in model._meta.fields: - if self.included_fields is None: - if field.name not in self.excluded_fields: - fields.append(field) - else: - if field.name in self.included_fields: - fields.append(field) - return fields + if self.included_fields: + fields = [i for i in model._meta.fields if i.name in self.included_fields] + return fields + return super().fields_included(model) class Account(BaseAccount): @@ -39,9 +51,7 @@ class Account(BaseAccount): on_delete=models.SET_NULL, verbose_name=_("Su from") ) version = models.IntegerField(default=0, verbose_name=_('Version')) - history = AccountHistoricalRecords( - included_fields=['id', 'secret_type', 'secret', 'version'] - ) + history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version']) class Meta: verbose_name = _('Account') diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 6faf2c946..1ad2dbda6 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -15,6 +15,7 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): push_now = serializers.BooleanField( default=False, label=_("Push now"), write_only=True ) + has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True) @staticmethod def validate_template(value): diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 2248ab02c..0f77ac7d1 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1086,7 +1086,7 @@ msgstr "" #: assets/serializers/account/account.py:16 msgid "Push now" -msgstr "" +msgstr "立刻推送" #: assets/serializers/account/account.py:24 msgid "Account template not found" From 7255cf68a9e42c2d3d6aa6460022e8db40ce4b23 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 20 Oct 2022 20:34:15 +0800 Subject: [PATCH 212/488] perf: automation change secret linux --- .../change_secret/host/linux/main.yml | 40 ++++++++++--- .../automations/change_secret/manager.py | 60 +++++++++++++++---- apps/ops/ansible/inventory.py | 7 ++- 3 files changed, 85 insertions(+), 22 deletions(-) diff --git a/apps/assets/automations/change_secret/host/linux/main.yml b/apps/assets/automations/change_secret/host/linux/main.yml index 39c5d0996..cc0e1ae61 100644 --- a/apps/assets/automations/change_secret/host/linux/main.yml +++ b/apps/assets/automations/change_secret/host/linux/main.yml @@ -3,24 +3,36 @@ tasks: - name: Test privileged account ansible.builtin.ping: -# -# - name: print variables -# debug: -# msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ account.secret_type }}" + # + # - name: print variables + # debug: + # msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}" - name: Change password ansible.builtin.user: name: "{{ account.username }}" password: "{{ account.secret | password_hash('sha512') }}" update_password: always - when: account.secret_type == "password" + when: "{{ secret_type == 'password' }}" - - name: Change public key + - name: create user If it already exists, no operation will be performed + ansible.builtin.user: + name: "{{ account.username }}" + when: "{{ secret_type == 'ssh_key' }}" + + - name: remove jumpserver ssh key + ansible.builtin.lineinfile: + dest: "{{ kwargs.dest }}" + regexp: "{{ kwargs.regexp }}" + state: absent + when: "{{ secret_type == 'ssh_key' and kwargs.strategy == 'set_jms' }}" + + - name: Change SSH key ansible.builtin.authorized_key: user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.secret_type == "public_key" + key: "{{ account.secret }}" + exclusive: "{{ kwargs.exclusive }}" + when: "{{ secret_type == 'ssh_key' }}" - name: Refresh connection ansible.builtin.meta: reset_connection @@ -32,3 +44,13 @@ ansible_user: "{{ account.username }}" ansible_password: "{{ account.secret }}" ansible_become: no + when: "{{ secret_type == 'password' }}" + + - name: Verify SSH key + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no + when: "{{ secret_type == 'ssh_key' }}" diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index fcb663ae8..80ca26d72 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -1,14 +1,17 @@ +import os import random import string +from hashlib import md5 from copy import deepcopy +from socket import gethostname from collections import defaultdict from django.utils import timezone -from common.utils import lazyproperty, gen_key_pair +from common.utils import lazyproperty, gen_key_pair, ssh_pubkey_gen, ssh_key_string_to_obj from assets.models import ChangeSecretRecord from assets.const import ( - AutomationTypes, SecretType, SecretStrategy, DEFAULT_PASSWORD_RULES + AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES ) from ..base.manager import BasePlaybookManager @@ -17,15 +20,15 @@ class ChangeSecretManager(BasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.method_hosts_mapper = defaultdict(list) + self.secret_type = self.execution.plan_snapshot.get('secret_type') self.secret_strategy = self.execution.plan_snapshot['secret_strategy'] - self.ssh_key_change_strategy = self.execution.plan_snapshot['ssh_key_change_strategy'] self._password_generated = None self._ssh_key_generated = None self.name_recorder_mapper = {} # 做个映射,方便后面处理 @classmethod def method_type(cls): - return AutomationTypes.change_secret + return AutomationTypes.method_id_meta_mapper @lazyproperty def related_accounts(self): @@ -36,6 +39,19 @@ class ChangeSecretManager(BasePlaybookManager): private_key, public_key = gen_key_pair() return private_key + @staticmethod + def generate_public_key(private_key): + return ssh_pubkey_gen(private_key=private_key, hostname=gethostname()) + + @staticmethod + def generate_private_key_path(secret, path_dir): + key_name = '.' + md5(secret.encode('utf-8')).hexdigest() + key_path = os.path.join(path_dir, key_name) + if not os.path.exists(key_path): + ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path) + os.chmod(key_path, 0o400) + return key_path + def generate_password(self): kwargs = self.automation.plan_snapshot['password_rules'] or {} length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length'])) @@ -77,16 +93,29 @@ class ChangeSecretManager(BasePlaybookManager): else: return self.generate_password() - def get_secret(self, account): - if account.secret_type == SecretType.ssh_key: + def get_secret(self): + if self.secret_type == SecretType.ssh_key: secret = self.get_ssh_key() - elif account.secret_type == SecretType.password: + elif self.secret_type == SecretType.password: secret = self.get_password() else: raise ValueError("Secret must be set") return secret - def host_callback(self, host, asset=None, account=None, automation=None, **kwargs): + def get_kwargs(self, account, secret): + kwargs = {} + if self.secret_type != SecretType.ssh_key: + return kwargs + kwargs['strategy'] = self.automation.plan_snapshot['ssh_key_change_strategy'] + kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no' + + if kwargs['strategy'] == SSHKeyStrategy.set_jms: + kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username) + kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip()) + + return kwargs + + def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs): host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs) if host.get('error'): return host @@ -95,7 +124,9 @@ class ChangeSecretManager(BasePlaybookManager): if account: accounts = accounts.exclude(id=account.id) if '*' not in self.automation.accounts: - accounts = accounts.filter(username__in=self.automation.accounts) + accounts = accounts.filter( + username__in=self.automation.accounts, secret_type=self.secret_type + ) method_attr = getattr(automation, self.method_type() + '_method') method_hosts = self.method_hosts_mapper[method_attr] @@ -103,11 +134,12 @@ class ChangeSecretManager(BasePlaybookManager): inventory_hosts = [] records = [] + host['secret_type'] = self.secret_type for account in accounts: h = deepcopy(host) h['name'] += '_' + account.username + new_secret = self.get_secret() - new_secret = self.get_secret(account) recorder = ChangeSecretRecord( account=account, execution=self.execution, old_secret=account.secret, new_secret=new_secret, @@ -115,11 +147,19 @@ class ChangeSecretManager(BasePlaybookManager): records.append(recorder) self.name_recorder_mapper[h['name']] = recorder + private_key_path = None + if self.secret_type == SecretType.ssh_key: + private_key_path = self.generate_private_key_path(new_secret, path_dir) + new_secret = self.generate_public_key(new_secret) + + h['kwargs'] = self.get_kwargs(account, new_secret) + h['account'] = { 'name': account.name, 'username': account.username, 'secret_type': account.secret_type, 'secret': new_secret, + 'private_key_path': private_key_path } inventory_hosts.append(h) method_hosts.append(h['name']) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index f6c19b7a2..032b81917 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -156,7 +156,7 @@ class JMSInventory: account_selected = accounts[0] if accounts else None return account_selected - def generate(self): + def generate(self, path_dir): hosts = [] platform_assets = self.group_by_platform(self.assets) for platform, assets in platform_assets.items(): @@ -173,7 +173,8 @@ class JMSInventory: if self.host_callback is not None: host = self.host_callback( host, asset=asset, account=account, - platform=platform, automation=automation + platform=platform, automation=automation, + path_dir=path_dir ) if isinstance(host, list): @@ -195,8 +196,8 @@ class JMSInventory: return data def write_to_file(self, path): - data = self.generate() path_dir = os.path.dirname(path) + data = self.generate(path_dir) if not os.path.exists(path_dir): os.makedirs(path_dir, 0o700, True) with open(path, 'w') as f: From c6dfc06003831b38e26a17106926b29ebfe75999 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 20 Oct 2022 20:34:44 +0800 Subject: [PATCH 213/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.po | 438 +++++++++++++------------ apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 468 +++++++++++++-------------- 3 files changed, 472 insertions(+), 438 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 92f434e6e..abb6e56fb 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-19 14:41+0800\n" +"POT-Creation-Date: 2022-10-20 20:21+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,10 +25,10 @@ msgstr "Acls" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:65 assets/models/cmd_filter.py:25 +#: assets/models/base.py:59 assets/models/cmd_filter.py:25 #: assets/models/domain.py:24 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:22 -#: assets/models/platform.py:68 assets/serializers/asset/common.py:85 +#: assets/models/platform.py:68 assets/serializers/asset/common.py:86 #: assets/serializers/platform.py:104 ops/mixin.py:22 ops/models/playbook.py:9 #: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 @@ -58,8 +58,8 @@ msgid "Active" msgstr "アクティブ" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:101 assets/models/automations/base.py:33 -#: assets/models/backup.py:30 assets/models/base.py:70 +#: assets/models/asset/common.py:101 assets/models/automations/base.py:24 +#: assets/models/backup.py:30 assets/models/base.py:66 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 @@ -124,17 +124,17 @@ msgstr "レビュー担当者" msgid "Login acl" msgstr "ログインacl" -#: acls/models/login_asset_acl.py:21 assets/models/account.py:48 +#: acls/models/login_asset_acl.py:21 assets/models/account.py:57 #: authentication/models.py:88 ops/models/base.py:18 #: terminal/models/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "アカウント" -#: acls/models/login_asset_acl.py:22 assets/models/account.py:36 -#: assets/models/asset/common.py:83 assets/models/asset/common.py:219 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:47 +#: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 -#: assets/serializers/account/account.py:57 assets/serializers/label.py:30 +#: assets/serializers/account/account.py:58 assets/serializers/label.py:30 #: audits/models.py:39 authentication/models.py:67 authentication/models.py:84 #: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:32 @@ -159,7 +159,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 -#: assets/models/base.py:66 assets/models/gathered_user.py:15 +#: assets/models/base.py:60 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models.py:248 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -246,7 +246,7 @@ msgid "Category" msgstr "カテゴリ" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:31 assets/models/cmd_filter.py:74 +#: assets/models/automations/base.py:22 assets/models/cmd_filter.py:74 #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 #: assets/serializers/platform.py:75 authentication/models.py:71 #: terminal/models/storage.py:58 terminal/models/storage.py:143 @@ -297,6 +297,117 @@ msgstr "アプリ資産" msgid "{} disabled" msgstr "無効" +#: assets/const/account.py:6 assets/tasks/const.py:51 audits/const.py:5 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 +msgid "Unknown" +msgstr "不明" + +#: assets/const/account.py:7 +msgid "Ok" +msgstr "OK" + +#: assets/const/account.py:8 audits/models.py:118 +#: xpack/plugins/change_auth_plan/serializers/asset.py:190 +#: xpack/plugins/cloud/const.py:33 +msgid "Failed" +msgstr "失敗しました" + +#: assets/const/account.py:12 assets/models/_user.py:35 +#: assets/models/base.py:53 assets/models/domain.py:71 +#: assets/serializers/base.py:15 audits/signal_handlers.py:50 +#: authentication/confirm/password.py:9 authentication/forms.py:32 +#: authentication/templates/authentication/login.html:228 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 +#: users/forms/profile.py:22 users/serializers/user.py:94 +#: users/templates/users/_msg_user_created.html:13 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models/base.py:42 +#: xpack/plugins/change_auth_plan/models/base.py:117 +#: xpack/plugins/change_auth_plan/models/base.py:192 +#: xpack/plugins/change_auth_plan/serializers/base.py:21 +#: xpack/plugins/change_auth_plan/serializers/base.py:73 +#: xpack/plugins/cloud/serializers/account_attrs.py:28 +msgid "Password" +msgstr "パスワード" + +#: assets/const/account.py:13 assets/models/base.py:54 +#, fuzzy +#| msgid "SSH Key" +msgid "SSH key" +msgstr "SSHキー" + +#: assets/const/account.py:14 assets/models/base.py:55 +#: authentication/models.py:38 +msgid "Access key" +msgstr "アクセスキー" + +#: assets/const/account.py:15 assets/models/_user.py:38 +#: assets/models/base.py:56 authentication/models.py:53 +msgid "Token" +msgstr "トークン" + +#: assets/const/automation.py:13 +msgid "Ping" +msgstr "" + +#: assets/const/automation.py:14 +#, fuzzy +#| msgid "Gather account" +msgid "Gather facts" +msgstr "アカウントを集める" + +#: assets/const/automation.py:15 +#, fuzzy +#| msgid "Gather account" +msgid "Create account" +msgstr "アカウントを集める" + +#: assets/const/automation.py:16 +#, fuzzy +#| msgid "Change auth" +msgid "Change secret" +msgstr "秘密を改める" + +#: assets/const/automation.py:17 +#, fuzzy +#| msgid "Verify auth" +msgid "Verify account" +msgstr "パスワード/キーの確認" + +#: assets/const/automation.py:18 rbac/tree.py:53 +msgid "Gather account" +msgstr "アカウントを集める" + +#: assets/const/automation.py:22 +msgid "Specific" +msgstr "" + +#: assets/const/automation.py:23 ops/const.py:20 +#: xpack/plugins/change_auth_plan/models/base.py:28 +msgid "All assets use the same random password" +msgstr "すべての資産は同じランダムパスワードを使用します" + +#: assets/const/automation.py:24 ops/const.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:29 +msgid "All assets use different random password" +msgstr "すべての資産は異なるランダムパスワードを使用します" + +#: assets/const/automation.py:28 ops/const.py:13 +#: xpack/plugins/change_auth_plan/models/asset.py:30 +msgid "Append SSH KEY" +msgstr "追加" + +#: assets/const/automation.py:29 ops/const.py:14 +#: xpack/plugins/change_auth_plan/models/asset.py:31 +msgid "Empty and append SSH KEY" +msgstr "すべてクリアして追加" + +#: assets/const/automation.py:30 ops/const.py:15 +#: xpack/plugins/change_auth_plan/models/asset.py:32 +msgid "Replace (The key generated by JumpServer) " +msgstr "置換(JumpServerによって生成された鍵)" + #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 #: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 #: xpack/plugins/cloud/serializers/account_attrs.py:72 @@ -308,7 +419,7 @@ msgid "Device" msgstr "" #: assets/const/category.py:13 assets/models/asset/database.py:8 -#: assets/models/asset/database.py:18 +#: assets/models/asset/database.py:24 #: xpack/plugins/change_auth_plan/models/app.py:31 msgid "Database" msgstr "データベース" @@ -363,24 +474,6 @@ msgstr "共通ユーザー" msgid "Admin user" msgstr "管理ユーザー" -#: assets/models/_user.py:35 assets/models/base.py:59 -#: assets/models/domain.py:71 assets/serializers/base.py:15 -#: audits/signal_handlers.py:50 authentication/confirm/password.py:9 -#: authentication/forms.py:32 -#: authentication/templates/authentication/login.html:228 -#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:94 -#: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models/base.py:42 -#: xpack/plugins/change_auth_plan/models/base.py:117 -#: xpack/plugins/change_auth_plan/models/base.py:192 -#: xpack/plugins/change_auth_plan/serializers/base.py:21 -#: xpack/plugins/change_auth_plan/serializers/base.py:73 -#: xpack/plugins/cloud/serializers/account_attrs.py:28 -msgid "Password" -msgstr "パスワード" - #: assets/models/_user.py:36 assets/models/domain.py:72 #: assets/serializers/base.py:19 #: xpack/plugins/change_auth_plan/models/asset.py:54 @@ -396,13 +489,8 @@ msgstr "SSH秘密鍵" msgid "SSH public key" msgstr "SSHパブリックキー" -#: assets/models/_user.py:38 assets/models/base.py:62 -#: authentication/models.py:53 -msgid "Token" -msgstr "トークン" - -#: assets/models/_user.py:41 assets/models/automations/base.py:87 -#: assets/models/base.py:71 assets/models/domain.py:26 +#: assets/models/_user.py:41 assets/models/automations/base.py:78 +#: assets/models/base.py:67 assets/models/domain.py:26 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 #: orgs/models.py:72 perms/models/asset_permission.py:82 @@ -410,13 +498,13 @@ msgstr "トークン" msgid "Date created" msgstr "作成された日付" -#: assets/models/_user.py:42 assets/models/base.py:72 +#: assets/models/_user.py:42 assets/models/base.py:68 #: assets/models/gathered_user.py:20 common/db/models.py:77 #: common/mixins/models.py:51 msgid "Date updated" msgstr "更新日" -#: assets/models/_user.py:43 assets/models/base.py:73 +#: assets/models/_user.py:43 assets/models/base.py:69 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 #: orgs/models.py:71 perms/models/asset_permission.py:81 @@ -483,33 +571,33 @@ msgstr "システムユーザー" msgid "Can match system user" msgstr "システムユーザーに一致できます" -#: assets/models/account.py:40 +#: assets/models/account.py:51 #, fuzzy #| msgid "Switch from" msgid "Su from" msgstr "から切り替え" -#: assets/models/account.py:42 settings/serializers/auth/cas.py:18 +#: assets/models/account.py:53 settings/serializers/auth/cas.py:18 msgid "Version" msgstr "バージョン" -#: assets/models/account.py:54 +#: assets/models/account.py:63 msgid "Can view asset account secret" msgstr "資産アカウントの秘密を表示できます" -#: assets/models/account.py:55 +#: assets/models/account.py:64 msgid "Can change asset account secret" msgstr "資産口座の秘密を変更できます" -#: assets/models/account.py:56 +#: assets/models/account.py:65 msgid "Can view asset history account" msgstr "資産履歴アカウントを表示できます" -#: assets/models/account.py:57 +#: assets/models/account.py:66 msgid "Can view asset history account secret" msgstr "資産履歴アカウントパスワードを表示できます" -#: assets/models/account.py:80 assets/serializers/account/account.py:13 +#: assets/models/account.py:89 assets/serializers/account/account.py:13 #, fuzzy #| msgid "Account name" msgid "Account template" @@ -534,14 +622,14 @@ msgstr "プラットフォーム" msgid "Domain" msgstr "ドメイン" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:26 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:17 #: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "ノード" -#: assets/models/asset/common.py:99 assets/models/automations/base.py:32 +#: assets/models/asset/common.py:99 assets/models/automations/base.py:23 #: assets/models/cmd_filter.py:39 assets/models/domain.py:70 #: assets/models/label.py:21 users/serializers/user.py:147 msgid "Is active" @@ -551,83 +639,84 @@ msgstr "アクティブです。" msgid "Labels" msgstr "ラベル" -#: assets/models/asset/common.py:222 +#: assets/models/asset/common.py:230 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" -#: assets/models/asset/common.py:223 +#: assets/models/asset/common.py:231 msgid "Can test asset connectivity" msgstr "資産接続をテストできます" -#: assets/models/asset/common.py:224 +#: assets/models/asset/common.py:232 msgid "Can push system user to asset" msgstr "システムユーザーを資産にプッシュできます" -#: assets/models/asset/common.py:225 +#: assets/models/asset/common.py:233 msgid "Can match asset" msgstr "アセットを一致させることができます" -#: assets/models/asset/common.py:226 +#: assets/models/asset/common.py:234 msgid "Add asset to node" msgstr "ノードにアセットを追加する" -#: assets/models/asset/common.py:227 +#: assets/models/asset/common.py:235 msgid "Move asset to node" msgstr "アセットをノードに移動する" -#: assets/models/automations/base.py:15 -msgid "Ping" +#: assets/models/asset/web.py:9 audits/models.py:111 +msgid "Disabled" +msgstr "無効" + +#: assets/models/asset/web.py:10 +msgid "Basic" msgstr "" -#: assets/models/automations/base.py:16 +#: assets/models/asset/web.py:11 assets/models/asset/web.py:17 +msgid "Script" +msgstr "" + +#: assets/models/asset/web.py:13 #, fuzzy -#| msgid "Gather account" -msgid "Gather facts" -msgstr "アカウントを集める" +#| msgid "Auto" +msgid "Autofill" +msgstr "自動" -#: assets/models/automations/base.py:17 +#: assets/models/asset/web.py:14 assets/serializers/platform.py:29 #, fuzzy -#| msgid "Gather account" -msgid "Create account" -msgstr "アカウントを集める" +#| msgid "Username attr" +msgid "Username selector" +msgstr "ユーザー名のプロパティ" -#: assets/models/automations/base.py:18 -#: assets/models/automations/change_secret.py:56 +#: assets/models/asset/web.py:15 assets/serializers/platform.py:30 #, fuzzy -#| msgid "Change auth" -msgid "Change secret" -msgstr "秘密を改める" +#| msgid "Password rules" +msgid "Password selector" +msgstr "パスワードルール" -#: assets/models/automations/base.py:19 -#, fuzzy -#| msgid "Verify code" -msgid "Verify secret" -msgstr "コードの確認" +#: assets/models/asset/web.py:16 assets/serializers/platform.py:31 +msgid "Submit selector" +msgstr "" -#: assets/models/automations/base.py:20 rbac/tree.py:53 -msgid "Gather account" -msgstr "アカウントを集める" - -#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:38 +#: assets/models/automations/base.py:15 assets/models/cmd_filter.py:38 #: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 #: rbac/tree.py:37 msgid "Accounts" msgstr "アカウント" -#: assets/models/automations/base.py:29 assets/serializers/domain.py:29 +#: assets/models/automations/base.py:20 assets/serializers/domain.py:29 #: ops/models/base.py:17 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "資産" -#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 +#: assets/models/automations/base.py:68 assets/models/automations/base.py:75 #, fuzzy #| msgid "Automatic managed" msgid "Automation task" msgstr "自動管理" -#: assets/models/automations/base.py:88 assets/models/backup.py:77 +#: assets/models/automations/base.py:79 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 #: perms/models/asset_permission.py:76 terminal/models/session.py:43 #: tickets/models/ticket/apply_application.py:28 @@ -638,73 +727,44 @@ msgstr "自動管理" msgid "Date start" msgstr "開始日" -#: assets/models/automations/base.py:89 -#: assets/models/automations/change_secret.py:51 ops/models/base.py:55 +#: assets/models/automations/base.py:80 +#: assets/models/automations/change_secret.py:67 ops/models/base.py:55 msgid "Date finished" msgstr "終了日" -#: assets/models/automations/base.py:91 +#: assets/models/automations/base.py:82 #, fuzzy #| msgid "Relation snapshot" msgid "Automation snapshot" msgstr "製造オーダスナップショット" -#: assets/models/automations/base.py:95 assets/models/backup.py:88 +#: assets/models/automations/base.py:86 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "トリガーモード" -#: assets/models/automations/base.py:99 +#: assets/models/automations/base.py:90 #, fuzzy #| msgid "Command execution" msgid "Automation task execution" msgstr "コマンド実行" -#: assets/models/automations/change_secret.py:13 -msgid "Specific" -msgstr "" - -#: assets/models/automations/change_secret.py:14 ops/const.py:20 -#: xpack/plugins/change_auth_plan/models/base.py:28 -msgid "All assets use the same random password" -msgstr "すべての資産は同じランダムパスワードを使用します" - -#: assets/models/automations/change_secret.py:15 ops/const.py:21 -#: xpack/plugins/change_auth_plan/models/base.py:29 -msgid "All assets use different random password" -msgstr "すべての資産は異なるランダムパスワードを使用します" - -#: assets/models/automations/change_secret.py:19 ops/const.py:13 -#: xpack/plugins/change_auth_plan/models/asset.py:30 -msgid "Append SSH KEY" -msgstr "追加" - -#: assets/models/automations/change_secret.py:20 ops/const.py:14 -#: xpack/plugins/change_auth_plan/models/asset.py:31 -msgid "Empty and append SSH KEY" -msgstr "すべてクリアして追加" - -#: assets/models/automations/change_secret.py:21 ops/const.py:15 -#: xpack/plugins/change_auth_plan/models/asset.py:32 -msgid "Replace (The key generated by JumpServer) " -msgstr "置換(JumpServerによって生成された鍵)" - -#: assets/models/automations/change_secret.py:25 +#: assets/models/automations/change_secret.py:17 assets/models/base.py:62 #, fuzzy #| msgid "Secret key" -msgid "Secret types" +msgid "Secret type" msgstr "秘密キー" -#: assets/models/automations/change_secret.py:27 users/serializers/user.py:81 -#: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:27 -msgid "Password strategy" -msgstr "パスワード戦略" +#: assets/models/automations/change_secret.py:21 +#, fuzzy +#| msgid "SSH Key strategy" +msgid "Secret strategy" +msgstr "SSHキー戦略" -#: assets/models/automations/change_secret.py:28 -#: assets/models/automations/change_secret.py:49 assets/models/base.py:68 +#: assets/models/automations/change_secret.py:23 +#: assets/models/automations/change_secret.py:65 assets/models/base.py:64 #: assets/serializers/account/base.py:17 authentication/models.py:73 #: authentication/models.py:249 #: authentication/templates/authentication/_access_key_modal.html:31 @@ -712,24 +772,18 @@ msgstr "パスワード戦略" msgid "Secret" msgstr "ひみつ" -#: assets/models/automations/change_secret.py:29 +#: assets/models/automations/change_secret.py:24 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" msgstr "パスワードルール" -#: assets/models/automations/change_secret.py:32 assets/models/base.py:60 -#, fuzzy -#| msgid "SSH Key" -msgid "SSH key" -msgstr "SSHキー" - -#: assets/models/automations/change_secret.py:34 +#: assets/models/automations/change_secret.py:27 #, fuzzy #| msgid "SSH Key strategy" -msgid "SSH key strategy" +msgid "SSH key change strategy" msgstr "SSHキー戦略" -#: assets/models/automations/change_secret.py:35 assets/models/backup.py:28 +#: assets/models/automations/change_secret.py:29 assets/models/backup.py:28 #: assets/serializers/account/backup.py:28 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 @@ -737,30 +791,36 @@ msgstr "SSHキー戦略" msgid "Recipient" msgstr "受信者" -#: assets/models/automations/change_secret.py:42 +#: assets/models/automations/change_secret.py:36 #, fuzzy #| msgid "Change auth" msgid "Change secret automation" msgstr "秘密を改める" -#: assets/models/automations/change_secret.py:48 +#: assets/models/automations/change_secret.py:64 #, fuzzy #| msgid "Secret" msgid "Old secret" msgstr "ひみつ" -#: assets/models/automations/change_secret.py:50 +#: assets/models/automations/change_secret.py:66 #, fuzzy #| msgid "Date start" msgid "Date started" msgstr "開始日" -#: assets/models/automations/change_secret.py:53 +#: assets/models/automations/change_secret.py:69 #, fuzzy #| msgid "WeCom Error" msgid "Error" msgstr "企業微信エラー" +#: assets/models/automations/change_secret.py:72 +#, fuzzy +#| msgid "Change auth" +msgid "Change secret record" +msgstr "秘密を改める" + #: assets/models/automations/discovery_account.py:8 #, fuzzy #| msgid "Verify auth" @@ -780,8 +840,10 @@ msgid "Push automation" msgstr "自動管理" #: assets/models/automations/verify_secret.py:8 -msgid "Verify secret automation" -msgstr "" +#, fuzzy +#| msgid "Verify auth" +msgid "Verify account automation" +msgstr "パスワード/キーの確認" #: assets/models/backup.py:38 assets/models/backup.py:96 msgid "Account backup plan" @@ -818,41 +880,15 @@ msgstr "成功は" msgid "Account backup execution" msgstr "アカウントバックアップの実行" -#: assets/models/base.py:28 assets/tasks/const.py:51 audits/const.py:5 -#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 -#: common/utils/ip/utils.py:84 -msgid "Unknown" -msgstr "不明" - -#: assets/models/base.py:29 -msgid "Ok" -msgstr "OK" - -#: assets/models/base.py:30 audits/models.py:118 -#: xpack/plugins/change_auth_plan/serializers/asset.py:190 -#: xpack/plugins/cloud/const.py:33 -msgid "Failed" -msgstr "失敗しました" - -#: assets/models/base.py:36 assets/serializers/domain.py:42 +#: assets/models/base.py:30 assets/serializers/domain.py:42 msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:38 authentication/models.py:251 +#: assets/models/base.py:32 authentication/models.py:251 msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:61 authentication/models.py:38 -msgid "Access key" -msgstr "アクセスキー" - -#: assets/models/base.py:67 -#, fuzzy -#| msgid "Secret key" -msgid "Secret type" -msgstr "秘密キー" - -#: assets/models/base.py:69 +#: assets/models/base.py:65 msgid "Privileged" msgstr "" @@ -1157,7 +1193,13 @@ msgstr "" msgid "Push now" msgstr "" -#: assets/serializers/account/account.py:24 +#: assets/serializers/account/account.py:18 +#, fuzzy +#| msgid "Secret" +msgid "Has secret" +msgstr "ひみつ" + +#: assets/serializers/account/account.py:25 msgid "Account template not found" msgstr "" @@ -1191,17 +1233,17 @@ msgstr "このフィールドは必須です。" msgid "Protocols" msgstr "プロトコル" -#: assets/serializers/asset/common.py:86 +#: assets/serializers/asset/common.py:87 msgid "Address" msgstr "アドレス" -#: assets/serializers/asset/common.py:129 +#: assets/serializers/asset/common.py:136 #, fuzzy #| msgid "Application not exists" msgid "Platform not exist" msgstr "アプリが存在しません" -#: assets/serializers/asset/common.py:145 +#: assets/serializers/asset/common.py:152 #, fuzzy #| msgid "Protocol duplicate: {}" msgid "Protocol is required: {}" @@ -1267,6 +1309,12 @@ msgstr "ホスト名生" msgid "Asset number" msgstr "資産番号" +#: assets/serializers/asset/host.py:40 +#, fuzzy +#| msgid "Host" +msgid "IP/Host" +msgstr "ホスト" + #: assets/serializers/base.py:24 msgid "Key password" msgstr "キーパスワード" @@ -1330,22 +1378,6 @@ msgstr "SFTPルート" msgid "Auto fill" msgstr "自動" -#: assets/serializers/platform.py:29 -#, fuzzy -#| msgid "Username attr" -msgid "Username selector" -msgstr "ユーザー名のプロパティ" - -#: assets/serializers/platform.py:30 -#, fuzzy -#| msgid "Password rules" -msgid "Password selector" -msgstr "パスワードルール" - -#: assets/serializers/platform.py:31 -msgid "Submit selector" -msgstr "" - #: assets/serializers/platform.py:64 msgid "Primary" msgstr "" @@ -1536,10 +1568,6 @@ msgstr "による変更" msgid "Password change log" msgstr "パスワード変更ログ" -#: audits/models.py:111 -msgid "Disabled" -msgstr "無効" - #: audits/models.py:113 msgid "-" msgstr "-" @@ -5812,6 +5840,12 @@ msgstr "システムロール表示" msgid "Org roles display" msgstr "組織ロール表示" +#: users/serializers/user.py:81 +#: xpack/plugins/change_auth_plan/models/base.py:35 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 +msgid "Password strategy" +msgstr "パスワード戦略" + #: users/serializers/user.py:83 msgid "MFA enabled" msgstr "MFA有効化" @@ -6851,9 +6885,14 @@ msgid "Community edition" msgstr "コミュニティ版" #, fuzzy -#~| msgid "Verify auth" -#~ msgid "Verify account" -#~ msgstr "パスワード/キーの確認" +#~| msgid "Verify code" +#~ msgid "Verify secret" +#~ msgstr "コードの確認" + +#, fuzzy +#~| msgid "Secret key" +#~ msgid "Secret types" +#~ msgstr "秘密キー" #, fuzzy #~| msgid "Gather account" @@ -6875,11 +6914,6 @@ msgstr "コミュニティ版" #~ msgid "Reconcile strategy" #~ msgstr "ホスト名戦略" -#, fuzzy -#~| msgid "SSH Key strategy" -#~ msgid "Verify strategy" -#~ msgstr "SSHキー戦略" - #, fuzzy #~| msgid "Can change auth setting" #~ msgid "Change auth strategy" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 5cf5d1d57..2c51cf456 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:7e313c2d3223de35efb1e9449ab3f6ef33a9d1d565b41ff97b8601c50cf4f70b -size 101988 +oid sha256:ab1c609cc4c83a223835be0eab2a5a5b9050c853b66ccd2b2fa480073c8fc763 +size 103346 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 0f77ac7d1..849577c24 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-10-19 14:41+0800\n" +"POT-Creation-Date: 2022-10-20 20:21+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -24,10 +24,10 @@ msgstr "访问控制" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:65 assets/models/cmd_filter.py:25 +#: assets/models/base.py:59 assets/models/cmd_filter.py:25 #: assets/models/domain.py:24 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:22 -#: assets/models/platform.py:68 assets/serializers/asset/common.py:85 +#: assets/models/platform.py:68 assets/serializers/asset/common.py:86 #: assets/serializers/platform.py:104 ops/mixin.py:22 ops/models/playbook.py:9 #: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 @@ -57,8 +57,8 @@ msgid "Active" msgstr "激活中" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:101 assets/models/automations/base.py:33 -#: assets/models/backup.py:30 assets/models/base.py:70 +#: assets/models/asset/common.py:101 assets/models/automations/base.py:24 +#: assets/models/backup.py:30 assets/models/base.py:66 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 @@ -123,17 +123,17 @@ msgstr "审批人" msgid "Login acl" msgstr "登录访问控制" -#: acls/models/login_asset_acl.py:21 assets/models/account.py:48 +#: acls/models/login_asset_acl.py:21 assets/models/account.py:57 #: authentication/models.py:88 ops/models/base.py:18 #: terminal/models/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "账号" -#: acls/models/login_asset_acl.py:22 assets/models/account.py:36 -#: assets/models/asset/common.py:83 assets/models/asset/common.py:219 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:47 +#: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 -#: assets/serializers/account/account.py:57 assets/serializers/label.py:30 +#: assets/serializers/account/account.py:58 assets/serializers/label.py:30 #: audits/models.py:39 authentication/models.py:67 authentication/models.py:84 #: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:32 @@ -158,7 +158,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 -#: assets/models/base.py:66 assets/models/gathered_user.py:15 +#: assets/models/base.py:60 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models.py:248 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -241,7 +241,7 @@ msgid "Category" msgstr "类别" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:31 assets/models/cmd_filter.py:74 +#: assets/models/automations/base.py:22 assets/models/cmd_filter.py:74 #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 #: assets/serializers/platform.py:75 authentication/models.py:71 #: terminal/models/storage.py:58 terminal/models/storage.py:143 @@ -290,6 +290,107 @@ msgstr "资产管理" msgid "{} disabled" msgstr "{} 已禁用" +#: assets/const/account.py:6 assets/tasks/const.py:51 audits/const.py:5 +#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 +#: common/utils/ip/utils.py:84 +msgid "Unknown" +msgstr "未知" + +#: assets/const/account.py:7 +msgid "Ok" +msgstr "成功" + +#: assets/const/account.py:8 audits/models.py:118 +#: xpack/plugins/change_auth_plan/serializers/asset.py:190 +#: xpack/plugins/cloud/const.py:33 +msgid "Failed" +msgstr "失败" + +#: assets/const/account.py:12 assets/models/_user.py:35 +#: assets/models/base.py:53 assets/models/domain.py:71 +#: assets/serializers/base.py:15 audits/signal_handlers.py:50 +#: authentication/confirm/password.py:9 authentication/forms.py:32 +#: authentication/templates/authentication/login.html:228 +#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 +#: users/forms/profile.py:22 users/serializers/user.py:94 +#: users/templates/users/_msg_user_created.html:13 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models/base.py:42 +#: xpack/plugins/change_auth_plan/models/base.py:117 +#: xpack/plugins/change_auth_plan/models/base.py:192 +#: xpack/plugins/change_auth_plan/serializers/base.py:21 +#: xpack/plugins/change_auth_plan/serializers/base.py:73 +#: xpack/plugins/cloud/serializers/account_attrs.py:28 +msgid "Password" +msgstr "密码" + +#: assets/const/account.py:13 assets/models/base.py:54 +msgid "SSH key" +msgstr "SSH 密钥" + +#: assets/const/account.py:14 assets/models/base.py:55 +#: authentication/models.py:38 +msgid "Access key" +msgstr "Access key" + +#: assets/const/account.py:15 assets/models/_user.py:38 +#: assets/models/base.py:56 authentication/models.py:53 +msgid "Token" +msgstr "Token" + +#: assets/const/automation.py:13 +msgid "Ping" +msgstr "" + +#: assets/const/automation.py:14 +msgid "Gather facts" +msgstr "收集信息" + +#: assets/const/automation.py:15 +msgid "Create account" +msgstr "收集账号" + +#: assets/const/automation.py:16 +msgid "Change secret" +msgstr "改密" + +#: assets/const/automation.py:17 +msgid "Verify account" +msgstr "验证密钥" + +#: assets/const/automation.py:18 rbac/tree.py:53 +msgid "Gather account" +msgstr "收集账号" + +#: assets/const/automation.py:22 +msgid "Specific" +msgstr "指定" + +#: assets/const/automation.py:23 ops/const.py:20 +#: xpack/plugins/change_auth_plan/models/base.py:28 +msgid "All assets use the same random password" +msgstr "使用相同的随机密码" + +#: assets/const/automation.py:24 ops/const.py:21 +#: xpack/plugins/change_auth_plan/models/base.py:29 +msgid "All assets use different random password" +msgstr "使用不同的随机密码" + +#: assets/const/automation.py:28 ops/const.py:13 +#: xpack/plugins/change_auth_plan/models/asset.py:30 +msgid "Append SSH KEY" +msgstr "追加" + +#: assets/const/automation.py:29 ops/const.py:14 +#: xpack/plugins/change_auth_plan/models/asset.py:31 +msgid "Empty and append SSH KEY" +msgstr "清空所有并添加" + +#: assets/const/automation.py:30 ops/const.py:15 +#: xpack/plugins/change_auth_plan/models/asset.py:32 +msgid "Replace (The key generated by JumpServer) " +msgstr "替换 (由 JumpServer 生成的密钥)" + #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 #: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 #: xpack/plugins/cloud/serializers/account_attrs.py:72 @@ -298,10 +399,10 @@ msgstr "主机" #: assets/const/category.py:12 msgid "Device" -msgstr "" +msgstr "网络设备" #: assets/const/category.py:13 assets/models/asset/database.py:8 -#: assets/models/asset/database.py:18 +#: assets/models/asset/database.py:24 #: xpack/plugins/change_auth_plan/models/app.py:31 msgid "Database" msgstr "数据库" @@ -316,7 +417,7 @@ msgstr "Web" #: assets/const/device.py:7 tickets/const.py:8 msgid "General" -msgstr "一般" +msgstr "通用" #: assets/const/device.py:8 msgid "Switch" @@ -350,24 +451,6 @@ msgstr "普通用户" msgid "Admin user" msgstr "特权用户" -#: assets/models/_user.py:35 assets/models/base.py:59 -#: assets/models/domain.py:71 assets/serializers/base.py:15 -#: audits/signal_handlers.py:50 authentication/confirm/password.py:9 -#: authentication/forms.py:32 -#: authentication/templates/authentication/login.html:228 -#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:94 -#: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models/base.py:42 -#: xpack/plugins/change_auth_plan/models/base.py:117 -#: xpack/plugins/change_auth_plan/models/base.py:192 -#: xpack/plugins/change_auth_plan/serializers/base.py:21 -#: xpack/plugins/change_auth_plan/serializers/base.py:73 -#: xpack/plugins/cloud/serializers/account_attrs.py:28 -msgid "Password" -msgstr "密码" - #: assets/models/_user.py:36 assets/models/domain.py:72 #: assets/serializers/base.py:19 #: xpack/plugins/change_auth_plan/models/asset.py:54 @@ -383,13 +466,8 @@ msgstr "SSH 密钥" msgid "SSH public key" msgstr "SSH 公钥" -#: assets/models/_user.py:38 assets/models/base.py:62 -#: authentication/models.py:53 -msgid "Token" -msgstr "Token" - -#: assets/models/_user.py:41 assets/models/automations/base.py:87 -#: assets/models/base.py:71 assets/models/domain.py:26 +#: assets/models/_user.py:41 assets/models/automations/base.py:78 +#: assets/models/base.py:67 assets/models/domain.py:26 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 #: orgs/models.py:72 perms/models/asset_permission.py:82 @@ -397,13 +475,13 @@ msgstr "Token" msgid "Date created" msgstr "创建日期" -#: assets/models/_user.py:42 assets/models/base.py:72 +#: assets/models/_user.py:42 assets/models/base.py:68 #: assets/models/gathered_user.py:20 common/db/models.py:77 #: common/mixins/models.py:51 msgid "Date updated" msgstr "更新日期" -#: assets/models/_user.py:43 assets/models/base.py:73 +#: assets/models/_user.py:43 assets/models/base.py:69 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 #: orgs/models.py:71 perms/models/asset_permission.py:81 @@ -470,31 +548,31 @@ msgstr "系统用户" msgid "Can match system user" msgstr "可以匹配系统用户" -#: assets/models/account.py:40 +#: assets/models/account.py:51 msgid "Su from" msgstr "切换自" -#: assets/models/account.py:42 settings/serializers/auth/cas.py:18 +#: assets/models/account.py:53 settings/serializers/auth/cas.py:18 msgid "Version" msgstr "版本" -#: assets/models/account.py:54 +#: assets/models/account.py:63 msgid "Can view asset account secret" msgstr "可以查看资产账号密码" -#: assets/models/account.py:55 +#: assets/models/account.py:64 msgid "Can change asset account secret" msgstr "可以更改资产账号密码" -#: assets/models/account.py:56 +#: assets/models/account.py:65 msgid "Can view asset history account" msgstr "可以查看资产历史账号" -#: assets/models/account.py:57 +#: assets/models/account.py:66 msgid "Can view asset history account secret" msgstr "可以查看资产历史账号密码" -#: assets/models/account.py:80 assets/serializers/account/account.py:13 +#: assets/models/account.py:89 assets/serializers/account/account.py:13 msgid "Account template" msgstr "账号模版" @@ -517,14 +595,14 @@ msgstr "资产平台" msgid "Domain" msgstr "网域" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:26 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:17 #: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "节点" -#: assets/models/asset/common.py:99 assets/models/automations/base.py:32 +#: assets/models/asset/common.py:99 assets/models/automations/base.py:23 #: assets/models/cmd_filter.py:39 assets/models/domain.py:70 #: assets/models/label.py:21 users/serializers/user.py:147 msgid "Is active" @@ -534,77 +612,78 @@ msgstr "激活" msgid "Labels" msgstr "标签管理" -#: assets/models/asset/common.py:222 +#: assets/models/asset/common.py:230 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" -#: assets/models/asset/common.py:223 +#: assets/models/asset/common.py:231 msgid "Can test asset connectivity" msgstr "可以测试资产连接性" -#: assets/models/asset/common.py:224 +#: assets/models/asset/common.py:232 msgid "Can push system user to asset" msgstr "可以推送系统用户到资产" -#: assets/models/asset/common.py:225 +#: assets/models/asset/common.py:233 msgid "Can match asset" msgstr "可以匹配资产" -#: assets/models/asset/common.py:226 +#: assets/models/asset/common.py:234 msgid "Add asset to node" msgstr "添加资产到节点" -#: assets/models/asset/common.py:227 +#: assets/models/asset/common.py:235 msgid "Move asset to node" msgstr "移动资产到节点" -#: assets/models/automations/base.py:15 -msgid "Ping" +#: assets/models/asset/web.py:9 audits/models.py:111 +msgid "Disabled" +msgstr "禁用" + +#: assets/models/asset/web.py:10 +msgid "Basic" msgstr "" -#: assets/models/automations/base.py:16 -msgid "Gather facts" -msgstr "收集信息" +#: assets/models/asset/web.py:11 assets/models/asset/web.py:17 +msgid "Script" +msgstr "" -#: assets/models/automations/base.py:17 -msgid "Create account" -msgstr "收集账号" - -#: assets/models/automations/base.py:18 -#: assets/models/automations/change_secret.py:56 +#: assets/models/asset/web.py:13 #, fuzzy -#| msgid "Change auth" -msgid "Change secret" -msgstr "执行改密" +#| msgid "Auto fill" +msgid "Autofill" +msgstr "自动填充" -#: assets/models/automations/base.py:19 -#, fuzzy -#| msgid "Verify code" -msgid "Verify secret" -msgstr "验证码" +#: assets/models/asset/web.py:14 assets/serializers/platform.py:29 +msgid "Username selector" +msgstr "用户名选择器" -#: assets/models/automations/base.py:20 rbac/tree.py:53 -msgid "Gather account" -msgstr "收集账号" +#: assets/models/asset/web.py:15 assets/serializers/platform.py:30 +msgid "Password selector" +msgstr "密码选择器" -#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:38 +#: assets/models/asset/web.py:16 assets/serializers/platform.py:31 +msgid "Submit selector" +msgstr "提交按钮选择器" + +#: assets/models/automations/base.py:15 assets/models/cmd_filter.py:38 #: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 #: rbac/tree.py:37 msgid "Accounts" msgstr "账号管理" -#: assets/models/automations/base.py:29 assets/serializers/domain.py:29 +#: assets/models/automations/base.py:20 assets/serializers/domain.py:29 #: ops/models/base.py:17 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" -#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 +#: assets/models/automations/base.py:68 assets/models/automations/base.py:75 msgid "Automation task" msgstr "自动化任务" -#: assets/models/automations/base.py:88 assets/models/backup.py:77 +#: assets/models/automations/base.py:79 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 #: perms/models/asset_permission.py:76 terminal/models/session.py:43 #: tickets/models/ticket/apply_application.py:28 @@ -615,71 +694,36 @@ msgstr "自动化任务" msgid "Date start" msgstr "开始日期" -#: assets/models/automations/base.py:89 -#: assets/models/automations/change_secret.py:51 ops/models/base.py:55 +#: assets/models/automations/base.py:80 +#: assets/models/automations/change_secret.py:67 ops/models/base.py:55 msgid "Date finished" msgstr "结束日期" -#: assets/models/automations/base.py:91 -#, fuzzy -#| msgid "Relation snapshot" +#: assets/models/automations/base.py:82 msgid "Automation snapshot" -msgstr "工单快照" +msgstr "自动化快照" -#: assets/models/automations/base.py:95 assets/models/backup.py:88 +#: assets/models/automations/base.py:86 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "触发模式" -#: assets/models/automations/base.py:99 +#: assets/models/automations/base.py:90 msgid "Automation task execution" msgstr "自动化任务执行" -#: assets/models/automations/change_secret.py:13 -msgid "Specific" -msgstr "指定" +#: assets/models/automations/change_secret.py:17 assets/models/base.py:62 +msgid "Secret type" +msgstr "密文类型" -#: assets/models/automations/change_secret.py:14 ops/const.py:20 -#: xpack/plugins/change_auth_plan/models/base.py:28 -msgid "All assets use the same random password" -msgstr "使用相同的随机密码" +#: assets/models/automations/change_secret.py:21 +msgid "Secret strategy" +msgstr "密钥策略" -#: assets/models/automations/change_secret.py:15 ops/const.py:21 -#: xpack/plugins/change_auth_plan/models/base.py:29 -msgid "All assets use different random password" -msgstr "使用不同的随机密码" - -#: assets/models/automations/change_secret.py:19 ops/const.py:13 -#: xpack/plugins/change_auth_plan/models/asset.py:30 -msgid "Append SSH KEY" -msgstr "追加" - -#: assets/models/automations/change_secret.py:20 ops/const.py:14 -#: xpack/plugins/change_auth_plan/models/asset.py:31 -msgid "Empty and append SSH KEY" -msgstr "清空所有并添加" - -#: assets/models/automations/change_secret.py:21 ops/const.py:15 -#: xpack/plugins/change_auth_plan/models/asset.py:32 -msgid "Replace (The key generated by JumpServer) " -msgstr "替换 (由 JumpServer 生成的密钥)" - -#: assets/models/automations/change_secret.py:25 -#, fuzzy -#| msgid "Secret key" -msgid "Secret types" -msgstr "Secret key" - -#: assets/models/automations/change_secret.py:27 users/serializers/user.py:81 -#: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:27 -msgid "Password strategy" -msgstr "密码策略" - -#: assets/models/automations/change_secret.py:28 -#: assets/models/automations/change_secret.py:49 assets/models/base.py:68 +#: assets/models/automations/change_secret.py:23 +#: assets/models/automations/change_secret.py:65 assets/models/base.py:64 #: assets/serializers/account/base.py:17 authentication/models.py:73 #: authentication/models.py:249 #: authentication/templates/authentication/_access_key_modal.html:31 @@ -687,20 +731,16 @@ msgstr "密码策略" msgid "Secret" msgstr "密钥" -#: assets/models/automations/change_secret.py:29 +#: assets/models/automations/change_secret.py:24 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" msgstr "密码规则" -#: assets/models/automations/change_secret.py:32 assets/models/base.py:60 -msgid "SSH key" -msgstr "SSH 密钥" - -#: assets/models/automations/change_secret.py:34 -msgid "SSH key strategy" +#: assets/models/automations/change_secret.py:27 +msgid "SSH key change strategy" msgstr "SSH 密钥策略" -#: assets/models/automations/change_secret.py:35 assets/models/backup.py:28 +#: assets/models/automations/change_secret.py:29 assets/models/backup.py:28 #: assets/serializers/account/backup.py:28 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 @@ -708,22 +748,26 @@ msgstr "SSH 密钥策略" msgid "Recipient" msgstr "收件人" -#: assets/models/automations/change_secret.py:42 +#: assets/models/automations/change_secret.py:36 msgid "Change secret automation" msgstr "自动化改密" -#: assets/models/automations/change_secret.py:48 +#: assets/models/automations/change_secret.py:64 msgid "Old secret" msgstr "原来密码" -#: assets/models/automations/change_secret.py:50 +#: assets/models/automations/change_secret.py:66 msgid "Date started" msgstr "开始日期" -#: assets/models/automations/change_secret.py:53 +#: assets/models/automations/change_secret.py:69 msgid "Error" msgstr "错误" +#: assets/models/automations/change_secret.py:72 +msgid "Change secret record" +msgstr "改密记录" + #: assets/models/automations/discovery_account.py:8 msgid "Discovery account automation" msgstr "自动化账号发现" @@ -737,8 +781,8 @@ msgid "Push automation" msgstr "自动化推送" #: assets/models/automations/verify_secret.py:8 -msgid "Verify secret automation" -msgstr "自动化验证" +msgid "Verify account automation" +msgstr "账号校验自动化" #: assets/models/backup.py:38 assets/models/backup.py:96 msgid "Account backup plan" @@ -775,39 +819,15 @@ msgstr "是否成功" msgid "Account backup execution" msgstr "账号备份执行" -#: assets/models/base.py:28 assets/tasks/const.py:51 audits/const.py:5 -#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 -#: common/utils/ip/utils.py:84 -msgid "Unknown" -msgstr "未知" - -#: assets/models/base.py:29 -msgid "Ok" -msgstr "成功" - -#: assets/models/base.py:30 audits/models.py:118 -#: xpack/plugins/change_auth_plan/serializers/asset.py:190 -#: xpack/plugins/cloud/const.py:33 -msgid "Failed" -msgstr "失败" - -#: assets/models/base.py:36 assets/serializers/domain.py:42 +#: assets/models/base.py:30 assets/serializers/domain.py:42 msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:38 authentication/models.py:251 +#: assets/models/base.py:32 authentication/models.py:251 msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:61 authentication/models.py:38 -msgid "Access key" -msgstr "Access key" - -#: assets/models/base.py:67 -msgid "Secret type" -msgstr "密文类型" - -#: assets/models/base.py:69 +#: assets/models/base.py:65 msgid "Privileged" msgstr "特权的" @@ -877,7 +897,7 @@ msgid "Test gateway" msgstr "测试网关" #: assets/models/domain.py:142 -#, fuzzy, python-brace-format +#, python-brace-format msgid "Unable to connect to port {port} on {address}" msgstr "无法连接到 {address} 上的端口 {port}" @@ -1033,32 +1053,24 @@ msgid "Charset" msgstr "编码" #: assets/models/platform.py:76 -#, fuzzy -#| msgid "Domain name" msgid "Domain enabled" -msgstr "网域名称" +msgstr "支持网域" #: assets/models/platform.py:77 -#, fuzzy -#| msgid "Protocols" msgid "Protocols enabled" -msgstr "协议组" +msgstr "协议支持" #: assets/models/platform.py:79 -#, fuzzy -#| msgid "MFA enabled" msgid "Su enabled" -msgstr "MFA 已启用" +msgstr "账号切换" #: assets/models/platform.py:80 msgid "SU method" -msgstr "" +msgstr "切换方式" #: assets/models/platform.py:82 assets/serializers/platform.py:78 -#, fuzzy -#| msgid "Automatic managed" msgid "Automation" -msgstr "托管密码" +msgstr "自动化" #: assets/models/utils.py:19 #, python-format @@ -1088,9 +1100,13 @@ msgstr "" msgid "Push now" msgstr "立刻推送" -#: assets/serializers/account/account.py:24 +#: assets/serializers/account/account.py:18 +msgid "Has secret" +msgstr "有密码" + +#: assets/serializers/account/account.py:25 msgid "Account template not found" -msgstr "" +msgstr "账号模版没有发现" #: assets/serializers/account/backup.py:27 ops/mixin.py:104 #: settings/serializers/auth/ldap.py:65 @@ -1122,21 +1138,17 @@ msgstr "该字段是必填项。" msgid "Protocols" msgstr "协议组" -#: assets/serializers/asset/common.py:86 +#: assets/serializers/asset/common.py:87 msgid "Address" msgstr "地址" -#: assets/serializers/asset/common.py:129 -#, fuzzy -#| msgid "Application not exists" +#: assets/serializers/asset/common.py:136 msgid "Platform not exist" -msgstr "应用不存在" +msgstr "平台不存在" -#: assets/serializers/asset/common.py:145 -#, fuzzy -#| msgid "Protocol duplicate: {}" +#: assets/serializers/asset/common.py:152 msgid "Protocol is required: {}" -msgstr "协议重复: {}" +msgstr "协议是必须的: {}" #: assets/serializers/asset/host.py:12 msgid "Vendor" @@ -1198,17 +1210,19 @@ msgstr "主机名原始" msgid "Asset number" msgstr "资产编号" +#: assets/serializers/asset/host.py:40 +msgid "IP/Host" +msgstr "IP/主机名" + #: assets/serializers/base.py:24 msgid "Key password" msgstr "密钥密码" #: assets/serializers/cagegory.py:9 msgid "Constraints" -msgstr "" +msgstr "约束" #: assets/serializers/cagegory.py:15 -#, fuzzy -#| msgid "Type" msgid "Types" msgstr "类型" @@ -1244,33 +1258,17 @@ msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" #: assets/serializers/platform.py:24 -#, fuzzy -#| msgid "MFA enabled" msgid "SFTP enabled" -msgstr "MFA 已启用" +msgstr "SFTP 启用" #: assets/serializers/platform.py:25 -#, fuzzy -#| msgid "SFTP Root" msgid "SFTP home" -msgstr "SFTP根路径" +msgstr "SFTP 根路径" #: assets/serializers/platform.py:28 msgid "Auto fill" msgstr "自动填充" -#: assets/serializers/platform.py:29 -msgid "Username selector" -msgstr "用户名选择器" - -#: assets/serializers/platform.py:30 -msgid "Password selector" -msgstr "密码选择器" - -#: assets/serializers/platform.py:31 -msgid "Submit selector" -msgstr "提交按钮选择器" - #: assets/serializers/platform.py:64 msgid "Primary" msgstr "主要的" @@ -1457,10 +1455,6 @@ msgstr "修改者" msgid "Password change log" msgstr "改密日志" -#: audits/models.py:111 -msgid "Disabled" -msgstr "禁用" - #: audits/models.py:113 msgid "-" msgstr "-" @@ -1786,10 +1780,6 @@ msgid "" msgstr "账号已被锁定(请联系管理员解锁或{}分钟后重试)" #: authentication/errors/const.py:51 -#, fuzzy -#| msgid "" -#| "The ip has been locked (please contact admin to unlock it or try again " -#| "after {} minutes)" msgid "" "The address has been locked (please contact admin to unlock it or try again " "after {} minutes)" @@ -2478,10 +2468,9 @@ msgid "Object" msgstr "对象" #: common/drf/fields.py:70 -#, fuzzy, python-brace-format -#| msgid "%s object does not exist." +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "%s对象不存在" +msgstr "{pk_value} 对象不存在" #: common/drf/fields.py:71 #, python-brace-format @@ -5637,6 +5626,12 @@ msgstr "系统角色显示" msgid "Org roles display" msgstr "组织角色显示" +#: users/serializers/user.py:81 +#: xpack/plugins/change_auth_plan/models/base.py:35 +#: xpack/plugins/change_auth_plan/serializers/base.py:27 +msgid "Password strategy" +msgstr "密码策略" + #: users/serializers/user.py:83 msgid "MFA enabled" msgstr "MFA 已启用" @@ -6659,8 +6654,16 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" -#~ msgid "Verify account" -#~ msgstr "验证密钥" +#~ msgid "Verify secret" +#~ msgstr "校验密码" + +#, fuzzy +#~| msgid "Secret key" +#~ msgid "Secret types" +#~ msgstr "Secret key" + +#~ msgid "Verify secret automation" +#~ msgstr "自动化验证" #~ msgid "Gather accounts" #~ msgstr "收集账号" @@ -6676,9 +6679,6 @@ msgstr "社区版" #~ msgid "Reconcile strategy" #~ msgstr "主机名策略" -#~ msgid "Verify strategy" -#~ msgstr "SSH 密钥策略" - #, fuzzy #~| msgid "Can change auth setting" #~ msgid "Change auth strategy" From 091bffa6263a025197e03c0ce8cde79df0867a6d Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 20 Oct 2022 20:34:15 +0800 Subject: [PATCH 214/488] perf: automation change secret linux --- apps/assets/automations/base/manager.py | 2 +- .../change_secret/host/linux/main.yml | 40 ++++++++++--- .../change_secret/host/windows/main.yml | 5 +- .../automations/change_secret/manager.py | 60 +++++++++++++++---- apps/ops/ansible/inventory.py | 23 ++++--- 5 files changed, 96 insertions(+), 34 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index ad8104127..d093d22eb 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -77,9 +77,9 @@ class BasePlaybookManager: def generate_inventory(self, platformed_assets, inventory_path): inventory = JMSInventory( + manager=self, assets=platformed_assets, account_policy=self.ansible_account_policy, - host_callback=self.host_callback ) inventory.write_to_file(inventory_path) diff --git a/apps/assets/automations/change_secret/host/linux/main.yml b/apps/assets/automations/change_secret/host/linux/main.yml index 39c5d0996..cc0e1ae61 100644 --- a/apps/assets/automations/change_secret/host/linux/main.yml +++ b/apps/assets/automations/change_secret/host/linux/main.yml @@ -3,24 +3,36 @@ tasks: - name: Test privileged account ansible.builtin.ping: -# -# - name: print variables -# debug: -# msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ account.secret_type }}" + # + # - name: print variables + # debug: + # msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}" - name: Change password ansible.builtin.user: name: "{{ account.username }}" password: "{{ account.secret | password_hash('sha512') }}" update_password: always - when: account.secret_type == "password" + when: "{{ secret_type == 'password' }}" - - name: Change public key + - name: create user If it already exists, no operation will be performed + ansible.builtin.user: + name: "{{ account.username }}" + when: "{{ secret_type == 'ssh_key' }}" + + - name: remove jumpserver ssh key + ansible.builtin.lineinfile: + dest: "{{ kwargs.dest }}" + regexp: "{{ kwargs.regexp }}" + state: absent + when: "{{ secret_type == 'ssh_key' and kwargs.strategy == 'set_jms' }}" + + - name: Change SSH key ansible.builtin.authorized_key: user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.secret_type == "public_key" + key: "{{ account.secret }}" + exclusive: "{{ kwargs.exclusive }}" + when: "{{ secret_type == 'ssh_key' }}" - name: Refresh connection ansible.builtin.meta: reset_connection @@ -32,3 +44,13 @@ ansible_user: "{{ account.username }}" ansible_password: "{{ account.secret }}" ansible_become: no + when: "{{ secret_type == 'password' }}" + + - name: Verify SSH key + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no + when: "{{ secret_type == 'ssh_key' }}" diff --git a/apps/assets/automations/change_secret/host/windows/main.yml b/apps/assets/automations/change_secret/host/windows/main.yml index bc66486dc..8a2e08363 100644 --- a/apps/assets/automations/change_secret/host/windows/main.yml +++ b/apps/assets/automations/change_secret/host/windows/main.yml @@ -1,7 +1,7 @@ - hosts: demo gather_facts: no tasks: - - name: ping + - name: Test privileged account ansible.windows.win_ping: # - name: Print variables @@ -13,7 +13,7 @@ name: "{{ account.username }}" password: "{{ account.secret }}" update_password: always - when: account.secret_type == "password" + when: "{{ account.secret_type == 'password' }}" - name: Refresh connection ansible.builtin.meta: reset_connection @@ -23,3 +23,4 @@ vars: ansible_user: "{{ account.username }}" ansible_password: "{{ account.secret }}" + when: "{{ account.secret_type == 'password' }}" diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index fcb663ae8..80ca26d72 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -1,14 +1,17 @@ +import os import random import string +from hashlib import md5 from copy import deepcopy +from socket import gethostname from collections import defaultdict from django.utils import timezone -from common.utils import lazyproperty, gen_key_pair +from common.utils import lazyproperty, gen_key_pair, ssh_pubkey_gen, ssh_key_string_to_obj from assets.models import ChangeSecretRecord from assets.const import ( - AutomationTypes, SecretType, SecretStrategy, DEFAULT_PASSWORD_RULES + AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES ) from ..base.manager import BasePlaybookManager @@ -17,15 +20,15 @@ class ChangeSecretManager(BasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.method_hosts_mapper = defaultdict(list) + self.secret_type = self.execution.plan_snapshot.get('secret_type') self.secret_strategy = self.execution.plan_snapshot['secret_strategy'] - self.ssh_key_change_strategy = self.execution.plan_snapshot['ssh_key_change_strategy'] self._password_generated = None self._ssh_key_generated = None self.name_recorder_mapper = {} # 做个映射,方便后面处理 @classmethod def method_type(cls): - return AutomationTypes.change_secret + return AutomationTypes.method_id_meta_mapper @lazyproperty def related_accounts(self): @@ -36,6 +39,19 @@ class ChangeSecretManager(BasePlaybookManager): private_key, public_key = gen_key_pair() return private_key + @staticmethod + def generate_public_key(private_key): + return ssh_pubkey_gen(private_key=private_key, hostname=gethostname()) + + @staticmethod + def generate_private_key_path(secret, path_dir): + key_name = '.' + md5(secret.encode('utf-8')).hexdigest() + key_path = os.path.join(path_dir, key_name) + if not os.path.exists(key_path): + ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path) + os.chmod(key_path, 0o400) + return key_path + def generate_password(self): kwargs = self.automation.plan_snapshot['password_rules'] or {} length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length'])) @@ -77,16 +93,29 @@ class ChangeSecretManager(BasePlaybookManager): else: return self.generate_password() - def get_secret(self, account): - if account.secret_type == SecretType.ssh_key: + def get_secret(self): + if self.secret_type == SecretType.ssh_key: secret = self.get_ssh_key() - elif account.secret_type == SecretType.password: + elif self.secret_type == SecretType.password: secret = self.get_password() else: raise ValueError("Secret must be set") return secret - def host_callback(self, host, asset=None, account=None, automation=None, **kwargs): + def get_kwargs(self, account, secret): + kwargs = {} + if self.secret_type != SecretType.ssh_key: + return kwargs + kwargs['strategy'] = self.automation.plan_snapshot['ssh_key_change_strategy'] + kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no' + + if kwargs['strategy'] == SSHKeyStrategy.set_jms: + kwargs['dest'] = '/home/{}/.ssh/authorized_keys'.format(account.username) + kwargs['regexp'] = '.*{}$'.format(secret.split()[2].strip()) + + return kwargs + + def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs): host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs) if host.get('error'): return host @@ -95,7 +124,9 @@ class ChangeSecretManager(BasePlaybookManager): if account: accounts = accounts.exclude(id=account.id) if '*' not in self.automation.accounts: - accounts = accounts.filter(username__in=self.automation.accounts) + accounts = accounts.filter( + username__in=self.automation.accounts, secret_type=self.secret_type + ) method_attr = getattr(automation, self.method_type() + '_method') method_hosts = self.method_hosts_mapper[method_attr] @@ -103,11 +134,12 @@ class ChangeSecretManager(BasePlaybookManager): inventory_hosts = [] records = [] + host['secret_type'] = self.secret_type for account in accounts: h = deepcopy(host) h['name'] += '_' + account.username + new_secret = self.get_secret() - new_secret = self.get_secret(account) recorder = ChangeSecretRecord( account=account, execution=self.execution, old_secret=account.secret, new_secret=new_secret, @@ -115,11 +147,19 @@ class ChangeSecretManager(BasePlaybookManager): records.append(recorder) self.name_recorder_mapper[h['name']] = recorder + private_key_path = None + if self.secret_type == SecretType.ssh_key: + private_key_path = self.generate_private_key_path(new_secret, path_dir) + new_secret = self.generate_public_key(new_secret) + + h['kwargs'] = self.get_kwargs(account, new_secret) + h['account'] = { 'name': account.name, 'username': account.username, 'secret_type': account.secret_type, 'secret': new_secret, + 'private_key_path': private_key_path } inventory_hosts.append(h) method_hosts.append(h['name']) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index f6c19b7a2..8f555eb85 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -5,28 +5,26 @@ from collections import defaultdict from django.utils.translation import gettext as _ - __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, assets, account_policy='smart', account_prefer='root,administrator', host_callback=None): + def __init__(self, manager, assets=None, account_policy='smart', account_prefer='root,administrator'): """ :param assets: :param account_prefer: account username name if not set use account_policy - :param account_policy: - :param host_callback: after generate host, call this callback to modify host + :param account_policy: smart, privileged_must, privileged_first """ + self.manager = manager self.assets = self.clean_assets(assets) self.account_prefer = account_prefer self.account_policy = account_policy - self.host_callback = host_callback @staticmethod def clean_assets(assets): from assets.models import Asset asset_ids = [asset.id for asset in assets] - assets = Asset.objects.filter(id__in=asset_ids, is_active=True)\ + assets = Asset.objects.filter(id__in=asset_ids, is_active=True) \ .prefetch_related('platform', 'domain', 'accounts') return assets @@ -107,7 +105,7 @@ class JMSInventory: 'protocol': asset.protocol, 'port': asset.port, 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, - 'jms_account': { + 'jms_account': { 'id': str(account.id), 'username': account.username, 'secret': account.secret, 'secret_type': account.secret_type } if account else None @@ -156,7 +154,7 @@ class JMSInventory: account_selected = accounts[0] if accounts else None return account_selected - def generate(self): + def generate(self, path_dir): hosts = [] platform_assets = self.group_by_platform(self.assets) for platform, assets in platform_assets.items(): @@ -170,10 +168,11 @@ class JMSInventory: if not automation.ansible_enabled: host['error'] = _('Ansible disabled') - if self.host_callback is not None: - host = self.host_callback( + if self.manager.host_callback is not None: + host = self.manager.host_callback( host, asset=asset, account=account, - platform=platform, automation=automation + platform=platform, automation=automation, + path_dir=path_dir ) if isinstance(host, list): @@ -195,8 +194,8 @@ class JMSInventory: return data def write_to_file(self, path): - data = self.generate() path_dir = os.path.dirname(path) + data = self.generate(path_dir) if not os.path.exists(path_dir): os.makedirs(path_dir, 0o700, True) with open(path, 'w') as f: From 64daacce634d6504971dcbcb20e146deece43ab5 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 21 Oct 2022 18:19:09 +0800 Subject: [PATCH 215/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 2 +- .../change_secret/host/linux/main.yml | 12 +++++----- .../change_secret/host/windows/main.yml | 4 ++-- .../automations/change_secret/manager.py | 23 +++++++++---------- apps/assets/models/automations/base.py | 11 +++++---- .../models/automations/change_secret.py | 2 +- apps/ops/ansible/inventory.py | 6 ++++- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index d093d22eb..e2faecd64 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -49,7 +49,7 @@ class BasePlaybookManager: ansible_dir = settings.ANSIBLE_DIR dir_name = '{}_{}'.format(self.automation.name.replace(' ', '_'), self.execution.id) path = os.path.join( - ansible_dir, 'automations', self.automation.type, + ansible_dir, 'automations', self.execution.snapshot['type'], dir_name, timezone.now().strftime('%Y%m%d_%H%M%S') ) if not os.path.exists(path): diff --git a/apps/assets/automations/change_secret/host/linux/main.yml b/apps/assets/automations/change_secret/host/linux/main.yml index cc0e1ae61..d295079ba 100644 --- a/apps/assets/automations/change_secret/host/linux/main.yml +++ b/apps/assets/automations/change_secret/host/linux/main.yml @@ -13,26 +13,26 @@ name: "{{ account.username }}" password: "{{ account.secret | password_hash('sha512') }}" update_password: always - when: "{{ secret_type == 'password' }}" + when: secret_type == "password" - name: create user If it already exists, no operation will be performed ansible.builtin.user: name: "{{ account.username }}" - when: "{{ secret_type == 'ssh_key' }}" + when: secret_type == "ssh_key" - name: remove jumpserver ssh key ansible.builtin.lineinfile: dest: "{{ kwargs.dest }}" regexp: "{{ kwargs.regexp }}" state: absent - when: "{{ secret_type == 'ssh_key' and kwargs.strategy == 'set_jms' }}" + when: secret_type == "ssh_key" and kwargs.strategy == "set_jms" - name: Change SSH key ansible.builtin.authorized_key: user: "{{ account.username }}" key: "{{ account.secret }}" exclusive: "{{ kwargs.exclusive }}" - when: "{{ secret_type == 'ssh_key' }}" + when: secret_type == "ssh_key" - name: Refresh connection ansible.builtin.meta: reset_connection @@ -44,7 +44,7 @@ ansible_user: "{{ account.username }}" ansible_password: "{{ account.secret }}" ansible_become: no - when: "{{ secret_type == 'password' }}" + when: secret_type == "password" - name: Verify SSH key ansible.builtin.ping: @@ -53,4 +53,4 @@ ansible_user: "{{ account.username }}" ansible_ssh_private_key_file: "{{ account.private_key_path }}" ansible_become: no - when: "{{ secret_type == 'ssh_key' }}" + when: secret_type == "ssh_key" diff --git a/apps/assets/automations/change_secret/host/windows/main.yml b/apps/assets/automations/change_secret/host/windows/main.yml index 8a2e08363..0c27301dc 100644 --- a/apps/assets/automations/change_secret/host/windows/main.yml +++ b/apps/assets/automations/change_secret/host/windows/main.yml @@ -13,7 +13,7 @@ name: "{{ account.username }}" password: "{{ account.secret }}" update_password: always - when: "{{ account.secret_type == 'password' }}" + when: account.secret_type == "password" - name: Refresh connection ansible.builtin.meta: reset_connection @@ -23,4 +23,4 @@ vars: ansible_user: "{{ account.username }}" ansible_password: "{{ account.secret }}" - when: "{{ account.secret_type == 'password' }}" + when: account.secret_type == "password" diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 80ca26d72..954a309b5 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -20,15 +20,15 @@ class ChangeSecretManager(BasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.method_hosts_mapper = defaultdict(list) - self.secret_type = self.execution.plan_snapshot.get('secret_type') - self.secret_strategy = self.execution.plan_snapshot['secret_strategy'] + self.secret_type = self.execution.snapshot['secret_type'] + self.secret_strategy = self.execution.snapshot['secret_strategy'] self._password_generated = None self._ssh_key_generated = None self.name_recorder_mapper = {} # 做个映射,方便后面处理 @classmethod def method_type(cls): - return AutomationTypes.method_id_meta_mapper + return AutomationTypes.change_secret @lazyproperty def related_accounts(self): @@ -53,7 +53,7 @@ class ChangeSecretManager(BasePlaybookManager): return key_path def generate_password(self): - kwargs = self.automation.plan_snapshot['password_rules'] or {} + kwargs = self.execution.snapshot['password_rules'] or {} length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length'])) symbol_set = kwargs.get('symbol_set') if symbol_set is None: @@ -69,7 +69,7 @@ class ChangeSecretManager(BasePlaybookManager): def get_ssh_key(self): if self.secret_strategy == SecretStrategy.custom: - ssh_key = self.automation.plan_snapshot['ssh_key'] + ssh_key = self.execution.snapshot['ssh_key'] if not ssh_key: raise ValueError("Automation SSH key must be set") return ssh_key @@ -82,7 +82,7 @@ class ChangeSecretManager(BasePlaybookManager): def get_password(self): if self.secret_strategy == SecretStrategy.custom: - password = self.automation.plan_snapshot['password'] + password = self.execution.snapshot['secret'] if not password: raise ValueError("Automation Password must be set") return password @@ -106,7 +106,7 @@ class ChangeSecretManager(BasePlaybookManager): kwargs = {} if self.secret_type != SecretType.ssh_key: return kwargs - kwargs['strategy'] = self.automation.plan_snapshot['ssh_key_change_strategy'] + kwargs['strategy'] = self.execution.snapshot['ssh_key_change_strategy'] kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no' if kwargs['strategy'] == SSHKeyStrategy.set_jms: @@ -123,11 +123,11 @@ class ChangeSecretManager(BasePlaybookManager): accounts = asset.accounts.all() if account: accounts = accounts.exclude(id=account.id) - if '*' not in self.automation.accounts: - accounts = accounts.filter( - username__in=self.automation.accounts, secret_type=self.secret_type - ) + if '*' not in self.execution.snapshot['accounts']: + accounts = accounts.filter(username__in=self.execution.snapshot['accounts']) + + accounts = accounts.filter(secret_type=self.secret_type) method_attr = getattr(automation, self.method_type() + '_method') method_hosts = self.method_hosts_mapper[method_attr] method_hosts = [h for h in method_hosts if h != host['name']] @@ -153,7 +153,6 @@ class ChangeSecretManager(BasePlaybookManager): new_secret = self.generate_public_key(new_secret) h['kwargs'] = self.get_kwargs(account, new_secret) - h['account'] = { 'name': account.name, 'username': account.username, diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index b0fbd80a2..904b98717 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -40,15 +40,18 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): def get_register_task(self): raise NotImplementedError + def get_many_to_many_ids(self, field: str): + return [str(i) for i in getattr(self, field).all().values_list('id', flat=True)] + def to_attr_json(self): return { 'name': self.name, 'type': self.type, - 'org_id': self.org_id, + 'org_id': str(self.org_id), 'comment': self.comment, 'accounts': self.accounts, - 'nodes': list(self.nodes.all().values_list('id', flat=True)), - 'assets': list(self.assets.all().values_list('id', flat=True)), + 'nodes': self.get_many_to_many_ids('nodes'), + 'assets': self.get_many_to_many_ids('assets'), } def execute(self, trigger=Trigger.manual): @@ -59,7 +62,7 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): execution = self.executions.model.objects.create( id=eid, trigger=trigger, automation=self, - plan_snapshot=self.to_attr_json(), + snapshot=self.to_attr_json(), ) return execution.start() diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index 47462320d..5624caf1f 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -18,7 +18,7 @@ class ChangeSecretAutomation(BaseAutomation): ) secret_strategy = models.CharField( choices=SecretStrategy.choices, max_length=16, - default=SecretStrategy.random_one, verbose_name=_('Secret strategy') + default=SecretStrategy.custom, verbose_name=_('Secret strategy') ) secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) password_rules = models.JSONField(default=dict, verbose_name=_('Password rules')) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 8f555eb85..09427f3e5 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -61,6 +61,7 @@ class JMSInventory: var = { 'ansible_user': account.username, } + if not account.secret: return var if account.secret_type == 'password': @@ -77,7 +78,10 @@ class JMSInventory: ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None host['ansible_host'] = asset.address - host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 + if asset.port == 0: + host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 + else: + host['ansible_port'] = asset.port su_from = account.su_from if platform.su_enabled and su_from: From 943b130035b3c37490fa54a06588a31363d0e869 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 22 Oct 2022 11:17:02 +0800 Subject: [PATCH 216/488] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=BF=9C?= =?UTF-8?q?=E7=A8=8B=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0110_auto_20221021_1506.py | 23 +++++ apps/assets/models/account.py | 4 +- apps/assets/models/asset/common.py | 3 +- apps/common/db/models.py | 4 - apps/locale/zh/LC_MESSAGES/django.po | 2 +- apps/ops/apps.py | 1 + apps/rbac/builtin.py | 2 +- apps/rbac/signal_handlers.py | 2 +- .../migrations/0054_auto_20221021_1433.py | 96 +++++++++++++++++++ apps/terminal/models/__init__.py | 10 +- apps/terminal/models/applet/__init__.py | 2 + apps/terminal/models/applet/applet.py | 33 +++++++ apps/terminal/models/applet/provider.py | 31 ++++++ apps/terminal/models/component/__init__.py | 5 + .../models/{ => component}/endpoint.py | 1 + .../terminal/models/{ => component}/status.py | 0 .../models/{ => component}/storage.py | 7 +- apps/terminal/models/{ => component}/task.py | 0 .../models/{ => component}/terminal.py | 6 +- apps/terminal/models/session/__init__.py | 4 + apps/terminal/models/{ => session}/command.py | 2 +- apps/terminal/models/{ => session}/replay.py | 0 apps/terminal/models/{ => session}/session.py | 2 +- apps/terminal/models/{ => session}/sharing.py | 0 24 files changed, 213 insertions(+), 27 deletions(-) create mode 100644 apps/assets/migrations/0110_auto_20221021_1506.py create mode 100644 apps/terminal/migrations/0054_auto_20221021_1433.py create mode 100644 apps/terminal/models/applet/__init__.py create mode 100644 apps/terminal/models/applet/applet.py create mode 100644 apps/terminal/models/applet/provider.py create mode 100644 apps/terminal/models/component/__init__.py rename apps/terminal/models/{ => component}/endpoint.py (99%) rename apps/terminal/models/{ => component}/status.py (100%) rename apps/terminal/models/{ => component}/storage.py (98%) rename apps/terminal/models/{ => component}/task.py (100%) rename apps/terminal/models/{ => component}/terminal.py (97%) create mode 100644 apps/terminal/models/session/__init__.py rename apps/terminal/models/{ => session}/command.py (96%) rename apps/terminal/models/{ => session}/replay.py (100%) rename apps/terminal/models/{ => session}/session.py (99%) rename apps/terminal/models/{ => session}/sharing.py (100%) diff --git a/apps/assets/migrations/0110_auto_20221021_1506.py b/apps/assets/migrations/0110_auto_20221021_1506.py new file mode 100644 index 000000000..039f7d179 --- /dev/null +++ b/apps/assets/migrations/0110_auto_20221021_1506.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-10-21 07:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0109_auto_20221019_2040'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='connectivity', + field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'), + ), + migrations.AddField( + model_name='account', + name='date_verified', + field=models.DateTimeField(null=True, verbose_name='Date verified'), + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 70351a726..70ddc81b0 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from common.utils import lazyproperty -from .base import BaseAccount +from .base import BaseAccount, AbsConnectivity __all__ = ['Account', 'AccountTemplate'] @@ -37,7 +37,7 @@ class AccountHistoricalRecords(HistoricalRecords): return super().fields_included(model) -class Account(BaseAccount): +class Account(AbsConnectivity, BaseAccount): class InnerAccount(models.TextChoices): INPUT = '@INPUT', '@INPUT' USER = '@USER', '@USER' diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index ae3c99b60..5355d0035 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -86,7 +86,7 @@ class Protocol(models.Model): return '{}/{}'.format(self.name, self.port) -class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): +class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) address = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) @@ -100,6 +100,7 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) info = models.JSONField(verbose_name='Info', default=dict, blank=True) + objects = AssetManager.from_queryset(AssetQuerySet)() def __str__(self): diff --git a/apps/common/db/models.py b/apps/common/db/models.py index bac1f1b52..7ff88a877 100644 --- a/apps/common/db/models.py +++ b/apps/common/db/models.py @@ -87,10 +87,6 @@ class JMSBaseModel(BaseCreateUpdateModel): abstract = True -def concated_display(name1, name2): - return Concat(F(name1), Value('('), F(name2), Value(')')) - - def output_as_string(field_name): return ExpressionWrapper(F(field_name), output_field=models.CharField()) diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 849577c24..d5c183d17 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1102,7 +1102,7 @@ msgstr "立刻推送" #: assets/serializers/account/account.py:18 msgid "Has secret" -msgstr "有密码" +msgstr "存在密码" #: assets/serializers/account/account.py:25 msgid "Account template not found" diff --git a/apps/ops/apps.py b/apps/ops/apps.py index 819a23002..7956a5dc1 100644 --- a/apps/ops/apps.py +++ b/apps/ops/apps.py @@ -15,4 +15,5 @@ class OpsConfig(AppConfig): from .celery import signal_handler from . import signal_handlers from . import notifications + from . import tasks super().ready() diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index 77ec14cf1..08e54374c 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -164,7 +164,7 @@ class BuiltinRole: @classmethod def sync_to_db(cls, show_msg=False): roles = cls.get_roles() - print("\n\tUpdate builtin roles") + print(" - Update builtin roles") for pre_role in roles.values(): role, created = pre_role.update_or_create_role() diff --git a/apps/rbac/signal_handlers.py b/apps/rbac/signal_handlers.py index a158b7e6a..37eed870a 100644 --- a/apps/rbac/signal_handlers.py +++ b/apps/rbac/signal_handlers.py @@ -11,7 +11,7 @@ def after_migrate_update_builtin_role_permissions(sender, app_config, **kwargs): # 最后一个 app migrations 后执行, 更新内置角色的权限 last_app = list(apps.get_app_configs())[-1] if app_config.name == last_app.name: - print("\tAfter migration, update builtin role permissions") + print("\nAfter migration, update builtin role permissions") BuiltinRole.sync_to_db() diff --git a/apps/terminal/migrations/0054_auto_20221021_1433.py b/apps/terminal/migrations/0054_auto_20221021_1433.py new file mode 100644 index 000000000..830ef897c --- /dev/null +++ b/apps/terminal/migrations/0054_auto_20221021_1433.py @@ -0,0 +1,96 @@ +# Generated by Django 3.2.14 on 2022-10-21 06:33 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0053_auto_20220830_1244'), + ] + + operations = [ + migrations.CreateModel( + name='Applet', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('version', models.CharField(max_length=16, verbose_name='Version')), + ('type', models.CharField(choices=[('app', 'App'), ('web', 'Web')], max_length=16, verbose_name='Type')), + ('icon', models.ImageField(upload_to='applet/icon', verbose_name='Icon')), + ('author', models.CharField(max_length=128, verbose_name='Author')), + ('protocols', models.JSONField(default=list, verbose_name='Protocol')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='AppletProvider', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('account_automation', models.BooleanField(default=False, verbose_name='Account automation')), + ('date_synced', models.DateTimeField(blank=True, null=True, verbose_name='Date synced')), + ('status', models.CharField(max_length=16, verbose_name='Status')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ProviderDeployment', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(max_length=16, verbose_name='Status')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='terminal.appletprovider', verbose_name='Provider')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='AppletPublication', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(max_length=16, verbose_name='Status')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('applet', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.applet', verbose_name='Applet')), + ('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.appletprovider', verbose_name='Provider')), + ], + options={ + 'unique_together': {('applet', 'provider')}, + }, + ), + migrations.AddField( + model_name='appletprovider', + name='applets', + field=models.ManyToManyField(through='terminal.AppletPublication', to='terminal.Applet', verbose_name='Applet'), + ), + migrations.AddField( + model_name='appletprovider', + name='asset', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='assets.asset', verbose_name='Asset'), + ), + ] diff --git a/apps/terminal/models/__init__.py b/apps/terminal/models/__init__.py index be079721d..268727394 100644 --- a/apps/terminal/models/__init__.py +++ b/apps/terminal/models/__init__.py @@ -1,9 +1,3 @@ -from .command import * from .session import * -from .status import * -from .storage import * -from .task import * -from .terminal import * -from .sharing import * -from .replay import * -from .endpoint import * +from .component import * +from .applet import * diff --git a/apps/terminal/models/applet/__init__.py b/apps/terminal/models/applet/__init__.py new file mode 100644 index 000000000..daef278e0 --- /dev/null +++ b/apps/terminal/models/applet/__init__.py @@ -0,0 +1,2 @@ +from .applet import * +from .provider import * diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py new file mode 100644 index 000000000..4fc16819f --- /dev/null +++ b/apps/terminal/models/applet/applet.py @@ -0,0 +1,33 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from common.db.models import JMSBaseModel + + +__all__ = ['Applet', 'AppletPublication'] + + +class Applet(JMSBaseModel): + class Type(models.TextChoices): + app = 'app', _('App') + web = 'web', _('Web') + name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) + version = models.CharField(max_length=16, verbose_name=_('Version')) + type = models.CharField(max_length=16, choices=Type.choices, verbose_name=_('Type')) + icon = models.ImageField(upload_to='applet/icon', verbose_name=_('Icon')) + author = models.CharField(max_length=128, verbose_name=_('Author')) + protocols = models.JSONField(default=list, verbose_name=_('Protocol')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + + def __str__(self): + return self.name + + +class AppletPublication(JMSBaseModel): + applet = models.ForeignKey('Applet', on_delete=models.PROTECT, verbose_name=_('Applet')) + provider = models.ForeignKey('AppletProvider', on_delete=models.PROTECT, verbose_name=_('Provider')) + status = models.CharField(max_length=16, verbose_name=_('Status')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + + class Meta: + unique_together = ('applet', 'provider') diff --git a/apps/terminal/models/applet/provider.py b/apps/terminal/models/applet/provider.py new file mode 100644 index 000000000..dac90850d --- /dev/null +++ b/apps/terminal/models/applet/provider.py @@ -0,0 +1,31 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from celery import current_app + +from common.db.models import JMSBaseModel + + +__all__ = ['AppletProvider', 'ProviderDeployment'] + + +class AppletProvider(JMSBaseModel): + name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) + asset = models.ForeignKey('assets.Asset', on_delete=models.PROTECT, verbose_name=_('Asset')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) + date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) + status = models.CharField(max_length=16, verbose_name=_('Status')) + applets = models.ManyToManyField( + 'Applet', verbose_name=_('Applet'), + through='AppletPublication', through_fields=('provider', 'applet'), + ) + + +class ProviderDeployment(JMSBaseModel): + provider = models.ForeignKey('AppletProvider', on_delete=models.CASCADE, verbose_name=_('Provider')) + status = models.CharField(max_length=16, verbose_name=_('Status')) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + + def install(self): + pass diff --git a/apps/terminal/models/component/__init__.py b/apps/terminal/models/component/__init__.py new file mode 100644 index 000000000..b136a5da3 --- /dev/null +++ b/apps/terminal/models/component/__init__.py @@ -0,0 +1,5 @@ +from .terminal import * +from .task import * +from .endpoint import * +from .status import * +from .storage import * diff --git a/apps/terminal/models/endpoint.py b/apps/terminal/models/component/endpoint.py similarity index 99% rename from apps/terminal/models/endpoint.py rename to apps/terminal/models/component/endpoint.py index f823f6c90..dac2b1f6d 100644 --- a/apps/terminal/models/endpoint.py +++ b/apps/terminal/models/component/endpoint.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator + from common.db.models import JMSBaseModel from common.db.fields import PortField from common.utils.ip import contains_ip diff --git a/apps/terminal/models/status.py b/apps/terminal/models/component/status.py similarity index 100% rename from apps/terminal/models/status.py rename to apps/terminal/models/component/status.py diff --git a/apps/terminal/models/storage.py b/apps/terminal/models/component/storage.py similarity index 98% rename from apps/terminal/models/storage.py rename to apps/terminal/models/component/storage.py index 555c44aea..7cab98058 100644 --- a/apps/terminal/models/storage.py +++ b/apps/terminal/models/component/storage.py @@ -1,22 +1,21 @@ from __future__ import unicode_literals - import copy import os - from importlib import import_module import jms_storage from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings + from common.mixins import CommonModelMixin from common.utils import get_logger from common.db.fields import EncryptJsonDictTextField from common.utils.timezone import local_now_date_display from terminal.backends import TYPE_ENGINE_MAPPING from .terminal import Terminal -from .command import Command -from .. import const +from ..session.command import Command +from terminal import const logger = get_logger(__file__) diff --git a/apps/terminal/models/task.py b/apps/terminal/models/component/task.py similarity index 100% rename from apps/terminal/models/task.py rename to apps/terminal/models/component/task.py diff --git a/apps/terminal/models/terminal.py b/apps/terminal/models/component/terminal.py similarity index 97% rename from apps/terminal/models/terminal.py rename to apps/terminal/models/component/terminal.py index 4b585cfff..4a95fbd3a 100644 --- a/apps/terminal/models/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -9,9 +9,9 @@ from common.utils import get_logger from users.models import User from orgs.utils import tmp_to_root_org from .status import Status -from .. import const -from ..const import ComponentStatusChoices as StatusChoice -from .session import Session +from terminal import const +from terminal.const import ComponentStatusChoices as StatusChoice +from ..session import Session logger = get_logger(__file__) diff --git a/apps/terminal/models/session/__init__.py b/apps/terminal/models/session/__init__.py new file mode 100644 index 000000000..073c9d078 --- /dev/null +++ b/apps/terminal/models/session/__init__.py @@ -0,0 +1,4 @@ +from .command import * +from .session import * +from .replay import * +from .sharing import * diff --git a/apps/terminal/models/command.py b/apps/terminal/models/session/command.py similarity index 96% rename from apps/terminal/models/command.py rename to apps/terminal/models/session/command.py index 44edf013c..c940e855b 100644 --- a/apps/terminal/models/command.py +++ b/apps/terminal/models/session/command.py @@ -4,7 +4,7 @@ from django.db import models from django.db.models.signals import post_save from django.utils.translation import ugettext_lazy as _ -from ..backends.command.models import AbstractSessionCommand +from terminal.backends.command.models import AbstractSessionCommand class CommandManager(models.Manager): diff --git a/apps/terminal/models/replay.py b/apps/terminal/models/session/replay.py similarity index 100% rename from apps/terminal/models/replay.py rename to apps/terminal/models/session/replay.py diff --git a/apps/terminal/models/session.py b/apps/terminal/models/session/session.py similarity index 99% rename from apps/terminal/models/session.py rename to apps/terminal/models/session/session.py index 4f01bde4a..0a095a401 100644 --- a/apps/terminal/models/session.py +++ b/apps/terminal/models/session/session.py @@ -16,7 +16,7 @@ from users.models import User from orgs.mixins.models import OrgModelMixin from django.db.models import TextChoices from common.utils import get_object_or_none, lazyproperty -from ..backends import get_multi_command_storage +from terminal.backends import get_multi_command_storage class Session(OrgModelMixin): diff --git a/apps/terminal/models/sharing.py b/apps/terminal/models/session/sharing.py similarity index 100% rename from apps/terminal/models/sharing.py rename to apps/terminal/models/session/sharing.py From 64e03a44129717866fdc1bd8d38cc9ac2a08dbd3 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 24 Oct 2022 20:14:18 +0800 Subject: [PATCH 217/488] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E4=BB=BB=E5=8A=A1api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/serializers/common.py | 4 +- apps/ops/api/adhoc.py | 2 +- apps/ops/api/celery.py | 28 ++++++-- .../ops/migrations/0027_auto_20221024_1709.py | 67 +++++++++++++++++++ .../ops/migrations/0028_auto_20221024_1712.py | 25 +++++++ apps/ops/models/celery.py | 19 ++++++ apps/ops/serializers/celery.py | 26 +++++-- apps/ops/signal_handlers.py | 23 +++++-- apps/ops/tasks.py | 6 +- apps/ops/urls/api_urls.py | 5 +- 10 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 apps/ops/migrations/0027_auto_20221024_1709.py create mode 100644 apps/ops/migrations/0028_auto_20221024_1712.py diff --git a/apps/common/drf/serializers/common.py b/apps/common/drf/serializers/common.py index 688e1bfcc..a522e3a82 100644 --- a/apps/common/drf/serializers/common.py +++ b/apps/common/drf/serializers/common.py @@ -12,7 +12,7 @@ from .mixin import BulkListSerializerMixin, BulkSerializerMixin __all__ = [ 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', - 'AdaptedBulkListSerializer', 'CeleryTaskSerializer', + 'AdaptedBulkListSerializer', 'CeleryTaskExecutionSerializer', 'WritableNestedModelSerializer', 'GroupedChoiceSerializer', ] @@ -73,7 +73,7 @@ class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer): pass -class CeleryTaskSerializer(serializers.Serializer): +class CeleryTaskExecutionSerializer(serializers.Serializer): task = serializers.CharField(read_only=True) diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 8644ac5d2..71e818fbb 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404 from rest_framework import viewsets, generics from rest_framework.views import Response -from common.drf.serializers import CeleryTaskSerializer +from common.drf.serializers import CeleryTaskExecutionSerializer from ..models import AdHoc, AdHocExecution from ..serializers import ( AdHocSerializer, diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index cd452c471..5fa8c902b 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -4,6 +4,7 @@ import os import re +from celery import current_app from django.utils.translation import ugettext as _ from rest_framework import viewsets from celery.result import AsyncResult @@ -12,20 +13,21 @@ from django_celery_beat.models import PeriodicTask from common.permissions import IsValidUser from common.api import LogTailApi -from ..models import CeleryTask +from ..models import CeleryTaskExecution, CeleryTask from ..serializers import CeleryResultSerializer, CeleryPeriodTaskSerializer from ..celery.utils import get_celery_task_log_path from ..ansible.utils import get_ansible_task_log_path from common.mixins.api import CommonApiMixin - __all__ = [ - 'CeleryTaskLogApi', 'CeleryResultApi', 'CeleryPeriodTaskViewSet', - 'AnsibleTaskLogApi', + 'CeleryTaskExecutionLogApi', 'CeleryResultApi', 'CeleryPeriodTaskViewSet', + 'AnsibleTaskLogApi', 'CeleryTaskViewSet', 'CeleryTaskExecutionViewSet' ] +from ..serializers.celery import CeleryTaskSerializer, CeleryTaskExecutionSerializer -class CeleryTaskLogApi(LogTailApi): + +class CeleryTaskExecutionLogApi(LogTailApi): permission_classes = (IsValidUser,) task = None task_id = '' @@ -46,8 +48,8 @@ class CeleryTaskLogApi(LogTailApi): if new_path and os.path.isfile(new_path): return new_path try: - task = CeleryTask.objects.get(id=self.task_id) - except CeleryTask.DoesNotExist: + task = CeleryTaskExecution.objects.get(id=self.task_id) + except CeleryTaskExecution.DoesNotExist: return None return task.full_log_path @@ -94,3 +96,15 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): queryset = super().get_queryset() queryset = queryset.exclude(description='') return queryset + + +class CeleryTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): + queryset = CeleryTask.objects.all() + serializer_class = CeleryTaskSerializer + http_method_names = ('get',) + + +class CeleryTaskExecutionViewSet(CommonApiMixin, viewsets.ModelViewSet): + queryset = CeleryTaskExecution.objects.all() + serializer_class = CeleryTaskExecutionSerializer + http_method_names = ('get',) diff --git a/apps/ops/migrations/0027_auto_20221024_1709.py b/apps/ops/migrations/0027_auto_20221024_1709.py new file mode 100644 index 000000000..4b29e4a3e --- /dev/null +++ b/apps/ops/migrations/0027_auto_20221024_1709.py @@ -0,0 +1,67 @@ +# Generated by Django 3.2.14 on 2022-10-24 09:09 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0026_auto_20221009_2050'), + ] + + operations = [ + migrations.CreateModel( + name='CeleryTaskExecution', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=1024)), + ('args', models.JSONField(verbose_name='Args')), + ('kwargs', models.JSONField(verbose_name='Kwargs')), + ('state', models.CharField(max_length=16, verbose_name='State')), + ('is_finished', models.BooleanField(default=False, verbose_name='Finished')), + ('date_published', models.DateTimeField(auto_now_add=True)), + ('date_start', models.DateTimeField(null=True)), + ('date_finished', models.DateTimeField(null=True)), + ], + ), + migrations.RenameField( + model_name='celerytask', + old_name='date_finished', + new_name='date_last_published', + ), + migrations.RemoveField( + model_name='celerytask', + name='args', + ), + migrations.RemoveField( + model_name='celerytask', + name='date_published', + ), + migrations.RemoveField( + model_name='celerytask', + name='date_start', + ), + migrations.RemoveField( + model_name='celerytask', + name='is_finished', + ), + migrations.RemoveField( + model_name='celerytask', + name='kwargs', + ), + migrations.RemoveField( + model_name='celerytask', + name='state', + ), + migrations.AddField( + model_name='celerytask', + name='description', + field=models.CharField(max_length=2048, null=True), + ), + migrations.AddField( + model_name='celerytask', + name='verbose_name', + field=models.CharField(max_length=1024, null=True), + ), + ] diff --git a/apps/ops/migrations/0028_auto_20221024_1712.py b/apps/ops/migrations/0028_auto_20221024_1712.py new file mode 100644 index 000000000..d246adbd8 --- /dev/null +++ b/apps/ops/migrations/0028_auto_20221024_1712.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.14 on 2022-10-24 09:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0027_auto_20221024_1709'), + ] + + operations = [ + migrations.RemoveField( + model_name='celerytask', + name='date_last_published', + ), + migrations.RemoveField( + model_name='celerytask', + name='description', + ), + migrations.RemoveField( + model_name='celerytask', + name='verbose_name', + ), + ] diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 2291eb6f1..6ea4e2641 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -7,8 +7,27 @@ from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.db import models +from ops.celery import app + class CeleryTask(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4) + name = models.CharField(max_length=1024) + + @property + def verbose_name(self): + task = app.tasks.get(self.name, None) + if task: + return getattr(task, 'verbose_name', None) + + @property + def description(self): + task = app.tasks.get(self.name, None) + if task: + return getattr(task, 'description', None) + + +class CeleryTaskExecution(models.Model): LOG_DIR = os.path.join(settings.PROJECT_DIR, 'data', 'celery') id = models.UUIDField(primary_key=True, default=uuid.uuid4) name = models.CharField(max_length=1024) diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py index 8015c2482..8122ed636 100644 --- a/apps/ops/serializers/celery.py +++ b/apps/ops/serializers/celery.py @@ -5,10 +5,12 @@ from rest_framework import serializers from django_celery_beat.models import PeriodicTask __all__ = [ - 'CeleryResultSerializer', 'CeleryTaskSerializer', - 'CeleryPeriodTaskSerializer' + 'CeleryResultSerializer', 'CeleryTaskExecutionSerializer', + 'CeleryPeriodTaskSerializer', 'CeleryTaskSerializer' ] +from ops.models import CeleryTask, CeleryTaskExecution + class CeleryResultSerializer(serializers.Serializer): id = serializers.UUIDField() @@ -16,10 +18,6 @@ class CeleryResultSerializer(serializers.Serializer): state = serializers.CharField(max_length=16) -class CeleryTaskSerializer(serializers.Serializer): - pass - - class CeleryPeriodTaskSerializer(serializers.ModelSerializer): class Meta: model = PeriodicTask @@ -27,3 +25,19 @@ class CeleryPeriodTaskSerializer(serializers.ModelSerializer): 'name', 'task', 'enabled', 'description', 'last_run_at', 'total_run_count' ] + + +class CeleryTaskSerializer(serializers.ModelSerializer): + class Meta: + model = CeleryTask + fields = [ + 'name', 'verbose_name', 'description', + ] + + +class CeleryTaskExecutionSerializer(serializers.ModelSerializer): + class Meta: + model = CeleryTaskExecution + fields = [ + "name", "args", "kwargs", "state", "is_finished", "date_published", "date_start", "date_finished" + ] diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index e48802d84..5882a10e8 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -1,12 +1,15 @@ import ast +from django.db import transaction +from django.dispatch import receiver from django.utils import translation, timezone from django.core.cache import cache -from celery import signals +from celery import signals, current_app from common.db.utils import close_old_connections, get_logger -from .models import CeleryTask - +from common.signals import django_ready +from .celery import app +from .models import CeleryTaskExecution, CeleryTask logger = get_logger(__name__) @@ -14,6 +17,14 @@ TASK_LANG_CACHE_KEY = 'TASK_LANG_{}' TASK_LANG_CACHE_TTL = 1800 +@receiver(django_ready) +def sync_registered_tasks(*args, **kwargs): + with transaction.atomic(): + CeleryTask.objects.all().delete() + for key in app.tasks: + CeleryTask(name=key).save() + + @signals.before_task_publish.connect def before_task_publish(headers=None, **kwargs): task_id = headers.get('id') @@ -25,7 +36,7 @@ def before_task_publish(headers=None, **kwargs): @signals.task_prerun.connect def on_celery_task_pre_run(task_id='', **kwargs): # 更新状态 - CeleryTask.objects.filter(id=task_id).update(state='RUNNING', date_start=timezone.now()) + CeleryTaskExecution.objects.filter(id=task_id).update(state='RUNNING', date_start=timezone.now()) # 关闭之前的数据库连接 close_old_connections() @@ -41,7 +52,7 @@ def on_celery_task_post_run(task_id='', state='', **kwargs): close_old_connections() print("Task post run: ", task_id, state) - CeleryTask.objects.filter(id=task_id).update( + CeleryTaskExecution.objects.filter(id=task_id).update( state=state, date_finished=timezone.now(), is_finished=True ) @@ -72,4 +83,4 @@ def task_sent_handler(headers=None, body=None, **kwargs): 'args': args, 'kwargs': kwargs } - CeleryTask.objects.create(**data) + CeleryTaskExecution.objects.create(**data) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index e9ba28eb7..dc3ac6e68 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -20,7 +20,7 @@ from .celery.utils import ( create_or_update_celery_periodic_tasks, get_celery_periodic_task, disable_celery_periodic_task, delete_celery_periodic_task ) -from .models import CeleryTask, AdHoc, Playbook +from .models import CeleryTaskExecution, AdHoc, Playbook from .notifications import ServerPerformanceCheckUtil logger = get_logger(__file__) @@ -94,9 +94,9 @@ def clean_celery_tasks_period(): logger.debug("Start clean celery task history") expire_days = get_log_keep_day('TASK_LOG_KEEP_DAYS') days_ago = timezone.now() - timezone.timedelta(days=expire_days) - tasks = CeleryTask.objects.filter(date_start__lt=days_ago) + tasks = CeleryTaskExecution.objects.filter(date_start__lt=days_ago) tasks.delete() - tasks = CeleryTask.objects.filter(date_start__isnull=True) + tasks = CeleryTaskExecution.objects.filter(date_start__isnull=True) tasks.delete() command = "find %s -mtime +%s -name '*.log' -type f -exec rm -f {} \\;" % ( settings.CELERY_LOG_DIR, expire_days diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index 49038b9b1..bc263c184 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -6,7 +6,6 @@ from rest_framework.routers import DefaultRouter from rest_framework_bulk.routes import BulkRouter from .. import api - app_name = "ops" router = DefaultRouter() @@ -15,9 +14,11 @@ bulk_router = BulkRouter() router.register(r'adhoc', api.AdHocViewSet, 'adhoc') router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution') router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task') +router.register(r'celery/tasks', api.CeleryTaskViewSet, 'celery-task') +router.register(r'celery/task-executions', api.CeleryTaskExecutionViewSet, 'task-execution') urlpatterns = [ - path('celery/task//log/', api.CeleryTaskLogApi.as_view(), name='celery-task-log'), + path('celery/task//log/', api.CeleryTaskExecutionLogApi.as_view(), name='celery-task-log'), path('celery/task//result/', api.CeleryResultApi.as_view(), name='celery-result'), path('ansible/task//log/', api.AnsibleTaskLogApi.as_view(), name='ansible-task-log'), From f743dea1fdc8e64014f0af3828002130beebe86c Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 24 Oct 2022 20:24:56 +0800 Subject: [PATCH 218/488] perf: mysql postgresql --- .../change_secret/database/mysql/main.yml | 9 --------- .../database/postgresql/main.yml | 19 ++++++------------- .../automations/change_secret/manager.py | 6 +++--- apps/ops/ansible/inventory.py | 2 +- requirements/requirements.txt | 2 +- 5 files changed, 11 insertions(+), 27 deletions(-) diff --git a/apps/assets/automations/change_secret/database/mysql/main.yml b/apps/assets/automations/change_secret/database/mysql/main.yml index a3c56768f..39560a383 100644 --- a/apps/assets/automations/change_secret/database/mysql/main.yml +++ b/apps/assets/automations/change_secret/database/mysql/main.yml @@ -2,15 +2,6 @@ gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python - jms_account: - username: root - secret: redhat - jms_asset: - address: 127.0.0.1 - port: 3306 - account: - username: web1 - secret: jumpserver tasks: - name: Test MySQL connection diff --git a/apps/assets/automations/change_secret/database/postgresql/main.yml b/apps/assets/automations/change_secret/database/postgresql/main.yml index ed4e60abf..816d4c0e2 100644 --- a/apps/assets/automations/change_secret/database/postgresql/main.yml +++ b/apps/assets/automations/change_secret/database/postgresql/main.yml @@ -1,18 +1,8 @@ - hosts: postgre gather_facts: no vars: - ansible_python_interpreter: /usr/local/bin/python - jms_account: - username: postgre - secret: postgre - jms_asset: - address: 127.0.0.1 - port: 5432 - database: testdb - account: - username: test - secret: jumpserver - +# ansible_python_interpreter: /usr/local/bin/python + ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python tasks: - name: Test PostgreSQL connection community.postgresql.postgresql_ping: @@ -25,7 +15,8 @@ - name: Display PostgreSQL version debug: - var: db_info.version.full + var: db_info.server_version.full + when: db_info is succeeded - name: Change PostgreSQL password community.postgresql.postgresql_user: @@ -37,6 +28,7 @@ name: "{{ account.username }}" password: "{{ account.secret }}" when: db_info is succeeded + register: change_info - name: Verify password community.postgresql.postgresql_ping: @@ -45,3 +37,4 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" db: "{{ jms_asset.database }}" + when: db_info is succeeded and change_info is changed diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 954a309b5..4ac49676b 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -69,10 +69,10 @@ class ChangeSecretManager(BasePlaybookManager): def get_ssh_key(self): if self.secret_strategy == SecretStrategy.custom: - ssh_key = self.execution.snapshot['ssh_key'] - if not ssh_key: + secret = self.execution.snapshot['secret'] + if not secret: raise ValueError("Automation SSH key must be set") - return ssh_key + return secret elif self.secret_strategy == SecretStrategy.random_one: if not self._ssh_key_generated: self._ssh_key_generated = self.generate_ssh_key() diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 09427f3e5..c544cfcd6 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -199,8 +199,8 @@ class JMSInventory: def write_to_file(self, path): path_dir = os.path.dirname(path) - data = self.generate(path_dir) if not os.path.exists(path_dir): os.makedirs(path_dir, 0o700, True) + data = self.generate(path_dir) with open(path, 'w') as f: f.write(json.dumps(data, indent=4)) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ea6c56b59..8af2fcb0f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -143,4 +143,4 @@ ForgeryPy3==0.3.1 django-debug-toolbar==3.5 Pympler==1.0.1 IPy==1.1 - +psycopg2==2.9.4 From 5606082ca3603ed1f46144aa423a6d384d76a19c Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Oct 2022 12:57:34 +0800 Subject: [PATCH 219/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=20applet=20?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/const/host.py | 30 ++++++-- apps/assets/const/types.py | 5 +- .../migrations/0108_auto_20221019_1706.py | 2 +- apps/terminal/api/__init__.py | 9 +-- apps/terminal/api/applet/__init__.py | 2 + apps/terminal/api/applet/applet.py | 71 +++++++++++++++++++ apps/terminal/api/applet/host.py | 16 +++++ apps/terminal/api/component/__init__.py | 4 ++ apps/terminal/api/{ => component}/endpoint.py | 8 +-- apps/terminal/api/{ => component}/status.py | 6 +- apps/terminal/api/{ => component}/storage.py | 4 +- apps/terminal/api/{ => component}/terminal.py | 6 +- apps/terminal/api/session/__init__.py | 4 ++ apps/terminal/api/{ => session}/command.py | 4 +- apps/terminal/api/{ => session}/session.py | 17 ++--- apps/terminal/api/{ => session}/sharing.py | 3 +- apps/terminal/api/{ => session}/task.py | 6 +- ...021_1433.py => 0054_auto_20221024_1452.py} | 57 ++++++++------- apps/terminal/models/applet/__init__.py | 2 +- apps/terminal/models/applet/applet.py | 58 +++++++++++++-- .../models/applet/{provider.py => host.py} | 23 +++--- apps/terminal/serializers/__init__.py | 1 + apps/terminal/serializers/applet.py | 65 +++++++++++++++++ apps/terminal/urls/api_urls.py | 5 ++ 24 files changed, 322 insertions(+), 86 deletions(-) create mode 100644 apps/terminal/api/applet/__init__.py create mode 100644 apps/terminal/api/applet/applet.py create mode 100644 apps/terminal/api/applet/host.py create mode 100644 apps/terminal/api/component/__init__.py rename apps/terminal/api/{ => component}/endpoint.py (97%) rename apps/terminal/api/{ => component}/status.py (93%) rename apps/terminal/api/{ => component}/storage.py (97%) rename apps/terminal/api/{ => component}/terminal.py (97%) create mode 100644 apps/terminal/api/session/__init__.py rename apps/terminal/api/{ => session}/command.py (98%) rename apps/terminal/api/{ => session}/session.py (96%) rename apps/terminal/api/{ => session}/sharing.py (97%) rename apps/terminal/api/{ => session}/task.py (96%) rename apps/terminal/migrations/{0054_auto_20221021_1433.py => 0054_auto_20221024_1452.py} (80%) rename apps/terminal/models/applet/{provider.py => host.py} (57%) create mode 100644 apps/terminal/serializers/applet.py diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 6236379a5..371ab3688 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -67,6 +67,7 @@ class HostTypes(BaseType): return { cls.LINUX: [ {'name': 'Linux'}, + {'name': 'Gateway'} ], cls.UNIX: [ {'name': 'Unix'}, @@ -75,16 +76,31 @@ class HostTypes(BaseType): {'name': 'AIX', 'automation': { 'push_account_method': 'push_account_aix', 'change_secret_method': 'push_secret_aix' - }}, + }} ], cls.WINDOWS: [ {'name': 'Windows'}, - {'name': 'Windows-TLS', 'protocols_setting': { - 'rdp': {'security': 'tls'}, - }}, - {'name': 'Windows-RDP', 'protocols_setting': { - 'rdp': {'security': 'rdp'}, - }} + { + 'name': 'Windows-TLS', + 'protocols_setting': { + 'rdp': {'security': 'tls'}, + } + }, + { + 'name': 'Windows-RDP', + 'protocols_setting': { + 'rdp': {'security': 'rdp'}, + } + }, + { + 'name': 'RemoteAppHost', + '_protocols': ['rdp', 'ssh'], + 'protocols_setting': { + 'ssh': { + 'required': True + } + } + } ], cls.OTHER_HOST: [] } diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index 620527bfd..b77872ad0 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -224,7 +224,10 @@ class AllTypes(ChoicesMixin): if _protocols: protocols_data = [p for p in protocols_data if p['name'] in _protocols] for p in protocols_data: - p['setting'] = {**_protocols_setting.get(p['name'], {}), **p.get('setting', {})} + setting = _protocols_setting.get(p['name'], {}) + p['required'] = setting.pop('required', False) + p['default'] = setting.pop('default', False) + p['setting'] = {**setting, **p.get('setting', {})} platform_data = { **default_platform_data, **d, diff --git a/apps/assets/migrations/0108_auto_20221019_1706.py b/apps/assets/migrations/0108_auto_20221019_1706.py index f59a5b7a9..57279fffc 100644 --- a/apps/assets/migrations/0108_auto_20221019_1706.py +++ b/apps/assets/migrations/0108_auto_20221019_1706.py @@ -55,7 +55,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='changesecretautomation', name='secret_strategy', - field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Secret strategy'), + field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16, verbose_name='Secret strategy'), ), migrations.AddField( model_name='changesecretautomation', diff --git a/apps/terminal/api/__init__.py b/apps/terminal/api/__init__.py index 16021a5ed..24c30fd5e 100644 --- a/apps/terminal/api/__init__.py +++ b/apps/terminal/api/__init__.py @@ -1,10 +1,5 @@ # -*- coding: utf-8 -*- # -from .terminal import * from .session import * -from .command import * -from .task import * -from .storage import * -from .status import * -from .sharing import * -from .endpoint import * +from .component import * +from .applet import * diff --git a/apps/terminal/api/applet/__init__.py b/apps/terminal/api/applet/__init__.py new file mode 100644 index 000000000..b2a4cac34 --- /dev/null +++ b/apps/terminal/api/applet/__init__.py @@ -0,0 +1,2 @@ +from .applet import * +from .host import * diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py new file mode 100644 index 000000000..5d683fa7b --- /dev/null +++ b/apps/terminal/api/applet/applet.py @@ -0,0 +1,71 @@ +import os.path +import shutil +import zipfile + +import yaml +from django.core.files.storage import default_storage +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response + +from terminal import serializers, models +from terminal.serializers import AppletUploadSerializer + + +class AppletViewSet(viewsets.ModelViewSet): + queryset = models.Applet.objects.all() + serializer_class = serializers.AppletSerializer + rbac_perms = { + 'upload': 'terminal.add_applet', + } + + @action(detail=False, methods=['post'], serializer_class=AppletUploadSerializer) + def upload(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + file = serializer.validated_data['file'] + save_to = 'applets/{}'.format(file.name + '.tmp.zip') + if default_storage.exists(save_to): + default_storage.delete(save_to) + rel_path = default_storage.save(save_to, file) + + path = default_storage.path(rel_path) + extract_to = default_storage.path('applets/{}.tmp'.format(file.name)) + if os.path.exists(extract_to): + shutil.rmtree(extract_to) + + update = request.query_params.get('update') + with zipfile.ZipFile(path) as zp: + if zp.testzip() is not None: + return Response({'msg': 'Invalid Zip file'}, status=400) + zp.extractall(extract_to) + + tmp_dir = os.path.join(extract_to, file.name.replace('.zip', '')) + files = ['manifest.yml', 'icon.png', 'i18n.yml'] + for name in files: + path = os.path.join(tmp_dir, name) + if not os.path.exists(path): + return Response({'error': 'Missing file: {}'.format(path)}, status=400) + + with open(os.path.join(tmp_dir, 'manifest.yml')) as f: + manifest = yaml.safe_load(f) + + name = manifest.get('name', '') + instance = models.Applet.objects.filter(name=name).first() + if instance and not update: + return Response({'error': 'Applet already exists: {}'.format(name)}, status=400) + + serializer = serializers.AppletSerializer(data=manifest, instance=instance) + serializer.is_valid(raise_exception=True) + save_to = default_storage.path('applets/{}'.format(name)) + if os.path.exists(save_to): + shutil.rmtree(save_to) + shutil.move(tmp_dir, save_to) + serializer.save() + return Response(serializer.data, status=201) + + +class AppletPublicationViewSet(viewsets.ModelViewSet): + queryset = models.AppletPublication.objects.all() + serializer_class = serializers.AppletPublicationSerializer diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py new file mode 100644 index 000000000..c3c301e30 --- /dev/null +++ b/apps/terminal/api/applet/host.py @@ -0,0 +1,16 @@ +from rest_framework import viewsets + +from terminal import serializers, models + +__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] + + +class AppletHostViewSet(viewsets.ModelViewSet): + queryset = models.AppletHost.objects.all() + serializer_class = serializers.AppletHostSerializer + + +class AppletHostDeploymentViewSet(viewsets.ModelViewSet): + queryset = models.AppletHostDeployment.objects.all() + serializer_class = serializers.AppletHostDeploymentSerializer + diff --git a/apps/terminal/api/component/__init__.py b/apps/terminal/api/component/__init__.py new file mode 100644 index 000000000..afefe0c18 --- /dev/null +++ b/apps/terminal/api/component/__init__.py @@ -0,0 +1,4 @@ +from .terminal import * +from .storage import * +from .status import * +from .endpoint import * diff --git a/apps/terminal/api/endpoint.py b/apps/terminal/api/component/endpoint.py similarity index 97% rename from apps/terminal/api/endpoint.py rename to apps/terminal/api/component/endpoint.py index f864c6d13..364cd803e 100644 --- a/apps/terminal/api/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -2,15 +2,15 @@ from rest_framework.decorators import action from rest_framework.response import Response from rest_framework import status from rest_framework.request import Request + from common.drf.api import JMSBulkModelViewSet +from common.permissions import IsValidUserOrConnectionToken from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from assets.models import Asset from orgs.utils import tmp_to_root_org -from terminal.models import Session -from ..models import Endpoint, EndpointRule -from .. import serializers -from common.permissions import IsValidUserOrConnectionToken +from terminal.models import Session, Endpoint, EndpointRule +from terminal import serializers __all__ = ['EndpointViewSet', 'EndpointRuleViewSet'] diff --git a/apps/terminal/api/status.py b/apps/terminal/api/component/status.py similarity index 93% rename from apps/terminal/api/status.py rename to apps/terminal/api/component/status.py index 3ec00436b..5e27926ea 100644 --- a/apps/terminal/api/status.py +++ b/apps/terminal/api/component/status.py @@ -9,9 +9,9 @@ from rest_framework import viewsets, generics from rest_framework.views import Response from rest_framework import status -from ..models import Terminal, Status, Session -from .. import serializers -from ..utils import TypedComponentsStatusMetricsUtil +from terminal.models import Terminal, Status, Session +from terminal import serializers +from terminal.utils import TypedComponentsStatusMetricsUtil logger = logging.getLogger(__file__) diff --git a/apps/terminal/api/storage.py b/apps/terminal/api/component/storage.py similarity index 97% rename from apps/terminal/api/storage.py rename to apps/terminal/api/component/storage.py index d9694c337..d46b6f91f 100644 --- a/apps/terminal/api/storage.py +++ b/apps/terminal/api/component/storage.py @@ -11,8 +11,8 @@ from django_filters import utils from terminal import const from common.const.http import GET from terminal.filters import CommandStorageFilter, CommandFilter, CommandFilterForStorageTree -from ..models import CommandStorage, ReplayStorage -from ..serializers import CommandStorageSerializer, ReplayStorageSerializer +from terminal.models import CommandStorage, ReplayStorage +from terminal.serializers import CommandStorageSerializer, ReplayStorageSerializer __all__ = [ 'CommandStorageViewSet', 'CommandStorageTestConnectiveApi', diff --git a/apps/terminal/api/terminal.py b/apps/terminal/api/component/terminal.py similarity index 97% rename from apps/terminal/api/terminal.py rename to apps/terminal/api/component/terminal.py index 209492baa..a58181bf4 100644 --- a/apps/terminal/api/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -14,9 +14,9 @@ from common.exceptions import JMSException from common.drf.api import JMSBulkModelViewSet from common.utils import get_object_or_none, get_request_ip from common.permissions import WithBootstrapToken -from ..models import Terminal -from .. import serializers -from .. import exceptions +from terminal.models import Terminal +from terminal import serializers +from terminal import exceptions __all__ = [ 'TerminalViewSet', 'TerminalConfig', diff --git a/apps/terminal/api/session/__init__.py b/apps/terminal/api/session/__init__.py new file mode 100644 index 000000000..a046d4b3d --- /dev/null +++ b/apps/terminal/api/session/__init__.py @@ -0,0 +1,4 @@ +from .session import * +from .sharing import * +from .command import * +from .task import * diff --git a/apps/terminal/api/command.py b/apps/terminal/api/session/command.py similarity index 98% rename from apps/terminal/api/command.py rename to apps/terminal/api/session/command.py index 5b60a114a..fec0848a5 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/session/command.py @@ -13,11 +13,11 @@ from common.drf.api import JMSBulkModelViewSet from common.utils import get_logger from terminal.backends.command.serializers import InsecureCommandAlertSerializer from terminal.exceptions import StorageInvalid -from ..backends import ( +from terminal.backends import ( get_command_storage, get_multi_command_storage, SessionCommandSerializer, ) -from ..notifications import CommandAlertMessage +from terminal.notifications import CommandAlertMessage logger = get_logger(__name__) __all__ = ['CommandViewSet', 'InsecureCommandAlertAPI'] diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session/session.py similarity index 96% rename from apps/terminal/api/session.py rename to apps/terminal/api/session/session.py index 25477166c..fe742024d 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session/session.py @@ -24,15 +24,16 @@ from common.drf.renders import PassthroughRenderer from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import tmp_to_root_org, tmp_to_org from users.models import User -from .. import utils -from ..utils import find_session_replay_local, download_session_replay -from ..models import Session -from .. import serializers -from terminal.utils import is_session_approver +from terminal.utils import ( + find_session_replay_local, download_session_replay, + is_session_approver, get_session_replay_url +) +from terminal.models import Session +from terminal import serializers __all__ = [ - 'SessionViewSet', 'SessionReplayViewSet', 'SessionJoinValidateAPI', - 'MySessionAPIView', + 'SessionViewSet', 'SessionReplayViewSet', + 'SessionJoinValidateAPI', 'MySessionAPIView', ] logger = get_logger(__name__) @@ -93,7 +94,7 @@ class SessionViewSet(OrgBulkModelViewSet): url_name='replay-download') def download(self, request, *args, **kwargs): session = self.get_object() - local_path, url = utils.get_session_replay_url(session) + local_path, url = get_session_replay_url(session) if local_path is None: return Response({"error": url}, status=404) file = self.prepare_offline_file(session, local_path) diff --git a/apps/terminal/api/sharing.py b/apps/terminal/api/session/sharing.py similarity index 97% rename from apps/terminal/api/sharing.py rename to apps/terminal/api/session/sharing.py index 1a9545e54..a3d324205 100644 --- a/apps/terminal/api/sharing.py +++ b/apps/terminal/api/session/sharing.py @@ -5,9 +5,8 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ from common.const.http import PATCH -from common.permissions import IsValidUser from orgs.mixins.api import OrgModelViewSet -from .. import serializers, models +from terminal import serializers, models __all__ = ['SessionSharingViewSet', 'SessionJoinRecordsViewSet'] diff --git a/apps/terminal/api/task.py b/apps/terminal/api/session/task.py similarity index 96% rename from apps/terminal/api/task.py rename to apps/terminal/api/session/task.py index c7e1a2681..80fee6097 100644 --- a/apps/terminal/api/task.py +++ b/apps/terminal/api/session/task.py @@ -7,10 +7,10 @@ from rest_framework import status from rest_framework.permissions import IsAuthenticated from common.utils import get_object_or_none -from ..models import Session, Task -from .. import serializers -from terminal.utils import is_session_approver from orgs.utils import tmp_to_root_org +from terminal.models import Session, Task +from terminal import serializers +from terminal.utils import is_session_approver __all__ = ['TaskViewSet', 'KillSessionAPI', 'KillSessionForTicketAPI'] logger = logging.getLogger(__file__) diff --git a/apps/terminal/migrations/0054_auto_20221021_1433.py b/apps/terminal/migrations/0054_auto_20221024_1452.py similarity index 80% rename from apps/terminal/migrations/0054_auto_20221021_1433.py rename to apps/terminal/migrations/0054_auto_20221024_1452.py index 830ef897c..3ee883a8b 100644 --- a/apps/terminal/migrations/0054_auto_20221021_1433.py +++ b/apps/terminal/migrations/0054_auto_20221024_1452.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.14 on 2022-10-21 06:33 +# Generated by Django 3.2.14 on 2022-10-24 06:52 from django.db import migrations, models import django.db.models.deletion @@ -8,6 +8,7 @@ import uuid class Migration(migrations.Migration): dependencies = [ + ('assets', '0110_auto_20221021_1506'), ('terminal', '0053_auto_20220830_1244'), ] @@ -22,10 +23,13 @@ class Migration(migrations.Migration): ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), ('version', models.CharField(max_length=16, verbose_name='Version')), - ('type', models.CharField(choices=[('app', 'App'), ('web', 'Web')], max_length=16, verbose_name='Type')), - ('icon', models.ImageField(upload_to='applet/icon', verbose_name='Icon')), ('author', models.CharField(max_length=128, verbose_name='Author')), + ('type', models.CharField(choices=[('general', 'General'), ('web', 'Web')], default='general', max_length=16, verbose_name='Type')), + ('path', models.FilePathField(verbose_name='Path')), + ('vcs_type', models.CharField(max_length=16, null=True, verbose_name='VCS type')), + ('vcs_url', models.CharField(max_length=256, null=True, verbose_name='URL')), ('protocols', models.JSONField(default=list, verbose_name='Protocol')), + ('tags', models.JSONField(default=list, verbose_name='Tags')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ], options={ @@ -33,14 +37,13 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='AppletProvider', + name='AppletHost', fields=[ ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('account_automation', models.BooleanField(default=False, verbose_name='Account automation')), ('date_synced', models.DateTimeField(blank=True, null=True, verbose_name='Date synced')), @@ -50,22 +53,6 @@ class Migration(migrations.Migration): 'abstract': False, }, ), - migrations.CreateModel( - name='ProviderDeployment', - fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('status', models.CharField(max_length=16, verbose_name='Status')), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='terminal.appletprovider', verbose_name='Provider')), - ], - options={ - 'abstract': False, - }, - ), migrations.CreateModel( name='AppletPublication', fields=[ @@ -77,20 +64,36 @@ class Migration(migrations.Migration): ('status', models.CharField(max_length=16, verbose_name='Status')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('applet', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.applet', verbose_name='Applet')), - ('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.appletprovider', verbose_name='Provider')), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.applethost', verbose_name='Host')), ], options={ - 'unique_together': {('applet', 'provider')}, + 'unique_together': {('applet', 'host')}, + }, + ), + migrations.CreateModel( + name='AppletHostDeployment', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('status', models.CharField(max_length=16, verbose_name='Status')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='terminal.applethost', verbose_name='Hosting')), + ], + options={ + 'abstract': False, }, ), migrations.AddField( - model_name='appletprovider', + model_name='applethost', name='applets', field=models.ManyToManyField(through='terminal.AppletPublication', to='terminal.Applet', verbose_name='Applet'), ), migrations.AddField( - model_name='appletprovider', - name='asset', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='assets.asset', verbose_name='Asset'), + model_name='applethost', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='assets.host', verbose_name='Host'), ), ] diff --git a/apps/terminal/models/applet/__init__.py b/apps/terminal/models/applet/__init__.py index daef278e0..b2a4cac34 100644 --- a/apps/terminal/models/applet/__init__.py +++ b/apps/terminal/models/applet/__init__.py @@ -1,2 +1,2 @@ from .applet import * -from .provider import * +from .host import * diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 4fc16819f..510d8da35 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -1,3 +1,7 @@ +import yaml +import os.path +from rest_framework.exceptions import ValidationError + from django.db import models from django.utils.translation import gettext_lazy as _ @@ -9,25 +13,69 @@ __all__ = ['Applet', 'AppletPublication'] class Applet(JMSBaseModel): class Type(models.TextChoices): - app = 'app', _('App') + general = 'general', _('General') web = 'web', _('Web') + + class VCSType(models.TextChoices): + manual = 'manual', _('Manual') + git = 'git', _('Git') + archive = 'archive', _('Remote gzip') + name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) version = models.CharField(max_length=16, verbose_name=_('Version')) - type = models.CharField(max_length=16, choices=Type.choices, verbose_name=_('Type')) - icon = models.ImageField(upload_to='applet/icon', verbose_name=_('Icon')) author = models.CharField(max_length=128, verbose_name=_('Author')) + path = models.FilePathField(verbose_name=_('Path')) + type = models.CharField(max_length=16, verbose_name=_('Type'), default='general', choices=Type.choices) + vcs_type = models.CharField(max_length=16, verbose_name=_('VCS type'), null=True) + vcs_url = models.CharField(max_length=256, verbose_name=_('URL'), null=True) protocols = models.JSONField(default=list, verbose_name=_('Protocol')) + tags = models.JSONField(default=list, verbose_name=_('Tags')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) def __str__(self): return self.name + @property + def manifest(self): + path = os.path.join(self.path, 'manifest.yml') + if not os.path.exists(path): + return None + with open(path, 'r') as f: + return yaml.safe_load(f) + + @property + def icon(self): + path = os.path.join(self.path, 'icon.png') + if not os.path.exists(path): + return None + with open(path, 'rb') as f: + return f.read() + + @classmethod + def validate_manifest(cls, manifest): + fields = ['name', 'display_name', 'version', 'author', 'type', 'tags', 'protocols'] + for field in fields: + if field not in manifest: + raise ValidationError(f'Missing field {field}') + if manifest['type'] not in [i[0] for i in cls.Type.choices]: + raise ValidationError('Invalid type') + if not isinstance(manifest['protocols'], list): + raise ValidationError('Invalid protocols') + + @classmethod + def create_by_manifest(cls, manifest): + obj = cls() + for k, v in manifest.items(): + setattr(obj, k, v) + obj.save() + return obj + class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.PROTECT, verbose_name=_('Applet')) - provider = models.ForeignKey('AppletProvider', on_delete=models.PROTECT, verbose_name=_('Provider')) + host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, verbose_name=_('Host')) status = models.CharField(max_length=16, verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) class Meta: - unique_together = ('applet', 'provider') + unique_together = ('applet', 'host') diff --git a/apps/terminal/models/applet/provider.py b/apps/terminal/models/applet/host.py similarity index 57% rename from apps/terminal/models/applet/provider.py rename to apps/terminal/models/applet/host.py index dac90850d..89b0bd576 100644 --- a/apps/terminal/models/applet/provider.py +++ b/apps/terminal/models/applet/host.py @@ -1,31 +1,34 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from celery import current_app - from common.db.models import JMSBaseModel -__all__ = ['AppletProvider', 'ProviderDeployment'] +__all__ = ['AppletHost', 'AppletHostDeployment'] -class AppletProvider(JMSBaseModel): - name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) - asset = models.ForeignKey('assets.Asset', on_delete=models.PROTECT, verbose_name=_('Asset')) +class AppletHost(JMSBaseModel): + host = models.ForeignKey('assets.Host', on_delete=models.PROTECT, verbose_name=_('Host')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) status = models.CharField(max_length=16, verbose_name=_('Status')) applets = models.ManyToManyField( 'Applet', verbose_name=_('Applet'), - through='AppletPublication', through_fields=('provider', 'applet'), + through='AppletPublication', through_fields=('host', 'applet'), ) + def __str__(self): + return self.host.name -class ProviderDeployment(JMSBaseModel): - provider = models.ForeignKey('AppletProvider', on_delete=models.CASCADE, verbose_name=_('Provider')) + +class AppletHostDeployment(JMSBaseModel): + host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting')) status = models.CharField(max_length=16, verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - def install(self): + def __str__(self): + return self.host + + def start(self): pass diff --git a/apps/terminal/serializers/__init__.py b/apps/terminal/serializers/__init__.py index e1312ebae..c14d4fba3 100644 --- a/apps/terminal/serializers/__init__.py +++ b/apps/terminal/serializers/__init__.py @@ -5,3 +5,4 @@ from .session import * from .storage import * from .sharing import * from .endpoint import * +from .applet import * diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py new file mode 100644 index 000000000..3f3919776 --- /dev/null +++ b/apps/terminal/serializers/applet.py @@ -0,0 +1,65 @@ +from rest_framework import serializers + +from common.drf.fields import ObjectRelatedField +from assets.models import Host +from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment + + +__all__ = [ + 'AppletSerializer', 'AppletPublicationSerializer', + 'AppletHostSerializer', 'AppletHostDeploymentSerializer', + 'AppletUploadSerializer' +] + + +class AppletSerializer(serializers.ModelSerializer): + class Meta: + model = Applet + fields_mini = ['id', 'name'] + read_only_fields = [ + 'date_created', 'date_updated' + ] + fields = fields_mini + [ + 'version', 'author', 'type', 'protocols', 'comment' + ] + read_only_fields + + +class AppletUploadSerializer(serializers.Serializer): + file = serializers.FileField() + + +class AppletPublicationSerializer(serializers.ModelSerializer): + applet = ObjectRelatedField(queryset=Applet.objects.all()) + host = ObjectRelatedField(queryset=AppletHost.objects.all()) + + class Meta: + model = AppletPublication + fields_mini = ['id', 'applet', 'host'] + read_only_fields = ['date_created', 'date_updated'] + fields = fields_mini + [ + 'status', 'comment', + ] + read_only_fields + + +class AppletHostSerializer(serializers.ModelSerializer): + host = ObjectRelatedField(queryset=Host.objects.all()) + + class Meta: + model = AppletHost + fields_mini = ['id', 'host'] + read_only_fields = ['date_created', 'date_updated'] + fields = fields_mini + [ + 'comment', 'account_automation', 'date_synced', 'status', + ] + read_only_fields + + +class AppletHostDeploymentSerializer(serializers.ModelSerializer): + host = ObjectRelatedField(queryset=AppletHost.objects.all()) + + class Meta: + model = AppletHostDeployment + fields_mini = ['id', 'host'] + read_only_fields = ['date_created', 'date_updated'] + fields = fields_mini + [ + 'status', 'comment', + ] + read_only_fields diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 3f0445350..8bed4f604 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -24,6 +24,11 @@ router.register(r'session-sharings', api.SessionSharingViewSet, 'session-sharing router.register(r'session-join-records', api.SessionJoinRecordsViewSet, 'session-sharing-record') router.register(r'endpoints', api.EndpointViewSet, 'endpoint') router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') +router.register(r'applets', api.AppletViewSet, 'applet') +router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') +router.register(r'applet-publication', api.AppletPublicationViewSet, 'applet-publication') +router.register(r'applet-host-deployment', api.AppletHostDeploymentViewSet, 'applet-host-deployment') + urlpatterns = [ path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'), From d95ced51090c7629ed45f57c5e74f94169a889eb Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 25 Oct 2022 14:26:56 +0800 Subject: [PATCH 220/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dchange=20accou?= =?UTF-8?q?nt=20perm=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/gather_facts/host/posix/main.yml | 2 +- apps/rbac/const.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/assets/automations/gather_facts/host/posix/main.yml b/apps/assets/automations/gather_facts/host/posix/main.yml index 6e900fccb..f42635458 100644 --- a/apps/assets/automations/gather_facts/host/posix/main.yml +++ b/apps/assets/automations/gather_facts/host/posix/main.yml @@ -2,7 +2,7 @@ gather_facts: yes tasks: - name: Get info - set_fact: + ansible.builtin.set_fact: info: arch: "{{ ansible_architecture }}" distribution: "{{ ansible_distribution }}" diff --git a/apps/rbac/const.py b/apps/rbac/const.py index 037358be0..71c8ea26d 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -39,7 +39,6 @@ exclude_permissions = ( ('assets', 'assetuser', '*', '*'), ('assets', 'gathereduser', 'add,delete,change', 'gathereduser'), ('assets', 'accountbackupplanexecution', 'delete,change', 'accountbackupplanexecution'), - ('assets', 'account', 'change', 'account'), # TODO 暂时去掉历史账号的权限 ('assets', 'account', '*', 'assethistoryaccount'), ('assets', 'account', '*', 'assethistoryaccountsecret'), From a445e47f3dcbbd9ceed0411c3567799ce33fdb4c Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 25 Oct 2022 15:07:51 +0800 Subject: [PATCH 221/488] perf: account add platform_id --- apps/assets/serializers/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 5ef2bfde3..c47a697c2 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -54,7 +54,7 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): asset = ObjectRelatedField( required=False, queryset=Asset.objects, - label=_('Asset'), attrs=('id', 'name', 'address') + label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id') ) class Meta(BaseAccountSerializer.Meta): From 4dd4c29e12311d663347f2b41c9a768c0a092ea8 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 25 Oct 2022 18:43:34 +0800 Subject: [PATCH 222/488] perf: gather facts --- .../gather_facts/database/mysql/main.yml | 14 +- .../database/postgresql/facts.json | 4574 ----------------- .../gather_facts/database/postgresql/main.yml | 22 +- .../gather_facts/database/sqlserver/main.yml | 10 - .../database/sqlserver/manifest.yml | 8 - .../roles/change_password/tasks/main.yml | 27 - .../automations/gather_facts/manager.py | 9 +- apps/assets/models/automations/base.py | 7 +- .../models/automations/change_secret.py | 8 - .../assets/models/automations/gather_facts.py | 9 +- apps/assets/tasks/automation.py | 7 +- apps/ops/mixin.py | 2 - 12 files changed, 28 insertions(+), 4669 deletions(-) delete mode 100644 apps/assets/automations/gather_facts/database/postgresql/facts.json delete mode 100644 apps/assets/automations/gather_facts/database/sqlserver/main.yml delete mode 100644 apps/assets/automations/gather_facts/database/sqlserver/manifest.yml delete mode 100644 apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml diff --git a/apps/assets/automations/gather_facts/database/mysql/main.yml b/apps/assets/automations/gather_facts/database/mysql/main.yml index e7ba00880..c4e90835e 100644 --- a/apps/assets/automations/gather_facts/database/mysql/main.yml +++ b/apps/assets/automations/gather_facts/database/mysql/main.yml @@ -2,27 +2,21 @@ gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python - jms_account: - username: root - secret: redhat - jms_asset: - address: 127.0.0.1 - port: 3306 tasks: - - name: Gather facts info + - name: Get info community.mysql.mysql_info: login_user: "{{ jms_account.username }}" login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" + filter: version register: db_info - - name: Get info + - name: Define Mysql info by set_fact set_fact: info: version: "{{ db_info.version.full }}" - debug: - var: db_info - + var: info diff --git a/apps/assets/automations/gather_facts/database/postgresql/facts.json b/apps/assets/automations/gather_facts/database/postgresql/facts.json deleted file mode 100644 index ca08cb03c..000000000 --- a/apps/assets/automations/gather_facts/database/postgresql/facts.json +++ /dev/null @@ -1,4574 +0,0 @@ -{ - "db_info": { - "changed": false, - "databases": { - "exampledb": { - "access_priv": "", - "collate": "en_US.utf8", - "ctype": "en_US.utf8", - "encoding": "UTF8", - "extensions": { - "plpgsql": { - "description": "PL/pgSQL procedural language", - "extversion": { - "major": 1, - "minor": 0, - "raw": "1.0" - }, - "nspname": "pg_catalog" - } - }, - "languages": { - "c": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "internal": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "plpgsql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "sql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "namespaces": { - "information_schema": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_catalog": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_toast": { - "nspacl": "", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "public": { - "nspacl": "{********=UC/********,=UC/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "owner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", - "publications": {}, - "size": "8758051", - "subscriptions": {} - }, - "exampledb1": { - "access_priv": "", - "collate": "en_US.utf8", - "ctype": "en_US.utf8", - "encoding": "UTF8", - "extensions": { - "plpgsql": { - "description": "PL/pgSQL procedural language", - "extversion": { - "major": 1, - "minor": 0, - "raw": "1.0" - }, - "nspname": "pg_catalog" - } - }, - "languages": { - "c": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "internal": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "plpgsql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "sql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "namespaces": { - "information_schema": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_catalog": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_toast": { - "nspacl": "", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "public": { - "nspacl": "{********=UC/********,=UC/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "owner": "web1", - "publications": {}, - "size": "8758051", - "subscriptions": {} - }, - "postgre": { - "access_priv": "", - "collate": "en_US.utf8", - "ctype": "en_US.utf8", - "encoding": "UTF8", - "extensions": { - "plpgsql": { - "description": "PL/pgSQL procedural language", - "extversion": { - "major": 1, - "minor": 0, - "raw": "1.0" - }, - "nspname": "pg_catalog" - } - }, - "languages": { - "c": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "internal": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "plpgsql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "sql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "namespaces": { - "information_schema": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_catalog": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_toast": { - "nspacl": "", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "public": { - "nspacl": "{********=UC/********,=UC/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "owner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", - "publications": {}, - "size": "8758051", - "subscriptions": {} - }, - "postgres": { - "access_priv": "", - "collate": "en_US.utf8", - "ctype": "en_US.utf8", - "encoding": "UTF8", - "extensions": { - "plpgsql": { - "description": "PL/pgSQL procedural language", - "extversion": { - "major": 1, - "minor": 0, - "raw": "1.0" - }, - "nspname": "pg_catalog" - } - }, - "languages": { - "c": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "internal": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "plpgsql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "sql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "namespaces": { - "information_schema": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_catalog": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_toast": { - "nspacl": "", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "public": { - "nspacl": "{********=UC/********,=UC/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "owner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", - "publications": {}, - "size": "8758051", - "subscriptions": {} - }, - "template1": { - "access_priv": "=c/********\n********=CTc/********", - "collate": "en_US.utf8", - "ctype": "en_US.utf8", - "encoding": "UTF8", - "extensions": { - "plpgsql": { - "description": "PL/pgSQL procedural language", - "extversion": { - "major": 1, - "minor": 0, - "raw": "1.0" - }, - "nspname": "pg_catalog" - } - }, - "languages": { - "c": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "internal": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "plpgsql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "sql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "namespaces": { - "information_schema": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_catalog": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_toast": { - "nspacl": "", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "public": { - "nspacl": "{********=UC/********,=UC/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "owner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", - "publications": {}, - "size": "8758051", - "subscriptions": {} - }, - "testdb": { - "access_priv": "=Tc/test\ntest=CTc/test", - "collate": "en_US.utf8", - "ctype": "en_US.utf8", - "encoding": "UTF8", - "extensions": { - "plpgsql": { - "description": "PL/pgSQL procedural language", - "extversion": { - "major": 1, - "minor": 0, - "raw": "1.0" - }, - "nspname": "pg_catalog" - } - }, - "languages": { - "c": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "internal": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "plpgsql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "sql": { - "lanacl": "", - "lanowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "namespaces": { - "information_schema": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_catalog": { - "nspacl": "{********=UC/********,=U/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_toast": { - "nspacl": "", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "public": { - "nspacl": "{********=UC/********,=UC/********}", - "nspowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "owner": "test", - "publications": {}, - "size": "8758051", - "subscriptions": {} - } - }, - "failed": false, - "in_recovery": false, - "pending_restart_settings": [], - "repl_slots": {}, - "replications": {}, - "roles": { - "postgre": { - "canlogin": true, - "member_of": [], - "superuser": true, - "valid_until": "" - }, - "test": { - "canlogin": true, - "member_of": [], - "superuser": false, - "valid_until": "" - }, - "web1": { - "canlogin": true, - "member_of": [], - "superuser": false, - "valid_until": "" - } - }, - "settings": { - "DateStyle": { - "boot_val": "ISO, MDY", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "ISO, MDY", - "setting": "ISO, MDY", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "IntervalStyle": { - "boot_val": "********s", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "********s", - "setting": "********s", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "TimeZone": { - "boot_val": "GMT", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "Etc/UTC", - "setting": "Etc/UTC", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "allow_in_place_tablespaces": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "allow_system_table_mods": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "application_name": { - "boot_val": "", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "archive_cleanup_command": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "archive_command": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "(disabled)", - "setting": "(disabled)", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "archive_mode": { - "boot_val": "off", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "archive_timeout": { - "boot_val": "0", - "context": "sighup", - "max_val": "1073741823", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "array_nulls": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "authentication_timeout": { - "boot_val": "60", - "context": "sighup", - "max_val": "600", - "min_val": "1", - "pending_restart": false, - "pretty_val": "1min", - "setting": "60", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "autovacuum": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "autovacuum_analyze_scale_factor": { - "boot_val": "0.1", - "context": "sighup", - "max_val": "100", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.1", - "setting": "0.1", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "autovacuum_analyze_threshold": { - "boot_val": "50", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "50", - "setting": "50", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "autovacuum_freeze_max_age": { - "boot_val": "200000000", - "context": "postmaster", - "max_val": "2000000000", - "min_val": "100000", - "pending_restart": false, - "pretty_val": "200000000", - "setting": "200000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "autovacuum_max_workers": { - "boot_val": "3", - "context": "postmaster", - "max_val": "262143", - "min_val": "1", - "pending_restart": false, - "pretty_val": "3", - "setting": "3", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "autovacuum_multixact_freeze_max_age": { - "boot_val": "400000000", - "context": "postmaster", - "max_val": "2000000000", - "min_val": "10000", - "pending_restart": false, - "pretty_val": "400000000", - "setting": "400000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "autovacuum_naptime": { - "boot_val": "60", - "context": "sighup", - "max_val": "2147483", - "min_val": "1", - "pending_restart": false, - "pretty_val": "1min", - "setting": "60", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "autovacuum_vacuum_cost_delay": { - "boot_val": "2", - "context": "sighup", - "max_val": "100", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "2ms", - "setting": "2", - "sourcefile": "", - "unit": "ms", - "vartype": "real" - }, - "autovacuum_vacuum_cost_limit": { - "boot_val": "-1", - "context": "sighup", - "max_val": "10000", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "autovacuum_vacuum_insert_scale_factor": { - "boot_val": "0.2", - "context": "sighup", - "max_val": "100", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.2", - "setting": "0.2", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "autovacuum_vacuum_insert_threshold": { - "boot_val": "1000", - "context": "sighup", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "1000", - "setting": "1000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "autovacuum_vacuum_scale_factor": { - "boot_val": "0.2", - "context": "sighup", - "max_val": "100", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.2", - "setting": "0.2", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "autovacuum_vacuum_threshold": { - "boot_val": "50", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "50", - "setting": "50", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "autovacuum_work_mem": { - "boot_val": "-1", - "context": "sighup", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "backend_flush_after": { - "boot_val": "0", - "context": "user", - "max_val": "256", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "backslash_quote": { - "boot_val": "safe_encoding", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "safe_encoding", - "setting": "safe_encoding", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "backtrace_functions": { - "boot_val": "", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "bgwriter_delay": { - "boot_val": "200", - "context": "sighup", - "max_val": "10000", - "min_val": "10", - "pending_restart": false, - "pretty_val": "200ms", - "setting": "200", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "bgwriter_flush_after": { - "boot_val": "64", - "context": "sighup", - "max_val": "256", - "min_val": "0", - "pending_restart": false, - "pretty_val": "512kB", - "setting": "64", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 524288, - "vartype": "integer" - }, - "bgwriter_lru_maxpages": { - "boot_val": "100", - "context": "sighup", - "max_val": "1073741823", - "min_val": "0", - "pending_restart": false, - "pretty_val": "100", - "setting": "100", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "bgwriter_lru_multiplier": { - "boot_val": "2", - "context": "sighup", - "max_val": "10", - "min_val": "0", - "pending_restart": false, - "pretty_val": "2", - "setting": "2", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "block_size": { - "boot_val": "8192", - "context": "internal", - "max_val": "8192", - "min_val": "8192", - "pending_restart": false, - "pretty_val": "8192", - "setting": "8192", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "bonjour": { - "boot_val": "off", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "bonjour_name": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "bytea_output": { - "boot_val": "hex", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "hex", - "setting": "hex", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "check_function_bodies": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "checkpoint_completion_target": { - "boot_val": "0.9", - "context": "sighup", - "max_val": "1", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.9", - "setting": "0.9", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "checkpoint_flush_after": { - "boot_val": "32", - "context": "sighup", - "max_val": "256", - "min_val": "0", - "pending_restart": false, - "pretty_val": "256kB", - "setting": "32", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 262144, - "vartype": "integer" - }, - "checkpoint_timeout": { - "boot_val": "300", - "context": "sighup", - "max_val": "86400", - "min_val": "30", - "pending_restart": false, - "pretty_val": "5min", - "setting": "300", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "checkpoint_warning": { - "boot_val": "30", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "30s", - "setting": "30", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "client_connection_check_interval": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "client_encoding": { - "boot_val": "SQL_ASCII", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "UTF8", - "setting": "UTF8", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "client_min_messages": { - "boot_val": "notice", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "notice", - "setting": "notice", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "cluster_name": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "commit_delay": { - "boot_val": "0", - "context": "superuser", - "max_val": "100000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "commit_siblings": { - "boot_val": "5", - "context": "user", - "max_val": "1000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "5", - "setting": "5", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "compute_query_id": { - "boot_val": "auto", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "auto", - "setting": "auto", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "config_file": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "/var/lib/********sql/data/********sql.conf", - "setting": "/var/lib/********sql/data/********sql.conf", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "constraint_exclusion": { - "boot_val": "partition", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "partition", - "setting": "partition", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "cpu_index_tuple_cost": { - "boot_val": "0.005", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.005", - "setting": "0.005", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "cpu_operator_cost": { - "boot_val": "0.0025", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.0025", - "setting": "0.0025", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "cpu_tuple_cost": { - "boot_val": "0.01", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.01", - "setting": "0.01", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "cursor_tuple_fraction": { - "boot_val": "0.1", - "context": "user", - "max_val": "1", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.1", - "setting": "0.1", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "data_checksums": { - "boot_val": "off", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "data_directory": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "/var/lib/********sql/data", - "setting": "/var/lib/********sql/data", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "data_directory_mode": { - "boot_val": "448", - "context": "internal", - "max_val": "511", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0700", - "setting": "0700", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "data_sync_retry": { - "boot_val": "off", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "db_user_namespace": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "deadlock_timeout": { - "boot_val": "1000", - "context": "superuser", - "max_val": "2147483647", - "min_val": "1", - "pending_restart": false, - "pretty_val": "1s", - "setting": "1000", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "debug_assertions": { - "boot_val": "off", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "debug_discard_caches": { - "boot_val": "0", - "context": "superuser", - "max_val": "0", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "debug_pretty_print": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "debug_print_parse": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "debug_print_plan": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "debug_print_rewritten": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "default_statistics_target": { - "boot_val": "100", - "context": "user", - "max_val": "10000", - "min_val": "1", - "pending_restart": false, - "pretty_val": "100", - "setting": "100", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "default_table_access_method": { - "boot_val": "heap", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "heap", - "setting": "heap", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "default_tablespace": { - "boot_val": "", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "default_text_search_config": { - "boot_val": "pg_catalog.simple", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "pg_catalog.english", - "setting": "pg_catalog.english", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "default_toast_compression": { - "boot_val": "pglz", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "pglz", - "setting": "pglz", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "default_transaction_deferrable": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "default_transaction_isolation": { - "boot_val": "read committed", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "read committed", - "setting": "read committed", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "default_transaction_read_only": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "dynamic_library_path": { - "boot_val": "$libdir", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "$libdir", - "setting": "$libdir", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "dynamic_shared_memory_type": { - "boot_val": "posix", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "posix", - "setting": "posix", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "enum" - }, - "effective_cache_size": { - "boot_val": "524288", - "context": "user", - "max_val": "2147483647", - "min_val": "1", - "pending_restart": false, - "pretty_val": "4GB", - "setting": "524288", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 4294967296, - "vartype": "integer" - }, - "effective_io_concurrency": { - "boot_val": "1", - "context": "user", - "max_val": "1000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1", - "setting": "1", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "enable_async_append": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_bitmapscan": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_gathermerge": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_hashagg": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_hashjoin": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_incremental_sort": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_indexonlyscan": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_indexscan": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_material": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_memoize": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_mergejoin": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_nestloop": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_parallel_append": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_parallel_hash": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_partition_pruning": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_partitionwise_aggregate": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_partitionwise_join": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_seqscan": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_sort": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "enable_tidscan": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "escape_string_warning": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "event_source": { - "boot_val": "PostgreSQL", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "PostgreSQL", - "setting": "PostgreSQL", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "exit_on_error": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "extension_destdir": { - "boot_val": "", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "external_pid_file": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "extra_float_digits": { - "boot_val": "1", - "context": "user", - "max_val": "3", - "min_val": "-15", - "pending_restart": false, - "pretty_val": "1", - "setting": "1", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "force_parallel_mode": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "from_collapse_limit": { - "boot_val": "8", - "context": "user", - "max_val": "2147483647", - "min_val": "1", - "pending_restart": false, - "pretty_val": "8", - "setting": "8", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "fsync": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "full_page_writes": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "geqo": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "geqo_effort": { - "boot_val": "5", - "context": "user", - "max_val": "10", - "min_val": "1", - "pending_restart": false, - "pretty_val": "5", - "setting": "5", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "geqo_generations": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "geqo_pool_size": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "geqo_seed": { - "boot_val": "0", - "context": "user", - "max_val": "1", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "geqo_selection_bias": { - "boot_val": "2", - "context": "user", - "max_val": "2", - "min_val": "1.5", - "pending_restart": false, - "pretty_val": "2", - "setting": "2", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "geqo_threshold": { - "boot_val": "12", - "context": "user", - "max_val": "2147483647", - "min_val": "2", - "pending_restart": false, - "pretty_val": "12", - "setting": "12", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "gin_fuzzy_search_limit": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "gin_pending_list_limit": { - "boot_val": "4096", - "context": "user", - "max_val": "2147483647", - "min_val": "64", - "pending_restart": false, - "pretty_val": "4MB", - "setting": "4096", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 4194304, - "vartype": "integer" - }, - "hash_mem_multiplier": { - "boot_val": "1", - "context": "user", - "max_val": "1000", - "min_val": "1", - "pending_restart": false, - "pretty_val": "1", - "setting": "1", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "hba_file": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "/var/lib/********sql/data/pg_hba.conf", - "setting": "/var/lib/********sql/data/pg_hba.conf", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "hot_standby": { - "boot_val": "on", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "hot_standby_feedback": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "huge_page_size": { - "boot_val": "0", - "context": "postmaster", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "huge_pages": { - "boot_val": "try", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "try", - "setting": "try", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "ident_file": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "/var/lib/********sql/data/pg_ident.conf", - "setting": "/var/lib/********sql/data/pg_ident.conf", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "idle_in_transaction_session_timeout": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "idle_session_timeout": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "ignore_checksum_failure": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "ignore_invalid_pages": { - "boot_val": "off", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "ignore_system_indexes": { - "boot_val": "off", - "context": "backend", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "in_hot_standby": { - "boot_val": "off", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "integer_datetimes": { - "boot_val": "on", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "jit": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "jit_above_cost": { - "boot_val": "100000", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "100000", - "setting": "100000", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "jit_debugging_support": { - "boot_val": "off", - "context": "superuser-backend", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "jit_dump_bitcode": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "jit_expressions": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "jit_inline_above_cost": { - "boot_val": "500000", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "500000", - "setting": "500000", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "jit_optimize_above_cost": { - "boot_val": "500000", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "500000", - "setting": "500000", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "jit_profiling_support": { - "boot_val": "off", - "context": "superuser-backend", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "jit_provider": { - "boot_val": "llvmjit", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "llvmjit", - "setting": "llvmjit", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "jit_tuple_deforming": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "join_collapse_limit": { - "boot_val": "8", - "context": "user", - "max_val": "2147483647", - "min_val": "1", - "pending_restart": false, - "pretty_val": "8", - "setting": "8", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "krb_caseins_users": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "krb_server_keyfile": { - "boot_val": "FILE:/etc/********sql-common/krb5.keytab", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "FILE:/etc/********sql-common/krb5.keytab", - "setting": "FILE:/etc/********sql-common/krb5.keytab", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "lc_collate": { - "boot_val": "C", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "en_US.utf8", - "setting": "en_US.utf8", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "lc_ctype": { - "boot_val": "C", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "en_US.utf8", - "setting": "en_US.utf8", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "lc_messages": { - "boot_val": "", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "en_US.utf8", - "setting": "en_US.utf8", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "lc_monetary": { - "boot_val": "C", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "en_US.utf8", - "setting": "en_US.utf8", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "lc_numeric": { - "boot_val": "C", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "en_US.utf8", - "setting": "en_US.utf8", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "lc_time": { - "boot_val": "C", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "en_US.utf8", - "setting": "en_US.utf8", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "listen_addresses": { - "boot_val": "localhost", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "*", - "setting": "*", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "lo_compat_privileges": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "local_preload_libraries": { - "boot_val": "", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "lock_timeout": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "log_autovacuum_min_duration": { - "boot_val": "-1", - "context": "sighup", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "log_checkpoints": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_connections": { - "boot_val": "off", - "context": "superuser-backend", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_destination": { - "boot_val": "stderr", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "stderr", - "setting": "stderr", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "log_directory": { - "boot_val": "log", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "log", - "setting": "log", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "log_disconnections": { - "boot_val": "off", - "context": "superuser-backend", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_duration": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_error_verbosity": { - "boot_val": "default", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "default", - "setting": "default", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "log_executor_stats": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_file_mode": { - "boot_val": "384", - "context": "sighup", - "max_val": "511", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0600", - "setting": "0600", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "log_filename": { - "boot_val": "********sql-%Y-%m-%d_%H%M%S.log", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "********sql-%Y-%m-%d_%H%M%S.log", - "setting": "********sql-%Y-%m-%d_%H%M%S.log", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "log_hostname": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_line_prefix": { - "boot_val": "%m [%p] ", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "%m [%p] ", - "setting": "%m [%p] ", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "log_lock_waits": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_min_duration_sample": { - "boot_val": "-1", - "context": "superuser", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "log_min_duration_statement": { - "boot_val": "-1", - "context": "superuser", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "log_min_error_statement": { - "boot_val": "error", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "error", - "setting": "error", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "log_min_messages": { - "boot_val": "warning", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "warning", - "setting": "warning", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "log_parameter_max_length": { - "boot_val": "-1", - "context": "superuser", - "max_val": "1073741823", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "B", - "vartype": "integer" - }, - "log_parameter_max_length_on_error": { - "boot_val": "0", - "context": "user", - "max_val": "1073741823", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "B", - "vartype": "integer" - }, - "log_parser_stats": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_planner_stats": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_recovery_conflict_waits": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_replication_commands": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_rotation_age": { - "boot_val": "1440", - "context": "sighup", - "max_val": "35791394", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1d", - "setting": "1440", - "sourcefile": "", - "unit": "min", - "vartype": "integer" - }, - "log_rotation_size": { - "boot_val": "10240", - "context": "sighup", - "max_val": "2097151", - "min_val": "0", - "pending_restart": false, - "pretty_val": "10MB", - "setting": "10240", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 10485760, - "vartype": "integer" - }, - "log_statement": { - "boot_val": "none", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "none", - "setting": "none", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "log_statement_sample_rate": { - "boot_val": "1", - "context": "superuser", - "max_val": "1", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1", - "setting": "1", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "log_statement_stats": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "log_temp_files": { - "boot_val": "-1", - "context": "superuser", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "log_timezone": { - "boot_val": "GMT", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "Etc/UTC", - "setting": "Etc/UTC", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "string" - }, - "log_transaction_sample_rate": { - "boot_val": "0", - "context": "superuser", - "max_val": "1", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "log_truncate_on_rotation": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "logging_collector": { - "boot_val": "off", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "logical_decoding_work_mem": { - "boot_val": "65536", - "context": "user", - "max_val": "2147483647", - "min_val": "64", - "pending_restart": false, - "pretty_val": "64MB", - "setting": "65536", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 67108864, - "vartype": "integer" - }, - "maintenance_io_concurrency": { - "boot_val": "10", - "context": "user", - "max_val": "1000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "10", - "setting": "10", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "maintenance_work_mem": { - "boot_val": "65536", - "context": "user", - "max_val": "2147483647", - "min_val": "1024", - "pending_restart": false, - "pretty_val": "64MB", - "setting": "65536", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 67108864, - "vartype": "integer" - }, - "max_connections": { - "boot_val": "100", - "context": "postmaster", - "max_val": "262143", - "min_val": "1", - "pending_restart": false, - "pretty_val": "100", - "setting": "100", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "", - "vartype": "integer" - }, - "max_files_per_process": { - "boot_val": "1000", - "context": "postmaster", - "max_val": "2147483647", - "min_val": "64", - "pending_restart": false, - "pretty_val": "1000", - "setting": "1000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_function_args": { - "boot_val": "100", - "context": "internal", - "max_val": "100", - "min_val": "100", - "pending_restart": false, - "pretty_val": "100", - "setting": "100", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_identifier_length": { - "boot_val": "63", - "context": "internal", - "max_val": "63", - "min_val": "63", - "pending_restart": false, - "pretty_val": "63", - "setting": "63", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_index_keys": { - "boot_val": "32", - "context": "internal", - "max_val": "32", - "min_val": "32", - "pending_restart": false, - "pretty_val": "32", - "setting": "32", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_locks_per_transaction": { - "boot_val": "64", - "context": "postmaster", - "max_val": "2147483647", - "min_val": "10", - "pending_restart": false, - "pretty_val": "64", - "setting": "64", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_logical_replication_workers": { - "boot_val": "4", - "context": "postmaster", - "max_val": "262143", - "min_val": "0", - "pending_restart": false, - "pretty_val": "4", - "setting": "4", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_parallel_maintenance_workers": { - "boot_val": "2", - "context": "user", - "max_val": "1024", - "min_val": "0", - "pending_restart": false, - "pretty_val": "2", - "setting": "2", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_parallel_workers": { - "boot_val": "8", - "context": "user", - "max_val": "1024", - "min_val": "0", - "pending_restart": false, - "pretty_val": "8", - "setting": "8", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_parallel_workers_per_gather": { - "boot_val": "2", - "context": "user", - "max_val": "1024", - "min_val": "0", - "pending_restart": false, - "pretty_val": "2", - "setting": "2", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_pred_locks_per_page": { - "boot_val": "2", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "2", - "setting": "2", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_pred_locks_per_relation": { - "boot_val": "-2", - "context": "sighup", - "max_val": "2147483647", - "min_val": "-2147483648", - "pending_restart": false, - "pretty_val": "-2", - "setting": "-2", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_pred_locks_per_transaction": { - "boot_val": "64", - "context": "postmaster", - "max_val": "2147483647", - "min_val": "10", - "pending_restart": false, - "pretty_val": "64", - "setting": "64", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_prepared_transactions": { - "boot_val": "0", - "context": "postmaster", - "max_val": "262143", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_replication_slots": { - "boot_val": "10", - "context": "postmaster", - "max_val": "262143", - "min_val": "0", - "pending_restart": false, - "pretty_val": "10", - "setting": "10", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_slot_wal_keep_size": { - "boot_val": "-1", - "context": "sighup", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "MB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "max_stack_depth": { - "boot_val": "100", - "context": "superuser", - "max_val": "2147483647", - "min_val": "100", - "pending_restart": false, - "pretty_val": "2MB", - "setting": "2048", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 2097152, - "vartype": "integer" - }, - "max_standby_archive_delay": { - "boot_val": "30000", - "context": "sighup", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "30s", - "setting": "30000", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "max_standby_streaming_delay": { - "boot_val": "30000", - "context": "sighup", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "30s", - "setting": "30000", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "max_sync_workers_per_subscription": { - "boot_val": "2", - "context": "sighup", - "max_val": "262143", - "min_val": "0", - "pending_restart": false, - "pretty_val": "2", - "setting": "2", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_wal_senders": { - "boot_val": "10", - "context": "postmaster", - "max_val": "262143", - "min_val": "0", - "pending_restart": false, - "pretty_val": "10", - "setting": "10", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "max_wal_size": { - "boot_val": "1024", - "context": "sighup", - "max_val": "2147483647", - "min_val": "2", - "pending_restart": false, - "pretty_val": "1GB", - "setting": "1024", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "MB", - "val_in_bytes": 1073741824, - "vartype": "integer" - }, - "max_worker_processes": { - "boot_val": "8", - "context": "postmaster", - "max_val": "262143", - "min_val": "0", - "pending_restart": false, - "pretty_val": "8", - "setting": "8", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "min_dynamic_shared_memory": { - "boot_val": "0", - "context": "postmaster", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "MB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "min_parallel_index_scan_size": { - "boot_val": "64", - "context": "user", - "max_val": "715827882", - "min_val": "0", - "pending_restart": false, - "pretty_val": "512kB", - "setting": "64", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 524288, - "vartype": "integer" - }, - "min_parallel_table_scan_size": { - "boot_val": "1024", - "context": "user", - "max_val": "715827882", - "min_val": "0", - "pending_restart": false, - "pretty_val": "8MB", - "setting": "1024", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 8388608, - "vartype": "integer" - }, - "min_wal_size": { - "boot_val": "80", - "context": "sighup", - "max_val": "2147483647", - "min_val": "2", - "pending_restart": false, - "pretty_val": "80MB", - "setting": "80", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "MB", - "val_in_bytes": 83886080, - "vartype": "integer" - }, - "old_snapshot_threshold": { - "boot_val": "-1", - "context": "postmaster", - "max_val": "86400", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "min", - "vartype": "integer" - }, - "parallel_leader_participation": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "parallel_setup_cost": { - "boot_val": "1000", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1000", - "setting": "1000", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "parallel_tuple_cost": { - "boot_val": "0.1", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0.1", - "setting": "0.1", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "password_encryption": { - "boot_val": "scram-sha-256", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "scram-sha-256", - "setting": "scram-sha-256", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "plan_cache_mode": { - "boot_val": "auto", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "auto", - "setting": "auto", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "port": { - "boot_val": "5432", - "context": "postmaster", - "max_val": "65535", - "min_val": "1", - "pending_restart": false, - "pretty_val": "5432", - "setting": "5432", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "post_auth_delay": { - "boot_val": "0", - "context": "backend", - "max_val": "2147", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "pre_auth_delay": { - "boot_val": "0", - "context": "sighup", - "max_val": "60", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "primary_conninfo": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "primary_slot_name": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "promote_trigger_file": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "quote_all_identifiers": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "random_page_cost": { - "boot_val": "4", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "0", - "pending_restart": false, - "pretty_val": "4", - "setting": "4", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "recovery_end_command": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "recovery_init_sync_method": { - "boot_val": "fsync", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "fsync", - "setting": "fsync", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "recovery_min_apply_delay": { - "boot_val": "0", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "recovery_target": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "recovery_target_action": { - "boot_val": "pause", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "pause", - "setting": "pause", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "recovery_target_inclusive": { - "boot_val": "on", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "recovery_target_lsn": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "recovery_target_name": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "recovery_target_time": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "recovery_target_timeline": { - "boot_val": "latest", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "latest", - "setting": "latest", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "recovery_target_xid": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "remove_temp_files_after_crash": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "restart_after_crash": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "restore_command": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "row_security": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "search_path": { - "boot_val": "\"$user\", public", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "\"$user\", public", - "setting": "\"$user\", public", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "segment_size": { - "boot_val": "131072", - "context": "internal", - "max_val": "131072", - "min_val": "131072", - "pending_restart": false, - "pretty_val": "1GB", - "setting": "131072", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 1073741824, - "vartype": "integer" - }, - "seq_page_cost": { - "boot_val": "1", - "context": "user", - "max_val": "1.79769e+308", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1", - "setting": "1", - "sourcefile": "", - "unit": "", - "vartype": "real" - }, - "server_encoding": { - "boot_val": "SQL_ASCII", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "UTF8", - "setting": "UTF8", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "server_version": { - "boot_val": "14.5 (Debian 14.5-1.pgdg110+1)", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "14.5 (Debian 14.5-1.pgdg110+1)", - "setting": "14.5 (Debian 14.5-1.pgdg110+1)", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "server_version_num": { - "boot_val": "140005", - "context": "internal", - "max_val": "140005", - "min_val": "140005", - "pending_restart": false, - "pretty_val": "140005", - "setting": "140005", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "session_preload_libraries": { - "boot_val": "", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "session_replication_role": { - "boot_val": "origin", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "origin", - "setting": "origin", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "shared_buffers": { - "boot_val": "1024", - "context": "postmaster", - "max_val": "1073741823", - "min_val": "16", - "pending_restart": false, - "pretty_val": "128MB", - "setting": "16384", - "sourcefile": "/var/lib/********sql/data/********sql.conf", - "unit": "8kB", - "val_in_bytes": 134217728, - "vartype": "integer" - }, - "shared_memory_type": { - "boot_val": "mmap", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "mmap", - "setting": "mmap", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "shared_preload_libraries": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "ssl_ca_file": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_cert_file": { - "boot_val": "server.crt", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "server.crt", - "setting": "server.crt", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_ciphers": { - "boot_val": "HIGH:MEDIUM:+3DES:!aNULL", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "HIGH:MEDIUM:+3DES:!aNULL", - "setting": "HIGH:MEDIUM:+3DES:!aNULL", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_crl_dir": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_crl_file": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_dh_params_file": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_ecdh_curve": { - "boot_val": "prime256v1", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "prime256v1", - "setting": "prime256v1", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_key_file": { - "boot_val": "server.key", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "server.key", - "setting": "server.key", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_library": { - "boot_val": "OpenSSL", - "context": "internal", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "OpenSSL", - "setting": "OpenSSL", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_max_protocol_version": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "ssl_min_protocol_version": { - "boot_val": "TLSv1.2", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "TLSv1.2", - "setting": "TLSv1.2", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "ssl_passphrase_command": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "ssl_passphrase_command_supports_reload": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "ssl_prefer_server_ciphers": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "standard_conforming_strings": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "statement_timeout": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "stats_temp_directory": { - "boot_val": "pg_stat_tmp", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "pg_stat_tmp", - "setting": "pg_stat_tmp", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "superuser_reserved_connections": { - "boot_val": "3", - "context": "postmaster", - "max_val": "262143", - "min_val": "0", - "pending_restart": false, - "pretty_val": "3", - "setting": "3", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "synchronize_seqscans": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "synchronous_commit": { - "boot_val": "on", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "synchronous_standby_names": { - "boot_val": "", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "syslog_facility": { - "boot_val": "local0", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "local0", - "setting": "local0", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "syslog_ident": { - "boot_val": "********s", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "********s", - "setting": "********s", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "syslog_sequence_numbers": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "syslog_split_messages": { - "boot_val": "on", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "tcp_keepalives_count": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "9", - "setting": "9", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "tcp_keepalives_idle": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "7200", - "setting": "7200", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "tcp_keepalives_interval": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "75", - "setting": "75", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "tcp_user_timeout": { - "boot_val": "0", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "temp_buffers": { - "boot_val": "1024", - "context": "user", - "max_val": "1073741823", - "min_val": "100", - "pending_restart": false, - "pretty_val": "8MB", - "setting": "1024", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 8388608, - "vartype": "integer" - }, - "temp_file_limit": { - "boot_val": "-1", - "context": "superuser", - "max_val": "2147483647", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "-1", - "setting": "-1", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "temp_tablespaces": { - "boot_val": "", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "timezone_abbreviations": { - "boot_val": "", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "Default", - "setting": "Default", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "trace_notify": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "trace_recovery_messages": { - "boot_val": "log", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "log", - "setting": "log", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "trace_sort": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "track_activities": { - "boot_val": "on", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "track_activity_query_size": { - "boot_val": "1024", - "context": "postmaster", - "max_val": "1048576", - "min_val": "100", - "pending_restart": false, - "pretty_val": "1kB", - "setting": "1024", - "sourcefile": "", - "unit": "B", - "vartype": "integer" - }, - "track_commit_timestamp": { - "boot_val": "off", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "track_counts": { - "boot_val": "on", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "track_functions": { - "boot_val": "none", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "none", - "setting": "none", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "track_io_timing": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "track_wal_io_timing": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "transaction_deferrable": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "transaction_isolation": { - "boot_val": "read committed", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "read committed", - "setting": "read committed", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "transaction_read_only": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "transform_null_equals": { - "boot_val": "off", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "unix_socket_directories": { - "boot_val": "/var/run/********sql", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "/var/run/********sql", - "setting": "/var/run/********sql", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "unix_socket_group": { - "boot_val": "", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "unix_socket_permissions": { - "boot_val": "511", - "context": "postmaster", - "max_val": "511", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0777", - "setting": "0777", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "update_process_title": { - "boot_val": "on", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "vacuum_cost_delay": { - "boot_val": "0", - "context": "user", - "max_val": "100", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "ms", - "vartype": "real" - }, - "vacuum_cost_limit": { - "boot_val": "200", - "context": "user", - "max_val": "10000", - "min_val": "1", - "pending_restart": false, - "pretty_val": "200", - "setting": "200", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_cost_page_dirty": { - "boot_val": "20", - "context": "user", - "max_val": "10000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "20", - "setting": "20", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_cost_page_hit": { - "boot_val": "1", - "context": "user", - "max_val": "10000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1", - "setting": "1", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_cost_page_miss": { - "boot_val": "2", - "context": "user", - "max_val": "10000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "2", - "setting": "2", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_defer_cleanup_age": { - "boot_val": "0", - "context": "sighup", - "max_val": "1000000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_failsafe_age": { - "boot_val": "1600000000", - "context": "user", - "max_val": "2100000000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1600000000", - "setting": "1600000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_freeze_min_age": { - "boot_val": "50000000", - "context": "user", - "max_val": "1000000000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "50000000", - "setting": "50000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_freeze_table_age": { - "boot_val": "150000000", - "context": "user", - "max_val": "2000000000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "150000000", - "setting": "150000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_multixact_failsafe_age": { - "boot_val": "1600000000", - "context": "user", - "max_val": "2100000000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1600000000", - "setting": "1600000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_multixact_freeze_min_age": { - "boot_val": "5000000", - "context": "user", - "max_val": "1000000000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "5000000", - "setting": "5000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "vacuum_multixact_freeze_table_age": { - "boot_val": "150000000", - "context": "user", - "max_val": "2000000000", - "min_val": "0", - "pending_restart": false, - "pretty_val": "150000000", - "setting": "150000000", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "wal_block_size": { - "boot_val": "8192", - "context": "internal", - "max_val": "8192", - "min_val": "8192", - "pending_restart": false, - "pretty_val": "8192", - "setting": "8192", - "sourcefile": "", - "unit": "", - "vartype": "integer" - }, - "wal_buffers": { - "boot_val": "-1", - "context": "postmaster", - "max_val": "262143", - "min_val": "-1", - "pending_restart": false, - "pretty_val": "4MB", - "setting": "512", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 4194304, - "vartype": "integer" - }, - "wal_compression": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "wal_consistency_checking": { - "boot_val": "", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "", - "setting": "", - "sourcefile": "", - "unit": "", - "vartype": "string" - }, - "wal_init_zero": { - "boot_val": "on", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "wal_keep_size": { - "boot_val": "0", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "0", - "setting": "0", - "sourcefile": "", - "unit": "MB", - "val_in_bytes": 0, - "vartype": "integer" - }, - "wal_level": { - "boot_val": "replica", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "replica", - "setting": "replica", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "wal_log_hints": { - "boot_val": "off", - "context": "postmaster", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "wal_receiver_create_temp_slot": { - "boot_val": "off", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "wal_receiver_status_interval": { - "boot_val": "10", - "context": "sighup", - "max_val": "2147483", - "min_val": "0", - "pending_restart": false, - "pretty_val": "10s", - "setting": "10", - "sourcefile": "", - "unit": "s", - "vartype": "integer" - }, - "wal_receiver_timeout": { - "boot_val": "60000", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1min", - "setting": "60000", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "wal_recycle": { - "boot_val": "on", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "on", - "setting": "on", - "sourcefile": "", - "unit": "", - "vartype": "bool" - }, - "wal_retrieve_retry_interval": { - "boot_val": "5000", - "context": "sighup", - "max_val": "2147483647", - "min_val": "1", - "pending_restart": false, - "pretty_val": "5s", - "setting": "5000", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "wal_segment_size": { - "boot_val": "16777216", - "context": "internal", - "max_val": "1073741824", - "min_val": "1048576", - "pending_restart": false, - "pretty_val": "16MB", - "setting": "16777216", - "sourcefile": "", - "unit": "B", - "vartype": "integer" - }, - "wal_sender_timeout": { - "boot_val": "60000", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1min", - "setting": "60000", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "wal_skip_threshold": { - "boot_val": "2048", - "context": "user", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "2MB", - "setting": "2048", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 2097152, - "vartype": "integer" - }, - "wal_sync_method": { - "boot_val": "fdatasync", - "context": "sighup", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "fdatasync", - "setting": "fdatasync", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "wal_writer_delay": { - "boot_val": "200", - "context": "sighup", - "max_val": "10000", - "min_val": "1", - "pending_restart": false, - "pretty_val": "200ms", - "setting": "200", - "sourcefile": "", - "unit": "ms", - "vartype": "integer" - }, - "wal_writer_flush_after": { - "boot_val": "128", - "context": "sighup", - "max_val": "2147483647", - "min_val": "0", - "pending_restart": false, - "pretty_val": "1MB", - "setting": "128", - "sourcefile": "", - "unit": "8kB", - "val_in_bytes": 1048576, - "vartype": "integer" - }, - "work_mem": { - "boot_val": "4096", - "context": "user", - "max_val": "2147483647", - "min_val": "64", - "pending_restart": false, - "pretty_val": "4MB", - "setting": "4096", - "sourcefile": "", - "unit": "kB", - "val_in_bytes": 4194304, - "vartype": "integer" - }, - "xmlbinary": { - "boot_val": "base64", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "base64", - "setting": "base64", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "xmloption": { - "boot_val": "content", - "context": "user", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "content", - "setting": "content", - "sourcefile": "", - "unit": "", - "vartype": "enum" - }, - "zero_damaged_pages": { - "boot_val": "off", - "context": "superuser", - "max_val": "", - "min_val": "", - "pending_restart": false, - "pretty_val": "off", - "setting": "off", - "sourcefile": "", - "unit": "", - "vartype": "bool" - } - }, - "tablespaces": { - "pg_default": { - "spcacl": "", - "spcoptions": [], - "spcowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - }, - "pg_global": { - "spcacl": "", - "spcoptions": [], - "spcowner": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - } - }, - "version": { - "full": "14.5", - "major": 14, - "minor": 5, - "raw": "PostgreSQL 14.5 (Debian 14.5-1.pgdg110+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit" - } - } -} - diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml index 6af98366b..55731a4fa 100644 --- a/apps/assets/automations/gather_facts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml @@ -2,19 +2,9 @@ gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python - jms_account: - username: postgre - secret: postgre - jms_asset: - address: 127.0.0.1 - port: 5432 - database: testdb - account: - username: test - secret: jumpserver tasks: - - name: Test PostgreSQL connection + - name: Get info community.postgresql.postgresql_info: login_user: "{{ jms_account.username }}" login_password: "{{ jms_account.secret }}" @@ -23,6 +13,10 @@ login_db: "{{ jms_asset.database }}" register: db_info - - name: Debug it - debug: - var: db_info + - name: Define Postgresql info by set_fact + set_fact: + info: + version: "{{ db_info.server_version.raw }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_facts/database/sqlserver/main.yml b/apps/assets/automations/gather_facts/database/sqlserver/main.yml deleted file mode 100644 index 8e75f0102..000000000 --- a/apps/assets/automations/gather_facts/database/sqlserver/main.yml +++ /dev/null @@ -1,10 +0,0 @@ -{% for account in accounts %} -- hosts: {{ account.asset.name }} - vars: - account: - username: {{ account.username }} - password: {{ account.password }} - public_key: {{ account.public_key }} - roles: - - change_secret -{% endfor %} diff --git a/apps/assets/automations/gather_facts/database/sqlserver/manifest.yml b/apps/assets/automations/gather_facts/database/sqlserver/manifest.yml deleted file mode 100644 index 3c4c82de4..000000000 --- a/apps/assets/automations/gather_facts/database/sqlserver/manifest.yml +++ /dev/null @@ -1,8 +0,0 @@ -id: gather_facts_sqlserver -name: Change password for SQLServer -version: 1 -category: database -type: - - sqlserver -method: gather_facts - diff --git a/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml b/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml deleted file mode 100644 index cb6480235..000000000 --- a/apps/assets/automations/gather_facts/database/sqlserver/roles/change_password/tasks/main.yml +++ /dev/null @@ -1,27 +0,0 @@ -- name: ping - ansible.builtin.ping: - -#- name: print variables -# debug: -# msg: "Username: {{ account.username }}, Password: {{ account.password }}" - -- name: Change password - user: - name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" - update_password: always - when: account.password - -- name: Change public key - authorized_key: - user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key - -- name: Verify password - ansible.builtin.ping: - vars: - ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko diff --git a/apps/assets/automations/gather_facts/manager.py b/apps/assets/automations/gather_facts/manager.py index a2072b138..bbc3f9add 100644 --- a/apps/assets/automations/gather_facts/manager.py +++ b/apps/assets/automations/gather_facts/manager.py @@ -1,4 +1,5 @@ from common.utils import get_logger +from assets.const import AutomationTypes from ..base.manager import BasePlaybookManager logger = get_logger(__name__) @@ -11,7 +12,7 @@ class GatherFactsManager(BasePlaybookManager): @classmethod def method_type(cls): - return 'gather_facts' + return AutomationTypes.gather_facts def host_callback(self, host, asset=None, **kwargs): super().host_callback(host, asset=asset, **kwargs) @@ -19,14 +20,10 @@ class GatherFactsManager(BasePlaybookManager): return host def on_host_success(self, host, result): - info = result.get('Get info', {}).get('res', {}).get('ansible_facts', {}).get('info', {}) + info = result.get('debug', {}).get('res', {}).get('info', {}) asset = self.host_asset_mapper.get(host) if asset and info: asset.info = info asset.save() else: logger.error("Not found info, task name must be 'Get info': {}".format(host)) - - - - diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 904b98717..1f9e1ac04 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -9,6 +9,7 @@ from common.db.fields import EncryptJsonDictTextField from orgs.mixins.models import OrgModelMixin from ops.mixin import PeriodTaskModelMixin from assets.models import Node, Asset +from assets.tasks import execute_automation class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): @@ -38,7 +39,11 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): return assets.group_by_platform() def get_register_task(self): - raise NotImplementedError + name = f"automation_{self.type}_strategy_period_{str(self.id)[:8]}" + task = execute_automation.name + args = (str(self.id), Trigger.timing, self._meta.model) + kwargs = {} + return name, task, args, kwargs def get_many_to_many_ids(self, field: str): return [str(i) for i in getattr(self, field).all().values_list('id', flat=True)] diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index 5624caf1f..ef20e27b0 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _ from common.db import fields from common.const.choices import Trigger from common.db.models import JMSBaseModel -from assets.tasks import execute_change_secret_automation from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy from .base import BaseAutomation @@ -35,13 +34,6 @@ class ChangeSecretAutomation(BaseAutomation): class Meta: verbose_name = _("Change secret automation") - def get_register_task(self): - name = "automation_change_secret_strategy_period_{}".format(str(self.id)[:8]) - task = execute_change_secret_automation.name - args = (str(self.id), Trigger.timing) - kwargs = {} - return name, task, args, kwargs - def to_attr_json(self): attr_json = super().to_attr_json() attr_json.update({ diff --git a/apps/assets/models/automations/gather_facts.py b/apps/assets/models/automations/gather_facts.py index 251e63944..1641c9f81 100644 --- a/apps/assets/models/automations/gather_facts.py +++ b/apps/assets/models/automations/gather_facts.py @@ -1,16 +1,15 @@ from django.utils.translation import ugettext_lazy as _ +from assets.const import AutomationTypes from .base import BaseAutomation - __all__ = ['GatherFactsAutomation'] class GatherFactsAutomation(BaseAutomation): - class Meta: - verbose_name = _("Gather asset facts") - def save(self, *args, **kwargs): - self.type = 'gather_facts' + self.type = AutomationTypes.gather_facts super().save(*args, **kwargs) + class Meta: + verbose_name = _("Gather asset facts") diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py index 0859381a8..f3484d3e9 100644 --- a/apps/assets/tasks/automation.py +++ b/apps/assets/tasks/automation.py @@ -7,12 +7,11 @@ logger = get_logger(__file__) @shared_task -def execute_change_secret_automation(pid, trigger): - from assets.models import ChangeSecretAutomation +def execute_automation(pid, trigger, mode): with tmp_to_root_org(): - instance = get_object_or_none(ChangeSecretAutomation, pk=pid) + instance = get_object_or_none(mode, pk=pid) if not instance: - logger.error("No automation plan found: {}".format(pid)) + logger.error("No automation task found: {}".format(pid)) return with tmp_to_org(instance.org): instance.execute(trigger) diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py index 7c0b473ec..74b946ea1 100644 --- a/apps/ops/mixin.py +++ b/apps/ops/mixin.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # import abc -import uuid from django.utils.translation import ugettext_lazy as _ from django.db import models -from django import forms from rest_framework import serializers from .celery.utils import ( From 11eb505c785b85200ae0f1ca31813160951534ef Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 25 Oct 2022 19:10:12 +0800 Subject: [PATCH 223/488] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20Connec?= =?UTF-8?q?tionToken=20=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0013_auto_20221025_1908.py | 29 +++++++++++++++++++ apps/authentication/models.py | 13 ++++----- 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 apps/authentication/migrations/0013_auto_20221025_1908.py diff --git a/apps/authentication/migrations/0013_auto_20221025_1908.py b/apps/authentication/migrations/0013_auto_20221025_1908.py new file mode 100644 index 000000000..17556e60c --- /dev/null +++ b/apps/authentication/migrations/0013_auto_20221025_1908.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.14 on 2022-10-25 11:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0111_alter_changesecretautomation_secret_strategy'), + ('authentication', '0012_auto_20220816_1629'), + ] + + operations = [ + migrations.RemoveField( + model_name='connectiontoken', + name='type', + ), + migrations.AddField( + model_name='connectiontoken', + name='account_display', + field=models.CharField(default='', max_length=128, verbose_name='Account display'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='account', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='assets.account', verbose_name='Account'), + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index ea4dbb5ff..642443e02 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -63,13 +63,6 @@ def date_expired_default(): class ConnectionToken(OrgModelMixin, JMSBaseModel): - class Type(models.TextChoices): - asset = 'asset', _('Asset') - application = 'application', _('Application') - - type = models.CharField( - max_length=16, default=Type.asset, choices=Type.choices, verbose_name=_("Type") - ) secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) date_expired = models.DateTimeField( default=date_expired_default, verbose_name=_("Date expired") @@ -85,7 +78,11 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): related_name='connection_tokens', null=True, blank=True ) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - account = models.CharField(max_length=128, default='', verbose_name=_("Account")) + account = models.ForeignKey( + 'assets.Account', on_delete=models.SET_NULL, verbose_name=_('Account'), + related_name='connection_tokens', null=True, blank=True + ) + account_display = models.CharField(max_length=128, default='', verbose_name=_("Account display")) class Meta: ordering = ('-date_expired',) From 82aca6b843fe0ccbf78ec406854755be010401ee Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Oct 2022 19:31:13 +0800 Subject: [PATCH 224/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 2 + apps/common/utils/lock.py | 30 +++++------ apps/orgs/utils.py | 15 ++++++ apps/terminal/api/applet/applet.py | 31 ++++++++--- apps/terminal/api/applet/host.py | 13 +++++ .../migrations/0054_auto_20221024_1452.py | 2 +- apps/terminal/models/applet/applet.py | 31 +++-------- apps/terminal/serializers/applet.py | 52 +++++++++++++++---- 8 files changed, 118 insertions(+), 58 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 8690b6c10..b6204e843 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -107,6 +107,8 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) return nodes_to_set = [] for full_value in nodes_display: + if not full_value.startswith('/'): + full_value = '/' + instance.org.name + '/' + full_value node = Node.objects.filter(full_value=full_value).first() if node: nodes_to_set.append(node) diff --git a/apps/common/utils/lock.py b/apps/common/utils/lock.py index a14fe1184..773647725 100644 --- a/apps/common/utils/lock.py +++ b/apps/common/utils/lock.py @@ -76,7 +76,6 @@ class DistributedLock(RedisLock): # 要创建一个新的锁对象 with self.__class__(**self.kwargs_copy): return func(*args, **kwds) - return inner @classmethod @@ -105,22 +104,21 @@ class DistributedLock(RedisLock): if self._reentrant: if self.locked_by_current_thread(): self._acquired_reentrant_lock = True - logger.debug(f'Reentry lock ok: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name} thread={self._thread_id}') + logger.debug(f'Reentry lock ok: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name}') return True - logger.debug(f'Attempt acquire reentrant-lock: lock_id={self.id} lock={self.name} thread={self._thread_id}') + logger.debug(f'Attempt acquire reentrant-lock: lock_id={self.id} lock={self.name}') acquired = super().acquire(blocking=blocking, timeout=timeout) if acquired: - logger.debug(f'Acquired reentrant-lock ok: lock_id={self.id} lock={self.name} thread={self._thread_id}') + logger.debug(f'Acquired reentrant-lock ok: lock_id={self.id} lock={self.name}') setattr(thread_local, self.name, self.id) else: - logger.debug( - f'Acquired reentrant-lock failed: lock_id={self.id} lock={self.name} thread={self._thread_id}') + logger.debug(f'Acquired reentrant-lock failed: lock_id={self.id} lock={self.name}') return acquired else: - logger.debug(f'Attempt acquire lock: lock_id={self.id} lock={self.name} thread={self._thread_id}') + logger.debug(f'Attempt acquire lock: lock_id={self.id} lock={self.name}') acquired = super().acquire(blocking=blocking, timeout=timeout) - logger.debug(f'Acquired lock: ok={acquired} lock_id={self.id} lock={self.name} thread={self._thread_id}') + logger.debug(f'Acquired lock: ok={acquired} lock_id={self.id} lock={self.name}') return acquired @property @@ -139,17 +137,17 @@ class DistributedLock(RedisLock): def _release_on_reentrant_locked_by_brother(self): if self._acquired_reentrant_lock: self._acquired_reentrant_lock = False - logger.debug(f'Released reentrant-lock: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name} thread={self._thread_id}') + logger.debug(f'Released reentrant-lock: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name}') return else: - self._raise_exc_with_log(f'Reentrant-lock is not acquired: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name} thread={self._thread_id}') + self._raise_exc_with_log(f'Reentrant-lock is not acquired: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name}') def _release_on_reentrant_locked_by_me(self): - logger.debug(f'Release reentrant-lock locked by me: lock_id={self.id} lock={self.name} thread={self._thread_id}') + logger.debug(f'Release reentrant-lock locked by me: lock_id={self.id} lock={self.name}') id = getattr(thread_local, self.name, None) if id != self.id: - raise PermissionError(f'Reentrant-lock is not locked by me: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name} thread={self._thread_id}') + raise PermissionError(f'Reentrant-lock is not locked by me: lock_id={self.id} owner_id={self.get_owner_id()} lock={self.name}') try: # 这里要保证先删除 thread_local 的标记, delattr(thread_local, self.name) @@ -171,9 +169,9 @@ class DistributedLock(RedisLock): def _release(self): try: self._release_redis_lock() - logger.debug(f'Released lock: lock_id={self.id} lock={self.name} thread={self._thread_id}') + logger.debug(f'Released lock: lock_id={self.id} lock={self.name}') except NotAcquired as e: - logger.error(f'Release lock failed: lock_id={self.id} lock={self.name} thread={self._thread_id} error: {e}') + logger.error(f'Release lock failed: lock_id={self.id} lock={self.name} error: {e}') self._raise_exc(e) def release(self): @@ -188,12 +186,12 @@ class DistributedLock(RedisLock): _release = self._release_on_reentrant_locked_by_brother else: self._raise_exc_with_log( - f'Reentrant-lock is not acquired: lock_id={self.id} lock={self.name} thread={self._thread_id}') + f'Reentrant-lock is not acquired: lock_id={self.id} lock={self.name}') # 处理是否在事务提交时才释放锁 if self._release_on_transaction_commit: logger.debug( - f'Release lock on transaction commit ... :lock_id={self.id} lock={self.name} thread={self._thread_id}') + f'Release lock on transaction commit ... :lock_id={self.id} lock={self.name}') transaction.on_commit(_release) else: _release() diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 29b1f03e9..0ea4085e7 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -88,6 +88,21 @@ def tmp_to_org(org): set_current_org(ori_org) +@contextmanager +def tmp_to_builtin_org(system=0, default=0): + if system: + org_id = Organization.SYSTEM_ID + elif default: + org_id = Organization.DEFAULT_ID + else: + raise ValueError("Must set system or default") + ori_org = get_current_org() + set_current_org(org_id) + yield + if ori_org is not None: + set_current_org(ori_org) + + def get_org_filters(): kwargs = {} diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 5d683fa7b..9ded63dc1 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -1,12 +1,13 @@ import os.path import shutil import zipfile - import yaml + from django.core.files.storage import default_storage from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.serializers import ValidationError from terminal import serializers, models from terminal.serializers import AppletUploadSerializer @@ -19,9 +20,16 @@ class AppletViewSet(viewsets.ModelViewSet): 'upload': 'terminal.add_applet', } - @action(detail=False, methods=['post'], serializer_class=AppletUploadSerializer) - def upload(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) + def perform_destroy(self, instance): + if not instance.name: + raise ValidationError('Applet is not null') + path = default_storage.path('applets/{}'.format(instance.name)) + if os.path.exists(path): + shutil.rmtree(path) + instance.delete() + + def extract_and_check_file(self, request): + serializer = self.get_serializer(data=self.request.data) serializer.is_valid(raise_exception=True) file = serializer.validated_data['file'] @@ -29,13 +37,11 @@ class AppletViewSet(viewsets.ModelViewSet): if default_storage.exists(save_to): default_storage.delete(save_to) rel_path = default_storage.save(save_to, file) - path = default_storage.path(rel_path) extract_to = default_storage.path('applets/{}.tmp'.format(file.name)) if os.path.exists(extract_to): shutil.rmtree(extract_to) - update = request.query_params.get('update') with zipfile.ZipFile(path) as zp: if zp.testzip() is not None: return Response({'msg': 'Invalid Zip file'}, status=400) @@ -46,12 +52,21 @@ class AppletViewSet(viewsets.ModelViewSet): for name in files: path = os.path.join(tmp_dir, name) if not os.path.exists(path): - return Response({'error': 'Missing file: {}'.format(path)}, status=400) + raise ValidationError({'error': 'Missing file {}'.format(name)}) with open(os.path.join(tmp_dir, 'manifest.yml')) as f: manifest = yaml.safe_load(f) - name = manifest.get('name', '') + if not manifest.get('name', ''): + raise ValidationError({'error': 'Missing name in manifest.yml'}) + return manifest, tmp_dir + + @action(detail=False, methods=['post'], serializer_class=AppletUploadSerializer) + def upload(self, request, *args, **kwargs): + manifest, tmp_dir = self.extract_and_check_file(request) + name = manifest['name'] + update = request.query_params.get('update') + instance = models.Applet.objects.filter(name=name).first() if instance and not update: return Response({'error': 'Applet already exists: {}'.format(name)}, status=400) diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index c3c301e30..09de7dbff 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -1,5 +1,9 @@ from rest_framework import viewsets +from rest_framework.decorators import action +from orgs.utils import tmp_to_root_org +from orgs.models import Organization +from assets.models import Host from terminal import serializers, models __all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] @@ -9,6 +13,15 @@ class AppletHostViewSet(viewsets.ModelViewSet): queryset = models.AppletHost.objects.all() serializer_class = serializers.AppletHostSerializer + @action(methods=['get'], detail=False) + def hosts(self, request): + with tmp_to_root_org(): + kwargs = { + 'platform__name': 'RemoteAppHost', + 'org_id': Organization.SYSTEM_ID + } + return Host.objects.filter(**kwargs) + class AppletHostDeploymentViewSet(viewsets.ModelViewSet): queryset = models.AppletHostDeployment.objects.all() diff --git a/apps/terminal/migrations/0054_auto_20221024_1452.py b/apps/terminal/migrations/0054_auto_20221024_1452.py index 3ee883a8b..687730158 100644 --- a/apps/terminal/migrations/0054_auto_20221024_1452.py +++ b/apps/terminal/migrations/0054_auto_20221024_1452.py @@ -22,10 +22,10 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('display_name', models.CharField(max_length=128, unique=True, verbose_name='Display name')), ('version', models.CharField(max_length=16, verbose_name='Version')), ('author', models.CharField(max_length=128, verbose_name='Author')), ('type', models.CharField(choices=[('general', 'General'), ('web', 'Web')], default='general', max_length=16, verbose_name='Type')), - ('path', models.FilePathField(verbose_name='Path')), ('vcs_type', models.CharField(max_length=16, null=True, verbose_name='VCS type')), ('vcs_url', models.CharField(max_length=256, null=True, verbose_name='URL')), ('protocols', models.JSONField(default=list, verbose_name='Protocol')), diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 510d8da35..3e06a5fe9 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -1,7 +1,8 @@ import yaml import os.path -from rest_framework.exceptions import ValidationError +from django.conf import settings +from django.core.files.storage import default_storage from django.db import models from django.utils.translation import gettext_lazy as _ @@ -22,9 +23,9 @@ class Applet(JMSBaseModel): archive = 'archive', _('Remote gzip') name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) + display_name = models.CharField(max_length=128, verbose_name=_('Display name')) version = models.CharField(max_length=16, verbose_name=_('Version')) author = models.CharField(max_length=128, verbose_name=_('Author')) - path = models.FilePathField(verbose_name=_('Path')) type = models.CharField(max_length=16, verbose_name=_('Type'), default='general', choices=Type.choices) vcs_type = models.CharField(max_length=16, verbose_name=_('VCS type'), null=True) vcs_url = models.CharField(max_length=256, verbose_name=_('URL'), null=True) @@ -35,6 +36,10 @@ class Applet(JMSBaseModel): def __str__(self): return self.name + @property + def path(self): + return default_storage.path('applets/{}'.format(self.name)) + @property def manifest(self): path = os.path.join(self.path, 'manifest.yml') @@ -48,27 +53,7 @@ class Applet(JMSBaseModel): path = os.path.join(self.path, 'icon.png') if not os.path.exists(path): return None - with open(path, 'rb') as f: - return f.read() - - @classmethod - def validate_manifest(cls, manifest): - fields = ['name', 'display_name', 'version', 'author', 'type', 'tags', 'protocols'] - for field in fields: - if field not in manifest: - raise ValidationError(f'Missing field {field}') - if manifest['type'] not in [i[0] for i in cls.Type.choices]: - raise ValidationError('Invalid type') - if not isinstance(manifest['protocols'], list): - raise ValidationError('Invalid protocols') - - @classmethod - def create_by_manifest(cls, manifest): - obj = cls() - for k, v in manifest.items(): - setattr(obj, k, v) - obj.save() - return obj + return os.path.join(settings.MEDIA_URL, 'applets', self.name, 'icon.png') class AppletPublication(JMSBaseModel): diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 3f3919776..25259b402 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -1,7 +1,10 @@ from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ -from common.drf.fields import ObjectRelatedField -from assets.models import Host +from common.drf.fields import ObjectRelatedField, LabeledChoiceField +from assets.models import Host, Platform +from assets.serializers import HostSerializer +from orgs.utils import tmp_to_builtin_org from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment @@ -13,14 +16,18 @@ __all__ = [ class AppletSerializer(serializers.ModelSerializer): + icon = serializers.ReadOnlyField(label=_("Icon")) + type = LabeledChoiceField(choices=Applet.Type.choices, label=_("Type")) + class Meta: model = Applet - fields_mini = ['id', 'name'] + fields_mini = ['id', 'name', 'display_name'] read_only_fields = [ - 'date_created', 'date_updated' + 'icon', 'date_created', 'date_updated' ] fields = fields_mini + [ - 'version', 'author', 'type', 'protocols', 'comment' + 'version', 'author', 'type', 'protocols', + 'tags', 'comment' ] + read_only_fields @@ -42,15 +49,40 @@ class AppletPublicationSerializer(serializers.ModelSerializer): class AppletHostSerializer(serializers.ModelSerializer): - host = ObjectRelatedField(queryset=Host.objects.all()) + host = HostSerializer(allow_null=True, required=False) class Meta: model = AppletHost fields_mini = ['id', 'host'] - read_only_fields = ['date_created', 'date_updated'] - fields = fields_mini + [ - 'comment', 'account_automation', 'date_synced', 'status', - ] + read_only_fields + read_only_fields = ['date_synced', 'status', 'date_created', 'date_updated'] + fields = fields_mini + ['comment', 'account_automation'] + read_only_fields + + def __init__(self, *args, **kwargs): + self.host_data = kwargs.get('data', {}).pop('host', {}) + super().__init__(*args, **kwargs) + + def _create_host(self): + platform = Platform.objects.get(name='RemoteAppHost') + data = { + **self.host_data, + 'platform': platform.id, + 'nodes_display': [ + 'RemoteAppHosts' + ] + } + serializer = HostSerializer(data=data) + try: + serializer.is_valid(raise_exception=True) + except serializers.ValidationError: + raise serializers.ValidationError({'host': serializer.errors}) + host = serializer.save() + return host + + def create(self, validated_data): + with tmp_to_builtin_org(system=1): + host = self._create_host() + instance = super().create({**validated_data, 'host': host}) + return instance class AppletHostDeploymentSerializer(serializers.ModelSerializer): From c018055d5f5a9987696b17dad49cca860f717cb7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Oct 2022 19:58:14 +0800 Subject: [PATCH 225/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/migrations/0013_auto_20221025_1908.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/migrations/0013_auto_20221025_1908.py b/apps/authentication/migrations/0013_auto_20221025_1908.py index 17556e60c..452063f35 100644 --- a/apps/authentication/migrations/0013_auto_20221025_1908.py +++ b/apps/authentication/migrations/0013_auto_20221025_1908.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('assets', '0111_alter_changesecretautomation_secret_strategy'), + ('assets', '0110_auto_20221021_1506'), ('authentication', '0012_auto_20220816_1629'), ] From e7a114a31d5892008968b8f71f558f6dd6f69c31 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 25 Oct 2022 20:02:23 +0800 Subject: [PATCH 226/488] =?UTF-8?q?feat:=20celery=20=E4=BB=BB=E5=8A=A1api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/celery.py | 19 +++++++++++++------ apps/ops/serializers/celery.py | 4 ++-- apps/ops/signal_handlers.py | 13 ++++++++++--- apps/ops/urls/api_urls.py | 21 ++++++++++++++------- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 5fa8c902b..61d13db17 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -4,7 +4,7 @@ import os import re -from celery import current_app +from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext as _ from rest_framework import viewsets from celery.result import AsyncResult @@ -98,13 +98,20 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): return queryset -class CeleryTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): +class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): queryset = CeleryTask.objects.all() serializer_class = CeleryTaskSerializer - http_method_names = ('get',) + http_method_names = ('get', 'head', 'options',) -class CeleryTaskExecutionViewSet(CommonApiMixin, viewsets.ModelViewSet): - queryset = CeleryTaskExecution.objects.all() +class CeleryTaskExecutionViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): serializer_class = CeleryTaskExecutionSerializer - http_method_names = ('get',) + http_method_names = ('get', 'head', 'options',) + + def get_queryset(self): + task_id = self.kwargs.get("task_pk") + if task_id: + task = CeleryTask.objects.get(pk=task_id) + return CeleryTaskExecution.objects.filter(name=task.name) + else: + return CeleryTaskExecution.objects.none() diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py index 8122ed636..b2aa6eb7a 100644 --- a/apps/ops/serializers/celery.py +++ b/apps/ops/serializers/celery.py @@ -31,7 +31,7 @@ class CeleryTaskSerializer(serializers.ModelSerializer): class Meta: model = CeleryTask fields = [ - 'name', 'verbose_name', 'description', + 'id', 'name', 'verbose_name', 'description', ] @@ -39,5 +39,5 @@ class CeleryTaskExecutionSerializer(serializers.ModelSerializer): class Meta: model = CeleryTaskExecution fields = [ - "name", "args", "kwargs", "state", "is_finished", "date_published", "date_start", "date_finished" + "id", "name", "args", "kwargs", "state", "is_finished", "date_published", "date_start", "date_finished" ] diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index 5882a10e8..3cb9b3f70 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -20,9 +20,16 @@ TASK_LANG_CACHE_TTL = 1800 @receiver(django_ready) def sync_registered_tasks(*args, **kwargs): with transaction.atomic(): - CeleryTask.objects.all().delete() - for key in app.tasks: - CeleryTask(name=key).save() + db_tasks = CeleryTask.objects.all() + celery_task_names = [key for key in app.tasks] + db_task_names = [task.name for task in db_tasks] + + for task in db_tasks: + if task.name not in celery_task_names: + task.delete() + for task in celery_task_names: + if task not in db_task_names: + CeleryTask(name=task).save() @signals.before_task_publish.connect diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index bc263c184..1edbd16e7 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -4,6 +4,8 @@ from __future__ import unicode_literals from django.urls import path from rest_framework.routers import DefaultRouter from rest_framework_bulk.routes import BulkRouter +from rest_framework_nested import routers + from .. import api app_name = "ops" @@ -14,15 +16,20 @@ bulk_router = BulkRouter() router.register(r'adhoc', api.AdHocViewSet, 'adhoc') router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution') router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task') -router.register(r'celery/tasks', api.CeleryTaskViewSet, 'celery-task') -router.register(r'celery/task-executions', api.CeleryTaskExecutionViewSet, 'task-execution') + +router.register(r'tasks', api.CeleryTaskViewSet, 'task') + +task_router = routers.NestedDefaultRouter(router, r'tasks', lookup='task') +task_router.register(r'executions', api.CeleryTaskExecutionViewSet, 'task-execution') urlpatterns = [ - path('celery/task//log/', api.CeleryTaskExecutionLogApi.as_view(), name='celery-task-log'), - path('celery/task//result/', api.CeleryResultApi.as_view(), name='celery-result'), - path('ansible/task//log/', api.AnsibleTaskLogApi.as_view(), name='ansible-task-log'), + path('celery/task//task-execution//log/', api.CeleryTaskExecutionLogApi.as_view(), + name='celery-task-execution-log'), + path('celery/task//task-execution//result/', api.CeleryResultApi.as_view(), + name='celery-task-execution-result'), + + path('ansible/task-execution//log/', api.AnsibleTaskLogApi.as_view(), name='ansible-task-log'), ] -urlpatterns += router.urls -urlpatterns += bulk_router.urls +urlpatterns += (router.urls + bulk_router.urls + task_router.urls) From e327c971700387ca65483c30517881d784d2168f Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 25 Oct 2022 20:09:05 +0800 Subject: [PATCH 227/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0103_auto_20220811_1511.py | 2 +- apps/assets/models/asset/common.py | 3 +-- apps/terminal/migrations/0054_auto_20221024_1452.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/assets/migrations/0103_auto_20220811_1511.py b/apps/assets/migrations/0103_auto_20220811_1511.py index 15cfff68b..b10455d2f 100644 --- a/apps/assets/migrations/0103_auto_20220811_1511.py +++ b/apps/assets/migrations/0103_auto_20220811_1511.py @@ -14,7 +14,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='asset', name='platform', - field=models.ForeignKey(default=assets.models.platform.Platform.default, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.platform', verbose_name='Platform'), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.platform', verbose_name='Platform'), ), migrations.RemoveField( model_name='asset', diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 5355d0035..fd29bf6a4 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -90,8 +90,7 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) address = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) - platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, - verbose_name=_("Platform"), related_name='assets') + platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', diff --git a/apps/terminal/migrations/0054_auto_20221024_1452.py b/apps/terminal/migrations/0054_auto_20221024_1452.py index 687730158..c2d67ca65 100644 --- a/apps/terminal/migrations/0054_auto_20221024_1452.py +++ b/apps/terminal/migrations/0054_auto_20221024_1452.py @@ -22,7 +22,7 @@ class Migration(migrations.Migration): ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), - ('display_name', models.CharField(max_length=128, unique=True, verbose_name='Display name')), + ('display_name', models.CharField(max_length=128, verbose_name='Display name')), ('version', models.CharField(max_length=16, verbose_name='Version')), ('author', models.CharField(max_length=128, verbose_name='Author')), ('type', models.CharField(choices=[('general', 'General'), ('web', 'Web')], default='general', max_length=16, verbose_name='Type')), From 13279c9d2a769adb3c5239de88d9fbccb1e15720 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 26 Oct 2022 16:09:07 +0800 Subject: [PATCH 228/488] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20Connec?= =?UTF-8?q?tionToken=20=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0013_auto_20221025_1908.py | 29 --------- .../0013_remove_connectiontoken_type.py | 17 ++++++ apps/authentication/models.py | 60 +++++++------------ 3 files changed, 37 insertions(+), 69 deletions(-) delete mode 100644 apps/authentication/migrations/0013_auto_20221025_1908.py create mode 100644 apps/authentication/migrations/0013_remove_connectiontoken_type.py diff --git a/apps/authentication/migrations/0013_auto_20221025_1908.py b/apps/authentication/migrations/0013_auto_20221025_1908.py deleted file mode 100644 index 17556e60c..000000000 --- a/apps/authentication/migrations/0013_auto_20221025_1908.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-25 11:08 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0111_alter_changesecretautomation_secret_strategy'), - ('authentication', '0012_auto_20220816_1629'), - ] - - operations = [ - migrations.RemoveField( - model_name='connectiontoken', - name='type', - ), - migrations.AddField( - model_name='connectiontoken', - name='account_display', - field=models.CharField(default='', max_length=128, verbose_name='Account display'), - ), - migrations.AlterField( - model_name='connectiontoken', - name='account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='assets.account', verbose_name='Account'), - ), - ] diff --git a/apps/authentication/migrations/0013_remove_connectiontoken_type.py b/apps/authentication/migrations/0013_remove_connectiontoken_type.py new file mode 100644 index 000000000..52c6813dc --- /dev/null +++ b/apps/authentication/migrations/0013_remove_connectiontoken_type.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.14 on 2022-10-26 08:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0012_auto_20220816_1629'), + ] + + operations = [ + migrations.RemoveField( + model_name='connectiontoken', + name='type', + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 642443e02..765f38ef9 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -78,11 +78,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): related_name='connection_tokens', null=True, blank=True ) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - account = models.ForeignKey( - 'assets.Account', on_delete=models.SET_NULL, verbose_name=_('Account'), - related_name='connection_tokens', null=True, blank=True - ) - account_display = models.CharField(max_length=128, default='', verbose_name=_("Account display")) + account = models.CharField(max_length=128, default='', verbose_name=_("Account")) class Meta: ordering = ('-date_expired',) @@ -127,7 +123,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def check_valid(self): from perms.utils.permission import validate_permission as asset_validate_permission - from perms.utils.application.permission import validate_permission as app_validate_permission if self.is_expired: is_valid = False @@ -143,45 +138,30 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): error = _('User invalid, disabled or expired') return is_valid, error - if not self.system_user: + if not self.account: is_valid = False - error = _('System user not exists') + error = _('Account not exists') return is_valid, error - if self.is_type(self.Type.asset): - if not self.asset: - is_valid = False - error = _('Asset not exists') - return is_valid, error - if not self.asset.is_active: - is_valid = False - error = _('Asset inactive') - return is_valid, error - has_perm, actions, expired_at = asset_validate_permission( - self.user, self.asset, self.system_user - ) - if not has_perm: - is_valid = False - error = _('User has no permission to access asset or permission expired') - return is_valid, error - self.actions = actions - self.expired_at = expired_at + if not self.asset: + is_valid = False + error = _('Asset not exists') + return is_valid, error - elif self.is_type(self.Type.application): - if not self.application: - is_valid = False - error = _('Application not exists') - return is_valid, error - has_perm, actions, expired_at = app_validate_permission( - self.user, self.application, self.system_user - ) - if not has_perm: - is_valid = False - error = _('User has no permission to access application or permission expired') - return is_valid, error - self.actions = actions - self.expired_at = expired_at + if not self.asset.is_active: + is_valid = False + error = _('Asset inactive') + return is_valid, error + has_perm, actions, expired_at = asset_validate_permission( + self.user, self.asset, self.account + ) + if not has_perm: + is_valid = False + error = _('User has no permission to access asset or permission expired') + return is_valid, error + self.actions = actions + self.expired_at = expired_at return True, '' @lazyproperty From bd001bb2628b058146e357bc46f67396bef75988 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 26 Oct 2022 16:16:12 +0800 Subject: [PATCH 229/488] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20Connec?= =?UTF-8?q?tionToken=20=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0013_auto_20221025_1908.py | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 apps/authentication/migrations/0013_auto_20221025_1908.py diff --git a/apps/authentication/migrations/0013_auto_20221025_1908.py b/apps/authentication/migrations/0013_auto_20221025_1908.py deleted file mode 100644 index 452063f35..000000000 --- a/apps/authentication/migrations/0013_auto_20221025_1908.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-25 11:08 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0110_auto_20221021_1506'), - ('authentication', '0012_auto_20220816_1629'), - ] - - operations = [ - migrations.RemoveField( - model_name='connectiontoken', - name='type', - ), - migrations.AddField( - model_name='connectiontoken', - name='account_display', - field=models.CharField(default='', max_length=128, verbose_name='Account display'), - ), - migrations.AlterField( - model_name='connectiontoken', - name='account', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connection_tokens', to='assets.account', verbose_name='Account'), - ), - ] From 99e48363112411ff0e33cad096f2299a66b1be15 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 26 Oct 2022 17:21:52 +0800 Subject: [PATCH 230/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=20deploy=20p?= =?UTF-8?q?laybook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.po | 637 ++++++++++-------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 633 +++++++++-------- .../migrations/0014_organization_builtin.py | 30 + apps/orgs/models.py | 5 +- apps/orgs/signal_handlers/common.py | 6 +- apps/terminal/automations/__init__.py | 0 .../automations/deploy_applet_host/manager.py | 0 .../deploy_applet_host/playbook.yml | 56 ++ .../migrations/0055_auto_20221026_1631.py | 26 + apps/terminal/models/applet/applet.py | 3 +- 11 files changed, 831 insertions(+), 569 deletions(-) create mode 100644 apps/orgs/migrations/0014_organization_builtin.py create mode 100644 apps/terminal/automations/__init__.py create mode 100644 apps/terminal/automations/deploy_applet_host/manager.py create mode 100644 apps/terminal/automations/deploy_applet_host/playbook.yml create mode 100644 apps/terminal/migrations/0055_auto_20221026_1631.py diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index abb6e56fb..545f1231e 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-20 20:21+0800\n" +"POT-Creation-Date: 2022-10-26 16:41+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,36 +29,37 @@ msgstr "Acls" #: assets/models/domain.py:24 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:22 #: assets/models/platform.py:68 assets/serializers/asset/common.py:86 -#: assets/serializers/platform.py:104 ops/mixin.py:22 ops/models/playbook.py:9 +#: assets/serializers/platform.py:104 ops/mixin.py:20 ops/models/playbook.py:9 #: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86 -#: terminal/models/storage.py:26 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:33 +#: terminal/models/applet/applet.py:25 terminal/models/component/endpoint.py:11 +#: terminal/models/component/endpoint.py:87 +#: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 +#: terminal/models/component/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 #: xpack/plugins/cloud/models.py:30 msgid "Name" msgstr "名前" #: acls/models/base.py:27 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:89 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:90 msgid "Priority" msgstr "優先順位" #: acls/models/base.py:28 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:90 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:91 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" #: acls/models/base.py:31 authentication/models.py:22 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:74 terminal/models/sharing.py:28 +#: perms/models/asset_permission.py:74 terminal/models/session/sharing.py:28 #: tickets/const.py:38 msgid "Active" msgstr "アクティブ" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:101 assets/models/automations/base.py:24 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:25 #: assets/models/backup.py:30 assets/models/base.py:66 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 @@ -66,9 +67,12 @@ msgstr "アクティブ" #: assets/models/platform.py:73 ops/models/playbook.py:11 #: ops/models/playbook.py:25 orgs/models.py:73 #: perms/models/asset_permission.py:84 rbac/models/role.py:37 -#: settings/models.py:38 terminal/models/endpoint.py:23 -#: terminal/models/endpoint.py:96 terminal/models/storage.py:29 -#: terminal/models/terminal.py:114 tickets/models/comment.py:32 +#: settings/models.py:38 terminal/models/applet/applet.py:33 +#: terminal/models/applet/applet.py:62 terminal/models/applet/host.py:12 +#: terminal/models/applet/host.py:28 terminal/models/component/endpoint.py:24 +#: terminal/models/component/endpoint.py:97 +#: terminal/models/component/storage.py:28 +#: terminal/models/component/terminal.py:114 tickets/models/comment.py:32 #: tickets/models/ticket/general.py:288 users/models/group.py:16 #: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 @@ -93,14 +97,14 @@ msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:62 audits/models.py:87 authentication/models.py:55 -#: authentication/models.py:79 perms/models/asset_permission.py:58 +#: authentication/models.py:72 perms/models/asset_permission.py:58 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:30 -#: terminal/models/sharing.py:33 terminal/notifications.py:91 -#: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14 -#: users/models/user.py:895 users/models/user.py:926 -#: users/serializers/group.py:19 +#: terminal/backends/command/serializers.py:13 +#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 +#: terminal/notifications.py:91 terminal/notifications.py:139 +#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:895 +#: users/models/user.py:926 users/serializers/group.py:19 msgid "User" msgstr "ユーザー" @@ -125,8 +129,8 @@ msgid "Login acl" msgstr "ログインacl" #: acls/models/login_asset_acl.py:21 assets/models/account.py:57 -#: authentication/models.py:88 ops/models/base.py:18 -#: terminal/models/session.py:34 xpack/plugins/cloud/models.py:87 +#: authentication/models.py:82 ops/models/base.py:18 +#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "アカウント" @@ -135,10 +139,10 @@ msgstr "アカウント" #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:58 assets/serializers/label.py:30 -#: audits/models.py:39 authentication/models.py:67 authentication/models.py:84 +#: audits/models.py:39 authentication/models.py:77 #: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:32 -#: terminal/notifications.py:90 +#: terminal/backends/command/serializers.py:14 +#: terminal/models/session/session.py:32 terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:200 #: xpack/plugins/change_auth_plan/serializers/asset.py:172 #: xpack/plugins/cloud/models.py:219 @@ -161,7 +165,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 #: assets/models/base.py:60 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:248 +#: authentication/models.py:245 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: users/forms/profile.py:32 users/models/user.py:663 @@ -246,10 +250,11 @@ msgid "Category" msgstr "カテゴリ" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:22 assets/models/cmd_filter.py:74 +#: assets/models/automations/base.py:23 assets/models/cmd_filter.py:74 #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 -#: assets/serializers/platform.py:75 authentication/models.py:71 -#: terminal/models/storage.py:58 terminal/models/storage.py:143 +#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:29 +#: terminal/models/component/storage.py:57 +#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 @@ -263,7 +268,7 @@ msgstr "タイプ" msgid "Attrs" msgstr "ツールバーの" -#: applications/models.py:23 authentication/models.py:68 +#: applications/models.py:23 msgid "Application" msgstr "アプリケーション" @@ -409,7 +414,8 @@ msgid "Replace (The key generated by JumpServer) " msgstr "置換(JumpServerによって生成された鍵)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 +#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:60 +#: terminal/models/applet/host.py:11 terminal/models/component/endpoint.py:12 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "ホスト" @@ -430,11 +436,12 @@ msgstr "データベース" msgid "Cloud service" msgstr "クラウドセンター" -#: assets/const/category.py:15 +#: assets/const/category.py:15 terminal/models/applet/applet.py:18 msgid "Web" msgstr "" -#: assets/const/device.py:7 tickets/const.py:8 +#: assets/const/device.py:7 terminal/models/applet/applet.py:17 +#: tickets/const.py:8 msgid "General" msgstr "一般" @@ -489,7 +496,7 @@ msgstr "SSH秘密鍵" msgid "SSH public key" msgstr "SSHパブリックキー" -#: assets/models/_user.py:41 assets/models/automations/base.py:78 +#: assets/models/_user.py:41 assets/models/automations/base.py:86 #: assets/models/base.py:67 assets/models/domain.py:26 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 @@ -518,8 +525,8 @@ msgid "Username same with user" msgstr "ユーザーと同じユーザー名" #: assets/models/_user.py:48 assets/models/domain.py:67 -#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 -#: terminal/serializers/storage.py:68 +#: terminal/models/applet/applet.py:31 terminal/serializers/session.py:18 +#: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "プロトコル" @@ -578,6 +585,7 @@ msgid "Su from" msgstr "から切り替え" #: assets/models/account.py:53 settings/serializers/auth/cas.py:18 +#: terminal/models/applet/applet.py:27 msgid "Version" msgstr "バージョン" @@ -610,32 +618,33 @@ msgstr "アカウント名" msgid "Port" msgstr "ポート" -#: assets/models/asset/common.py:94 assets/models/platform.py:104 +#: assets/models/asset/common.py:93 assets/models/platform.py:104 #: assets/serializers/asset/common.py:65 #: perms/serializers/user_permission.py:21 #: xpack/plugins/cloud/serializers/account_attrs.py:172 msgid "Platform" msgstr "プラットフォーム" -#: assets/models/asset/common.py:96 assets/models/domain.py:29 +#: assets/models/asset/common.py:95 assets/models/domain.py:29 #: assets/models/domain.py:68 assets/serializers/asset/common.py:64 msgid "Domain" msgstr "ドメイン" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:17 +#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 #: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "ノード" -#: assets/models/asset/common.py:99 assets/models/automations/base.py:23 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:24 #: assets/models/cmd_filter.py:39 assets/models/domain.py:70 -#: assets/models/label.py:21 users/serializers/user.py:147 +#: assets/models/label.py:21 terminal/models/applet/applet.py:30 +#: users/serializers/user.py:147 msgid "Is active" msgstr "アクティブです。" -#: assets/models/asset/common.py:100 assets/serializers/asset/common.py:67 +#: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 msgid "Labels" msgstr "ラベル" @@ -697,28 +706,28 @@ msgstr "パスワードルール" msgid "Submit selector" msgstr "" -#: assets/models/automations/base.py:15 assets/models/cmd_filter.py:38 +#: assets/models/automations/base.py:16 assets/models/cmd_filter.py:38 #: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 #: rbac/tree.py:37 msgid "Accounts" msgstr "アカウント" -#: assets/models/automations/base.py:20 assets/serializers/domain.py:29 +#: assets/models/automations/base.py:21 assets/serializers/domain.py:29 #: ops/models/base.py:17 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "資産" -#: assets/models/automations/base.py:68 assets/models/automations/base.py:75 +#: assets/models/automations/base.py:76 assets/models/automations/base.py:83 #, fuzzy #| msgid "Automatic managed" msgid "Automation task" msgstr "自動管理" -#: assets/models/automations/base.py:79 assets/models/backup.py:77 +#: assets/models/automations/base.py:87 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/session.py:43 +#: perms/models/asset_permission.py:76 terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:21 #: xpack/plugins/change_auth_plan/models/base.py:108 @@ -727,63 +736,63 @@ msgstr "自動管理" msgid "Date start" msgstr "開始日" -#: assets/models/automations/base.py:80 -#: assets/models/automations/change_secret.py:67 ops/models/base.py:55 +#: assets/models/automations/base.py:88 +#: assets/models/automations/change_secret.py:59 ops/models/base.py:55 msgid "Date finished" msgstr "終了日" -#: assets/models/automations/base.py:82 +#: assets/models/automations/base.py:90 #, fuzzy #| msgid "Relation snapshot" msgid "Automation snapshot" msgstr "製造オーダスナップショット" -#: assets/models/automations/base.py:86 assets/models/backup.py:88 +#: assets/models/automations/base.py:94 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "トリガーモード" -#: assets/models/automations/base.py:90 +#: assets/models/automations/base.py:98 #, fuzzy #| msgid "Command execution" msgid "Automation task execution" msgstr "コマンド実行" -#: assets/models/automations/change_secret.py:17 assets/models/base.py:62 +#: assets/models/automations/change_secret.py:16 assets/models/base.py:62 #, fuzzy #| msgid "Secret key" msgid "Secret type" msgstr "秘密キー" -#: assets/models/automations/change_secret.py:21 +#: assets/models/automations/change_secret.py:20 #, fuzzy #| msgid "SSH Key strategy" msgid "Secret strategy" msgstr "SSHキー戦略" -#: assets/models/automations/change_secret.py:23 -#: assets/models/automations/change_secret.py:65 assets/models/base.py:64 -#: assets/serializers/account/base.py:17 authentication/models.py:73 -#: authentication/models.py:249 +#: assets/models/automations/change_secret.py:22 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:64 +#: assets/serializers/account/base.py:17 authentication/models.py:66 +#: authentication/models.py:246 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "ひみつ" -#: assets/models/automations/change_secret.py:24 +#: assets/models/automations/change_secret.py:23 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" msgstr "パスワードルール" -#: assets/models/automations/change_secret.py:27 +#: assets/models/automations/change_secret.py:26 #, fuzzy #| msgid "SSH Key strategy" msgid "SSH key change strategy" msgstr "SSHキー戦略" -#: assets/models/automations/change_secret.py:29 assets/models/backup.py:28 +#: assets/models/automations/change_secret.py:28 assets/models/backup.py:28 #: assets/serializers/account/backup.py:28 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 @@ -791,31 +800,31 @@ msgstr "SSHキー戦略" msgid "Recipient" msgstr "受信者" -#: assets/models/automations/change_secret.py:36 +#: assets/models/automations/change_secret.py:35 #, fuzzy #| msgid "Change auth" msgid "Change secret automation" msgstr "秘密を改める" -#: assets/models/automations/change_secret.py:64 +#: assets/models/automations/change_secret.py:56 #, fuzzy #| msgid "Secret" msgid "Old secret" msgstr "ひみつ" -#: assets/models/automations/change_secret.py:66 +#: assets/models/automations/change_secret.py:58 #, fuzzy #| msgid "Date start" msgid "Date started" msgstr "開始日" -#: assets/models/automations/change_secret.py:69 +#: assets/models/automations/change_secret.py:61 #, fuzzy #| msgid "WeCom Error" msgid "Error" msgstr "企業微信エラー" -#: assets/models/automations/change_secret.py:72 +#: assets/models/automations/change_secret.py:64 #, fuzzy #| msgid "Change auth" msgid "Change secret record" @@ -827,7 +836,7 @@ msgstr "秘密を改める" msgid "Discovery account automation" msgstr "パスワード/キーの確認" -#: assets/models/automations/gather_facts.py:11 +#: assets/models/automations/gather_facts.py:15 #, fuzzy #| msgid "Gather assets users" msgid "Gather asset facts" @@ -863,7 +872,7 @@ msgid "Account backup snapshot" msgstr "アカウントのバックアップスナップショット" #: assets/models/backup.py:91 audits/models.py:127 -#: terminal/models/sharing.py:108 +#: terminal/models/session/sharing.py:108 #: xpack/plugins/change_auth_plan/models/base.py:197 #: xpack/plugins/change_auth_plan/serializers/asset.py:171 #: xpack/plugins/cloud/models.py:175 @@ -884,7 +893,7 @@ msgstr "アカウントバックアップの実行" msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:32 authentication/models.py:251 +#: assets/models/base.py:32 authentication/models.py:248 msgid "Date verified" msgstr "確認済みの日付" @@ -906,7 +915,7 @@ msgid "Regex" msgstr "正規情報" #: assets/models/cmd_filter.py:60 terminal/backends/command/serializers.py:15 -#: terminal/models/session.py:41 +#: terminal/models/session/session.py:41 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -1203,7 +1212,7 @@ msgstr "ひみつ" msgid "Account template not found" msgstr "" -#: assets/serializers/account/backup.py:27 ops/mixin.py:104 +#: assets/serializers/account/backup.py:27 ops/mixin.py:102 #: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" @@ -1237,13 +1246,13 @@ msgstr "プロトコル" msgid "Address" msgstr "アドレス" -#: assets/serializers/asset/common.py:136 +#: assets/serializers/asset/common.py:138 #, fuzzy #| msgid "Application not exists" msgid "Platform not exist" msgstr "アプリが存在しません" -#: assets/serializers/asset/common.py:152 +#: assets/serializers/asset/common.py:154 #, fuzzy #| msgid "Protocol duplicate: {}" msgid "Protocol is required: {}" @@ -1506,7 +1515,7 @@ msgid "Symlink" msgstr "Symlink" #: audits/models.py:38 audits/models.py:66 audits/models.py:89 -#: terminal/models/session.py:37 terminal/models/sharing.py:96 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 msgid "Remote addr" msgstr "リモートaddr" @@ -1518,8 +1527,8 @@ msgstr "操作" msgid "Filename" msgstr "ファイル名" -#: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:104 -#: tickets/views/approve.py:114 +#: audits/models.py:43 audits/models.py:117 +#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" msgstr "成功" @@ -1597,7 +1606,9 @@ msgstr "ユーザーエージェント" msgid "MFA" msgstr "MFA" -#: audits/models.py:128 ops/models/base.py:48 terminal/models/status.py:33 +#: audits/models.py:128 ops/models/base.py:48 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:15 +#: terminal/models/applet/host.py:27 terminal/models/component/status.py:33 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 #: xpack/plugins/cloud/models.py:223 msgid "Status" @@ -1661,7 +1672,7 @@ msgstr "本を飛ばす" msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:56 authentication/models.py:255 +#: audits/signal_handlers.py:56 authentication/models.py:252 msgid "Temporary token" msgstr "仮パスワード" @@ -1814,7 +1825,7 @@ msgstr "" msgid "Invalid token or cache refreshed." msgstr "無効なトークンまたはキャッシュの更新。" -#: authentication/backends/oauth2/backends.py:155 authentication/models.py:146 +#: authentication/backends/oauth2/backends.py:155 authentication/models.py:143 msgid "User invalid, disabled or expired" msgstr "ユーザーが無効、無効、または期限切れです" @@ -2089,70 +2100,76 @@ msgstr "期限切れ" msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:75 authentication/models.py:252 +#: authentication/models.py:68 authentication/models.py:249 #: perms/models/asset_permission.py:79 #: tickets/models/ticket/apply_application.py:29 #: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 msgid "Date expired" msgstr "期限切れの日付" -#: authentication/models.py:82 rbac/serializers/rolebinding.py:21 +#: authentication/models.py:75 rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "ユーザー表示" -#: authentication/models.py:87 +#: authentication/models.py:80 msgid "Asset display" msgstr "アセット名" -#: authentication/models.py:92 +#: authentication/models.py:85 +#, fuzzy +#| msgid "Action display" +msgid "Account display" +msgstr "アクション表示" + +#: authentication/models.py:89 msgid "Connection token" msgstr "接続トークン" -#: authentication/models.py:94 +#: authentication/models.py:91 msgid "Can view connection token secret" msgstr "接続トークンの秘密を表示できます" -#: authentication/models.py:137 +#: authentication/models.py:134 msgid "Connection token expired at: {}" msgstr "接続トークンの有効期限: {}" -#: authentication/models.py:142 +#: authentication/models.py:139 msgid "User not exists" msgstr "ユーザーは存在しません" -#: authentication/models.py:151 +#: authentication/models.py:148 msgid "System user not exists" msgstr "システムユーザーが存在しません" -#: authentication/models.py:157 +#: authentication/models.py:154 msgid "Asset not exists" msgstr "アセットが存在しません" -#: authentication/models.py:161 +#: authentication/models.py:158 msgid "Asset inactive" msgstr "アセットがアクティブ化されていません" -#: authentication/models.py:168 +#: authentication/models.py:165 msgid "User has no permission to access asset or permission expired" msgstr "" "ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて" "います" -#: authentication/models.py:176 +#: authentication/models.py:173 msgid "Application not exists" msgstr "アプリが存在しません" -#: authentication/models.py:183 +#: authentication/models.py:180 msgid "User has no permission to access application or permission expired" msgstr "" "ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れてい" "ます" -#: authentication/models.py:250 +#: authentication/models.py:247 msgid "Verified" msgstr "確認済み" -#: authentication/models.py:271 +#: authentication/models.py:268 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2818,19 +2835,19 @@ msgstr "メール" msgid "Site message" msgstr "サイトメッセージ" -#: ops/ansible/inventory.py:76 +#: ops/ansible/inventory.py:75 #, fuzzy #| msgid "Account unavailable" msgid "No account available" msgstr "利用できないアカウント" -#: ops/ansible/inventory.py:171 +#: ops/ansible/inventory.py:173 #, fuzzy #| msgid "User disabled." msgid "Ansible disabled" msgstr "ユーザーが無効になりました。" -#: ops/ansible/inventory.py:186 +#: ops/ansible/inventory.py:189 msgid "Skip hosts below:" msgstr "" @@ -2866,28 +2883,28 @@ msgstr "パスワードの変更" msgid "Custom password" msgstr "カスタムパスワード" -#: ops/mixin.py:27 ops/mixin.py:90 settings/serializers/auth/ldap.py:72 +#: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "サイクル実行" -#: ops/mixin.py:31 ops/mixin.py:88 ops/mixin.py:107 +#: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 #: settings/serializers/auth/ldap.py:69 msgid "Regularly perform" msgstr "定期的に実行する" -#: ops/mixin.py:110 +#: ops/mixin.py:108 msgid "Interval" msgstr "間隔" -#: ops/mixin.py:120 +#: ops/mixin.py:118 msgid "* Please enter a valid crontab expression" msgstr "* 有効なcrontab式を入力してください" -#: ops/mixin.py:127 +#: ops/mixin.py:125 msgid "Range {} to {}" msgstr "{} から {} までの範囲" -#: ops/mixin.py:138 +#: ops/mixin.py:136 msgid "Require periodic or regularly perform setting" msgstr "定期的または定期的に設定を行う必要があります" @@ -2899,7 +2916,8 @@ msgstr "パターン" msgid "Module" msgstr "" -#: ops/models/adhoc.py:20 ops/models/celery.py:15 terminal/models/task.py:17 +#: ops/models/adhoc.py:20 ops/models/celery.py:15 +#: terminal/models/component/task.py:17 msgid "Args" msgstr "アルグ" @@ -2917,7 +2935,8 @@ msgstr "" msgid "AdHoc execution" msgstr "アドホックエキューション" -#: ops/models/base.py:16 ops/models/base.py:52 terminal/models/sharing.py:24 +#: ops/models/base.py:16 ops/models/base.py:52 +#: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "作成者" @@ -2941,7 +2960,7 @@ msgstr "結果" msgid "Summary" msgstr "" -#: ops/models/celery.py:16 terminal/models/task.py:18 +#: ops/models/celery.py:16 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "クワーグ" @@ -2950,8 +2969,8 @@ msgstr "クワーグ" msgid "State" msgstr "状態" -#: ops/models/celery.py:18 terminal/models/sharing.py:111 tickets/const.py:25 -#: xpack/plugins/change_auth_plan/models/base.py:188 +#: ops/models/celery.py:18 terminal/models/session/sharing.py:111 +#: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "終了" @@ -2975,7 +2994,7 @@ msgstr "" msgid "Template" msgstr "テンプレート" -#: ops/models/playbook.py:38 terminal/models/task.py:26 +#: ops/models/playbook.py:38 terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "タスク" @@ -3366,7 +3385,7 @@ msgstr "パーマ" msgid "Scope display" msgstr "スコープ表示" -#: rbac/serializers/role.py:27 +#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:26 msgid "Display name" msgstr "表示名" @@ -4630,8 +4649,8 @@ msgstr "期限切れです。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" @@ -4652,34 +4671,34 @@ msgid "" " " msgstr "" "\n" -" クリックしてください リンク パスワードの更新\n" +" クリックしてください リンク パスワードの更新\n" " " #: templates/_message.html:43 #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" -" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" +" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" " " #: templates/_message.html:56 #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" +" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -4743,62 +4762,62 @@ msgstr "" msgid "Offline video player" msgstr "オフラインビデオプレーヤー" -#: terminal/api/endpoint.py:33 +#: terminal/api/component/endpoint.py:33 msgid "Not found protocol query params" msgstr "プロトコルクエリパラメータが見つかりません" -#: terminal/api/session.py:216 -msgid "Session does not exist: {}" -msgstr "セッションが存在しません: {}" - -#: terminal/api/session.py:219 -msgid "Session is finished or the protocol not supported" -msgstr "セッションが終了したか、プロトコルがサポートされていません" - -#: terminal/api/session.py:224 -msgid "User does not exist: {}" -msgstr "ユーザーが存在しない: {}" - -#: terminal/api/session.py:232 -msgid "User does not have permission" -msgstr "ユーザーに権限がありません" - -#: terminal/api/sharing.py:30 -msgid "Secure session sharing settings is disabled" -msgstr "安全なセッション共有設定が無効になっています" - -#: terminal/api/storage.py:28 +#: terminal/api/component/storage.py:28 msgid "Deleting the default storage is not allowed" msgstr "デフォルトのストレージの削除は許可されていません" -#: terminal/api/storage.py:31 +#: terminal/api/component/storage.py:31 msgid "Cannot delete storage that is being used" msgstr "使用中のストレージを削除できません" -#: terminal/api/storage.py:72 terminal/api/storage.py:73 +#: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 msgid "Command storages" msgstr "コマンドストア" -#: terminal/api/storage.py:79 +#: terminal/api/component/storage.py:79 msgid "Invalid" msgstr "無効" -#: terminal/api/storage.py:119 +#: terminal/api/component/storage.py:119 msgid "Test failure: {}" msgstr "テスト失敗: {}" -#: terminal/api/storage.py:122 +#: terminal/api/component/storage.py:122 msgid "Test successful" msgstr "テスト成功" -#: terminal/api/storage.py:124 +#: terminal/api/component/storage.py:124 msgid "Test failure: Account invalid" msgstr "テスト失敗: アカウントが無効" -#: terminal/api/terminal.py:39 +#: terminal/api/component/terminal.py:39 msgid "Have online sessions" msgstr "オンラインセッションを持つ" +#: terminal/api/session/session.py:217 +msgid "Session does not exist: {}" +msgstr "セッションが存在しません: {}" + +#: terminal/api/session/session.py:220 +msgid "Session is finished or the protocol not supported" +msgstr "セッションが終了したか、プロトコルがサポートされていません" + +#: terminal/api/session/session.py:225 +msgid "User does not exist: {}" +msgstr "ユーザーが存在しない: {}" + +#: terminal/api/session/session.py:233 +msgid "User does not have permission" +msgstr "ユーザーに権限がありません" + +#: terminal/api/session/sharing.py:29 +msgid "Secure session sharing settings is disabled" +msgstr "安全なセッション共有設定が無効になっています" + #: terminal/apps.py:9 msgid "Terminals" msgstr "ターミナル管理" @@ -4828,8 +4847,8 @@ msgstr "入力" msgid "Output" msgstr "出力" -#: terminal/backends/command/models.py:25 terminal/models/replay.py:9 -#: terminal/models/sharing.py:19 terminal/models/sharing.py:78 +#: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 +#: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 #: terminal/templates/terminal/_msg_command_alert.html:10 #: tickets/models/ticket/command_confirm.py:17 msgid "Session" @@ -4852,7 +4871,8 @@ msgstr "リスクレベル表示" msgid "Timestamp" msgstr "タイムスタンプ" -#: terminal/backends/command/serializers.py:41 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:41 +#: terminal/models/component/terminal.py:105 msgid "Remote Address" msgstr "リモートアドレス" @@ -4880,209 +4900,264 @@ msgstr "一括作成非サポート" msgid "Storage is invalid" msgstr "ストレージが無効です" -#: terminal/models/command.py:66 -msgid "Command record" -msgstr "コマンドレコード" +#: terminal/models/applet/applet.py:21 +#, fuzzy +#| msgid "Manually input" +msgid "Manual" +msgstr "手動入力" -#: terminal/models/endpoint.py:13 +#: terminal/models/applet/applet.py:22 +msgid "Git" +msgstr "" + +#: terminal/models/applet/applet.py:23 +#, fuzzy +#| msgid "Remote app" +msgid "Remote gzip" +msgstr "リモートアプリ" + +#: terminal/models/applet/applet.py:28 +#, fuzzy +#| msgid "Auth url" +msgid "Author" +msgstr "認証アドレス" + +#: terminal/models/applet/applet.py:32 +msgid "Tags" +msgstr "" + +#: terminal/models/applet/applet.py:59 terminal/models/applet/host.py:17 +#, fuzzy +#| msgid "Apply assets" +msgid "Applet" +msgstr "資産の適用" + +#: terminal/models/applet/host.py:13 +#, fuzzy +#| msgid "Verify auth" +msgid "Account automation" +msgstr "パスワード/キーの確認" + +#: terminal/models/applet/host.py:14 +#, fuzzy +#| msgid "Date sync" +msgid "Date synced" +msgstr "日付の同期" + +#: terminal/models/applet/host.py:26 +#, fuzzy +#| msgid "Host" +msgid "Hosting" +msgstr "ホスト" + +#: terminal/models/component/endpoint.py:14 msgid "HTTPS Port" msgstr "HTTPS ポート" -#: terminal/models/endpoint.py:14 terminal/models/terminal.py:107 +#: terminal/models/component/endpoint.py:15 +#: terminal/models/component/terminal.py:107 msgid "HTTP Port" msgstr "HTTP ポート" -#: terminal/models/endpoint.py:15 terminal/models/terminal.py:106 +#: terminal/models/component/endpoint.py:16 +#: terminal/models/component/terminal.py:106 msgid "SSH Port" msgstr "SSH ポート" -#: terminal/models/endpoint.py:16 +#: terminal/models/component/endpoint.py:17 msgid "RDP Port" msgstr "RDP ポート" -#: terminal/models/endpoint.py:17 +#: terminal/models/component/endpoint.py:18 msgid "MySQL Port" msgstr "MySQL ポート" -#: terminal/models/endpoint.py:18 +#: terminal/models/component/endpoint.py:19 msgid "MariaDB Port" msgstr "MariaDB ポート" -#: terminal/models/endpoint.py:19 +#: terminal/models/component/endpoint.py:20 msgid "PostgreSQL Port" msgstr "PostgreSQL ポート" -#: terminal/models/endpoint.py:20 +#: terminal/models/component/endpoint.py:21 msgid "Redis Port" msgstr "Redis ポート" -#: terminal/models/endpoint.py:21 +#: terminal/models/component/endpoint.py:22 msgid "Oracle 11g Port" msgstr "Oracle 11g ポート" -#: terminal/models/endpoint.py:22 +#: terminal/models/component/endpoint.py:23 msgid "Oracle 12c Port" msgstr "Oracle 12c ポート" -#: terminal/models/endpoint.py:28 terminal/models/endpoint.py:94 -#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 -#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 -#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 +#: terminal/models/component/endpoint.py:29 +#: terminal/models/component/endpoint.py:95 terminal/serializers/endpoint.py:57 +#: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50 +#: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 +#: terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "エンドポイント" -#: terminal/models/endpoint.py:87 +#: terminal/models/component/endpoint.py:88 msgid "IP group" msgstr "IP グループ" -#: terminal/models/endpoint.py:99 +#: terminal/models/component/endpoint.py:100 msgid "Endpoint rule" msgstr "エンドポイントルール" -#: terminal/models/replay.py:12 -msgid "Session replay" -msgstr "セッション再生" - -#: terminal/models/replay.py:14 -msgid "Can upload session replay" -msgstr "セッションのリプレイをアップロードできます" - -#: terminal/models/replay.py:15 -msgid "Can download session replay" -msgstr "セッション再生をダウンロードできます" - -#: terminal/models/session.py:36 terminal/models/sharing.py:101 -msgid "Login from" -msgstr "ログイン元" - -#: terminal/models/session.py:40 -msgid "Replay" -msgstr "リプレイ" - -#: terminal/models/session.py:44 -msgid "Date end" -msgstr "終了日" - -#: terminal/models/session.py:236 -msgid "Session record" -msgstr "セッション記録" - -#: terminal/models/session.py:238 -msgid "Can monitor session" -msgstr "セッションを監視できます" - -#: terminal/models/session.py:239 -msgid "Can share session" -msgstr "セッションを共有できます" - -#: terminal/models/session.py:240 -msgid "Can terminate session" -msgstr "セッションを終了できます" - -#: terminal/models/session.py:241 -msgid "Can validate session action perm" -msgstr "セッションアクションのパーマを検証できます" - -#: terminal/models/sharing.py:26 terminal/models/sharing.py:80 -msgid "Verify code" -msgstr "コードの確認" - -#: terminal/models/sharing.py:31 -msgid "Expired time (min)" -msgstr "期限切れ時間 (分)" - -#: terminal/models/sharing.py:37 terminal/models/sharing.py:83 -msgid "Session sharing" -msgstr "セッション共有" - -#: terminal/models/sharing.py:39 -msgid "Can add super session sharing" -msgstr "スーパーセッション共有を追加できます" - -#: terminal/models/sharing.py:66 -msgid "Link not active" -msgstr "リンクがアクティブでない" - -#: terminal/models/sharing.py:68 -msgid "Link expired" -msgstr "リンク期限切れ" - -#: terminal/models/sharing.py:70 -msgid "User not allowed to join" -msgstr "ユーザーはセッションに参加できません" - -#: terminal/models/sharing.py:87 terminal/serializers/sharing.py:59 -msgid "Joiner" -msgstr "ジョイナー" - -#: terminal/models/sharing.py:90 -msgid "Date joined" -msgstr "参加日" - -#: terminal/models/sharing.py:93 -msgid "Date left" -msgstr "日付が残っています" - -#: terminal/models/sharing.py:116 -msgid "Session join record" -msgstr "セッション参加記録" - -#: terminal/models/sharing.py:132 -msgid "Invalid verification code" -msgstr "検証コードが無効" - -#: terminal/models/status.py:18 +#: terminal/models/component/status.py:18 msgid "Session Online" msgstr "セッションオンライン" -#: terminal/models/status.py:19 +#: terminal/models/component/status.py:19 msgid "CPU Load" msgstr "CPUロード" -#: terminal/models/status.py:20 +#: terminal/models/component/status.py:20 msgid "Memory Used" msgstr "使用メモリ" -#: terminal/models/status.py:21 +#: terminal/models/component/status.py:21 msgid "Disk Used" msgstr "使用済みディスク" -#: terminal/models/status.py:22 +#: terminal/models/component/status.py:22 msgid "Connections" msgstr "接続" -#: terminal/models/status.py:23 +#: terminal/models/component/status.py:23 msgid "Threads" msgstr "スレッド" -#: terminal/models/status.py:24 +#: terminal/models/component/status.py:24 msgid "Boot Time" msgstr "ブート時間" -#: terminal/models/storage.py:28 +#: terminal/models/component/storage.py:27 msgid "Default storage" msgstr "デフォルトのストレージ" -#: terminal/models/storage.py:137 terminal/models/terminal.py:108 +#: terminal/models/component/storage.py:136 +#: terminal/models/component/terminal.py:108 msgid "Command storage" msgstr "コマンドストレージ" -#: terminal/models/storage.py:197 terminal/models/terminal.py:109 +#: terminal/models/component/storage.py:196 +#: terminal/models/component/terminal.py:109 msgid "Replay storage" msgstr "再生ストレージ" -#: terminal/models/terminal.py:103 +#: terminal/models/component/terminal.py:103 msgid "type" msgstr "タイプ" -#: terminal/models/terminal.py:183 +#: terminal/models/component/terminal.py:183 msgid "Terminal" msgstr "ターミナル" -#: terminal/models/terminal.py:185 +#: terminal/models/component/terminal.py:185 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" +#: terminal/models/session/command.py:66 +msgid "Command record" +msgstr "コマンドレコード" + +#: terminal/models/session/replay.py:12 +msgid "Session replay" +msgstr "セッション再生" + +#: terminal/models/session/replay.py:14 +msgid "Can upload session replay" +msgstr "セッションのリプレイをアップロードできます" + +#: terminal/models/session/replay.py:15 +msgid "Can download session replay" +msgstr "セッション再生をダウンロードできます" + +#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 +msgid "Login from" +msgstr "ログイン元" + +#: terminal/models/session/session.py:40 +msgid "Replay" +msgstr "リプレイ" + +#: terminal/models/session/session.py:44 +msgid "Date end" +msgstr "終了日" + +#: terminal/models/session/session.py:236 +msgid "Session record" +msgstr "セッション記録" + +#: terminal/models/session/session.py:238 +msgid "Can monitor session" +msgstr "セッションを監視できます" + +#: terminal/models/session/session.py:239 +msgid "Can share session" +msgstr "セッションを共有できます" + +#: terminal/models/session/session.py:240 +msgid "Can terminate session" +msgstr "セッションを終了できます" + +#: terminal/models/session/session.py:241 +msgid "Can validate session action perm" +msgstr "セッションアクションのパーマを検証できます" + +#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 +msgid "Verify code" +msgstr "コードの確認" + +#: terminal/models/session/sharing.py:31 +msgid "Expired time (min)" +msgstr "期限切れ時間 (分)" + +#: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 +msgid "Session sharing" +msgstr "セッション共有" + +#: terminal/models/session/sharing.py:39 +msgid "Can add super session sharing" +msgstr "スーパーセッション共有を追加できます" + +#: terminal/models/session/sharing.py:66 +msgid "Link not active" +msgstr "リンクがアクティブでない" + +#: terminal/models/session/sharing.py:68 +msgid "Link expired" +msgstr "リンク期限切れ" + +#: terminal/models/session/sharing.py:70 +msgid "User not allowed to join" +msgstr "ユーザーはセッションに参加できません" + +#: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 +msgid "Joiner" +msgstr "ジョイナー" + +#: terminal/models/session/sharing.py:90 +msgid "Date joined" +msgstr "参加日" + +#: terminal/models/session/sharing.py:93 +msgid "Date left" +msgstr "日付が残っています" + +#: terminal/models/session/sharing.py:116 +msgid "Session join record" +msgstr "セッション参加記録" + +#: terminal/models/session/sharing.py:132 +msgid "Invalid verification code" +msgstr "検証コードが無効" + #: terminal/notifications.py:22 msgid "Sessions" msgstr "セッション" @@ -5099,6 +5174,10 @@ msgstr "レベル" msgid "Batch danger command alert" msgstr "一括危険コマンド警告" +#: terminal/serializers/applet.py:19 +msgid "Icon" +msgstr "" + #: terminal/serializers/endpoint.py:12 msgid "Oracle port" msgstr "" @@ -6925,9 +7004,6 @@ msgstr "コミュニティ版" #~ msgid "Unsupported protocols: {}" #~ msgstr "サポートされていないプロトコル: {}" -#~ msgid "Remote app" -#~ msgstr "リモートアプリ" - #~ msgid "Custom" #~ msgstr "カスタム" @@ -7040,9 +7116,6 @@ msgstr "コミュニティ版" #~ msgid "CPU info" #~ msgstr "CPU情報" -#~ msgid "Action display" -#~ msgstr "アクション表示" - #~ msgid "Applications amount" #~ msgstr "申し込み金額" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 2c51cf456..59384abe0 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:ab1c609cc4c83a223835be0eab2a5a5b9050c853b66ccd2b2fa480073c8fc763 -size 103346 +oid sha256:5d945fc6151d6f6354052a0a09706a52e7a2fa2f9e02254965cf26b134d78c3a +size 103377 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index d5c183d17..06af10e1f 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-10-20 20:21+0800\n" +"POT-Creation-Date: 2022-10-26 16:41+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -28,36 +28,37 @@ msgstr "访问控制" #: assets/models/domain.py:24 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:22 #: assets/models/platform.py:68 assets/serializers/asset/common.py:86 -#: assets/serializers/platform.py:104 ops/mixin.py:22 ops/models/playbook.py:9 +#: assets/serializers/platform.py:104 ops/mixin.py:20 ops/models/playbook.py:9 #: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/endpoint.py:10 terminal/models/endpoint.py:86 -#: terminal/models/storage.py:26 terminal/models/task.py:16 -#: terminal/models/terminal.py:100 users/forms/profile.py:33 +#: terminal/models/applet/applet.py:25 terminal/models/component/endpoint.py:11 +#: terminal/models/component/endpoint.py:87 +#: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 +#: terminal/models/component/terminal.py:100 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 #: xpack/plugins/cloud/models.py:30 msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:89 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:90 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/endpoint.py:90 +#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:91 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" #: acls/models/base.py:31 authentication/models.py:22 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:74 terminal/models/sharing.py:28 +#: perms/models/asset_permission.py:74 terminal/models/session/sharing.py:28 #: tickets/const.py:38 msgid "Active" msgstr "激活中" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:101 assets/models/automations/base.py:24 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:25 #: assets/models/backup.py:30 assets/models/base.py:66 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 @@ -65,9 +66,12 @@ msgstr "激活中" #: assets/models/platform.py:73 ops/models/playbook.py:11 #: ops/models/playbook.py:25 orgs/models.py:73 #: perms/models/asset_permission.py:84 rbac/models/role.py:37 -#: settings/models.py:38 terminal/models/endpoint.py:23 -#: terminal/models/endpoint.py:96 terminal/models/storage.py:29 -#: terminal/models/terminal.py:114 tickets/models/comment.py:32 +#: settings/models.py:38 terminal/models/applet/applet.py:33 +#: terminal/models/applet/applet.py:62 terminal/models/applet/host.py:12 +#: terminal/models/applet/host.py:28 terminal/models/component/endpoint.py:24 +#: terminal/models/component/endpoint.py:97 +#: terminal/models/component/storage.py:28 +#: terminal/models/component/terminal.py:114 tickets/models/comment.py:32 #: tickets/models/ticket/general.py:288 users/models/group.py:16 #: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 @@ -92,14 +96,14 @@ msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 #: audits/models.py:62 audits/models.py:87 authentication/models.py:55 -#: authentication/models.py:79 perms/models/asset_permission.py:58 +#: authentication/models.py:72 perms/models/asset_permission.py:58 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:30 -#: terminal/models/sharing.py:33 terminal/notifications.py:91 -#: terminal/notifications.py:139 tickets/models/comment.py:21 users/const.py:14 -#: users/models/user.py:895 users/models/user.py:926 -#: users/serializers/group.py:19 +#: terminal/backends/command/serializers.py:13 +#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 +#: terminal/notifications.py:91 terminal/notifications.py:139 +#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:895 +#: users/models/user.py:926 users/serializers/group.py:19 msgid "User" msgstr "用户" @@ -124,8 +128,8 @@ msgid "Login acl" msgstr "登录访问控制" #: acls/models/login_asset_acl.py:21 assets/models/account.py:57 -#: authentication/models.py:88 ops/models/base.py:18 -#: terminal/models/session.py:34 xpack/plugins/cloud/models.py:87 +#: authentication/models.py:82 ops/models/base.py:18 +#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "账号" @@ -134,10 +138,10 @@ msgstr "账号" #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:58 assets/serializers/label.py:30 -#: audits/models.py:39 authentication/models.py:67 authentication/models.py:84 +#: audits/models.py:39 authentication/models.py:77 #: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:32 -#: terminal/notifications.py:90 +#: terminal/backends/command/serializers.py:14 +#: terminal/models/session/session.py:32 terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:200 #: xpack/plugins/change_auth_plan/serializers/asset.py:172 #: xpack/plugins/cloud/models.py:219 @@ -160,7 +164,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 #: assets/models/base.py:60 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:248 +#: authentication/models.py:245 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: users/forms/profile.py:32 users/models/user.py:663 @@ -241,10 +245,11 @@ msgid "Category" msgstr "类别" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:22 assets/models/cmd_filter.py:74 +#: assets/models/automations/base.py:23 assets/models/cmd_filter.py:74 #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 -#: assets/serializers/platform.py:75 authentication/models.py:71 -#: terminal/models/storage.py:58 terminal/models/storage.py:143 +#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:29 +#: terminal/models/component/storage.py:57 +#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 @@ -258,7 +263,7 @@ msgstr "类型" msgid "Attrs" msgstr "属性" -#: applications/models.py:23 authentication/models.py:68 +#: applications/models.py:23 msgid "Application" msgstr "应用程序" @@ -392,7 +397,8 @@ msgid "Replace (The key generated by JumpServer) " msgstr "替换 (由 JumpServer 生成的密钥)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/endpoint.py:11 +#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:60 +#: terminal/models/applet/host.py:11 terminal/models/component/endpoint.py:12 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "主机" @@ -411,11 +417,12 @@ msgstr "数据库" msgid "Cloud service" msgstr "云服务" -#: assets/const/category.py:15 +#: assets/const/category.py:15 terminal/models/applet/applet.py:18 msgid "Web" msgstr "Web" -#: assets/const/device.py:7 tickets/const.py:8 +#: assets/const/device.py:7 terminal/models/applet/applet.py:17 +#: tickets/const.py:8 msgid "General" msgstr "通用" @@ -466,7 +473,7 @@ msgstr "SSH 密钥" msgid "SSH public key" msgstr "SSH 公钥" -#: assets/models/_user.py:41 assets/models/automations/base.py:78 +#: assets/models/_user.py:41 assets/models/automations/base.py:86 #: assets/models/base.py:67 assets/models/domain.py:26 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 @@ -495,8 +502,8 @@ msgid "Username same with user" msgstr "用户名与用户相同" #: assets/models/_user.py:48 assets/models/domain.py:67 -#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 -#: terminal/serializers/storage.py:68 +#: terminal/models/applet/applet.py:31 terminal/serializers/session.py:18 +#: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "协议" @@ -553,6 +560,7 @@ msgid "Su from" msgstr "切换自" #: assets/models/account.py:53 settings/serializers/auth/cas.py:18 +#: terminal/models/applet/applet.py:27 msgid "Version" msgstr "版本" @@ -583,32 +591,33 @@ msgstr "账号模版" msgid "Port" msgstr "端口" -#: assets/models/asset/common.py:94 assets/models/platform.py:104 +#: assets/models/asset/common.py:93 assets/models/platform.py:104 #: assets/serializers/asset/common.py:65 #: perms/serializers/user_permission.py:21 #: xpack/plugins/cloud/serializers/account_attrs.py:172 msgid "Platform" msgstr "资产平台" -#: assets/models/asset/common.py:96 assets/models/domain.py:29 +#: assets/models/asset/common.py:95 assets/models/domain.py:29 #: assets/models/domain.py:68 assets/serializers/asset/common.py:64 msgid "Domain" msgstr "网域" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:17 +#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 #: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "节点" -#: assets/models/asset/common.py:99 assets/models/automations/base.py:23 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:24 #: assets/models/cmd_filter.py:39 assets/models/domain.py:70 -#: assets/models/label.py:21 users/serializers/user.py:147 +#: assets/models/label.py:21 terminal/models/applet/applet.py:30 +#: users/serializers/user.py:147 msgid "Is active" msgstr "激活" -#: assets/models/asset/common.py:100 assets/serializers/asset/common.py:67 +#: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 msgid "Labels" msgstr "标签管理" @@ -666,26 +675,26 @@ msgstr "密码选择器" msgid "Submit selector" msgstr "提交按钮选择器" -#: assets/models/automations/base.py:15 assets/models/cmd_filter.py:38 +#: assets/models/automations/base.py:16 assets/models/cmd_filter.py:38 #: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 #: rbac/tree.py:37 msgid "Accounts" msgstr "账号管理" -#: assets/models/automations/base.py:20 assets/serializers/domain.py:29 +#: assets/models/automations/base.py:21 assets/serializers/domain.py:29 #: ops/models/base.py:17 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" -#: assets/models/automations/base.py:68 assets/models/automations/base.py:75 +#: assets/models/automations/base.py:76 assets/models/automations/base.py:83 msgid "Automation task" msgstr "自动化任务" -#: assets/models/automations/base.py:79 assets/models/backup.py:77 +#: assets/models/automations/base.py:87 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/session.py:43 +#: perms/models/asset_permission.py:76 terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:21 #: xpack/plugins/change_auth_plan/models/base.py:108 @@ -694,53 +703,53 @@ msgstr "自动化任务" msgid "Date start" msgstr "开始日期" -#: assets/models/automations/base.py:80 -#: assets/models/automations/change_secret.py:67 ops/models/base.py:55 +#: assets/models/automations/base.py:88 +#: assets/models/automations/change_secret.py:59 ops/models/base.py:55 msgid "Date finished" msgstr "结束日期" -#: assets/models/automations/base.py:82 +#: assets/models/automations/base.py:90 msgid "Automation snapshot" msgstr "自动化快照" -#: assets/models/automations/base.py:86 assets/models/backup.py:88 +#: assets/models/automations/base.py:94 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "触发模式" -#: assets/models/automations/base.py:90 +#: assets/models/automations/base.py:98 msgid "Automation task execution" msgstr "自动化任务执行" -#: assets/models/automations/change_secret.py:17 assets/models/base.py:62 +#: assets/models/automations/change_secret.py:16 assets/models/base.py:62 msgid "Secret type" msgstr "密文类型" -#: assets/models/automations/change_secret.py:21 +#: assets/models/automations/change_secret.py:20 msgid "Secret strategy" msgstr "密钥策略" -#: assets/models/automations/change_secret.py:23 -#: assets/models/automations/change_secret.py:65 assets/models/base.py:64 -#: assets/serializers/account/base.py:17 authentication/models.py:73 -#: authentication/models.py:249 +#: assets/models/automations/change_secret.py:22 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:64 +#: assets/serializers/account/base.py:17 authentication/models.py:66 +#: authentication/models.py:246 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "密钥" -#: assets/models/automations/change_secret.py:24 +#: assets/models/automations/change_secret.py:23 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" msgstr "密码规则" -#: assets/models/automations/change_secret.py:27 +#: assets/models/automations/change_secret.py:26 msgid "SSH key change strategy" msgstr "SSH 密钥策略" -#: assets/models/automations/change_secret.py:29 assets/models/backup.py:28 +#: assets/models/automations/change_secret.py:28 assets/models/backup.py:28 #: assets/serializers/account/backup.py:28 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 @@ -748,23 +757,23 @@ msgstr "SSH 密钥策略" msgid "Recipient" msgstr "收件人" -#: assets/models/automations/change_secret.py:36 +#: assets/models/automations/change_secret.py:35 msgid "Change secret automation" msgstr "自动化改密" -#: assets/models/automations/change_secret.py:64 +#: assets/models/automations/change_secret.py:56 msgid "Old secret" msgstr "原来密码" -#: assets/models/automations/change_secret.py:66 +#: assets/models/automations/change_secret.py:58 msgid "Date started" msgstr "开始日期" -#: assets/models/automations/change_secret.py:69 +#: assets/models/automations/change_secret.py:61 msgid "Error" msgstr "错误" -#: assets/models/automations/change_secret.py:72 +#: assets/models/automations/change_secret.py:64 msgid "Change secret record" msgstr "改密记录" @@ -772,7 +781,7 @@ msgstr "改密记录" msgid "Discovery account automation" msgstr "自动化账号发现" -#: assets/models/automations/gather_facts.py:11 +#: assets/models/automations/gather_facts.py:15 msgid "Gather asset facts" msgstr "收集资产信息" @@ -802,7 +811,7 @@ msgid "Account backup snapshot" msgstr "账号备份快照" #: assets/models/backup.py:91 audits/models.py:127 -#: terminal/models/sharing.py:108 +#: terminal/models/session/sharing.py:108 #: xpack/plugins/change_auth_plan/models/base.py:197 #: xpack/plugins/change_auth_plan/serializers/asset.py:171 #: xpack/plugins/cloud/models.py:175 @@ -823,7 +832,7 @@ msgstr "账号备份执行" msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:32 authentication/models.py:251 +#: assets/models/base.py:32 authentication/models.py:248 msgid "Date verified" msgstr "校验日期" @@ -845,7 +854,7 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:60 terminal/backends/command/serializers.py:15 -#: terminal/models/session.py:41 +#: terminal/models/session/session.py:41 #: terminal/templates/terminal/_msg_command_alert.html:12 #: terminal/templates/terminal/_msg_command_execute_alert.html:10 msgid "Command" @@ -1108,7 +1117,7 @@ msgstr "存在密码" msgid "Account template not found" msgstr "账号模版没有发现" -#: assets/serializers/account/backup.py:27 ops/mixin.py:104 +#: assets/serializers/account/backup.py:27 ops/mixin.py:102 #: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" @@ -1142,11 +1151,11 @@ msgstr "协议组" msgid "Address" msgstr "地址" -#: assets/serializers/asset/common.py:136 +#: assets/serializers/asset/common.py:138 msgid "Platform not exist" msgstr "平台不存在" -#: assets/serializers/asset/common.py:152 +#: assets/serializers/asset/common.py:154 msgid "Protocol is required: {}" msgstr "协议是必须的: {}" @@ -1393,7 +1402,7 @@ msgid "Symlink" msgstr "建立软链接" #: audits/models.py:38 audits/models.py:66 audits/models.py:89 -#: terminal/models/session.py:37 terminal/models/sharing.py:96 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 msgid "Remote addr" msgstr "远端地址" @@ -1405,8 +1414,8 @@ msgstr "操作" msgid "Filename" msgstr "文件名" -#: audits/models.py:43 audits/models.py:117 terminal/models/sharing.py:104 -#: tickets/views/approve.py:114 +#: audits/models.py:43 audits/models.py:117 +#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" msgstr "成功" @@ -1484,7 +1493,9 @@ msgstr "用户代理" msgid "MFA" msgstr "MFA" -#: audits/models.py:128 ops/models/base.py:48 terminal/models/status.py:33 +#: audits/models.py:128 ops/models/base.py:48 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:15 +#: terminal/models/applet/host.py:27 terminal/models/component/status.py:33 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 #: xpack/plugins/cloud/models.py:223 msgid "Status" @@ -1548,7 +1559,7 @@ msgstr "飞书" msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:56 authentication/models.py:255 +#: audits/signal_handlers.py:56 authentication/models.py:252 msgid "Temporary token" msgstr "临时密码" @@ -1699,7 +1710,7 @@ msgstr "无效的令牌头。符号字符串不应包含无效字符。" msgid "Invalid token or cache refreshed." msgstr "刷新的令牌或缓存无效。" -#: authentication/backends/oauth2/backends.py:155 authentication/models.py:146 +#: authentication/backends/oauth2/backends.py:155 authentication/models.py:143 msgid "User invalid, disabled or expired" msgstr "用户无效,已禁用或已过期" @@ -1963,66 +1974,72 @@ msgstr "过期时间" msgid "SSO token" msgstr "SSO token" -#: authentication/models.py:75 authentication/models.py:252 +#: authentication/models.py:68 authentication/models.py:249 #: perms/models/asset_permission.py:79 #: tickets/models/ticket/apply_application.py:29 #: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 msgid "Date expired" msgstr "失效日期" -#: authentication/models.py:82 rbac/serializers/rolebinding.py:21 +#: authentication/models.py:75 rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "用户名称" -#: authentication/models.py:87 +#: authentication/models.py:80 msgid "Asset display" msgstr "资产名称" -#: authentication/models.py:92 +#: authentication/models.py:85 +#, fuzzy +#| msgid "Action display" +msgid "Account display" +msgstr "动作名称" + +#: authentication/models.py:89 msgid "Connection token" msgstr "连接令牌" -#: authentication/models.py:94 +#: authentication/models.py:91 msgid "Can view connection token secret" msgstr "可以查看连接令牌密文" -#: authentication/models.py:137 +#: authentication/models.py:134 msgid "Connection token expired at: {}" msgstr "连接令牌过期: {}" -#: authentication/models.py:142 +#: authentication/models.py:139 msgid "User not exists" msgstr "用户不存在" -#: authentication/models.py:151 +#: authentication/models.py:148 msgid "System user not exists" msgstr "系统用户不存在" -#: authentication/models.py:157 +#: authentication/models.py:154 msgid "Asset not exists" msgstr "资产不存在" -#: authentication/models.py:161 +#: authentication/models.py:158 msgid "Asset inactive" msgstr "资产未激活" -#: authentication/models.py:168 +#: authentication/models.py:165 msgid "User has no permission to access asset or permission expired" msgstr "用户没有权限访问资产或权限已过期" -#: authentication/models.py:176 +#: authentication/models.py:173 msgid "Application not exists" msgstr "应用不存在" -#: authentication/models.py:183 +#: authentication/models.py:180 msgid "User has no permission to access application or permission expired" msgstr "用户没有权限访问应用或权限已过期" -#: authentication/models.py:250 +#: authentication/models.py:247 msgid "Verified" msgstr "已校验" -#: authentication/models.py:271 +#: authentication/models.py:268 msgid "Super connection token" msgstr "超级连接令牌" @@ -2671,15 +2688,15 @@ msgstr "邮件" msgid "Site message" msgstr "站内信" -#: ops/ansible/inventory.py:76 +#: ops/ansible/inventory.py:75 msgid "No account available" msgstr "没有账号可以使用" -#: ops/ansible/inventory.py:171 +#: ops/ansible/inventory.py:173 msgid "Ansible disabled" msgstr "Ansible 已禁用" -#: ops/ansible/inventory.py:186 +#: ops/ansible/inventory.py:189 msgid "Skip hosts below:" msgstr "跳过一下主机:" @@ -2711,28 +2728,28 @@ msgstr "更改密码" msgid "Custom password" msgstr "自定义密码" -#: ops/mixin.py:27 ops/mixin.py:90 settings/serializers/auth/ldap.py:72 +#: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "周期执行" -#: ops/mixin.py:31 ops/mixin.py:88 ops/mixin.py:107 +#: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 #: settings/serializers/auth/ldap.py:69 msgid "Regularly perform" msgstr "定期执行" -#: ops/mixin.py:110 +#: ops/mixin.py:108 msgid "Interval" msgstr "间隔" -#: ops/mixin.py:120 +#: ops/mixin.py:118 msgid "* Please enter a valid crontab expression" msgstr "* 请输入有效的 crontab 表达式" -#: ops/mixin.py:127 +#: ops/mixin.py:125 msgid "Range {} to {}" msgstr "输入在 {} - {} 范围之间" -#: ops/mixin.py:138 +#: ops/mixin.py:136 msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" @@ -2744,7 +2761,8 @@ msgstr "模式" msgid "Module" msgstr "" -#: ops/models/adhoc.py:20 ops/models/celery.py:15 terminal/models/task.py:17 +#: ops/models/adhoc.py:20 ops/models/celery.py:15 +#: terminal/models/component/task.py:17 msgid "Args" msgstr "参数" @@ -2760,7 +2778,8 @@ msgstr "" msgid "AdHoc execution" msgstr "任务执行" -#: ops/models/base.py:16 ops/models/base.py:52 terminal/models/sharing.py:24 +#: ops/models/base.py:16 ops/models/base.py:52 +#: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "创建者" @@ -2780,7 +2799,7 @@ msgstr "结果" msgid "Summary" msgstr "汇总" -#: ops/models/celery.py:16 terminal/models/task.py:18 +#: ops/models/celery.py:16 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "其它参数" @@ -2789,8 +2808,8 @@ msgstr "其它参数" msgid "State" msgstr "状态" -#: ops/models/celery.py:18 terminal/models/sharing.py:111 tickets/const.py:25 -#: xpack/plugins/change_auth_plan/models/base.py:188 +#: ops/models/celery.py:18 terminal/models/session/sharing.py:111 +#: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "结束" @@ -2814,7 +2833,7 @@ msgstr "Owner" msgid "Template" msgstr "模板" -#: ops/models/playbook.py:38 terminal/models/task.py:26 +#: ops/models/playbook.py:38 terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "任务" @@ -3197,7 +3216,7 @@ msgstr "权限" msgid "Scope display" msgstr "范围名称" -#: rbac/serializers/role.py:27 +#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:26 msgid "Display name" msgstr "显示名称" @@ -4432,13 +4451,13 @@ msgstr "过期。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" -" 您的密码已经过期,请点击 链接 更新密码\n" +" 您的密码已经过期,请点击 链接 更新密码\n" " " #: templates/_message.html:30 @@ -4462,8 +4481,8 @@ msgstr "" #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" @@ -4475,13 +4494,13 @@ msgstr "" #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" +" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -4540,62 +4559,62 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远 msgid "Offline video player" msgstr "离线录像播放器" -#: terminal/api/endpoint.py:33 +#: terminal/api/component/endpoint.py:33 msgid "Not found protocol query params" msgstr "" -#: terminal/api/session.py:216 -msgid "Session does not exist: {}" -msgstr "会话不存在: {}" - -#: terminal/api/session.py:219 -msgid "Session is finished or the protocol not supported" -msgstr "会话已经完成或协议不支持" - -#: terminal/api/session.py:224 -msgid "User does not exist: {}" -msgstr "用户不存在: {}" - -#: terminal/api/session.py:232 -msgid "User does not have permission" -msgstr "用户没有权限" - -#: terminal/api/sharing.py:30 -msgid "Secure session sharing settings is disabled" -msgstr "未开启会话共享" - -#: terminal/api/storage.py:28 +#: terminal/api/component/storage.py:28 msgid "Deleting the default storage is not allowed" msgstr "不允许删除默认存储配置" -#: terminal/api/storage.py:31 +#: terminal/api/component/storage.py:31 msgid "Cannot delete storage that is being used" msgstr "不允许删除正在使用的存储配置" -#: terminal/api/storage.py:72 terminal/api/storage.py:73 +#: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 msgid "Command storages" msgstr "命令存储" -#: terminal/api/storage.py:79 +#: terminal/api/component/storage.py:79 msgid "Invalid" msgstr "无效" -#: terminal/api/storage.py:119 +#: terminal/api/component/storage.py:119 msgid "Test failure: {}" msgstr "测试失败: {}" -#: terminal/api/storage.py:122 +#: terminal/api/component/storage.py:122 msgid "Test successful" msgstr "测试成功" -#: terminal/api/storage.py:124 +#: terminal/api/component/storage.py:124 msgid "Test failure: Account invalid" msgstr "测试失败: 账号无效" -#: terminal/api/terminal.py:39 +#: terminal/api/component/terminal.py:39 msgid "Have online sessions" msgstr "有在线会话" +#: terminal/api/session/session.py:217 +msgid "Session does not exist: {}" +msgstr "会话不存在: {}" + +#: terminal/api/session/session.py:220 +msgid "Session is finished or the protocol not supported" +msgstr "会话已经完成或协议不支持" + +#: terminal/api/session/session.py:225 +msgid "User does not exist: {}" +msgstr "用户不存在: {}" + +#: terminal/api/session/session.py:233 +msgid "User does not have permission" +msgstr "用户没有权限" + +#: terminal/api/session/sharing.py:29 +msgid "Secure session sharing settings is disabled" +msgstr "未开启会话共享" + #: terminal/apps.py:9 msgid "Terminals" msgstr "终端管理" @@ -4625,8 +4644,8 @@ msgstr "输入" msgid "Output" msgstr "输出" -#: terminal/backends/command/models.py:25 terminal/models/replay.py:9 -#: terminal/models/sharing.py:19 terminal/models/sharing.py:78 +#: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 +#: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 #: terminal/templates/terminal/_msg_command_alert.html:10 #: tickets/models/ticket/command_confirm.py:17 msgid "Session" @@ -4649,7 +4668,8 @@ msgstr "风险等级名称" msgid "Timestamp" msgstr "时间戳" -#: terminal/backends/command/serializers.py:41 terminal/models/terminal.py:105 +#: terminal/backends/command/serializers.py:41 +#: terminal/models/component/terminal.py:105 msgid "Remote Address" msgstr "远端地址" @@ -4677,209 +4697,264 @@ msgstr "不支持批量创建" msgid "Storage is invalid" msgstr "存储无效" -#: terminal/models/command.py:66 -msgid "Command record" -msgstr "命令记录" +#: terminal/models/applet/applet.py:21 +#, fuzzy +#| msgid "Manually input" +msgid "Manual" +msgstr "手动输入" -#: terminal/models/endpoint.py:13 +#: terminal/models/applet/applet.py:22 +msgid "Git" +msgstr "" + +#: terminal/models/applet/applet.py:23 +#, fuzzy +#| msgid "Remote app" +msgid "Remote gzip" +msgstr "远程应用" + +#: terminal/models/applet/applet.py:28 +#, fuzzy +#| msgid "Auth url" +msgid "Author" +msgstr "认证地址" + +#: terminal/models/applet/applet.py:32 +msgid "Tags" +msgstr "" + +#: terminal/models/applet/applet.py:59 terminal/models/applet/host.py:17 +#, fuzzy +#| msgid "Apply assets" +msgid "Applet" +msgstr "申请资产" + +#: terminal/models/applet/host.py:13 +#, fuzzy +#| msgid "Verify account automation" +msgid "Account automation" +msgstr "账号校验自动化" + +#: terminal/models/applet/host.py:14 +#, fuzzy +#| msgid "Date sync" +msgid "Date synced" +msgstr "同步日期" + +#: terminal/models/applet/host.py:26 +#, fuzzy +#| msgid "Host" +msgid "Hosting" +msgstr "主机" + +#: terminal/models/component/endpoint.py:14 msgid "HTTPS Port" msgstr "HTTPS 端口" -#: terminal/models/endpoint.py:14 terminal/models/terminal.py:107 +#: terminal/models/component/endpoint.py:15 +#: terminal/models/component/terminal.py:107 msgid "HTTP Port" msgstr "HTTP 端口" -#: terminal/models/endpoint.py:15 terminal/models/terminal.py:106 +#: terminal/models/component/endpoint.py:16 +#: terminal/models/component/terminal.py:106 msgid "SSH Port" msgstr "SSH 端口" -#: terminal/models/endpoint.py:16 +#: terminal/models/component/endpoint.py:17 msgid "RDP Port" msgstr "RDP 端口" -#: terminal/models/endpoint.py:17 +#: terminal/models/component/endpoint.py:18 msgid "MySQL Port" msgstr "MySQL 端口" -#: terminal/models/endpoint.py:18 +#: terminal/models/component/endpoint.py:19 msgid "MariaDB Port" msgstr "MariaDB 端口" -#: terminal/models/endpoint.py:19 +#: terminal/models/component/endpoint.py:20 msgid "PostgreSQL Port" msgstr "PostgreSQL 端口" -#: terminal/models/endpoint.py:20 +#: terminal/models/component/endpoint.py:21 msgid "Redis Port" msgstr "Redis 端口" -#: terminal/models/endpoint.py:21 +#: terminal/models/component/endpoint.py:22 msgid "Oracle 11g Port" msgstr "Oracle 11g 端口" -#: terminal/models/endpoint.py:22 +#: terminal/models/component/endpoint.py:23 msgid "Oracle 12c Port" msgstr "Oracle 12c 端口" -#: terminal/models/endpoint.py:28 terminal/models/endpoint.py:94 -#: terminal/serializers/endpoint.py:57 terminal/serializers/storage.py:38 -#: terminal/serializers/storage.py:50 terminal/serializers/storage.py:80 -#: terminal/serializers/storage.py:90 terminal/serializers/storage.py:98 +#: terminal/models/component/endpoint.py:29 +#: terminal/models/component/endpoint.py:95 terminal/serializers/endpoint.py:57 +#: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50 +#: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 +#: terminal/serializers/storage.py:98 msgid "Endpoint" msgstr "端点" -#: terminal/models/endpoint.py:87 +#: terminal/models/component/endpoint.py:88 msgid "IP group" msgstr "IP 组" -#: terminal/models/endpoint.py:99 +#: terminal/models/component/endpoint.py:100 msgid "Endpoint rule" msgstr "端点规则" -#: terminal/models/replay.py:12 -msgid "Session replay" -msgstr "会话录像" - -#: terminal/models/replay.py:14 -msgid "Can upload session replay" -msgstr "可以上传会话录像" - -#: terminal/models/replay.py:15 -msgid "Can download session replay" -msgstr "可以下载会话录像" - -#: terminal/models/session.py:36 terminal/models/sharing.py:101 -msgid "Login from" -msgstr "登录来源" - -#: terminal/models/session.py:40 -msgid "Replay" -msgstr "回放" - -#: terminal/models/session.py:44 -msgid "Date end" -msgstr "结束日期" - -#: terminal/models/session.py:236 -msgid "Session record" -msgstr "会话记录" - -#: terminal/models/session.py:238 -msgid "Can monitor session" -msgstr "可以监控会话" - -#: terminal/models/session.py:239 -msgid "Can share session" -msgstr "可以分享会话" - -#: terminal/models/session.py:240 -msgid "Can terminate session" -msgstr "可以终断会话" - -#: terminal/models/session.py:241 -msgid "Can validate session action perm" -msgstr "可以验证会话动作权限" - -#: terminal/models/sharing.py:26 terminal/models/sharing.py:80 -msgid "Verify code" -msgstr "验证码" - -#: terminal/models/sharing.py:31 -msgid "Expired time (min)" -msgstr "过期时间 (分)" - -#: terminal/models/sharing.py:37 terminal/models/sharing.py:83 -msgid "Session sharing" -msgstr "会话分享" - -#: terminal/models/sharing.py:39 -msgid "Can add super session sharing" -msgstr "可以创建超级会话分享" - -#: terminal/models/sharing.py:66 -msgid "Link not active" -msgstr "链接失效" - -#: terminal/models/sharing.py:68 -msgid "Link expired" -msgstr "链接过期" - -#: terminal/models/sharing.py:70 -msgid "User not allowed to join" -msgstr "该用户无权加入会话" - -#: terminal/models/sharing.py:87 terminal/serializers/sharing.py:59 -msgid "Joiner" -msgstr "加入者" - -#: terminal/models/sharing.py:90 -msgid "Date joined" -msgstr "加入日期" - -#: terminal/models/sharing.py:93 -msgid "Date left" -msgstr "结束日期" - -#: terminal/models/sharing.py:116 -msgid "Session join record" -msgstr "会话加入记录" - -#: terminal/models/sharing.py:132 -msgid "Invalid verification code" -msgstr "验证码不正确" - -#: terminal/models/status.py:18 +#: terminal/models/component/status.py:18 msgid "Session Online" msgstr "在线会话" -#: terminal/models/status.py:19 +#: terminal/models/component/status.py:19 msgid "CPU Load" msgstr "CPU负载" -#: terminal/models/status.py:20 +#: terminal/models/component/status.py:20 msgid "Memory Used" msgstr "内存使用" -#: terminal/models/status.py:21 +#: terminal/models/component/status.py:21 msgid "Disk Used" msgstr "磁盘使用" -#: terminal/models/status.py:22 +#: terminal/models/component/status.py:22 msgid "Connections" msgstr "连接数" -#: terminal/models/status.py:23 +#: terminal/models/component/status.py:23 msgid "Threads" msgstr "线程数" -#: terminal/models/status.py:24 +#: terminal/models/component/status.py:24 msgid "Boot Time" msgstr "运行时间" -#: terminal/models/storage.py:28 +#: terminal/models/component/storage.py:27 msgid "Default storage" msgstr "默认存储" -#: terminal/models/storage.py:137 terminal/models/terminal.py:108 +#: terminal/models/component/storage.py:136 +#: terminal/models/component/terminal.py:108 msgid "Command storage" msgstr "命令存储" -#: terminal/models/storage.py:197 terminal/models/terminal.py:109 +#: terminal/models/component/storage.py:196 +#: terminal/models/component/terminal.py:109 msgid "Replay storage" msgstr "录像存储" -#: terminal/models/terminal.py:103 +#: terminal/models/component/terminal.py:103 msgid "type" msgstr "类型" -#: terminal/models/terminal.py:183 +#: terminal/models/component/terminal.py:183 msgid "Terminal" msgstr "终端" -#: terminal/models/terminal.py:185 +#: terminal/models/component/terminal.py:185 msgid "Can view terminal config" msgstr "可以查看终端配置" +#: terminal/models/session/command.py:66 +msgid "Command record" +msgstr "命令记录" + +#: terminal/models/session/replay.py:12 +msgid "Session replay" +msgstr "会话录像" + +#: terminal/models/session/replay.py:14 +msgid "Can upload session replay" +msgstr "可以上传会话录像" + +#: terminal/models/session/replay.py:15 +msgid "Can download session replay" +msgstr "可以下载会话录像" + +#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 +msgid "Login from" +msgstr "登录来源" + +#: terminal/models/session/session.py:40 +msgid "Replay" +msgstr "回放" + +#: terminal/models/session/session.py:44 +msgid "Date end" +msgstr "结束日期" + +#: terminal/models/session/session.py:236 +msgid "Session record" +msgstr "会话记录" + +#: terminal/models/session/session.py:238 +msgid "Can monitor session" +msgstr "可以监控会话" + +#: terminal/models/session/session.py:239 +msgid "Can share session" +msgstr "可以分享会话" + +#: terminal/models/session/session.py:240 +msgid "Can terminate session" +msgstr "可以终断会话" + +#: terminal/models/session/session.py:241 +msgid "Can validate session action perm" +msgstr "可以验证会话动作权限" + +#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 +msgid "Verify code" +msgstr "验证码" + +#: terminal/models/session/sharing.py:31 +msgid "Expired time (min)" +msgstr "过期时间 (分)" + +#: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 +msgid "Session sharing" +msgstr "会话分享" + +#: terminal/models/session/sharing.py:39 +msgid "Can add super session sharing" +msgstr "可以创建超级会话分享" + +#: terminal/models/session/sharing.py:66 +msgid "Link not active" +msgstr "链接失效" + +#: terminal/models/session/sharing.py:68 +msgid "Link expired" +msgstr "链接过期" + +#: terminal/models/session/sharing.py:70 +msgid "User not allowed to join" +msgstr "该用户无权加入会话" + +#: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 +msgid "Joiner" +msgstr "加入者" + +#: terminal/models/session/sharing.py:90 +msgid "Date joined" +msgstr "加入日期" + +#: terminal/models/session/sharing.py:93 +msgid "Date left" +msgstr "结束日期" + +#: terminal/models/session/sharing.py:116 +msgid "Session join record" +msgstr "会话加入记录" + +#: terminal/models/session/sharing.py:132 +msgid "Invalid verification code" +msgstr "验证码不正确" + #: terminal/notifications.py:22 msgid "Sessions" msgstr "会话管理" @@ -4896,6 +4971,10 @@ msgstr "级别" msgid "Batch danger command alert" msgstr "批量危险命令告警" +#: terminal/serializers/applet.py:19 +msgid "Icon" +msgstr "图标" + #: terminal/serializers/endpoint.py:12 msgid "Oracle port" msgstr "" @@ -6690,9 +6769,6 @@ msgstr "社区版" #~ msgid "Unsupported protocols: {}" #~ msgstr "不支持的协议: {}" -#~ msgid "Remote app" -#~ msgstr "远程应用" - #~ msgid "Custom" #~ msgstr "自定义" @@ -6804,9 +6880,6 @@ msgstr "社区版" #~ msgid "CPU info" #~ msgstr "CPU信息" -#~ msgid "Action display" -#~ msgstr "动作名称" - #~ msgid "Applications amount" #~ msgstr "应用数量" diff --git a/apps/orgs/migrations/0014_organization_builtin.py b/apps/orgs/migrations/0014_organization_builtin.py new file mode 100644 index 000000000..6541fe1a7 --- /dev/null +++ b/apps/orgs/migrations/0014_organization_builtin.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.14 on 2022-10-26 09:07 + +from django.db import migrations, models + + +def update_builtin_org(apps, schema_editor): + org_model = apps.get_model('orgs', 'Organization') + org_model.objects.create( + id='00000000-0000-0000-0000-000000000004', + name='SYSTEM', builtin=True + ) + + # 更新 Default + org_model.objects.filter(name='DEFAULT').update(builtin=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('orgs', '0013_alter_organization_options'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='builtin', + field=models.BooleanField(default=False, verbose_name='Builtin'), + ), + migrations.RunPython(update_builtin_org), + ] diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 7c7babd76..5e78d2757 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -69,6 +69,7 @@ class Organization(OrgRoleMixin, models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) + builtin = models.BooleanField(default=False, verbose_name=_('Builtin')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) members = models.ManyToManyField( @@ -139,13 +140,13 @@ class Organization(OrgRoleMixin, models.Model): @classmethod def default(cls): defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME) - obj, created = cls.objects.get_or_create(defaults=defaults, id=cls.DEFAULT_ID) + obj, created = cls.objects.get_or_create(defaults=defaults, id=cls.DEFAULT_ID, builtin=True) return obj @classmethod def root(cls): name = settings.GLOBAL_ORG_DISPLAY_NAME or cls.ROOT_NAME - return cls(id=cls.ROOT_ID, name=name) + return cls(id=cls.ROOT_ID, name=name, builtin=True) def is_root(self): return self.id == self.ROOT_ID diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index a4e1c7085..2136b28a0 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -4,6 +4,7 @@ import threading from collections import defaultdict from functools import partial +import django.db.utils from django.dispatch import receiver from django.conf import settings from django.utils.functional import LazyObject @@ -45,7 +46,10 @@ def expire_orgs_mapping_for_memory(org_id): def subscribe_orgs_mapping_expire(sender, **kwargs): logger.debug("Start subscribe for expire orgs mapping from memory") if settings.DEBUG: - set_to_default_org() + try: + set_to_default_org() + except django.db.utils.OperationalError: + pass def keep_subscribe_org_mapping(): orgs_mapping_for_memory_pub_sub.subscribe( diff --git a/apps/terminal/automations/__init__.py b/apps/terminal/automations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/terminal/automations/deploy_applet_host/manager.py b/apps/terminal/automations/deploy_applet_host/manager.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml new file mode 100644 index 000000000..17016328d --- /dev/null +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -0,0 +1,56 @@ +--- +- hosts: windows + vars: + - DownloadHost: https://demo.jumpserver.org/download + - RDS_Licensing: enabled + - RDS_LicenseServer: 127.0.0.1 + - RDS_LicensingMode: 4 + - RDS_fSingleSessionPerUser: 0 + - RDS_MaxDisconnectionTime: 60000 + - RDS_RemoteAppLogoffTimeLimit: 0 + tasks: + - name: Install RDS-Licensing (RDS) + ansible.windows.win_feature: + name: RDS-Licensing + state: present + include_management_tools: yes + when: RDS_Licensing == "enabled" + - name: Install RDS-RD-Server (RDS) + ansible.windows.win_feature: + name: RDS-RD-Server + state: present + include_management_tools: yes + register: win_feature + - name: Reboot if installing RDS feature requires it + ansible.windows.win_reboot: + when: win_feature.reboot_required + - name: Set RDS LicenseServer (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: LicenseServers + data: "{{ RDS_LicenseServer }}" + type: string + - name: Set RDS LicensingMode (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: LicensingMode + data: "{{ RDS_LicensingMode }}" + type: dword + - name: Set RDS fSingleSessionPerUser (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: fSingleSessionPerUser + data: "{{ RDS_fSingleSessionPerUser }}" + type: dword + - name: Set RDS MaxDisconnectionTime (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: MaxDisconnectionTime + data: "{{ RDS_MaxDisconnectionTime }}" + type: dword + when: RDS_MaxDisconnectionTime >= 60000 + - name: Set RDS RemoteAppLogoffTimeLimit (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: RemoteAppLogoffTimeLimit + data: "{{ RDS_RemoteAppLogoffTime }}" diff --git a/apps/terminal/migrations/0055_auto_20221026_1631.py b/apps/terminal/migrations/0055_auto_20221026_1631.py new file mode 100644 index 000000000..cbfe73256 --- /dev/null +++ b/apps/terminal/migrations/0055_auto_20221026_1631.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.14 on 2022-10-26 08:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0054_auto_20221024_1452'), + ] + + operations = [ + migrations.RemoveField( + model_name='applet', + name='vcs_type', + ), + migrations.RemoveField( + model_name='applet', + name='vcs_url', + ), + migrations.AddField( + model_name='applet', + name='is_active', + field=models.BooleanField(default=True, verbose_name='Is active'), + ), + ] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 3e06a5fe9..25e3a2784 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -27,8 +27,7 @@ class Applet(JMSBaseModel): version = models.CharField(max_length=16, verbose_name=_('Version')) author = models.CharField(max_length=128, verbose_name=_('Author')) type = models.CharField(max_length=16, verbose_name=_('Type'), default='general', choices=Type.choices) - vcs_type = models.CharField(max_length=16, verbose_name=_('VCS type'), null=True) - vcs_url = models.CharField(max_length=256, verbose_name=_('URL'), null=True) + is_active = models.BooleanField(default=True, verbose_name=_('Is active')) protocols = models.JSONField(default=list, verbose_name=_('Protocol')) tags = models.JSONField(default=list, verbose_name=_('Tags')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) From 651228795efb13944189ee778e7ddf04d3efe0fb Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 26 Oct 2022 17:25:57 +0800 Subject: [PATCH 231/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ops/migrations/0027_auto_20221024_1709.py | 15 ----------- .../ops/migrations/0028_auto_20221024_1712.py | 25 ------------------- 2 files changed, 40 deletions(-) delete mode 100644 apps/ops/migrations/0028_auto_20221024_1712.py diff --git a/apps/ops/migrations/0027_auto_20221024_1709.py b/apps/ops/migrations/0027_auto_20221024_1709.py index 4b29e4a3e..58340f75b 100644 --- a/apps/ops/migrations/0027_auto_20221024_1709.py +++ b/apps/ops/migrations/0027_auto_20221024_1709.py @@ -25,11 +25,6 @@ class Migration(migrations.Migration): ('date_finished', models.DateTimeField(null=True)), ], ), - migrations.RenameField( - model_name='celerytask', - old_name='date_finished', - new_name='date_last_published', - ), migrations.RemoveField( model_name='celerytask', name='args', @@ -54,14 +49,4 @@ class Migration(migrations.Migration): model_name='celerytask', name='state', ), - migrations.AddField( - model_name='celerytask', - name='description', - field=models.CharField(max_length=2048, null=True), - ), - migrations.AddField( - model_name='celerytask', - name='verbose_name', - field=models.CharField(max_length=1024, null=True), - ), ] diff --git a/apps/ops/migrations/0028_auto_20221024_1712.py b/apps/ops/migrations/0028_auto_20221024_1712.py deleted file mode 100644 index d246adbd8..000000000 --- a/apps/ops/migrations/0028_auto_20221024_1712.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-24 09:12 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0027_auto_20221024_1709'), - ] - - operations = [ - migrations.RemoveField( - model_name='celerytask', - name='date_last_published', - ), - migrations.RemoveField( - model_name='celerytask', - name='description', - ), - migrations.RemoveField( - model_name='celerytask', - name='verbose_name', - ), - ] From 8f88b898d0a4085f66d79bba0423a5000b0ff237 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 26 Oct 2022 17:38:32 +0800 Subject: [PATCH 232/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/apps.py | 6 ++-- .../ops/migrations/0027_auto_20221024_1709.py | 32 +++++-------------- apps/ops/signal_handlers.py | 17 +++++----- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/apps/common/apps.py b/apps/common/apps.py index 2a5799d10..c55e4c6e3 100644 --- a/apps/common/apps.py +++ b/apps/common/apps.py @@ -10,6 +10,8 @@ class CommonConfig(AppConfig): def ready(self): from . import signal_handlers from .signals import django_ready - if 'migrate' in sys.argv or 'compilemessages' in sys.argv: - return + excludes = ['migrate', 'compilemessages', 'makemigrations'] + for i in excludes: + if i in sys.argv: + return django_ready.send(CommonConfig) diff --git a/apps/ops/migrations/0027_auto_20221024_1709.py b/apps/ops/migrations/0027_auto_20221024_1709.py index 58340f75b..34e244363 100644 --- a/apps/ops/migrations/0027_auto_20221024_1709.py +++ b/apps/ops/migrations/0027_auto_20221024_1709.py @@ -11,6 +11,14 @@ class Migration(migrations.Migration): ] operations = [ + migrations.DeleteModel(name='CeleryTask'), + migrations.CreateModel( + name='CeleryTask', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=1024)), + ] + ), migrations.CreateModel( name='CeleryTaskExecution', fields=[ @@ -25,28 +33,4 @@ class Migration(migrations.Migration): ('date_finished', models.DateTimeField(null=True)), ], ), - migrations.RemoveField( - model_name='celerytask', - name='args', - ), - migrations.RemoveField( - model_name='celerytask', - name='date_published', - ), - migrations.RemoveField( - model_name='celerytask', - name='date_start', - ), - migrations.RemoveField( - model_name='celerytask', - name='is_finished', - ), - migrations.RemoveField( - model_name='celerytask', - name='kwargs', - ), - migrations.RemoveField( - model_name='celerytask', - name='state', - ), ] diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index 3cb9b3f70..a444558bc 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -20,16 +20,17 @@ TASK_LANG_CACHE_TTL = 1800 @receiver(django_ready) def sync_registered_tasks(*args, **kwargs): with transaction.atomic(): - db_tasks = CeleryTask.objects.all() + try: + db_tasks = CeleryTask.objects.all() + except Exception as e: + return celery_task_names = [key for key in app.tasks] - db_task_names = [task.name for task in db_tasks] + db_task_names = db_tasks.values_list('name', flat=True) - for task in db_tasks: - if task.name not in celery_task_names: - task.delete() - for task in celery_task_names: - if task not in db_task_names: - CeleryTask(name=task).save() + db_tasks.exclude(name__in=celery_task_names).delete() + not_in_db_tasks = set(celery_task_names) - set(db_task_names) + tasks_to_create = [CeleryTask(name=name) for name in not_in_db_tasks] + CeleryTask.objects.bulk_create(tasks_to_create) @signals.before_task_publish.connect From 1239247b5a7af62232832db850e2a19a2b9794ac Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 26 Oct 2022 17:56:37 +0800 Subject: [PATCH 233/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E5=88=A0?= =?UTF-8?q?=E6=8E=89=20connection=20token=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 2 +- .../migrations/0012_auto_20220816_1629.py | 6 +++++- .../0013_remove_connectiontoken_type.py | 17 ----------------- .../serializers/connection_token.py | 2 +- 4 files changed, 7 insertions(+), 20 deletions(-) delete mode 100644 apps/authentication/migrations/0013_remove_connectiontoken_type.py diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 990e9d814..f85b1ee54 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -210,7 +210,7 @@ class ConnectionTokenMixin: class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet): filterset_fields = ( - 'type', 'user_display', 'asset_display' + 'user_display', 'asset_display' ) search_fields = filterset_fields serializer_classes = { diff --git a/apps/authentication/migrations/0012_auto_20220816_1629.py b/apps/authentication/migrations/0012_auto_20220816_1629.py index 6e22b0e0f..23bcccb68 100644 --- a/apps/authentication/migrations/0012_auto_20220816_1629.py +++ b/apps/authentication/migrations/0012_auto_20220816_1629.py @@ -49,5 +49,9 @@ class Migration(migrations.Migration): migrations.RemoveField( model_name='connectiontoken', name='system_user', - ) + ), + migrations.RemoveField( + model_name='connectiontoken', + name='type', + ), ] diff --git a/apps/authentication/migrations/0013_remove_connectiontoken_type.py b/apps/authentication/migrations/0013_remove_connectiontoken_type.py deleted file mode 100644 index 52c6813dc..000000000 --- a/apps/authentication/migrations/0013_remove_connectiontoken_type.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-26 08:07 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0012_auto_20220816_1629'), - ] - - operations = [ - migrations.RemoveField( - model_name='connectiontoken', - name='type', - ), - ] diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 0905d5c60..58973ba70 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -23,7 +23,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken - fields_mini = ['id', 'type'] + fields_mini = ['id'] fields_small = fields_mini + [ 'secret', 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', From a260da6cec1e1021f591ad48171be12ddb86ac1d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 27 Oct 2022 15:47:05 +0800 Subject: [PATCH 234/488] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20Connec?= =?UTF-8?q?tionToken=20=E5=85=B3=E8=81=94=E7=9A=84=E9=80=BB=E8=BE=91(1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/cmd_filter.py | 8 +- apps/authentication/api/connection_token.py | 63 ++++---- apps/authentication/models.py | 105 +++++--------- .../serializers/connection_token.py | 4 +- apps/perms/api/user_permission/common.py | 82 +---------- apps/perms/urls/asset_permission.py | 6 - apps/perms/utils/account.py | 28 +++- apps/perms/utils/permission.py | 135 ++++-------------- 8 files changed, 122 insertions(+), 309 deletions(-) diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index e26393fff..be8945c55 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -180,9 +180,10 @@ class CommandFilterRule(OrgModelMixin): @classmethod def get_queryset( - cls, user_id=None, user_group_id=None, system_user_id=None, + cls, user_id=None, user_group_id=None, account=None, asset_id=None, org_id=None ): + from perms.models.const import SpecialAccount user_groups = [] user = get_object_or_none(User, pk=user_id) if user: @@ -191,7 +192,7 @@ class CommandFilterRule(OrgModelMixin): if user_group: org_id = user_group.org_id user_groups.append(user_group) - account = get_object_or_none(Account, pk=system_user_id) + asset = get_object_or_none(Asset, pk=asset_id) q = Q() if user: @@ -200,7 +201,8 @@ class CommandFilterRule(OrgModelMixin): q |= Q(user_groups__in=set(user_groups)) if account: org_id = account.org_id - q |= Q(accounts=account) + q |= Q(accounts__contains=list(account)) |\ + Q(accounts__contains=SpecialAccount.ALL.value) if asset: org_id = asset.org_id q |= Q(assets=asset) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 990e9d814..1ddd9fa77 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -1,6 +1,7 @@ import abc import os import json +import time import base64 import urllib.parse from django.http import HttpResponse @@ -41,17 +42,25 @@ class ConnectionTokenMixin: def get_request_resources(self, serializer): user = self.get_request_resource_user(serializer) asset = serializer.validated_data.get('asset') - application = serializer.validated_data.get('application') - system_user = serializer.validated_data.get('system_user') - return user, asset, application, system_user + account = serializer.validated_data.get('account') + return user, asset, account @staticmethod - def check_user_has_resource_permission(user, asset, application, system_user): - from perms.utils.asset import has_asset_system_permission + def check_user_has_resource_permission(user, asset, account): + from perms.utils.account import PermAccountUtil + if not asset or not user: + error = '' + raise PermissionDenied(error) - if asset and not has_asset_system_permission(user, asset, system_user): - error = f'User not has this asset and system user permission: ' \ - f'user={user.id} system_user={system_user.id} asset={asset.id}' + actions, expire_at = PermAccountUtil().validate_permission( + user, asset, account_username=account + ) + if not actions: + error = '' + raise PermissionDenied(error) + + if expire_at < time.time(): + error = '' raise PermissionDenied(error) def get_smart_endpoint(self, protocol, asset=None, application=None): @@ -69,13 +78,12 @@ class ConnectionTokenMixin: return true_value if is_true(os.getenv(env_key, env_default)) else false_value def get_client_protocol_data(self, token: ConnectionToken): - from assets.models import SystemUser - protocol = token.system_user.protocol + protocol = token.protocol username = token.user.username rdp_config = ssh_token = '' - if protocol == SystemUser.Protocol.rdp: + if protocol == 'rdp': filename, rdp_config = self.get_rdp_file_info(token) - elif protocol == SystemUser.Protocol.ssh: + elif protocol == 'ssh': filename, ssh_token = self.get_ssh_token(token) else: raise ValueError('Protocol not support: {}'.format(protocol)) @@ -134,15 +142,12 @@ class ConnectionTokenMixin: rdp_options['screen mode id:i'] = '2' if full_screen else '1' # 设置 RDP Server 地址 - endpoint = self.get_smart_endpoint( - protocol='rdp', asset=token.asset, application=token.application - ) + endpoint = self.get_smart_endpoint(protocol='rdp', asset=token.asset) rdp_options['full address:s'] = f'{endpoint.host}:{endpoint.rdp_port}' # 设置用户名 rdp_options['username:s'] = '{}|{}'.format(token.user.username, str(token.id)) - if token.system_user.ad_domain: - rdp_options['domain:s'] = token.system_user.ad_domain + # rdp_options['domain:s'] = token.account_ad_domain # 设置宽高 height = self.request.query_params.get('height') @@ -158,13 +163,12 @@ class ConnectionTokenMixin: if token.asset: name = token.asset.name - elif token.application and token.application.category_remote_app: - app = '||jmservisor' - name = token.application.name - rdp_options['remoteapplicationmode:i'] = '1' - rdp_options['alternate shell:s'] = app - rdp_options['remoteapplicationprogram:s'] = app - rdp_options['remoteapplicationname:s'] = name + # remote-app + # app = '||jmservisor' + # rdp_options['remoteapplicationmode:i'] = '1' + # rdp_options['alternate shell:s'] = app + # rdp_options['remoteapplicationprogram:s'] = app + # rdp_options['remoteapplicationname:s'] = name else: name = '*' prefix_name = f'{token.user.username}-{name}' @@ -188,16 +192,12 @@ class ConnectionTokenMixin: def get_ssh_token(self, token: ConnectionToken): if token.asset: name = token.asset.name - elif token.application: - name = token.application.name else: name = '*' prefix_name = f'{token.user.username}-{name}' filename = self.get_connect_filename(prefix_name) - endpoint = self.get_smart_endpoint( - protocol='ssh', asset=token.asset, application=token.application - ) + endpoint = self.get_smart_endpoint(protocol='ssh', asset=token.asset) data = { 'ip': endpoint.host, 'port': str(endpoint.ssh_port), @@ -251,8 +251,8 @@ class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelVie return token def perform_create(self, serializer): - user, asset, application, system_user = self.get_request_resources(serializer) - self.check_user_has_resource_permission(user, asset, application, system_user) + user, asset, account = self.get_request_resources(serializer) + self.check_user_has_resource_permission(user, asset, account) return super(ConnectionTokenViewSet, self).perform_create(serializer) @action(methods=['POST'], detail=False, url_path='secret-info/detail') @@ -264,7 +264,6 @@ class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelVie token_id = request.data.get('token') or '' token = get_object_or_404(ConnectionToken, pk=token_id) self.check_token_valid(token) - token.load_system_user_auth() serializer = self.get_serializer(instance=token) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 765f38ef9..2ec71451f 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -1,3 +1,4 @@ +import time import uuid from datetime import datetime, timedelta from django.utils import timezone @@ -63,22 +64,20 @@ def date_expired_default(): class ConnectionToken(OrgModelMixin, JMSBaseModel): - secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) - date_expired = models.DateTimeField( - default=date_expired_default, verbose_name=_("Date expired") - ) - user = models.ForeignKey( - 'users.User', on_delete=models.SET_NULL, verbose_name=_('User'), - related_name='connection_tokens', null=True, blank=True + 'users.User', on_delete=models.SET_NULL, null=True, blank=True, + related_name='connection_tokens', verbose_name=_('User') + ) + asset = models.ForeignKey( + 'assets.Asset', on_delete=models.SET_NULL, null=True, blank=True, + related_name='connection_tokens', verbose_name=_('Asset'), ) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) - asset = models.ForeignKey( - 'assets.Asset', on_delete=models.SET_NULL, verbose_name=_('Asset'), - related_name='connection_tokens', null=True, blank=True - ) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) + protocol = '' account = models.CharField(max_length=128, default='', verbose_name=_("Account")) + secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) + date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_("Date expired")) class Meta: ordering = ('-date_expired',) @@ -87,10 +86,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): ('view_connectiontokensecret', _('Can view connection token secret')) ] - @classmethod - def get_default_date_expired(cls): - return date_expired_default() - @property def is_expired(self): return self.date_expired < timezone.now() @@ -103,32 +98,32 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): seconds = 0 return int(seconds) - def expire(self): - self.date_expired = timezone.now() - self.save() - @property def is_valid(self): return not self.is_expired - def is_type(self, tp): - return self.type == tp + @classmethod + def get_default_date_expired(cls): + return date_expired_default() + + def expire(self): + self.date_expired = timezone.now() + self.save() def renewal(self): """ 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """ self.date_expired = self.get_default_date_expired() self.save() - actions = expired_at = None # actions 和 expired_at 在 check_valid() 中赋值 + # actions 和 expired_at 在 check_valid() 中赋值 + actions = expire_at = None def check_valid(self): - from perms.utils.permission import validate_permission as asset_validate_permission - + from perms.utils.account import PermAccountUtil if self.is_expired: is_valid = False error = _('Connection token expired at: {}').format(as_current_tz(self.date_expired)) return is_valid, error - if not self.user: is_valid = False error = _('User not exists') @@ -137,44 +132,33 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('User invalid, disabled or expired') return is_valid, error - + if not self.asset: + is_valid = False + error = _('Asset not exists') + return is_valid, error + if not self.asset.is_active: + is_valid = False + error = _('Asset inactive') + return is_valid, error if not self.account: is_valid = False error = _('Account not exists') return is_valid, error - if not self.asset: - is_valid = False - error = _('Asset not exists') - return is_valid, error - - if not self.asset.is_active: - is_valid = False - error = _('Asset inactive') - return is_valid, error - - has_perm, actions, expired_at = asset_validate_permission( + actions, expire_at = PermAccountUtil().validate_permission( self.user, self.asset, self.account ) - if not has_perm: + if not actions or expire_at < time.time(): is_valid = False error = _('User has no permission to access asset or permission expired') return is_valid, error self.actions = actions - self.expired_at = expired_at + self.expire_at = expire_at return True, '' @lazyproperty def domain(self): - if self.asset: - return self.asset.domain - if not self.application: - return - if self.application.category_remote_app: - asset = self.application.get_remote_app_asset() - domain = asset.domain if asset else None - else: - domain = self.application.domain + domain = self.asset.domain if self.asset else None return domain @lazyproperty @@ -185,41 +169,18 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): self.domain: Domain return self.domain.random_gateway() - @lazyproperty - def remote_app(self): - if not self.application: - return {} - if not self.application.category_remote_app: - return {} - return self.application.get_rdp_remote_app_setting() - - @lazyproperty - def asset_or_remote_app_asset(self): - if self.asset: - return self.asset - if self.application and self.application.category_remote_app: - return self.application.get_remote_app_asset() - @lazyproperty def cmd_filter_rules(self): from assets.models import CommandFilterRule kwargs = { 'user_id': self.user.id, - 'system_user_id': self.system_user.id, + 'account': self.account, } if self.asset: kwargs['asset_id'] = self.asset.id - elif self.application: - kwargs['application_id'] = self.application_id rules = CommandFilterRule.get_queryset(**kwargs) return rules - def load_system_user_auth(self): - if self.asset: - self.system_user.load_asset_more_auth(self.asset.id, self.user.username, self.user.id) - elif self.application: - self.system_user.load_app_more_auth(self.application.id, self.user.username, self.user.id) - class TempToken(JMSBaseModel): username = models.CharField(max_length=128, verbose_name=_("Username")) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 0905d5c60..03df575ba 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -165,6 +165,6 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'secret', 'type', 'user', 'asset', 'application', 'system_user', - 'remote_app', 'cmd_filter_rules', 'domain', 'gateway', 'actions', 'expired_at', + 'id', 'secret', 'type', 'user', 'asset', 'account', + 'cmd_filter_rules', 'domain', 'gateway', 'actions', 'expired_at', ] diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index ebcbcf3e6..927ec7443 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -1,34 +1,20 @@ # -*- coding: utf-8 -*- # -import uuid -import time -from collections import defaultdict - from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from rest_framework.views import APIView, Response -from rest_framework import status from rest_framework.generics import ( - ListAPIView, get_object_or_404, RetrieveAPIView -) - -from orgs.utils import tmp_to_root_org -from perms.utils.permission import ( - get_asset_system_user_ids_with_actions_by_user, validate_permission + ListAPIView, get_object_or_404 ) from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty from perms.hands import User, Asset, Account from perms import serializers -from perms.models import AssetPermission, Action +from perms.models import Action from perms.utils import PermAccountUtil logger = get_logger(__name__) __all__ = [ - 'ValidateUserAssetPermissionApi', - 'GetUserAssetPermissionActionsApi', 'UserGrantedAssetAccountsApi', 'MyGrantedAssetAccountsApi', 'UserGrantedAssetSpecialAccountsApi', @@ -36,70 +22,6 @@ __all__ = [ ] -@method_decorator(tmp_to_root_org(), name='get') -class GetUserAssetPermissionActionsApi(RetrieveAPIView): - serializer_class = serializers.ActionsSerializer - rbac_perms = { - 'retrieve': 'perms.view_userassets', - 'GET': 'perms.view_userassets', - } - - def get_user(self): - user_id = self.request.query_params.get('user_id', '') - user = get_object_or_404(User, id=user_id) - return user - - def get_object(self): - asset_id = self.request.query_params.get('asset_id', '') - account = self.request.query_params.get('account', '') - - try: - asset_id = uuid.UUID(asset_id) - except ValueError: - return Response({'msg': False}, status=403) - - asset = get_object_or_404(Asset, id=asset_id) - - system_users_actions = get_asset_system_user_ids_with_actions_by_user(self.get_user(), asset) - # actions = system_users_actions.get(system_user.id) - actions = system_users_actions.get(account) - return {"actions": actions} - - -@method_decorator(tmp_to_root_org(), name='get') -class ValidateUserAssetPermissionApi(APIView): - rbac_perms = { - 'GET': 'perms.view_userassets' - } - - def get(self, request, *args, **kwargs): - user_id = self.request.query_params.get('user_id', '') - asset_id = request.query_params.get('asset_id', '') - account = request.query_params.get('account', '') - action_name = request.query_params.get('action_name', '') - - data = { - 'has_permission': False, - 'expire_at': int(time.time()), - 'actions': [] - } - - if not all((user_id, asset_id, account, action_name)): - return Response(data) - - user = User.objects.get(id=user_id) - asset = Asset.objects.valid().get(id=asset_id) - - has_perm, actions, expire_at = validate_permission(user, asset, account, action_name) - status_code = status.HTTP_200_OK if has_perm else status.HTTP_403_FORBIDDEN - data = { - 'has_permission': has_perm, - 'actions': actions, - 'expire_at': int(expire_at) - } - return Response(data, status=status_code) - - class UserGrantedAssetAccountsApi(ListAPIView): serializer_class = serializers.AccountsGrantedSerializer rbac_perms = { diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index cc8e10f36..a97727550 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -84,12 +84,6 @@ permission_urlpatterns = [ # 授权规则中授权的资产 path('/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'), path('/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'), - - # 验证用户是否有某个资产和系统用户的权限 - # Todo: v3 先不动, 可能会修改连接资产时的逻辑, 直接获取认证信息,获取不到就时没有权限,就不需要校验了 - path('user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), - path('user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'), - ] asset_permission_urlpatterns = [ diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index a5fdadc6b..34b839fab 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -1,3 +1,4 @@ +import time from collections import defaultdict from assets.models import Account from .permission import AssetPermissionUtil @@ -8,25 +9,29 @@ __all__ = ['PermAccountUtil'] class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ - def get_perm_accounts_for_user_asset(self, user, asset, with_actions=False): - """ 获取授权给用户某个资产的账号 """ - perms = self.get_permissions_for_user_asset(user, asset) - accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) - return accounts - def get_perm_accounts_for_user(self, user, with_actions=False): """ 获取授权给用户的所有账号 """ perms = self.get_permissions_for_user(user) accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) return accounts + def get_perm_accounts_for_user_asset(self, user, asset, with_actions=False, with_perms=False): + """ 获取授权给用户某个资产的账号 """ + perms = self.get_permissions_for_user_asset(user, asset) + accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) + if with_perms: + return perms, accounts + return accounts + def get_perm_accounts_for_user_group_asset(self, user_group, asset, with_actions=False): + """ 获取授权给用户组某个资产的账号 """ perms = self.get_permissions_for_user_group_asset(user_group, asset) accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) return accounts @staticmethod def get_perm_accounts_for_permissions(permissions, with_actions=False): + """ 获取授权规则包含的账号 """ aid_actions_map = defaultdict(int) for perm in permissions: account_ids = perm.get_all_accounts(flat=True) @@ -40,3 +45,14 @@ class PermAccountUtil(AssetPermissionUtil): account.actions = aid_actions_map.get(str(account.id)) return accounts + def validate_permission(self, user, asset, account_username): + """ 校验用户有某个资产下某个账号名的权限 """ + perms, accounts = self.get_perm_accounts_for_user_asset( + user, asset, with_actions=True, with_perms=True + ) + perm = perms.first() + # Todo: 后面可能需要加上 protocol 进行过滤, 因为同名的账号协议是不一样可能会存在多个 + account = accounts.filter(username=account_username).first() + actions = account.actions if account else [] + expire_at = perm.date_expired if perm else time.time() + return actions, expire_at diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index b16c69fa0..e7d88e06d 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -14,21 +14,6 @@ logger = get_logger(__file__) class AssetPermissionUtil(object): """ 资产授权相关的方法工具 """ - def get_permissions_for_user_asset(self, user, asset): - """ 获取同时包含用户、资产的授权规则 """ - user_perm_ids = self.get_permissions_for_user(user, flat=True) - asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) - perm_ids = set(user_perm_ids) & set(asset_perm_ids) - perms = AssetPermission.objects.filter(id__in=perm_ids) - return perms - - def get_permissions_for_user_group_asset(self, user_group, asset): - user_perm_ids = self.get_permissions_for_user_groups([user_group], flat=True) - asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) - perm_ids = set(user_perm_ids) & set(asset_perm_ids) - perms = AssetPermission.objects.filter(id__in=perm_ids) - return perms - def get_permissions_for_user(self, user, with_group=True, flat=False): """ 获取用户的授权规则 """ perm_ids = set() @@ -43,22 +28,21 @@ class AssetPermissionUtil(object): perm_ids.update(group_perm_ids) if flat: return perm_ids - perms = AssetPermission.objects.filter(id__in=perm_ids) + perms = self.get_permissions(ids=perm_ids) return perms - @staticmethod - def get_permissions_for_user_groups(user_groups, flat=False): + def get_permissions_for_user_groups(self, user_groups, flat=False): """ 获取用户组的授权规则 """ if isinstance(user_groups, list): group_ids = [g.id for g in user_groups] else: group_ids = user_groups.values_list('id', flat=True).distinct() - group_perm_ids = AssetPermission.user_groups.through.objects\ - .filter(usergroup_id__in=group_ids)\ + group_perm_ids = AssetPermission.user_groups.through.objects \ + .filter(usergroup_id__in=group_ids) \ .values_list('assetpermission_id', flat=True).distinct() if flat: return group_perm_ids - perms = AssetPermission.objects.filter(id__in=group_perm_ids) + perms = self.get_permissions(ids=group_perm_ids) return perms def get_permissions_for_asset(self, asset, with_node=True, flat=False): @@ -73,11 +57,10 @@ class AssetPermissionUtil(object): perm_ids.update(node_perm_ids) if flat: return perm_ids - perms = AssetPermission.objects.filter(id__in=perm_ids) + perms = self.get_permissions(ids=perm_ids) return perms - @staticmethod - def get_permissions_for_nodes(nodes, with_ancestor=False, flat=False): + def get_permissions_for_nodes(self, nodes, with_ancestor=False, flat=False): """ 获取节点的授权规则 """ if with_ancestor: node_ids = set() @@ -87,93 +70,29 @@ class AssetPermissionUtil(object): node_ids.update(_node_ids) else: node_ids = nodes.values_list('id', flat=True).distinct() - node_perm_ids = AssetPermission.nodes.through.objects.filter(node_id__in=node_ids) \ + perm_ids = AssetPermission.nodes.through.objects.filter(node_id__in=node_ids) \ .values_list('assetpermission_id', flat=True).distinct() if flat: - return node_perm_ids - perms = AssetPermission.objects.filter(id__in=node_perm_ids) + return perm_ids + perms = self.get_permissions(ids=perm_ids) return perms + def get_permissions_for_user_asset(self, user, asset): + """ 获取同时包含用户、资产的授权规则 """ + user_perm_ids = self.get_permissions_for_user(user, flat=True) + asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) + perm_ids = set(user_perm_ids) & set(asset_perm_ids) + perms = self.get_permissions(ids=perm_ids) + return perms -# TODO: 下面的方法放到类中进行实现 - - -def validate_permission(user, asset, account, action='connect'): - asset_perm_ids = get_user_all_asset_perm_ids(user) - - asset_perm_ids_from_asset = AssetPermission.assets.through.objects.filter( - assetpermission_id__in=asset_perm_ids, - asset_id=asset.id - ).values_list('assetpermission_id', flat=True) - - nodes = asset.get_nodes() - node_keys = set() - for node in nodes: - ancestor_keys = node.get_ancestor_keys(with_self=True) - node_keys.update(ancestor_keys) - node_ids = set(Node.objects.filter(key__in=node_keys).values_list('id', flat=True)) - - asset_perm_ids_from_node = AssetPermission.nodes.through.objects.filter( - assetpermission_id__in=asset_perm_ids, - node_id__in=node_ids - ).values_list('assetpermission_id', flat=True) - - asset_perm_ids = {*asset_perm_ids_from_asset, *asset_perm_ids_from_node} - - asset_perms = AssetPermission.objects\ - .filter(id__in=asset_perm_ids, accounts__contains=account)\ - .order_by('-date_expired') - - if asset_perms: - actions = set() - actions_values = asset_perms.values_list('actions', flat=True) - for value in actions_values: - _actions = Action.value_to_choices(value) - actions.update(_actions) - asset_perm: AssetPermission = asset_perms.first() - actions = list(actions) - expire_at = asset_perm.date_expired.timestamp() - else: - actions = [] - expire_at = time.time() - - # TODO: 组件改造API完成后统一通过actions判断has_perm - has_perm = action in actions - return has_perm, actions, expire_at - - -def get_asset_system_user_ids_with_actions(asset_perm_ids, asset: Asset): - nodes = asset.get_nodes() - node_keys = set() - for node in nodes: - ancestor_keys = node.get_ancestor_keys(with_self=True) - node_keys.update(ancestor_keys) - - queryset = AssetPermission.objects.filter(id__in=asset_perm_ids)\ - .filter(Q(assets=asset) | Q(nodes__key__in=node_keys)) - - asset_protocols = asset.protocols_as_dict.keys() - values = queryset.filter( - system_users__protocol__in=asset_protocols - ).distinct().values_list('system_users', 'actions') - system_users_actions = defaultdict(int) - - for system_user_id, actions in values: - if None in (system_user_id, actions): - continue - system_users_actions[system_user_id] |= actions - return system_users_actions - - -def get_asset_system_user_ids_with_actions_by_user(user: User, asset: Asset): - asset_perm_ids = get_user_all_asset_perm_ids(user) - return get_asset_system_user_ids_with_actions(asset_perm_ids, asset) - - -def has_asset_system_permission(user: User, asset: Asset, account: str): - systemuser_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset) - actions = systemuser_actions_mapper.get(account, 0) - if actions: - return True - return False + def get_permissions_for_user_group_asset(self, user_group, asset): + user_perm_ids = self.get_permissions_for_user_groups([user_group], flat=True) + asset_perm_ids = self.get_permissions_for_asset(asset, flat=True) + perm_ids = set(user_perm_ids) & set(asset_perm_ids) + perms = self.get_permissions(ids=perm_ids) + return perms + @staticmethod + def get_permissions(ids): + perms = AssetPermission.objects.filter(id__in=ids).order_by('-date_expired') + return perms From 28f4905a8120657c302091e85222493b37288ef4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 27 Oct 2022 16:26:15 +0800 Subject: [PATCH 235/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applet=20h?= =?UTF-8?q?ost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/migrations/0092_add_host.py | 1 + .../migrations/0099_auto_20220711_1409.py | 3 + .../migrations/0107_auto_20221019_1115.py | 13 ++-- .../migrations/0108_auto_20221019_1706.py | 75 ------------------- .../migrations/0109_auto_20221019_2040.py | 24 ------ .../migrations/0110_auto_20221021_1506.py | 23 ------ apps/orgs/mixins/serializers.py | 3 +- apps/terminal/api/applet/host.py | 20 ++--- ...024_1452.py => 0054_auto_20221027_1125.py} | 20 ++--- .../migrations/0055_auto_20221026_1631.py | 26 ------- apps/terminal/models/applet/host.py | 7 +- apps/terminal/serializers/applet.py | 57 +++++++------- 12 files changed, 54 insertions(+), 218 deletions(-) delete mode 100644 apps/assets/migrations/0108_auto_20221019_1706.py delete mode 100644 apps/assets/migrations/0109_auto_20221019_2040.py delete mode 100644 apps/assets/migrations/0110_auto_20221021_1506.py rename apps/terminal/migrations/{0054_auto_20221024_1452.py => 0054_auto_20221027_1125.py} (80%) delete mode 100644 apps/terminal/migrations/0055_auto_20221026_1631.py diff --git a/apps/assets/migrations/0092_add_host.py b/apps/assets/migrations/0092_add_host.py index 72ad9f7d8..83a63056f 100644 --- a/apps/assets/migrations/0092_add_host.py +++ b/apps/assets/migrations/0092_add_host.py @@ -96,6 +96,7 @@ class Migration(migrations.Migration): ('password_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Password selector')), ('submit_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Submit selector')), ('username_selector', models.CharField(blank=True, default='', max_length=128, verbose_name='Username selector')), + ('script', models.JSONField(blank=True, default=list, verbose_name='Script')), ], options={ 'abstract': False, diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index cf04f8b66..01ad1cf1f 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -22,6 +22,7 @@ class Migration(migrations.Migration): ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), + ('version', models.IntegerField(default=0, verbose_name='Version'),), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), ('history_change_reason', models.CharField(max_length=100, null=True)), @@ -46,6 +47,8 @@ class Migration(migrations.Migration): ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')), + ('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), diff --git a/apps/assets/migrations/0107_auto_20221019_1115.py b/apps/assets/migrations/0107_auto_20221019_1115.py index 6c812b708..c720655a8 100644 --- a/apps/assets/migrations/0107_auto_20221019_1115.py +++ b/apps/assets/migrations/0107_auto_20221019_1115.py @@ -35,7 +35,6 @@ class Migration(migrations.Migration): name='BaseAutomation', fields=[ ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), @@ -144,7 +143,7 @@ class Migration(migrations.Migration): ('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.automationexecution')), ], options={ - 'verbose_name': 'Change secret', + 'verbose_name': 'Change secret record', }, ), migrations.AddField( @@ -156,13 +155,11 @@ class Migration(migrations.Migration): name='ChangeSecretAutomation', fields=[ ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), - ('secret_types', models.JSONField(default=list, verbose_name='Secret types')), - ('password_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16, verbose_name='Password strategy')), - ('password', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), + ('secret_type', models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type')), + ('secret_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16, verbose_name='Secret strategy')), + ('secret', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Secret')), ('password_rules', models.JSONField(default=dict, verbose_name='Password rules')), - ('ssh_key_strategy', models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='random_one', max_length=16)), - ('ssh_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH key')), - ('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key strategy')), + ('ssh_key_change_strategy', models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy')), ('recipients', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Recipient')), ], options={ diff --git a/apps/assets/migrations/0108_auto_20221019_1706.py b/apps/assets/migrations/0108_auto_20221019_1706.py deleted file mode 100644 index 57279fffc..000000000 --- a/apps/assets/migrations/0108_auto_20221019_1706.py +++ /dev/null @@ -1,75 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-19 09:06 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0107_auto_20221019_1115'), - ] - - operations = [ - migrations.AlterModelOptions( - name='automationexecution', - options={'verbose_name': 'Automation task execution'}, - ), - migrations.AlterModelOptions( - name='baseautomation', - options={'verbose_name': 'Automation task'}, - ), - migrations.AlterModelOptions( - name='changesecretrecord', - options={'verbose_name': 'Change secret record'}, - ), - migrations.AlterModelOptions( - name='verifyaccountautomation', - options={'verbose_name': 'Verify account automation'}, - ), - migrations.RenameField( - model_name='changesecretautomation', - old_name='password', - new_name='secret', - ), - migrations.RemoveField( - model_name='baseautomation', - name='updated_by', - ), - migrations.RemoveField( - model_name='changesecretautomation', - name='password_strategy', - ), - migrations.RemoveField( - model_name='changesecretautomation', - name='secret_types', - ), - migrations.RemoveField( - model_name='changesecretautomation', - name='ssh_key', - ), - migrations.RemoveField( - model_name='changesecretautomation', - name='ssh_key_strategy', - ), - migrations.AddField( - model_name='changesecretautomation', - name='secret_strategy', - field=models.CharField(choices=[('specific', 'Specific'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], default='specific', max_length=16, verbose_name='Secret strategy'), - ), - migrations.AddField( - model_name='changesecretautomation', - name='secret_type', - field=models.CharField(choices=[('password', 'Password'), ('ssh_key', 'SSH key'), ('access_key', 'Access key'), ('token', 'Token')], default='password', max_length=16, verbose_name='Secret type'), - ), - migrations.AlterField( - model_name='automationexecution', - name='automation', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='assets.baseautomation', verbose_name='Automation task'), - ), - migrations.AlterField( - model_name='changesecretautomation', - name='ssh_key_change_strategy', - field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'), - ), - ] diff --git a/apps/assets/migrations/0109_auto_20221019_2040.py b/apps/assets/migrations/0109_auto_20221019_2040.py deleted file mode 100644 index 958ca4bea..000000000 --- a/apps/assets/migrations/0109_auto_20221019_2040.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-19 12:40 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0108_auto_20221019_1706'), - ] - - operations = [ - migrations.AddField( - model_name='web', - name='script', - field=models.JSONField(blank=True, default=list, verbose_name='Script'), - ), - migrations.AddField( - model_name='historicalaccount', - name='version', - field=models.IntegerField(default=0, verbose_name='Version'), - ), - ] diff --git a/apps/assets/migrations/0110_auto_20221021_1506.py b/apps/assets/migrations/0110_auto_20221021_1506.py deleted file mode 100644 index 039f7d179..000000000 --- a/apps/assets/migrations/0110_auto_20221021_1506.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-21 07:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0109_auto_20221019_2040'), - ] - - operations = [ - migrations.AddField( - model_name='account', - name='connectivity', - field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'), - ), - migrations.AddField( - model_name='account', - name='date_verified', - field=models.DateTimeField(null=True, verbose_name='Date verified'), - ), - ] diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py index 4ec936c2f..8f70814bc 100644 --- a/apps/orgs/mixins/serializers.py +++ b/apps/orgs/mixins/serializers.py @@ -31,8 +31,7 @@ class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer): validators = [] for v in _validators: - if isinstance(v, UniqueTogetherValidator) \ - and "org_id" in v.fields: + if isinstance(v, UniqueTogetherValidator) and "org_id" in v.fields: v = ProjectUniqueValidator(v.queryset, v.fields) validators.append(v) return validators diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index 09de7dbff..d4166ea98 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -1,26 +1,20 @@ from rest_framework import viewsets -from rest_framework.decorators import action -from orgs.utils import tmp_to_root_org -from orgs.models import Organization -from assets.models import Host +from orgs.utils import tmp_to_builtin_org from terminal import serializers, models __all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] class AppletHostViewSet(viewsets.ModelViewSet): - queryset = models.AppletHost.objects.all() serializer_class = serializers.AppletHostSerializer - @action(methods=['get'], detail=False) - def hosts(self, request): - with tmp_to_root_org(): - kwargs = { - 'platform__name': 'RemoteAppHost', - 'org_id': Organization.SYSTEM_ID - } - return Host.objects.filter(**kwargs) + def get_queryset(self): + return models.AppletHost.objects.all() + + def dispatch(self, request, *args, **kwargs): + with tmp_to_builtin_org(system=1): + return super().dispatch(request, *args, **kwargs) class AppletHostDeploymentViewSet(viewsets.ModelViewSet): diff --git a/apps/terminal/migrations/0054_auto_20221024_1452.py b/apps/terminal/migrations/0054_auto_20221027_1125.py similarity index 80% rename from apps/terminal/migrations/0054_auto_20221024_1452.py rename to apps/terminal/migrations/0054_auto_20221027_1125.py index c2d67ca65..418cbe993 100644 --- a/apps/terminal/migrations/0054_auto_20221024_1452.py +++ b/apps/terminal/migrations/0054_auto_20221027_1125.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.14 on 2022-10-24 06:52 +# Generated by Django 3.2.14 on 2022-10-27 03:25 from django.db import migrations, models import django.db.models.deletion @@ -8,7 +8,7 @@ import uuid class Migration(migrations.Migration): dependencies = [ - ('assets', '0110_auto_20221021_1506'), + ('assets', '0107_auto_20221019_1115'), ('terminal', '0053_auto_20220830_1244'), ] @@ -26,8 +26,7 @@ class Migration(migrations.Migration): ('version', models.CharField(max_length=16, verbose_name='Version')), ('author', models.CharField(max_length=128, verbose_name='Author')), ('type', models.CharField(choices=[('general', 'General'), ('web', 'Web')], default='general', max_length=16, verbose_name='Type')), - ('vcs_type', models.CharField(max_length=16, null=True, verbose_name='VCS type')), - ('vcs_url', models.CharField(max_length=256, null=True, verbose_name='URL')), + ('is_active', models.BooleanField(default=True, verbose_name='Is active')), ('protocols', models.JSONField(default=list, verbose_name='Protocol')), ('tags', models.JSONField(default=list, verbose_name='Tags')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), @@ -39,12 +38,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AppletHost', fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('host_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.host')), ('account_automation', models.BooleanField(default=False, verbose_name='Account automation')), ('date_synced', models.DateTimeField(blank=True, null=True, verbose_name='Date synced')), ('status', models.CharField(max_length=16, verbose_name='Status')), @@ -52,6 +46,7 @@ class Migration(migrations.Migration): options={ 'abstract': False, }, + bases=('assets.host',), ), migrations.CreateModel( name='AppletPublication', @@ -91,9 +86,4 @@ class Migration(migrations.Migration): name='applets', field=models.ManyToManyField(through='terminal.AppletPublication', to='terminal.Applet', verbose_name='Applet'), ), - migrations.AddField( - model_name='applethost', - name='host', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='assets.host', verbose_name='Host'), - ), ] diff --git a/apps/terminal/migrations/0055_auto_20221026_1631.py b/apps/terminal/migrations/0055_auto_20221026_1631.py deleted file mode 100644 index cbfe73256..000000000 --- a/apps/terminal/migrations/0055_auto_20221026_1631.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-26 08:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('terminal', '0054_auto_20221024_1452'), - ] - - operations = [ - migrations.RemoveField( - model_name='applet', - name='vcs_type', - ), - migrations.RemoveField( - model_name='applet', - name='vcs_url', - ), - migrations.AddField( - model_name='applet', - name='is_active', - field=models.BooleanField(default=True, verbose_name='Is active'), - ), - ] diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 89b0bd576..2fee8d15d 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -2,14 +2,13 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from common.db.models import JMSBaseModel +from assets.models import Host __all__ = ['AppletHost', 'AppletHostDeployment'] -class AppletHost(JMSBaseModel): - host = models.ForeignKey('assets.Host', on_delete=models.PROTECT, verbose_name=_('Host')) - comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) +class AppletHost(Host): account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) status = models.CharField(max_length=16, verbose_name=_('Status')) @@ -19,7 +18,7 @@ class AppletHost(JMSBaseModel): ) def __str__(self): - return self.host.name + return self.name class AppletHostDeployment(JMSBaseModel): diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 25259b402..5ad2416b8 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -2,9 +2,9 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.drf.fields import ObjectRelatedField, LabeledChoiceField -from assets.models import Host, Platform +from common.validators import ProjectUniqueValidator +from assets.models import Platform from assets.serializers import HostSerializer -from orgs.utils import tmp_to_builtin_org from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment @@ -48,41 +48,42 @@ class AppletPublicationSerializer(serializers.ModelSerializer): ] + read_only_fields -class AppletHostSerializer(serializers.ModelSerializer): - host = HostSerializer(allow_null=True, required=False) - - class Meta: +class AppletHostSerializer(HostSerializer): + class Meta(HostSerializer.Meta): model = AppletHost - fields_mini = ['id', 'host'] - read_only_fields = ['date_synced', 'status', 'date_created', 'date_updated'] - fields = fields_mini + ['comment', 'account_automation'] + read_only_fields + fields = HostSerializer.Meta.fields + [ + 'account_automation', 'status', 'date_synced' + ] + extra_kwargs = { + 'status': {'read_only': True}, + 'date_synced': {'read_only': True} + } - def __init__(self, *args, **kwargs): - self.host_data = kwargs.get('data', {}).pop('host', {}) - super().__init__(*args, **kwargs) + def __init__(self, *args, data=None, **kwargs): + self.set_initial_data(data) + super().__init__(*args, data=data, **kwargs) - def _create_host(self): + @staticmethod + def set_initial_data(data): + if not data: + return platform = Platform.objects.get(name='RemoteAppHost') - data = { - **self.host_data, + data.update({ 'platform': platform.id, 'nodes_display': [ 'RemoteAppHosts' ] - } - serializer = HostSerializer(data=data) - try: - serializer.is_valid(raise_exception=True) - except serializers.ValidationError: - raise serializers.ValidationError({'host': serializer.errors}) - host = serializer.save() - return host + }) - def create(self, validated_data): - with tmp_to_builtin_org(system=1): - host = self._create_host() - instance = super().create({**validated_data, 'host': host}) - return instance + def get_validators(self): + validators = super().get_validators() + # 不知道为啥没有继承过来 + uniq_validator = ProjectUniqueValidator( + queryset=AppletHost.objects.all(), + fields=('org_id', 'name') + ) + validators.append(uniq_validator) + return validators class AppletHostDeploymentSerializer(serializers.ModelSerializer): From e4d372be32b16fe86fb7ca879ccafd4bb66c2a43 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 27 Oct 2022 16:49:22 +0800 Subject: [PATCH 236/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E7=BB=84=E7=BB=87=20builtin=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/orgs/models.py b/apps/orgs/models.py index 5e78d2757..73dc3c6ad 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -140,7 +140,10 @@ class Organization(OrgRoleMixin, models.Model): @classmethod def default(cls): defaults = dict(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME) - obj, created = cls.objects.get_or_create(defaults=defaults, id=cls.DEFAULT_ID, builtin=True) + obj, created = cls.objects.get_or_create(defaults=defaults, id=cls.DEFAULT_ID) + if not obj.builtin: + obj.builtin = True + obj.save() return obj @classmethod From bb01a60fc151f974458686d6c8dbb782262edda6 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 27 Oct 2022 17:20:31 +0800 Subject: [PATCH 237/488] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20authen?= =?UTF-8?q?tication=20models=20=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/models/__init__.py | 5 ++ apps/authentication/models/access_key.py | 31 ++++++++ .../{models.py => models/connection_token.py} | 73 +------------------ apps/authentication/models/private_token.py | 9 +++ apps/authentication/models/sso_token.py | 18 +++++ apps/authentication/models/temp_token.py | 26 +++++++ 6 files changed, 91 insertions(+), 71 deletions(-) create mode 100644 apps/authentication/models/__init__.py create mode 100644 apps/authentication/models/access_key.py rename apps/authentication/{models.py => models/connection_token.py} (63%) create mode 100644 apps/authentication/models/private_token.py create mode 100644 apps/authentication/models/sso_token.py create mode 100644 apps/authentication/models/temp_token.py diff --git a/apps/authentication/models/__init__.py b/apps/authentication/models/__init__.py new file mode 100644 index 000000000..e889e03b1 --- /dev/null +++ b/apps/authentication/models/__init__.py @@ -0,0 +1,5 @@ +from .access_key import * +from .connection_token import * +from .private_token import * +from .sso_token import * +from .temp_token import * diff --git a/apps/authentication/models/access_key.py b/apps/authentication/models/access_key.py new file mode 100644 index 000000000..67aa6b812 --- /dev/null +++ b/apps/authentication/models/access_key.py @@ -0,0 +1,31 @@ +import uuid +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + +from django.db import models + + +class AccessKey(models.Model): + id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, + default=uuid.uuid4, editable=False) + secret = models.UUIDField(verbose_name='AccessKeySecret', + default=uuid.uuid4, editable=False) + user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User', + on_delete=models.CASCADE, related_name='access_keys') + is_active = models.BooleanField(default=True, verbose_name=_('Active')) + date_created = models.DateTimeField(auto_now_add=True) + + def get_id(self): + return str(self.id) + + def get_secret(self): + return str(self.secret) + + def get_full_value(self): + return '{}:{}'.format(self.id, self.secret) + + def __str__(self): + return str(self.id) + + class Meta: + verbose_name = _("Access key") diff --git a/apps/authentication/models.py b/apps/authentication/models/connection_token.py similarity index 63% rename from apps/authentication/models.py rename to apps/authentication/models/connection_token.py index 2ec71451f..c76d6e0f4 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models/connection_token.py @@ -1,62 +1,14 @@ import time -import uuid -from datetime import datetime, timedelta +from datetime import timedelta from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from rest_framework.authtoken.models import Token from orgs.mixins.models import OrgModelMixin from django.db import models from common.utils import lazyproperty from common.utils.timezone import as_current_tz -from common.db.models import BaseCreateUpdateModel, JMSBaseModel - - -class AccessKey(models.Model): - id = models.UUIDField(verbose_name='AccessKeyID', primary_key=True, - default=uuid.uuid4, editable=False) - secret = models.UUIDField(verbose_name='AccessKeySecret', - default=uuid.uuid4, editable=False) - user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='User', - on_delete=models.CASCADE, related_name='access_keys') - is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_created = models.DateTimeField(auto_now_add=True) - - def get_id(self): - return str(self.id) - - def get_secret(self): - return str(self.secret) - - def get_full_value(self): - return '{}:{}'.format(self.id, self.secret) - - def __str__(self): - return str(self.id) - - class Meta: - verbose_name = _("Access key") - - -class PrivateToken(Token): - """Inherit from auth token, otherwise migration is boring""" - - class Meta: - verbose_name = _('Private Token') - - -class SSOToken(BaseCreateUpdateModel): - """ - 类似腾讯企业邮的 [单点登录](https://exmail.qq.com/qy_mng_logic/doc#10036) - 出于安全考虑,这里的 `token` 使用一次随即过期。但我们保留每一个生成过的 `token`。 - """ - authkey = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('Token')) - expired = models.BooleanField(default=False, verbose_name=_('Expired')) - user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'), db_constraint=False) - - class Meta: - verbose_name = _('SSO token') +from common.db.models import JMSBaseModel def date_expired_default(): @@ -182,27 +134,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): return rules -class TempToken(JMSBaseModel): - username = models.CharField(max_length=128, verbose_name=_("Username")) - secret = models.CharField(max_length=64, verbose_name=_("Secret")) - verified = models.BooleanField(default=False, verbose_name=_("Verified")) - date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified")) - date_expired = models.DateTimeField(verbose_name=_("Date expired")) - - class Meta: - verbose_name = _("Temporary token") - - @property - def user(self): - from users.models import User - return User.objects.filter(username=self.username).first() - - @property - def is_valid(self): - not_expired = self.date_expired and self.date_expired > timezone.now() - return not self.verified and not_expired - - class SuperConnectionToken(ConnectionToken): class Meta: proxy = True diff --git a/apps/authentication/models/private_token.py b/apps/authentication/models/private_token.py new file mode 100644 index 000000000..8d83d1e0a --- /dev/null +++ b/apps/authentication/models/private_token.py @@ -0,0 +1,9 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework.authtoken.models import Token + + +class PrivateToken(Token): + """Inherit from auth token, otherwise migration is boring""" + + class Meta: + verbose_name = _('Private Token') diff --git a/apps/authentication/models/sso_token.py b/apps/authentication/models/sso_token.py new file mode 100644 index 000000000..fb4c68827 --- /dev/null +++ b/apps/authentication/models/sso_token.py @@ -0,0 +1,18 @@ +import uuid +from django.utils.translation import ugettext_lazy as _ + +from django.db import models +from common.db.models import BaseCreateUpdateModel + + +class SSOToken(BaseCreateUpdateModel): + """ + 类似腾讯企业邮的 [单点登录](https://exmail.qq.com/qy_mng_logic/doc#10036) + 出于安全考虑,这里的 `token` 使用一次随即过期。但我们保留每一个生成过的 `token`。 + """ + authkey = models.UUIDField(primary_key=True, default=uuid.uuid4, verbose_name=_('Token')) + expired = models.BooleanField(default=False, verbose_name=_('Expired')) + user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User'), db_constraint=False) + + class Meta: + verbose_name = _('SSO token') diff --git a/apps/authentication/models/temp_token.py b/apps/authentication/models/temp_token.py new file mode 100644 index 000000000..d76a30a42 --- /dev/null +++ b/apps/authentication/models/temp_token.py @@ -0,0 +1,26 @@ +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + +from django.db import models +from common.db.models import JMSBaseModel + + +class TempToken(JMSBaseModel): + username = models.CharField(max_length=128, verbose_name=_("Username")) + secret = models.CharField(max_length=64, verbose_name=_("Secret")) + verified = models.BooleanField(default=False, verbose_name=_("Verified")) + date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified")) + date_expired = models.DateTimeField(verbose_name=_("Date expired")) + + class Meta: + verbose_name = _("Temporary token") + + @property + def user(self): + from users.models import User + return User.objects.filter(username=self.username).first() + + @property + def is_valid(self): + not_expired = self.date_expired and self.date_expired > timezone.now() + return not self.verified and not_expired From c0540e6787a16871faf03c658381f52515d92633 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 27 Oct 2022 18:34:25 +0800 Subject: [PATCH 238/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20celery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 6 ++++++ .../management/commands/services/services/celery_base.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 31726fc02..411bf8b86 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -22,6 +22,12 @@ class AssetPlatformViewSet(JMSModelViewSet): 'ops_methods': 'assets.view_platform' } + def get_object(self): + pk = self.kwargs.get('pk', '') + if pk.isnumeric(): + return super().get_object() + return self.get_queryset().get(name=pk) + def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( diff --git a/apps/common/management/commands/services/services/celery_base.py b/apps/common/management/commands/services/services/celery_base.py index 435e4ebe2..6fd2a499d 100644 --- a/apps/common/management/commands/services/services/celery_base.py +++ b/apps/common/management/commands/services/services/celery_base.py @@ -30,7 +30,8 @@ class CeleryBaseService(BaseService): '-l', 'INFO', '-c', str(self.num), '-Q', self.queue, - '-n', f'{self.queue}@{server_hostname}' + '-n', f'{self.queue}@{server_hostname}', + '--without-mingle', ] return cmd From 2355d1af8344b3bab0bff0ade201d06bf2b1b007 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 27 Oct 2022 18:53:10 +0800 Subject: [PATCH 239/488] perf: gather accounts --- .../change_secret/database/mysql/main.yml | 4 + .../database/postgresql/main.yml | 8 +- .../change_secret/host/aix/main.yml | 59 ++++++++++---- .../host/{linux => posix}/main.yml | 4 +- .../host/{linux => posix}/manifest.yml | 4 +- apps/assets/automations/endpoint.py | 5 +- .../automations/gather_accounts/__init__.py | 0 .../gather_accounts/database/mysql/main.yml | 22 ++++++ .../database/mysql/manifest.yml | 6 ++ .../database/postgresql/main.yml | 23 ++++++ .../database/postgresql/manifest.yml | 6 ++ .../automations/gather_accounts/filter.py | 56 +++++++++++++ .../gather_accounts/host/posix/main.yml | 14 ++++ .../gather_accounts/host/posix/manifest.yml | 7 ++ .../gather_accounts/host/windows/main.yml | 18 +++++ .../gather_accounts/host/windows/manifest.yml | 7 ++ .../automations/gather_accounts/manager.py | 45 +++++++++++ .../gather_facts/database/mysql/main.yml | 2 +- .../gather_facts/database/postgresql/main.yml | 4 +- .../gather_facts/host/posix/main.yml | 2 +- apps/assets/automations/ping/manager.py | 78 ++----------------- apps/assets/const/automation.py | 2 +- apps/assets/const/types.py | 1 + .../migrations/0111_auto_20221027_1053.py | 29 +++++++ apps/assets/models/automations/__init__.py | 1 + apps/assets/models/automations/base.py | 3 +- .../models/automations/gather_accounts.py | 15 ++++ apps/ops/ansible/inventory.py | 6 +- 28 files changed, 328 insertions(+), 103 deletions(-) rename apps/assets/automations/change_secret/host/{linux => posix}/main.yml (95%) rename apps/assets/automations/change_secret/host/{linux => posix}/manifest.yml (52%) create mode 100644 apps/assets/automations/gather_accounts/__init__.py create mode 100644 apps/assets/automations/gather_accounts/database/mysql/main.yml create mode 100644 apps/assets/automations/gather_accounts/database/mysql/manifest.yml create mode 100644 apps/assets/automations/gather_accounts/database/postgresql/main.yml create mode 100644 apps/assets/automations/gather_accounts/database/postgresql/manifest.yml create mode 100644 apps/assets/automations/gather_accounts/filter.py create mode 100644 apps/assets/automations/gather_accounts/host/posix/main.yml create mode 100644 apps/assets/automations/gather_accounts/host/posix/manifest.yml create mode 100644 apps/assets/automations/gather_accounts/host/windows/main.yml create mode 100644 apps/assets/automations/gather_accounts/host/windows/manifest.yml create mode 100644 apps/assets/automations/gather_accounts/manager.py create mode 100644 apps/assets/migrations/0111_auto_20221027_1053.py create mode 100644 apps/assets/models/automations/gather_accounts.py diff --git a/apps/assets/automations/change_secret/database/mysql/main.yml b/apps/assets/automations/change_secret/database/mysql/main.yml index 39560a383..c76b53b08 100644 --- a/apps/assets/automations/change_secret/database/mysql/main.yml +++ b/apps/assets/automations/change_secret/database/mysql/main.yml @@ -27,6 +27,7 @@ password: "{{ account.secret }}" host: "%" when: db_info is succeeded + register: change_info - name: Verify password community.mysql.mysql_info: @@ -35,3 +36,6 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" filter: version + when: + - db_info is succeeded + - change_info is succeeded \ No newline at end of file diff --git a/apps/assets/automations/change_secret/database/postgresql/main.yml b/apps/assets/automations/change_secret/database/postgresql/main.yml index 816d4c0e2..40c326704 100644 --- a/apps/assets/automations/change_secret/database/postgresql/main.yml +++ b/apps/assets/automations/change_secret/database/postgresql/main.yml @@ -1,8 +1,8 @@ - hosts: postgre gather_facts: no vars: -# ansible_python_interpreter: /usr/local/bin/python - ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + ansible_python_interpreter: /usr/local/bin/python + tasks: - name: Test PostgreSQL connection community.postgresql.postgresql_ping: @@ -37,4 +37,6 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" db: "{{ jms_asset.database }}" - when: db_info is succeeded and change_info is changed + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/host/aix/main.yml b/apps/assets/automations/change_secret/host/aix/main.yml index 41e093df8..1a4e6a6a4 100644 --- a/apps/assets/automations/change_secret/host/aix/main.yml +++ b/apps/assets/automations/change_secret/host/aix/main.yml @@ -1,29 +1,58 @@ - hosts: demo + gather_facts: no tasks: - - name: ping + - name: Test privileged account ansible.builtin.ping: - - #- name: print variables - # debug: - # msg: "Username: {{ account.username }}, Password: {{ account.password }}" + # + # - name: print variables + # debug: + # msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}" - name: Change password - user: + ansible.builtin.user: name: "{{ account.username }}" - password: "{{ account.password | password_hash('des') }}" + password: "{{ account.secret | password_hash('sha512') }}" update_password: always - when: account.password + when: secret_type == "password" - - name: Change public key - authorized_key: + - name: create user If it already exists, no operation will be performed + ansible.builtin.user: + name: "{{ account.username }}" + when: secret_type == "ssh_key" + + - name: remove jumpserver ssh key + ansible.builtin.lineinfile: + dest: "{{ kwargs.dest }}" + regexp: "{{ kwargs.regexp }}" + state: absent + when: + - secret_type == "ssh_key" + - kwargs.strategy == "set_jms" + + - name: Change SSH key + ansible.builtin.authorized_key: user: "{{ account.username }}" - key: "{{ account.public_key }}" - state: present - when: account.public_key + key: "{{ account.secret }}" + exclusive: "{{ kwargs.exclusive }}" + when: secret_type == "ssh_key" + + - name: Refresh connection + ansible.builtin.meta: reset_connection - name: Verify password ansible.builtin.ping: + become: no vars: ansible_user: "{{ account.username }}" - ansible_pass: "{{ account.password }}" - ansible_ssh_connection: paramiko + ansible_password: "{{ account.secret }}" + ansible_become: no + when: secret_type == "password" + + - name: Verify SSH key + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no + when: secret_type == "ssh_key" diff --git a/apps/assets/automations/change_secret/host/linux/main.yml b/apps/assets/automations/change_secret/host/posix/main.yml similarity index 95% rename from apps/assets/automations/change_secret/host/linux/main.yml rename to apps/assets/automations/change_secret/host/posix/main.yml index d295079ba..1a4e6a6a4 100644 --- a/apps/assets/automations/change_secret/host/linux/main.yml +++ b/apps/assets/automations/change_secret/host/posix/main.yml @@ -25,7 +25,9 @@ dest: "{{ kwargs.dest }}" regexp: "{{ kwargs.regexp }}" state: absent - when: secret_type == "ssh_key" and kwargs.strategy == "set_jms" + when: + - secret_type == "ssh_key" + - kwargs.strategy == "set_jms" - name: Change SSH key ansible.builtin.authorized_key: diff --git a/apps/assets/automations/change_secret/host/linux/manifest.yml b/apps/assets/automations/change_secret/host/posix/manifest.yml similarity index 52% rename from apps/assets/automations/change_secret/host/linux/manifest.yml rename to apps/assets/automations/change_secret/host/posix/manifest.yml index afd9b9671..491fb14a2 100644 --- a/apps/assets/automations/change_secret/host/linux/manifest.yml +++ b/apps/assets/automations/change_secret/host/posix/manifest.yml @@ -1,5 +1,5 @@ -id: change_secret_linux -name: Change password for Linux +id: change_secret_posix +name: Change secret for posix category: host type: - unix diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index 0efbc55ea..11330370a 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -1,8 +1,6 @@ -# from .backup.manager import AccountBackupExecutionManager -# -# from .change_secret.manager import ChangeSecretManager from .gather_facts.manager import GatherFactsManager +from .gather_accounts.manager import GatherAccountsManager from ..const import AutomationTypes @@ -10,6 +8,7 @@ class ExecutionManager: manager_type_mapper = { AutomationTypes.change_secret: ChangeSecretManager, AutomationTypes.gather_facts: GatherFactsManager, + AutomationTypes.gather_accounts: GatherAccountsManager, } def __init__(self, execution): diff --git a/apps/assets/automations/gather_accounts/__init__.py b/apps/assets/automations/gather_accounts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/automations/gather_accounts/database/mysql/main.yml b/apps/assets/automations/gather_accounts/database/mysql/main.yml new file mode 100644 index 000000000..fc61d26fc --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mysql/main.yml @@ -0,0 +1,22 @@ +- hosts: mysql + gather_facts: no + vars: +# ansible_python_interpreter: /usr/local/bin/python + ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + + tasks: + - name: Get info + community.mysql.mysql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: users + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.users }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/mysql/manifest.yml b/apps/assets/automations/gather_accounts/database/mysql/manifest.yml new file mode 100644 index 000000000..e69cca67b --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mysql/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_mysql +name: Gather account from MySQL +category: database +type: + - mysql +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/database/postgresql/main.yml b/apps/assets/automations/gather_accounts/database/postgresql/main.yml new file mode 100644 index 000000000..c5680f6b3 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/postgresql/main.yml @@ -0,0 +1,23 @@ +- hosts: postgresql + gather_facts: no + vars: +# ansible_python_interpreter: /usr/local/bin/python + ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + + tasks: + - name: Get info + community.postgresql.postgresql_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_db: "{{ jms_asset.database }}" + filter: "roles" + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.roles }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/postgresql/manifest.yml b/apps/assets/automations/gather_accounts/database/postgresql/manifest.yml new file mode 100644 index 000000000..3e563053a --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_postgresql +name: Gather account for PostgreSQL +category: database +type: + - postgresql +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/filter.py b/apps/assets/automations/gather_accounts/filter.py new file mode 100644 index 000000000..0c8f32536 --- /dev/null +++ b/apps/assets/automations/gather_accounts/filter.py @@ -0,0 +1,56 @@ +from django.utils import timezone + +__all__ = ['GatherAccountsFilter'] + + +# TODO 后期会挪到playbook中 +class GatherAccountsFilter: + + def __init__(self, tp): + self.tp = tp + + @staticmethod + def mysql_filter(info): + result = {} + for _, user_dict in info.items(): + for username, data in user_dict.items(): + if data.get('account_locked') == 'N': + result[username] = {} + return result + + @staticmethod + def postgresql_filter(info): + result = {} + for username in info: + result[username] = {} + return result + + @staticmethod + def posix_filter(info): + result = {} + for line in info: + data = line.split('@') + if len(data) != 3: + continue + username, address, dt = data + date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z') + result[username] = {'address': address, 'date': date} + return result + + @staticmethod + def windows_filter(info): + # TODO + result = {} + return result + + def run(self, method_id_meta_mapper, info): + run_method_name = None + for k, v in method_id_meta_mapper.items(): + if self.tp not in v['type']: + continue + run_method_name = k.replace(f'{v["method"]}_', '') + + if not run_method_name: + return info + + return getattr(self, f'{run_method_name}_filter')(info) diff --git a/apps/assets/automations/gather_accounts/host/posix/main.yml b/apps/assets/automations/gather_accounts/host/posix/main.yml new file mode 100644 index 000000000..97326431d --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/posix/main.yml @@ -0,0 +1,14 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Gather posix account + ansible.builtin.win_shell: + cmd: net user + register: result + + - name: Define info by set_fact + ansible.builtin.set_fact: + info: "{{ result.stdout_lines }}" + + - debug: + var: info \ No newline at end of file diff --git a/apps/assets/automations/gather_accounts/host/posix/manifest.yml b/apps/assets/automations/gather_accounts/host/posix/manifest.yml new file mode 100644 index 000000000..a761c9796 --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/posix/manifest.yml @@ -0,0 +1,7 @@ +id: gather_accounts_posix +name: Gather posix account +category: host +type: + - linux + - unix +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/host/windows/main.yml b/apps/assets/automations/gather_accounts/host/windows/main.yml new file mode 100644 index 000000000..377ffd10a --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/windows/main.yml @@ -0,0 +1,18 @@ +- hosts: windows + gather_facts: yes + tasks: + - name: Get info + set_fact: + info: + arch: "{{ ansible_architecture2 }}" + distribution: "{{ ansible_distribution }}" + distribution_version: "{{ ansible_distribution_version }}" + kernel: "{{ ansible_kernel }}" + vendor: "{{ ansible_system_vendor }}" + model: "{{ ansible_product_name }}" + sn: "{{ ansible_product_serial }}" + cpu_vcpus: "{{ ansible_processor_vcpus }}" + memory: "{{ ansible_memtotal_mb }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/host/windows/manifest.yml b/apps/assets/automations/gather_accounts/host/windows/manifest.yml new file mode 100644 index 000000000..ffc2ef7ee --- /dev/null +++ b/apps/assets/automations/gather_accounts/host/windows/manifest.yml @@ -0,0 +1,7 @@ +id: gather_accounts_windows +name: Gather account windows +version: 1 +method: gather_accounts +category: host +type: + - windows diff --git a/apps/assets/automations/gather_accounts/manager.py b/apps/assets/automations/gather_accounts/manager.py new file mode 100644 index 000000000..d6881f96c --- /dev/null +++ b/apps/assets/automations/gather_accounts/manager.py @@ -0,0 +1,45 @@ +from common.utils import get_logger +from assets.const import AutomationTypes +from orgs.utils import tmp_to_org +from .filter import GatherAccountsFilter +from ...models import Account, GatheredUser +from ..base.manager import BasePlaybookManager + +logger = get_logger(__name__) + + +class GatherAccountsManager(BasePlaybookManager): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.host_asset_mapper = {} + + @classmethod + def method_type(cls): + return AutomationTypes.gather_accounts + + def host_callback(self, host, asset=None, **kwargs): + super().host_callback(host, asset=asset, **kwargs) + self.host_asset_mapper[host['name']] = asset + return host + + def filter_success_result(self, host, result): + result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result) + return result + + def on_host_success(self, host, result): + info = result.get('debug', {}).get('res', {}).get('info', {}) + asset = self.host_asset_mapper.get(host) + org_id = asset.org_id + if asset and info: + result = self.filter_success_result(host, info) + with tmp_to_org(org_id): + GatheredUser.objects.filter(asset=asset, present=True).update(present=False) + for username, data in result.items(): + defaults = {'asset': asset, 'present': True, 'username': username} + if data.get('date'): + defaults['date_last_login'] = data['date'] + if data.get('address'): + defaults['ip_last_login'] = data['address'][:32] + GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username) + else: + logger.error("Not found info, task name must be 'Get info': {}".format(host)) diff --git a/apps/assets/automations/gather_facts/database/mysql/main.yml b/apps/assets/automations/gather_facts/database/mysql/main.yml index c4e90835e..8ba210283 100644 --- a/apps/assets/automations/gather_facts/database/mysql/main.yml +++ b/apps/assets/automations/gather_facts/database/mysql/main.yml @@ -13,7 +13,7 @@ filter: version register: db_info - - name: Define Mysql info by set_fact + - name: Define info by set_fact set_fact: info: version: "{{ db_info.version.full }}" diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml index 55731a4fa..82adcdc16 100644 --- a/apps/assets/automations/gather_facts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml @@ -1,4 +1,4 @@ -- hosts: postgre +- hosts: postgresql gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python @@ -13,7 +13,7 @@ login_db: "{{ jms_asset.database }}" register: db_info - - name: Define Postgresql info by set_fact + - name: Define info by set_fact set_fact: info: version: "{{ db_info.server_version.raw }}" diff --git a/apps/assets/automations/gather_facts/host/posix/main.yml b/apps/assets/automations/gather_facts/host/posix/main.yml index f42635458..81aef9aac 100644 --- a/apps/assets/automations/gather_facts/host/posix/main.yml +++ b/apps/assets/automations/gather_facts/host/posix/main.yml @@ -1,4 +1,4 @@ -- hosts: website +- hosts: demo gather_facts: yes tasks: - name: Get info diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py index 36017438b..84712fb44 100644 --- a/apps/assets/automations/ping/manager.py +++ b/apps/assets/automations/ping/manager.py @@ -1,75 +1,9 @@ -import os -import shutil -from copy import deepcopy -from collections import defaultdict - -import yaml -from django.utils.translation import gettext as _ - -from ops.ansible import PlaybookRunner +from common.utils import get_logger +from assets.const import AutomationTypes from ..base.manager import BasePlaybookManager -from assets.automations.methods import platform_automation_methods - - -class ChangePasswordManager(BasePlaybookManager): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.id_method_mapper = { - method['id']: method - for method in platform_automation_methods - } - self.method_hosts_mapper = defaultdict(list) - self.playbooks = [] - - def inventory_kwargs(self): - return { - 'host_callback': self.host_duplicator - } - - def generate_playbook(self): - playbook = [] - for method_id, host_names in self.method_hosts_mapper.items(): - method = self.id_method_mapper[method_id] - method_playbook_dir_path = method['dir'] - method_playbook_dir_name = os.path.basename(method_playbook_dir_path) - sub_playbook_dir = os.path.join(os.path.dirname(self.playbook_path), method_playbook_dir_name) - shutil.copytree(method_playbook_dir_path, sub_playbook_dir) - sub_playbook_path = os.path.join(sub_playbook_dir, 'main.yml') - - with open(sub_playbook_path, 'r') as f: - host_playbook_play = yaml.safe_load(f) - - if isinstance(host_playbook_play, list): - host_playbook_play = host_playbook_play[0] - - step = 10 - hosts_grouped = [host_names[i:i+step] for i in range(0, len(host_names), step)] - for i, hosts in enumerate(hosts_grouped): - plays = [] - play = deepcopy(host_playbook_play) - play['hosts'] = ':'.join(hosts) - plays.append(play) - - playbook_path = os.path.join(sub_playbook_dir, 'part_{}.yml'.format(i)) - with open(playbook_path, 'w') as f: - yaml.safe_dump(plays, f) - self.playbooks.append(playbook_path) - - playbook.append({ - 'name': method['name'] + ' for part {}'.format(i), - 'import_playbook': os.path.join(method_playbook_dir_name, 'part_{}.yml'.format(i)) - }) - - with open(self.playbook_path, 'w') as f: - yaml.safe_dump(playbook, f) - - print("Generate playbook done: " + self.playbook_path) - - def get_runner(self): - return PlaybookRunner( - self.inventory_path, - self.playbook_path, - self.runtime_dir - ) + +logger = get_logger(__name__) +class PingManager(BasePlaybookManager): + pass diff --git a/apps/assets/const/automation.py b/apps/assets/const/automation.py index 54b3cc019..6b3b6dbd4 100644 --- a/apps/assets/const/automation.py +++ b/apps/assets/const/automation.py @@ -15,7 +15,7 @@ class AutomationTypes(TextChoices): push_account = 'push_account', _('Create account') change_secret = 'change_secret', _('Change secret') verify_account = 'verify_account', _('Verify account') - gather_account = 'gather_account', _('Gather account') + gather_accounts = 'gather_accounts', _('Gather accounts') class SecretStrategy(TextChoices): diff --git a/apps/assets/const/types.py b/apps/assets/const/types.py index b77872ad0..f9cf83b85 100644 --- a/apps/assets/const/types.py +++ b/apps/assets/const/types.py @@ -166,6 +166,7 @@ class AllTypes(ChoicesMixin): data['protocols'] = protocols automation = constraints.get('automation', {}) + enable_fields = {k: v for k, v in automation.items() if k.endswith('_enabled')} for k, v in enable_fields.items(): auto_item = k.replace('_enabled', '') diff --git a/apps/assets/migrations/0111_auto_20221027_1053.py b/apps/assets/migrations/0111_auto_20221027_1053.py new file mode 100644 index 000000000..093ddb115 --- /dev/null +++ b/apps/assets/migrations/0111_auto_20221027_1053.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.14 on 2022-10-27 02:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_auto_20221021_1506'), + ] + + operations = [ + migrations.CreateModel( + name='GatherAccountsAutomation', + fields=[ + ('baseautomation_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.baseautomation')), + ], + options={ + 'verbose_name': 'Gather asset accounts', + }, + bases=('assets.baseautomation',), + ), + migrations.AlterField( + model_name='baseautomation', + name='type', + field=models.CharField(choices=[('ping', 'Ping'), ('gather_facts', 'Gather facts'), ('push_account', 'Create account'), ('change_secret', 'Change secret'), ('verify_account', 'Verify account'), ('gather_accounts', 'Gather accounts')], max_length=16, verbose_name='Type'), + ), + ] diff --git a/apps/assets/models/automations/__init__.py b/apps/assets/models/automations/__init__.py index 77a885b1b..1c62bbddd 100644 --- a/apps/assets/models/automations/__init__.py +++ b/apps/assets/models/automations/__init__.py @@ -3,3 +3,4 @@ from .discovery_account import * from .push_account import * from .verify_secret import * from .gather_facts import * +from .gather_accounts import * diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 1f9e1ac04..aabfd241f 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -10,6 +10,7 @@ from orgs.mixins.models import OrgModelMixin from ops.mixin import PeriodTaskModelMixin from assets.models import Node, Asset from assets.tasks import execute_automation +from assets.const import AutomationTypes class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): @@ -20,7 +21,7 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): assets = models.ManyToManyField( 'assets.Asset', blank=True, verbose_name=_("Assets") ) - type = models.CharField(max_length=16, verbose_name=_('Type')) + type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type')) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) comment = models.TextField(blank=True, verbose_name=_('Comment')) diff --git a/apps/assets/models/automations/gather_accounts.py b/apps/assets/models/automations/gather_accounts.py new file mode 100644 index 000000000..861031af4 --- /dev/null +++ b/apps/assets/models/automations/gather_accounts.py @@ -0,0 +1,15 @@ +from django.utils.translation import ugettext_lazy as _ + +from assets.const import AutomationTypes +from .base import BaseAutomation + +__all__ = ['GatherAccountsAutomation'] + + +class GatherAccountsAutomation(BaseAutomation): + def save(self, *args, **kwargs): + self.type = AutomationTypes.gather_accounts + super().save(*args, **kwargs) + + class Meta: + verbose_name = _("Gather asset accounts") diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index c544cfcd6..35344ad6a 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -106,7 +106,7 @@ class JMSInventory: 'jms_asset': { 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, - 'protocol': asset.protocol, 'port': asset.port, + 'protocol': asset.protocol, 'port': asset.port,'database': '', 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, 'jms_account': { @@ -117,6 +117,10 @@ class JMSInventory: ansible_config = dict(automation.ansible_config) ansible_connection = ansible_config.get('ansible_connection', 'ssh') host.update(ansible_config) + + if platform.category == 'database': + host['jms_asset']['database'] = asset.database.db_name + gateway = None if asset.domain: gateway = asset.domain.select_gateway() From 4ab14b4a59197da68a762564ac2099d9f23bc7f7 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 27 Oct 2022 19:03:42 +0800 Subject: [PATCH 240/488] perf: migrat --- .../assets/automations/gather_accounts/database/mysql/main.yml | 3 +-- .../automations/gather_accounts/database/postgresql/main.yml | 3 +-- .../{0111_auto_20221027_1053.py => 0108_auto_20221027_1053.py} | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) rename apps/assets/migrations/{0111_auto_20221027_1053.py => 0108_auto_20221027_1053.py} (95%) diff --git a/apps/assets/automations/gather_accounts/database/mysql/main.yml b/apps/assets/automations/gather_accounts/database/mysql/main.yml index fc61d26fc..cc934f20f 100644 --- a/apps/assets/automations/gather_accounts/database/mysql/main.yml +++ b/apps/assets/automations/gather_accounts/database/mysql/main.yml @@ -1,8 +1,7 @@ - hosts: mysql gather_facts: no vars: -# ansible_python_interpreter: /usr/local/bin/python - ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + ansible_python_interpreter: /usr/local/bin/python tasks: - name: Get info diff --git a/apps/assets/automations/gather_accounts/database/postgresql/main.yml b/apps/assets/automations/gather_accounts/database/postgresql/main.yml index c5680f6b3..2e12f51e5 100644 --- a/apps/assets/automations/gather_accounts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_accounts/database/postgresql/main.yml @@ -1,8 +1,7 @@ - hosts: postgresql gather_facts: no vars: -# ansible_python_interpreter: /usr/local/bin/python - ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + ansible_python_interpreter: /usr/local/bin/python tasks: - name: Get info diff --git a/apps/assets/migrations/0111_auto_20221027_1053.py b/apps/assets/migrations/0108_auto_20221027_1053.py similarity index 95% rename from apps/assets/migrations/0111_auto_20221027_1053.py rename to apps/assets/migrations/0108_auto_20221027_1053.py index 093ddb115..c59dcf7e7 100644 --- a/apps/assets/migrations/0111_auto_20221027_1053.py +++ b/apps/assets/migrations/0108_auto_20221027_1053.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('assets', '0110_auto_20221021_1506'), + ('assets', '0107_auto_20221019_1115'), ] operations = [ From da911651aac0312a50a6e719b918e31a81a2934a Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Thu, 27 Oct 2022 19:23:15 +0800 Subject: [PATCH 241/488] feat: celery task api --- apps/ops/api/celery.py | 2 +- .../0028_celerytask_last_published_time.py | 18 ++++++++++ apps/ops/models/celery.py | 33 +++++++++++++++---- apps/ops/serializers/celery.py | 2 +- apps/ops/signal_handlers.py | 1 + apps/ops/tasks.py | 21 +++++++++--- apps/rbac/const.py | 1 - 7 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 apps/ops/migrations/0028_celerytask_last_published_time.py diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 61d13db17..85a5c00d2 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -99,7 +99,7 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): - queryset = CeleryTask.objects.all() + queryset = CeleryTask.objects.filter(name__in=['ops.tasks.hello', 'ops.tasks.hello_error', 'ops.tasks.hello_random']) serializer_class = CeleryTaskSerializer http_method_names = ('get', 'head', 'options',) diff --git a/apps/ops/migrations/0028_celerytask_last_published_time.py b/apps/ops/migrations/0028_celerytask_last_published_time.py new file mode 100644 index 000000000..6732508e3 --- /dev/null +++ b/apps/ops/migrations/0028_celerytask_last_published_time.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-10-27 06:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0027_auto_20221024_1709'), + ] + + operations = [ + migrations.AddField( + model_name='celerytask', + name='last_published_time', + field=models.DateTimeField(null=True), + ), + ] diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 6ea4e2641..b7e1296a0 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -13,18 +13,37 @@ from ops.celery import app class CeleryTask(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) name = models.CharField(max_length=1024) + last_published_time = models.DateTimeField(null=True) @property - def verbose_name(self): + def meta(self): task = app.tasks.get(self.name, None) - if task: - return getattr(task, 'verbose_name', None) + return { + "verbose_name": getattr(task, 'verbose_name', None), + "comment": getattr(task, 'comment', None), + "queue": getattr(task, 'queue', 'default') + } @property - def description(self): - task = app.tasks.get(self.name, None) - if task: - return getattr(task, 'description', None) + def success_count(self): + return CeleryTaskExecution.objects.filter(name=self.name, state='SUCCESS').count() + + @property + def publish_count(self): + return CeleryTaskExecution.objects.filter(name=self.name).count() + + @property + def state(self): + last_five_executions = CeleryTaskExecution.objects.filter(name=self.name).order_by('-date_published')[:5] + + if len(last_five_executions) > 0: + if last_five_executions[0].state == 'FAILURE': + return "red" + + for execution in last_five_executions: + if execution.state == 'FAILURE': + return "yellow" + return "green" class CeleryTaskExecution(models.Model): diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py index b2aa6eb7a..3fd72fde3 100644 --- a/apps/ops/serializers/celery.py +++ b/apps/ops/serializers/celery.py @@ -31,7 +31,7 @@ class CeleryTaskSerializer(serializers.ModelSerializer): class Meta: model = CeleryTask fields = [ - 'id', 'name', 'verbose_name', 'description', + 'id', 'name', 'meta', 'publish_count', 'state', 'success_count', 'last_published_time', ] diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index a444558bc..b713ccbf4 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -92,3 +92,4 @@ def task_sent_handler(headers=None, body=None, **kwargs): 'kwargs': kwargs } CeleryTaskExecution.objects.create(**data) + CeleryTask.objects.filter(name=task).update(last_published_time=timezone.now()) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index dc3ac6e68..c749cb66c 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,5 +1,6 @@ # coding: utf-8 import os +import random import subprocess from django.conf import settings @@ -76,7 +77,7 @@ def run_playbook(pid, **kwargs): @shared_task @after_app_shutdown_clean_periodic -@register_as_period_task(interval=3600*24, description=_("Clean task history period")) +@register_as_period_task(interval=3600 * 24, description=_("Clean task history period")) def clean_tasks_adhoc_period(): logger.debug("Start clean task adhoc and run history") tasks = Task.objects.all() @@ -89,7 +90,7 @@ def clean_tasks_adhoc_period(): @shared_task @after_app_shutdown_clean_periodic -@register_as_period_task(interval=3600*24, description=_("Clean celery log period")) +@register_as_period_task(interval=3600 * 24, description=_("Clean celery log period")) def clean_celery_tasks_period(): logger.debug("Start clean celery task history") expire_days = get_log_keep_day('TASK_LOG_KEEP_DAYS') @@ -143,7 +144,7 @@ def check_server_performance_period(): ServerPerformanceCheckUtil().check_and_publish() -@shared_task(queue="ansible", verbose_name=_("Hello")) +@shared_task(queue="ansible", verbose_name=_("Hello"), comment="an test shared task") def hello(name, callback=None): from users.models import User import time @@ -155,6 +156,18 @@ def hello(name, callback=None): return gettext("Hello") +@shared_task(verbose_name="Hello Error", comment="an test shared task error") +def hello_error(): + raise Exception("must be error") + + +@shared_task(verbose_name="Hello Random", comment="some time error and some time success") +def hello_random(): + i = random.randint(0, 1) + if i == 1: + raise Exception("must be error") + + @shared_task def hello_callback(result): print(result) @@ -171,5 +184,3 @@ def execute_automation_strategy(pid, trigger): return with tmp_to_org(instance.org): instance.execute(trigger) - - diff --git a/apps/rbac/const.py b/apps/rbac/const.py index 71c8ea26d..541b0b3da 100644 --- a/apps/rbac/const.py +++ b/apps/rbac/const.py @@ -57,7 +57,6 @@ exclude_permissions = ( ('rbac', 'role', '*', '*'), ('ops', 'adhoc', 'delete,change', '*'), ('ops', 'adhocexecution', 'add,delete,change', '*'), - ('ops', 'celerytask', '*', '*'), ('ops', 'task', 'add,change', 'task'), ('ops', 'commandexecution', 'delete,change', 'commandexecution'), ('orgs', 'organizationmember', '*', '*'), From 53b0041b096890f8e971c25ea87f2779d3a338e9 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Thu, 27 Oct 2022 19:25:48 +0800 Subject: [PATCH 242/488] feat: celery task api --- apps/ops/models/celery.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index b7e1296a0..2cc989fc3 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -23,15 +23,6 @@ class CeleryTask(models.Model): "comment": getattr(task, 'comment', None), "queue": getattr(task, 'queue', 'default') } - - @property - def success_count(self): - return CeleryTaskExecution.objects.filter(name=self.name, state='SUCCESS').count() - - @property - def publish_count(self): - return CeleryTaskExecution.objects.filter(name=self.name).count() - @property def state(self): last_five_executions = CeleryTaskExecution.objects.filter(name=self.name).order_by('-date_published')[:5] From 3d616b01b057aa933a48f26ceb322b31e20ca4a8 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 27 Oct 2022 20:01:50 +0800 Subject: [PATCH 243/488] =?UTF-8?q?refactor:=20ConnectionToken=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20protocol=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0013_connectiontoken_protocol.py | 18 ++++++++++ .../authentication/models/connection_token.py | 9 +++-- .../serializers/connection_token.py | 33 ++++++++----------- apps/perms/utils/account.py | 1 - 4 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 apps/authentication/migrations/0013_connectiontoken_protocol.py diff --git a/apps/authentication/migrations/0013_connectiontoken_protocol.py b/apps/authentication/migrations/0013_connectiontoken_protocol.py new file mode 100644 index 000000000..f6e310e24 --- /dev/null +++ b/apps/authentication/migrations/0013_connectiontoken_protocol.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-10-27 12:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0012_auto_20220816_1629'), + ] + + operations = [ + migrations.AddField( + model_name='connectiontoken', + name='protocol', + field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S'), ('http', 'HTTP'), ('None', ' Settings')], default='ssh', max_length=16, verbose_name='Protocol'), + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index c76d6e0f4..9476d348c 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -9,6 +9,7 @@ from django.db import models from common.utils import lazyproperty from common.utils.timezone import as_current_tz from common.db.models import JMSBaseModel +from assets.const import Protocol def date_expired_default(): @@ -26,10 +27,14 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): ) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - protocol = '' account = models.CharField(max_length=128, default='', verbose_name=_("Account")) + protocol = models.CharField( + choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") + ) secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) - date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_("Date expired")) + date_expired = models.DateTimeField( + default=date_expired_default, verbose_name=_("Date expired") + ) class Meta: ordering = ('-date_expired',) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 1093da1f9..86388155b 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -17,7 +17,6 @@ __all__ = [ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_("Type display")) is_valid = serializers.BooleanField(read_only=True, label=_('Validity')) expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) @@ -29,13 +28,13 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): 'created_by', 'updated_by', 'org_id', 'org_name', ] fields_fk = [ - 'user', 'system_user', 'asset', 'application', + 'user', 'system_user', 'asset', ] read_only_fields = [ # 普通 Token 不支持指定 user 'user', 'is_valid', 'expire_time', - 'type_display', 'user_display', 'system_user_display', - 'asset_display', 'application_display', + 'user_display', 'system_user_display', + 'asset_display', ] fields = fields_small + fields_fk + read_only_fields @@ -54,28 +53,23 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): return self.request_user def construct_internal_fields_attrs(self, attrs): - user = self.get_user(attrs) - system_user = attrs.get('system_user') or '' asset = attrs.get('asset') or '' - application = attrs.get('application') or '' + asset_display = pretty_string(str(asset), max_length=128) + user = self.get_user(attrs) + user_display = pretty_string(str(user), max_length=128) secret = attrs.get('secret') or random_string(16) date_expired = attrs.get('date_expired') or ConnectionToken.get_default_date_expired() - - if isinstance(asset, Asset): - tp = ConnectionToken.Type.asset - org_id = asset.org_id - else: - raise serializers.ValidationError(_('Asset or application required')) + org_id = asset.org_id + if not isinstance(asset, Asset): + error = '' + raise serializers.ValidationError(error) return { - 'type': tp, 'user': user, 'secret': secret, + 'user_display': user_display, + 'asset_display': asset_display, 'date_expired': date_expired, - 'user_display': pretty_string(str(user), max_length=128), - 'system_user_display': pretty_string(str(system_user), max_length=128), - 'asset_display': pretty_string(str(asset), max_length=128), - 'application_display': pretty_string(str(application), max_length=128), 'org_id': org_id, } @@ -155,7 +149,6 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) - account = serializers.CharField(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) domain = ConnectionTokenDomainSerializer(read_only=True) cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) @@ -165,6 +158,6 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'secret', 'type', 'user', 'asset', 'account', + 'id', 'secret', 'type', 'user', 'asset', 'account', 'protocol', 'cmd_filter_rules', 'domain', 'gateway', 'actions', 'expired_at', ] diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 34b839fab..63bfcc723 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -51,7 +51,6 @@ class PermAccountUtil(AssetPermissionUtil): user, asset, with_actions=True, with_perms=True ) perm = perms.first() - # Todo: 后面可能需要加上 protocol 进行过滤, 因为同名的账号协议是不一样可能会存在多个 account = accounts.filter(username=account_username).first() actions = account.actions if account else [] expire_at = perm.date_expired if perm else time.time() From a9eb4fa7dd78afcd80e60e56d28569d383288e5f Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 27 Oct 2022 20:20:40 +0800 Subject: [PATCH 244/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 32 ++++++--------------------- apps/terminal/models/applet/applet.py | 5 ----- apps/terminal/serializers/applet.py | 8 +++---- 4 files changed, 13 insertions(+), 36 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 59384abe0..eada29942 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:5d945fc6151d6f6354052a0a09706a52e7a2fa2f9e02254965cf26b134d78c3a -size 103377 +oid sha256:97b2aa050b5846dd3399d45286d34f282acb55dca3e2b18a79c16b9d34ec938f +size 103818 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 06af10e1f..aeda480a1 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -658,8 +658,6 @@ msgid "Script" msgstr "" #: assets/models/asset/web.py:13 -#, fuzzy -#| msgid "Auto fill" msgid "Autofill" msgstr "自动填充" @@ -1990,10 +1988,8 @@ msgid "Asset display" msgstr "资产名称" #: authentication/models.py:85 -#, fuzzy -#| msgid "Action display" msgid "Account display" -msgstr "动作名称" +msgstr "账号显示" #: authentication/models.py:89 msgid "Connection token" @@ -4698,52 +4694,38 @@ msgid "Storage is invalid" msgstr "存储无效" #: terminal/models/applet/applet.py:21 -#, fuzzy -#| msgid "Manually input" msgid "Manual" -msgstr "手动输入" +msgstr "手动" #: terminal/models/applet/applet.py:22 msgid "Git" msgstr "" #: terminal/models/applet/applet.py:23 -#, fuzzy -#| msgid "Remote app" msgid "Remote gzip" msgstr "远程应用" #: terminal/models/applet/applet.py:28 -#, fuzzy -#| msgid "Auth url" msgid "Author" -msgstr "认证地址" +msgstr "作者" #: terminal/models/applet/applet.py:32 msgid "Tags" -msgstr "" +msgstr "标签" #: terminal/models/applet/applet.py:59 terminal/models/applet/host.py:17 -#, fuzzy -#| msgid "Apply assets" msgid "Applet" -msgstr "申请资产" +msgstr "远程应用" #: terminal/models/applet/host.py:13 -#, fuzzy -#| msgid "Verify account automation" msgid "Account automation" -msgstr "账号校验自动化" +msgstr "账号自动化" #: terminal/models/applet/host.py:14 -#, fuzzy -#| msgid "Date sync" msgid "Date synced" -msgstr "同步日期" +msgstr "最后同步日期" #: terminal/models/applet/host.py:26 -#, fuzzy -#| msgid "Host" msgid "Hosting" msgstr "主机" diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 25e3a2784..0969204b7 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -17,11 +17,6 @@ class Applet(JMSBaseModel): general = 'general', _('General') web = 'web', _('Web') - class VCSType(models.TextChoices): - manual = 'manual', _('Manual') - git = 'git', _('Git') - archive = 'archive', _('Remote gzip') - name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) display_name = models.CharField(max_length=128, verbose_name=_('Display name')) version = models.CharField(max_length=16, verbose_name=_('Version')) diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 5ad2416b8..97f8fae64 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -60,13 +60,13 @@ class AppletHostSerializer(HostSerializer): } def __init__(self, *args, data=None, **kwargs): - self.set_initial_data(data) - super().__init__(*args, data=data, **kwargs) + if data: + self.set_initial_data(data) + kwargs['data'] = data + super().__init__(*args, **kwargs) @staticmethod def set_initial_data(data): - if not data: - return platform = Platform.objects.get(name='RemoteAppHost') data.update({ 'platform': platform.id, From 2b5b4ad605397b0d5e649924d636c5d7fea7dfc4 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 28 Oct 2022 15:01:17 +0800 Subject: [PATCH 245/488] =?UTF-8?q?refactor:=20ConnectionToken=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=A1=A8=E5=AD=97=E6=AE=B5=E5=90=8D=E7=A7=B0=20accoun?= =?UTF-8?q?t=20->=20account=5Fusername?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0012_auto_20220816_1629.py | 6 +-- .../authentication/models/connection_token.py | 37 +++++++------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/apps/authentication/migrations/0012_auto_20220816_1629.py b/apps/authentication/migrations/0012_auto_20220816_1629.py index 23bcccb68..8310c4fbb 100644 --- a/apps/authentication/migrations/0012_auto_20220816_1629.py +++ b/apps/authentication/migrations/0012_auto_20220816_1629.py @@ -16,9 +16,9 @@ def migrate_system_user_to_account(apps, schema_editor): count += len(connection_tokens) updated = [] for connection_token in connection_tokens: - connection_token.account = connection_token.system_user.username + connection_token.account_username = connection_token.system_user.username updated.append(connection_token) - connection_token_model.objects.bulk_update(updated, ['account']) + connection_token_model.objects.bulk_update(updated, ['account_username']) class Migration(migrations.Migration): @@ -42,7 +42,7 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='connectiontoken', - name='account', + name='account_username', field=models.CharField(default='', max_length=128, verbose_name='Account'), ), migrations.RunPython(migrate_system_user_to_account), diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 9476d348c..7bf66a2ee 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -25,12 +25,12 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): 'assets.Asset', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('Asset'), ) - user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) - asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - account = models.CharField(max_length=128, default='', verbose_name=_("Account")) protocol = models.CharField( choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) + user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) + asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) + account_username = models.CharField(max_length=128, default='', verbose_name=_("Account")) secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) date_expired = models.DateTimeField( default=date_expired_default, verbose_name=_("Date expired") @@ -43,6 +43,10 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): ('view_connectiontokensecret', _('Can view connection token secret')) ] + @property + def is_valid(self): + return not self.is_expired + @property def is_expired(self): return self.date_expired < timezone.now() @@ -55,10 +59,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): seconds = 0 return int(seconds) - @property - def is_valid(self): - return not self.is_expired - @classmethod def get_default_date_expired(cls): return date_expired_default() @@ -81,30 +81,21 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('Connection token expired at: {}').format(as_current_tz(self.date_expired)) return is_valid, error - if not self.user: + if not self.user or not self.user.is_valid: is_valid = False - error = _('User not exists') + error = _('No user or invalid user') return is_valid, error - if not self.user.is_valid: + if not self.asset or self.asset.is_active: is_valid = False - error = _('User invalid, disabled or expired') - return is_valid, error - if not self.asset: - is_valid = False - error = _('Asset not exists') - return is_valid, error - if not self.asset.is_active: - is_valid = False - error = _('Asset inactive') + error = _('No asset or inactive asset') return is_valid, error if not self.account: is_valid = False - error = _('Account not exists') + error = _('No account') return is_valid, error - actions, expire_at = PermAccountUtil().validate_permission( - self.user, self.asset, self.account - ) + account_util = PermAccountUtil() + actions, expire_at = account_util.validate_permission(self.user, self.asset, self.account) if not actions or expire_at < time.time(): is_valid = False error = _('User has no permission to access asset or permission expired') From bcd1d5585b154e6092128affa4ceb04dec9117e8 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 28 Oct 2022 15:58:05 +0800 Subject: [PATCH 246/488] =?UTF-8?q?refactor:=20ConnectionToken=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20Model=20=E5=92=8C=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/models/connection_token.py | 15 ++++- .../serializers/connection_token.py | 62 +++++++++++-------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 7bf66a2ee..c8fae3790 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -75,7 +75,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): # actions 和 expired_at 在 check_valid() 中赋值 actions = expire_at = None - def check_valid(self): + def check_permission(self): from perms.utils.account import PermAccountUtil if self.is_expired: is_valid = False @@ -89,13 +89,15 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('No asset or inactive asset') return is_valid, error - if not self.account: + if not self.account_username: is_valid = False error = _('No account') return is_valid, error account_util = PermAccountUtil() - actions, expire_at = account_util.validate_permission(self.user, self.asset, self.account) + actions, expire_at = account_util.validate_permission( + self.user, self.asset, self.account_username + ) if not actions or expire_at < time.time(): is_valid = False error = _('User has no permission to access asset or permission expired') @@ -104,6 +106,13 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): self.expire_at = expire_at return True, '' + @lazyproperty + def account(self): + if not self.asset: + return None + account = self.asset.accounts.filter(username=self.account_username).first() + return account + @lazyproperty def domain(self): domain = self.asset.domain if self.asset else None diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 86388155b..8f36ddc2b 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -5,7 +5,7 @@ from orgs.mixins.serializers import OrgResourceModelSerializerMixin from authentication.models import ConnectionToken from common.utils import pretty_string from common.utils.random import random_string -from assets.models import Asset, Gateway, Domain, CommandFilterRule +from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account from users.models import User from perms.serializers.permission import ActionsField @@ -24,34 +24,33 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields_mini = ['id'] fields_small = fields_mini + [ - 'secret', 'date_expired', 'date_created', 'date_updated', + 'secret', 'account_username', 'date_expired', + 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', ] fields_fk = [ - 'user', 'system_user', 'asset', + 'user', 'asset', ] read_only_fields = [ # 普通 Token 不支持指定 user 'user', 'is_valid', 'expire_time', - 'user_display', 'system_user_display', - 'asset_display', + 'user_display', 'asset_display', ] fields = fields_small + fields_fk + read_only_fields + def get_request_user(self): + request = self.context.get('request') + user = request.user if request else None + return user + + def get_user(self, attrs): + return self.get_request_user() + def validate(self, attrs): fields_attrs = self.construct_internal_fields_attrs(attrs) attrs.update(fields_attrs) return attrs - @property - def request_user(self): - request = self.context.get('request') - if request: - return request.user - - def get_user(self, attrs): - return self.request_user - def construct_internal_fields_attrs(self, attrs): asset = attrs.get('asset') or '' asset_display = pretty_string(str(asset), max_length=128) @@ -63,8 +62,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): if not isinstance(asset, Asset): error = '' raise serializers.ValidationError(error) - - return { + attrs = { 'user': user, 'secret': secret, 'user_display': user_display, @@ -72,6 +70,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): 'date_expired': date_expired, 'org_id': org_id, } + return attrs class ConnectionTokenDisplaySerializer(ConnectionTokenSerializer): @@ -95,7 +94,7 @@ class SuperConnectionTokenSerializer(ConnectionTokenSerializer): ] def get_user(self, attrs): - return attrs.get('user') or self.request_user + return attrs.get('user') or self.get_request_user() # @@ -104,31 +103,37 @@ class SuperConnectionTokenSerializer(ConnectionTokenSerializer): class ConnectionTokenUserSerializer(serializers.ModelSerializer): + """ User """ class Meta: model = User fields = ['id', 'name', 'username', 'email'] class ConnectionTokenAssetSerializer(serializers.ModelSerializer): - + """ Asset """ class Meta: model = Asset fields = ['id', 'name', 'ip', 'protocols', 'org_id'] +class ConnectionTokenAccountSerializer(serializers.ModelSerializer): + """ Account """ + class Meta: + model = Account + fields = [ + 'id', 'name', 'username', 'secret_type', 'secret', 'version' + ] + + class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): + """ Gateway """ class Meta: model = Gateway fields = ['id', 'ip', 'port', 'username', 'password', 'private_key'] -class ConnectionTokenRemoteAppSerializer(serializers.Serializer): - program = serializers.CharField(allow_null=True, allow_blank=True) - working_directory = serializers.CharField(allow_null=True, allow_blank=True) - parameters = serializers.CharField(allow_null=True, allow_blank=True) - - class ConnectionTokenDomainSerializer(serializers.ModelSerializer): + """ Domain """ gateways = ConnectionTokenGatewaySerializer(many=True, read_only=True) class Meta: @@ -137,6 +142,7 @@ class ConnectionTokenDomainSerializer(serializers.ModelSerializer): class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): + """ Command filter rule """ class Meta: model = CommandFilterRule fields = [ @@ -148,7 +154,7 @@ class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) - remote_app = ConnectionTokenRemoteAppSerializer(read_only=True) + account = ConnectionTokenAccountSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) domain = ConnectionTokenDomainSerializer(read_only=True) cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) @@ -158,6 +164,8 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'secret', 'type', 'user', 'asset', 'account', 'protocol', - 'cmd_filter_rules', 'domain', 'gateway', 'actions', 'expired_at', + 'id', 'secret', + 'user', 'asset', 'account_username', 'account', 'protocol', + 'domain', 'gateway', 'cmd_filter_rules', + 'actions', 'expired_at', ] From 084dcc7b4435dba7ddafb46c4b65f21dadb78fcb Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 28 Oct 2022 16:25:16 +0800 Subject: [PATCH 247/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20inventory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 35344ad6a..919b0948c 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -9,16 +9,17 @@ __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, manager, assets=None, account_policy='smart', account_prefer='root,administrator'): + def __init__(self, manager, assets=None, account_policy='smart', + account_prefer='root,administrator', host_callback=None): """ :param assets: :param account_prefer: account username name if not set use account_policy :param account_policy: smart, privileged_must, privileged_first """ - self.manager = manager self.assets = self.clean_assets(assets) self.account_prefer = account_prefer self.account_policy = account_policy + self.host_callback = host_callback @staticmethod def clean_assets(assets): @@ -106,7 +107,8 @@ class JMSInventory: 'jms_asset': { 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, - 'protocol': asset.protocol, 'port': asset.port,'database': '', + 'protocol': asset.protocol, 'port': asset.port, + 'category_property': asset.category_property, 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, 'jms_account': { @@ -118,9 +120,6 @@ class JMSInventory: ansible_connection = ansible_config.get('ansible_connection', 'ssh') host.update(ansible_config) - if platform.category == 'database': - host['jms_asset']['database'] = asset.database.db_name - gateway = None if asset.domain: gateway = asset.domain.select_gateway() @@ -176,8 +175,8 @@ class JMSInventory: if not automation.ansible_enabled: host['error'] = _('Ansible disabled') - if self.manager.host_callback is not None: - host = self.manager.host_callback( + if self.host_callback is not None: + host = self.host_callback( host, asset=asset, account=account, platform=platform, automation=automation, path_dir=path_dir From 85574b43eeb62682cb20892d21d6c7ab93a64f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Wed, 26 Oct 2022 19:25:34 +0800 Subject: [PATCH 248/488] perf: update playbook.yml --- .../deploy_applet_host/playbook.yml | 176 +++++++++++++----- 1 file changed, 130 insertions(+), 46 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 17016328d..ffb748b63 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -1,56 +1,140 @@ --- + - hosts: windows vars: - DownloadHost: https://demo.jumpserver.org/download - RDS_Licensing: enabled - RDS_LicenseServer: 127.0.0.1 - RDS_LicensingMode: 4 - - RDS_fSingleSessionPerUser: 0 + - RDS_fSingleSessionPerUser: 1 - RDS_MaxDisconnectionTime: 60000 - RDS_RemoteAppLogoffTimeLimit: 0 + tasks: - - name: Install RDS-Licensing (RDS) - ansible.windows.win_feature: - name: RDS-Licensing - state: present - include_management_tools: yes - when: RDS_Licensing == "enabled" - - name: Install RDS-RD-Server (RDS) - ansible.windows.win_feature: - name: RDS-RD-Server - state: present - include_management_tools: yes - register: win_feature - - name: Reboot if installing RDS feature requires it - ansible.windows.win_reboot: - when: win_feature.reboot_required - - name: Set RDS LicenseServer (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: LicenseServers - data: "{{ RDS_LicenseServer }}" - type: string - - name: Set RDS LicensingMode (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: LicensingMode - data: "{{ RDS_LicensingMode }}" - type: dword - - name: Set RDS fSingleSessionPerUser (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: fSingleSessionPerUser - data: "{{ RDS_fSingleSessionPerUser }}" - type: dword - - name: Set RDS MaxDisconnectionTime (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: MaxDisconnectionTime - data: "{{ RDS_MaxDisconnectionTime }}" - type: dword - when: RDS_MaxDisconnectionTime >= 60000 - - name: Set RDS RemoteAppLogoffTimeLimit (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: RemoteAppLogoffTimeLimit - data: "{{ RDS_RemoteAppLogoffTime }}" + - name: Install RDS-Licensing (RDS) + ansible.windows.win_feature: + name: RDS-Licensing + state: present + include_management_tools: yes + when: RDS_Licensing == "enabled" + + - name: Install RDS-RD-Server (RDS) + ansible.windows.win_feature: + name: RDS-RD-Server + state: present + include_management_tools: yes + register: rds_install + + - name: Reboot if installing RDS feature requires it + ansible.windows.win_reboot: + post_reboot_delay: 10 + test_command: whoami + when: rds_install.reboot_required + + - name: Set RDS LicenseServer (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: LicenseServers + data: "{{ RDS_LicenseServer }}" + type: string + + - name: Set RDS LicensingMode (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: LicensingMode + data: "{{ RDS_LicensingMode }}" + type: dword + + - name: Set RDS fSingleSessionPerUser (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: fSingleSessionPerUser + data: "{{ RDS_fSingleSessionPerUser }}" + type: dword + + - name: Set RDS MaxDisconnectionTime (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: MaxDisconnectionTime + data: "{{ RDS_MaxDisconnectionTime }}" + type: dword + when: RDS_MaxDisconnectionTime >= 60000 + + - name: Set RDS RemoteAppLogoffTimeLimit (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: RemoteAppLogoffTimeLimit + data: "{{ RDS_RemoteAppLogoffTimeLimit }}" + type: dword + + - name: Download Jmservisor (jumpserver) + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/Jmservisor.msi" + dest: "{{ ansible_env.TEMP }}\\Jmservisor.msi" + + - name: Install the Jmservisor (jumpserver) + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\Jmservisor.msi" + state: present + + - name: Download python-3.10.8 + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" + dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" + + - name: Install the python-3.10.8 + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" + product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}' + arguments: + - /quiet + - InstallAllUsers=1 + - PrependPath=1 + - Include_test=0 + - Include_launcher=0 + state: present + register: win_install_python + + - name: Reboot if installing Python package requires it + ansible.windows.win_reboot: + when: win_install_python.reboot_required + + - name: Download pip packages + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip" + dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" + + - name: Unzip pip_packages + community.windows.win_unzip: + src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" + dest: "{{ ansible_env.TEMP }}" + + - name: Install python requirements offline + ansible.windows.win_shell: pip install -r "{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt" --no-index --find-links="{{ ansible_env.TEMP }}\pip_packages_v0.0.1" + + - name: Download chromedriver (chrome) + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/chromedriver_win32.106.zip" + dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip" + + - name: Unzip chromedriver (chrome) + community.windows.win_unzip: + src: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip" + dest: C:\Program Files\JumpServer\drivers + + - name: Set chromedriver on the global system path (chrome) + ansible.windows.win_path: + elements: + - 'C:\Program Files\JumpServer\drivers' + + - name: Download chrome msi package (chrome) + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi" + dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi" + + - name: Install chrome (chrome) + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi" + state: present + arguments: + - /quiet From 8f9eb64c8df124689bee4585428847303eea7708 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 28 Oct 2022 17:12:18 +0800 Subject: [PATCH 249/488] perf: update playbook.yml --- .../deploy_applet_host/playbook.yml | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index ffb748b63..443482fc1 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -25,11 +25,39 @@ include_management_tools: yes register: rds_install - - name: Reboot if installing RDS feature requires it + - name: Download Jmservisor (jumpserver) + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/Jmservisor.msi" + dest: "{{ ansible_env.TEMP }}\\Jmservisor.msi" + + - name: Install the Jmservisor (jumpserver) + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\Jmservisor.msi" + state: present + + - name: Download python-3.10.8 + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" + dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" + + - name: Install the python-3.10.8 + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" + product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}' + arguments: + - /quiet + - InstallAllUsers=1 + - PrependPath=1 + - Include_test=0 + - Include_launcher=0 + state: present + register: win_install_python + + - name: Reboot if installing requires it ansible.windows.win_reboot: post_reboot_delay: 10 test_command: whoami - when: rds_install.reboot_required + when: rds_install.reboot_required or win_install_python.reboot_required - name: Set RDS LicenseServer (regedit) ansible.windows.win_regedit: @@ -67,38 +95,6 @@ data: "{{ RDS_RemoteAppLogoffTimeLimit }}" type: dword - - name: Download Jmservisor (jumpserver) - ansible.windows.win_get_url: - url: "{{ DownloadHost }}/Jmservisor.msi" - dest: "{{ ansible_env.TEMP }}\\Jmservisor.msi" - - - name: Install the Jmservisor (jumpserver) - ansible.windows.win_package: - path: "{{ ansible_env.TEMP }}\\Jmservisor.msi" - state: present - - - name: Download python-3.10.8 - ansible.windows.win_get_url: - url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" - dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" - - - name: Install the python-3.10.8 - ansible.windows.win_package: - path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" - product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}' - arguments: - - /quiet - - InstallAllUsers=1 - - PrependPath=1 - - Include_test=0 - - Include_launcher=0 - state: present - register: win_install_python - - - name: Reboot if installing Python package requires it - ansible.windows.win_reboot: - when: win_install_python.reboot_required - - name: Download pip packages ansible.windows.win_get_url: url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip" From 12b74093e29f312acea696c210974be3fb6ea9d8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 28 Oct 2022 18:19:44 +0800 Subject: [PATCH 250/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applet=20h?= =?UTF-8?q?ost=20deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 10 ++--- apps/ops/api/celery.py | 2 +- apps/terminal/api/applet/applet.py | 14 +++--- apps/terminal/api/applet/host.py | 24 ++++++++--- .../automations/deploy_applet_host/manager.py | 43 +++++++++++++++++++ .../deploy_applet_host/playbook.yml | 2 +- .../migrations/0055_auto_20221028_1544.py | 42 ++++++++++++++++++ apps/terminal/models/applet/applet.py | 5 ++- apps/terminal/models/applet/host.py | 22 ++++------ apps/terminal/serializers/applet.py | 15 +------ apps/terminal/tasks.py | 8 +++- apps/terminal/urls/api_urls.py | 7 +-- 12 files changed, 140 insertions(+), 54 deletions(-) create mode 100644 apps/terminal/migrations/0055_auto_20221028_1544.py diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 919b0948c..aee85face 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -9,8 +9,9 @@ __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, manager, assets=None, account_policy='smart', - account_prefer='root,administrator', host_callback=None): + def __init__(self, assets, account_policy='smart', + account_prefer='root,administrator', + host_callback=None): """ :param assets: :param account_prefer: account username name if not set use account_policy @@ -79,10 +80,7 @@ class JMSInventory: ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None host['ansible_host'] = asset.address - if asset.port == 0: - host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 - else: - host['ansible_port'] = asset.port + host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 su_from = account.su_from if platform.su_enabled and su_from: diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 85a5c00d2..61d13db17 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -99,7 +99,7 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): - queryset = CeleryTask.objects.filter(name__in=['ops.tasks.hello', 'ops.tasks.hello_error', 'ops.tasks.hello_random']) + queryset = CeleryTask.objects.all() serializer_class = CeleryTaskSerializer http_method_names = ('get', 'head', 'options',) diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 9ded63dc1..bce0738d6 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -1,7 +1,7 @@ -import os.path import shutil import zipfile import yaml +import os.path from django.core.files.storage import default_storage from rest_framework import viewsets @@ -9,12 +9,16 @@ from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.serializers import ValidationError -from terminal import serializers, models +from terminal import serializers +from terminal.models import AppletPublication, Applet from terminal.serializers import AppletUploadSerializer +__all__ = ['AppletViewSet', 'AppletPublicationViewSet'] + + class AppletViewSet(viewsets.ModelViewSet): - queryset = models.Applet.objects.all() + queryset = Applet.objects.all() serializer_class = serializers.AppletSerializer rbac_perms = { 'upload': 'terminal.add_applet', @@ -67,7 +71,7 @@ class AppletViewSet(viewsets.ModelViewSet): name = manifest['name'] update = request.query_params.get('update') - instance = models.Applet.objects.filter(name=name).first() + instance = Applet.objects.filter(name=name).first() if instance and not update: return Response({'error': 'Applet already exists: {}'.format(name)}, status=400) @@ -82,5 +86,5 @@ class AppletViewSet(viewsets.ModelViewSet): class AppletPublicationViewSet(viewsets.ModelViewSet): - queryset = models.AppletPublication.objects.all() + queryset = AppletPublication.objects.all() serializer_class = serializers.AppletPublicationSerializer diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index d4166ea98..ea8831b8f 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -1,23 +1,35 @@ from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response from orgs.utils import tmp_to_builtin_org -from terminal import serializers, models +from terminal import serializers +from terminal.models import AppletHost, Applet +from terminal.tasks import run_applet_host_deployment -__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] +__all__ = ['AppletHostViewSet'] class AppletHostViewSet(viewsets.ModelViewSet): serializer_class = serializers.AppletHostSerializer def get_queryset(self): - return models.AppletHost.objects.all() + return AppletHost.objects.all() def dispatch(self, request, *args, **kwargs): with tmp_to_builtin_org(system=1): return super().dispatch(request, *args, **kwargs) + @action(methods=['post'], detail=True) + def deploy(self, request): + from terminal.automations.deploy_applet_host.manager import DeployAppletHostManager + manager = DeployAppletHostManager(self) + manager.run() -class AppletHostDeploymentViewSet(viewsets.ModelViewSet): - queryset = models.AppletHostDeployment.objects.all() - serializer_class = serializers.AppletHostDeploymentSerializer + @action(methods=['get'], detail=True, url_path='') + def not_published_applets(self, request, *args, **kwargs): + instance = self.get_object() + applets = Applet.objects.exclude(id__in=instance.applets.all()) + serializer = serializers.AppletSerializer(applets, many=True) + return Response(serializer.data) diff --git a/apps/terminal/automations/deploy_applet_host/manager.py b/apps/terminal/automations/deploy_applet_host/manager.py index e69de29bb..22a8510af 100644 --- a/apps/terminal/automations/deploy_applet_host/manager.py +++ b/apps/terminal/automations/deploy_applet_host/manager.py @@ -0,0 +1,43 @@ +import os +import datetime +import shutil +from django.conf import settings + +from ops.ansible import PlaybookRunner, JMSInventory + +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class DeployAppletHostManager: + def __init__(self, applet_host): + self.applet_host = applet_host + self.run_dir = self.get_run_dir() + + @staticmethod + def get_run_dir(): + base = os.path.join(settings.ANSIBLE_DIR, 'applet_host_deploy') + now = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + return os.path.join(base, now) + + def generate_playbook(self): + playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml') + playbook_dir = os.path.join(self.run_dir, 'playbook') + playbook_dst = os.path.join(playbook_dir, 'main.yml') + os.makedirs(playbook_dir, exist_ok=True) + shutil.copy(playbook_src, playbook_dst) + return playbook_dst + + def generate_inventory(self): + inventory = JMSInventory([self.applet_host], account_policy='privileged_only') + inventory_dir = os.path.join(self.run_dir, 'inventory') + inventory_path = os.path.join(inventory_dir, 'hosts.yml') + inventory.write_to_file(inventory_path) + return inventory_path + + def run(self, **kwargs): + inventory = self.generate_inventory() + playbook = self.generate_playbook() + runner = PlaybookRunner( + inventory=inventory, playbook=playbook, project_dir=self.run_dir + ) + return runner.run(**kwargs) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 443482fc1..348f50fdb 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -1,6 +1,6 @@ --- -- hosts: windows +- hosts: all vars: - DownloadHost: https://demo.jumpserver.org/download - RDS_Licensing: enabled diff --git a/apps/terminal/migrations/0055_auto_20221028_1544.py b/apps/terminal/migrations/0055_auto_20221028_1544.py new file mode 100644 index 000000000..3aeeff3f6 --- /dev/null +++ b/apps/terminal/migrations/0055_auto_20221028_1544.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.14 on 2022-10-28 07:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0054_auto_20221027_1125'), + ] + + operations = [ + migrations.AddField( + model_name='applet', + name='hosts', + field=models.ManyToManyField(through='terminal.AppletPublication', to='terminal.AppletHost', verbose_name='Hosts'), + ), + migrations.AddField( + model_name='applethost', + name='date_inited', + field=models.DateTimeField(blank=True, null=True, verbose_name='Date initialized'), + ), + migrations.AddField( + model_name='applethost', + name='initialized', + field=models.BooleanField(default=False, verbose_name='Initialized'), + ), + migrations.AlterField( + model_name='appletpublication', + name='applet', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='terminal.applet', verbose_name='Applet'), + ), + migrations.AlterField( + model_name='appletpublication', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='terminal.applethost', verbose_name='Host'), + ), + migrations.DeleteModel( + name='AppletHostDeployment', + ), + ] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 0969204b7..9dde129e7 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -26,6 +26,7 @@ class Applet(JMSBaseModel): protocols = models.JSONField(default=list, verbose_name=_('Protocol')) tags = models.JSONField(default=list, verbose_name=_('Tags')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + hosts = models.ManyToManyField(through_fields=('applet', 'host'), through='AppletPublication', to='AppletHost', verbose_name=_('Hosts')) def __str__(self): return self.name @@ -51,8 +52,8 @@ class Applet(JMSBaseModel): class AppletPublication(JMSBaseModel): - applet = models.ForeignKey('Applet', on_delete=models.PROTECT, verbose_name=_('Applet')) - host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, verbose_name=_('Host')) + applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet')) + host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host')) status = models.CharField(max_length=16, verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 2fee8d15d..7733e7b0f 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -1,15 +1,17 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from common.db.models import JMSBaseModel from assets.models import Host +from ops.ansible import PlaybookRunner, JMSInventory -__all__ = ['AppletHost', 'AppletHostDeployment'] +__all__ = ['AppletHost'] class AppletHost(Host): account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) + initialized = models.BooleanField(default=False, verbose_name=_('Initialized')) + date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date initialized')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) status = models.CharField(max_length=16, verbose_name=_('Status')) applets = models.ManyToManyField( @@ -17,17 +19,11 @@ class AppletHost(Host): through='AppletPublication', through_fields=('host', 'applet'), ) + def deploy(self): + inventory = JMSInventory([self]) + playbook = PlaybookRunner(inventory, 'applets.yml') + playbook.run() + def __str__(self): return self.name - -class AppletHostDeployment(JMSBaseModel): - host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting')) - status = models.CharField(max_length=16, verbose_name=_('Status')) - comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - - def __str__(self): - return self.host - - def start(self): - pass diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 97f8fae64..802b0a304 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -5,12 +5,12 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField from common.validators import ProjectUniqueValidator from assets.models import Platform from assets.serializers import HostSerializer -from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment +from ..models import Applet, AppletPublication, AppletHost __all__ = [ 'AppletSerializer', 'AppletPublicationSerializer', - 'AppletHostSerializer', 'AppletHostDeploymentSerializer', + 'AppletHostSerializer', 'AppletUploadSerializer' ] @@ -85,14 +85,3 @@ class AppletHostSerializer(HostSerializer): validators.append(uniq_validator) return validators - -class AppletHostDeploymentSerializer(serializers.ModelSerializer): - host = ObjectRelatedField(queryset=AppletHost.objects.all()) - - class Meta: - model = AppletHostDeployment - fields_mini = ['id', 'host'] - read_only_fields = ['date_created', 'date_updated'] - fields = fields_mini + [ - 'status', 'comment', - ] + read_only_fields diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index 59cf00fa4..e05d673e5 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -14,7 +14,7 @@ from common.utils import get_log_keep_day from ops.celery.decorator import ( register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic ) -from .models import Status, Session, Command, Task +from .models import Status, Session, Command, Task, AppletHost from .backends import server_replay_storage from .utils import find_session_replay_local @@ -99,3 +99,9 @@ def upload_session_replay_to_external_storage(session_id): except: pass return + + +@shared_task +def run_applet_host_deployment(did): + host = AppletHost.objects.get(id=did) + host.deploy() diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 8bed4f604..1d369cc52 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -26,8 +26,7 @@ router.register(r'endpoints', api.EndpointViewSet, 'endpoint') router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') router.register(r'applets', api.AppletViewSet, 'applet') router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') -router.register(r'applet-publication', api.AppletPublicationViewSet, 'applet-publication') -router.register(r'applet-host-deployment', api.AppletHostDeploymentViewSet, 'applet-host-deployment') +router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication') urlpatterns = [ @@ -46,10 +45,6 @@ urlpatterns = [ path('command-storages//test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'), # components path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'), - # v2: get session's replay - # path('v2/sessions//replay/', - # api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}), - # name='session-replay-v2'), ] old_version_urlpatterns = [ From 121ba1df072baa4790932b0b79760b0b4e0ad3d9 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 28 Oct 2022 18:28:41 +0800 Subject: [PATCH 251/488] perf: automations push ping verify --- apps/assets/automations/base/manager.py | 76 +++++++++++++++++-- .../database/postgresql/main.yml | 6 +- .../change_secret/host/aix/main.yml | 58 -------------- .../change_secret/host/aix/manifest.yml | 6 -- .../automations/change_secret/manager.py | 18 +---- apps/assets/automations/endpoint.py | 4 + .../gather_accounts/database/mysql/main.yml | 4 +- .../database/postgresql/main.yml | 2 +- .../gather_accounts/host/posix/main.yml | 6 +- .../gather_accounts/host/windows/main.yml | 26 +++---- .../automations/gather_accounts/manager.py | 4 +- .../gather_facts/database/postgresql/main.yml | 2 +- .../automations/gather_facts/manager.py | 2 +- .../automations/ping/database/mysql/main.yml | 7 -- .../ping/database/postgresql/main.yml | 12 +-- .../automations/ping/host/windows/main.yml | 2 +- apps/assets/automations/ping/manager.py | 13 +++- .../README.md => push_account/__init__.py} | 0 .../push_account/database/mysql/main.yml | 15 ++++ .../push_account/database/mysql/manifest.yml | 6 ++ .../push_account/database/postgresql/main.yml | 16 ++++ .../database/postgresql/manifest.yml | 6 ++ .../push_account/host/posix/main.yml | 19 +++++ .../push_account/host/posix/manifest.yml | 7 ++ .../push_account/host/windows/main.yml | 13 ++++ .../push_account/host/windows/manifest.yml | 7 ++ .../automations/push_account/manager.py | 17 +++++ .../automations/verify_account/__init__.py | 0 .../verify_account/database/mysql/main.yml | 13 ++++ .../database/mysql/manifest.yml | 6 ++ .../database/postgresql/main.yml | 13 ++++ .../database/postgresql/manifest.yml | 6 ++ .../verify_account/host/posix/main.yml | 11 +++ .../verify_account/host/posix/manifest.yml | 7 ++ .../verify_account/host/windows/main.yml | 8 ++ .../verify_account/host/windows/manifest.yml | 7 ++ .../automations/verify_account/manager.py | 25 ++++++ apps/assets/models/automations/__init__.py | 2 +- .../models/automations/change_secret.py | 1 - .../assets/models/automations/push_account.py | 17 +++-- .../{verify_secret.py => verify_account.py} | 11 ++- apps/ops/ansible/callback.py | 2 +- apps/ops/ansible/inventory.py | 13 ++-- 43 files changed, 339 insertions(+), 157 deletions(-) delete mode 100644 apps/assets/automations/change_secret/host/aix/main.yml delete mode 100644 apps/assets/automations/change_secret/host/aix/manifest.yml rename apps/assets/automations/{gather_facts/README.md => push_account/__init__.py} (100%) create mode 100644 apps/assets/automations/push_account/database/mysql/main.yml create mode 100644 apps/assets/automations/push_account/database/mysql/manifest.yml create mode 100644 apps/assets/automations/push_account/database/postgresql/main.yml create mode 100644 apps/assets/automations/push_account/database/postgresql/manifest.yml create mode 100644 apps/assets/automations/push_account/host/posix/main.yml create mode 100644 apps/assets/automations/push_account/host/posix/manifest.yml create mode 100644 apps/assets/automations/push_account/host/windows/main.yml create mode 100644 apps/assets/automations/push_account/host/windows/manifest.yml create mode 100644 apps/assets/automations/push_account/manager.py create mode 100644 apps/assets/automations/verify_account/__init__.py create mode 100644 apps/assets/automations/verify_account/database/mysql/main.yml create mode 100644 apps/assets/automations/verify_account/database/mysql/manifest.yml create mode 100644 apps/assets/automations/verify_account/database/postgresql/main.yml create mode 100644 apps/assets/automations/verify_account/database/postgresql/manifest.yml create mode 100644 apps/assets/automations/verify_account/host/posix/main.yml create mode 100644 apps/assets/automations/verify_account/host/posix/manifest.yml create mode 100644 apps/assets/automations/verify_account/host/windows/main.yml create mode 100644 apps/assets/automations/verify_account/host/windows/manifest.yml create mode 100644 apps/assets/automations/verify_account/manager.py rename apps/assets/models/automations/{verify_secret.py => verify_account.py} (56%) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index e2faecd64..2339867c1 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -1,19 +1,68 @@ import os -import shutil import yaml +import shutil +from hashlib import md5 +from copy import deepcopy +from socket import gethostname from collections import defaultdict from django.conf import settings from django.utils import timezone +from django.db.models import Model from django.utils.translation import gettext as _ from common.utils import get_logger +from common.utils import ssh_pubkey_gen, ssh_key_string_to_obj +from assets.const import SecretType from assets.automations.methods import platform_automation_methods from ops.ansible import JMSInventory, PlaybookRunner, DefaultCallback logger = get_logger(__name__) +class PushOrVerifyHostCallbackMixin: + execution: Model() + host_account_mapper: dict + ignore_account: bool + generate_public_key: callable + generate_private_key_path: callable + + def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs): + host = super().host_callback(host, asset=asset, account=account, automation=automation, **kwargs) + if host.get('error'): + return host + + accounts = asset.accounts.all() + if self.ignore_account and account: + accounts = accounts.exclude(id=account.id) + + if '*' not in self.execution.snapshot['accounts']: + accounts = accounts.filter(username__in=self.execution.snapshot['accounts']) + + inventory_hosts = [] + for account in accounts: + h = deepcopy(host) + h['name'] += '_' + account.username + self.host_account_mapper[h['name']] = account + secret = account.secret + + private_key_path = None + if account.secret_type == SecretType.ssh_key: + private_key_path = self.generate_private_key_path(secret, path_dir) + secret = self.generate_public_key(secret) + + h['secret_type'] = account.secret_type + h['account'] = { + 'name': account.name, + 'username': account.username, + 'secret_type': account.secret_type, + 'secret': secret, + 'private_key_path': private_key_path + } + inventory_hosts.append(h) + return inventory_hosts + + class PlaybookCallback(DefaultCallback): def playbook_on_stats(self, event_data, **kwargs): super().playbook_on_stats(event_data, **kwargs) @@ -66,20 +115,33 @@ class BasePlaybookManager: method_attr = '{}_method'.format(self.__class__.method_type()) method_enabled = automation and \ - getattr(automation, enabled_attr) and \ - getattr(automation, method_attr) and \ - getattr(automation, method_attr) in self.method_id_meta_mapper + getattr(automation, enabled_attr) and \ + getattr(automation, method_attr) and \ + getattr(automation, method_attr) in self.method_id_meta_mapper if not method_enabled: host['error'] = _('{} disabled'.format(self.__class__.method_type())) return host return host + @staticmethod + def generate_public_key(private_key): + return ssh_pubkey_gen(private_key=private_key, hostname=gethostname()) + + @staticmethod + def generate_private_key_path(secret, path_dir): + key_name = '.' + md5(secret.encode('utf-8')).hexdigest() + key_path = os.path.join(path_dir, key_name) + if not os.path.exists(key_path): + ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path) + os.chmod(key_path, 0o400) + return key_path + def generate_inventory(self, platformed_assets, inventory_path): inventory = JMSInventory( - manager=self, assets=platformed_assets, account_policy=self.ansible_account_policy, + host_callback=self.host_callback, ) inventory.write_to_file(inventory_path) @@ -105,7 +167,7 @@ class BasePlaybookManager: def get_runners(self): runners = [] for platform, assets in self.get_assets_group_by_platform().items(): - assets_bulked = [assets[i:i+self.bulk_size] for i in range(0, len(assets), self.bulk_size)] + assets_bulked = [assets[i:i + self.bulk_size] for i in range(0, len(assets), self.bulk_size)] for i, _assets in enumerate(assets_bulked, start=1): sub_dir = '{}_{}'.format(platform.name, i) @@ -148,7 +210,7 @@ class BasePlaybookManager: print(" inventory: {}".format(runner.inventory)) print(" playbook: {}".format(runner.playbook)) - def run(self, *args, **kwargs): + def run(self, *args, **kwargs): runners = self.get_runners() if len(runners) > 1: print("### 分批次执行开始任务, 总共 {}\n".format(len(runners))) diff --git a/apps/assets/automations/change_secret/database/postgresql/main.yml b/apps/assets/automations/change_secret/database/postgresql/main.yml index 40c326704..4ef9d65ab 100644 --- a/apps/assets/automations/change_secret/database/postgresql/main.yml +++ b/apps/assets/automations/change_secret/database/postgresql/main.yml @@ -10,7 +10,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.database }}" + login_db: "{{ jms_asset.category_property.db_name }}" register: db_info - name: Display PostgreSQL version @@ -24,7 +24,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - db: "{{ jms_asset.database }}" + db: "{{ jms_asset.category_property.db_name }}" name: "{{ account.username }}" password: "{{ account.secret }}" when: db_info is succeeded @@ -36,7 +36,7 @@ login_password: "{{ account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - db: "{{ jms_asset.database }}" + db: "{{ jms_asset.category_property.db_name }}" when: - db_info is succeeded - change_info is succeeded diff --git a/apps/assets/automations/change_secret/host/aix/main.yml b/apps/assets/automations/change_secret/host/aix/main.yml deleted file mode 100644 index 1a4e6a6a4..000000000 --- a/apps/assets/automations/change_secret/host/aix/main.yml +++ /dev/null @@ -1,58 +0,0 @@ -- hosts: demo - gather_facts: no - tasks: - - name: Test privileged account - ansible.builtin.ping: - # - # - name: print variables - # debug: - # msg: "Username: {{ account.username }}, Secret: {{ account.secret }}, Secret type: {{ secret_type }}" - - - name: Change password - ansible.builtin.user: - name: "{{ account.username }}" - password: "{{ account.secret | password_hash('sha512') }}" - update_password: always - when: secret_type == "password" - - - name: create user If it already exists, no operation will be performed - ansible.builtin.user: - name: "{{ account.username }}" - when: secret_type == "ssh_key" - - - name: remove jumpserver ssh key - ansible.builtin.lineinfile: - dest: "{{ kwargs.dest }}" - regexp: "{{ kwargs.regexp }}" - state: absent - when: - - secret_type == "ssh_key" - - kwargs.strategy == "set_jms" - - - name: Change SSH key - ansible.builtin.authorized_key: - user: "{{ account.username }}" - key: "{{ account.secret }}" - exclusive: "{{ kwargs.exclusive }}" - when: secret_type == "ssh_key" - - - name: Refresh connection - ansible.builtin.meta: reset_connection - - - name: Verify password - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_password: "{{ account.secret }}" - ansible_become: no - when: secret_type == "password" - - - name: Verify SSH key - ansible.builtin.ping: - become: no - vars: - ansible_user: "{{ account.username }}" - ansible_ssh_private_key_file: "{{ account.private_key_path }}" - ansible_become: no - when: secret_type == "ssh_key" diff --git a/apps/assets/automations/change_secret/host/aix/manifest.yml b/apps/assets/automations/change_secret/host/aix/manifest.yml deleted file mode 100644 index 94d93f3af..000000000 --- a/apps/assets/automations/change_secret/host/aix/manifest.yml +++ /dev/null @@ -1,6 +0,0 @@ -id: change_secret_aix -name: Change password for AIX -category: host -type: - - aix -method: change_secret diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 4ac49676b..b8868acd1 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -1,14 +1,11 @@ -import os import random import string -from hashlib import md5 from copy import deepcopy -from socket import gethostname from collections import defaultdict from django.utils import timezone -from common.utils import lazyproperty, gen_key_pair, ssh_pubkey_gen, ssh_key_string_to_obj +from common.utils import lazyproperty, gen_key_pair from assets.models import ChangeSecretRecord from assets.const import ( AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES @@ -39,19 +36,6 @@ class ChangeSecretManager(BasePlaybookManager): private_key, public_key = gen_key_pair() return private_key - @staticmethod - def generate_public_key(private_key): - return ssh_pubkey_gen(private_key=private_key, hostname=gethostname()) - - @staticmethod - def generate_private_key_path(secret, path_dir): - key_name = '.' + md5(secret.encode('utf-8')).hexdigest() - key_path = os.path.join(path_dir, key_name) - if not os.path.exists(key_path): - ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path) - os.chmod(key_path, 0o400) - return key_path - def generate_password(self): kwargs = self.execution.snapshot['password_rules'] or {} length = int(kwargs.get('length', DEFAULT_PASSWORD_RULES['length'])) diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index 11330370a..c6eb04593 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -1,6 +1,8 @@ from .change_secret.manager import ChangeSecretManager from .gather_facts.manager import GatherFactsManager from .gather_accounts.manager import GatherAccountsManager +from .verify_account.manager import VerifyAccountManager +from .push_account.manager import PushAccountManager from ..const import AutomationTypes @@ -9,6 +11,8 @@ class ExecutionManager: AutomationTypes.change_secret: ChangeSecretManager, AutomationTypes.gather_facts: GatherFactsManager, AutomationTypes.gather_accounts: GatherAccountsManager, + AutomationTypes.verify_account: VerifyAccountManager, + AutomationTypes.push_account: PushAccountManager, } def __init__(self, execution): diff --git a/apps/assets/automations/gather_accounts/database/mysql/main.yml b/apps/assets/automations/gather_accounts/database/mysql/main.yml index cc934f20f..4b166322a 100644 --- a/apps/assets/automations/gather_accounts/database/mysql/main.yml +++ b/apps/assets/automations/gather_accounts/database/mysql/main.yml @@ -1,7 +1,7 @@ - hosts: mysql gather_facts: no vars: - ansible_python_interpreter: /usr/local/bin/python + ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python tasks: - name: Get info @@ -9,7 +9,7 @@ login_user: "{{ jms_account.username }}" login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" - login_port: "{{ jms_asset.port }}" + login_port: 1234 filter: users register: db_info diff --git a/apps/assets/automations/gather_accounts/database/postgresql/main.yml b/apps/assets/automations/gather_accounts/database/postgresql/main.yml index 2e12f51e5..cf0320627 100644 --- a/apps/assets/automations/gather_accounts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_accounts/database/postgresql/main.yml @@ -10,7 +10,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.database }}" + login_db: "{{ jms_asset.category_property.db_name }}" filter: "roles" register: db_info diff --git a/apps/assets/automations/gather_accounts/host/posix/main.yml b/apps/assets/automations/gather_accounts/host/posix/main.yml index 97326431d..a64323f9d 100644 --- a/apps/assets/automations/gather_accounts/host/posix/main.yml +++ b/apps/assets/automations/gather_accounts/host/posix/main.yml @@ -2,8 +2,10 @@ gather_facts: no tasks: - name: Gather posix account - ansible.builtin.win_shell: - cmd: net user + ansible.builtin.shell: + cmd: > + users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users; + do last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }';done register: result - name: Define info by set_fact diff --git a/apps/assets/automations/gather_accounts/host/windows/main.yml b/apps/assets/automations/gather_accounts/host/windows/main.yml index 377ffd10a..97326431d 100644 --- a/apps/assets/automations/gather_accounts/host/windows/main.yml +++ b/apps/assets/automations/gather_accounts/host/windows/main.yml @@ -1,18 +1,14 @@ -- hosts: windows - gather_facts: yes +- hosts: demo + gather_facts: no tasks: - - name: Get info - set_fact: - info: - arch: "{{ ansible_architecture2 }}" - distribution: "{{ ansible_distribution }}" - distribution_version: "{{ ansible_distribution_version }}" - kernel: "{{ ansible_kernel }}" - vendor: "{{ ansible_system_vendor }}" - model: "{{ ansible_product_name }}" - sn: "{{ ansible_product_serial }}" - cpu_vcpus: "{{ ansible_processor_vcpus }}" - memory: "{{ ansible_memtotal_mb }}" + - name: Gather posix account + ansible.builtin.win_shell: + cmd: net user + register: result + + - name: Define info by set_fact + ansible.builtin.set_fact: + info: "{{ result.stdout_lines }}" - debug: - var: info + var: info \ No newline at end of file diff --git a/apps/assets/automations/gather_accounts/manager.py b/apps/assets/automations/gather_accounts/manager.py index d6881f96c..40253be82 100644 --- a/apps/assets/automations/gather_accounts/manager.py +++ b/apps/assets/automations/gather_accounts/manager.py @@ -2,7 +2,7 @@ from common.utils import get_logger from assets.const import AutomationTypes from orgs.utils import tmp_to_org from .filter import GatherAccountsFilter -from ...models import Account, GatheredUser +from ...models import GatheredUser from ..base.manager import BasePlaybookManager logger = get_logger(__name__) @@ -42,4 +42,4 @@ class GatherAccountsManager(BasePlaybookManager): defaults['ip_last_login'] = data['address'][:32] GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username) else: - logger.error("Not found info, task name must be 'Get info': {}".format(host)) + logger.error("Not found info".format(host)) diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml index 82adcdc16..98183e94c 100644 --- a/apps/assets/automations/gather_facts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml @@ -10,7 +10,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.database }}" + login_db: "{{ jms_asset.category_property.db_name }}" register: db_info - name: Define info by set_fact diff --git a/apps/assets/automations/gather_facts/manager.py b/apps/assets/automations/gather_facts/manager.py index bbc3f9add..90fae1d75 100644 --- a/apps/assets/automations/gather_facts/manager.py +++ b/apps/assets/automations/gather_facts/manager.py @@ -26,4 +26,4 @@ class GatherFactsManager(BasePlaybookManager): asset.info = info asset.save() else: - logger.error("Not found info, task name must be 'Get info': {}".format(host)) + logger.error("Not found info: {}".format(host)) diff --git a/apps/assets/automations/ping/database/mysql/main.yml b/apps/assets/automations/ping/database/mysql/main.yml index fab498c76..ec7ca9432 100644 --- a/apps/assets/automations/ping/database/mysql/main.yml +++ b/apps/assets/automations/ping/database/mysql/main.yml @@ -2,12 +2,6 @@ gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python - jms_account: - username: root - password: redhat - jms_asset: - address: 127.0.0.1 - port: 3306 tasks: - name: Test MySQL connection @@ -17,4 +11,3 @@ login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" filter: version - register: db_info diff --git a/apps/assets/automations/ping/database/postgresql/main.yml b/apps/assets/automations/ping/database/postgresql/main.yml index 3bc2f7957..d76ba3ae3 100644 --- a/apps/assets/automations/ping/database/postgresql/main.yml +++ b/apps/assets/automations/ping/database/postgresql/main.yml @@ -2,16 +2,6 @@ gather_facts: no vars: ansible_python_interpreter: /usr/local/bin/python - jms_account: - username: postgre - secret: postgre - jms_asset: - address: 127.0.0.1 - port: 5432 - database: testdb - account: - username: test - secret: jumpserver tasks: - name: Test PostgreSQL connection @@ -20,4 +10,4 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.database }}" + login_db: "{{ jms_asset.category_property.db_name }}" diff --git a/apps/assets/automations/ping/host/windows/main.yml b/apps/assets/automations/ping/host/windows/main.yml index 495b82a3d..d5af857a4 100644 --- a/apps/assets/automations/ping/host/windows/main.yml +++ b/apps/assets/automations/ping/host/windows/main.yml @@ -2,4 +2,4 @@ gather_facts: no tasks: - name: Windows ping - win_ping: + ansible.builtin.win_ping: diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py index 84712fb44..791009e36 100644 --- a/apps/assets/automations/ping/manager.py +++ b/apps/assets/automations/ping/manager.py @@ -6,4 +6,15 @@ logger = get_logger(__name__) class PingManager(BasePlaybookManager): - pass + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.host_asset_mapper = {} + + @classmethod + def method_type(cls): + return AutomationTypes.ping + + def host_callback(self, host, asset=None, **kwargs): + super().host_callback(host, asset=asset, **kwargs) + self.host_asset_mapper[host['name']] = asset + return host diff --git a/apps/assets/automations/gather_facts/README.md b/apps/assets/automations/push_account/__init__.py similarity index 100% rename from apps/assets/automations/gather_facts/README.md rename to apps/assets/automations/push_account/__init__.py diff --git a/apps/assets/automations/push_account/database/mysql/main.yml b/apps/assets/automations/push_account/database/mysql/main.yml new file mode 100644 index 000000000..bf10c95af --- /dev/null +++ b/apps/assets/automations/push_account/database/mysql/main.yml @@ -0,0 +1,15 @@ +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Add user account.username + community.mysql.mysql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + host: "%" diff --git a/apps/assets/automations/push_account/database/mysql/manifest.yml b/apps/assets/automations/push_account/database/mysql/manifest.yml new file mode 100644 index 000000000..d954cb1d8 --- /dev/null +++ b/apps/assets/automations/push_account/database/mysql/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_mysql +name: Push account from MySQL +category: database +type: + - mysql +method: push_account diff --git a/apps/assets/automations/push_account/database/postgresql/main.yml b/apps/assets/automations/push_account/database/postgresql/main.yml new file mode 100644 index 000000000..72fbc5e83 --- /dev/null +++ b/apps/assets/automations/push_account/database/postgresql/main.yml @@ -0,0 +1,16 @@ +- hosts: postgresql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Add user account.username + community.postgresql.postgresql_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.category_property.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + diff --git a/apps/assets/automations/push_account/database/postgresql/manifest.yml b/apps/assets/automations/push_account/database/postgresql/manifest.yml new file mode 100644 index 000000000..6488ddd5a --- /dev/null +++ b/apps/assets/automations/push_account/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_postgresql +name: Push account for PostgreSQL +category: database +type: + - postgresql +method: push_account diff --git a/apps/assets/automations/push_account/host/posix/main.yml b/apps/assets/automations/push_account/host/posix/main.yml new file mode 100644 index 000000000..afe13b226 --- /dev/null +++ b/apps/assets/automations/push_account/host/posix/main.yml @@ -0,0 +1,19 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Add user account.username + ansible.builtin.user: + name: "{{ account.username }}" + + - name: Set account.username password + ansible.builtin.user: + name: "{{ account.username }}" + password: "{{ account.secret | password_hash('sha512') }}" + update_password: always + when: secret_type == "password" + + - name: Set account.username SSH key + ansible.builtin.authorized_key: + user: "{{ account.username }}" + key: "{{ account.secret }}" + when: secret_type == "ssh_key" diff --git a/apps/assets/automations/push_account/host/posix/manifest.yml b/apps/assets/automations/push_account/host/posix/manifest.yml new file mode 100644 index 000000000..9a7cc5c8c --- /dev/null +++ b/apps/assets/automations/push_account/host/posix/manifest.yml @@ -0,0 +1,7 @@ +id: push_account_posix +name: Push posix account +category: host +type: + - linux + - unix +method: push_account diff --git a/apps/assets/automations/push_account/host/windows/main.yml b/apps/assets/automations/push_account/host/windows/main.yml new file mode 100644 index 000000000..bbe8219d0 --- /dev/null +++ b/apps/assets/automations/push_account/host/windows/main.yml @@ -0,0 +1,13 @@ +- hosts: windows + gather_facts: yes + tasks: + - name: Add user account.username + ansible.windows.win_user: + vars: + fullname: "{{ account.username }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + state: present + password_expired: no + update_password: always + password_never_expires: yes diff --git a/apps/assets/automations/push_account/host/windows/manifest.yml b/apps/assets/automations/push_account/host/windows/manifest.yml new file mode 100644 index 000000000..7e0256f44 --- /dev/null +++ b/apps/assets/automations/push_account/host/windows/manifest.yml @@ -0,0 +1,7 @@ +id: push_account_windows +name: Push account windows +version: 1 +method: push_account +category: host +type: + - windows diff --git a/apps/assets/automations/push_account/manager.py b/apps/assets/automations/push_account/manager.py new file mode 100644 index 000000000..ea5e2193f --- /dev/null +++ b/apps/assets/automations/push_account/manager.py @@ -0,0 +1,17 @@ +from common.utils import get_logger +from assets.const import AutomationTypes +from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin + +logger = get_logger(__name__) + + +class PushAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager): + ignore_account = True + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.host_account_mapper = {} + + @classmethod + def method_type(cls): + return AutomationTypes.push_account diff --git a/apps/assets/automations/verify_account/__init__.py b/apps/assets/automations/verify_account/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/automations/verify_account/database/mysql/main.yml b/apps/assets/automations/verify_account/database/mysql/main.yml new file mode 100644 index 000000000..59c13d98a --- /dev/null +++ b/apps/assets/automations/verify_account/database/mysql/main.yml @@ -0,0 +1,13 @@ +- hosts: mysql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + community.mysql.mysql_info: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + filter: version diff --git a/apps/assets/automations/verify_account/database/mysql/manifest.yml b/apps/assets/automations/verify_account/database/mysql/manifest.yml new file mode 100644 index 000000000..a20b5c7d7 --- /dev/null +++ b/apps/assets/automations/verify_account/database/mysql/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_mysql +name: Verify account from MySQL +category: database +type: + - mysql +method: verify_account diff --git a/apps/assets/automations/verify_account/database/postgresql/main.yml b/apps/assets/automations/verify_account/database/postgresql/main.yml new file mode 100644 index 000000000..c70364767 --- /dev/null +++ b/apps/assets/automations/verify_account/database/postgresql/main.yml @@ -0,0 +1,13 @@ +- hosts: postgresql + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + community.postgresql.postgresql_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + db: "{{ jms_asset.category_property.db_name }}" diff --git a/apps/assets/automations/verify_account/database/postgresql/manifest.yml b/apps/assets/automations/verify_account/database/postgresql/manifest.yml new file mode 100644 index 000000000..4c9e2cbec --- /dev/null +++ b/apps/assets/automations/verify_account/database/postgresql/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_postgresql +name: Verify account for PostgreSQL +category: database +type: + - postgresql +method: verify_account diff --git a/apps/assets/automations/verify_account/host/posix/main.yml b/apps/assets/automations/verify_account/host/posix/main.yml new file mode 100644 index 000000000..41ae1768d --- /dev/null +++ b/apps/assets/automations/verify_account/host/posix/main.yml @@ -0,0 +1,11 @@ +- hosts: demo + gather_facts: no + tasks: + - name: Verify account + ansible.builtin.ping: + become: no + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" + ansible_ssh_private_key_file: "{{ account.private_key_path }}" + ansible_become: no diff --git a/apps/assets/automations/verify_account/host/posix/manifest.yml b/apps/assets/automations/verify_account/host/posix/manifest.yml new file mode 100644 index 000000000..5b9a1e51b --- /dev/null +++ b/apps/assets/automations/verify_account/host/posix/manifest.yml @@ -0,0 +1,7 @@ +id: verify_account_posix +name: Verify posix account +category: host +type: + - linux + - unix +method: verify_account diff --git a/apps/assets/automations/verify_account/host/windows/main.yml b/apps/assets/automations/verify_account/host/windows/main.yml new file mode 100644 index 000000000..da9d40a74 --- /dev/null +++ b/apps/assets/automations/verify_account/host/windows/main.yml @@ -0,0 +1,8 @@ +- hosts: windows + gather_facts: yes + tasks: + - name: Verify account + ansible.windows.win_ping: + vars: + ansible_user: "{{ account.username }}" + ansible_password: "{{ account.secret }}" diff --git a/apps/assets/automations/verify_account/host/windows/manifest.yml b/apps/assets/automations/verify_account/host/windows/manifest.yml new file mode 100644 index 000000000..69faf4217 --- /dev/null +++ b/apps/assets/automations/verify_account/host/windows/manifest.yml @@ -0,0 +1,7 @@ +id: verify_account_windows +name: Verify account windows +version: 1 +method: verify_account +category: host +type: + - windows diff --git a/apps/assets/automations/verify_account/manager.py b/apps/assets/automations/verify_account/manager.py new file mode 100644 index 000000000..5445511ba --- /dev/null +++ b/apps/assets/automations/verify_account/manager.py @@ -0,0 +1,25 @@ +from common.utils import get_logger +from assets.const import AutomationTypes, Connectivity +from ..base.manager import BasePlaybookManager, PushOrVerifyHostCallbackMixin + +logger = get_logger(__name__) + + +class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager): + ignore_account = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.host_account_mapper = {} + + @classmethod + def method_type(cls): + return AutomationTypes.verify_account + + def on_host_success(self, host, result): + account = self.host_account_mapper.get(host) + account.set_connectivity(Connectivity.ok) + + def on_host_error(self, host, error, result): + account = self.host_account_mapper.get(host) + account.set_connectivity(Connectivity.failed) diff --git a/apps/assets/models/automations/__init__.py b/apps/assets/models/automations/__init__.py index 1c62bbddd..5c2a3e031 100644 --- a/apps/assets/models/automations/__init__.py +++ b/apps/assets/models/automations/__init__.py @@ -1,6 +1,6 @@ from .change_secret import * from .discovery_account import * from .push_account import * -from .verify_secret import * from .gather_facts import * from .gather_accounts import * +from .verify_account import * diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index ef20e27b0..53ca08aba 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -2,7 +2,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.db import fields -from common.const.choices import Trigger from common.db.models import JMSBaseModel from assets.const import AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy from .base import BaseAutomation diff --git a/apps/assets/models/automations/push_account.py b/apps/assets/models/automations/push_account.py index c36d89b43..b1da1966f 100644 --- a/apps/assets/models/automations/push_account.py +++ b/apps/assets/models/automations/push_account.py @@ -1,15 +1,16 @@ from django.utils.translation import ugettext_lazy as _ +from assets.const import AutomationTypes from .base import BaseAutomation +__all__ = ['PushAccountAutomation'] + class PushAccountAutomation(BaseAutomation): - class Meta: - verbose_name = _("Push automation") - def to_attr_json(self): - attr_json = super().to_attr_json() - attr_json.update({ - 'type': 'push_account' - }) - return attr_json + def save(self, *args, **kwargs): + self.type = AutomationTypes.verify_account + super().save(*args, **kwargs) + + class Meta: + verbose_name = _("Push asset account") diff --git a/apps/assets/models/automations/verify_secret.py b/apps/assets/models/automations/verify_account.py similarity index 56% rename from apps/assets/models/automations/verify_secret.py rename to apps/assets/models/automations/verify_account.py index 62326c8bb..cf7004820 100644 --- a/apps/assets/models/automations/verify_secret.py +++ b/apps/assets/models/automations/verify_account.py @@ -1,12 +1,15 @@ from django.utils.translation import ugettext_lazy as _ +from assets.const import AutomationTypes from .base import BaseAutomation +__all__ = ['VerifyAccountAutomation'] + class VerifyAccountAutomation(BaseAutomation): - class Meta: - verbose_name = _("Verify account automation") - def save(self, *args, **kwargs): - self.type = 'verify_account' + self.type = AutomationTypes.verify_account super().save(*args, **kwargs) + + class Meta: + verbose_name = _("Verify asset account") diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 344605a18..c613d5080 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -49,7 +49,7 @@ class DefaultCallback: } self.result['ok'][host][task] = detail - def runer_on_failed(self, event_data, host=None, task=None, res=None, **kwargs): + def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs): detail = { 'action': event_data.get('task_action', ''), 'res': res, diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 919b0948c..27988c3d0 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -9,8 +9,10 @@ __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, manager, assets=None, account_policy='smart', - account_prefer='root,administrator', host_callback=None): + def __init__( + self, assets=None, account_policy='smart', + account_prefer='root,administrator', host_callback=None + ): """ :param assets: :param account_prefer: account username name if not set use account_policy @@ -79,10 +81,7 @@ class JMSInventory: ssh_protocol_matched = list(filter(lambda x: x.name == 'ssh', protocols)) ssh_protocol = ssh_protocol_matched[0] if ssh_protocol_matched else None host['ansible_host'] = asset.address - if asset.port == 0: - host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 - else: - host['ansible_port'] = asset.port + host['ansible_port'] = ssh_protocol.port if ssh_protocol else 22 su_from = account.su_from if platform.su_enabled and su_from: @@ -166,9 +165,9 @@ class JMSInventory: platform_assets = self.group_by_platform(self.assets) for platform, assets in platform_assets.items(): automation = platform.automation - protocols = platform.protocols.all() for asset in assets: + protocols = asset.protocols.all() account = self.select_account(asset) host = self.asset_to_host(asset, account, automation, protocols, platform) From f5fd674f089cb2b5fb300a95dc88544d11ba635e Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 28 Oct 2022 19:10:19 +0800 Subject: [PATCH 252/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20v3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/ansible/inventory.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index aee85face..739d84aff 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -63,7 +63,6 @@ class JMSInventory: var = { 'ansible_user': account.username, } - if not account.secret: return var if account.secret_type == 'password': From a11770e96d52dd5bf4a7a1698a9f7428ecb66ae6 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 31 Oct 2022 10:42:12 +0800 Subject: [PATCH 253/488] fix: automiation --- apps/assets/automations/base/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 2339867c1..a927987d1 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -21,7 +21,7 @@ logger = get_logger(__name__) class PushOrVerifyHostCallbackMixin: - execution: Model() + execution: callable host_account_mapper: dict ignore_account: bool generate_public_key: callable From d123c7f105b756bf2209c7f148afec4152b0dd3e Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 31 Oct 2022 10:57:19 +0800 Subject: [PATCH 254/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 1 - apps/terminal/automations/deploy_applet_host/playbook.yml | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 2339867c1..5d8fcf412 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -21,7 +21,6 @@ logger = get_logger(__name__) class PushOrVerifyHostCallbackMixin: - execution: Model() host_account_mapper: dict ignore_account: bool generate_public_key: callable diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 348f50fdb..20970c952 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -106,7 +106,9 @@ dest: "{{ ansible_env.TEMP }}" - name: Install python requirements offline - ansible.windows.win_shell: pip install -r "{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt" --no-index --find-links="{{ ansible_env.TEMP }}\pip_packages_v0.0.1" + ansible.windows.win_shell: > + pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt' + --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1' - name: Download chromedriver (chrome) ansible.windows.win_get_url: From 8df15cb564c9b103600ef7c783322d220cefa44d Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 31 Oct 2022 14:29:42 +0800 Subject: [PATCH 255/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=A5=AE?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/startup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/startup.py b/apps/terminal/startup.py index 7e454da83..672d31830 100644 --- a/apps/terminal/startup.py +++ b/apps/terminal/startup.py @@ -11,7 +11,7 @@ from common.utils import get_disk_usage, get_cpu_load, get_memory_usage, get_log from .serializers.terminal import TerminalRegistrationSerializer, StatusSerializer from .const import TerminalTypeChoices -from .models.terminal import Terminal +from .models import Terminal __all__ = ['CoreTerminal', 'CeleryTerminal'] From 094e144a513865b7dd19c1d98f22ed98154d290b Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 31 Oct 2022 17:37:54 +0800 Subject: [PATCH 256/488] perf: history secret --- apps/assets/api/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 8e8e320bd..4aef92de0 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -43,7 +43,7 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): serializer_classes = { 'default': serializers.AccountSecretSerializer } - http_method_names = ['get'] + http_method_names = ['get', 'options'] # Todo: 记得打开 # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] rbac_perms = { From 4f2250b7a847ecb1fc655b864692f857666d6e76 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 31 Oct 2022 18:32:07 +0800 Subject: [PATCH 257/488] perf: gather account windows --- apps/assets/automations/gather_accounts/filter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/assets/automations/gather_accounts/filter.py b/apps/assets/automations/gather_accounts/filter.py index 0c8f32536..ebaf6d9b1 100644 --- a/apps/assets/automations/gather_accounts/filter.py +++ b/apps/assets/automations/gather_accounts/filter.py @@ -39,8 +39,11 @@ class GatherAccountsFilter: @staticmethod def windows_filter(info): - # TODO + info = info[4:-2] result = {} + for i in info: + for username in i.split(): + result[username] = {} return result def run(self, method_id_meta_mapper, info): From 81e38094354b9316bd8aff441564b8fd663c0b91 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 31 Oct 2022 18:47:12 +0800 Subject: [PATCH 258/488] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20Connec?= =?UTF-8?q?tionToken=20API=20=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 253 +++++++++--------- .../authentication/models/connection_token.py | 7 +- 2 files changed, 125 insertions(+), 135 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index a67fc92bd..08b59581e 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -1,15 +1,16 @@ -import abc import os +import abc import json import time import base64 import urllib.parse from django.http import HttpResponse from django.shortcuts import get_object_or_404 +from rest_framework.request import Request +from rest_framework import status from rest_framework.exceptions import PermissionDenied from rest_framework.decorators import action from rest_framework.response import Response -from rest_framework import status from rest_framework.request import Request from common.drf.api import JMSModelViewSet @@ -25,76 +26,12 @@ from ..models import ConnectionToken __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] +# ExtraActionApiMixin -class ConnectionTokenMixin: + +class RDPFileClientProtocolURLMixin: request: Request - - @staticmethod - def check_token_valid(token: ConnectionToken): - is_valid, error = token.check_valid() - if not is_valid: - raise PermissionDenied(error) - - @abc.abstractmethod - def get_request_resource_user(self, serializer): - raise NotImplementedError - - def get_request_resources(self, serializer): - user = self.get_request_resource_user(serializer) - asset = serializer.validated_data.get('asset') - account = serializer.validated_data.get('account') - return user, asset, account - - @staticmethod - def check_user_has_resource_permission(user, asset, account): - from perms.utils.account import PermAccountUtil - if not asset or not user: - error = '' - raise PermissionDenied(error) - - actions, expire_at = PermAccountUtil().validate_permission( - user, asset, account_username=account - ) - if not actions: - error = '' - raise PermissionDenied(error) - - if expire_at < time.time(): - error = '' - raise PermissionDenied(error) - - def get_smart_endpoint(self, protocol, asset=None, application=None): - if asset: - target_ip = asset.get_target_ip() - elif application: - target_ip = application.get_target_ip() - else: - target_ip = '' - endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request) - return endpoint - - @staticmethod - def parse_env_bool(env_key, env_default, true_value, false_value): - return true_value if is_true(os.getenv(env_key, env_default)) else false_value - - def get_client_protocol_data(self, token: ConnectionToken): - protocol = token.protocol - username = token.user.username - rdp_config = ssh_token = '' - if protocol == 'rdp': - filename, rdp_config = self.get_rdp_file_info(token) - elif protocol == 'ssh': - filename, ssh_token = self.get_ssh_token(token) - else: - raise ValueError('Protocol not support: {}'.format(protocol)) - - return { - "filename": filename, - "protocol": protocol, - "username": username, - "token": ssh_token, - "config": rdp_config - } + get_serializer: callable def get_rdp_file_info(self, token: ConnectionToken): rdp_options = { @@ -189,6 +126,29 @@ class ConnectionTokenMixin: filename = urllib.parse.quote(filename) return filename + @staticmethod + def parse_env_bool(env_key, env_default, true_value, false_value): + return true_value if is_true(os.getenv(env_key, env_default)) else false_value + + def get_client_protocol_data(self, token: ConnectionToken): + protocol = token.protocol + username = token.user.username + rdp_config = ssh_token = '' + if protocol == 'rdp': + filename, rdp_config = self.get_rdp_file_info(token) + elif protocol == 'ssh': + filename, ssh_token = self.get_ssh_token(token) + else: + raise ValueError('Protocol not support: {}'.format(protocol)) + + return { + "filename": filename, + "protocol": protocol, + "username": username, + "token": ssh_token, + "config": rdp_config + } + def get_ssh_token(self, token: ConnectionToken): if token.asset: name = token.asset.name @@ -207,8 +167,79 @@ class ConnectionTokenMixin: token = json.dumps(data) return filename, token + def get_smart_endpoint(self, protocol, asset=None): + target_ip = asset.get_target_ip() if asset else '' + endpoint = EndpointRule.match_endpoint(target_ip, protocol, self.request) + return endpoint -class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet): + +class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): + request: Request + get_object: callable + get_serializer: callable + perform_create: callable + check_token_permission: callable + create_connection_token: callable + + @action(methods=['POST'], detail=False, url_path='secret-info/detail') + def get_secret_detail(self, request, *args, **kwargs): + """ 非常重要的 api, 在逻辑层再判断一下 rbac 权限, 双重保险 """ + rbac_perm = 'authentication.view_connectiontokensecret' + if not request.user.has_perm(rbac_perm): + raise PermissionDenied('Not allow to view secret') + token_id = request.data.get('token') or '' + token = get_object_or_404(ConnectionToken, pk=token_id) + self.check_token_permission(token) + serializer = self.get_serializer(instance=token) + return Response(serializer.data, status=status.HTTP_200_OK) + + @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') + def get_rdp_file(self, request, *args, **kwargs): + token = self.create_connection_token() + self.check_token_permission(token) + filename, content = self.get_rdp_file_info(token) + filename = '{}.rdp'.format(filename) + response = HttpResponse(content, content_type='application/octet-stream') + response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % filename + return response + + @action(methods=['POST', 'GET'], detail=False, url_path='client-url') + def get_client_protocol_url(self, request, *args, **kwargs): + token = self.create_connection_token() + self.check_token_permission(token) + try: + protocol_data = self.get_client_protocol_data(token) + except ValueError as e: + return Response(data={'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + protocol_data = json.dumps(protocol_data).encode() + protocol_data = base64.b64encode(protocol_data).decode() + data = { + 'url': 'jms://{}'.format(protocol_data) + } + return Response(data=data) + + @action(methods=['PATCH'], detail=True) + def expire(self, request, *args, **kwargs): + instance = self.get_object() + instance.expire() + return Response(status=status.HTTP_204_NO_CONTENT) + + @staticmethod + def check_token_permission(token: ConnectionToken): + is_valid, error = token.check_permission() + if not is_valid: + raise PermissionDenied(error) + + def create_connection_token(self): + data = self.request.query_params if self.request.method == 'GET' else self.request.data + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + token: ConnectionToken = serializer.instance + return token + + +class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelViewSet): filterset_fields = ( 'user_display', 'asset_display' ) @@ -231,72 +262,29 @@ class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelVie def get_queryset(self): return ConnectionToken.objects.filter(user=self.request.user) - def get_request_resource_user(self, serializer): + def get_user(self, serializer): return self.request.user - def get_object(self): - if self.request.user.is_service_account: - # TODO: 组件获取 token 详情,将来放在 Super-connection-token API 中 - obj = get_object_or_404(ConnectionToken, pk=self.kwargs.get('pk')) - else: - obj = super(ConnectionTokenViewSet, self).get_object() - return obj - - def create_connection_token(self): - data = self.request.query_params if self.request.method == 'GET' else self.request.data - serializer = self.get_serializer(data=data) - serializer.is_valid(raise_exception=True) - self.perform_create(serializer) - token: ConnectionToken = serializer.instance - return token - def perform_create(self, serializer): - user, asset, account = self.get_request_resources(serializer) - self.check_user_has_resource_permission(user, asset, account) + user = self.get_user(serializer) + asset = serializer.validated_data.get('asset') + account_username = serializer.validated_data.get('account_username') + self.validate_asset_permission(user, asset, account_username) return super(ConnectionTokenViewSet, self).perform_create(serializer) - @action(methods=['POST'], detail=False, url_path='secret-info/detail') - def get_secret_detail(self, request, *args, **kwargs): - # 非常重要的 api,在逻辑层再判断一下,双重保险 - perm_required = 'authentication.view_connectiontokensecret' - if not request.user.has_perm(perm_required): - raise PermissionDenied('Not allow to view secret') - token_id = request.data.get('token') or '' - token = get_object_or_404(ConnectionToken, pk=token_id) - self.check_token_valid(token) - serializer = self.get_serializer(instance=token) - return Response(serializer.data, status=status.HTTP_200_OK) + @staticmethod + def validate_asset_permission(user, asset, account_username): + from perms.utils.account import PermAccountUtil + actions, expire_at = PermAccountUtil().validate_permission(user, asset, account_username) + if not actions: + error = '' + raise PermissionDenied(error) + if expire_at < time.time(): + error = '' + raise PermissionDenied(error) - @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') - def get_rdp_file(self, request, *args, **kwargs): - token = self.create_connection_token() - self.check_token_valid(token) - filename, content = self.get_rdp_file_info(token) - filename = '{}.rdp'.format(filename) - response = HttpResponse(content, content_type='application/octet-stream') - response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % filename - return response - @action(methods=['POST', 'GET'], detail=False, url_path='client-url') - def get_client_protocol_url(self, request, *args, **kwargs): - token = self.create_connection_token() - self.check_token_valid(token) - try: - protocol_data = self.get_client_protocol_data(token) - except ValueError as e: - return Response(data={'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) - protocol_data = json.dumps(protocol_data).encode() - protocol_data = base64.b64encode(protocol_data).decode() - data = { - 'url': 'jms://{}'.format(protocol_data) - } - return Response(data=data) - - @action(methods=['PATCH'], detail=True) - def expire(self, request, *args, **kwargs): - instance = self.get_object() - instance.expire() - return Response(status=status.HTTP_204_NO_CONTENT) +# SuperConnectionToken class SuperConnectionTokenViewSet(ConnectionTokenViewSet): @@ -308,7 +296,10 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet): 'renewal': 'authentication.add_superconnectiontoken' } - def get_request_resource_user(self, serializer): + def get_queryset(self): + return ConnectionToken.objects.all() + + def get_user(self, serializer): return serializer.validated_data.get('user') @action(methods=['PATCH'], detail=False) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index c8fae3790..3ed4c2a54 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -93,9 +93,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('No account') return is_valid, error - - account_util = PermAccountUtil() - actions, expire_at = account_util.validate_permission( + actions, expire_at = PermAccountUtil().validate_permission( self.user, self.asset, self.account_username ) if not actions or expire_at < time.time(): @@ -104,7 +102,8 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): return is_valid, error self.actions = actions self.expire_at = expire_at - return True, '' + is_valid, error = True, '' + return is_valid, error @lazyproperty def account(self): From 5bd40fcd224a14927d8a7c573a214a47d4a57260 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 31 Oct 2022 19:27:45 +0800 Subject: [PATCH 259/488] fix: swagger --- apps/acls/models/login_asset_acl.py | 2 +- apps/assets/api/account/account.py | 2 +- apps/assets/filters.py | 2 +- apps/authentication/serializers/connection_token.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 3425ac8de..37bea242a 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -64,7 +64,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): Q(assets__hostname_group__contains=asset.name) | Q(assets__hostname_group__contains='*') ) - ids = [q.id for q in queryset if contains_ip(asset.ip, q.assets.get('ip_group', []))] + ids = [q.id for q in queryset if contains_ip(asset.address, q.assets.get('ip_group', []))] queryset = cls.objects.filter(id__in=ids) return queryset diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 4aef92de0..94b29995f 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -26,7 +26,7 @@ class AccountViewSet(OrgBulkModelViewSet): } rbac_perms = { 'verify': 'assets.test_account', - 'partial_update': 'assets.change_assetaccountsecret', + 'partial_update': 'assets.change_accountsecret', } @action(methods=['post'], detail=True, url_path='verify') diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 2af062718..de2550ceb 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -142,7 +142,7 @@ class IpInFilterBackend(filters.BaseFilterBackend): if not ips: return queryset ip_list = [i.strip() for i in ips.split(',')] - queryset = queryset.filter(ip__in=ip_list) + queryset = queryset.filter(address__in=ip_list) return queryset def get_schema_fields(self, view): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 8f36ddc2b..e809ed78c 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -113,7 +113,7 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer): """ Asset """ class Meta: model = Asset - fields = ['id', 'name', 'ip', 'protocols', 'org_id'] + fields = ['id', 'name', 'address', 'protocols', 'org_id'] class ConnectionTokenAccountSerializer(serializers.ModelSerializer): From 5fa852c61da3f1e5f2466da000e931cb8765730e Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 31 Oct 2022 19:28:15 +0800 Subject: [PATCH 260/488] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E7=AE=A1=E7=94=A8=E7=9A=84=E5=BA=8F=E5=88=97=E5=8C=96=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/serializers/celery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py index 3fd72fde3..351d63213 100644 --- a/apps/ops/serializers/celery.py +++ b/apps/ops/serializers/celery.py @@ -31,7 +31,7 @@ class CeleryTaskSerializer(serializers.ModelSerializer): class Meta: model = CeleryTask fields = [ - 'id', 'name', 'meta', 'publish_count', 'state', 'success_count', 'last_published_time', + 'id', 'name', 'meta', 'state', 'last_published_time', ] From 796758cbb2c13d323366e296121651aa96753048 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 1 Nov 2022 11:13:18 +0800 Subject: [PATCH 261/488] perf: histories account --- apps/assets/api/account/account.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 94b29995f..31aaccb96 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -1,6 +1,6 @@ from rest_framework.decorators import action from rest_framework.response import Response -from rest_framework.generics import CreateAPIView +from rest_framework.generics import CreateAPIView, get_object_or_404 from orgs.mixins.api import OrgBulkModelViewSet from rbac.permissions import RBACPermission @@ -41,7 +41,8 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): 因为可能要导出所有账号,所以单独建立了一个 viewset """ serializer_classes = { - 'default': serializers.AccountSecretSerializer + 'default': serializers.AccountSecretSerializer, + 'histories': serializers.AccountHistorySerializer, } http_method_names = ['get', 'options'] # Todo: 记得打开 @@ -52,12 +53,11 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): 'histories': ['assets.view_accountsecret'], } - @action(methods=['get'], detail=True, url_path='histories', serializer_class=serializers.AccountHistorySerializer) + @action(methods=['get'], detail=True, url_path='histories') def histories(self, request, *args, **kwargs): - account = self.get_object() - histories = account.history.all() - serializer = serializers.AccountHistorySerializer(histories, many=True) - return Response(serializer.data) + account = get_object_or_404(self.get_queryset(), **kwargs) + self.queryset = account.history.all() + return super().list(request, *args, **kwargs) class AccountTaskCreateAPI(CreateAPIView): From cf81f08b7ab8542eeac9fbae6cc3b4d906f61b31 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 1 Nov 2022 11:52:51 +0800 Subject: [PATCH 262/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=20host?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/automations/base/manager.py | 8 ++-- .../automations/change_secret/manager.py | 2 +- apps/assets/models/asset/common.py | 2 +- apps/assets/models/asset/database.py | 2 +- apps/assets/models/automations/base.py | 2 +- apps/assets/serializers/asset/common.py | 4 +- apps/common/const/choices.py | 9 +++++ apps/ops/ansible/callback.py | 13 ++++++- apps/ops/ansible/inventory.py | 38 ++++++++++--------- apps/ops/models/adhoc.py | 4 +- apps/ops/models/base.py | 10 ++--- apps/ops/models/celery.py | 1 + apps/ops/models/playbook.py | 4 +- apps/orgs/mixins/models.py | 23 +++++------ apps/orgs/utils.py | 26 ++++++------- apps/terminal/api/applet/host.py | 32 ++++++++-------- .../{manager.py => __init__.py} | 29 ++++++++++++-- .../migrations/0054_auto_20221027_1125.py | 2 +- ...028_1544.py => 0055_auto_20221031_1848.py} | 21 ++++++---- apps/terminal/models/applet/applet.py | 1 + apps/terminal/models/applet/host.py | 27 ++++++++----- apps/terminal/serializers/applet.py | 16 ++++++-- apps/terminal/tasks.py | 17 +++++++-- apps/terminal/urls/api_urls.py | 1 + 24 files changed, 186 insertions(+), 108 deletions(-) rename apps/terminal/automations/deploy_applet_host/{manager.py => __init__.py} (63%) rename apps/terminal/migrations/{0055_auto_20221028_1544.py => 0055_auto_20221031_1848.py} (71%) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index a927987d1..758dea52c 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -8,7 +8,6 @@ from collections import defaultdict from django.conf import settings from django.utils import timezone -from django.db.models import Model from django.utils.translation import gettext as _ from common.utils import get_logger @@ -115,9 +114,9 @@ class BasePlaybookManager: method_attr = '{}_method'.format(self.__class__.method_type()) method_enabled = automation and \ - getattr(automation, enabled_attr) and \ - getattr(automation, method_attr) and \ - getattr(automation, method_attr) in self.method_id_meta_mapper + getattr(automation, enabled_attr) and \ + getattr(automation, method_attr) and \ + getattr(automation, method_attr) in self.method_id_meta_mapper if not method_enabled: host['error'] = _('{} disabled'.format(self.__class__.method_type())) @@ -132,6 +131,7 @@ class BasePlaybookManager: def generate_private_key_path(secret, path_dir): key_name = '.' + md5(secret.encode('utf-8')).hexdigest() key_path = os.path.join(path_dir, key_name) + if not os.path.exists(key_path): ssh_key_string_to_obj(secret, password=None).write_private_key_file(key_path) os.chmod(key_path, 0o400) diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index b8868acd1..4f77dfe85 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -154,7 +154,7 @@ class ChangeSecretManager(BasePlaybookManager): recorder = self.name_recorder_mapper.get(host) if not recorder: return - recorder.status = 'succeed' + recorder.status = 'success' recorder.date_finished = timezone.now() recorder.save() diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index fd29bf6a4..44fbddc23 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -106,7 +106,7 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): return '{0.name}({0.address})'.format(self) @property - def category_property(self): + def specific(self): if not hasattr(self, self.category): return {} instance = getattr(self, self.category) diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index de6b6a758..6aef15a8f 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -15,7 +15,7 @@ class Database(Asset): return self.address @property - def category_property(self): + def specific(self): return { 'db_name': self.db_name, } diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index aabfd241f..a60a0e060 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -3,7 +3,7 @@ from celery import current_task from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.const.choices import Trigger +from common.const.choices import Trigger, Status from common.mixins.models import CommonModelMixin from common.db.fields import EncryptJsonDictTextField from orgs.mixins.models import OrgModelMixin diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index b6204e843..d05b430cb 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -77,7 +77,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) 'nodes', 'labels', 'accounts', 'protocols', 'nodes_display', ] read_only_fields = [ - 'category', 'type', 'category_property', + 'category', 'type', 'specific', 'connectivity', 'date_verified', 'created_by', 'date_created', ] @@ -90,7 +90,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) def get_field_names(self, declared_fields, info): names = super().get_field_names(declared_fields, info) if self.__class__.__name__ != 'AssetSerializer': - names.remove('category_property') + names.remove('specific') return names @classmethod diff --git a/apps/common/const/choices.py b/apps/common/const/choices.py index f752bd50e..fba8418df 100644 --- a/apps/common/const/choices.py +++ b/apps/common/const/choices.py @@ -9,3 +9,12 @@ AUDITOR = 'Auditor' class Trigger(models.TextChoices): manual = 'manual', _('Manual trigger') timing = 'timing', _('Timing trigger') + + +class Status(models.TextChoices): + pending = 'pending', _("Pending") + running = 'running', _("Running") + success = 'success', _("Success") + failed = 'failed', _("Failed") + error = 'error', _("Error") + canceled = 'canceled', _("Canceled") diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index c613d5080..3f794a194 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -2,6 +2,14 @@ from collections import defaultdict class DefaultCallback: + STATUS_MAPPER = { + 'successful': 'success', + 'failure': 'failed', + 'running': 'running', + 'pending': 'pending', + 'unknown': 'unknown' + } + def __init__(self): self.result = dict( ok=defaultdict(dict), @@ -27,7 +35,7 @@ class DefaultCallback: return results def is_success(self): - return self.status != 'successful' + return self.status != 'success' def event_handler(self, data, **kwargs): event = data.get('event', None) @@ -131,4 +139,5 @@ class DefaultCallback: pass def status_handler(self, data, **kwargs): - self.status = data.get('status', 'unknown') + status = data.get('status', '') + self.status = self.STATUS_MAPPER.get(status, 'unknown') diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 8a1ae0634..1e006ae77 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -9,13 +9,12 @@ __all__ = ['JMSInventory'] class JMSInventory: - def __init__(self, assets, account_policy='smart', - account_prefer='root,administrator', - host_callback=None): + def __init__(self, assets, account_policy='privileged_first', + account_prefer='root,Administrator', host_callback=None): """ :param assets: :param account_prefer: account username name if not set use account_policy - :param account_policy: smart, privileged_must, privileged_first + :param account_policy: privileged_only, privileged_first, skip """ self.assets = self.clean_assets(assets) self.account_prefer = account_prefer @@ -105,7 +104,7 @@ class JMSInventory: 'id': str(asset.id), 'name': asset.name, 'address': asset.address, 'type': asset.type, 'category': asset.category, 'protocol': asset.protocol, 'port': asset.port, - 'category_property': asset.category_property, + 'specific': asset.specific, 'protocols': [{'name': p.name, 'port': p.port} for p in protocols], }, 'jms_account': { @@ -137,24 +136,27 @@ class JMSInventory: def select_account(self, asset): accounts = list(asset.accounts.all()) account_selected = None - account_username = self.account_prefer + account_usernames = self.account_prefer if isinstance(self.account_prefer, str): - account_username = self.account_prefer.split(',') + account_usernames = self.account_prefer.split(',') - if account_username: - for username in account_username: - account_matched = list(filter(lambda account: account.username == username, accounts)) - if account_matched: - account_selected = account_matched[0] - break + # 优先使用提供的名称 + if account_usernames: + account_matched = list(filter(lambda account: account.username in account_usernames, accounts)) + account_selected = account_matched[0] if account_matched else None - if not account_selected: - if self.account_policy in ['privileged_must', 'privileged_first']: - account_matched = list(filter(lambda account: account.privileged, accounts)) - account_selected = account_matched[0] if account_matched else None + if account_selected or self.account_policy == 'skip': + return account_selected - if not account_selected and self.account_policy == 'privileged_first': + if self.account_policy in ['privileged_only', 'privileged_first']: + account_matched = list(filter(lambda account: account.privileged, accounts)) + account_selected = account_matched[0] if account_matched else None + + if account_selected: + return account_selected + + if self.account_policy == 'privileged_first': account_selected = accounts[0] if accounts else None return account_selected diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 2273a21b6..22fd0f054 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -5,7 +5,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger -from .base import BaseAnsibleTask, BaseAnsibleExecution +from .base import BaseAnsibleJob, BaseAnsibleExecution from ..ansible import AdHocRunner __all__ = ["AdHoc", "AdHocExecution"] @@ -14,7 +14,7 @@ __all__ = ["AdHoc", "AdHocExecution"] logger = get_logger(__file__) -class AdHoc(BaseAnsibleTask): +class AdHoc(BaseAnsibleJob): pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all') module = models.CharField(max_length=128, default='shell', verbose_name=_('Module')) args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) diff --git a/apps/ops/models/base.py b/apps/ops/models/base.py index 4ff69277f..a37982673 100644 --- a/apps/ops/models/base.py +++ b/apps/ops/models/base.py @@ -12,7 +12,7 @@ from ..ansible.inventory import JMSInventory from ..mixin import PeriodTaskModelMixin -class BaseAnsibleTask(PeriodTaskModelMixin, JMSOrgBaseModel): +class BaseAnsibleJob(PeriodTaskModelMixin, JMSOrgBaseModel): owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) account = models.CharField(max_length=128, default='root', verbose_name=_('Account')) @@ -46,7 +46,7 @@ class BaseAnsibleTask(PeriodTaskModelMixin, JMSOrgBaseModel): class BaseAnsibleExecution(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') - task = models.ForeignKey(BaseAnsibleTask, on_delete=models.CASCADE, related_name='executions', null=True) + task = models.ForeignKey(BaseAnsibleJob, on_delete=models.CASCADE, related_name='executions', null=True) result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) summary = models.JSONField(default=dict, verbose_name=_('Summary')) creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) @@ -86,7 +86,7 @@ class BaseAnsibleExecution(models.Model): def set_result(self, cb): status_mapper = { - 'successful': 'succeeded', + 'successful': 'success', } this = self.__class__.objects.get(id=self.id) this.status = status_mapper.get(cb.status, cb.status) @@ -112,11 +112,11 @@ class BaseAnsibleExecution(models.Model): @property def is_finished(self): - return self.status in ['succeeded', 'failed'] + return self.status in ['success', 'failed'] @property def is_success(self): - return self.status == 'succeeded' + return self.status == 'success' @property def time_cost(self): diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 2cc989fc3..55f05129f 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -23,6 +23,7 @@ class CeleryTask(models.Model): "comment": getattr(task, 'comment', None), "queue": getattr(task, 'queue', 'default') } + @property def state(self): last_five_executions = CeleryTaskExecution.objects.filter(name=self.name).order_by('-date_published')[:5] diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index 0701ed13e..a0c11db3b 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -2,7 +2,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from orgs.mixins.models import JMSOrgBaseModel -from .base import BaseAnsibleExecution, BaseAnsibleTask +from .base import BaseAnsibleExecution, BaseAnsibleJob class PlaybookTemplate(JMSOrgBaseModel): @@ -19,7 +19,7 @@ class PlaybookTemplate(JMSOrgBaseModel): unique_together = [('org_id', 'name')] -class Playbook(BaseAnsibleTask): +class Playbook(BaseAnsibleJob): path = models.FilePathField(max_length=1024, verbose_name=_("Playbook")) owner = models.ForeignKey('users.User', verbose_name=_("Owner"), on_delete=models.SET_NULL, null=True) comment = models.TextField(blank=True, verbose_name=_("Comment")) diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index 63b78bd23..80674ddc6 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -20,16 +20,15 @@ __all__ = [ class OrgManager(models.Manager): - def all_group_by_org(self): from ..models import Organization orgs = list(Organization.objects.all()) - querysets = {} + org_queryset = {} for org in orgs: org_id = org.id queryset = super(OrgManager, self).get_queryset().filter(org_id=org_id) - querysets[org] = queryset - return querysets + org_queryset[org] = queryset + return org_queryset def get_queryset(self): queryset = super(OrgManager, self).get_queryset() @@ -46,7 +45,7 @@ class OrgManager(models.Manager): for obj in objs: if org.is_root(): if not obj.org_id: - raise ValidationError('Please save in a organization') + raise ValidationError('Please save in a org') else: obj.org_id = org.id return super().bulk_create(objs, batch_size, ignore_conflicts) @@ -54,20 +53,24 @@ class OrgManager(models.Manager): class OrgModelMixin(models.Model): org_id = models.CharField( - max_length=36, blank=True, default='', verbose_name=_("Organization"), db_index=True + max_length=36, blank=True, default='', + verbose_name=_("Organization"), db_index=True ) objects = OrgManager() - sep = '@' def save(self, *args, **kwargs): - org = get_current_org() + locking_org = getattr(self, 'locking_org', None) + if locking_org: + org = Organization.get_instance(locking_org) + else: + org = get_current_org() # 这里不可以优化成, 因为 root 组织下可以设置组织 id 来保存 # if org.is_root() and not self.org_id: # raise ... if org.is_root(): if not self.org_id: - raise ValidationError('Please save in a organization') + raise ValidationError('Please save in a org') else: self.org_id = org.id return super().save(*args, **kwargs) @@ -87,8 +90,6 @@ class OrgModelMixin(models.Model): name = getattr(self, attr) elif hasattr(self, 'name'): name = self.name - elif hasattr(self, 'name'): - name = self.hostname return name + self.sep + self.org_name def validate_unique(self, exclude=None): diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 0ea4085e7..a80c7854d 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -103,22 +103,20 @@ def tmp_to_builtin_org(system=0, default=0): set_current_org(ori_org) -def get_org_filters(): - kwargs = {} - - _current_org = get_current_org() - if _current_org is None: - return kwargs - if _current_org.is_root(): - return kwargs - kwargs['org_id'] = _current_org.id - return kwargs - - def filter_org_queryset(queryset): - kwargs = get_org_filters() + locking_org = getattr(queryset.model, 'LOCKING_ORG', None) + if locking_org: + org = Organization.get_instance(locking_org) + else: + org = get_current_org() + + if org is None: + kwargs = {} + elif org.is_root(): + kwargs = {} + else: + kwargs = {'org_id': org.id} - # # lines = traceback.format_stack() # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") # for line in lines[-10:-1]: diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index ea8831b8f..d9c7cde7e 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -2,29 +2,17 @@ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response -from orgs.utils import tmp_to_builtin_org from terminal import serializers -from terminal.models import AppletHost, Applet +from terminal.models import AppletHost, Applet, AppletHostDeployment from terminal.tasks import run_applet_host_deployment -__all__ = ['AppletHostViewSet'] + +__all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] class AppletHostViewSet(viewsets.ModelViewSet): serializer_class = serializers.AppletHostSerializer - - def get_queryset(self): - return AppletHost.objects.all() - - def dispatch(self, request, *args, **kwargs): - with tmp_to_builtin_org(system=1): - return super().dispatch(request, *args, **kwargs) - - @action(methods=['post'], detail=True) - def deploy(self, request): - from terminal.automations.deploy_applet_host.manager import DeployAppletHostManager - manager = DeployAppletHostManager(self) - manager.run() + queryset = AppletHost.objects.all() @action(methods=['get'], detail=True, url_path='') def not_published_applets(self, request, *args, **kwargs): @@ -33,3 +21,15 @@ class AppletHostViewSet(viewsets.ModelViewSet): serializer = serializers.AppletSerializer(applets, many=True) return Response(serializer.data) + +class AppletHostDeploymentViewSet(viewsets.ModelViewSet): + serializer_class = serializers.AppletHostDeploymentSerializer + queryset = AppletHostDeployment.objects.all() + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + instance = serializer.save() + task = run_applet_host_deployment.delay(instance.id) + return Response({'task': str(task.id)}, status=201) + diff --git a/apps/terminal/automations/deploy_applet_host/manager.py b/apps/terminal/automations/deploy_applet_host/__init__.py similarity index 63% rename from apps/terminal/automations/deploy_applet_host/manager.py rename to apps/terminal/automations/deploy_applet_host/__init__.py index 22a8510af..287d44b3e 100644 --- a/apps/terminal/automations/deploy_applet_host/manager.py +++ b/apps/terminal/automations/deploy_applet_host/__init__.py @@ -1,16 +1,21 @@ import os import datetime import shutil + +from django.utils import timezone from django.conf import settings +from common.utils import get_logger +from common.db.utils import safe_db_connection from ops.ansible import PlaybookRunner, JMSInventory +logger = get_logger(__name__) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) class DeployAppletHostManager: - def __init__(self, applet_host): - self.applet_host = applet_host + def __init__(self, deployment): + self.deployment = deployment self.run_dir = self.get_run_dir() @staticmethod @@ -28,16 +33,32 @@ class DeployAppletHostManager: return playbook_dst def generate_inventory(self): - inventory = JMSInventory([self.applet_host], account_policy='privileged_only') + inventory = JMSInventory([self.deployment.host], account_policy='privileged_only') inventory_dir = os.path.join(self.run_dir, 'inventory') inventory_path = os.path.join(inventory_dir, 'hosts.yml') inventory.write_to_file(inventory_path) return inventory_path - def run(self, **kwargs): + def _run(self, **kwargs): inventory = self.generate_inventory() playbook = self.generate_playbook() runner = PlaybookRunner( inventory=inventory, playbook=playbook, project_dir=self.run_dir ) return runner.run(**kwargs) + + def run(self, **kwargs): + try: + self.deployment.date_start = timezone.now() + cb = self._run(**kwargs) + self.deployment.status = cb.status + except Exception as e: + logger.error("Error: {}".format(e)) + self.deployment.status = 'error' + finally: + self.deployment.date_finished = timezone.now() + with safe_db_connection(): + self.deployment.save() + + + diff --git a/apps/terminal/migrations/0054_auto_20221027_1125.py b/apps/terminal/migrations/0054_auto_20221027_1125.py index 418cbe993..4589d6970 100644 --- a/apps/terminal/migrations/0054_auto_20221027_1125.py +++ b/apps/terminal/migrations/0054_auto_20221027_1125.py @@ -73,7 +73,7 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('status', models.CharField(max_length=16, verbose_name='Status')), + ('status', models.CharField(max_length=16, default='', verbose_name='Status')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='terminal.applethost', verbose_name='Hosting')), ], diff --git a/apps/terminal/migrations/0055_auto_20221028_1544.py b/apps/terminal/migrations/0055_auto_20221031_1848.py similarity index 71% rename from apps/terminal/migrations/0055_auto_20221028_1544.py rename to apps/terminal/migrations/0055_auto_20221031_1848.py index 3aeeff3f6..e36ed5a9b 100644 --- a/apps/terminal/migrations/0055_auto_20221028_1544.py +++ b/apps/terminal/migrations/0055_auto_20221031_1848.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.14 on 2022-10-28 07:44 +# Generated by Django 3.2.14 on 2022-10-31 10:48 from django.db import migrations, models import django.db.models.deletion @@ -19,12 +19,22 @@ class Migration(migrations.Migration): migrations.AddField( model_name='applethost', name='date_inited', - field=models.DateTimeField(blank=True, null=True, verbose_name='Date initialized'), + field=models.DateTimeField(blank=True, null=True, verbose_name='Date inited'), ), migrations.AddField( model_name='applethost', - name='initialized', - field=models.BooleanField(default=False, verbose_name='Initialized'), + name='inited', + field=models.BooleanField(default=False, verbose_name='Inited'), + ), + migrations.AddField( + model_name='applethostdeployment', + name='date_finished', + field=models.DateTimeField(null=True, verbose_name='Date finished'), + ), + migrations.AddField( + model_name='applethostdeployment', + name='date_start', + field=models.DateTimeField(db_index=True, null=True, verbose_name='Date start'), ), migrations.AlterField( model_name='appletpublication', @@ -36,7 +46,4 @@ class Migration(migrations.Migration): name='host', field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='terminal.applethost', verbose_name='Host'), ), - migrations.DeleteModel( - name='AppletHostDeployment', - ), ] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 9dde129e7..14f363517 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -55,6 +55,7 @@ class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet')) host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host')) status = models.CharField(max_length=16, verbose_name=_('Status')) + published = models.BooleanField(default=False, verbose_name=_('Published')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) class Meta: diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 7733e7b0f..e1edc2239 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -1,17 +1,19 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from common.db.models import JMSBaseModel from assets.models import Host -from ops.ansible import PlaybookRunner, JMSInventory -__all__ = ['AppletHost'] +__all__ = ['AppletHost', 'AppletHostDeployment'] class AppletHost(Host): + LOCKING_ORG = 'SYSTEM' + account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) - initialized = models.BooleanField(default=False, verbose_name=_('Initialized')) - date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date initialized')) + inited = models.BooleanField(default=False, verbose_name=_('Inited')) + date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date inited')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) status = models.CharField(max_length=16, verbose_name=_('Status')) applets = models.ManyToManyField( @@ -19,11 +21,18 @@ class AppletHost(Host): through='AppletPublication', through_fields=('host', 'applet'), ) - def deploy(self): - inventory = JMSInventory([self]) - playbook = PlaybookRunner(inventory, 'applets.yml') - playbook.run() - def __str__(self): return self.name + +class AppletHostDeployment(JMSBaseModel): + host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting')) + status = models.CharField(max_length=16, default='', verbose_name=_('Status')) + date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) + date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + + def start(self, **kwargs): + from ...automations.deploy_applet_host import DeployAppletHostManager + manager = DeployAppletHostManager(self) + manager.run(**kwargs) diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 802b0a304..aad7e10ae 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -5,13 +5,13 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField from common.validators import ProjectUniqueValidator from assets.models import Platform from assets.serializers import HostSerializer -from ..models import Applet, AppletPublication, AppletHost +from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment __all__ = [ 'AppletSerializer', 'AppletPublicationSerializer', - 'AppletHostSerializer', - 'AppletUploadSerializer' + 'AppletHostSerializer', 'AppletHostDeploymentSerializer', + 'AppletUploadSerializer', ] @@ -85,3 +85,13 @@ class AppletHostSerializer(HostSerializer): validators.append(uniq_validator) return validators + +class AppletHostDeploymentSerializer(serializers.ModelSerializer): + class Meta: + model = AppletHostDeployment + fields_mini = ['id', 'host', 'status'] + read_only_fields = [ + 'status', 'date_created', 'date_updated', + 'date_start', 'date_finished' + ] + fields = fields_mini + ['comment'] + read_only_fields diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index e05d673e5..396369d16 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -12,9 +12,14 @@ from django.core.files.storage import default_storage from common.utils import get_log_keep_day from ops.celery.decorator import ( - register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic + register_as_period_task, after_app_ready_start, + after_app_shutdown_clean_periodic ) -from .models import Status, Session, Command, Task, AppletHost +from .models import ( + Status, Session, Command, Task, AppletHost, + AppletHostDeployment +) +from orgs.utils import tmp_to_builtin_org from .backends import server_replay_storage from .utils import find_session_replay_local @@ -84,16 +89,19 @@ def upload_session_replay_to_external_storage(session_id): if not session: logger.error(f'Session db item not found: {session_id}') return + local_path, foobar = find_session_replay_local(session) if not local_path: logger.error(f'Session replay not found, may be upload error: {local_path}') return + abs_path = default_storage.path(local_path) remote_path = session.get_relative_path_by_local_path(abs_path) ok, err = server_replay_storage.upload(abs_path, remote_path) if not ok: logger.error(f'Session replay upload to external error: {err}') return + try: default_storage.delete(local_path) except: @@ -103,5 +111,6 @@ def upload_session_replay_to_external_storage(session_id): @shared_task def run_applet_host_deployment(did): - host = AppletHost.objects.get(id=did) - host.deploy() + with tmp_to_builtin_org(system=1): + deployment = AppletHostDeployment.objects.get(id=did) + deployment.start() diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 1d369cc52..9a573acc5 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -27,6 +27,7 @@ router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') router.register(r'applets', api.AppletViewSet, 'applet') router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication') +router.register(r'applet-host-deployments', api.AppletHostDeploymentViewSet, 'applet-host-deployment') urlpatterns = [ From 8231f727c2c3f84bc8aff739c344ae11b9d354d9 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 1 Nov 2022 15:04:13 +0800 Subject: [PATCH 263/488] perf: history account --- apps/assets/api/account/account.py | 24 +++++++++++++++--------- apps/assets/models/account.py | 2 +- apps/assets/urls/api_urls.py | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 31aaccb96..3275dc67d 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -1,6 +1,6 @@ from rest_framework.decorators import action from rest_framework.response import Response -from rest_framework.generics import CreateAPIView, get_object_or_404 +from rest_framework.generics import CreateAPIView, ListAPIView from orgs.mixins.api import OrgBulkModelViewSet from rbac.permissions import RBACPermission @@ -13,7 +13,7 @@ from assets.filters import AccountFilterSet from assets.tasks.account_connectivity import test_accounts_connectivity_manual from assets import serializers -__all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI'] +__all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI', 'AccountHistoriesSecretAPI'] class AccountViewSet(OrgBulkModelViewSet): @@ -42,7 +42,6 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): """ serializer_classes = { 'default': serializers.AccountSecretSerializer, - 'histories': serializers.AccountHistorySerializer, } http_method_names = ['get', 'options'] # Todo: 记得打开 @@ -50,14 +49,21 @@ class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): rbac_perms = { 'list': 'assets.view_accountsecret', 'retrieve': 'assets.view_accountsecret', - 'histories': ['assets.view_accountsecret'], } - @action(methods=['get'], detail=True, url_path='histories') - def histories(self, request, *args, **kwargs): - account = get_object_or_404(self.get_queryset(), **kwargs) - self.queryset = account.history.all() - return super().list(request, *args, **kwargs) + +class AccountHistoriesSecretAPI(RecordViewLogMixin, ListAPIView): + model = Account.history.model + serializer_class = serializers.AccountHistorySerializer + http_method_names = ['get', 'options'] + # Todo: 记得打开 + # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] + rbac_perms = { + 'list': 'assets.view_accountsecret', + } + + def get_queryset(self): + return self.model.objects.filter(id=self.kwargs.get('pk')) class AccountTaskCreateAPI(CreateAPIView): diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 70ddc81b0..c2bb40a99 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -71,7 +71,7 @@ class Account(AbsConnectivity, BaseAccount): return self.asset.platform def __str__(self): - return '{}@{}'.format(self.username, self.asset.name) + return '{}'.format(self.username) @classmethod def get_input_account(cls): diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 688319ef6..f1c286054 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -37,6 +37,7 @@ urlpatterns = [ path('assets//perm-user-groups//permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'), path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'), + path('account-secrets//histories/', api.AccountHistoriesSecretAPI.as_view(), name='account-secret-history'), path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'), path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), From e6d845ae5590cc7584fee6e65e63cbfa63a6906d Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 1 Nov 2022 16:18:46 +0800 Subject: [PATCH 264/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=A9=BA?= =?UTF-8?q?=E5=BA=93=20migrate=20=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/mixins/models.py | 2 +- apps/orgs/utils.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index 80674ddc6..0795edc2e 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -60,7 +60,7 @@ class OrgModelMixin(models.Model): sep = '@' def save(self, *args, **kwargs): - locking_org = getattr(self, 'locking_org', None) + locking_org = getattr(self, 'LOCKING_ORG', None) if locking_org: org = Organization.get_instance(locking_org) else: diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index a80c7854d..3c2509f6a 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -105,12 +105,11 @@ def tmp_to_builtin_org(system=0, default=0): def filter_org_queryset(queryset): locking_org = getattr(queryset.model, 'LOCKING_ORG', None) - if locking_org: - org = Organization.get_instance(locking_org) - else: - org = get_current_org() + org = get_current_org() - if org is None: + if locking_org: + kwargs = {'org_id': locking_org} + elif org is None: kwargs = {} elif org.is_root(): kwargs = {} From 3bacd626e831f92d3dc7b13ccd31d3ccb8048f10 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 1 Nov 2022 17:04:44 +0800 Subject: [PATCH 265/488] =?UTF-8?q?pref:=20=E5=9F=BA=E6=9C=AC=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 466 +++++++++++------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 440 ++++++++++------- apps/ops/signal_handlers.py | 6 +- apps/orgs/utils.py | 4 +- .../deploy_applet_host/__init__.py | 13 +- .../deploy_applet_host/playbook.yml | 27 +- .../migrations/0054_auto_20221027_1125.py | 2 +- .../migrations/0056_auto_20221101_1353.py | 23 + apps/terminal/models/applet/applet.py | 3 +- apps/terminal/models/applet/host.py | 4 +- apps/terminal/serializers/applet.py | 21 +- 13 files changed, 635 insertions(+), 382 deletions(-) create mode 100644 apps/terminal/migrations/0056_auto_20221101_1353.py diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index cbc8ff533..c95e1d78e 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0070188de11b8ace3a23cdf03ac803b50bf98beccef49b94bcfd0131fea8604 -size 119875 +oid sha256:c4c889e251a4de3161f462e042882ba3c4ab40eaf34799e2d49d4788ad961586 +size 119171 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 545f1231e..4b15fdcde 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-26 16:41+0800\n" +"POT-Creation-Date: 2022-11-01 15:30+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -32,7 +32,7 @@ msgstr "Acls" #: assets/serializers/platform.py:104 ops/mixin.py:20 ops/models/playbook.py:9 #: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/applet/applet.py:25 terminal/models/component/endpoint.py:11 +#: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 #: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 #: terminal/models/component/terminal.py:100 users/forms/profile.py:33 @@ -51,7 +51,7 @@ msgstr "優先順位" msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" -#: acls/models/base.py:31 authentication/models.py:22 +#: acls/models/base.py:31 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:74 terminal/models/session/sharing.py:28 #: tickets/const.py:38 @@ -59,17 +59,17 @@ msgid "Active" msgstr "アクティブ" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:100 assets/models/automations/base.py:25 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:26 #: assets/models/backup.py:30 assets/models/base.py:66 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 #: assets/models/platform.py:73 ops/models/playbook.py:11 -#: ops/models/playbook.py:25 orgs/models.py:73 +#: ops/models/playbook.py:25 orgs/models.py:74 #: perms/models/asset_permission.py:84 rbac/models/role.py:37 -#: settings/models.py:38 terminal/models/applet/applet.py:33 -#: terminal/models/applet/applet.py:62 terminal/models/applet/host.py:12 -#: terminal/models/applet/host.py:28 terminal/models/component/endpoint.py:24 +#: settings/models.py:38 terminal/models/applet/applet.py:28 +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:34 +#: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 #: terminal/models/component/terminal.py:114 tickets/models/comment.py:32 @@ -96,8 +96,9 @@ msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:62 audits/models.py:87 authentication/models.py:55 -#: authentication/models.py:72 perms/models/asset_permission.py:58 +#: audits/models.py:62 audits/models.py:87 +#: authentication/models/connection_token.py:22 +#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:58 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 @@ -129,7 +130,7 @@ msgid "Login acl" msgstr "ログインacl" #: acls/models/login_asset_acl.py:21 assets/models/account.py:57 -#: authentication/models.py:82 ops/models/base.py:18 +#: authentication/models/connection_token.py:33 ops/models/base.py:18 #: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" @@ -139,7 +140,7 @@ msgstr "アカウント" #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:58 assets/serializers/label.py:30 -#: audits/models.py:39 authentication/models.py:77 +#: audits/models.py:39 authentication/models/connection_token.py:26 #: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 #: terminal/models/session/session.py:32 terminal/notifications.py:90 @@ -165,7 +166,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 #: assets/models/base.py:60 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:245 +#: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: users/forms/profile.py:32 users/models/user.py:663 @@ -250,9 +251,9 @@ msgid "Category" msgstr "カテゴリ" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:23 assets/models/cmd_filter.py:74 +#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:74 #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 -#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:29 +#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20 #: tickets/models/comment.py:26 tickets/models/flow.py:57 @@ -296,7 +297,7 @@ msgstr "削除に失敗し、ノードにアセットが含まれています。 msgid "App assets" msgstr "アプリ資産" -#: assets/automations/base/manager.py:74 +#: assets/automations/base/manager.py:122 #, fuzzy #| msgid "Disabled" msgid "{} disabled" @@ -312,7 +313,7 @@ msgstr "不明" msgid "Ok" msgstr "OK" -#: assets/const/account.py:8 audits/models.py:118 +#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:18 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 msgid "Failed" @@ -343,12 +344,12 @@ msgid "SSH key" msgstr "SSHキー" #: assets/const/account.py:14 assets/models/base.py:55 -#: authentication/models.py:38 +#: authentication/models/access_key.py:31 msgid "Access key" msgstr "アクセスキー" #: assets/const/account.py:15 assets/models/_user.py:38 -#: assets/models/base.py:56 authentication/models.py:53 +#: assets/models/base.py:56 authentication/models/sso_token.py:13 msgid "Token" msgstr "トークン" @@ -380,8 +381,10 @@ msgstr "秘密を改める" msgid "Verify account" msgstr "パスワード/キーの確認" -#: assets/const/automation.py:18 rbac/tree.py:53 -msgid "Gather account" +#: assets/const/automation.py:18 +#, fuzzy +#| msgid "Gather account" +msgid "Gather accounts" msgstr "アカウントを集める" #: assets/const/automation.py:22 @@ -414,8 +417,8 @@ msgid "Replace (The key generated by JumpServer) " msgstr "置換(JumpServerによって生成された鍵)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:60 -#: terminal/models/applet/host.py:11 terminal/models/component/endpoint.py:12 +#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:56 +#: terminal/models/component/endpoint.py:12 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "ホスト" @@ -496,11 +499,11 @@ msgstr "SSH秘密鍵" msgid "SSH public key" msgstr "SSHパブリックキー" -#: assets/models/_user.py:41 assets/models/automations/base.py:86 +#: assets/models/_user.py:41 assets/models/automations/base.py:87 #: assets/models/base.py:67 assets/models/domain.py:26 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 -#: orgs/models.py:72 perms/models/asset_permission.py:82 +#: orgs/models.py:73 perms/models/asset_permission.py:82 #: users/models/group.py:18 users/models/user.py:927 msgid "Date created" msgstr "作成された日付" @@ -525,7 +528,8 @@ msgid "Username same with user" msgstr "ユーザーと同じユーザー名" #: assets/models/_user.py:48 assets/models/domain.py:67 -#: terminal/models/applet/applet.py:31 terminal/serializers/session.py:18 +#: authentication/models/connection_token.py:29 +#: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 #: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "プロトコル" @@ -585,7 +589,7 @@ msgid "Su from" msgstr "から切り替え" #: assets/models/account.py:53 settings/serializers/auth/cas.py:18 -#: terminal/models/applet/applet.py:27 +#: terminal/models/applet/applet.py:22 msgid "Version" msgstr "バージョン" @@ -630,16 +634,16 @@ msgstr "プラットフォーム" msgid "Domain" msgstr "ドメイン" -#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 +#: assets/models/asset/common.py:97 assets/models/automations/base.py:19 #: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "ノード" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:24 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:25 #: assets/models/cmd_filter.py:39 assets/models/domain.py:70 -#: assets/models/label.py:21 terminal/models/applet/applet.py:30 +#: assets/models/label.py:21 terminal/models/applet/applet.py:25 #: users/serializers/user.py:147 msgid "Is active" msgstr "アクティブです。" @@ -706,28 +710,29 @@ msgstr "パスワードルール" msgid "Submit selector" msgstr "" -#: assets/models/automations/base.py:16 assets/models/cmd_filter.py:38 +#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 #: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 #: rbac/tree.py:37 msgid "Accounts" msgstr "アカウント" -#: assets/models/automations/base.py:21 assets/serializers/domain.py:29 +#: assets/models/automations/base.py:22 assets/serializers/domain.py:29 #: ops/models/base.py:17 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "資産" -#: assets/models/automations/base.py:76 assets/models/automations/base.py:83 +#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 #, fuzzy #| msgid "Automatic managed" msgid "Automation task" msgstr "自動管理" -#: assets/models/automations/base.py:87 assets/models/backup.py:77 +#: assets/models/automations/base.py:88 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/session/session.py:43 +#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:32 +#: terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:21 #: xpack/plugins/change_auth_plan/models/base.py:108 @@ -736,63 +741,65 @@ msgstr "自動管理" msgid "Date start" msgstr "開始日" -#: assets/models/automations/base.py:88 -#: assets/models/automations/change_secret.py:59 ops/models/base.py:55 +#: assets/models/automations/base.py:89 +#: assets/models/automations/change_secret.py:58 ops/models/base.py:55 +#: terminal/models/applet/host.py:33 msgid "Date finished" msgstr "終了日" -#: assets/models/automations/base.py:90 +#: assets/models/automations/base.py:91 #, fuzzy #| msgid "Relation snapshot" msgid "Automation snapshot" msgstr "製造オーダスナップショット" -#: assets/models/automations/base.py:94 assets/models/backup.py:88 +#: assets/models/automations/base.py:95 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "トリガーモード" -#: assets/models/automations/base.py:98 +#: assets/models/automations/base.py:99 #, fuzzy #| msgid "Command execution" msgid "Automation task execution" msgstr "コマンド実行" -#: assets/models/automations/change_secret.py:16 assets/models/base.py:62 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:62 #, fuzzy #| msgid "Secret key" msgid "Secret type" msgstr "秘密キー" -#: assets/models/automations/change_secret.py:20 +#: assets/models/automations/change_secret.py:19 #, fuzzy #| msgid "SSH Key strategy" msgid "Secret strategy" msgstr "SSHキー戦略" -#: assets/models/automations/change_secret.py:22 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:64 -#: assets/serializers/account/base.py:17 authentication/models.py:66 -#: authentication/models.py:246 +#: assets/models/automations/change_secret.py:21 +#: assets/models/automations/change_secret.py:56 assets/models/base.py:64 +#: assets/serializers/account/base.py:17 +#: authentication/models/connection_token.py:34 +#: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "ひみつ" -#: assets/models/automations/change_secret.py:23 +#: assets/models/automations/change_secret.py:22 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" msgstr "パスワードルール" -#: assets/models/automations/change_secret.py:26 +#: assets/models/automations/change_secret.py:25 #, fuzzy #| msgid "SSH Key strategy" msgid "SSH key change strategy" msgstr "SSHキー戦略" -#: assets/models/automations/change_secret.py:28 assets/models/backup.py:28 +#: assets/models/automations/change_secret.py:27 assets/models/backup.py:28 #: assets/serializers/account/backup.py:28 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 @@ -800,31 +807,31 @@ msgstr "SSHキー戦略" msgid "Recipient" msgstr "受信者" -#: assets/models/automations/change_secret.py:35 +#: assets/models/automations/change_secret.py:34 #, fuzzy #| msgid "Change auth" msgid "Change secret automation" msgstr "秘密を改める" -#: assets/models/automations/change_secret.py:56 +#: assets/models/automations/change_secret.py:55 #, fuzzy #| msgid "Secret" msgid "Old secret" msgstr "ひみつ" -#: assets/models/automations/change_secret.py:58 +#: assets/models/automations/change_secret.py:57 #, fuzzy #| msgid "Date start" msgid "Date started" msgstr "開始日" -#: assets/models/automations/change_secret.py:61 +#: assets/models/automations/change_secret.py:60 common/const/choices.py:19 #, fuzzy #| msgid "WeCom Error" msgid "Error" msgstr "企業微信エラー" -#: assets/models/automations/change_secret.py:64 +#: assets/models/automations/change_secret.py:63 #, fuzzy #| msgid "Change auth" msgid "Change secret record" @@ -836,22 +843,28 @@ msgstr "秘密を改める" msgid "Discovery account automation" msgstr "パスワード/キーの確認" +#: assets/models/automations/gather_accounts.py:15 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather asset accounts" +msgstr "資産ユーザーの収集" + #: assets/models/automations/gather_facts.py:15 #, fuzzy #| msgid "Gather assets users" msgid "Gather asset facts" msgstr "資産ユーザーの収集" -#: assets/models/automations/push_account.py:8 +#: assets/models/automations/push_account.py:16 #, fuzzy -#| msgid "Automatic managed" -msgid "Push automation" -msgstr "自動管理" +#| msgid "Is service account" +msgid "Push asset account" +msgstr "サービスアカウントです" -#: assets/models/automations/verify_secret.py:8 +#: assets/models/automations/verify_account.py:15 #, fuzzy #| msgid "Verify auth" -msgid "Verify account automation" +msgid "Verify asset account" msgstr "パスワード/キーの確認" #: assets/models/backup.py:38 assets/models/backup.py:96 @@ -893,7 +906,7 @@ msgstr "アカウントバックアップの実行" msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:32 authentication/models.py:248 +#: assets/models/base.py:32 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "確認済みの日付" @@ -1352,7 +1365,6 @@ msgid "Category display" msgstr "カテゴリ表示" #: assets/serializers/mixin.py:10 audits/serializers.py:27 -#: authentication/serializers/connection_token.py:20 #: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 msgid "Type display" msgstr "タイプ表示" @@ -1527,7 +1539,7 @@ msgstr "操作" msgid "Filename" msgstr "ファイル名" -#: audits/models.py:43 audits/models.py:117 +#: audits/models.py:43 audits/models.py:117 common/const/choices.py:17 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" @@ -1607,8 +1619,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 ops/models/base.py:48 -#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:15 -#: terminal/models/applet/host.py:27 terminal/models/component/status.py:33 +#: terminal/models/applet/applet.py:57 terminal/models/applet/host.py:19 +#: terminal/models/applet/host.py:31 terminal/models/component/status.py:33 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 #: xpack/plugins/cloud/models.py:223 msgid "Status" @@ -1672,7 +1684,7 @@ msgstr "本を飛ばす" msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:56 authentication/models.py:252 +#: audits/signal_handlers.py:56 authentication/models/temp_token.py:16 msgid "Temporary token" msgstr "仮パスワード" @@ -1825,7 +1837,7 @@ msgstr "" msgid "Invalid token or cache refreshed." msgstr "無効なトークンまたはキャッシュの更新。" -#: authentication/backends/oauth2/backends.py:155 authentication/models.py:143 +#: authentication/backends/oauth2/backends.py:155 msgid "User invalid, disabled or expired" msgstr "ユーザーが無効、無効、または期限切れです" @@ -2088,91 +2100,76 @@ msgstr "MFAタイプ ({}) が有効になっていない" msgid "Please change your password" msgstr "パスワードを変更してください" -#: authentication/models.py:45 -msgid "Private Token" -msgstr "プライベートトークン" +#: authentication/models/connection_token.py:31 +#: rbac/serializers/rolebinding.py:21 +msgid "User display" +msgstr "ユーザー表示" -#: authentication/models.py:54 -msgid "Expired" -msgstr "期限切れ" +#: authentication/models/connection_token.py:32 +msgid "Asset display" +msgstr "アセット名" -#: authentication/models.py:58 -msgid "SSO token" -msgstr "SSO token" - -#: authentication/models.py:68 authentication/models.py:249 -#: perms/models/asset_permission.py:79 +#: authentication/models/connection_token.py:36 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:79 #: tickets/models/ticket/apply_application.py:29 #: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 msgid "Date expired" msgstr "期限切れの日付" -#: authentication/models.py:75 rbac/serializers/rolebinding.py:21 -msgid "User display" -msgstr "ユーザー表示" - -#: authentication/models.py:80 -msgid "Asset display" -msgstr "アセット名" - -#: authentication/models.py:85 -#, fuzzy -#| msgid "Action display" -msgid "Account display" -msgstr "アクション表示" - -#: authentication/models.py:89 +#: authentication/models/connection_token.py:41 msgid "Connection token" msgstr "接続トークン" -#: authentication/models.py:91 +#: authentication/models/connection_token.py:43 msgid "Can view connection token secret" msgstr "接続トークンの秘密を表示できます" -#: authentication/models.py:134 +#: authentication/models/connection_token.py:82 msgid "Connection token expired at: {}" msgstr "接続トークンの有効期限: {}" -#: authentication/models.py:139 -msgid "User not exists" -msgstr "ユーザーは存在しません" +#: authentication/models/connection_token.py:86 +msgid "No user or invalid user" +msgstr "" -#: authentication/models.py:148 -msgid "System user not exists" -msgstr "システムユーザーが存在しません" - -#: authentication/models.py:154 -msgid "Asset not exists" -msgstr "アセットが存在しません" - -#: authentication/models.py:158 -msgid "Asset inactive" +#: authentication/models/connection_token.py:90 +#, fuzzy +#| msgid "Asset inactive" +msgid "No asset or inactive asset" msgstr "アセットがアクティブ化されていません" -#: authentication/models.py:165 +#: authentication/models/connection_token.py:94 +#, fuzzy +#| msgid "Login acl" +msgid "No account" +msgstr "ログインacl" + +#: authentication/models/connection_token.py:103 msgid "User has no permission to access asset or permission expired" msgstr "" "ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて" "います" -#: authentication/models.py:173 -msgid "Application not exists" -msgstr "アプリが存在しません" - -#: authentication/models.py:180 -msgid "User has no permission to access application or permission expired" -msgstr "" -"ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れてい" -"ます" - -#: authentication/models.py:247 -msgid "Verified" -msgstr "確認済み" - -#: authentication/models.py:268 +#: authentication/models/connection_token.py:145 msgid "Super connection token" msgstr "スーパー接続トークン" +#: authentication/models/private_token.py:9 +msgid "Private Token" +msgstr "プライベートトークン" + +#: authentication/models/sso_token.py:14 +msgid "Expired" +msgstr "期限切れ" + +#: authentication/models/sso_token.py:18 +msgid "SSO token" +msgstr "SSO token" + +#: authentication/models/temp_token.py:11 +msgid "Verified" +msgstr "確認済み" + #: authentication/notifications.py:19 msgid "Different city login reminder" msgstr "異なる都市ログインのリマインダー" @@ -2181,19 +2178,15 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" -#: authentication/serializers/connection_token.py:21 +#: authentication/serializers/connection_token.py:20 #: xpack/plugins/cloud/models.py:36 msgid "Validity" msgstr "有効性" -#: authentication/serializers/connection_token.py:22 +#: authentication/serializers/connection_token.py:21 msgid "Expired time" msgstr "期限切れ時間" -#: authentication/serializers/connection_token.py:68 -msgid "Asset or application required" -msgstr "アセットまたはアプリが必要" - #: authentication/serializers/token.py:79 perms/serializers/permission.py:60 #: perms/serializers/permission.py:87 users/serializers/user.py:148 msgid "Is valid" @@ -2282,7 +2275,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 ops/tasks.py:146 ops/tasks.py:152 ops/tasks.py:155 +#: jumpserver/conf.py:390 ops/tasks.py:147 ops/tasks.py:153 ops/tasks.py:156 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2583,6 +2576,20 @@ msgstr "手動トリガー" msgid "Timing trigger" msgstr "タイミングトリガー" +#: common/const/choices.py:15 tickets/const.py:29 tickets/const.py:37 +msgid "Pending" +msgstr "未定" + +#: common/const/choices.py:16 +msgid "Running" +msgstr "" + +#: common/const/choices.py:20 +#, fuzzy +#| msgid "Cancel" +msgid "Canceled" +msgstr "キャンセル" + #: common/db/encoder.py:11 msgid "ugettext_lazy" msgstr "ugettext_lazy" @@ -2841,17 +2848,17 @@ msgstr "サイトメッセージ" msgid "No account available" msgstr "利用できないアカウント" -#: ops/ansible/inventory.py:173 +#: ops/ansible/inventory.py:175 #, fuzzy #| msgid "User disabled." msgid "Ansible disabled" msgstr "ユーザーが無効になりました。" -#: ops/ansible/inventory.py:189 +#: ops/ansible/inventory.py:191 msgid "Skip hosts below:" msgstr "" -#: ops/api/celery.py:61 ops/api/celery.py:76 +#: ops/api/celery.py:63 ops/api/celery.py:78 msgid "Waiting task start" msgstr "タスク開始待ち" @@ -2916,7 +2923,7 @@ msgstr "パターン" msgid "Module" msgstr "" -#: ops/models/adhoc.py:20 ops/models/celery.py:15 +#: ops/models/adhoc.py:20 ops/models/celery.py:45 #: terminal/models/component/task.py:17 msgid "Args" msgstr "アルグ" @@ -2960,16 +2967,16 @@ msgstr "結果" msgid "Summary" msgstr "" -#: ops/models/celery.py:16 terminal/models/component/task.py:18 +#: ops/models/celery.py:46 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "クワーグ" -#: ops/models/celery.py:17 tickets/models/comment.py:13 +#: ops/models/celery.py:47 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 msgid "State" msgstr "状態" -#: ops/models/celery.py:18 terminal/models/session/sharing.py:111 +#: ops/models/celery.py:48 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "終了" @@ -2994,7 +3001,8 @@ msgstr "" msgid "Template" msgstr "テンプレート" -#: ops/models/playbook.py:38 terminal/models/component/task.py:26 +#: ops/models/playbook.py:38 ops/signal_handlers.py:63 +#: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "タスク" @@ -3033,23 +3041,23 @@ msgstr "{max_threshold}%: => {value} を超える使用メモリ" msgid "CPU load more than {max_threshold}: => {value}" msgstr "{max_threshold} を超えるCPUロード: => {value}" -#: ops/tasks.py:33 +#: ops/tasks.py:34 #, fuzzy #| msgid "Run asset" msgid "Run ansible task" msgstr "アセットの実行" -#: ops/tasks.py:57 +#: ops/tasks.py:58 #, fuzzy #| msgid "Run command" msgid "Run ansible command" msgstr "実行コマンド" -#: ops/tasks.py:79 +#: ops/tasks.py:80 msgid "Clean task history period" msgstr "クリーンなタスク履歴期間" -#: ops/tasks.py:92 +#: ops/tasks.py:93 msgid "Clean celery log period" msgstr "きれいなセロリログ期間" @@ -3080,7 +3088,7 @@ msgstr "組織のリソース ({}) は削除できません" msgid "App organizations" msgstr "アプリ組織" -#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:87 +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 #: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 @@ -3091,23 +3099,29 @@ msgstr "組織" msgid "Org name" msgstr "組織名" -#: orgs/models.py:79 +#: orgs/models.py:72 +#, fuzzy +#| msgid "Built-in" +msgid "Builtin" +msgstr "内蔵" + +#: orgs/models.py:80 msgid "GLOBAL" msgstr "グローバル組織" -#: orgs/models.py:81 +#: orgs/models.py:82 msgid "DEFAULT" msgstr "" -#: orgs/models.py:83 +#: orgs/models.py:84 msgid "SYSTEM" msgstr "" -#: orgs/models.py:89 +#: orgs/models.py:90 msgid "Can view root org" msgstr "グローバル組織を表示できます" -#: orgs/models.py:90 +#: orgs/models.py:91 msgid "Can view all joined org" msgstr "参加しているすべての組織を表示できます" @@ -3385,7 +3399,7 @@ msgstr "パーマ" msgid "Scope display" msgstr "スコープ表示" -#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:26 +#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 msgid "Display name" msgstr "表示名" @@ -3433,6 +3447,10 @@ msgstr "クラウドインポート" msgid "Backup account" msgstr "バックアップアカウント" +#: rbac/tree.py:53 +msgid "Gather account" +msgstr "アカウントを集める" + #: rbac/tree.py:54 msgid "App change auth" msgstr "応用改密" @@ -4900,51 +4918,55 @@ msgstr "一括作成非サポート" msgid "Storage is invalid" msgstr "ストレージが無効です" -#: terminal/models/applet/applet.py:21 -#, fuzzy -#| msgid "Manually input" -msgid "Manual" -msgstr "手動入力" - -#: terminal/models/applet/applet.py:22 -msgid "Git" -msgstr "" - #: terminal/models/applet/applet.py:23 #, fuzzy -#| msgid "Remote app" -msgid "Remote gzip" -msgstr "リモートアプリ" - -#: terminal/models/applet/applet.py:28 -#, fuzzy #| msgid "Auth url" msgid "Author" msgstr "認証アドレス" -#: terminal/models/applet/applet.py:32 +#: terminal/models/applet/applet.py:27 msgid "Tags" msgstr "" -#: terminal/models/applet/applet.py:59 terminal/models/applet/host.py:17 +#: terminal/models/applet/applet.py:29 terminal/serializers/storage.py:157 +msgid "Hosts" +msgstr "ホスト" + +#: terminal/models/applet/applet.py:55 terminal/models/applet/host.py:21 #, fuzzy #| msgid "Apply assets" msgid "Applet" msgstr "資産の適用" -#: terminal/models/applet/host.py:13 +#: terminal/models/applet/host.py:14 #, fuzzy #| msgid "Verify auth" msgid "Account automation" msgstr "パスワード/キーの確認" -#: terminal/models/applet/host.py:14 +#: terminal/models/applet/host.py:15 terminal/serializers/applet.py:66 +#, fuzzy +#| msgid "More login options" +msgid "Deploy options" +msgstr "その他のログインオプション" + +#: terminal/models/applet/host.py:16 +msgid "Inited" +msgstr "" + +#: terminal/models/applet/host.py:17 +#, fuzzy +#| msgid "Date finished" +msgid "Date inited" +msgstr "終了日" + +#: terminal/models/applet/host.py:18 #, fuzzy #| msgid "Date sync" msgid "Date synced" msgstr "日付の同期" -#: terminal/models/applet/host.py:26 +#: terminal/models/applet/host.py:30 #, fuzzy #| msgid "Host" msgid "Hosting" @@ -5178,6 +5200,48 @@ msgstr "一括危険コマンド警告" msgid "Icon" msgstr "" +#: terminal/serializers/applet.py:53 +#, fuzzy +#| msgid "Phone not set" +msgid "Not set" +msgstr "電話が設定されていない" + +#: terminal/serializers/applet.py:54 +#, fuzzy +#| msgid "Session" +msgid "Per Session" +msgstr "セッション" + +#: terminal/serializers/applet.py:55 +msgid "Per Device" +msgstr "" + +#: terminal/serializers/applet.py:57 +#, fuzzy +#| msgid "License" +msgid "RDS Licensing" +msgstr "ライセンス" + +#: terminal/serializers/applet.py:58 +msgid "RDS License Server" +msgstr "" + +#: terminal/serializers/applet.py:59 +msgid "RDS Licensing Mode" +msgstr "" + +#: terminal/serializers/applet.py:60 +msgid "RDS fSingleSessionPerUser" +msgstr "" + +#: terminal/serializers/applet.py:61 +msgid "RDS Max Disconnection Time" +msgstr "" + +#: terminal/serializers/applet.py:62 +msgid "RDS Remote App Logoff Time Limit" +msgstr "" + #: terminal/serializers/endpoint.py:12 msgid "Oracle port" msgstr "" @@ -5279,10 +5343,6 @@ msgstr "ホスト無効" msgid "Port invalid" msgstr "ポートが無効" -#: terminal/serializers/storage.py:157 -msgid "Hosts" -msgstr "ホスト" - #: terminal/serializers/storage.py:160 msgid "Index by date" msgstr "日付による索引付け" @@ -5339,10 +5399,6 @@ msgstr "拒否" msgid "Reopen" msgstr "" -#: tickets/const.py:29 tickets/const.py:37 -msgid "Pending" -msgstr "未定" - #: tickets/const.py:32 tickets/const.py:39 msgid "Closed" msgstr "クローズ" @@ -6963,6 +7019,51 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#, fuzzy +#~| msgid "Automatic managed" +#~ msgid "Push automation" +#~ msgstr "自動管理" + +#, fuzzy +#~| msgid "Verify auth" +#~ msgid "Verify account automation" +#~ msgstr "パスワード/キーの確認" + +#, fuzzy +#~| msgid "Action display" +#~ msgid "Account display" +#~ msgstr "アクション表示" + +#~ msgid "User not exists" +#~ msgstr "ユーザーは存在しません" + +#~ msgid "System user not exists" +#~ msgstr "システムユーザーが存在しません" + +#~ msgid "Asset not exists" +#~ msgstr "アセットが存在しません" + +#~ msgid "Application not exists" +#~ msgstr "アプリが存在しません" + +#~ msgid "User has no permission to access application or permission expired" +#~ msgstr "" +#~ "ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れて" +#~ "います" + +#~ msgid "Asset or application required" +#~ msgstr "アセットまたはアプリが必要" + +#, fuzzy +#~| msgid "Manually input" +#~ msgid "Manual" +#~ msgstr "手動入力" + +#, fuzzy +#~| msgid "Remote app" +#~ msgid "Remote gzip" +#~ msgstr "リモートアプリ" + #, fuzzy #~| msgid "Verify code" #~ msgid "Verify secret" @@ -6973,11 +7074,6 @@ msgstr "コミュニティ版" #~ msgid "Secret types" #~ msgstr "秘密キー" -#, fuzzy -#~| msgid "Gather account" -#~ msgid "Gather accounts" -#~ msgstr "アカウントを集める" - #, fuzzy #~| msgid "Hostname strategy" #~ msgid "Automation strategy" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index eada29942..6e116b000 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:97b2aa050b5846dd3399d45286d34f282acb55dca3e2b18a79c16b9d34ec938f -size 103818 +oid sha256:08529907ac3879f60c2026f91e7ba3f48a3a7d288f7b29cd35c0f73bc3999c21 +size 103630 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index aeda480a1..013be87be 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-10-26 16:41+0800\n" +"POT-Creation-Date: 2022-11-01 15:30+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -31,7 +31,7 @@ msgstr "访问控制" #: assets/serializers/platform.py:104 ops/mixin.py:20 ops/models/playbook.py:9 #: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/applet/applet.py:25 terminal/models/component/endpoint.py:11 +#: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 #: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 #: terminal/models/component/terminal.py:100 users/forms/profile.py:33 @@ -50,7 +50,7 @@ msgstr "优先级" msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" -#: acls/models/base.py:31 authentication/models.py:22 +#: acls/models/base.py:31 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:74 terminal/models/session/sharing.py:28 #: tickets/const.py:38 @@ -58,17 +58,17 @@ msgid "Active" msgstr "激活中" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:100 assets/models/automations/base.py:25 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:26 #: assets/models/backup.py:30 assets/models/base.py:66 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 #: assets/models/platform.py:73 ops/models/playbook.py:11 -#: ops/models/playbook.py:25 orgs/models.py:73 +#: ops/models/playbook.py:25 orgs/models.py:74 #: perms/models/asset_permission.py:84 rbac/models/role.py:37 -#: settings/models.py:38 terminal/models/applet/applet.py:33 -#: terminal/models/applet/applet.py:62 terminal/models/applet/host.py:12 -#: terminal/models/applet/host.py:28 terminal/models/component/endpoint.py:24 +#: settings/models.py:38 terminal/models/applet/applet.py:28 +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:34 +#: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 #: terminal/models/component/terminal.py:114 tickets/models/comment.py:32 @@ -95,8 +95,9 @@ msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:62 audits/models.py:87 authentication/models.py:55 -#: authentication/models.py:72 perms/models/asset_permission.py:58 +#: audits/models.py:62 audits/models.py:87 +#: authentication/models/connection_token.py:22 +#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:58 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 @@ -128,7 +129,7 @@ msgid "Login acl" msgstr "登录访问控制" #: acls/models/login_asset_acl.py:21 assets/models/account.py:57 -#: authentication/models.py:82 ops/models/base.py:18 +#: authentication/models/connection_token.py:33 ops/models/base.py:18 #: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" @@ -138,7 +139,7 @@ msgstr "账号" #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:58 assets/serializers/label.py:30 -#: audits/models.py:39 authentication/models.py:77 +#: audits/models.py:39 authentication/models/connection_token.py:26 #: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 #: terminal/models/session/session.py:32 terminal/notifications.py:90 @@ -164,7 +165,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 #: assets/models/base.py:60 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models.py:245 +#: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: users/forms/profile.py:32 users/models/user.py:663 @@ -245,9 +246,9 @@ msgid "Category" msgstr "类别" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:23 assets/models/cmd_filter.py:74 +#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:74 #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 -#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:29 +#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20 #: tickets/models/comment.py:26 tickets/models/flow.py:57 @@ -291,7 +292,7 @@ msgstr "删除失败,节点包含资产" msgid "App assets" msgstr "资产管理" -#: assets/automations/base/manager.py:74 +#: assets/automations/base/manager.py:122 msgid "{} disabled" msgstr "{} 已禁用" @@ -305,7 +306,7 @@ msgstr "未知" msgid "Ok" msgstr "成功" -#: assets/const/account.py:8 audits/models.py:118 +#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:18 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 msgid "Failed" @@ -334,12 +335,12 @@ msgid "SSH key" msgstr "SSH 密钥" #: assets/const/account.py:14 assets/models/base.py:55 -#: authentication/models.py:38 +#: authentication/models/access_key.py:31 msgid "Access key" msgstr "Access key" #: assets/const/account.py:15 assets/models/_user.py:38 -#: assets/models/base.py:56 authentication/models.py:53 +#: assets/models/base.py:56 authentication/models/sso_token.py:13 msgid "Token" msgstr "Token" @@ -363,8 +364,8 @@ msgstr "改密" msgid "Verify account" msgstr "验证密钥" -#: assets/const/automation.py:18 rbac/tree.py:53 -msgid "Gather account" +#: assets/const/automation.py:18 +msgid "Gather accounts" msgstr "收集账号" #: assets/const/automation.py:22 @@ -397,8 +398,8 @@ msgid "Replace (The key generated by JumpServer) " msgstr "替换 (由 JumpServer 生成的密钥)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:60 -#: terminal/models/applet/host.py:11 terminal/models/component/endpoint.py:12 +#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:56 +#: terminal/models/component/endpoint.py:12 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" msgstr "主机" @@ -473,11 +474,11 @@ msgstr "SSH 密钥" msgid "SSH public key" msgstr "SSH 公钥" -#: assets/models/_user.py:41 assets/models/automations/base.py:86 +#: assets/models/_user.py:41 assets/models/automations/base.py:87 #: assets/models/base.py:67 assets/models/domain.py:26 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 -#: orgs/models.py:72 perms/models/asset_permission.py:82 +#: orgs/models.py:73 perms/models/asset_permission.py:82 #: users/models/group.py:18 users/models/user.py:927 msgid "Date created" msgstr "创建日期" @@ -502,7 +503,8 @@ msgid "Username same with user" msgstr "用户名与用户相同" #: assets/models/_user.py:48 assets/models/domain.py:67 -#: terminal/models/applet/applet.py:31 terminal/serializers/session.py:18 +#: authentication/models/connection_token.py:29 +#: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 #: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" msgstr "协议" @@ -560,7 +562,7 @@ msgid "Su from" msgstr "切换自" #: assets/models/account.py:53 settings/serializers/auth/cas.py:18 -#: terminal/models/applet/applet.py:27 +#: terminal/models/applet/applet.py:22 msgid "Version" msgstr "版本" @@ -603,16 +605,16 @@ msgstr "资产平台" msgid "Domain" msgstr "网域" -#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 +#: assets/models/asset/common.py:97 assets/models/automations/base.py:19 #: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "节点" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:24 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:25 #: assets/models/cmd_filter.py:39 assets/models/domain.py:70 -#: assets/models/label.py:21 terminal/models/applet/applet.py:30 +#: assets/models/label.py:21 terminal/models/applet/applet.py:25 #: users/serializers/user.py:147 msgid "Is active" msgstr "激活" @@ -673,26 +675,27 @@ msgstr "密码选择器" msgid "Submit selector" msgstr "提交按钮选择器" -#: assets/models/automations/base.py:16 assets/models/cmd_filter.py:38 +#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 #: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 #: rbac/tree.py:37 msgid "Accounts" msgstr "账号管理" -#: assets/models/automations/base.py:21 assets/serializers/domain.py:29 +#: assets/models/automations/base.py:22 assets/serializers/domain.py:29 #: ops/models/base.py:17 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" -#: assets/models/automations/base.py:76 assets/models/automations/base.py:83 +#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 msgid "Automation task" msgstr "自动化任务" -#: assets/models/automations/base.py:87 assets/models/backup.py:77 +#: assets/models/automations/base.py:88 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/session/session.py:43 +#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:32 +#: terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:21 #: xpack/plugins/change_auth_plan/models/base.py:108 @@ -701,53 +704,55 @@ msgstr "自动化任务" msgid "Date start" msgstr "开始日期" -#: assets/models/automations/base.py:88 -#: assets/models/automations/change_secret.py:59 ops/models/base.py:55 +#: assets/models/automations/base.py:89 +#: assets/models/automations/change_secret.py:58 ops/models/base.py:55 +#: terminal/models/applet/host.py:33 msgid "Date finished" msgstr "结束日期" -#: assets/models/automations/base.py:90 +#: assets/models/automations/base.py:91 msgid "Automation snapshot" msgstr "自动化快照" -#: assets/models/automations/base.py:94 assets/models/backup.py:88 +#: assets/models/automations/base.py:95 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "触发模式" -#: assets/models/automations/base.py:98 +#: assets/models/automations/base.py:99 msgid "Automation task execution" msgstr "自动化任务执行" -#: assets/models/automations/change_secret.py:16 assets/models/base.py:62 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:62 msgid "Secret type" msgstr "密文类型" -#: assets/models/automations/change_secret.py:20 +#: assets/models/automations/change_secret.py:19 msgid "Secret strategy" msgstr "密钥策略" -#: assets/models/automations/change_secret.py:22 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:64 -#: assets/serializers/account/base.py:17 authentication/models.py:66 -#: authentication/models.py:246 +#: assets/models/automations/change_secret.py:21 +#: assets/models/automations/change_secret.py:56 assets/models/base.py:64 +#: assets/serializers/account/base.py:17 +#: authentication/models/connection_token.py:34 +#: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "密钥" -#: assets/models/automations/change_secret.py:23 +#: assets/models/automations/change_secret.py:22 #: xpack/plugins/change_auth_plan/models/base.py:39 msgid "Password rules" msgstr "密码规则" -#: assets/models/automations/change_secret.py:26 +#: assets/models/automations/change_secret.py:25 msgid "SSH key change strategy" msgstr "SSH 密钥策略" -#: assets/models/automations/change_secret.py:28 assets/models/backup.py:28 +#: assets/models/automations/change_secret.py:27 assets/models/backup.py:28 #: assets/serializers/account/backup.py:28 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 @@ -755,23 +760,23 @@ msgstr "SSH 密钥策略" msgid "Recipient" msgstr "收件人" -#: assets/models/automations/change_secret.py:35 +#: assets/models/automations/change_secret.py:34 msgid "Change secret automation" msgstr "自动化改密" -#: assets/models/automations/change_secret.py:56 +#: assets/models/automations/change_secret.py:55 msgid "Old secret" msgstr "原来密码" -#: assets/models/automations/change_secret.py:58 +#: assets/models/automations/change_secret.py:57 msgid "Date started" msgstr "开始日期" -#: assets/models/automations/change_secret.py:61 +#: assets/models/automations/change_secret.py:60 common/const/choices.py:19 msgid "Error" msgstr "错误" -#: assets/models/automations/change_secret.py:64 +#: assets/models/automations/change_secret.py:63 msgid "Change secret record" msgstr "改密记录" @@ -779,17 +784,27 @@ msgstr "改密记录" msgid "Discovery account automation" msgstr "自动化账号发现" +#: assets/models/automations/gather_accounts.py:15 +#, fuzzy +#| msgid "Gather asset facts" +msgid "Gather asset accounts" +msgstr "收集资产信息" + #: assets/models/automations/gather_facts.py:15 msgid "Gather asset facts" msgstr "收集资产信息" -#: assets/models/automations/push_account.py:8 -msgid "Push automation" -msgstr "自动化推送" +#: assets/models/automations/push_account.py:16 +#, fuzzy +#| msgid "Is service account" +msgid "Push asset account" +msgstr "服务账号" -#: assets/models/automations/verify_secret.py:8 -msgid "Verify account automation" -msgstr "账号校验自动化" +#: assets/models/automations/verify_account.py:15 +#, fuzzy +#| msgid "Verify account" +msgid "Verify asset account" +msgstr "验证密钥" #: assets/models/backup.py:38 assets/models/backup.py:96 msgid "Account backup plan" @@ -830,7 +845,7 @@ msgstr "账号备份执行" msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:32 authentication/models.py:248 +#: assets/models/base.py:32 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "校验日期" @@ -1247,7 +1262,6 @@ msgid "Category display" msgstr "类别名称" #: assets/serializers/mixin.py:10 audits/serializers.py:27 -#: authentication/serializers/connection_token.py:20 #: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 msgid "Type display" msgstr "类型名称" @@ -1412,7 +1426,7 @@ msgstr "操作" msgid "Filename" msgstr "文件名" -#: audits/models.py:43 audits/models.py:117 +#: audits/models.py:43 audits/models.py:117 common/const/choices.py:17 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" @@ -1492,8 +1506,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 ops/models/base.py:48 -#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:15 -#: terminal/models/applet/host.py:27 terminal/models/component/status.py:33 +#: terminal/models/applet/applet.py:57 terminal/models/applet/host.py:19 +#: terminal/models/applet/host.py:31 terminal/models/component/status.py:33 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 #: xpack/plugins/cloud/models.py:223 msgid "Status" @@ -1557,7 +1571,7 @@ msgstr "飞书" msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:56 authentication/models.py:252 +#: audits/signal_handlers.py:56 authentication/models/temp_token.py:16 msgid "Temporary token" msgstr "临时密码" @@ -1708,7 +1722,7 @@ msgstr "无效的令牌头。符号字符串不应包含无效字符。" msgid "Invalid token or cache refreshed." msgstr "刷新的令牌或缓存无效。" -#: authentication/backends/oauth2/backends.py:155 authentication/models.py:143 +#: authentication/backends/oauth2/backends.py:155 msgid "User invalid, disabled or expired" msgstr "用户无效,已禁用或已过期" @@ -1960,85 +1974,74 @@ msgstr "该 MFA ({}) 方式没有启用" msgid "Please change your password" msgstr "请修改密码" -#: authentication/models.py:45 -msgid "Private Token" -msgstr "SSH 密钥" +#: authentication/models/connection_token.py:31 +#: rbac/serializers/rolebinding.py:21 +msgid "User display" +msgstr "用户名称" -#: authentication/models.py:54 -msgid "Expired" -msgstr "过期时间" +#: authentication/models/connection_token.py:32 +msgid "Asset display" +msgstr "资产名称" -#: authentication/models.py:58 -msgid "SSO token" -msgstr "SSO token" - -#: authentication/models.py:68 authentication/models.py:249 -#: perms/models/asset_permission.py:79 +#: authentication/models/connection_token.py:36 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:79 #: tickets/models/ticket/apply_application.py:29 #: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 msgid "Date expired" msgstr "失效日期" -#: authentication/models.py:75 rbac/serializers/rolebinding.py:21 -msgid "User display" -msgstr "用户名称" - -#: authentication/models.py:80 -msgid "Asset display" -msgstr "资产名称" - -#: authentication/models.py:85 -msgid "Account display" -msgstr "账号显示" - -#: authentication/models.py:89 +#: authentication/models/connection_token.py:41 msgid "Connection token" msgstr "连接令牌" -#: authentication/models.py:91 +#: authentication/models/connection_token.py:43 msgid "Can view connection token secret" msgstr "可以查看连接令牌密文" -#: authentication/models.py:134 +#: authentication/models/connection_token.py:82 msgid "Connection token expired at: {}" msgstr "连接令牌过期: {}" -#: authentication/models.py:139 -msgid "User not exists" -msgstr "用户不存在" +#: authentication/models/connection_token.py:86 +msgid "No user or invalid user" +msgstr "" -#: authentication/models.py:148 -msgid "System user not exists" -msgstr "系统用户不存在" - -#: authentication/models.py:154 -msgid "Asset not exists" -msgstr "资产不存在" - -#: authentication/models.py:158 -msgid "Asset inactive" +#: authentication/models/connection_token.py:90 +#, fuzzy +#| msgid "Asset inactive" +msgid "No asset or inactive asset" msgstr "资产未激活" -#: authentication/models.py:165 +#: authentication/models/connection_token.py:94 +#, fuzzy +#| msgid "Login account" +msgid "No account" +msgstr "登录账号" + +#: authentication/models/connection_token.py:103 msgid "User has no permission to access asset or permission expired" msgstr "用户没有权限访问资产或权限已过期" -#: authentication/models.py:173 -msgid "Application not exists" -msgstr "应用不存在" - -#: authentication/models.py:180 -msgid "User has no permission to access application or permission expired" -msgstr "用户没有权限访问应用或权限已过期" - -#: authentication/models.py:247 -msgid "Verified" -msgstr "已校验" - -#: authentication/models.py:268 +#: authentication/models/connection_token.py:145 msgid "Super connection token" msgstr "超级连接令牌" +#: authentication/models/private_token.py:9 +msgid "Private Token" +msgstr "SSH 密钥" + +#: authentication/models/sso_token.py:14 +msgid "Expired" +msgstr "过期时间" + +#: authentication/models/sso_token.py:18 +msgid "SSO token" +msgstr "SSO token" + +#: authentication/models/temp_token.py:11 +msgid "Verified" +msgstr "已校验" + #: authentication/notifications.py:19 msgid "Different city login reminder" msgstr "异地登录提醒" @@ -2047,19 +2050,15 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" -#: authentication/serializers/connection_token.py:21 +#: authentication/serializers/connection_token.py:20 #: xpack/plugins/cloud/models.py:36 msgid "Validity" msgstr "有效" -#: authentication/serializers/connection_token.py:22 +#: authentication/serializers/connection_token.py:21 msgid "Expired time" msgstr "过期时间" -#: authentication/serializers/connection_token.py:68 -msgid "Asset or application required" -msgstr "资产或应用必填" - #: authentication/serializers/token.py:79 perms/serializers/permission.py:60 #: perms/serializers/permission.py:87 users/serializers/user.py:148 msgid "Is valid" @@ -2148,7 +2147,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 ops/tasks.py:146 ops/tasks.py:152 ops/tasks.py:155 +#: jumpserver/conf.py:390 ops/tasks.py:147 ops/tasks.py:153 ops/tasks.py:156 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2440,6 +2439,20 @@ msgstr "手动触发" msgid "Timing trigger" msgstr "定时触发" +#: common/const/choices.py:15 tickets/const.py:29 tickets/const.py:37 +msgid "Pending" +msgstr "待定的" + +#: common/const/choices.py:16 +msgid "Running" +msgstr "" + +#: common/const/choices.py:20 +#, fuzzy +#| msgid "Cancel" +msgid "Canceled" +msgstr "取消" + #: common/db/encoder.py:11 msgid "ugettext_lazy" msgstr "ugettext_lazy" @@ -2688,15 +2701,15 @@ msgstr "站内信" msgid "No account available" msgstr "没有账号可以使用" -#: ops/ansible/inventory.py:173 +#: ops/ansible/inventory.py:175 msgid "Ansible disabled" msgstr "Ansible 已禁用" -#: ops/ansible/inventory.py:189 +#: ops/ansible/inventory.py:191 msgid "Skip hosts below:" msgstr "跳过一下主机:" -#: ops/api/celery.py:61 ops/api/celery.py:76 +#: ops/api/celery.py:63 ops/api/celery.py:78 msgid "Waiting task start" msgstr "等待任务开始" @@ -2757,7 +2770,7 @@ msgstr "模式" msgid "Module" msgstr "" -#: ops/models/adhoc.py:20 ops/models/celery.py:15 +#: ops/models/adhoc.py:20 ops/models/celery.py:45 #: terminal/models/component/task.py:17 msgid "Args" msgstr "参数" @@ -2795,16 +2808,16 @@ msgstr "结果" msgid "Summary" msgstr "汇总" -#: ops/models/celery.py:16 terminal/models/component/task.py:18 +#: ops/models/celery.py:46 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "其它参数" -#: ops/models/celery.py:17 tickets/models/comment.py:13 +#: ops/models/celery.py:47 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 msgid "State" msgstr "状态" -#: ops/models/celery.py:18 terminal/models/session/sharing.py:111 +#: ops/models/celery.py:48 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "结束" @@ -2829,7 +2842,8 @@ msgstr "Owner" msgid "Template" msgstr "模板" -#: ops/models/playbook.py:38 terminal/models/component/task.py:26 +#: ops/models/playbook.py:38 ops/signal_handlers.py:63 +#: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "任务" @@ -2866,19 +2880,19 @@ msgstr "内存使用率超过 {max_threshold}%: => {value}" msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" -#: ops/tasks.py:33 +#: ops/tasks.py:34 msgid "Run ansible task" msgstr "运行 ansible 任务" -#: ops/tasks.py:57 +#: ops/tasks.py:58 msgid "Run ansible command" msgstr "运行 ansible 命令" -#: ops/tasks.py:79 +#: ops/tasks.py:80 msgid "Clean task history period" msgstr "定期清除任务历史" -#: ops/tasks.py:92 +#: ops/tasks.py:93 msgid "Clean celery log period" msgstr "定期清除Celery日志" @@ -2908,7 +2922,7 @@ msgstr "组织存在资源 ({}) 不能被删除" msgid "App organizations" msgstr "组织管理" -#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:87 +#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 #: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 @@ -2919,23 +2933,29 @@ msgstr "组织" msgid "Org name" msgstr "组织名称" -#: orgs/models.py:79 +#: orgs/models.py:72 +#, fuzzy +#| msgid "Built-in" +msgid "Builtin" +msgstr "内置" + +#: orgs/models.py:80 msgid "GLOBAL" msgstr "全局组织" -#: orgs/models.py:81 +#: orgs/models.py:82 msgid "DEFAULT" msgstr "" -#: orgs/models.py:83 +#: orgs/models.py:84 msgid "SYSTEM" msgstr "" -#: orgs/models.py:89 +#: orgs/models.py:90 msgid "Can view root org" msgstr "可以查看全局组织" -#: orgs/models.py:90 +#: orgs/models.py:91 msgid "Can view all joined org" msgstr "可以查看所有加入的组织" @@ -3212,7 +3232,7 @@ msgstr "权限" msgid "Scope display" msgstr "范围名称" -#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:26 +#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 msgid "Display name" msgstr "显示名称" @@ -3260,6 +3280,10 @@ msgstr "云同步" msgid "Backup account" msgstr "备份账号" +#: rbac/tree.py:53 +msgid "Gather account" +msgstr "收集账号" + #: rbac/tree.py:54 msgid "App change auth" msgstr "应用改密" @@ -4693,39 +4717,47 @@ msgstr "不支持批量创建" msgid "Storage is invalid" msgstr "存储无效" -#: terminal/models/applet/applet.py:21 -msgid "Manual" -msgstr "手动" - -#: terminal/models/applet/applet.py:22 -msgid "Git" -msgstr "" - #: terminal/models/applet/applet.py:23 -msgid "Remote gzip" -msgstr "远程应用" - -#: terminal/models/applet/applet.py:28 msgid "Author" msgstr "作者" -#: terminal/models/applet/applet.py:32 +#: terminal/models/applet/applet.py:27 msgid "Tags" msgstr "标签" -#: terminal/models/applet/applet.py:59 terminal/models/applet/host.py:17 +#: terminal/models/applet/applet.py:29 terminal/serializers/storage.py:157 +msgid "Hosts" +msgstr "主机" + +#: terminal/models/applet/applet.py:55 terminal/models/applet/host.py:21 msgid "Applet" msgstr "远程应用" -#: terminal/models/applet/host.py:13 +#: terminal/models/applet/host.py:14 msgid "Account automation" msgstr "账号自动化" -#: terminal/models/applet/host.py:14 +#: terminal/models/applet/host.py:15 terminal/serializers/applet.py:66 +#, fuzzy +#| msgid "More login options" +msgid "Deploy options" +msgstr "其他方式登录" + +#: terminal/models/applet/host.py:16 +msgid "Inited" +msgstr "" + +#: terminal/models/applet/host.py:17 +#, fuzzy +#| msgid "Date finished" +msgid "Date inited" +msgstr "结束日期" + +#: terminal/models/applet/host.py:18 msgid "Date synced" msgstr "最后同步日期" -#: terminal/models/applet/host.py:26 +#: terminal/models/applet/host.py:30 msgid "Hosting" msgstr "主机" @@ -4957,6 +4989,42 @@ msgstr "批量危险命令告警" msgid "Icon" msgstr "图标" +#: terminal/serializers/applet.py:53 +msgid "Not set" +msgstr "不设置" + +#: terminal/serializers/applet.py:54 +msgid "Per Session" +msgstr "按会话" + +#: terminal/serializers/applet.py:55 +msgid "Per Device" +msgstr "按设备" + +#: terminal/serializers/applet.py:57 +msgid "RDS Licensing" +msgstr "部署 RDS 许可服务" + +#: terminal/serializers/applet.py:58 +msgid "RDS License Server" +msgstr "RDS 许可服务主机" + +#: terminal/serializers/applet.py:59 +msgid "RDS Licensing Mode" +msgstr "RDS 许可模式" + +#: terminal/serializers/applet.py:60 +msgid "RDS fSingleSessionPerUser" +msgstr "RDS 会话用户数" + +#: terminal/serializers/applet.py:61 +msgid "RDS Max Disconnection Time" +msgstr "RDS 会话断开时间" + +#: terminal/serializers/applet.py:62 +msgid "RDS Remote App Logoff Time Limit" +msgstr "RDS 远程应用注销时间" + #: terminal/serializers/endpoint.py:12 msgid "Oracle port" msgstr "" @@ -5056,10 +5124,6 @@ msgstr "主机无效" msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:157 -msgid "Hosts" -msgstr "主机" - #: terminal/serializers/storage.py:160 msgid "Index by date" msgstr "按日期建索引" @@ -5116,10 +5180,6 @@ msgstr "已拒绝" msgid "Reopen" msgstr "" -#: tickets/const.py:29 tickets/const.py:37 -msgid "Pending" -msgstr "待定的" - #: tickets/const.py:32 tickets/const.py:39 msgid "Closed" msgstr "关闭的" @@ -6715,6 +6775,39 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Push automation" +#~ msgstr "自动化推送" + +#~ msgid "Verify account automation" +#~ msgstr "账号校验自动化" + +#~ msgid "Account display" +#~ msgstr "账号显示" + +#~ msgid "User not exists" +#~ msgstr "用户不存在" + +#~ msgid "System user not exists" +#~ msgstr "系统用户不存在" + +#~ msgid "Asset not exists" +#~ msgstr "资产不存在" + +#~ msgid "Application not exists" +#~ msgstr "应用不存在" + +#~ msgid "User has no permission to access application or permission expired" +#~ msgstr "用户没有权限访问应用或权限已过期" + +#~ msgid "Asset or application required" +#~ msgstr "资产或应用必填" + +#~ msgid "Manual" +#~ msgstr "手动" + +#~ msgid "Remote gzip" +#~ msgstr "远程应用" + #~ msgid "Verify secret" #~ msgstr "校验密码" @@ -6726,9 +6819,6 @@ msgstr "社区版" #~ msgid "Verify secret automation" #~ msgstr "自动化验证" -#~ msgid "Gather accounts" -#~ msgstr "收集账号" - #, fuzzy #~| msgid "Hostname strategy" #~ msgid "Automation strategy" diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index b713ccbf4..dd49a4d94 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -3,6 +3,7 @@ import ast from django.db import transaction from django.dispatch import receiver from django.utils import translation, timezone +from django.utils.translation import gettext as _ from django.core.cache import cache from celery import signals, current_app @@ -44,7 +45,8 @@ def before_task_publish(headers=None, **kwargs): @signals.task_prerun.connect def on_celery_task_pre_run(task_id='', **kwargs): # 更新状态 - CeleryTaskExecution.objects.filter(id=task_id).update(state='RUNNING', date_start=timezone.now()) + CeleryTaskExecution.objects.filter(id=task_id)\ + .update(state='RUNNING', date_start=timezone.now()) # 关闭之前的数据库连接 close_old_connections() @@ -58,7 +60,7 @@ def on_celery_task_pre_run(task_id='', **kwargs): @signals.task_postrun.connect def on_celery_task_post_run(task_id='', state='', **kwargs): close_old_connections() - print("Task post run: ", task_id, state) + print(_("Task") + ": {} {}".format(task_id, state)) CeleryTaskExecution.objects.filter(id=task_id).update( state=state, date_finished=timezone.now(), is_finished=True diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 3c2509f6a..38cd0e764 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -109,9 +109,7 @@ def filter_org_queryset(queryset): if locking_org: kwargs = {'org_id': locking_org} - elif org is None: - kwargs = {} - elif org.is_root(): + elif org is None or org.is_root(): kwargs = {} else: kwargs = {'org_id': org.id} diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py index 287d44b3e..c02aa491a 100644 --- a/apps/terminal/automations/deploy_applet_host/__init__.py +++ b/apps/terminal/automations/deploy_applet_host/__init__.py @@ -2,6 +2,7 @@ import os import datetime import shutil +import yaml from django.utils import timezone from django.conf import settings @@ -26,10 +27,20 @@ class DeployAppletHostManager: def generate_playbook(self): playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml') + with open(playbook_src) as f: + plays = yaml.safe_load(f) + for play in plays: + play['vars'].update(self.deployment.host.deploy_options) + play['vars']['DownloadHost'] = settings.BASE_URL + '/download/' + play['vars']['CORE_HOST'] = settings.BASE_URL + play['vars']['BOOTSTRAP_TOKEN'] = settings.BOOSTRAP_TOKEN + play['vars']['HOST_NAME'] = self.deployment.host.name + playbook_dir = os.path.join(self.run_dir, 'playbook') playbook_dst = os.path.join(playbook_dir, 'main.yml') os.makedirs(playbook_dir, exist_ok=True) - shutil.copy(playbook_src, playbook_dst) + with open(playbook_dst, 'w') as f: + yaml.safe_dump(plays, f) return playbook_dst def generate_inventory(self): diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 20970c952..920546e33 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -2,13 +2,17 @@ - hosts: all vars: - - DownloadHost: https://demo.jumpserver.org/download - - RDS_Licensing: enabled - - RDS_LicenseServer: 127.0.0.1 - - RDS_LicensingMode: 4 - - RDS_fSingleSessionPerUser: 1 - - RDS_MaxDisconnectionTime: 60000 - - RDS_RemoteAppLogoffTimeLimit: 0 + DownloadHost: https://demo.jumpserver.org/download + Initial: 0 + HOST_NAME: test + CORE_HOST: https://demo.jumpserver.org + BOOTSTRAP_TOKEN: PleaseChangeMe + RDS_Licensing: enabled + RDS_LicenseServer: 127.0.0.1 + RDS_LicensingMode: 4 + RDS_fSingleSessionPerUser: 1 + RDS_MaxDisconnectionTime: 60000 + RDS_RemoteAppLogoffTimeLimit: 0 tasks: - name: Install RDS-Licensing (RDS) @@ -136,3 +140,12 @@ state: present arguments: - /quiet + + - name: Generate component config + ansible.windows.win_shell: > + echo "Todo: Set config" + + - name: Sync all remote applets + ansible.windows.win_shell: > + echo "TODO: Sync all remote applets" + when: Initial diff --git a/apps/terminal/migrations/0054_auto_20221027_1125.py b/apps/terminal/migrations/0054_auto_20221027_1125.py index 4589d6970..b35108ca0 100644 --- a/apps/terminal/migrations/0054_auto_20221027_1125.py +++ b/apps/terminal/migrations/0054_auto_20221027_1125.py @@ -56,7 +56,7 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('status', models.CharField(max_length=16, verbose_name='Status')), + ('status', models.CharField(default='', max_length=16, verbose_name='Status')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('applet', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.applet', verbose_name='Applet')), ('host', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='terminal.applethost', verbose_name='Host')), diff --git a/apps/terminal/migrations/0056_auto_20221101_1353.py b/apps/terminal/migrations/0056_auto_20221101_1353.py new file mode 100644 index 000000000..798420e2c --- /dev/null +++ b/apps/terminal/migrations/0056_auto_20221101_1353.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-11-01 05:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0055_auto_20221031_1848'), + ] + + operations = [ + migrations.AddField( + model_name='applethost', + name='deploy_options', + field=models.JSONField(default=dict, verbose_name='Deploy options'), + ), + migrations.AddField( + model_name='applethostdeployment', + name='initial', + field=models.BooleanField(default=False, verbose_name='Initial'), + ), + ] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 14f363517..80be76e89 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -54,8 +54,7 @@ class Applet(JMSBaseModel): class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet')) host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host')) - status = models.CharField(max_length=16, verbose_name=_('Status')) - published = models.BooleanField(default=False, verbose_name=_('Published')) + status = models.CharField(max_length=16, default='', verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) class Meta: diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index e1edc2239..9c2338591 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -9,9 +9,10 @@ __all__ = ['AppletHost', 'AppletHostDeployment'] class AppletHost(Host): - LOCKING_ORG = 'SYSTEM' + LOCKING_ORG = '00000000-0000-0000-0000-000000000004' account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) + deploy_options = models.JSONField(default=dict, verbose_name=_('Deploy options')) inited = models.BooleanField(default=False, verbose_name=_('Inited')) date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date inited')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) @@ -27,6 +28,7 @@ class AppletHost(Host): class AppletHostDeployment(JMSBaseModel): host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting')) + initial = models.BooleanField(default=False, verbose_name=_('Initial')) status = models.CharField(max_length=16, default='', verbose_name=_('Status')) date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index aad7e10ae..9530120b9 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -48,11 +48,30 @@ class AppletPublicationSerializer(serializers.ModelSerializer): ] + read_only_fields +class DeployOptionsSerializer(serializers.Serializer): + LICENSE_MODE_CHOICES = ( + (4, _('Per Session')), + (2, _('Per Device')), + ) + SESSION_PER_USER = ( + (1, _("Disabled")), + (0, _("Enabled")), + ) + RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing")) + RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024) + RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode')) + RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1, label=_("RDS fSingleSessionPerUser")) + RDS_MaxDisconnectionTime = serializers.IntegerField(default=60000, label=_("RDS Max Disconnection Time")) + RDS_RemoteAppLogoffTimeLimit = serializers.IntegerField(default=0, label=_("RDS Remote App Logoff Time Limit")) + + class AppletHostSerializer(HostSerializer): + deploy_options = DeployOptionsSerializer(required=False, label=_("Deploy options")) + class Meta(HostSerializer.Meta): model = AppletHost fields = HostSerializer.Meta.fields + [ - 'account_automation', 'status', 'date_synced' + 'account_automation', 'status', 'date_synced', 'deploy_options' ] extra_kwargs = { 'status': {'read_only': True}, From b159f165133d84c4df8e6a72bb72d86c0acd03d7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 1 Nov 2022 18:40:42 +0800 Subject: [PATCH 266/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=20applet=20d?= =?UTF-8?q?ownload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/applet/applet.py | 12 ++++++++++++ .../automations/deploy_applet_host/playbook.yml | 4 ++-- apps/terminal/signal_handlers.py | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index bce0738d6..54391cc48 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -5,6 +5,7 @@ import os.path from django.core.files.storage import default_storage from rest_framework import viewsets +from django.http import HttpResponse from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.serializers import ValidationError @@ -22,6 +23,7 @@ class AppletViewSet(viewsets.ModelViewSet): serializer_class = serializers.AppletSerializer rbac_perms = { 'upload': 'terminal.add_applet', + 'download': 'terminal.view_applet', } def perform_destroy(self, instance): @@ -84,6 +86,16 @@ class AppletViewSet(viewsets.ModelViewSet): serializer.save() return Response(serializer.data, status=201) + @action(detail=True, methods=['get']) + def download(self, request, *args, **kwargs): + instance = super().get_object() + path = default_storage.path('applets/{}'.format(instance.name)) + zip_path = shutil.make_archive(path, 'zip', path) + with open(zip_path, 'rb') as f: + response = HttpResponse(f.read(), status=200, content_type='application/octet-stream') + response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}.zip'.format(instance.name) + return response + class AppletPublicationViewSet(viewsets.ModelViewSet): queryset = AppletPublication.objects.all() diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 920546e33..0c115e91a 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -7,7 +7,7 @@ HOST_NAME: test CORE_HOST: https://demo.jumpserver.org BOOTSTRAP_TOKEN: PleaseChangeMe - RDS_Licensing: enabled + RDS_Licensing: true RDS_LicenseServer: 127.0.0.1 RDS_LicensingMode: 4 RDS_fSingleSessionPerUser: 1 @@ -20,7 +20,7 @@ name: RDS-Licensing state: present include_management_tools: yes - when: RDS_Licensing == "enabled" + when: RDS_Licensing - name: Install RDS-RD-Server (RDS) ansible.windows.win_feature: diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index ec51c5a2b..5c92fd2ab 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -1,2 +1,18 @@ # -*- coding: utf-8 -*- # + +from django.db.models.signals import post_save +from django.dispatch import receiver + + +from .models import Applet, AppletHost + + +@receiver(post_save, sender=AppletHost) +def on_applet_host_create(sender, instance, created=False, **kwargs): + pass + + +@receiver(post_save, sender=Applet) +def on_applet_create(sender, instance, created=False, **kwargs): + pass From 1c9f754e27c727a21cc00eecdc2b0f07374e06c9 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 1 Nov 2022 19:06:35 +0800 Subject: [PATCH 267/488] =?UTF-8?q?perf:=20applet=20=E6=B7=BB=E5=8A=A0=20s?= =?UTF-8?q?etup.yml=20=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/applet/applet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 54391cc48..7e3092755 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -54,7 +54,7 @@ class AppletViewSet(viewsets.ModelViewSet): zp.extractall(extract_to) tmp_dir = os.path.join(extract_to, file.name.replace('.zip', '')) - files = ['manifest.yml', 'icon.png', 'i18n.yml'] + files = ['manifest.yml', 'icon.png', 'i18n.yml', 'setup.yml'] for name in files: path = os.path.join(tmp_dir, name) if not os.path.exists(path): From 0e5ebfad1c0721ec49fa22818276306a40d33819 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 1 Nov 2022 19:37:50 +0800 Subject: [PATCH 268/488] perf: gather asset info and test asset connectivity --- apps/assets/api/asset/asset.py | 7 +- apps/assets/api/node.py | 8 +- apps/assets/automations/ping/manager.py | 24 ++- .../migrations/0108_auto_20221027_1053.py | 12 ++ apps/assets/models/automations/__init__.py | 1 + apps/assets/models/automations/base.py | 9 ++ apps/assets/models/automations/ping.py | 15 ++ apps/assets/signal_handlers/asset.py | 6 +- apps/assets/tasks/asset_connectivity.py | 108 +++---------- .../tasks/gather_asset_hardware_info.py | 143 +++--------------- 10 files changed, 103 insertions(+), 230 deletions(-) create mode 100644 apps/assets/models/automations/ping.py diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index f1d09f6a5..bcc4e99cf 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -12,7 +12,8 @@ from orgs.mixins import generics from assets import serializers from assets.models import Asset, Gateway from assets.tasks import ( - update_assets_hardware_info_manual, test_assets_connectivity_manual, + test_assets_connectivity_manual, + update_assets_hardware_info_manual, ) from assets.filters import NodeFilterBackend, LabelFilterBackend, IpInFilterBackend from ..mixin import NodeFilterMixin @@ -78,12 +79,10 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): class AssetsTaskMixin: def perform_assets_task(self, serializer): data = serializer.validated_data - action = data['action'] assets = data.get('assets', []) - if action == "refresh": + if data['action'] == "refresh": task = update_assets_hardware_info_manual.delay(assets) else: - # action == 'test': task = test_assets_connectivity_manual.delay(assets) return task diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index a32e6644d..85935dec2 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -4,11 +4,11 @@ from collections import namedtuple, defaultdict from django.core.exceptions import PermissionDenied from rest_framework import status +from rest_framework.generics import get_object_or_404 from rest_framework.serializers import ValidationError from rest_framework.response import Response from rest_framework.decorators import action from django.utils.translation import ugettext_lazy as _ -from django.shortcuts import get_object_or_404, Http404 from django.db.models.signals import m2m_changed from common.const.http import POST @@ -16,7 +16,7 @@ from common.exceptions import SomeoneIsDoingThis from common.const.signals import PRE_REMOVE, POST_REMOVE from common.mixins.api import SuggestionMixin from assets.models import Asset -from common.utils import get_logger, get_object_or_none +from common.utils import get_logger from common.tree import TreeNodeSerializer from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics @@ -339,7 +339,7 @@ class NodeTaskCreateApi(generics.CreateAPIView): def get_object(self): node_id = self.kwargs.get('pk') - node = get_object_or_none(self.model, id=node_id) + node = get_object_or_404(self.model, id=node_id) return node @staticmethod @@ -361,8 +361,6 @@ class NodeTaskCreateApi(generics.CreateAPIView): task = self.refresh_nodes_cache() self.set_serializer_data(serializer, task) return - if node is None: - raise Http404() if action == "refresh": task = update_node_assets_hardware_info_manual.delay(node) else: diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py index 791009e36..34c05a8f4 100644 --- a/apps/assets/automations/ping/manager.py +++ b/apps/assets/automations/ping/manager.py @@ -1,5 +1,5 @@ from common.utils import get_logger -from assets.const import AutomationTypes +from assets.const import AutomationTypes, Connectivity from ..base.manager import BasePlaybookManager logger = get_logger(__name__) @@ -8,13 +8,27 @@ logger = get_logger(__name__) class PingManager(BasePlaybookManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.host_asset_mapper = {} + self.host_asset_and_account_mapper = {} @classmethod def method_type(cls): return AutomationTypes.ping - def host_callback(self, host, asset=None, **kwargs): - super().host_callback(host, asset=asset, **kwargs) - self.host_asset_mapper[host['name']] = asset + def host_callback(self, host, asset=None, account=None, **kwargs): + super().host_callback(host, asset=asset, account=account, **kwargs) + self.host_asset_and_account_mapper[host['name']] = (asset, account) return host + + def on_host_success(self, host, result): + asset, account = self.host_asset_and_account_mapper.get(host) + asset.set_connectivity(Connectivity.ok) + if not account: + return + account.set_connectivity(Connectivity.ok) + + def on_host_error(self, host, error, result): + asset, account = self.host_asset_and_account_mapper.get(host) + asset.set_connectivity(Connectivity.failed) + if not account: + return + account.set_connectivity(Connectivity.failed) diff --git a/apps/assets/migrations/0108_auto_20221027_1053.py b/apps/assets/migrations/0108_auto_20221027_1053.py index c59dcf7e7..ac8069207 100644 --- a/apps/assets/migrations/0108_auto_20221027_1053.py +++ b/apps/assets/migrations/0108_auto_20221027_1053.py @@ -26,4 +26,16 @@ class Migration(migrations.Migration): name='type', field=models.CharField(choices=[('ping', 'Ping'), ('gather_facts', 'Gather facts'), ('push_account', 'Create account'), ('change_secret', 'Change secret'), ('verify_account', 'Verify account'), ('gather_accounts', 'Gather accounts')], max_length=16, verbose_name='Type'), ), + migrations.CreateModel( + name='PingAutomation', + fields=[ + ('baseautomation_ptr', + models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, + primary_key=True, serialize=False, to='assets.baseautomation')), + ], + options={ + 'verbose_name': 'Ping asset', + }, + bases=('assets.baseautomation',), + ), ] diff --git a/apps/assets/models/automations/__init__.py b/apps/assets/models/automations/__init__.py index 5c2a3e031..e579fc10f 100644 --- a/apps/assets/models/automations/__init__.py +++ b/apps/assets/models/automations/__init__.py @@ -4,3 +4,4 @@ from .push_account import * from .gather_facts import * from .gather_accounts import * from .verify_account import * +from .ping import * diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index a60a0e060..ac1fdb046 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -28,6 +28,15 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): def __str__(self): return self.name + '@' + str(self.created_by) + @classmethod + def generate_unique_name(cls, name): + while True: + name = name + str(uuid.uuid4())[:8] + try: + cls.objects.get(name=name) + except cls.DoesNotExist: + return name + def get_all_assets(self): nodes = self.nodes.all() node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) diff --git a/apps/assets/models/automations/ping.py b/apps/assets/models/automations/ping.py new file mode 100644 index 000000000..b327bc4ea --- /dev/null +++ b/apps/assets/models/automations/ping.py @@ -0,0 +1,15 @@ +from django.utils.translation import ugettext_lazy as _ + +from assets.const import AutomationTypes +from .base import BaseAutomation + +__all__ = ['PingAutomation'] + + +class PingAutomation(BaseAutomation): + def save(self, *args, **kwargs): + self.type = AutomationTypes.ping + super().save(*args, **kwargs) + + class Meta: + verbose_name = _("Ping asset") diff --git a/apps/assets/signal_handlers/asset.py b/apps/assets/signal_handlers/asset.py index 6caef6385..5aac26319 100644 --- a/apps/assets/signal_handlers/asset.py +++ b/apps/assets/signal_handlers/asset.py @@ -19,14 +19,12 @@ logger = get_logger(__file__) def update_asset_hardware_info_on_created(asset): logger.debug("Update asset `{}` hardware info".format(asset)) - # Todo: - # update_assets_hardware_info_util.delay([asset]) + update_assets_hardware_info_util.delay([asset]) def test_asset_conn_on_created(asset): logger.debug("Test asset `{}` connectivity".format(asset)) - # Todo: - # test_asset_connectivity_util.delay([asset]) + test_asset_connectivity_util.delay([asset]) @receiver(pre_save, sender=Node) diff --git a/apps/assets/tasks/asset_connectivity.py b/apps/assets/tasks/asset_connectivity.py index ce379832a..68bfe7e6b 100644 --- a/apps/assets/tasks/asset_connectivity.py +++ b/apps/assets/tasks/asset_connectivity.py @@ -1,124 +1,50 @@ # ~*~ coding: utf-8 ~*~ -from itertools import groupby -from collections import defaultdict from celery import shared_task from django.utils.translation import gettext_noop from common.utils import get_logger from orgs.utils import org_aware_func, tmp_to_root_org -from ..models import Asset, Connectivity, Account, Node -from . import const -from .utils import clean_ansible_task_hosts, group_asset_by_platform - logger = get_logger(__file__) __all__ = [ - 'test_asset_connectivity_util', 'test_asset_connectivity_manual', - 'test_node_assets_connectivity_manual', 'test_assets_connectivity_manual', + 'test_asset_connectivity_util', + 'test_assets_connectivity_manual', + 'test_node_assets_connectivity_manual', ] -# Todo: 这里可能有问题了 -def set_assets_accounts_connectivity(assets, results_summary): - asset_ids_ok = set() - asset_ids_failed = set() - - asset_hostnames_ok = results_summary.get('contacted', {}).keys() - - for asset in assets: - if asset.name in asset_hostnames_ok: - asset_ids_ok.add(asset.id) - else: - asset_ids_failed.add(asset.id) - - Asset.bulk_set_connectivity(asset_ids_ok, Connectivity.ok) - Asset.bulk_set_connectivity(asset_ids_failed, Connectivity.failed) - - accounts_ok = Account.objects.filter(asset_id__in=asset_ids_ok,) - accounts_failed = Account.objects.filter(asset_id__in=asset_ids_faile) - - Account.bulk_set_connectivity(accounts_ok, Connectivity.ok) - Account.bulk_set_connectivity(accounts_failed, Connectivity.failed) - - @org_aware_func('assets') def test_asset_connectivity_util(assets, task_name=None): - from ops.utils import update_or_create_ansible_task - + from assets.models import PingAutomation if task_name is None: task_name = gettext_noop("Test assets connectivity. ") - hosts = clean_ansible_task_hosts(assets) - if not hosts: - return {} - platform_hosts_map = {} - hosts_sorted = sorted(hosts, key=group_asset_by_platform) - platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform) - for i in platform_hosts: - platform_hosts_map[i[0]] = list(i[1]) - - platform_tasks_map = { - "unixlike": const.PING_UNIXLIKE_TASKS, - "windows": const.PING_WINDOWS_TASKS + task_name = PingAutomation.generate_unique_name(task_name) + data = { + 'name': task_name, + 'comment': ', '.join([str(i) for i in assets]) } - results_summary = dict( - contacted=defaultdict(dict), dark=defaultdict(dict), success=True - ) - for platform, _hosts in platform_hosts_map.items(): - if not _hosts: - continue - logger.debug("System user not has special auth") - tasks = platform_tasks_map.get(platform) - task, created = update_or_create_ansible_task( - task_name=task_name, hosts=_hosts, tasks=tasks, - pattern='all', options=const.TASK_OPTIONS, run_as_admin=True, - ) - raw, summary = task.run() - success = summary.get('success', False) - contacted = summary.get('contacted', {}) - dark = summary.get('dark', {}) - - results_summary['success'] &= success - results_summary['contacted'].update(contacted) - results_summary['dark'].update(dark) - continue - set_assets_accounts_connectivity(assets, results_summary) - return results_summary - - -@shared_task(queue="ansible") -def test_asset_connectivity_manual(asset_id): - asset = Asset.objects.filter(id=asset_id).first() - if not asset: - return - task_name = gettext_noop("Test assets connectivity: ") + str(asset) - summary = test_asset_connectivity_util([asset], task_name=task_name) - - if summary.get('dark'): - return False, summary['dark'] - else: - return True, "" + instance = PingAutomation.objects.create(**data) + instance.assets.add(*assets) + instance.execute() @shared_task(queue="ansible") def test_assets_connectivity_manual(asset_ids): + from assets.models import Asset with tmp_to_root_org(): assets = Asset.objects.filter(id__in=asset_ids) - task_name = gettext_noop("Test assets connectivity: ") + str([asset.name for asset in assets]) - summary = test_asset_connectivity_util(assets, task_name=task_name) - if summary.get('dark'): - return False, summary['dark'] - else: - return True, "" + task_name = gettext_noop("Test assets connectivity: ") + test_asset_connectivity_util(assets, task_name=task_name) @shared_task(queue="ansible") def test_node_assets_connectivity_manual(node_id): + from assets.models import Node with tmp_to_root_org(): node = Node.objects.get(id=node_id) - task_name = gettext_noop("Test if the assets under the node are connectable: ") + node.name + task_name = gettext_noop("Test if the assets under the node are connectable: ") assets = node.get_all_assets() - result = test_asset_connectivity_util(assets, task_name=task_name) - return result + test_asset_connectivity_util(assets, task_name=task_name) diff --git a/apps/assets/tasks/gather_asset_hardware_info.py b/apps/assets/tasks/gather_asset_hardware_info.py index 1973dd4ff..9c667a078 100644 --- a/apps/assets/tasks/gather_asset_hardware_info.py +++ b/apps/assets/tasks/gather_asset_hardware_info.py @@ -1,149 +1,50 @@ # -*- coding: utf-8 -*- # -import json -import re - from celery import shared_task -from django.utils.translation import ugettext as _, gettext_noop +from django.utils.translation import gettext_noop -from common.utils import ( - capacity_convert, sum_capacity, get_logger -) +from common.utils import get_logger from orgs.utils import org_aware_func, tmp_to_root_org -from . import const -from ..models import Asset, Node -from .utils import clean_ansible_task_hosts - logger = get_logger(__file__) -disk_pattern = re.compile(r'^hd|sd|xvd|vd|nv') __all__ = [ - 'update_assets_hardware_info_util', 'update_asset_hardware_info_manual', - 'update_assets_hardware_info_period', 'update_node_assets_hardware_info_manual', + 'update_assets_hardware_info_util', + 'update_node_assets_hardware_info_manual', 'update_assets_hardware_info_manual', ] -def set_assets_hardware_info(assets, result, **kwargs): - """ - Using ops task run result, to update asset info - - be a celery task also - :param assets: - :param result: - :param kwargs: {task_name: ""} - :return: - """ - result_raw = result[0] - assets_updated = [] - success_result = result_raw.get('ok', {}) - - for asset in assets: - hostname = asset.name - info = success_result.get(hostname, {}) - info = info.get('setup', {}).get('ansible_facts', {}) - if not info: - logger.error(_("Get asset info failed: {}").format(hostname)) - continue - ___vendor = info.get('ansible_system_vendor', 'Unknown') - ___model = info.get('ansible_product_name', 'Unknown') - ___sn = info.get('ansible_product_serial', 'Unknown') - - for ___cpu_model in info.get('ansible_processor', []): - if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"): - break - else: - ___cpu_model = 'Unknown' - ___cpu_model = ___cpu_model[:48] - ___cpu_count = info.get('ansible_processor_count', 0) - ___cpu_cores = info.get('ansible_processor_cores', None) or \ - len(info.get('ansible_processor', [])) - ___cpu_vcpus = info.get('ansible_processor_vcpus', 0) - ___memory = '%s %s' % capacity_convert( - '{} MB'.format(info.get('ansible_memtotal_mb')) - ) - disk_info = {} - for dev, dev_info in info.get('ansible_devices', {}).items(): - if disk_pattern.match(dev) and dev_info['removable'] == '0': - disk_info[dev] = dev_info['size'] - ___disk_total = '%.1f %s' % sum_capacity(disk_info.values()) - ___disk_info = json.dumps(disk_info) - - # ___platform = info.get('ansible_system', 'Unknown') - ___os = info.get('ansible_distribution', 'Unknown') - ___os_version = info.get('ansible_distribution_version', 'Unknown') - ___os_arch = info.get('ansible_architecture', 'Unknown') - ___hostname_raw = info.get('ansible_hostname', 'Unknown') - - for k, v in locals().items(): - if k.startswith('___'): - setattr(asset, k.strip('_'), v) - asset.save() - assets_updated.append(asset) - return assets_updated - - @org_aware_func('assets') def update_assets_hardware_info_util(assets, task_name=None): - """ - Using ansible api to update asset hardware info - :param asset_ids: asset seq - :param task_name: task_name running - :return: result summary ['contacted': {}, 'dark': {}] - """ - - from ops.utils import update_or_create_ansible_task + from assets.models import GatherFactsAutomation if task_name is None: task_name = gettext_noop("Update some assets hardware info. ") - tasks = const.UPDATE_ASSETS_HARDWARE_TASKS - hosts = clean_ansible_task_hosts(assets) - if not hosts: - return {} - task, created = update_or_create_ansible_task( - task_name, hosts=hosts, tasks=tasks, - pattern='all', options=const.TASK_OPTIONS, - run_as_admin=True, - ) - result = task.run() - set_assets_hardware_info(assets, result) - return True - -@shared_task(queue="ansible") -def update_asset_hardware_info_manual(asset_id): - with tmp_to_root_org(): - asset = Asset.objects.filter(id=asset_id).first() - if not asset: - return - task_name = gettext_noop("Update asset hardware info: ") + str(asset.name) - update_assets_hardware_info_util([asset], task_name=task_name) + task_name = GatherFactsAutomation.generate_unique_name(task_name) + data = { + 'name': task_name, + 'comment': ', '.join([str(i) for i in assets]) + } + instance = GatherFactsAutomation.objects.create(**data) + instance.assets.add(*assets) + instance.execute() @shared_task(queue="ansible") def update_assets_hardware_info_manual(asset_ids): - task_name = gettext_noop("Update assets hardware info: ") + str([asset.name for asset in assets]) - update_assets_hardware_info_util(asset_ids, task_name=task_name) - - -@shared_task(queue="ansible") -def update_assets_hardware_info_period(): - """ - Update asset hardware period task - :return: - """ - if not const.PERIOD_TASK_ENABLED: - logger.debug("Period task disabled, update assets hardware info pass") - return + from assets.models import Asset + with tmp_to_root_org(): + assets = Asset.objects.filter(id__in=asset_ids) + task_name = gettext_noop("Update assets hardware info: ") + update_assets_hardware_info_util(assets, task_name=task_name) @shared_task(queue="ansible") def update_node_assets_hardware_info_manual(node_id): + from assets.models import Node with tmp_to_root_org(): - node = Node.objects.filter(id=node_id).first() - if not node: - return + node = Node.objects.get(id=node_id) - task_name = gettext_noop("Update node asset hardware information: ") + str(node.name) + task_name = gettext_noop("Update node asset hardware information: ") assets = node.get_all_assets() - result = update_assets_hardware_info_util(assets, task_name=task_name) - return result + update_assets_hardware_info_util(assets, task_name=task_name) From 0728868af2c4267e46ca872412b34e2d42387f01 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 1 Nov 2022 19:55:49 +0800 Subject: [PATCH 269/488] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0API=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=8E=88=E6=9D=83=E8=A7=84=E5=88=99=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E7=9A=84=E6=89=80=E6=9C=89=E8=B4=A6=E5=8F=B7=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/asset_permission_relation.py | 15 +++++++++++++++ apps/perms/models/asset_permission.py | 2 +- apps/perms/serializers/permission_relation.py | 2 -- apps/perms/urls/asset_permission.py | 1 + 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index d63b326b2..c2c116248 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -10,11 +10,13 @@ from orgs.utils import current_org from perms import serializers from perms import models from perms.utils.user_permission import UserGrantedAssetsQueryUtils +from assets.serializers import AccountSerializer __all__ = [ 'AssetPermissionUserRelationViewSet', 'AssetPermissionUserGroupRelationViewSet', 'AssetPermissionAssetRelationViewSet', 'AssetPermissionNodeRelationViewSet', 'AssetPermissionAllAssetListApi', 'AssetPermissionAllUserListApi', + 'AssetPermissionAccountListApi', ] @@ -111,3 +113,16 @@ class AssetPermissionNodeRelationViewSet(RelationMixin): queryset = queryset.annotate(node_key=F('node__key')) return queryset + +class AssetPermissionAccountListApi(generics.ListAPIView): + serializer_class = AccountSerializer + filterset_fields = ("name", "username", "privileged", "version") + search_fields = filterset_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + perm = get_object_or_404(models.AssetPermission, pk=pk) + accounts = perm.get_all_accounts() + return accounts + + diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index cc071065d..6e2b6e637 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -136,7 +136,7 @@ class AssetPermission(OrgModelMixin): q = Q(asset_id__in=asset_ids) if not self.is_perm_all_accounts: q &= Q(username__in=self.accounts) - accounts = Account.objects.filter(q) + accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') if not flat: return accounts return accounts.values_list('id', flat=True) diff --git a/apps/perms/serializers/permission_relation.py b/apps/perms/serializers/permission_relation.py index 4c76ae3fa..3e469106a 100644 --- a/apps/perms/serializers/permission_relation.py +++ b/apps/perms/serializers/permission_relation.py @@ -3,9 +3,7 @@ from rest_framework import serializers from common.drf.serializers import BulkSerializerMixin -from assets.models import Asset, Node from perms.models import AssetPermission -from users.models import User __all__ = [ 'AssetPermissionUserRelationSerializer', diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index a97727550..095a67dba 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -84,6 +84,7 @@ permission_urlpatterns = [ # 授权规则中授权的资产 path('/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'), path('/users/all/', api.AssetPermissionAllUserListApi.as_view(), name='asset-permission-all-users'), + path('/accounts/', api.AssetPermissionAccountListApi.as_view(), name='asset-permission-accounts'), ] asset_permission_urlpatterns = [ From 6ba4b750f2e634157ee6c3ef973414675580fdf3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 1 Nov 2022 20:37:04 +0800 Subject: [PATCH 270/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20publicatio?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/applet/applet.py | 52 +++++++++---- apps/terminal/api/applet/host.py | 7 -- apps/terminal/models/applet/applet.py | 10 ++- apps/terminal/serializers/__init__.py | 1 + apps/terminal/serializers/applet.py | 95 +++--------------------- apps/terminal/serializers/applet_host.py | 88 ++++++++++++++++++++++ apps/terminal/signal_handlers.py | 10 ++- 7 files changed, 154 insertions(+), 109 deletions(-) create mode 100644 apps/terminal/serializers/applet_host.py diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 54391cc48..aed2a6b70 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -1,5 +1,7 @@ import shutil import zipfile +from typing import Callable + import yaml import os.path @@ -7,6 +9,7 @@ from django.core.files.storage import default_storage from rest_framework import viewsets from django.http import HttpResponse from rest_framework.decorators import action +from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ValidationError @@ -18,21 +21,10 @@ from terminal.serializers import AppletUploadSerializer __all__ = ['AppletViewSet', 'AppletPublicationViewSet'] -class AppletViewSet(viewsets.ModelViewSet): - queryset = Applet.objects.all() - serializer_class = serializers.AppletSerializer - rbac_perms = { - 'upload': 'terminal.add_applet', - 'download': 'terminal.view_applet', - } - - def perform_destroy(self, instance): - if not instance.name: - raise ValidationError('Applet is not null') - path = default_storage.path('applets/{}'.format(instance.name)) - if os.path.exists(path): - shutil.rmtree(path) - instance.delete() +class DownloadUploadMixin: + get_serializer: Callable + request: Request + get_object: Callable def extract_and_check_file(self, request): serializer = self.get_serializer(data=self.request.data) @@ -88,7 +80,7 @@ class AppletViewSet(viewsets.ModelViewSet): @action(detail=True, methods=['get']) def download(self, request, *args, **kwargs): - instance = super().get_object() + instance = self.get_object() path = default_storage.path('applets/{}'.format(instance.name)) zip_path = shutil.make_archive(path, 'zip', path) with open(zip_path, 'rb') as f: @@ -97,6 +89,34 @@ class AppletViewSet(viewsets.ModelViewSet): return response +class AppletViewSet(DownloadUploadMixin, viewsets.ModelViewSet): + queryset = Applet.objects.all() + serializer_class = serializers.AppletSerializer + rbac_perms = { + 'upload': 'terminal.add_applet', + 'download': 'terminal.view_applet', + } + + def filter_queryset(self, queryset): + queryset = list(super().filter_queryset(queryset)) + host = self.request.query_params.get('host') + if not host: + return queryset + + publication_mapper = {p.applet: p for p in AppletPublication.objects.filter(host_id=host)} + for applet in queryset: + applet.publication = publication_mapper.get(applet) + return queryset + + def perform_destroy(self, instance): + if not instance.name: + raise ValidationError('Applet is not null') + path = default_storage.path('applets/{}'.format(instance.name)) + if os.path.exists(path): + shutil.rmtree(path) + instance.delete() + + class AppletPublicationViewSet(viewsets.ModelViewSet): queryset = AppletPublication.objects.all() serializer_class = serializers.AppletPublicationSerializer diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index d9c7cde7e..e5dee8c7f 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -14,13 +14,6 @@ class AppletHostViewSet(viewsets.ModelViewSet): serializer_class = serializers.AppletHostSerializer queryset = AppletHost.objects.all() - @action(methods=['get'], detail=True, url_path='') - def not_published_applets(self, request, *args, **kwargs): - instance = self.get_object() - applets = Applet.objects.exclude(id__in=instance.applets.all()) - serializer = serializers.AppletSerializer(applets, many=True) - return Response(serializer.data) - class AppletHostDeploymentViewSet(viewsets.ModelViewSet): serializer_class = serializers.AppletHostDeploymentSerializer diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 80be76e89..844158136 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -6,6 +6,7 @@ from django.core.files.storage import default_storage from django.db import models from django.utils.translation import gettext_lazy as _ +from common.utils import lazyproperty from common.db.models import JMSBaseModel @@ -26,7 +27,10 @@ class Applet(JMSBaseModel): protocols = models.JSONField(default=list, verbose_name=_('Protocol')) tags = models.JSONField(default=list, verbose_name=_('Tags')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - hosts = models.ManyToManyField(through_fields=('applet', 'host'), through='AppletPublication', to='AppletHost', verbose_name=_('Hosts')) + hosts = models.ManyToManyField( + through_fields=('applet', 'host'), through='AppletPublication', + to='AppletHost', verbose_name=_('Hosts') + ) def __str__(self): return self.name @@ -50,6 +54,10 @@ class Applet(JMSBaseModel): return None return os.path.join(settings.MEDIA_URL, 'applets', self.name, 'icon.png') + @lazyproperty + def publication(self): + return None + class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet')) diff --git a/apps/terminal/serializers/__init__.py b/apps/terminal/serializers/__init__.py index c14d4fba3..019c3623c 100644 --- a/apps/terminal/serializers/__init__.py +++ b/apps/terminal/serializers/__init__.py @@ -6,3 +6,4 @@ from .storage import * from .sharing import * from .endpoint import * from .applet import * +from .applet_host import * diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 9530120b9..3b98b446b 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -2,35 +2,15 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.drf.fields import ObjectRelatedField, LabeledChoiceField -from common.validators import ProjectUniqueValidator -from assets.models import Platform -from assets.serializers import HostSerializer -from ..models import Applet, AppletPublication, AppletHost, AppletHostDeployment +from ..models import Applet, AppletPublication, AppletHost __all__ = [ 'AppletSerializer', 'AppletPublicationSerializer', - 'AppletHostSerializer', 'AppletHostDeploymentSerializer', 'AppletUploadSerializer', ] -class AppletSerializer(serializers.ModelSerializer): - icon = serializers.ReadOnlyField(label=_("Icon")) - type = LabeledChoiceField(choices=Applet.Type.choices, label=_("Type")) - - class Meta: - model = Applet - fields_mini = ['id', 'name', 'display_name'] - read_only_fields = [ - 'icon', 'date_created', 'date_updated' - ] - fields = fields_mini + [ - 'version', 'author', 'type', 'protocols', - 'tags', 'comment' - ] + read_only_fields - - class AppletUploadSerializer(serializers.Serializer): file = serializers.FileField() @@ -48,69 +28,18 @@ class AppletPublicationSerializer(serializers.ModelSerializer): ] + read_only_fields -class DeployOptionsSerializer(serializers.Serializer): - LICENSE_MODE_CHOICES = ( - (4, _('Per Session')), - (2, _('Per Device')), - ) - SESSION_PER_USER = ( - (1, _("Disabled")), - (0, _("Enabled")), - ) - RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing")) - RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024) - RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode')) - RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1, label=_("RDS fSingleSessionPerUser")) - RDS_MaxDisconnectionTime = serializers.IntegerField(default=60000, label=_("RDS Max Disconnection Time")) - RDS_RemoteAppLogoffTimeLimit = serializers.IntegerField(default=0, label=_("RDS Remote App Logoff Time Limit")) +class AppletSerializer(serializers.ModelSerializer): + icon = serializers.ReadOnlyField(label=_("Icon")) + type = LabeledChoiceField(choices=Applet.Type.choices, label=_("Type")) + publication = AppletPublicationSerializer(allow_null=True, label=_("Publication")) - -class AppletHostSerializer(HostSerializer): - deploy_options = DeployOptionsSerializer(required=False, label=_("Deploy options")) - - class Meta(HostSerializer.Meta): - model = AppletHost - fields = HostSerializer.Meta.fields + [ - 'account_automation', 'status', 'date_synced', 'deploy_options' - ] - extra_kwargs = { - 'status': {'read_only': True}, - 'date_synced': {'read_only': True} - } - - def __init__(self, *args, data=None, **kwargs): - if data: - self.set_initial_data(data) - kwargs['data'] = data - super().__init__(*args, **kwargs) - - @staticmethod - def set_initial_data(data): - platform = Platform.objects.get(name='RemoteAppHost') - data.update({ - 'platform': platform.id, - 'nodes_display': [ - 'RemoteAppHosts' - ] - }) - - def get_validators(self): - validators = super().get_validators() - # 不知道为啥没有继承过来 - uniq_validator = ProjectUniqueValidator( - queryset=AppletHost.objects.all(), - fields=('org_id', 'name') - ) - validators.append(uniq_validator) - return validators - - -class AppletHostDeploymentSerializer(serializers.ModelSerializer): class Meta: - model = AppletHostDeployment - fields_mini = ['id', 'host', 'status'] + model = Applet + fields_mini = ['id', 'name', 'display_name'] read_only_fields = [ - 'status', 'date_created', 'date_updated', - 'date_start', 'date_finished' + 'publication', 'icon', 'date_created', 'date_updated', ] - fields = fields_mini + ['comment'] + read_only_fields + fields = fields_mini + [ + 'version', 'author', 'type', 'protocols', + 'tags', 'comment' + ] + read_only_fields diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py new file mode 100644 index 000000000..b87ee78d0 --- /dev/null +++ b/apps/terminal/serializers/applet_host.py @@ -0,0 +1,88 @@ +from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ + +from common.validators import ProjectUniqueValidator +from assets.models import Platform +from assets.serializers import HostSerializer +from ..models import AppletHost, AppletHostDeployment, Applet +from .applet import AppletSerializer + + +__all__ = [ + 'AppletHostSerializer', 'AppletHostDeploymentSerializer', +] + + +class DeployOptionsSerializer(serializers.Serializer): + LICENSE_MODE_CHOICES = ( + (4, _('Per Session')), + (2, _('Per Device')), + ) + SESSION_PER_USER = ( + (1, _("Disabled")), + (0, _("Enabled")), + ) + RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing")) + RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024) + RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode')) + RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1, label=_("RDS fSingleSessionPerUser")) + RDS_MaxDisconnectionTime = serializers.IntegerField(default=60000, label=_("RDS Max Disconnection Time")) + RDS_RemoteAppLogoffTimeLimit = serializers.IntegerField(default=0, label=_("RDS Remote App Logoff Time Limit")) + + +class AppletHostSerializer(HostSerializer): + deploy_options = DeployOptionsSerializer(required=False, label=_("Deploy options")) + + class Meta(HostSerializer.Meta): + model = AppletHost + fields = HostSerializer.Meta.fields + [ + 'account_automation', 'status', 'date_synced', 'deploy_options' + ] + extra_kwargs = { + 'status': {'read_only': True}, + 'date_synced': {'read_only': True} + } + + def __init__(self, *args, data=None, **kwargs): + if data: + self.set_initial_data(data) + kwargs['data'] = data + super().__init__(*args, **kwargs) + + @staticmethod + def set_initial_data(data): + platform = Platform.objects.get(name='RemoteAppHost') + data.update({ + 'platform': platform.id, + 'nodes_display': [ + 'RemoteAppHosts' + ] + }) + + def get_validators(self): + validators = super().get_validators() + # 不知道为啥没有继承过来 + uniq_validator = ProjectUniqueValidator( + queryset=AppletHost.objects.all(), + fields=('org_id', 'name') + ) + validators.append(uniq_validator) + return validators + + +class HostAppletSerializer(AppletSerializer): + publication = serializers.SerializerMethodField() + + class Meta(AppletSerializer.Meta): + fields = AppletSerializer.Meta.fields + ['publication'] + + +class AppletHostDeploymentSerializer(serializers.ModelSerializer): + class Meta: + model = AppletHostDeployment + fields_mini = ['id', 'host', 'status'] + read_only_fields = [ + 'status', 'date_created', 'date_updated', + 'date_start', 'date_finished' + ] + fields = fields_mini + ['comment'] + read_only_fields diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index 5c92fd2ab..bac9c3253 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -10,9 +10,15 @@ from .models import Applet, AppletHost @receiver(post_save, sender=AppletHost) def on_applet_host_create(sender, instance, created=False, **kwargs): - pass + if not created: + return + applets = Applet.objects.all() + instance.applets.set(applets) @receiver(post_save, sender=Applet) def on_applet_create(sender, instance, created=False, **kwargs): - pass + if not created: + return + hosts = AppletHost.objects.all() + instance.hosts.set(hosts) From c8881d56ea9705075710241400d8cd9f741dfd5f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 2 Nov 2022 11:08:13 +0800 Subject: [PATCH 271/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applets=20?= =?UTF-8?q?api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/applet/applet.py | 18 +++++++++--------- apps/terminal/models/applet/applet.py | 3 ++- apps/terminal/serializers/applet.py | 6 +++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 8cf42f75a..1ce665184 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -5,9 +5,10 @@ from typing import Callable import yaml import os.path -from django.core.files.storage import default_storage from rest_framework import viewsets from django.http import HttpResponse +from django.core.files.storage import default_storage +from django.db.models import Prefetch from rest_framework.decorators import action from rest_framework.request import Request from rest_framework.response import Response @@ -97,16 +98,15 @@ class AppletViewSet(DownloadUploadMixin, viewsets.ModelViewSet): 'download': 'terminal.view_applet', } - def filter_queryset(self, queryset): - queryset = list(super().filter_queryset(queryset)) + def get_queryset(self): + queryset = super().get_queryset() host = self.request.query_params.get('host') - if not host: - return queryset - publication_mapper = {p.applet: p for p in AppletPublication.objects.filter(host_id=host)} - for applet in queryset: - applet.publication = publication_mapper.get(applet) - return queryset + if not host: + return queryset.prefetch_related('publications') + else: + publications = AppletPublication.objects.filter(host_id=host) + return queryset.prefetch_related(Prefetch('publications', queryset=publications)) def perform_destroy(self, instance): if not instance.name: diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 844158136..0d0b37dfc 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -56,7 +56,7 @@ class Applet(JMSBaseModel): @lazyproperty def publication(self): - return None + return self.publications.latest() class AppletPublication(JMSBaseModel): @@ -67,3 +67,4 @@ class AppletPublication(JMSBaseModel): class Meta: unique_together = ('applet', 'host') + get_latest_by = 'date_created' diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 3b98b446b..efd337e29 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -2,6 +2,7 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.drf.fields import ObjectRelatedField, LabeledChoiceField +from common.const.choices import Status from ..models import Applet, AppletPublication, AppletHost @@ -18,14 +19,13 @@ class AppletUploadSerializer(serializers.Serializer): class AppletPublicationSerializer(serializers.ModelSerializer): applet = ObjectRelatedField(queryset=Applet.objects.all()) host = ObjectRelatedField(queryset=AppletHost.objects.all()) + status = LabeledChoiceField(choices=Status.choices, label=_("Status")) class Meta: model = AppletPublication fields_mini = ['id', 'applet', 'host'] read_only_fields = ['date_created', 'date_updated'] - fields = fields_mini + [ - 'status', 'comment', - ] + read_only_fields + fields = fields_mini + ['status', 'comment'] + read_only_fields class AppletSerializer(serializers.ModelSerializer): From f6fe673b283164af1018b896942d25645af591e5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 2 Nov 2022 14:13:45 +0800 Subject: [PATCH 272/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20applet=20p?= =?UTF-8?q?ublications?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/applet/applet.py | 11 +---------- apps/terminal/models/applet/applet.py | 5 ----- apps/terminal/serializers/applet.py | 5 ++--- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 1ce665184..97e6fb103 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -98,16 +98,6 @@ class AppletViewSet(DownloadUploadMixin, viewsets.ModelViewSet): 'download': 'terminal.view_applet', } - def get_queryset(self): - queryset = super().get_queryset() - host = self.request.query_params.get('host') - - if not host: - return queryset.prefetch_related('publications') - else: - publications = AppletPublication.objects.filter(host_id=host) - return queryset.prefetch_related(Prefetch('publications', queryset=publications)) - def perform_destroy(self, instance): if not instance.name: raise ValidationError('Applet is not null') @@ -120,3 +110,4 @@ class AppletViewSet(DownloadUploadMixin, viewsets.ModelViewSet): class AppletPublicationViewSet(viewsets.ModelViewSet): queryset = AppletPublication.objects.all() serializer_class = serializers.AppletPublicationSerializer + filterset_fields = ['host', 'applet'] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 0d0b37dfc..d8e6c2fc6 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -54,10 +54,6 @@ class Applet(JMSBaseModel): return None return os.path.join(settings.MEDIA_URL, 'applets', self.name, 'icon.png') - @lazyproperty - def publication(self): - return self.publications.latest() - class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet')) @@ -67,4 +63,3 @@ class AppletPublication(JMSBaseModel): class Meta: unique_together = ('applet', 'host') - get_latest_by = 'date_created' diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index efd337e29..c8c132e58 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -17,7 +17,7 @@ class AppletUploadSerializer(serializers.Serializer): class AppletPublicationSerializer(serializers.ModelSerializer): - applet = ObjectRelatedField(queryset=Applet.objects.all()) + applet = ObjectRelatedField(attrs=('id', 'display_name', 'icon'), queryset=Applet.objects.all()) host = ObjectRelatedField(queryset=AppletHost.objects.all()) status = LabeledChoiceField(choices=Status.choices, label=_("Status")) @@ -31,13 +31,12 @@ class AppletPublicationSerializer(serializers.ModelSerializer): class AppletSerializer(serializers.ModelSerializer): icon = serializers.ReadOnlyField(label=_("Icon")) type = LabeledChoiceField(choices=Applet.Type.choices, label=_("Type")) - publication = AppletPublicationSerializer(allow_null=True, label=_("Publication")) class Meta: model = Applet fields_mini = ['id', 'name', 'display_name'] read_only_fields = [ - 'publication', 'icon', 'date_created', 'date_updated', + 'icon', 'date_created', 'date_updated', ] fields = fields_mini + [ 'version', 'author', 'type', 'protocols', From 956367cfed90dbf970b5166367fc0da6571ab46f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 2 Nov 2022 15:01:52 +0800 Subject: [PATCH 273/488] =?UTF-8?q?pref:=20applet=20detail=20api=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20slug=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/applet/applet.py | 14 ++++++++++---- .../terminal/migrations/0054_auto_20221027_1125.py | 2 +- apps/terminal/models/applet/applet.py | 3 +-- apps/tickets/serializers/ticket/ticket.py | 6 ++++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 97e6fb103..91fe417d0 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -1,19 +1,18 @@ import shutil import zipfile -from typing import Callable - import yaml import os.path +from typing import Callable -from rest_framework import viewsets from django.http import HttpResponse from django.core.files.storage import default_storage -from django.db.models import Prefetch +from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ValidationError +from common.utils import is_uuid from terminal import serializers from terminal.models import AppletPublication, Applet from terminal.serializers import AppletUploadSerializer @@ -98,6 +97,13 @@ class AppletViewSet(DownloadUploadMixin, viewsets.ModelViewSet): 'download': 'terminal.view_applet', } + def get_object(self): + pk = self.kwargs.get('pk') + if not is_uuid(pk): + return self.queryset.get(name=pk) + else: + return self.queryset.get(pk=pk) + def perform_destroy(self, instance): if not instance.name: raise ValidationError('Applet is not null') diff --git a/apps/terminal/migrations/0054_auto_20221027_1125.py b/apps/terminal/migrations/0054_auto_20221027_1125.py index b35108ca0..9a3bf4b85 100644 --- a/apps/terminal/migrations/0054_auto_20221027_1125.py +++ b/apps/terminal/migrations/0054_auto_20221027_1125.py @@ -21,7 +21,7 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('name', models.SlugField(max_length=128, unique=True, verbose_name='Name')), ('display_name', models.CharField(max_length=128, verbose_name='Display name')), ('version', models.CharField(max_length=16, verbose_name='Version')), ('author', models.CharField(max_length=128, verbose_name='Author')), diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index d8e6c2fc6..fc819d83c 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -6,7 +6,6 @@ from django.core.files.storage import default_storage from django.db import models from django.utils.translation import gettext_lazy as _ -from common.utils import lazyproperty from common.db.models import JMSBaseModel @@ -18,7 +17,7 @@ class Applet(JMSBaseModel): general = 'general', _('General') web = 'web', _('Web') - name = models.CharField(max_length=128, verbose_name=_('Name'), unique=True) + name = models.SlugField(max_length=128, verbose_name=_('Name'), unique=True) display_name = models.CharField(max_length=128, verbose_name=_('Display name')) version = models.CharField(max_length=16, verbose_name=_('Version')) author = models.CharField(max_length=128, verbose_name=_('Author')) diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index a0760e4d0..461e61870 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -68,7 +68,8 @@ class TicketApproveSerializer(TicketSerializer): class TicketApplySerializer(TicketSerializer): org_id = serializers.CharField( - required=True, max_length=36, allow_blank=True, label=_("Organization") + required=True, max_length=36, + allow_blank=True, label=_("Organization") ) class Meta: @@ -92,7 +93,8 @@ class TicketApplySerializer(TicketSerializer): ticket_type = attrs.get('type') org_id = attrs.get('org_id') - flow = TicketFlow.get_org_related_flows(org_id=org_id).filter(type=ticket_type).first() + flow = TicketFlow.get_org_related_flows(org_id=org_id)\ + .filter(type=ticket_type).first() if flow: attrs['flow'] = flow else: From 697b3fb860042f5f0eaf9a43e32cbc6e1ef9014e Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:27:47 +0800 Subject: [PATCH 274/488] =?UTF-8?q?perf:=20=E8=87=AA=E5=8A=A8=E5=8C=96?= =?UTF-8?q?=E6=8C=89=E9=92=AE=20(#9008)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/account/account.py | 12 +- apps/assets/api/asset/asset.py | 29 ++-- .../migrations/0108_auto_20221027_1053.py | 9 ++ apps/assets/models/asset/common.py | 2 +- apps/assets/serializers/asset/common.py | 3 + apps/assets/tasks/__init__.py | 9 +- apps/assets/tasks/account_connectivity.py | 110 ------------- apps/assets/tasks/automation.py | 2 +- apps/assets/tasks/gather_asset_users.py | 150 ------------------ ...asset_hardware_info.py => gather_facts.py} | 29 ++-- .../tasks/{asset_connectivity.py => ping.py} | 6 +- apps/assets/tasks/push_account.py | 37 +++++ apps/assets/tasks/verify_account.py | 37 +++++ apps/ops/tasks.py | 12 -- 14 files changed, 139 insertions(+), 308 deletions(-) delete mode 100644 apps/assets/tasks/account_connectivity.py delete mode 100644 apps/assets/tasks/gather_asset_users.py rename apps/assets/tasks/{gather_asset_hardware_info.py => gather_facts.py} (64%) rename apps/assets/tasks/{asset_connectivity.py => ping.py} (90%) create mode 100644 apps/assets/tasks/push_account.py create mode 100644 apps/assets/tasks/verify_account.py diff --git a/apps/assets/api/account/account.py b/apps/assets/api/account/account.py index 3275dc67d..e22b13c16 100644 --- a/apps/assets/api/account/account.py +++ b/apps/assets/api/account/account.py @@ -10,7 +10,7 @@ from common.permissions import UserConfirmation from authentication.const import ConfirmType from assets.models import Account from assets.filters import AccountFilterSet -from assets.tasks.account_connectivity import test_accounts_connectivity_manual +from assets.tasks import verify_accounts_connectivity from assets import serializers __all__ = ['AccountViewSet', 'AccountSecretsViewSet', 'AccountTaskCreateAPI', 'AccountHistoriesSecretAPI'] @@ -32,7 +32,9 @@ class AccountViewSet(OrgBulkModelViewSet): @action(methods=['post'], detail=True, url_path='verify') def verify_account(self, request, *args, **kwargs): account = super().get_object() - task = test_accounts_connectivity_manual.delay([account.id]) + account_ids = [account.id] + asset_ids = [account.asset_id] + task = verify_accounts_connectivity.delay(account_ids, asset_ids) return Response(data={'task': task.id}) @@ -80,8 +82,10 @@ class AccountTaskCreateAPI(CreateAPIView): return queryset def perform_create(self, serializer): - account_ids = self.get_accounts().values_list('id', flat=True) - task = test_accounts_connectivity_manual.delay(account_ids) + accounts = self.get_accounts() + account_ids = accounts.values_list('id', flat=True) + asset_ids = [account.asset_id for account in accounts] + task = verify_accounts_connectivity.delay(account_ids, asset_ids) data = getattr(serializer, '_data', {}) data["task"] = task.id setattr(serializer, '_data', data) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index bcc4e99cf..43461730f 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -12,6 +12,8 @@ from orgs.mixins import generics from assets import serializers from assets.models import Asset, Gateway from assets.tasks import ( + push_accounts_to_assets, + verify_accounts_connectivity, test_assets_connectivity_manual, update_assets_hardware_info_manual, ) @@ -110,9 +112,9 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): action = request.data.get('action') action_perm_require = { 'refresh': 'assets.refresh_assethardwareinfo', - 'push_system_user': 'assets.push_assetsystemuser', + 'push_account': 'assets.push_assetsystemuser', 'test': 'assets.test_assetconnectivity', - 'test_system_user': 'assets.test_assetconnectivity' + 'test_account': 'assets.test_assetconnectivity' } perm_required = action_perm_require.get(action) has = self.request.user.has_perm(perm_required) @@ -120,20 +122,23 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): if not has: self.permission_denied(request) - def perform_asset_task(self, serializer): + @staticmethod + def perform_asset_task(serializer): data = serializer.validated_data - action = data['action'] - if action not in ['push_system_user', 'test_system_user']: + if data['action'] not in ['push_system_user', 'test_system_user']: return asset = data['asset'] - system_users = data.get('system_users') - if not system_users: - system_users = asset.get_all_system_users() - if action == 'push_system_user': - task = push_system_users_a_asset.delay(system_users, asset=asset) - elif action == 'test_system_user': - task = test_system_users_connectivity_a_asset.delay(system_users, asset=asset) + accounts = data.get('accounts') + if not accounts: + accounts = asset.accounts.all() + + asset_ids = [asset.id] + account_ids = accounts.values_list('id', flat=True) + if action == 'push_account': + task = push_accounts_to_assets.delay(account_ids, asset_ids) + elif action == 'test_account': + task = verify_accounts_connectivity.delay(account_ids, asset_ids) else: task = None return task diff --git a/apps/assets/migrations/0108_auto_20221027_1053.py b/apps/assets/migrations/0108_auto_20221027_1053.py index ac8069207..0aa1b5c7a 100644 --- a/apps/assets/migrations/0108_auto_20221027_1053.py +++ b/apps/assets/migrations/0108_auto_20221027_1053.py @@ -38,4 +38,13 @@ class Migration(migrations.Migration): }, bases=('assets.baseautomation',), ), + migrations.AlterModelOptions( + name='asset', + options={'ordering': ['name'], + 'permissions': [('refresh_assethardwareinfo', 'Can refresh asset hardware info'), + ('test_assetconnectivity', 'Can test asset connectivity'), + ('push_assetaccount', 'Can push account to asset'), + ('match_asset', 'Can match asset'), ('add_assettonode', 'Add asset to node'), + ('move_assettonode', 'Move asset to node')], 'verbose_name': 'Asset'}, + ), ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 44fbddc23..8ea75bc2a 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -229,7 +229,7 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): permissions = [ ('refresh_assethardwareinfo', _('Can refresh asset hardware info')), ('test_assetconnectivity', _('Can test asset connectivity')), - ('push_assetsystemuser', _('Can push system user to asset')), + ('push_assetaccount', _('Can push account to asset')), ('match_asset', _('Can match asset')), ('add_assettonode', _('Add asset to node')), ('move_assettonode', _('Move asset to node')), diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index d05b430cb..d7f25111c 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -206,3 +206,6 @@ class AssetTaskSerializer(AssetsTaskSerializer): asset = serializers.PrimaryKeyRelatedField( queryset=Asset.objects, required=False, allow_empty=True, many=False ) + accounts = serializers.PrimaryKeyRelatedField( + queryset=Account.objects, required=False, allow_empty=True, many=True + ) diff --git a/apps/assets/tasks/__init__.py b/apps/assets/tasks/__init__.py index 1f26cde3f..c4d56528c 100644 --- a/apps/assets/tasks/__init__.py +++ b/apps/assets/tasks/__init__.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- # - +from .ping import * from .utils import * from .common import * from .backup import * from .automation import * +from .gather_facts import * from .nodes_amount import * -from .gather_asset_users import * -from .asset_connectivity import * -from .account_connectivity import * -from .gather_asset_hardware_info import * +from .push_account import * +from .verify_account import * diff --git a/apps/assets/tasks/account_connectivity.py b/apps/assets/tasks/account_connectivity.py deleted file mode 100644 index 694a7fe3a..000000000 --- a/apps/assets/tasks/account_connectivity.py +++ /dev/null @@ -1,110 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from celery import shared_task -from django.utils.translation import ugettext as _, gettext_noop - -from common.utils import get_logger -from orgs.utils import org_aware_func -from ..models import Connectivity, Account -from . import const -from .utils import check_asset_can_run_ansible - - -logger = get_logger(__file__) - - -__all__ = [ - 'test_account_connectivity_util', 'test_accounts_connectivity_manual', - 'get_test_account_connectivity_tasks', 'test_user_connectivity', - 'run_adhoc', -] - - -def get_test_account_connectivity_tasks(asset): - if asset.is_unixlike(): - tasks = const.PING_UNIXLIKE_TASKS - elif asset.is_windows(): - tasks = const.PING_WINDOWS_TASKS - else: - msg = _( - "The asset {} system platform {} does not " - "support run Ansible tasks".format(asset.name, asset.platform) - ) - logger.info(msg) - tasks = [] - return tasks - - -def run_adhoc(task_name, tasks, inventory): - """ - :param task_name - :param tasks - :param inventory - """ - from ops.ansible.runner import AdHocRunner - runner = AdHocRunner(inventory, options=const.TASK_OPTIONS) - result = runner.run(tasks, 'all', task_name) - return result.results_raw, result.results_summary - - -def test_user_connectivity(task_name, asset, username, password=None, private_key=None): - """ - :param task_name - :param asset - :param username - :param password - :param private_key - """ - from ops.inventory import JMSCustomInventory - - tasks = get_test_account_connectivity_tasks(asset) - if not tasks: - logger.debug("No tasks ") - return {}, {} - inventory = JMSCustomInventory( - assets=[asset], username=username, password=password, - private_key=private_key - ) - raw, summary = run_adhoc( - task_name=task_name, tasks=tasks, inventory=inventory - ) - return raw, summary - - -@org_aware_func("account") -def test_account_connectivity_util(account, task_name): - """ - :param account: 对象 - :param task_name: - :return: - """ - if not check_asset_can_run_ansible(account.asset): - return - - account.load_auth() - try: - raw, summary = test_user_connectivity( - task_name=task_name, asset=account.asset, - username=account.username, password=account.password, - private_key=account.private_key_file - ) - except Exception as e: - logger.warn("Failed run adhoc {}, {}".format(task_name, e)) - return - - if summary.get('success'): - account.set_connectivity(Connectivity.ok) - else: - account.set_connectivity(Connectivity.failed) - - -@shared_task(queue="ansible") -def test_accounts_connectivity_manual(account_ids): - """ - :param accounts: 对象 - """ - accounts = Account.objects.filter(id__in=account_ids) - for account in accounts: - task_name = gettext_noop("Test account connectivity: ") + str(account) - test_account_connectivity_util(account, task_name) - print(".\n") diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py index f3484d3e9..c4d5f5043 100644 --- a/apps/assets/tasks/automation.py +++ b/apps/assets/tasks/automation.py @@ -6,7 +6,7 @@ from common.utils import get_logger, get_object_or_none logger = get_logger(__file__) -@shared_task +@shared_task(queue='ansible') def execute_automation(pid, trigger, mode): with tmp_to_root_org(): instance = get_object_or_none(mode, pk=pid) diff --git a/apps/assets/tasks/gather_asset_users.py b/apps/assets/tasks/gather_asset_users.py deleted file mode 100644 index 0ce6c7453..000000000 --- a/apps/assets/tasks/gather_asset_users.py +++ /dev/null @@ -1,150 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -import re -from collections import defaultdict - -from celery import shared_task -from django.utils.translation import gettext_noop -from django.utils import timezone - -from orgs.utils import tmp_to_org, org_aware_func, tmp_to_root_org -from common.utils import get_logger -from ..models import GatheredUser, Node -from .utils import clean_ansible_task_hosts -from . import const - -__all__ = ['gather_asset_users', 'gather_nodes_asset_users'] -logger = get_logger(__name__) -space = re.compile('\s+') -ignore_login_shell = re.compile(r'nologin$|sync$|shutdown$|halt$') - - -def parse_linux_result_to_users(result): - users = defaultdict(dict) - users_result = result.get('gather host users', {})\ - .get('ansible_facts', {})\ - .get('getent_passwd') - if not isinstance(users_result, dict): - users_result = {} - for username, attr in users_result.items(): - if ignore_login_shell.search(attr[-1]): - continue - users[username] = {} - last_login_result = result.get('get last login', {}).get('stdout_lines', []) - for line in last_login_result: - data = line.split('@') - if len(data) != 3: - continue - username, ip, dt = data - dt += ' +0800' - date = timezone.datetime.strptime(dt, '%b %d %H:%M:%S %Y %z') - users[username] = {"ip": ip, "date": date} - return users - - -def parse_windows_result_to_users(result): - task_result = [] - for task_name, raw in result.items(): - res = raw.get('stdout_lines', {}) - if res: - task_result = res - break - if not task_result: - return [] - - users = {} - - for i in range(4): - task_result.pop(0) - for i in range(2): - task_result.pop() - - for line in task_result: - username_list = space.split(line) - # such as: ['Admini', 'appadm', 'DefaultAccount', ''] - for username in username_list: - if not username: - continue - users[username] = {} - return users - - -def add_asset_users(assets, results): - assets_map = {a.name: a for a in assets} - parser_map = { - 'linux': parse_linux_result_to_users, - 'windows': parse_windows_result_to_users - } - - assets_users_map = {} - - for platform, platform_results in results.items(): - for hostname, res in platform_results.items(): - parse = parser_map.get(platform) - users = parse(res) - logger.debug('Gathered host users: {} {}'.format(hostname, users)) - asset = assets_map.get(hostname) - if not asset: - continue - assets_users_map[asset] = users - - for asset, users in assets_users_map.items(): - with tmp_to_org(asset.org_id): - GatheredUser.objects.filter(asset=asset, present=True)\ - .update(present=False) - for username, data in users.items(): - defaults = {'asset': asset, 'username': username, 'present': True} - if data.get("ip"): - defaults["ip_last_login"] = data["address"][:32] - if data.get("date"): - defaults["date_last_login"] = data["date"] - GatheredUser.objects.update_or_create( - defaults=defaults, asset=asset, username=username, - ) - - -@org_aware_func("assets") -def gather_asset_users(assets, task_name=None): - from ops.utils import update_or_create_ansible_task - if task_name is None: - task_name = gettext_noop("Gather assets users") - assets = clean_ansible_task_hosts(assets) - if not assets: - return - hosts_category = { - 'linux': { - 'hosts': [], - 'tasks': const.GATHER_ASSET_USERS_TASKS - }, - 'windows': { - 'hosts': [], - 'tasks': const.GATHER_ASSET_USERS_TASKS_WINDOWS - } - } - for asset in assets: - hosts_list = hosts_category['windows']['hosts'] if asset.is_windows() \ - else hosts_category['linux']['hosts'] - hosts_list.append(asset) - - results = {'linux': defaultdict(dict), 'windows': defaultdict(dict)} - for k, value in hosts_category.items(): - if not value['hosts']: - continue - _task_name = '{}: {}'.format(task_name, k) - task, created = update_or_create_ansible_task( - task_name=_task_name, hosts=value['hosts'], tasks=value['tasks'], - pattern='all', options=const.TASK_OPTIONS, - run_as_admin=True, - ) - raw, summary = task.run() - results[k].update(raw['ok']) - add_asset_users(assets, results) - - -@shared_task(queue="ansible") -def gather_nodes_asset_users(nodes_key): - nodes = Node.objects.filter(key__in=nodes_key) - assets = Node.get_nodes_all_assets(*nodes) - assets_groups_by_100 = [assets[i:i+100] for i in range(0, len(assets), 100)] - for _assets in assets_groups_by_100: - gather_asset_users(_assets) diff --git a/apps/assets/tasks/gather_asset_hardware_info.py b/apps/assets/tasks/gather_facts.py similarity index 64% rename from apps/assets/tasks/gather_asset_hardware_info.py rename to apps/assets/tasks/gather_facts.py index 9c667a078..805f8b336 100644 --- a/apps/assets/tasks/gather_asset_hardware_info.py +++ b/apps/assets/tasks/gather_facts.py @@ -15,18 +15,28 @@ __all__ = [ @org_aware_func('assets') -def update_assets_hardware_info_util(assets, task_name=None): +def update_assets_hardware_info_util(assets=None, nodes=None, task_name=None): from assets.models import GatherFactsAutomation + if not assets and not nodes: + logger.info("No assets or nodes to update hardware info") + return + if task_name is None: task_name = gettext_noop("Update some assets hardware info. ") - task_name = GatherFactsAutomation.generate_unique_name(task_name) - data = { - 'name': task_name, - 'comment': ', '.join([str(i) for i in assets]) - } + comment = '' + if assets: + comment += 'asset:' + ', '.join([str(i) for i in assets]) + '\n' + if nodes: + comment += 'node:' + ', '.join([str(i) for i in nodes]) + + data = {'name': task_name, 'comment': comment} instance = GatherFactsAutomation.objects.create(**data) - instance.assets.add(*assets) + + if assets: + instance.assets.add(*assets) + if nodes: + instance.nodes.add(*nodes) instance.execute() @@ -36,7 +46,7 @@ def update_assets_hardware_info_manual(asset_ids): with tmp_to_root_org(): assets = Asset.objects.filter(id__in=asset_ids) task_name = gettext_noop("Update assets hardware info: ") - update_assets_hardware_info_util(assets, task_name=task_name) + update_assets_hardware_info_util(assets=assets, task_name=task_name) @shared_task(queue="ansible") @@ -46,5 +56,4 @@ def update_node_assets_hardware_info_manual(node_id): node = Node.objects.get(id=node_id) task_name = gettext_noop("Update node asset hardware information: ") - assets = node.get_all_assets() - update_assets_hardware_info_util(assets, task_name=task_name) + update_assets_hardware_info_util(nodes=[node], task_name=task_name) diff --git a/apps/assets/tasks/asset_connectivity.py b/apps/assets/tasks/ping.py similarity index 90% rename from apps/assets/tasks/asset_connectivity.py rename to apps/assets/tasks/ping.py index 68bfe7e6b..f1bfc93d9 100644 --- a/apps/assets/tasks/asset_connectivity.py +++ b/apps/assets/tasks/ping.py @@ -17,7 +17,7 @@ __all__ = [ def test_asset_connectivity_util(assets, task_name=None): from assets.models import PingAutomation if task_name is None: - task_name = gettext_noop("Test assets connectivity. ") + task_name = gettext_noop("Test assets connectivity ") task_name = PingAutomation.generate_unique_name(task_name) data = { @@ -35,7 +35,7 @@ def test_assets_connectivity_manual(asset_ids): with tmp_to_root_org(): assets = Asset.objects.filter(id__in=asset_ids) - task_name = gettext_noop("Test assets connectivity: ") + task_name = gettext_noop("Test assets connectivity ") test_asset_connectivity_util(assets, task_name=task_name) @@ -45,6 +45,6 @@ def test_node_assets_connectivity_manual(node_id): with tmp_to_root_org(): node = Node.objects.get(id=node_id) - task_name = gettext_noop("Test if the assets under the node are connectable: ") + task_name = gettext_noop("Test if the assets under the node are connectable ") assets = node.get_all_assets() test_asset_connectivity_util(assets, task_name=task_name) diff --git a/apps/assets/tasks/push_account.py b/apps/assets/tasks/push_account.py new file mode 100644 index 000000000..19ebd5045 --- /dev/null +++ b/apps/assets/tasks/push_account.py @@ -0,0 +1,37 @@ +from celery import shared_task +from django.utils.translation import gettext_noop + +from common.utils import get_logger +from orgs.utils import org_aware_func, tmp_to_root_org + +logger = get_logger(__file__) +__all__ = [ + 'push_accounts_to_assets', +] + + +@org_aware_func("assets") +def push_accounts_to_assets_util(accounts, assets, task_name): + from assets.models import PushAccountAutomation + task_name = PushAccountAutomation.generate_unique_name(task_name) + account_usernames = list(accounts.values_list('username', flat=True)) + + data = { + 'name': task_name, + 'accounts': account_usernames, + 'comment': ', '.join([str(i) for i in assets]) + } + instance = PushAccountAutomation.objects.create(**data) + instance.assets.add(*assets) + instance.execute() + + +@shared_task(queue="ansible") +def push_accounts_to_assets(account_ids, asset_ids): + from assets.models import Asset, Account + with tmp_to_root_org(): + assets = Asset.objects.get(id=asset_ids) + accounts = Account.objects.get(id=account_ids) + + task_name = gettext_noop("Push accounts to assets") + return push_accounts_to_assets_util(accounts, assets, task_name) diff --git a/apps/assets/tasks/verify_account.py b/apps/assets/tasks/verify_account.py new file mode 100644 index 000000000..afb98a4a3 --- /dev/null +++ b/apps/assets/tasks/verify_account.py @@ -0,0 +1,37 @@ +from celery import shared_task +from django.utils.translation import gettext_noop + +from common.utils import get_logger +from orgs.utils import org_aware_func, tmp_to_root_org + +logger = get_logger(__name__) +__all__ = [ + 'verify_accounts_connectivity' +] + + +@org_aware_func("assets") +def verify_accounts_connectivity_util(accounts, assets, task_name): + from assets.models import VerifyAccountAutomation + task_name = VerifyAccountAutomation.generate_unique_name(task_name) + account_usernames = list(accounts.values_list('username', flat=True)) + + data = { + 'name': task_name, + 'accounts': account_usernames, + 'comment': ', '.join([str(i) for i in assets]) + } + instance = VerifyAccountAutomation.objects.create(**data) + instance.assets.add(*assets) + instance.execute() + + +@shared_task(queue="ansible") +def verify_accounts_connectivity(account_ids, asset_ids): + from assets.models import Asset, Account + with tmp_to_root_org(): + assets = Asset.objects.get(id=asset_ids) + accounts = Account.objects.get(id=account_ids) + + task_name = gettext_noop("Verify accounts connectivity") + return verify_accounts_connectivity_util(accounts, assets, task_name) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index c749cb66c..24cca604e 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -172,15 +172,3 @@ def hello_random(): def hello_callback(result): print(result) print("Hello callback") - - -@shared_task -def execute_automation_strategy(pid, trigger): - from .models import AutomationStrategy - with tmp_to_root_org(): - instance = get_object_or_none(AutomationStrategy, pk=pid) - if not instance: - logger.error("No automation plan found: {}".format(pid)) - return - with tmp_to_org(instance.org): - instance.execute(trigger) From 0c259730ca7abd8a4c822276b0756e28b77144f4 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 2 Nov 2022 17:45:30 +0800 Subject: [PATCH 275/488] perf: del surplus code --- apps/assets/tasks/const.py | 81 -------------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 apps/assets/tasks/const.py diff --git a/apps/assets/tasks/const.py b/apps/assets/tasks/const.py deleted file mode 100644 index 51f92bd51..000000000 --- a/apps/assets/tasks/const.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# -import os - -from django.conf import settings -from django.utils.translation import ugettext_lazy as _ - - -ENV_PERIOD_TASK = os.environ.get("PERIOD_TASK", "on") == 'on' -PERIOD_TASK_ENABLED = settings.PERIOD_TASK_ENABLED and ENV_PERIOD_TASK - -UPDATE_ASSETS_HARDWARE_TASKS = [ - { - 'name': "setup", - 'action': { - 'module': 'setup' - } - } -] - -ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}" - -SYSTEM_USER_CONN_CACHE_KEY = "SYSTEM_USER_CONN_{}" -PING_UNIXLIKE_TASKS = [ - { - "name": "ping", - "action": { - "module": "ping", - } - } -] -PING_WINDOWS_TASKS = [ - { - "name": "ping", - "action": { - "module": "win_ping", - } - } -] - -TASK_OPTIONS = { - 'timeout': 10, - 'forks': 10, -} - -CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}' -CONN_UNREACHABLE, CONN_REACHABLE, CONN_UNKNOWN = range(0, 3) -CONNECTIVITY_CHOICES = ( - (CONN_UNREACHABLE, _("Unreachable")), - (CONN_REACHABLE, _('Reachable')), - (CONN_UNKNOWN, _("Unknown")), -) - -GATHER_ASSET_USERS_TASKS = [ - { - "name": "gather host users", - "action": { - "module": 'getent', - "args": "database=passwd" - }, - }, - { - "name": "get last login", - "action": { - "module": "shell", - "args": "users=$(getent passwd | grep -v 'nologin' | " - "grep -v 'shudown' | awk -F: '{ print $1 }');for i in $users;do last -w -F $i -1 | " - "head -1 | grep -v '^$' | awk '{ print $1\"@\"$3\"@\"$5,$6,$7,$8 }';done" - } - } -] - -GATHER_ASSET_USERS_TASKS_WINDOWS = [ - { - "name": "gather windows host users", - "action": { - "module": 'win_shell', - "args": "net user" - } - } -] From ab6ffda435d39c9f3e998ca3949a9dc1396763ea Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 2 Nov 2022 19:06:08 +0800 Subject: [PATCH 276/488] perf: terminal status --- apps/terminal/models/component/status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/terminal/models/component/status.py b/apps/terminal/models/component/status.py index 048bca103..1ecfc0e2a 100644 --- a/apps/terminal/models/component/status.py +++ b/apps/terminal/models/component/status.py @@ -42,7 +42,7 @@ class Status(models.Model): @classmethod def get_terminal_latest_status(cls, terminal): - from ..utils import ComputeStatUtil + from ...utils import ComputeStatUtil stat = cls.get_terminal_latest_stat(terminal) return ComputeStatUtil.compute_component_status(stat) From 23e44c49b542cbf374a9a6f8bfc2eefc59b3ae91 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 2 Nov 2022 19:07:07 +0800 Subject: [PATCH 277/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applet=20d?= =?UTF-8?q?eployments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/const/choices.py | 1 + apps/common/drf/fields.py | 2 +- apps/terminal/api/applet/applet.py | 3 ++- apps/terminal/models/applet/applet.py | 2 +- apps/terminal/serializers/applet.py | 2 +- apps/terminal/utils.py | 3 +-- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/common/const/choices.py b/apps/common/const/choices.py index fba8418df..9b6c817f3 100644 --- a/apps/common/const/choices.py +++ b/apps/common/const/choices.py @@ -12,6 +12,7 @@ class Trigger(models.TextChoices): class Status(models.TextChoices): + ready = 'ready', _('Ready') pending = 'pending', _("Pending") running = 'running', _("Running") success = 'success', _("Success") diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 8dc88ae9e..97f9785f5 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -51,7 +51,7 @@ class LabeledChoiceField(ChoiceField): } def to_representation(self, value): - if value in ('', None): + if value is None: return value return { 'value': value, diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 91fe417d0..4a627e451 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -116,4 +116,5 @@ class AppletViewSet(DownloadUploadMixin, viewsets.ModelViewSet): class AppletPublicationViewSet(viewsets.ModelViewSet): queryset = AppletPublication.objects.all() serializer_class = serializers.AppletPublicationSerializer - filterset_fields = ['host', 'applet'] + filterset_fields = ['host', 'applet', 'status'] + search_fields = ['applet__name', 'applet__display_name', 'host__name'] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index fc819d83c..cf854b036 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -57,7 +57,7 @@ class Applet(JMSBaseModel): class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Applet')) host = models.ForeignKey('AppletHost', on_delete=models.PROTECT, related_name='publications', verbose_name=_('Host')) - status = models.CharField(max_length=16, default='', verbose_name=_('Status')) + status = models.CharField(max_length=16, default='ready', verbose_name=_('Status')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) class Meta: diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index c8c132e58..183a34c93 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -17,7 +17,7 @@ class AppletUploadSerializer(serializers.Serializer): class AppletPublicationSerializer(serializers.ModelSerializer): - applet = ObjectRelatedField(attrs=('id', 'display_name', 'icon'), queryset=Applet.objects.all()) + applet = ObjectRelatedField(attrs=('id', 'display_name', 'icon', 'version'), queryset=Applet.objects.all()) host = ObjectRelatedField(queryset=AppletHost.objects.all()) status = LabeledChoiceField(choices=Status.choices, label=_("Status")) diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index abdfbd738..eed67f408 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -11,8 +11,7 @@ import jms_storage from common.utils import get_logger from . import const from .models import ReplayStorage -from tickets.models import TicketSession, TicketStep, TicketAssignee -from tickets.const import StepState +from tickets.models import TicketSession logger = get_logger(__name__) From 651c7ca152ec35cf0ff3ced93d9c9318c3b2ae3d Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 2 Nov 2022 19:25:39 +0800 Subject: [PATCH 278/488] perf: playbook specific --- .../automations/change_secret/database/postgresql/main.yml | 6 +++--- .../gather_accounts/database/postgresql/main.yml | 2 +- .../automations/gather_facts/database/postgresql/main.yml | 2 +- apps/assets/automations/ping/database/postgresql/main.yml | 2 +- .../automations/push_account/database/postgresql/main.yml | 2 +- .../automations/verify_account/database/postgresql/main.yml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/assets/automations/change_secret/database/postgresql/main.yml b/apps/assets/automations/change_secret/database/postgresql/main.yml index 4ef9d65ab..ada11bbd6 100644 --- a/apps/assets/automations/change_secret/database/postgresql/main.yml +++ b/apps/assets/automations/change_secret/database/postgresql/main.yml @@ -10,7 +10,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.category_property.db_name }}" + login_db: "{{ jms_asset.specific.db_name }}" register: db_info - name: Display PostgreSQL version @@ -24,7 +24,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - db: "{{ jms_asset.category_property.db_name }}" + db: "{{ jms_asset.specific.db_name }}" name: "{{ account.username }}" password: "{{ account.secret }}" when: db_info is succeeded @@ -36,7 +36,7 @@ login_password: "{{ account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - db: "{{ jms_asset.category_property.db_name }}" + db: "{{ jms_asset.specific.db_name }}" when: - db_info is succeeded - change_info is succeeded diff --git a/apps/assets/automations/gather_accounts/database/postgresql/main.yml b/apps/assets/automations/gather_accounts/database/postgresql/main.yml index cf0320627..f282b390d 100644 --- a/apps/assets/automations/gather_accounts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_accounts/database/postgresql/main.yml @@ -10,7 +10,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.category_property.db_name }}" + login_db: "{{ jms_asset.specific.db_name }}" filter: "roles" register: db_info diff --git a/apps/assets/automations/gather_facts/database/postgresql/main.yml b/apps/assets/automations/gather_facts/database/postgresql/main.yml index 98183e94c..a3c481b48 100644 --- a/apps/assets/automations/gather_facts/database/postgresql/main.yml +++ b/apps/assets/automations/gather_facts/database/postgresql/main.yml @@ -10,7 +10,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.category_property.db_name }}" + login_db: "{{ jms_asset.specific.db_name }}" register: db_info - name: Define info by set_fact diff --git a/apps/assets/automations/ping/database/postgresql/main.yml b/apps/assets/automations/ping/database/postgresql/main.yml index d76ba3ae3..e97b3946d 100644 --- a/apps/assets/automations/ping/database/postgresql/main.yml +++ b/apps/assets/automations/ping/database/postgresql/main.yml @@ -10,4 +10,4 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - login_db: "{{ jms_asset.category_property.db_name }}" + login_db: "{{ jms_asset.specific.db_name }}" diff --git a/apps/assets/automations/push_account/database/postgresql/main.yml b/apps/assets/automations/push_account/database/postgresql/main.yml index 72fbc5e83..febb213c4 100644 --- a/apps/assets/automations/push_account/database/postgresql/main.yml +++ b/apps/assets/automations/push_account/database/postgresql/main.yml @@ -10,7 +10,7 @@ login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - db: "{{ jms_asset.category_property.db_name }}" + db: "{{ jms_asset.specific.db_name }}" name: "{{ account.username }}" password: "{{ account.secret }}" diff --git a/apps/assets/automations/verify_account/database/postgresql/main.yml b/apps/assets/automations/verify_account/database/postgresql/main.yml index c70364767..08db4d869 100644 --- a/apps/assets/automations/verify_account/database/postgresql/main.yml +++ b/apps/assets/automations/verify_account/database/postgresql/main.yml @@ -10,4 +10,4 @@ login_password: "{{ account.secret }}" login_host: "{{ jms_asset.address }}" login_port: "{{ jms_asset.port }}" - db: "{{ jms_asset.category_property.db_name }}" + db: "{{ jms_asset.specific.db_name }}" From 0d2bfaa7684cf88f6d11b1b66810a3be1dd34ee1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 2 Nov 2022 20:33:27 +0800 Subject: [PATCH 279/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=20applet=20h?= =?UTF-8?q?ost=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0109_auto_20221102_2017.py | 53 +++++++++++++++++++ apps/assets/models/base.py | 9 ++-- apps/terminal/api/applet/host.py | 17 ++++++ .../migrations/0057_auto_20221102_1941.py | 24 +++++++++ apps/terminal/models/applet/host.py | 4 ++ apps/terminal/utils.py | 3 +- 6 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 apps/assets/migrations/0109_auto_20221102_2017.py create mode 100644 apps/terminal/migrations/0057_auto_20221102_1941.py diff --git a/apps/assets/migrations/0109_auto_20221102_2017.py b/apps/assets/migrations/0109_auto_20221102_2017.py new file mode 100644 index 000000000..a9dcc4446 --- /dev/null +++ b/apps/assets/migrations/0109_auto_20221102_2017.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.14 on 2022-11-02 12:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0108_auto_20221027_1053'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='is_active', + field=models.BooleanField(default=True, verbose_name='Is active'), + ), + migrations.AddField( + model_name='account', + name='updated_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='accounttemplate', + name='is_active', + field=models.BooleanField(default=True, verbose_name='Is active'), + ), + migrations.AddField( + model_name='accounttemplate', + name='updated_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), + ), + migrations.AddField( + model_name='gateway', + name='updated_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by'), + ), + migrations.AlterField( + model_name='account', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + migrations.AlterField( + model_name='accounttemplate', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + migrations.AlterField( + model_name='gateway', + name='date_created', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created'), + ), + ] diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index f293b9a93..f40e65bad 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -6,7 +6,6 @@ import uuid from hashlib import md5 import sshpubkeys -from django.core.cache import cache from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -19,7 +18,7 @@ from common.utils import ( ) from common.db import fields from assets.const import Connectivity -from orgs.mixins.models import OrgModelMixin +from orgs.mixins.models import JMSOrgBaseModel logger = get_logger(__file__) @@ -48,14 +47,13 @@ class AbsConnectivity(models.Model): abstract = True -class BaseAccount(OrgModelMixin): +class BaseAccount(JMSOrgBaseModel): class SecretType(models.TextChoices): password = 'password', _('Password') ssh_key = 'ssh_key', _('SSH key') access_key = 'access_key', _('Access key') token = 'token', _('Token') - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) secret_type = models.CharField( @@ -63,9 +61,8 @@ class BaseAccount(OrgModelMixin): ) secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) + is_active = models.BooleanField(default=True, verbose_name=_("Is active")) comment = models.TextField(blank=True, verbose_name=_('Comment')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) - date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) @property diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index e5dee8c7f..c9e08e4eb 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -14,6 +14,23 @@ class AppletHostViewSet(viewsets.ModelViewSet): serializer_class = serializers.AppletHostSerializer queryset = AppletHost.objects.all() + @action(methods=['post'], detail=True) + def report(self, request, *args, **kwargs): + # TODO: + # 1. 上报 安装的 Applets 每小时 + # 2. Host 和 Terminal 关联 + instance = self.get_object() + instance.sync() + return Response({'msg': 'ok'}) + + @action(methods=['get'], detail=True) + def accounts(self, request, *args, **kwargs): + # TODO: + # 1. 返回 host 上的所有用户, host 可以去创建和更新 每小时 + # 2. 密码长度最少 8 位,包含大小写字母和数字和特殊字符 + instance = self.get_object() + return Response(instance.get_accounts()) + class AppletHostDeploymentViewSet(viewsets.ModelViewSet): serializer_class = serializers.AppletHostDeploymentSerializer diff --git a/apps/terminal/migrations/0057_auto_20221102_1941.py b/apps/terminal/migrations/0057_auto_20221102_1941.py new file mode 100644 index 000000000..56f1e699a --- /dev/null +++ b/apps/terminal/migrations/0057_auto_20221102_1941.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-11-02 11:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0056_auto_20221101_1353'), + ] + + operations = [ + migrations.AddField( + model_name='applethost', + name='terminal', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='applet_host', to='terminal.terminal', verbose_name='Terminal'), + ), + migrations.AlterField( + model_name='appletpublication', + name='status', + field=models.CharField(default='ready', max_length=16, verbose_name='Status'), + ), + ] diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 9c2338591..e21e173df 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -17,6 +17,10 @@ class AppletHost(Host): date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date inited')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) status = models.CharField(max_length=16, verbose_name=_('Status')) + terminal = models.OneToOneField( + 'terminal.Terminal', on_delete=models.PROTECT, null=True, blank=True, + related_name='applet_host', verbose_name=_('Terminal') + ) applets = models.ManyToManyField( 'Applet', verbose_name=_('Applet'), through='AppletPublication', through_fields=('host', 'applet'), diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index eed67f408..c41ce9bb3 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -5,13 +5,12 @@ from itertools import groupby, chain from django.conf import settings from django.core.files.storage import default_storage - import jms_storage from common.utils import get_logger +from tickets.models import TicketSession from . import const from .models import ReplayStorage -from tickets.models import TicketSession logger = get_logger(__name__) From 0fdc30bed3f448581cce2d979cab716e23f6b84a Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 2 Nov 2022 20:36:40 +0800 Subject: [PATCH 280/488] perf: account --- apps/assets/models/base.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index f293b9a93..78fd36cb2 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -3,22 +3,21 @@ import io import os import uuid +import sshpubkeys from hashlib import md5 -import sshpubkeys -from django.core.cache import cache from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django.db.models import QuerySet +from common.db import fields from common.utils import ( ssh_key_string_to_obj, ssh_key_gen, get_logger, random_string, ssh_pubkey_gen, ) -from common.db import fields -from assets.const import Connectivity +from assets.const import Connectivity, SecretType from orgs.mixins.models import OrgModelMixin logger = get_logger(__file__) @@ -49,12 +48,6 @@ class AbsConnectivity(models.Model): class BaseAccount(OrgModelMixin): - class SecretType(models.TextChoices): - password = 'password', _('Password') - ssh_key = 'ssh_key', _('SSH key') - access_key = 'access_key', _('Access key') - token = 'token', _('Token') - id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) @@ -78,7 +71,7 @@ class BaseAccount(OrgModelMixin): @property def private_key(self): - if self.secret_type == self.SecretType.ssh_key: + if self.secret_type == SecretType.ssh_key: return self.secret return None @@ -89,7 +82,7 @@ class BaseAccount(OrgModelMixin): @private_key.setter def private_key(self, value): self.secret = value - self.secret_type = 'private_key' + self.secret_type = SecretType.ssh_key @property def ssh_key_fingerprint(self): From 7087d5a74e182e50f33ea833bbf6b172714957c4 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 3 Nov 2022 12:42:57 +0800 Subject: [PATCH 281/488] perf: account specific --- apps/assets/models/base.py | 28 +++++++++++++++---------- apps/assets/serializers/account/base.py | 6 +++++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 78fd36cb2..9f4f05649 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -15,7 +15,7 @@ from django.db.models import QuerySet from common.db import fields from common.utils import ( ssh_key_string_to_obj, ssh_key_gen, get_logger, - random_string, ssh_pubkey_gen, + random_string, ssh_pubkey_gen, lazyproperty ) from assets.const import Connectivity, SecretType from orgs.mixins.models import OrgModelMixin @@ -61,36 +61,42 @@ class BaseAccount(OrgModelMixin): date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) - @property - def password(self): - return self.secret - @property def has_secret(self): return bool(self.secret) + @property + def specific(self): + data = {} + if self.secret_type != SecretType.ssh_key: + return data + data['ssh_key_fingerprint'] = self.ssh_key_fingerprint + return data + @property def private_key(self): if self.secret_type == SecretType.ssh_key: return self.secret return None - @property - def public_key(self): - return '' - @private_key.setter def private_key(self, value): self.secret = value self.secret_type = SecretType.ssh_key + @lazyproperty + def public_key(self): + if self.secret_type == SecretType.ssh_key: + return ssh_pubkey_gen(private_key=self.private_key) + return None + @property def ssh_key_fingerprint(self): if self.public_key: public_key = self.public_key elif self.private_key: try: - public_key = ssh_pubkey_gen(private_key=self.private_key, password=self.password) + public_key = ssh_pubkey_gen(private_key=self.private_key) except IOError as e: return str(e) else: @@ -103,7 +109,7 @@ class BaseAccount(OrgModelMixin): @property def private_key_obj(self): if self.private_key: - key_obj = ssh_key_string_to_obj(self.private_key, password=self.password) + key_obj = ssh_key_string_to_obj(self.private_key) return key_obj else: return None diff --git a/apps/assets/serializers/account/base.py b/apps/assets/serializers/account/base.py index 262272a0a..5db43257e 100644 --- a/apps/assets/serializers/account/base.py +++ b/apps/assets/serializers/account/base.py @@ -21,9 +21,13 @@ class BaseAccountSerializer(BulkOrgResourceModelSerializer): class Meta: model = BaseAccount fields_mini = ['id', 'name', 'username'] - fields_small = fields_mini + ['privileged', 'secret_type', 'secret', 'has_secret'] + fields_small = fields_mini + ['privileged', 'secret_type', 'secret', 'has_secret', 'specific'] fields_other = ['created_by', 'date_created', 'date_updated', 'comment'] fields = fields_small + fields_other + read_only_fields = [ + 'has_secret', 'specific', + 'date_verified', 'created_by', 'date_created', + ] extra_kwargs = { 'secret': {'write_only': True}, 'passphrase': {'write_only': True}, From 4bf147a93fcc81bf0781417aa003dec04bfe0549 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 3 Nov 2022 15:11:20 +0800 Subject: [PATCH 282/488] perf: add remote app installer --- .../deploy_applet_host/playbook.yml | 49 +++++++++++++++---- apps/terminal/const.py | 1 + 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 0c115e91a..b2c64f0cb 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -13,6 +13,7 @@ RDS_fSingleSessionPerUser: 1 RDS_MaxDisconnectionTime: 60000 RDS_RemoteAppLogoffTimeLimit: 0 + TinkerInstaller: JumpServer-Remoteapp_v0.0.1.exe tasks: - name: Install RDS-Licensing (RDS) @@ -29,16 +30,26 @@ include_management_tools: yes register: rds_install - - name: Download Jmservisor (jumpserver) + - name: Download JumpServer Remoteapp installer (jumpserver) ansible.windows.win_get_url: - url: "{{ DownloadHost }}/Jmservisor.msi" - dest: "{{ ansible_env.TEMP }}\\Jmservisor.msi" + url: "{{ DownloadHost }}/{{ TinkerInstaller }}" + dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" - - name: Install the Jmservisor (jumpserver) + - name: Install JumpServer Remoteapp agent (jumpserver) ansible.windows.win_package: - path: "{{ ansible_env.TEMP }}\\Jmservisor.msi" + path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" + args: + - /VERYSILENT + - /SUPPRESSMSGBOXES + - /NORESTART state: present + - name: Set remote-server on the global system path (remote-server) + ansible.windows.win_path: + elements: + - '%USERPROFILE%\AppData\Local\Programs\JumpServer-Remoteapp\' + scope: user + - name: Download python-3.10.8 ansible.windows.win_get_url: url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" @@ -116,12 +127,12 @@ - name: Download chromedriver (chrome) ansible.windows.win_get_url: - url: "{{ DownloadHost }}/chromedriver_win32.106.zip" - dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip" + url: "{{ DownloadHost }}/chromedriver_win32.107.zip" + dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" - name: Unzip chromedriver (chrome) community.windows.win_unzip: - src: "{{ ansible_env.TEMP }}\\chromedriver_win32.106.zip" + src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" dest: C:\Program Files\JumpServer\drivers - name: Set chromedriver on the global system path (chrome) @@ -142,8 +153,26 @@ - /quiet - name: Generate component config - ansible.windows.win_shell: > - echo "Todo: Set config" + ansible.windows.win_shell: + "remoteapp-server config --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}" + + - name: Install remoteapp-server service + ansible.windows.win_shell: + "remoteapp-server service install" + + - name: Start remoteapp-server service + ansible.windows.win_shell: + "remoteapp-server service start" + + - name: Wait Tinker api health + ansible.windows.win_uri: + url: http://localhost:6068/api/health/ + status_code: 200 + method: GET + register: _result + until: _result.status_code == 200 + retries: 30 + delay: 5 - name: Sync all remote applets ansible.windows.win_shell: > diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 7289f3180..ef23030df 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -50,6 +50,7 @@ class TerminalTypeChoices(TextChoices): celery = 'celery', 'Celery' magnus = 'magnus', 'Magnus' razor = 'razor', 'Razor' + tinker = 'tinker', 'Tinker' @classmethod def types(cls): From 340d39d7f7ab2405b65e5bbf632fc4632d4b7e4a Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Thu, 3 Nov 2022 16:41:51 +0800 Subject: [PATCH 283/488] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E7=BB=99=E7=94=A8=E6=88=B7=E6=89=80=E6=9C=89=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E5=88=97=E8=A1=A8=E7=9A=84API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/__init__.py | 1 + apps/perms/api/user_permission/accounts.py | 24 ++++++++++++++++++++++ apps/perms/urls/asset_permission.py | 7 +++++-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 apps/perms/api/user_permission/accounts.py diff --git a/apps/perms/api/user_permission/__init__.py b/apps/perms/api/user_permission/__init__.py index 47f3e84a3..b0db20ee0 100644 --- a/apps/perms/api/user_permission/__init__.py +++ b/apps/perms/api/user_permission/__init__.py @@ -4,3 +4,4 @@ from .common import * from .nodes import * from .assets import * from .nodes_with_assets import * +from .accounts import * diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py new file mode 100644 index 000000000..d504ac8f9 --- /dev/null +++ b/apps/perms/api/user_permission/accounts.py @@ -0,0 +1,24 @@ +from rest_framework import generics +from assets.serializers import AccountSerializer +from perms.utils.account import PermAccountUtil +from .mixin import RoleAdminMixin, RoleUserMixin + + +__all__ = ['UserAllGrantedAccountsApi', 'MyAllGrantedAccountsApi'] + + +class UserAllGrantedAccountsApi(RoleAdminMixin, generics.ListAPIView): + """ 授权给用户的所有账号列表 """ + serializer_class = AccountSerializer + filterset_fields = ("name", "username", "privileged", "version") + search_fields = filterset_fields + + def get_queryset(self): + util = PermAccountUtil() + accounts = util.get_perm_accounts_for_user(self.user) + return accounts + + +class MyAllGrantedAccountsApi(RoleUserMixin, UserAllGrantedAccountsApi): + """ 授权给我的所有账号列表 """ + pass diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 095a67dba..99605372d 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -58,9 +58,12 @@ user_permission_urlpatterns = [ # 收藏的资产 path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), - # v3 中上面的 API 基本不用动 - # 获取所有和资产-用户关联的账号列表 + # 获取授权给用户的所有账号 + path('/accounts/', api.UserAllGrantedAccountsApi.as_view(), name='user-accounts'), + path('accounts/', api.MyAllGrantedAccountsApi.as_view(), name='my-accounts'), + + # 获取授权给用户某个资产的所有账号 path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), # 用户登录资产的特殊账号, @INPUT, @USER 等 From b0ae9b47ca8d095a10bb4971e89c76afed328e26 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 3 Nov 2022 16:55:38 +0800 Subject: [PATCH 284/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applet=20h?= =?UTF-8?q?ost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/account.py | 12 +- apps/assets/signal_handlers/account.py | 2 - apps/common/drf/serializers/common.py | 8 +- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 320 ++++++++++-------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 304 +++++++++-------- apps/terminal/api/applet/applet.py | 4 +- apps/terminal/api/applet/host.py | 36 +- apps/terminal/api/component/status.py | 2 +- apps/terminal/api/component/terminal.py | 31 -- apps/terminal/const.py | 1 + .../migrations/0050_auto_20220606_1745.py | 6 +- .../migrations/0054_auto_20221027_1125.py | 1 - .../migrations/0058_auto_20221103_1624.py | 33 ++ apps/terminal/models/applet/host.py | 79 ++++- apps/terminal/models/component/status.py | 2 +- apps/terminal/models/component/terminal.py | 12 +- apps/terminal/serializers/applet.py | 12 +- apps/terminal/serializers/applet_host.py | 16 +- apps/terminal/serializers/terminal.py | 2 +- apps/terminal/signal_handlers.py | 2 +- 22 files changed, 527 insertions(+), 366 deletions(-) create mode 100644 apps/terminal/migrations/0058_auto_20221103_1624.py diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index c2bb40a99..9aa007e53 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -30,11 +30,13 @@ class AccountHistoricalRecords(HistoricalRecords): return super().post_save(instance, created, using=using, **kwargs) - def fields_included(self, model): - if self.included_fields: - fields = [i for i in model._meta.fields if i.name in self.included_fields] - return fields - return super().fields_included(model) + def create_history_model(self, model, inherited): + if self.included_fields and not self.excluded_fields: + self.excluded_fields = [ + field.name for field in model._meta.fields + if field.name not in self.included_fields + ] + return super().create_history_model(model, inherited) class Account(AbsConnectivity, BaseAccount): diff --git a/apps/assets/signal_handlers/account.py b/apps/assets/signal_handlers/account.py index 26cf75d02..8020e4087 100644 --- a/apps/assets/signal_handlers/account.py +++ b/apps/assets/signal_handlers/account.py @@ -9,8 +9,6 @@ logger = get_logger(__name__) @receiver(pre_save, sender=Account) def on_account_pre_create(sender, instance, **kwargs): - # Todo: 是否只有更改密码的时候才有版本增加, bitwarden 只有再改密码的时候, - # 才会有版本,代表的是 password_version # 升级版本号 instance.version += 1 # 即使在 root 组织也不怕 diff --git a/apps/common/drf/serializers/common.py b/apps/common/drf/serializers/common.py index a522e3a82..805aaa453 100644 --- a/apps/common/drf/serializers/common.py +++ b/apps/common/drf/serializers/common.py @@ -13,8 +13,8 @@ from .mixin import BulkListSerializerMixin, BulkSerializerMixin __all__ = [ 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskExecutionSerializer', - 'WritableNestedModelSerializer', - 'GroupedChoiceSerializer', + 'WritableNestedModelSerializer', 'GroupedChoiceSerializer', + 'FileSerializer' ] @@ -88,3 +88,7 @@ class GroupedChoiceSerializer(ChoiceSerializer): class WritableNestedModelSerializer(NestedModelSerializer): pass + + +class FileSerializer(serializers.Serializer): + file = serializers.FileField(label=_("File")) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index c95e1d78e..093842b71 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4c889e251a4de3161f462e042882ba3c4ab40eaf34799e2d49d4788ad961586 -size 119171 +oid sha256:07f1cfd07039142f4847b4139586bf815467f266119eae57476c073130f0ac92 +size 118098 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 4b15fdcde..59f5db0eb 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-01 15:30+0800\n" +"POT-Creation-Date: 2022-11-03 16:00+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,7 +25,7 @@ msgstr "Acls" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:59 assets/models/cmd_filter.py:25 +#: assets/models/base.py:57 assets/models/cmd_filter.py:25 #: assets/models/domain.py:24 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:22 #: assets/models/platform.py:68 assets/serializers/asset/common.py:86 @@ -60,7 +60,7 @@ msgstr "アクティブ" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 #: assets/models/asset/common.py:100 assets/models/automations/base.py:26 -#: assets/models/backup.py:30 assets/models/base.py:66 +#: assets/models/backup.py:30 assets/models/base.py:65 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 @@ -68,7 +68,7 @@ msgstr "アクティブ" #: ops/models/playbook.py:25 orgs/models.py:74 #: perms/models/asset_permission.py:84 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 -#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:34 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:104 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 @@ -129,14 +129,14 @@ msgstr "レビュー担当者" msgid "Login acl" msgstr "ログインacl" -#: acls/models/login_asset_acl.py:21 assets/models/account.py:57 +#: acls/models/login_asset_acl.py:21 assets/models/account.py:59 #: authentication/models/connection_token.py:33 ops/models/base.py:18 #: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "アカウント" -#: acls/models/login_asset_acl.py:22 assets/models/account.py:47 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:49 #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:58 assets/serializers/label.py:30 @@ -164,7 +164,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 -#: assets/models/base.py:60 assets/models/gathered_user.py:15 +#: assets/models/base.py:58 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -210,7 +210,7 @@ msgstr "" "ション: {}" #: acls/serializers/login_asset_acl.py:84 -#: tickets/serializers/ticket/ticket.py:85 +#: tickets/serializers/ticket/ticket.py:86 msgid "The organization `{}` does not exist" msgstr "組織 '{}'は存在しません" @@ -255,7 +255,7 @@ msgstr "カテゴリ" #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 #: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 -#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20 +#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 @@ -303,7 +303,7 @@ msgstr "アプリ資産" msgid "{} disabled" msgstr "無効" -#: assets/const/account.py:6 assets/tasks/const.py:51 audits/const.py:5 +#: assets/const/account.py:6 audits/const.py:5 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/utils.py:84 msgid "Unknown" @@ -313,14 +313,14 @@ msgstr "不明" msgid "Ok" msgstr "OK" -#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:18 +#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:19 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 msgid "Failed" msgstr "失敗しました" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/base.py:53 assets/models/domain.py:71 +#: assets/models/base.py:52 assets/models/domain.py:71 #: assets/serializers/base.py:15 audits/signal_handlers.py:50 #: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 @@ -337,19 +337,19 @@ msgstr "失敗しました" msgid "Password" msgstr "パスワード" -#: assets/const/account.py:13 assets/models/base.py:54 +#: assets/const/account.py:13 assets/models/base.py:53 #, fuzzy #| msgid "SSH Key" msgid "SSH key" msgstr "SSHキー" -#: assets/const/account.py:14 assets/models/base.py:55 +#: assets/const/account.py:14 assets/models/base.py:54 #: authentication/models/access_key.py:31 msgid "Access key" msgstr "アクセスキー" #: assets/const/account.py:15 assets/models/_user.py:38 -#: assets/models/base.py:56 authentication/models/sso_token.py:13 +#: assets/models/base.py:55 authentication/models/sso_token.py:13 msgid "Token" msgstr "トークン" @@ -417,7 +417,7 @@ msgid "Replace (The key generated by JumpServer) " msgstr "置換(JumpServerによって生成された鍵)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:56 +#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:59 #: terminal/models/component/endpoint.py:12 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" @@ -499,22 +499,20 @@ msgstr "SSH秘密鍵" msgid "SSH public key" msgstr "SSHパブリックキー" -#: assets/models/_user.py:41 assets/models/automations/base.py:87 -#: assets/models/base.py:67 assets/models/domain.py:26 -#: assets/models/gathered_user.py:19 assets/models/group.py:22 -#: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 -#: orgs/models.py:73 perms/models/asset_permission.py:82 +#: assets/models/_user.py:41 assets/models/automations/base.py:96 +#: assets/models/domain.py:26 assets/models/gathered_user.py:19 +#: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 +#: ops/models/base.py:53 orgs/models.py:73 perms/models/asset_permission.py:82 #: users/models/group.py:18 users/models/user.py:927 msgid "Date created" msgstr "作成された日付" -#: assets/models/_user.py:42 assets/models/base.py:68 -#: assets/models/gathered_user.py:20 common/db/models.py:77 -#: common/mixins/models.py:51 +#: assets/models/_user.py:42 assets/models/gathered_user.py:20 +#: common/db/models.py:77 common/mixins/models.py:51 msgid "Date updated" msgstr "更新日" -#: assets/models/_user.py:43 assets/models/base.py:69 +#: assets/models/_user.py:43 assets/models/base.py:66 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 #: orgs/models.py:71 perms/models/asset_permission.py:81 @@ -582,34 +580,34 @@ msgstr "システムユーザー" msgid "Can match system user" msgstr "システムユーザーに一致できます" -#: assets/models/account.py:51 +#: assets/models/account.py:53 #, fuzzy #| msgid "Switch from" msgid "Su from" msgstr "から切り替え" -#: assets/models/account.py:53 settings/serializers/auth/cas.py:18 +#: assets/models/account.py:55 settings/serializers/auth/cas.py:18 #: terminal/models/applet/applet.py:22 msgid "Version" msgstr "バージョン" -#: assets/models/account.py:63 +#: assets/models/account.py:65 msgid "Can view asset account secret" msgstr "資産アカウントの秘密を表示できます" -#: assets/models/account.py:64 +#: assets/models/account.py:66 msgid "Can change asset account secret" msgstr "資産口座の秘密を変更できます" -#: assets/models/account.py:65 +#: assets/models/account.py:67 msgid "Can view asset history account" msgstr "資産履歴アカウントを表示できます" -#: assets/models/account.py:66 +#: assets/models/account.py:68 msgid "Can view asset history account secret" msgstr "資産履歴アカウントパスワードを表示できます" -#: assets/models/account.py:89 assets/serializers/account/account.py:13 +#: assets/models/account.py:91 assets/serializers/account/account.py:13 #, fuzzy #| msgid "Account name" msgid "Account template" @@ -642,9 +640,9 @@ msgid "Nodes" msgstr "ノード" #: assets/models/asset/common.py:98 assets/models/automations/base.py:25 -#: assets/models/cmd_filter.py:39 assets/models/domain.py:70 -#: assets/models/label.py:21 terminal/models/applet/applet.py:25 -#: users/serializers/user.py:147 +#: assets/models/base.py:64 assets/models/cmd_filter.py:39 +#: assets/models/domain.py:70 assets/models/label.py:21 +#: terminal/models/applet/applet.py:25 users/serializers/user.py:147 msgid "Is active" msgstr "アクティブです。" @@ -661,7 +659,9 @@ msgid "Can test asset connectivity" msgstr "資産接続をテストできます" #: assets/models/asset/common.py:232 -msgid "Can push system user to asset" +#, fuzzy +#| msgid "Can push system user to asset" +msgid "Can push account to asset" msgstr "システムユーザーを資産にプッシュできます" #: assets/models/asset/common.py:233 @@ -677,6 +677,7 @@ msgid "Move asset to node" msgstr "アセットをノードに移動する" #: assets/models/asset/web.py:9 audits/models.py:111 +#: terminal/serializers/applet_host.py:24 msgid "Disabled" msgstr "無効" @@ -723,15 +724,15 @@ msgstr "アカウント" msgid "Assets" msgstr "資産" -#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 +#: assets/models/automations/base.py:86 assets/models/automations/base.py:93 #, fuzzy #| msgid "Automatic managed" msgid "Automation task" msgstr "自動管理" -#: assets/models/automations/base.py:88 assets/models/backup.py:77 +#: assets/models/automations/base.py:97 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:32 +#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:102 #: terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:21 @@ -741,32 +742,32 @@ msgstr "自動管理" msgid "Date start" msgstr "開始日" -#: assets/models/automations/base.py:89 +#: assets/models/automations/base.py:98 #: assets/models/automations/change_secret.py:58 ops/models/base.py:55 -#: terminal/models/applet/host.py:33 +#: terminal/models/applet/host.py:103 msgid "Date finished" msgstr "終了日" -#: assets/models/automations/base.py:91 +#: assets/models/automations/base.py:100 #, fuzzy #| msgid "Relation snapshot" msgid "Automation snapshot" msgstr "製造オーダスナップショット" -#: assets/models/automations/base.py:95 assets/models/backup.py:88 +#: assets/models/automations/base.py:104 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "トリガーモード" -#: assets/models/automations/base.py:99 +#: assets/models/automations/base.py:108 #, fuzzy #| msgid "Command execution" msgid "Automation task execution" msgstr "コマンド実行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:62 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:60 #, fuzzy #| msgid "Secret key" msgid "Secret type" @@ -779,7 +780,7 @@ msgid "Secret strategy" msgstr "SSHキー戦略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:56 assets/models/base.py:64 +#: assets/models/automations/change_secret.py:56 assets/models/base.py:62 #: assets/serializers/account/base.py:17 #: authentication/models/connection_token.py:34 #: authentication/models/temp_token.py:10 @@ -825,7 +826,7 @@ msgstr "ひみつ" msgid "Date started" msgstr "開始日" -#: assets/models/automations/change_secret.py:60 common/const/choices.py:19 +#: assets/models/automations/change_secret.py:60 common/const/choices.py:20 #, fuzzy #| msgid "WeCom Error" msgid "Error" @@ -855,6 +856,12 @@ msgstr "資産ユーザーの収集" msgid "Gather asset facts" msgstr "資産ユーザーの収集" +#: assets/models/automations/ping.py:15 +#, fuzzy +#| msgid "Login asset" +msgid "Ping asset" +msgstr "ログイン資産" + #: assets/models/automations/push_account.py:16 #, fuzzy #| msgid "Is service account" @@ -902,15 +909,15 @@ msgstr "成功は" msgid "Account backup execution" msgstr "アカウントバックアップの実行" -#: assets/models/base.py:30 assets/serializers/domain.py:42 +#: assets/models/base.py:29 assets/serializers/domain.py:42 msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:32 authentication/models/temp_token.py:12 +#: assets/models/base.py:31 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:65 +#: assets/models/base.py:63 msgid "Privileged" msgstr "" @@ -1079,6 +1086,7 @@ msgid "Setting" msgstr "設定" #: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37 +#: terminal/serializers/applet_host.py:25 msgid "Enabled" msgstr "有効化" @@ -1415,60 +1423,18 @@ msgstr "パスワードには `'` を含まない" msgid "Password can not contains `\"` " msgstr "パスワードには `\"` を含まない" -#: assets/tasks/account_connectivity.py:30 -msgid "The asset {} system platform {} does not support run Ansible tasks" -msgstr "" -"資産 {} システムプラットフォーム {} はAnsibleタスクの実行をサポートしていませ" -"ん。" - -#: assets/tasks/account_connectivity.py:108 -msgid "Test account connectivity: " -msgstr "テストアカウント接続:" - -#: assets/tasks/asset_connectivity.py:49 -msgid "Test assets connectivity. " -msgstr "資産の接続性をテストします。" - -#: assets/tasks/asset_connectivity.py:94 assets/tasks/asset_connectivity.py:107 -msgid "Test assets connectivity: " -msgstr "資産の接続性のテスト:" - -#: assets/tasks/asset_connectivity.py:121 -msgid "Test if the assets under the node are connectable: " -msgstr "ノードの下のアセットが接続可能かどうかをテストします。" - -#: assets/tasks/const.py:49 -msgid "Unreachable" -msgstr "達成できない" - -#: assets/tasks/const.py:50 -msgid "Reachable" -msgstr "接続可能" - -#: assets/tasks/gather_asset_hardware_info.py:46 -msgid "Get asset info failed: {}" -msgstr "資産情報の取得に失敗しました: {}" - -#: assets/tasks/gather_asset_hardware_info.py:97 +#: assets/tasks/gather_facts.py:25 msgid "Update some assets hardware info. " msgstr "一部の資産ハードウェア情報を更新します。" -#: assets/tasks/gather_asset_hardware_info.py:118 -msgid "Update asset hardware info: " -msgstr "資産ハードウェア情報の更新:" - -#: assets/tasks/gather_asset_hardware_info.py:124 +#: assets/tasks/gather_facts.py:48 msgid "Update assets hardware info: " msgstr "資産のハードウェア情報を更新する:" -#: assets/tasks/gather_asset_hardware_info.py:146 +#: assets/tasks/gather_facts.py:58 msgid "Update node asset hardware information: " msgstr "ノード資産のハードウェア情報を更新します。" -#: assets/tasks/gather_asset_users.py:110 -msgid "Gather assets users" -msgstr "資産ユーザーの収集" - #: assets/tasks/nodes_amount.py:29 msgid "" "The task of self-checking is already running and cannot be started repeatedly" @@ -1476,6 +1442,24 @@ msgstr "" "セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ" "ん" +#: assets/tasks/ping.py:20 assets/tasks/ping.py:38 +#, fuzzy +#| msgid "Test assets connectivity. " +msgid "Test assets connectivity " +msgstr "資産の接続性をテストします。" + +#: assets/tasks/ping.py:48 +#, fuzzy +#| msgid "Test if the assets under the node are connectable: " +msgid "Test if the assets under the node are connectable " +msgstr "ノードの下のアセットが接続可能かどうかをテストします。" + +#: assets/tasks/push_account.py:36 +#, fuzzy +#| msgid "Create account successfully" +msgid "Push accounts to assets" +msgstr "アカウントを正常に作成" + #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" msgstr "資産が無効化されました。スキップ: {}" @@ -1492,6 +1476,12 @@ msgstr "セキュリティのために、ユーザー {} をプッシュしな msgid "No assets matched, stop task" msgstr "一致する資産がない、タスクを停止" +#: assets/tasks/verify_account.py:36 +#, fuzzy +#| msgid "Test account connectivity: " +msgid "Verify accounts connectivity" +msgstr "テストアカウント接続:" + #: audits/apps.py:9 msgid "Audits" msgstr "監査" @@ -1539,7 +1529,7 @@ msgstr "操作" msgid "Filename" msgstr "ファイル名" -#: audits/models.py:43 audits/models.py:117 common/const/choices.py:17 +#: audits/models.py:43 audits/models.py:117 common/const/choices.py:18 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" @@ -1619,8 +1609,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 ops/models/base.py:48 -#: terminal/models/applet/applet.py:57 terminal/models/applet/host.py:19 -#: terminal/models/applet/host.py:31 terminal/models/component/status.py:33 +#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:101 +#: terminal/models/component/status.py:33 terminal/serializers/applet.py:22 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 #: xpack/plugins/cloud/models.py:223 msgid "Status" @@ -2144,13 +2134,13 @@ msgstr "アセットがアクティブ化されていません" msgid "No account" msgstr "ログインacl" -#: authentication/models/connection_token.py:103 +#: authentication/models/connection_token.py:101 msgid "User has no permission to access asset or permission expired" msgstr "" "ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて" "います" -#: authentication/models/connection_token.py:145 +#: authentication/models/connection_token.py:144 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2576,15 +2566,19 @@ msgstr "手動トリガー" msgid "Timing trigger" msgstr "タイミングトリガー" -#: common/const/choices.py:15 tickets/const.py:29 tickets/const.py:37 +#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 +msgid "Ready" +msgstr "の準備を" + +#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:37 msgid "Pending" msgstr "未定" -#: common/const/choices.py:16 +#: common/const/choices.py:17 msgid "Running" msgstr "" -#: common/const/choices.py:20 +#: common/const/choices.py:21 #, fuzzy #| msgid "Cancel" msgid "Canceled" @@ -2653,6 +2647,12 @@ msgstr "解析ファイルエラー: {}" msgid "Children" msgstr "" +#: common/drf/serializers/common.py:94 +#, fuzzy +#| msgid "Filename" +msgid "File" +msgstr "ファイル名" + #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." @@ -3091,7 +3091,7 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 +#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:72 msgid "Organization" msgstr "組織" @@ -4928,50 +4928,52 @@ msgstr "認証アドレス" msgid "Tags" msgstr "" -#: terminal/models/applet/applet.py:29 terminal/serializers/storage.py:157 +#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 msgid "Hosts" msgstr "ホスト" -#: terminal/models/applet/applet.py:55 terminal/models/applet/host.py:21 +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:28 #, fuzzy #| msgid "Apply assets" msgid "Applet" msgstr "資産の適用" -#: terminal/models/applet/host.py:14 -#, fuzzy -#| msgid "Verify auth" -msgid "Account automation" -msgstr "パスワード/キーの確認" - -#: terminal/models/applet/host.py:15 terminal/serializers/applet.py:66 +#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:36 #, fuzzy #| msgid "More login options" msgid "Deploy options" msgstr "その他のログインオプション" -#: terminal/models/applet/host.py:16 +#: terminal/models/applet/host.py:20 msgid "Inited" msgstr "" -#: terminal/models/applet/host.py:17 +#: terminal/models/applet/host.py:21 #, fuzzy #| msgid "Date finished" msgid "Date inited" msgstr "終了日" -#: terminal/models/applet/host.py:18 +#: terminal/models/applet/host.py:22 #, fuzzy #| msgid "Date sync" msgid "Date synced" msgstr "日付の同期" -#: terminal/models/applet/host.py:30 +#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:183 +msgid "Terminal" +msgstr "ターミナル" + +#: terminal/models/applet/host.py:99 #, fuzzy #| msgid "Host" msgid "Hosting" msgstr "ホスト" +#: terminal/models/applet/host.py:100 +msgid "Initial" +msgstr "" + #: terminal/models/component/endpoint.py:14 msgid "HTTPS Port" msgstr "HTTPS ポート" @@ -5076,10 +5078,6 @@ msgstr "再生ストレージ" msgid "type" msgstr "タイプ" -#: terminal/models/component/terminal.py:183 -msgid "Terminal" -msgstr "ターミナル" - #: terminal/models/component/terminal.py:185 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" @@ -5196,49 +5194,61 @@ msgstr "レベル" msgid "Batch danger command alert" msgstr "一括危険コマンド警告" -#: terminal/serializers/applet.py:19 +#: terminal/serializers/applet.py:16 +#, fuzzy +#| msgid "Public key" +msgid "Published" +msgstr "公開キー" + +#: terminal/serializers/applet.py:17 +#, fuzzy +#| msgid "Finished" +msgid "Unpublished" +msgstr "終了" + +#: terminal/serializers/applet.py:18 +#, fuzzy +#| msgid "Phone not set" +msgid "Not match" +msgstr "電話が設定されていない" + +#: terminal/serializers/applet.py:32 msgid "Icon" msgstr "" -#: terminal/serializers/applet.py:53 -#, fuzzy -#| msgid "Phone not set" -msgid "Not set" -msgstr "電話が設定されていない" - -#: terminal/serializers/applet.py:54 +#: terminal/serializers/applet_host.py:20 #, fuzzy #| msgid "Session" msgid "Per Session" msgstr "セッション" -#: terminal/serializers/applet.py:55 +#: terminal/serializers/applet_host.py:21 msgid "Per Device" msgstr "" -#: terminal/serializers/applet.py:57 +#: terminal/serializers/applet_host.py:27 #, fuzzy #| msgid "License" msgid "RDS Licensing" msgstr "ライセンス" -#: terminal/serializers/applet.py:58 +#: terminal/serializers/applet_host.py:28 msgid "RDS License Server" msgstr "" -#: terminal/serializers/applet.py:59 +#: terminal/serializers/applet_host.py:29 msgid "RDS Licensing Mode" msgstr "" -#: terminal/serializers/applet.py:60 +#: terminal/serializers/applet_host.py:30 msgid "RDS fSingleSessionPerUser" msgstr "" -#: terminal/serializers/applet.py:61 +#: terminal/serializers/applet_host.py:31 msgid "RDS Max Disconnection Time" msgstr "" -#: terminal/serializers/applet.py:62 +#: terminal/serializers/applet_host.py:32 msgid "RDS Remote App Logoff Time Limit" msgstr "" @@ -5673,7 +5683,7 @@ msgstr "有効期限は開始日より大きくする必要があります" msgid "Permission named `{}` already exists" msgstr "'{}'という名前の権限は既に存在します" -#: tickets/serializers/ticket/ticket.py:99 +#: tickets/serializers/ticket/ticket.py:101 msgid "The ticket flow `{}` does not exist" msgstr "チケットフロー '{}'が存在しない" @@ -6377,10 +6387,6 @@ msgstr "公開鍵をnull、exitに設定することはできません。" msgid "Change auth plan snapshot" msgstr "計画スナップショットの暗号化" -#: xpack/plugins/change_auth_plan/models/base.py:183 -msgid "Ready" -msgstr "の準備を" - #: xpack/plugins/change_auth_plan/models/base.py:184 msgid "Preflight check" msgstr "プリフライトチェック" @@ -6991,11 +6997,11 @@ msgstr "テーマ" msgid "Interface setting" msgstr "インターフェイスの設定" -#: xpack/plugins/license/api.py:50 +#: xpack/plugins/license/api.py:53 msgid "License import successfully" msgstr "ライセンスのインポートに成功" -#: xpack/plugins/license/api.py:51 +#: xpack/plugins/license/api.py:54 msgid "License is invalid" msgstr "ライセンスが無効です" @@ -7019,6 +7025,34 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#, fuzzy +#~| msgid "Verify auth" +#~ msgid "Account automation" +#~ msgstr "パスワード/キーの確認" + +#~ msgid "The asset {} system platform {} does not support run Ansible tasks" +#~ msgstr "" +#~ "資産 {} システムプラットフォーム {} はAnsibleタスクの実行をサポートしてい" +#~ "ません。" + +#~ msgid "Test assets connectivity: " +#~ msgstr "資産の接続性のテスト:" + +#~ msgid "Unreachable" +#~ msgstr "達成できない" + +#~ msgid "Reachable" +#~ msgstr "接続可能" + +#~ msgid "Get asset info failed: {}" +#~ msgstr "資産情報の取得に失敗しました: {}" + +#~ msgid "Update asset hardware info: " +#~ msgstr "資産ハードウェア情報の更新:" + +#~ msgid "Gather assets users" +#~ msgstr "資産ユーザーの収集" + #, fuzzy #~| msgid "Automatic managed" #~ msgid "Push automation" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 6e116b000..9ba5f0837 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:08529907ac3879f60c2026f91e7ba3f48a3a7d288f7b29cd35c0f73bc3999c21 -size 103630 +oid sha256:0b396cc9a485f6474d14ca30a1a7ba4f954b07754148b964efbb21519c55b280 +size 102849 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 013be87be..131862b1a 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-01 15:30+0800\n" +"POT-Creation-Date: 2022-11-03 16:00+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -24,7 +24,7 @@ msgstr "访问控制" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:59 assets/models/cmd_filter.py:25 +#: assets/models/base.py:57 assets/models/cmd_filter.py:25 #: assets/models/domain.py:24 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:22 #: assets/models/platform.py:68 assets/serializers/asset/common.py:86 @@ -59,7 +59,7 @@ msgstr "激活中" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 #: assets/models/asset/common.py:100 assets/models/automations/base.py:26 -#: assets/models/backup.py:30 assets/models/base.py:66 +#: assets/models/backup.py:30 assets/models/base.py:65 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 @@ -67,7 +67,7 @@ msgstr "激活中" #: ops/models/playbook.py:25 orgs/models.py:74 #: perms/models/asset_permission.py:84 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 -#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:34 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:104 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 @@ -128,14 +128,14 @@ msgstr "审批人" msgid "Login acl" msgstr "登录访问控制" -#: acls/models/login_asset_acl.py:21 assets/models/account.py:57 +#: acls/models/login_asset_acl.py:21 assets/models/account.py:59 #: authentication/models/connection_token.py:33 ops/models/base.py:18 #: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 #: xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "账号" -#: acls/models/login_asset_acl.py:22 assets/models/account.py:47 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:49 #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:58 assets/serializers/label.py:30 @@ -163,7 +163,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 #: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 -#: assets/models/base.py:60 assets/models/gathered_user.py:15 +#: assets/models/base.py:58 assets/models/gathered_user.py:15 #: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -206,7 +206,7 @@ msgid "" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" #: acls/serializers/login_asset_acl.py:84 -#: tickets/serializers/ticket/ticket.py:85 +#: tickets/serializers/ticket/ticket.py:86 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" @@ -250,7 +250,7 @@ msgstr "类别" #: assets/models/platform.py:70 assets/serializers/asset/common.py:63 #: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 -#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:20 +#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 @@ -296,7 +296,7 @@ msgstr "资产管理" msgid "{} disabled" msgstr "{} 已禁用" -#: assets/const/account.py:6 assets/tasks/const.py:51 audits/const.py:5 +#: assets/const/account.py:6 audits/const.py:5 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/utils.py:84 msgid "Unknown" @@ -306,14 +306,14 @@ msgstr "未知" msgid "Ok" msgstr "成功" -#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:18 +#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:19 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 msgid "Failed" msgstr "失败" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/base.py:53 assets/models/domain.py:71 +#: assets/models/base.py:52 assets/models/domain.py:71 #: assets/serializers/base.py:15 audits/signal_handlers.py:50 #: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 @@ -330,17 +330,17 @@ msgstr "失败" msgid "Password" msgstr "密码" -#: assets/const/account.py:13 assets/models/base.py:54 +#: assets/const/account.py:13 assets/models/base.py:53 msgid "SSH key" msgstr "SSH 密钥" -#: assets/const/account.py:14 assets/models/base.py:55 +#: assets/const/account.py:14 assets/models/base.py:54 #: authentication/models/access_key.py:31 msgid "Access key" msgstr "Access key" #: assets/const/account.py:15 assets/models/_user.py:38 -#: assets/models/base.py:56 authentication/models/sso_token.py:13 +#: assets/models/base.py:55 authentication/models/sso_token.py:13 msgid "Token" msgstr "Token" @@ -398,7 +398,7 @@ msgid "Replace (The key generated by JumpServer) " msgstr "替换 (由 JumpServer 生成的密钥)" #: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:56 +#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:59 #: terminal/models/component/endpoint.py:12 #: xpack/plugins/cloud/serializers/account_attrs.py:72 msgid "Host" @@ -474,22 +474,20 @@ msgstr "SSH 密钥" msgid "SSH public key" msgstr "SSH 公钥" -#: assets/models/_user.py:41 assets/models/automations/base.py:87 -#: assets/models/base.py:67 assets/models/domain.py:26 -#: assets/models/gathered_user.py:19 assets/models/group.py:22 -#: common/db/models.py:76 common/mixins/models.py:50 ops/models/base.py:53 -#: orgs/models.py:73 perms/models/asset_permission.py:82 +#: assets/models/_user.py:41 assets/models/automations/base.py:96 +#: assets/models/domain.py:26 assets/models/gathered_user.py:19 +#: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 +#: ops/models/base.py:53 orgs/models.py:73 perms/models/asset_permission.py:82 #: users/models/group.py:18 users/models/user.py:927 msgid "Date created" msgstr "创建日期" -#: assets/models/_user.py:42 assets/models/base.py:68 -#: assets/models/gathered_user.py:20 common/db/models.py:77 -#: common/mixins/models.py:51 +#: assets/models/_user.py:42 assets/models/gathered_user.py:20 +#: common/db/models.py:77 common/mixins/models.py:51 msgid "Date updated" msgstr "更新日期" -#: assets/models/_user.py:43 assets/models/base.py:69 +#: assets/models/_user.py:43 assets/models/base.py:66 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 #: orgs/models.py:71 perms/models/asset_permission.py:81 @@ -557,32 +555,32 @@ msgstr "系统用户" msgid "Can match system user" msgstr "可以匹配系统用户" -#: assets/models/account.py:51 +#: assets/models/account.py:53 msgid "Su from" msgstr "切换自" -#: assets/models/account.py:53 settings/serializers/auth/cas.py:18 +#: assets/models/account.py:55 settings/serializers/auth/cas.py:18 #: terminal/models/applet/applet.py:22 msgid "Version" msgstr "版本" -#: assets/models/account.py:63 +#: assets/models/account.py:65 msgid "Can view asset account secret" msgstr "可以查看资产账号密码" -#: assets/models/account.py:64 +#: assets/models/account.py:66 msgid "Can change asset account secret" msgstr "可以更改资产账号密码" -#: assets/models/account.py:65 +#: assets/models/account.py:67 msgid "Can view asset history account" msgstr "可以查看资产历史账号" -#: assets/models/account.py:66 +#: assets/models/account.py:68 msgid "Can view asset history account secret" msgstr "可以查看资产历史账号密码" -#: assets/models/account.py:89 assets/serializers/account/account.py:13 +#: assets/models/account.py:91 assets/serializers/account/account.py:13 msgid "Account template" msgstr "账号模版" @@ -613,9 +611,9 @@ msgid "Nodes" msgstr "节点" #: assets/models/asset/common.py:98 assets/models/automations/base.py:25 -#: assets/models/cmd_filter.py:39 assets/models/domain.py:70 -#: assets/models/label.py:21 terminal/models/applet/applet.py:25 -#: users/serializers/user.py:147 +#: assets/models/base.py:64 assets/models/cmd_filter.py:39 +#: assets/models/domain.py:70 assets/models/label.py:21 +#: terminal/models/applet/applet.py:25 users/serializers/user.py:147 msgid "Is active" msgstr "激活" @@ -632,7 +630,9 @@ msgid "Can test asset connectivity" msgstr "可以测试资产连接性" #: assets/models/asset/common.py:232 -msgid "Can push system user to asset" +#, fuzzy +#| msgid "Can push system user to asset" +msgid "Can push account to asset" msgstr "可以推送系统用户到资产" #: assets/models/asset/common.py:233 @@ -648,6 +648,7 @@ msgid "Move asset to node" msgstr "移动资产到节点" #: assets/models/asset/web.py:9 audits/models.py:111 +#: terminal/serializers/applet_host.py:24 msgid "Disabled" msgstr "禁用" @@ -688,13 +689,13 @@ msgstr "账号管理" msgid "Assets" msgstr "资产" -#: assets/models/automations/base.py:77 assets/models/automations/base.py:84 +#: assets/models/automations/base.py:86 assets/models/automations/base.py:93 msgid "Automation task" msgstr "自动化任务" -#: assets/models/automations/base.py:88 assets/models/backup.py:77 +#: assets/models/automations/base.py:97 assets/models/backup.py:77 #: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:32 +#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:102 #: terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:21 @@ -704,28 +705,28 @@ msgstr "自动化任务" msgid "Date start" msgstr "开始日期" -#: assets/models/automations/base.py:89 +#: assets/models/automations/base.py:98 #: assets/models/automations/change_secret.py:58 ops/models/base.py:55 -#: terminal/models/applet/host.py:33 +#: terminal/models/applet/host.py:103 msgid "Date finished" msgstr "结束日期" -#: assets/models/automations/base.py:91 +#: assets/models/automations/base.py:100 msgid "Automation snapshot" msgstr "自动化快照" -#: assets/models/automations/base.py:95 assets/models/backup.py:88 +#: assets/models/automations/base.py:104 assets/models/backup.py:88 #: assets/serializers/account/backup.py:36 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "触发模式" -#: assets/models/automations/base.py:99 +#: assets/models/automations/base.py:108 msgid "Automation task execution" msgstr "自动化任务执行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:62 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:60 msgid "Secret type" msgstr "密文类型" @@ -734,7 +735,7 @@ msgid "Secret strategy" msgstr "密钥策略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:56 assets/models/base.py:64 +#: assets/models/automations/change_secret.py:56 assets/models/base.py:62 #: assets/serializers/account/base.py:17 #: authentication/models/connection_token.py:34 #: authentication/models/temp_token.py:10 @@ -772,7 +773,7 @@ msgstr "原来密码" msgid "Date started" msgstr "开始日期" -#: assets/models/automations/change_secret.py:60 common/const/choices.py:19 +#: assets/models/automations/change_secret.py:60 common/const/choices.py:20 msgid "Error" msgstr "错误" @@ -794,6 +795,12 @@ msgstr "收集资产信息" msgid "Gather asset facts" msgstr "收集资产信息" +#: assets/models/automations/ping.py:15 +#, fuzzy +#| msgid "Login asset" +msgid "Ping asset" +msgstr "登录资产" + #: assets/models/automations/push_account.py:16 #, fuzzy #| msgid "Is service account" @@ -841,15 +848,15 @@ msgstr "是否成功" msgid "Account backup execution" msgstr "账号备份执行" -#: assets/models/base.py:30 assets/serializers/domain.py:42 +#: assets/models/base.py:29 assets/serializers/domain.py:42 msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:32 authentication/models/temp_token.py:12 +#: assets/models/base.py:31 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:65 +#: assets/models/base.py:63 msgid "Privileged" msgstr "特权的" @@ -1015,6 +1022,7 @@ msgid "Setting" msgstr "设置" #: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37 +#: terminal/serializers/applet_host.py:25 msgid "Enabled" msgstr "启用" @@ -1306,63 +1314,41 @@ msgstr "密码不能包含 `'` 字符" msgid "Password can not contains `\"` " msgstr "密码不能包含 `\"` 字符" -#: assets/tasks/account_connectivity.py:30 -msgid "The asset {} system platform {} does not support run Ansible tasks" -msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" - -#: assets/tasks/account_connectivity.py:108 -msgid "Test account connectivity: " -msgstr "测试账号可连接性: " - -#: assets/tasks/asset_connectivity.py:49 -msgid "Test assets connectivity. " -msgstr "测试资产可连接性. " - -#: assets/tasks/asset_connectivity.py:94 assets/tasks/asset_connectivity.py:107 -msgid "Test assets connectivity: " -msgstr "测试资产可连接性: " - -#: assets/tasks/asset_connectivity.py:121 -msgid "Test if the assets under the node are connectable: " -msgstr "测试节点下资产是否可连接: " - -#: assets/tasks/const.py:49 -msgid "Unreachable" -msgstr "不可达" - -#: assets/tasks/const.py:50 -msgid "Reachable" -msgstr "可连接" - -#: assets/tasks/gather_asset_hardware_info.py:46 -msgid "Get asset info failed: {}" -msgstr "获取资产信息失败:{}" - -#: assets/tasks/gather_asset_hardware_info.py:97 +#: assets/tasks/gather_facts.py:25 msgid "Update some assets hardware info. " msgstr "更新资产硬件信息. " -#: assets/tasks/gather_asset_hardware_info.py:118 -msgid "Update asset hardware info: " -msgstr "更新资产硬件信息: " - -#: assets/tasks/gather_asset_hardware_info.py:124 +#: assets/tasks/gather_facts.py:48 msgid "Update assets hardware info: " msgstr "更新资产硬件信息: " -#: assets/tasks/gather_asset_hardware_info.py:146 +#: assets/tasks/gather_facts.py:58 msgid "Update node asset hardware information: " msgstr "更新节点资产硬件信息: " -#: assets/tasks/gather_asset_users.py:110 -msgid "Gather assets users" -msgstr "收集资产上的用户" - #: assets/tasks/nodes_amount.py:29 msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "自检程序已经在运行,不能重复启动" +#: assets/tasks/ping.py:20 assets/tasks/ping.py:38 +#, fuzzy +#| msgid "Test assets connectivity. " +msgid "Test assets connectivity " +msgstr "测试资产可连接性. " + +#: assets/tasks/ping.py:48 +#, fuzzy +#| msgid "Test if the assets under the node are connectable: " +msgid "Test if the assets under the node are connectable " +msgstr "测试节点下资产是否可连接: " + +#: assets/tasks/push_account.py:36 +#, fuzzy +#| msgid "Push account method" +msgid "Push accounts to assets" +msgstr "推送方式" + #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" msgstr "资产已经被禁用, 跳过: {}" @@ -1379,6 +1365,12 @@ msgstr "为了安全,禁止推送用户 {}" msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" +#: assets/tasks/verify_account.py:36 +#, fuzzy +#| msgid "Test account connectivity: " +msgid "Verify accounts connectivity" +msgstr "测试账号可连接性: " + #: audits/apps.py:9 msgid "Audits" msgstr "日志审计" @@ -1426,7 +1418,7 @@ msgstr "操作" msgid "Filename" msgstr "文件名" -#: audits/models.py:43 audits/models.py:117 common/const/choices.py:17 +#: audits/models.py:43 audits/models.py:117 common/const/choices.py:18 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" @@ -1506,8 +1498,8 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:128 ops/models/base.py:48 -#: terminal/models/applet/applet.py:57 terminal/models/applet/host.py:19 -#: terminal/models/applet/host.py:31 terminal/models/component/status.py:33 +#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:101 +#: terminal/models/component/status.py:33 terminal/serializers/applet.py:22 #: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 #: xpack/plugins/cloud/models.py:223 msgid "Status" @@ -2018,11 +2010,11 @@ msgstr "资产未激活" msgid "No account" msgstr "登录账号" -#: authentication/models/connection_token.py:103 +#: authentication/models/connection_token.py:101 msgid "User has no permission to access asset or permission expired" msgstr "用户没有权限访问资产或权限已过期" -#: authentication/models/connection_token.py:145 +#: authentication/models/connection_token.py:144 msgid "Super connection token" msgstr "超级连接令牌" @@ -2439,15 +2431,19 @@ msgstr "手动触发" msgid "Timing trigger" msgstr "定时触发" -#: common/const/choices.py:15 tickets/const.py:29 tickets/const.py:37 +#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 +msgid "Ready" +msgstr "准备" + +#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:37 msgid "Pending" msgstr "待定的" -#: common/const/choices.py:16 +#: common/const/choices.py:17 msgid "Running" msgstr "" -#: common/const/choices.py:20 +#: common/const/choices.py:21 #, fuzzy #| msgid "Cancel" msgid "Canceled" @@ -2515,6 +2511,12 @@ msgstr "解析文件错误: {}" msgid "Children" msgstr "" +#: common/drf/serializers/common.py:94 +#, fuzzy +#| msgid "Filename" +msgid "File" +msgstr "文件名" + #: common/exceptions.py:15 #, python-format msgid "%s object does not exist." @@ -2925,7 +2927,7 @@ msgstr "组织管理" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:71 +#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:72 msgid "Organization" msgstr "组织" @@ -4725,42 +4727,46 @@ msgstr "作者" msgid "Tags" msgstr "标签" -#: terminal/models/applet/applet.py:29 terminal/serializers/storage.py:157 +#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 msgid "Hosts" msgstr "主机" -#: terminal/models/applet/applet.py:55 terminal/models/applet/host.py:21 +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:28 msgid "Applet" msgstr "远程应用" -#: terminal/models/applet/host.py:14 -msgid "Account automation" -msgstr "账号自动化" - -#: terminal/models/applet/host.py:15 terminal/serializers/applet.py:66 +#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:36 #, fuzzy #| msgid "More login options" msgid "Deploy options" msgstr "其他方式登录" -#: terminal/models/applet/host.py:16 +#: terminal/models/applet/host.py:20 msgid "Inited" msgstr "" -#: terminal/models/applet/host.py:17 +#: terminal/models/applet/host.py:21 #, fuzzy #| msgid "Date finished" msgid "Date inited" msgstr "结束日期" -#: terminal/models/applet/host.py:18 +#: terminal/models/applet/host.py:22 msgid "Date synced" msgstr "最后同步日期" -#: terminal/models/applet/host.py:30 +#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:183 +msgid "Terminal" +msgstr "终端" + +#: terminal/models/applet/host.py:99 msgid "Hosting" msgstr "主机" +#: terminal/models/applet/host.py:100 +msgid "Initial" +msgstr "" + #: terminal/models/component/endpoint.py:14 msgid "HTTPS Port" msgstr "HTTPS 端口" @@ -4865,10 +4871,6 @@ msgstr "录像存储" msgid "type" msgstr "类型" -#: terminal/models/component/terminal.py:183 -msgid "Terminal" -msgstr "终端" - #: terminal/models/component/terminal.py:185 msgid "Can view terminal config" msgstr "可以查看终端配置" @@ -4985,43 +4987,51 @@ msgstr "级别" msgid "Batch danger command alert" msgstr "批量危险命令告警" -#: terminal/serializers/applet.py:19 +#: terminal/serializers/applet.py:16 +msgid "Published" +msgstr "已安装" + +#: terminal/serializers/applet.py:17 +msgid "Unpublished" +msgstr "未安装" + +#: terminal/serializers/applet.py:18 +msgid "Not match" +msgstr "不匹配" + +#: terminal/serializers/applet.py:32 msgid "Icon" msgstr "图标" -#: terminal/serializers/applet.py:53 -msgid "Not set" -msgstr "不设置" - -#: terminal/serializers/applet.py:54 +#: terminal/serializers/applet_host.py:20 msgid "Per Session" msgstr "按会话" -#: terminal/serializers/applet.py:55 +#: terminal/serializers/applet_host.py:21 msgid "Per Device" msgstr "按设备" -#: terminal/serializers/applet.py:57 +#: terminal/serializers/applet_host.py:27 msgid "RDS Licensing" msgstr "部署 RDS 许可服务" -#: terminal/serializers/applet.py:58 +#: terminal/serializers/applet_host.py:28 msgid "RDS License Server" msgstr "RDS 许可服务主机" -#: terminal/serializers/applet.py:59 +#: terminal/serializers/applet_host.py:29 msgid "RDS Licensing Mode" msgstr "RDS 许可模式" -#: terminal/serializers/applet.py:60 +#: terminal/serializers/applet_host.py:30 msgid "RDS fSingleSessionPerUser" msgstr "RDS 会话用户数" -#: terminal/serializers/applet.py:61 +#: terminal/serializers/applet_host.py:31 msgid "RDS Max Disconnection Time" msgstr "RDS 会话断开时间" -#: terminal/serializers/applet.py:62 +#: terminal/serializers/applet_host.py:32 msgid "RDS Remote App Logoff Time Limit" msgstr "RDS 远程应用注销时间" @@ -5447,7 +5457,7 @@ msgstr "过期时间要大于开始时间" msgid "Permission named `{}` already exists" msgstr "授权名称 `{}` 已存在" -#: tickets/serializers/ticket/ticket.py:99 +#: tickets/serializers/ticket/ticket.py:101 msgid "The ticket flow `{}` does not exist" msgstr "工单流程 `{}` 不存在" @@ -6136,10 +6146,6 @@ msgstr "公钥不能设置为空, 退出. " msgid "Change auth plan snapshot" msgstr "改密计划快照" -#: xpack/plugins/change_auth_plan/models/base.py:183 -msgid "Ready" -msgstr "准备" - #: xpack/plugins/change_auth_plan/models/base.py:184 msgid "Preflight check" msgstr "改密前的校验" @@ -6747,11 +6753,11 @@ msgstr "主题" msgid "Interface setting" msgstr "界面设置" -#: xpack/plugins/license/api.py:50 +#: xpack/plugins/license/api.py:53 msgid "License import successfully" msgstr "许可证导入成功" -#: xpack/plugins/license/api.py:51 +#: xpack/plugins/license/api.py:54 msgid "License is invalid" msgstr "无效的许可证" @@ -6775,6 +6781,30 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Account automation" +#~ msgstr "账号自动化" + +#~ msgid "The asset {} system platform {} does not support run Ansible tasks" +#~ msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" + +#~ msgid "Test assets connectivity: " +#~ msgstr "测试资产可连接性: " + +#~ msgid "Unreachable" +#~ msgstr "不可达" + +#~ msgid "Reachable" +#~ msgstr "可连接" + +#~ msgid "Get asset info failed: {}" +#~ msgstr "获取资产信息失败:{}" + +#~ msgid "Update asset hardware info: " +#~ msgstr "更新资产硬件信息: " + +#~ msgid "Gather assets users" +#~ msgstr "收集资产上的用户" + #~ msgid "Push automation" #~ msgstr "自动化推送" diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 4a627e451..a00814dd3 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -13,9 +13,9 @@ from rest_framework.response import Response from rest_framework.serializers import ValidationError from common.utils import is_uuid +from common.drf.serializers import FileSerializer from terminal import serializers from terminal.models import AppletPublication, Applet -from terminal.serializers import AppletUploadSerializer __all__ = ['AppletViewSet', 'AppletPublicationViewSet'] @@ -59,7 +59,7 @@ class DownloadUploadMixin: raise ValidationError({'error': 'Missing name in manifest.yml'}) return manifest, tmp_dir - @action(detail=False, methods=['post'], serializer_class=AppletUploadSerializer) + @action(detail=False, methods=['post'], serializer_class=FileSerializer) def upload(self, request, *args, **kwargs): manifest, tmp_dir = self.extract_and_check_file(request) name = manifest['name'] diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index c9e08e4eb..f434b5b89 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -2,6 +2,8 @@ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response +from common.drf.api import JMSModelViewSet +from orgs.utils import tmp_to_builtin_org from terminal import serializers from terminal.models import AppletHost, Applet, AppletHostDeployment from terminal.tasks import run_applet_host_deployment @@ -10,26 +12,34 @@ from terminal.tasks import run_applet_host_deployment __all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] -class AppletHostViewSet(viewsets.ModelViewSet): +class AppletHostViewSet(JMSModelViewSet): serializer_class = serializers.AppletHostSerializer queryset = AppletHost.objects.all() + rbac_perms = { + 'accounts': 'terminal.view_applethost', + 'reports': '*' + } - @action(methods=['post'], detail=True) - def report(self, request, *args, **kwargs): - # TODO: - # 1. 上报 安装的 Applets 每小时 - # 2. Host 和 Terminal 关联 + @action(methods=['post'], detail=True, serializer_class=serializers.AppletHostReportSerializer) + def reports(self, request, *args, **kwargs): + # 1. Host 和 Terminal 关联 + # 2. 上报 安装的 Applets 每小时 instance = self.get_object() - instance.sync() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + data = serializer.validated_data + instance.check_terminal_binding(request) + instance.check_applets_state(data['applets']) return Response({'msg': 'ok'}) - @action(methods=['get'], detail=True) + @action(methods=['get'], detail=True, serializer_class=serializers.AppletHostAccountSerializer) def accounts(self, request, *args, **kwargs): - # TODO: - # 1. 返回 host 上的所有用户, host 可以去创建和更新 每小时 - # 2. 密码长度最少 8 位,包含大小写字母和数字和特殊字符 - instance = self.get_object() - return Response(instance.get_accounts()) + host = self.get_object() + with tmp_to_builtin_org(system=1): + accounts = host.accounts.all().filter(privileged=False) + response = self.get_paginated_response_from_queryset(accounts) + return response class AppletHostDeploymentViewSet(viewsets.ModelViewSet): diff --git a/apps/terminal/api/component/status.py b/apps/terminal/api/component/status.py index 5e27926ea..05f424283 100644 --- a/apps/terminal/api/component/status.py +++ b/apps/terminal/api/component/status.py @@ -52,7 +52,7 @@ class StatusViewSet(viewsets.ModelViewSet): terminal_id = self.kwargs.get("terminal", None) if terminal_id: terminal = get_object_or_404(Terminal, id=terminal_id) - return terminal.status_set.all() + return terminal.status.all() return super().get_queryset() diff --git a/apps/terminal/api/component/terminal.py b/apps/terminal/api/component/terminal.py index a58181bf4..872a8e2e3 100644 --- a/apps/terminal/api/component/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -42,37 +42,6 @@ class TerminalViewSet(JMSBulkModelViewSet): self.perform_destroy(instance) return Response(status=status.HTTP_204_NO_CONTENT) - def create(self, request, *args, **kwargs): - if isinstance(request.data, list): - raise exceptions.BulkCreateNotSupport() - - name = request.data.get('name') - remote_ip = request.META.get('REMOTE_ADDR') - x_real_ip = request.META.get('X-Real-IP') - remote_addr = x_real_ip or remote_ip - - terminal = get_object_or_none(Terminal, name=name, is_deleted=False) - if terminal: - msg = 'Terminal name %s already used' % name - return Response({'msg': msg}, status=409) - - serializer = self.serializer_class(data={ - 'name': name, 'remote_addr': remote_addr - }) - - if serializer.is_valid(): - terminal = serializer.save() - - # App should use id, token get access key, if accepted - token = uuid.uuid4().hex - cache.set(token, str(terminal.id), 3600) - data = {"id": str(terminal.id), "token": token, "msg": "Need accept"} - return Response(data, status=201) - else: - data = serializer.errors - logger.error("Register terminal error: {}".format(data)) - return Response(data, status=400) - def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) s = self.request.query_params.get('status') diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 7289f3180..91d6f5659 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -50,6 +50,7 @@ class TerminalTypeChoices(TextChoices): celery = 'celery', 'Celery' magnus = 'magnus', 'Magnus' razor = 'razor', 'Razor' + tinker = 'tinker', 'Tinker' @classmethod def types(cls): diff --git a/apps/terminal/migrations/0050_auto_20220606_1745.py b/apps/terminal/migrations/0050_auto_20220606_1745.py index 88e7cc138..e88d37971 100644 --- a/apps/terminal/migrations/0050_auto_20220606_1745.py +++ b/apps/terminal/migrations/0050_auto_20220606_1745.py @@ -13,6 +13,10 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='terminal', name='type', - field=models.CharField(choices=[('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'), ('xrdp', 'Xrdp'), ('lion', 'Lion'), ('core', 'Core'), ('celery', 'Celery'), ('magnus', 'Magnus'), ('razor', 'Razor')], default='koko', max_length=64, verbose_name='type'), + field=models.CharField(choices=[ + ('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'), + ('xrdp', 'Xrdp'), ('lion', 'Lion'), ('core', 'Core'), ('celery', 'Celery'), + ('magnus', 'Magnus'), ('razor', 'Razor'), ('tinker', 'Tinker'), + ], default='koko', max_length=64, verbose_name='type'), ), ] diff --git a/apps/terminal/migrations/0054_auto_20221027_1125.py b/apps/terminal/migrations/0054_auto_20221027_1125.py index 9a3bf4b85..9fde61be2 100644 --- a/apps/terminal/migrations/0054_auto_20221027_1125.py +++ b/apps/terminal/migrations/0054_auto_20221027_1125.py @@ -39,7 +39,6 @@ class Migration(migrations.Migration): name='AppletHost', fields=[ ('host_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.host')), - ('account_automation', models.BooleanField(default=False, verbose_name='Account automation')), ('date_synced', models.DateTimeField(blank=True, null=True, verbose_name='Date synced')), ('status', models.CharField(max_length=16, verbose_name='Status')), ], diff --git a/apps/terminal/migrations/0058_auto_20221103_1624.py b/apps/terminal/migrations/0058_auto_20221103_1624.py new file mode 100644 index 000000000..0c8091e5c --- /dev/null +++ b/apps/terminal/migrations/0058_auto_20221103_1624.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.14 on 2022-11-03 08:24 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0057_auto_20221102_1941'), + ] + + operations = [ + migrations.AlterModelOptions( + name='terminal', + options={'permissions': (('view_terminalconfig', 'Can view terminal config'),), 'verbose_name': 'Terminal'}, + ), + migrations.RemoveField( + model_name='terminal', + name='http_port', + ), + migrations.RemoveField( + model_name='terminal', + name='is_accepted', + ), + migrations.RemoveField( + model_name='terminal', + name='ssh_port', + ), + migrations.RemoveField( + model_name='applethost', + name='status', + ), + ] diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index e21e173df..073ccc1fb 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -1,7 +1,14 @@ +import os +from collections import defaultdict + from django.db import models from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from rest_framework.exceptions import ValidationError +from simple_history.utils import bulk_create_with_history from common.db.models import JMSBaseModel +from common.utils import random_string from assets.models import Host @@ -9,14 +16,10 @@ __all__ = ['AppletHost', 'AppletHostDeployment'] class AppletHost(Host): - LOCKING_ORG = '00000000-0000-0000-0000-000000000004' - - account_automation = models.BooleanField(default=False, verbose_name=_('Account automation')) deploy_options = models.JSONField(default=dict, verbose_name=_('Deploy options')) inited = models.BooleanField(default=False, verbose_name=_('Inited')) date_inited = models.DateTimeField(null=True, blank=True, verbose_name=_('Date inited')) date_synced = models.DateTimeField(null=True, blank=True, verbose_name=_('Date synced')) - status = models.CharField(max_length=16, verbose_name=_('Status')) terminal = models.OneToOneField( 'terminal.Terminal', on_delete=models.PROTECT, null=True, blank=True, related_name='applet_host', verbose_name=_('Terminal') @@ -25,10 +28,78 @@ class AppletHost(Host): 'Applet', verbose_name=_('Applet'), through='AppletPublication', through_fields=('host', 'applet'), ) + LOCKING_ORG = '00000000-0000-0000-0000-000000000004' def __str__(self): return self.name + @property + def status(self): + if self.terminal: + return 'online' + return self.terminal.status + + def check_terminal_binding(self, request): + request_terminal = getattr(request.user, 'terminal', None) + if not request_terminal: + raise ValidationError('Request user has no terminal') + + self.date_synced = timezone.now() + if not self.terminal: + self.terminal = request_terminal + self.save(update_fields=['terminal', 'date_synced']) + elif self.terminal and self.terminal != request_terminal: + raise ValidationError('Terminal has been set') + else: + self.save(update_fields=['date_synced']) + + def check_applets_state(self, applets_value_list): + applets = self.applets.all() + name_version_mapper = { + value['name']: value['version'] + for value in applets_value_list + } + + status_applets = defaultdict(list) + for applet in applets: + if applet.name not in name_version_mapper: + status_applets['unpublished'].append(applet) + elif applet.version != name_version_mapper[applet.name]: + status_applets['not_match'].append(applet) + else: + status_applets['published'].append(applet) + + for status, applets in status_applets.items(): + self.publications.filter(applet__in=applets)\ + .exclude(status=status)\ + .update(status=status) + + @staticmethod + def random_username(): + return 'jms_' + random_string(8) + + @staticmethod + def random_password(): + return random_string(16, special_char=True) + + def generate_accounts(self): + amount = int(os.getenv('TERMINAL_ACCOUNTS_AMOUNT', 100)) + now_count = self.accounts.filter(privileged=False).count() + need = amount - now_count + + accounts = [] + account_model = self.accounts.model + for i in range(need): + username = self.random_username() + password = self.random_password() + account = account_model( + username=username, secret=password, name=username, + asset_id=self.id, secret_type='password', version=1, + org_id=self.LOCKING_ORG + ) + accounts.append(account) + bulk_create_with_history(accounts, account_model, batch_size=20) + class AppletHostDeployment(JMSBaseModel): host = models.ForeignKey('AppletHost', on_delete=models.CASCADE, verbose_name=_('Hosting')) diff --git a/apps/terminal/models/component/status.py b/apps/terminal/models/component/status.py index 1ecfc0e2a..863dea9dd 100644 --- a/apps/terminal/models/component/status.py +++ b/apps/terminal/models/component/status.py @@ -22,7 +22,7 @@ class Status(models.Model): connections = models.IntegerField(verbose_name=_("Connections"), default=0) threads = models.IntegerField(verbose_name=_("Threads"), default=0) boot_time = models.FloatField(verbose_name=_("Boot Time"), default=0) - terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.CASCADE) + terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.CASCADE, related_name='status') date_created = models.DateTimeField(auto_now_add=True) CACHE_KEY = 'TERMINAL_STATUS_{}' diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index 4a95fbd3a..c24b0bd86 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -9,7 +9,7 @@ from common.utils import get_logger from users.models import User from orgs.utils import tmp_to_root_org from .status import Status -from terminal import const +from terminal.const import TerminalTypeChoices as TypeChoices from terminal.const import ComponentStatusChoices as StatusChoice from ..session import Session @@ -99,16 +99,13 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) type = models.CharField( - choices=const.TerminalTypeChoices.choices, default=const.TerminalTypeChoices.koko.value, + choices=TypeChoices.choices, default=TypeChoices.koko, max_length=64, verbose_name=_('type') ) remote_addr = models.CharField(max_length=128, blank=True, verbose_name=_('Remote Address')) - ssh_port = models.IntegerField(verbose_name=_('SSH Port'), default=2222) - http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000) command_storage = models.CharField(max_length=128, verbose_name=_("Command storage"), default='default') replay_storage = models.CharField(max_length=128, verbose_name=_("Replay storage"), default='default') user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE) - is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted') is_deleted = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -167,9 +164,7 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): def __str__(self): status = "Active" - if not self.is_accepted: - status = "NotAccept" - elif self.is_deleted: + if self.is_deleted: status = "Deleted" elif not self.is_active: status = "Disable" @@ -178,7 +173,6 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): return '%s: %s' % (self.name, status) class Meta: - ordering = ('is_accepted',) db_table = "terminal" verbose_name = _("Terminal") permissions = ( diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index 183a34c93..b5aa4db68 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -1,22 +1,22 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ +from django.db import models from common.drf.fields import ObjectRelatedField, LabeledChoiceField -from common.const.choices import Status from ..models import Applet, AppletPublication, AppletHost __all__ = [ 'AppletSerializer', 'AppletPublicationSerializer', - 'AppletUploadSerializer', ] -class AppletUploadSerializer(serializers.Serializer): - file = serializers.FileField() - - class AppletPublicationSerializer(serializers.ModelSerializer): + class Status(models.TextChoices): + PUBLISHED = 'published', _('Published') + UNPUBLISHED = 'unpublished', _('Unpublished') + NOT_MATCH = 'not_match', _('Not match') + applet = ObjectRelatedField(attrs=('id', 'display_name', 'icon', 'version'), queryset=Applet.objects.all()) host = ObjectRelatedField(queryset=AppletHost.objects.all()) status = LabeledChoiceField(choices=Status.choices, label=_("Status")) diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py index b87ee78d0..10ca442c1 100644 --- a/apps/terminal/serializers/applet_host.py +++ b/apps/terminal/serializers/applet_host.py @@ -2,7 +2,8 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.validators import ProjectUniqueValidator -from assets.models import Platform +from common.drf.fields import ObjectRelatedField +from assets.models import Platform, Account from assets.serializers import HostSerializer from ..models import AppletHost, AppletHostDeployment, Applet from .applet import AppletSerializer @@ -10,6 +11,7 @@ from .applet import AppletSerializer __all__ = [ 'AppletHostSerializer', 'AppletHostDeploymentSerializer', + 'AppletHostAccountSerializer', 'AppletHostReportSerializer' ] @@ -36,7 +38,7 @@ class AppletHostSerializer(HostSerializer): class Meta(HostSerializer.Meta): model = AppletHost fields = HostSerializer.Meta.fields + [ - 'account_automation', 'status', 'date_synced', 'deploy_options' + 'status', 'date_synced', 'deploy_options' ] extra_kwargs = { 'status': {'read_only': True}, @@ -86,3 +88,13 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer): 'date_start', 'date_finished' ] fields = fields_mini + ['comment'] + read_only_fields + + +class AppletHostAccountSerializer(serializers.ModelSerializer): + class Meta: + model = Account + fields = ['id', 'username', 'secret', 'date_updated'] + + +class AppletHostReportSerializer(serializers.Serializer): + applets = ObjectRelatedField(attrs=('id', 'name', 'version'), queryset=Applet.objects.all(), many=True) diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index 1ef4f6158..f0ac5a7c9 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -3,8 +3,8 @@ from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import BulkModelSerializer from common.utils import is_uuid -from users.serializers import ServiceAccountSerializer from common.utils import get_request_ip, pretty_string +from users.serializers import ServiceAccountSerializer from .. import const from ..models import ( diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index bac9c3253..cbbed376b 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -4,7 +4,6 @@ from django.db.models.signals import post_save from django.dispatch import receiver - from .models import Applet, AppletHost @@ -14,6 +13,7 @@ def on_applet_host_create(sender, instance, created=False, **kwargs): return applets = Applet.objects.all() instance.applets.set(applets) + instance.generate_accounts() @receiver(post_save, sender=Applet) From 54f92e100e32c7a36e213b40607ba9c914062c39 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:57:34 +0800 Subject: [PATCH 285/488] perf: account backup (#9013) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/account/backup.py | 5 ++--- .../{backup => backup_account}/__init__.py | 0 .../{backup => backup_account}/handlers.py | 7 ++++--- .../{backup => backup_account}/manager.py | 2 +- apps/assets/automations/endpoint.py | 4 +++- .../0109_rename_categories_to_types.py | 18 ++++++++++++++++++ apps/assets/models/backup.py | 19 +++++++++++-------- 7 files changed, 39 insertions(+), 16 deletions(-) rename apps/assets/automations/{backup => backup_account}/__init__.py (100%) rename apps/assets/automations/{backup => backup_account}/handlers.py (97%) rename apps/assets/automations/{backup => backup_account}/manager.py (97%) create mode 100644 apps/assets/migrations/0109_rename_categories_to_types.py diff --git a/apps/assets/api/account/backup.py b/apps/assets/api/account/backup.py index 79ae721f8..ff46c3caf 100644 --- a/apps/assets/api/account/backup.py +++ b/apps/assets/api/account/backup.py @@ -4,6 +4,7 @@ from rest_framework import status, viewsets from rest_framework.response import Response from orgs.mixins.api import OrgBulkModelViewSet +from common.const.choices import Trigger from assets import serializers from assets.tasks import execute_account_backup_plan from assets.models import ( @@ -38,9 +39,7 @@ class AccountBackupPlanExecutionViewSet(viewsets.ModelViewSet): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) pid = serializer.data.get('plan') - task = execute_account_backup_plan.delay( - pid=pid, trigger=AccountBackupPlanExecution.Trigger.manual - ) + task = execute_account_backup_plan.delay(pid=pid, trigger=Trigger.manual) return Response({'task': task.id}, status=status.HTTP_201_CREATED) def filter_queryset(self, queryset): diff --git a/apps/assets/automations/backup/__init__.py b/apps/assets/automations/backup_account/__init__.py similarity index 100% rename from apps/assets/automations/backup/__init__.py rename to apps/assets/automations/backup_account/__init__.py diff --git a/apps/assets/automations/backup/handlers.py b/apps/assets/automations/backup_account/handlers.py similarity index 97% rename from apps/assets/automations/backup/handlers.py rename to apps/assets/automations/backup_account/handlers.py index 2addebb86..c95f823a4 100644 --- a/apps/assets/automations/backup/handlers.py +++ b/apps/assets/automations/backup_account/handlers.py @@ -82,15 +82,16 @@ class AssetAccountHandler(BaseAccountHandler): # TODO 可以优化一下查询 在账号上做 category 的缓存 避免数据量大时连表操作 qs = Account.objects.filter( - asset__platform__category__in=categories - ).annotate(category=F('asset__platform__category')) + asset__platform__type__in=categories + ).annotate(category=F('asset__platform__type')) + print(qs, categories) if not qs.exists(): return data_map category_dict = {} for i in AllTypes.grouped_choices_to_objs(): for j in i['children']: - category_dict[j['value']] = j['label'] + category_dict[j['value']] = j['display_name'] header_fields = cls.get_header_fields(AccountSecretSerializer(qs.first())) account_category_map = defaultdict(list) diff --git a/apps/assets/automations/backup/manager.py b/apps/assets/automations/backup_account/manager.py similarity index 97% rename from apps/assets/automations/backup/manager.py rename to apps/assets/automations/backup_account/manager.py index c9558fea0..311361c69 100644 --- a/apps/assets/automations/backup/manager.py +++ b/apps/assets/automations/backup_account/manager.py @@ -12,7 +12,7 @@ from .handlers import AccountBackupHandler logger = get_logger(__name__) -class AccountBackupExecutionManager: +class AccountBackupManager: def __init__(self, execution): self.execution = execution self.date_start = timezone.now() diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index c6eb04593..eb7b2f4ca 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -3,6 +3,7 @@ from .gather_facts.manager import GatherFactsManager from .gather_accounts.manager import GatherAccountsManager from .verify_account.manager import VerifyAccountManager from .push_account.manager import PushAccountManager +from .backup_account.manager import AccountBackupManager from ..const import AutomationTypes @@ -13,6 +14,8 @@ class ExecutionManager: AutomationTypes.gather_accounts: GatherAccountsManager, AutomationTypes.verify_account: VerifyAccountManager, AutomationTypes.push_account: PushAccountManager, + # TODO 后期迁移到自动化策略中 + 'backup_account': AccountBackupManager, } def __init__(self, execution): @@ -21,4 +24,3 @@ class ExecutionManager: def run(self, *args, **kwargs): return self._runner.run(*args, **kwargs) - diff --git a/apps/assets/migrations/0109_rename_categories_to_types.py b/apps/assets/migrations/0109_rename_categories_to_types.py new file mode 100644 index 000000000..aa7fcad8f --- /dev/null +++ b/apps/assets/migrations/0109_rename_categories_to_types.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-03 08:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0108_auto_20221027_1053'), + ] + + operations = [ + migrations.RenameField( + model_name='accountbackupplan', + old_name='categories', + new_name='types', + ), + ] diff --git a/apps/assets/models/backup.py b/apps/assets/models/backup.py index 3a27adb45..3cf49a94d 100644 --- a/apps/assets/models/backup.py +++ b/apps/assets/models/backup.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- # import uuid -from functools import reduce from celery import current_task from django.db import models @@ -11,9 +10,9 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.models import OrgModelMixin from ops.mixin import PeriodTaskModelMixin from common.utils import get_logger +from common.const.choices import Trigger from common.db.encoder import ModelJSONFieldEncoder from common.mixins.models import CommonModelMixin -from common.const.choices import Trigger __all__ = ['AccountBackupPlan', 'AccountBackupPlanExecution'] @@ -22,7 +21,7 @@ logger = get_logger(__file__) class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - categories = models.JSONField(default=list) + types = models.JSONField(default=list) recipients = models.ManyToManyField( 'users.User', related_name='recipient_escape_route_plans', blank=True, verbose_name=_("Recipient") @@ -53,7 +52,7 @@ class AccountBackupPlan(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): 'crontab': self.crontab, 'org_id': self.org_id, 'created_by': self.created_by, - 'categories': self.categories, + 'types': self.types, 'recipients': { str(recipient.id): (str(recipient), bool(recipient.secret_key)) for recipient in self.recipients.all() @@ -100,9 +99,9 @@ class AccountBackupPlanExecution(OrgModelMixin): verbose_name = _('Account backup execution') @property - def categories(self): - categories = self.plan_snapshot.get('categories') - return categories + def types(self): + types = self.plan_snapshot.get('types') + return types @property def recipients(self): @@ -111,7 +110,11 @@ class AccountBackupPlanExecution(OrgModelMixin): return [] return recipients.values() + @property + def manager_type(self): + return 'backup_account' + def start(self): - from ..task_handlers import ExecutionManager + from assets.automations.endpoint import ExecutionManager manager = ExecutionManager(execution=self) return manager.run() From 7560a5cd1f3a1d32f49ea92392420a89e731e623 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 3 Nov 2022 18:03:46 +0800 Subject: [PATCH 286/488] perf: deploy applet host --- .../automations/deploy_applet_host/__init__.py | 15 +++++++++------ .../automations/deploy_applet_host/playbook.yml | 6 ++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py index c02aa491a..8b415ece4 100644 --- a/apps/terminal/automations/deploy_applet_host/__init__.py +++ b/apps/terminal/automations/deploy_applet_host/__init__.py @@ -27,13 +27,19 @@ class DeployAppletHostManager: def generate_playbook(self): playbook_src = os.path.join(CURRENT_DIR, 'playbook.yml') + base_site_url = settings.BASE_SITE_URL + bootstrap_token = settings.BOOTSTRAP_TOKEN + host_id = str(self.deployment.host.id) + if not base_site_url: + base_site_url = "localhost:8080" with open(playbook_src) as f: plays = yaml.safe_load(f) for play in plays: play['vars'].update(self.deployment.host.deploy_options) - play['vars']['DownloadHost'] = settings.BASE_URL + '/download/' - play['vars']['CORE_HOST'] = settings.BASE_URL - play['vars']['BOOTSTRAP_TOKEN'] = settings.BOOSTRAP_TOKEN + play['vars']['DownloadHost'] = base_site_url + '/download/' + play['vars']['CORE_HOST'] = base_site_url + play['vars']['BOOTSTRAP_TOKEN'] = bootstrap_token + play['vars']['HOST_ID'] = host_id play['vars']['HOST_NAME'] = self.deployment.host.name playbook_dir = os.path.join(self.run_dir, 'playbook') @@ -70,6 +76,3 @@ class DeployAppletHostManager: self.deployment.date_finished = timezone.now() with safe_db_connection(): self.deployment.save() - - - diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index b2c64f0cb..867d58f76 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -5,6 +5,7 @@ DownloadHost: https://demo.jumpserver.org/download Initial: 0 HOST_NAME: test + HOST_ID: 00000000-0000-0000-0000-000000000000 CORE_HOST: https://demo.jumpserver.org BOOTSTRAP_TOKEN: PleaseChangeMe RDS_Licensing: true @@ -38,7 +39,7 @@ - name: Install JumpServer Remoteapp agent (jumpserver) ansible.windows.win_package: path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" - args: + arguments: - /VERYSILENT - /SUPPRESSMSGBOXES - /NORESTART @@ -154,7 +155,8 @@ - name: Generate component config ansible.windows.win_shell: - "remoteapp-server config --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}" + "remoteapp-server config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} + --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}" - name: Install remoteapp-server service ansible.windows.win_shell: From ebfc3b7b38e98ee6eca4d0c5ab24f564c05264f7 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 3 Nov 2022 22:39:48 +0800 Subject: [PATCH 287/488] perf: change secret (#9014) Co-authored-by: feng <1304903146@qq.com> --- .../automations/change_secret/manager.py | 64 ++++++++++++++++++- .../0110_changesecretrecord_asset.py | 19 ++++++ apps/assets/models/automations/base.py | 7 ++ .../models/automations/change_secret.py | 1 + apps/assets/models/base.py | 1 - apps/assets/notifications.py | 28 +++++++- apps/assets/serializers/__init__.py | 1 + apps/assets/serializers/automation.py | 35 ++++++++++ 8 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 apps/assets/migrations/0110_changesecretrecord_asset.py create mode 100644 apps/assets/serializers/automation.py diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index 4f77dfe85..fd289b12f 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -1,17 +1,28 @@ +import os +import time import random import string from copy import deepcopy +from openpyxl import Workbook from collections import defaultdict from django.utils import timezone +from django.conf import settings -from common.utils import lazyproperty, gen_key_pair +from common.utils.timezone import local_now_display +from common.utils.file import encrypt_and_compress_zip_file +from common.utils import get_logger, lazyproperty, gen_key_pair +from users.models import User from assets.models import ChangeSecretRecord +from assets.notifications import ChangeSecretExecutionTaskMsg +from assets.serializers import ChangeSecretRecordBackUpSerializer from assets.const import ( AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy, DEFAULT_PASSWORD_RULES ) from ..base.manager import BasePlaybookManager +logger = get_logger(__name__) + class ChangeSecretManager(BasePlaybookManager): def __init__(self, *args, **kwargs): @@ -125,7 +136,7 @@ class ChangeSecretManager(BasePlaybookManager): new_secret = self.get_secret() recorder = ChangeSecretRecord( - account=account, execution=self.execution, + asset=asset, account=account, execution=self.execution, old_secret=account.secret, new_secret=new_secret, ) records.append(recorder) @@ -172,4 +183,51 @@ class ChangeSecretManager(BasePlaybookManager): recorder.save() def on_runner_failed(self, runner, e): - pass + logger.error("Change secret error: ", e) + + def run(self, *args, **kwargs): + super().run(*args, **kwargs) + recorders = self.name_recorder_mapper.values() + recorders = list(recorders) + self.send_recorder_mail(recorders) + + def send_recorder_mail(self, recorders): + if not recorders: + return + recipients = self.execution.recipients + if not recipients: + return + recipients = User.objects.filter(id__in=list(recipients)) + + name = self.execution.snapshot['name'] + path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') + filename = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.xlsx') + if not self.create_file(recorders, filename): + return + + for user in recipients: + attachments = [] + if user.secret_key: + password = user.secret_key.encode('utf8') + attachment = os.path.join(path, f'{name}-{local_now_display()}-{time.time()}.zip') + encrypt_and_compress_zip_file(attachment, password, [filename]) + attachments = [attachment] + ChangeSecretExecutionTaskMsg(name, user).publish(attachments) + os.remove(filename) + + @staticmethod + def create_file(recorders, filename): + serializer_cls = ChangeSecretRecordBackUpSerializer + serializer = serializer_cls(recorders, many=True) + header = [v.label for v in serializer.child.fields.values()] + rows = [list(row.values()) for row in serializer.data] + if not rows: + return False + + rows.insert(0, header) + wb = Workbook(filename) + ws = wb.create_sheet('Sheet1') + for row in rows: + ws.append(row) + wb.save(filename) + return True diff --git a/apps/assets/migrations/0110_changesecretrecord_asset.py b/apps/assets/migrations/0110_changesecretrecord_asset.py new file mode 100644 index 000000000..7a4e862ff --- /dev/null +++ b/apps/assets/migrations/0110_changesecretrecord_asset.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.14 on 2022-11-03 13:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0109_rename_categories_to_types'), + ] + + operations = [ + migrations.AddField( + model_name='changesecretrecord', + name='asset', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.asset'), + ), + ] diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index ac1fdb046..5eadca8c4 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -111,6 +111,13 @@ class AutomationExecution(OrgModelMixin): def manager_type(self): return self.snapshot['type'] + @property + def recipients(self): + recipients = self.snapshot.get('recipients') + if not recipients: + return [] + return recipients.values() + def start(self): from assets.automations.endpoint import ExecutionManager manager = ExecutionManager(execution=self) diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index 53ca08aba..81871fb3b 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -51,6 +51,7 @@ class ChangeSecretAutomation(BaseAutomation): class ChangeSecretRecord(JMSBaseModel): execution = models.ForeignKey('assets.AutomationExecution', on_delete=models.CASCADE) + asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, null=True) account = models.ForeignKey('assets.Account', on_delete=models.CASCADE, null=True) old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret')) new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 9f4f05649..8fa91b2cb 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -158,7 +158,6 @@ class BaseAccount(OrgModelMixin): return { 'name': self.name, 'username': self.username, - 'password': self.password, 'public_key': self.public_key, } diff --git a/apps/assets/notifications.py b/apps/assets/notifications.py index 58c02686c..6a67878c9 100644 --- a/apps/assets/notifications.py +++ b/apps/assets/notifications.py @@ -15,11 +15,35 @@ class AccountBackupExecutionTaskMsg(object): def message(self): name = self.name if self.user.secret_key: - return _('{} - The account backup passage task has been completed. See the attachment for details').format(name) + return _('{} - The account backup passage task has been completed. See the attachment for details').format( + name) return _("{} - The account backup passage task has been completed: the encryption password has not been set - " - "please go to personal information -> file encryption password to set the encryption password").format(name) + "please go to personal information -> file encryption password to set the encryption password").format( + name) def publish(self, attachment_list=None): send_mail_attachment_async.delay( self.subject, self.message, [self.user.email], attachment_list ) + + +class ChangeSecretExecutionTaskMsg(object): + subject = _('Notification of implementation result of encryption change plan') + + def __init__(self, name: str, user: User): + self.name = name + self.user = user + + @property + def message(self): + name = self.name + if self.user.secret_key: + return _('{} - The encryption change task has been completed. See the attachment for details').format(name) + return _("{} - The encryption change task has been completed: the encryption password has not been set - " + "please go to personal information -> file encryption password to set the encryption password").format( + name) + + def publish(self, attachments=None): + send_mail_attachment_async.delay( + self.subject, self.message, [self.user.email], attachments + ) diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 586c6f2e5..252b2dc64 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -11,3 +11,4 @@ from .account import * from assets.serializers.account.backup import * from .platform import * from .cagegory import * +from .automation import * diff --git a/apps/assets/serializers/automation.py b/apps/assets/serializers/automation.py new file mode 100644 index 000000000..482f95fc8 --- /dev/null +++ b/apps/assets/serializers/automation.py @@ -0,0 +1,35 @@ +from django.utils.translation import ugettext as _ +from rest_framework import serializers + +from common.utils import get_logger + +from assets.models import ChangeSecretRecord + +logger = get_logger(__file__) + + +class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer): + asset = serializers.SerializerMethodField(label=_('Asset')) + account = serializers.SerializerMethodField(label=_('Account')) + is_success = serializers.SerializerMethodField(label=_('Is success')) + + class Meta: + model = ChangeSecretRecord + fields = [ + 'id', 'asset', 'account', 'old_secret', 'new_secret', + 'status', 'error', 'is_success' + ] + + @staticmethod + def get_asset(instance): + return str(instance.asset) + + @staticmethod + def get_account(instance): + return str(instance.account) + + @staticmethod + def get_is_success(obj): + if obj.status == 'success': + return _("Success") + return _("Failed") From e995e3b35ae63066bf359cf5c5e1eb0595b4e11f Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 4 Nov 2022 11:09:56 +0800 Subject: [PATCH 288/488] perf: change secret adjustment --- apps/assets/automations/change_secret/manager.py | 4 +--- apps/assets/models/base.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index fd289b12f..a8b7dd515 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -192,10 +192,8 @@ class ChangeSecretManager(BasePlaybookManager): self.send_recorder_mail(recorders) def send_recorder_mail(self, recorders): - if not recorders: - return recipients = self.execution.recipients - if not recipients: + if not recorders or not recipients: return recipients = User.objects.filter(id__in=list(recipients)) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 8fa91b2cb..c0bf5b078 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -116,7 +116,7 @@ class BaseAccount(OrgModelMixin): @property def private_key_path(self): - if not self.secret_type != 'ssh_key' or not self.secret: + if not self.secret_type != SecretType.ssh_key or not self.secret: return None project_dir = settings.PROJECT_DIR tmp_dir = os.path.join(project_dir, 'tmp') From 8b05bc4b82154dc93387ef0e2cd5a04c5e158f6f Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 4 Nov 2022 11:15:34 +0800 Subject: [PATCH 289/488] =?UTF-8?q?perf:=20=E5=A4=9A=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=8F=91=E9=80=81=E6=96=87=E4=BB=B6=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/notifications.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/notifications.py b/apps/assets/notifications.py index 6a67878c9..a797bc845 100644 --- a/apps/assets/notifications.py +++ b/apps/assets/notifications.py @@ -22,7 +22,7 @@ class AccountBackupExecutionTaskMsg(object): name) def publish(self, attachment_list=None): - send_mail_attachment_async.delay( + send_mail_attachment_async( self.subject, self.message, [self.user.email], attachment_list ) @@ -44,6 +44,6 @@ class ChangeSecretExecutionTaskMsg(object): name) def publish(self, attachments=None): - send_mail_attachment_async.delay( + send_mail_attachment_async( self.subject, self.message, [self.user.email], attachments ) From 1981bdd3acb954ab99a2305b5bddb10d43414281 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Fri, 4 Nov 2022 11:39:34 +0800 Subject: [PATCH 290/488] perf: account serializer --- apps/assets/serializers/account/account.py | 4 ++-- apps/assets/serializers/account/template.py | 26 +++++++++------------ apps/assets/tasks/push_account.py | 6 ++--- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index b219211c2..efd8d9060 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -3,6 +3,7 @@ from rest_framework import serializers from common.drf.serializers import SecretReadableMixin from common.drf.fields import ObjectRelatedField +from assets.tasks import push_accounts_to_assets from assets.models import Account, AccountTemplate, Asset from .base import BaseAccountSerializer @@ -47,8 +48,7 @@ class AccountSerializerCreateMixin(serializers.ModelSerializer): def create(self, validated_data): instance = super().create(validated_data) if self.push_now: - # Todo: push it - print("Start push account to asset") + push_accounts_to_assets.delay([instance.id], [instance.asset_id]) return instance diff --git a/apps/assets/serializers/account/template.py b/apps/assets/serializers/account/template.py index 09c5b4541..4599ca0e7 100644 --- a/apps/assets/serializers/account/template.py +++ b/apps/assets/serializers/account/template.py @@ -1,6 +1,3 @@ -from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers - from assets.models import AccountTemplate from .base import BaseAccountSerializer @@ -9,15 +6,14 @@ class AccountTemplateSerializer(BaseAccountSerializer): class Meta(BaseAccountSerializer.Meta): model = AccountTemplate - @classmethod - def validate_required(cls, attrs): - # Todo: why ? - required_field_dict = {} - error = _('This field is required.') - for k, v in cls().fields.items(): - if v.required and k not in attrs: - required_field_dict[k] = error - if not required_field_dict: - return - raise serializers.ValidationError(required_field_dict) - + # @classmethod + # def validate_required(cls, attrs): + # # TODO 选择模版后检查一些必填项 + # required_field_dict = {} + # error = _('This field is required.') + # for k, v in cls().fields.items(): + # if v.required and k not in attrs: + # required_field_dict[k] = error + # if not required_field_dict: + # return + # raise serializers.ValidationError(required_field_dict) diff --git a/apps/assets/tasks/push_account.py b/apps/assets/tasks/push_account.py index 19ebd5045..8af71cd3f 100644 --- a/apps/assets/tasks/push_account.py +++ b/apps/assets/tasks/push_account.py @@ -11,8 +11,9 @@ __all__ = [ @org_aware_func("assets") -def push_accounts_to_assets_util(accounts, assets, task_name): +def push_accounts_to_assets_util(accounts, assets): from assets.models import PushAccountAutomation + task_name = gettext_noop("Push accounts to assets") task_name = PushAccountAutomation.generate_unique_name(task_name) account_usernames = list(accounts.values_list('username', flat=True)) @@ -33,5 +34,4 @@ def push_accounts_to_assets(account_ids, asset_ids): assets = Asset.objects.get(id=asset_ids) accounts = Account.objects.get(id=account_ids) - task_name = gettext_noop("Push accounts to assets") - return push_accounts_to_assets_util(accounts, assets, task_name) + return push_accounts_to_assets_util(accounts, assets) From 30106bdbbb24c2ca1c7002f5d68e1a6ec8eb397e Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 4 Nov 2022 11:40:16 +0800 Subject: [PATCH 291/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/component/status.py | 4 +- apps/terminal/api/component/storage.py | 2 +- apps/terminal/api/component/terminal.py | 2 +- apps/terminal/const.py | 8 +-- apps/terminal/models/component/status.py | 50 +----------------- apps/terminal/models/component/storage.py | 24 ++++----- apps/terminal/models/component/terminal.py | 49 ++++++------------ apps/terminal/serializers/storage.py | 16 +++--- apps/terminal/serializers/terminal.py | 36 ++++++------- apps/terminal/startup.py | 18 ++++--- apps/terminal/utils.py | 59 +++++++++------------- 11 files changed, 93 insertions(+), 175 deletions(-) diff --git a/apps/terminal/api/component/status.py b/apps/terminal/api/component/status.py index 05f424283..4c4e31f27 100644 --- a/apps/terminal/api/component/status.py +++ b/apps/terminal/api/component/status.py @@ -21,7 +21,7 @@ __all__ = ['StatusViewSet', 'ComponentsMetricsAPIView'] class StatusViewSet(viewsets.ModelViewSet): queryset = Status.objects.all() - serializer_class = serializers.StatusSerializer + serializer_class = serializers.StatSerializer session_serializer_class = serializers.SessionSerializer task_serializer_class = serializers.TaskSerializer @@ -52,7 +52,7 @@ class StatusViewSet(viewsets.ModelViewSet): terminal_id = self.kwargs.get("terminal", None) if terminal_id: terminal = get_object_or_404(Terminal, id=terminal_id) - return terminal.status.all() + return terminal.status_set.all() return super().get_queryset() diff --git a/apps/terminal/api/component/storage.py b/apps/terminal/api/component/storage.py index d46b6f91f..c912e3131 100644 --- a/apps/terminal/api/component/storage.py +++ b/apps/terminal/api/component/storage.py @@ -61,7 +61,7 @@ class CommandStorageViewSet(BaseStorageViewSetMixin, viewsets.ModelViewSet): if not filterset.is_valid(): raise utils.translate_validation(filterset.errors) command_qs = filterset.qs - if storage.type == const.CommandStorageTypeChoices.es: + if storage.type == const.CommandStorageType.es: command_count = command_qs.count(limit_to_max_result_window=False) else: command_count = command_qs.count() diff --git a/apps/terminal/api/component/terminal.py b/apps/terminal/api/component/terminal.py index 872a8e2e3..3133db6eb 100644 --- a/apps/terminal/api/component/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -47,7 +47,7 @@ class TerminalViewSet(JMSBulkModelViewSet): s = self.request.query_params.get('status') if not s: return queryset - filtered_queryset_id = [str(q.id) for q in queryset if q.latest_status == s] + filtered_queryset_id = [str(q.id) for q in queryset if q.load == s] queryset = queryset.filter(id__in=filtered_queryset_id) return queryset diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 91d6f5659..acea15238 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ # -------------------------------- -class ReplayStorageTypeChoices(TextChoices): +class ReplayStorageType(TextChoices): null = 'null', 'Null', server = 'server', 'Server' s3 = 's3', 'S3' @@ -20,7 +20,7 @@ class ReplayStorageTypeChoices(TextChoices): cos = 'cos', 'COS' -class CommandStorageTypeChoices(TextChoices): +class CommandStorageType(TextChoices): null = 'null', 'Null', server = 'server', 'Server' es = 'es', 'Elasticsearch' @@ -29,7 +29,7 @@ class CommandStorageTypeChoices(TextChoices): # Component Status Choices # ------------------------ -class ComponentStatusChoices(TextChoices): +class ComponentLoad(TextChoices): critical = 'critical', _('Critical') high = 'high', _('High') normal = 'normal', _('Normal') @@ -40,7 +40,7 @@ class ComponentStatusChoices(TextChoices): return set(dict(cls.choices).keys()) -class TerminalTypeChoices(TextChoices): +class TerminalType(TextChoices): koko = 'koko', 'KoKo' guacamole = 'guacamole', 'Guacamole' omnidb = 'omnidb', 'OmniDB' diff --git a/apps/terminal/models/component/status.py b/apps/terminal/models/component/status.py index 863dea9dd..3da13d9f0 100644 --- a/apps/terminal/models/component/status.py +++ b/apps/terminal/models/component/status.py @@ -1,10 +1,6 @@ -from __future__ import unicode_literals - import uuid from django.db import models -from django.forms.models import model_to_dict -from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger @@ -22,56 +18,12 @@ class Status(models.Model): connections = models.IntegerField(verbose_name=_("Connections"), default=0) threads = models.IntegerField(verbose_name=_("Threads"), default=0) boot_time = models.FloatField(verbose_name=_("Boot Time"), default=0) - terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.CASCADE, related_name='status') + terminal = models.ForeignKey('terminal.Terminal', null=True, on_delete=models.CASCADE) date_created = models.DateTimeField(auto_now_add=True) - CACHE_KEY = 'TERMINAL_STATUS_{}' - class Meta: db_table = 'terminal_status' get_latest_by = 'date_created' verbose_name = _("Status") - def save_to_cache(self): - if not self.terminal: - return - key = self.CACHE_KEY.format(self.terminal.id) - data = model_to_dict(self) - cache.set(key, data, 60*3) - return data - - @classmethod - def get_terminal_latest_status(cls, terminal): - from ...utils import ComputeStatUtil - stat = cls.get_terminal_latest_stat(terminal) - return ComputeStatUtil.compute_component_status(stat) - - @classmethod - def get_terminal_latest_stat(cls, terminal): - key = cls.CACHE_KEY.format(terminal.id) - data = cache.get(key) - if not data: - return None - data.pop('terminal', None) - stat = cls(**data) - stat.terminal = terminal - stat.is_alive = terminal.is_alive - stat.keep_one_decimal_place() - return stat - - def keep_one_decimal_place(self): - keys = ['cpu_load', 'memory_used', 'disk_used'] - for key in keys: - value = getattr(self, key, 0) - if not isinstance(value, (int, float)): - continue - value = '%.1f' % value - setattr(self, key, float(value)) - - def save(self, force_insert=False, force_update=False, using=None, - update_fields=None): - self.terminal.set_alive(ttl=120) - return self.save_to_cache() - # return super().save() - diff --git a/apps/terminal/models/component/storage.py b/apps/terminal/models/component/storage.py index 7cab98058..11a4849a6 100644 --- a/apps/terminal/models/component/storage.py +++ b/apps/terminal/models/component/storage.py @@ -53,21 +53,21 @@ class CommonStorageModelMixin(models.Model): class CommandStorage(CommonStorageModelMixin, CommonModelMixin): type = models.CharField( - max_length=16, choices=const.CommandStorageTypeChoices.choices, - default=const.CommandStorageTypeChoices.server.value, verbose_name=_('Type'), + max_length=16, choices=const.CommandStorageType.choices, + default=const.CommandStorageType.server.value, verbose_name=_('Type'), ) @property def type_null(self): - return self.type == const.CommandStorageTypeChoices.null.value + return self.type == const.CommandStorageType.null.value @property def type_server(self): - return self.type == const.CommandStorageTypeChoices.server.value + return self.type == const.CommandStorageType.server.value @property def type_es(self): - return self.type == const.CommandStorageTypeChoices.es.value + return self.type == const.CommandStorageType.es.value @property def type_null_or_server(self): @@ -138,17 +138,17 @@ class CommandStorage(CommonStorageModelMixin, CommonModelMixin): class ReplayStorage(CommonStorageModelMixin, CommonModelMixin): type = models.CharField( - max_length=16, choices=const.ReplayStorageTypeChoices.choices, - default=const.ReplayStorageTypeChoices.server.value, verbose_name=_('Type') + max_length=16, choices=const.ReplayStorageType.choices, + default=const.ReplayStorageType.server.value, verbose_name=_('Type') ) @property def type_null(self): - return self.type == const.ReplayStorageTypeChoices.null.value + return self.type == const.ReplayStorageType.null.value @property def type_server(self): - return self.type == const.ReplayStorageTypeChoices.server.value + return self.type == const.ReplayStorageType.server.value @property def type_null_or_server(self): @@ -156,11 +156,11 @@ class ReplayStorage(CommonStorageModelMixin, CommonModelMixin): @property def type_swift(self): - return self.type == const.ReplayStorageTypeChoices.swift.value + return self.type == const.ReplayStorageType.swift.value @property def type_ceph(self): - return self.type == const.ReplayStorageTypeChoices.ceph.value + return self.type == const.ReplayStorageType.ceph.value @property def config(self): @@ -168,7 +168,7 @@ class ReplayStorage(CommonStorageModelMixin, CommonModelMixin): # add type config if self.type_ceph: - _type = const.ReplayStorageTypeChoices.s3.value + _type = const.ReplayStorageType.s3.value else: _type = self.type _config.update({'TYPE': _type}) diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index c24b0bd86..eb68915e4 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,16 +1,15 @@ import uuid +from django.utils import timezone from django.db import models from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from common.utils import get_logger +from common.utils import get_logger, lazyproperty from users.models import User from orgs.utils import tmp_to_root_org -from .status import Status -from terminal.const import TerminalTypeChoices as TypeChoices -from terminal.const import ComponentStatusChoices as StatusChoice +from terminal.const import TerminalType as TypeChoices, ComponentLoad as StatusChoice from ..session import Session @@ -18,42 +17,24 @@ logger = get_logger(__file__) class TerminalStatusMixin: - ALIVE_KEY = 'TERMINAL_ALIVE_{}' id: str + ALIVE_KEY = 'TERMINAL_ALIVE_{}' + status_set: models.Manager - @property - def latest_status(self): - return Status.get_terminal_latest_status(self) + @lazyproperty + def last_stat(self): + return self.status_set.order_by('date_created').last() - @property - def latest_status_display(self): - return self.latest_status.label - - @property - def latest_stat(self): - return Status.get_terminal_latest_stat(self) - - @property - def is_normal(self): - return self.latest_status == StatusChoice.normal - - @property - def is_high(self): - return self.latest_status == StatusChoice.high - - @property - def is_critical(self): - return self.latest_status == StatusChoice.critical + @lazyproperty + def load(self): + from ...utils import ComputeLoadUtil + return ComputeLoadUtil.compute_load(self.last_stat) @property def is_alive(self): - key = self.ALIVE_KEY.format(self.id) - # return self.latest_status != StatusChoice.offline - return cache.get(key, False) - - def set_alive(self, ttl=120): - key = self.ALIVE_KEY.format(self.id) - cache.set(key, True, ttl) + if not self.last_stat: + return False + return self.last_stat.date_created > timezone.now() - timezone.timedelta(seconds=120) class StorageMixin: diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index ff7d386de..cc4478c2d 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -118,13 +118,13 @@ class ReplayStorageTypeAzureSerializer(serializers.Serializer): # mapping replay_storage_type_serializer_classes_mapping = { - const.ReplayStorageTypeChoices.s3.value: ReplayStorageTypeS3Serializer, - const.ReplayStorageTypeChoices.ceph.value: ReplayStorageTypeCephSerializer, - const.ReplayStorageTypeChoices.swift.value: ReplayStorageTypeSwiftSerializer, - const.ReplayStorageTypeChoices.oss.value: ReplayStorageTypeOSSSerializer, - const.ReplayStorageTypeChoices.azure.value: ReplayStorageTypeAzureSerializer, - const.ReplayStorageTypeChoices.obs.value: ReplayStorageTypeOBSSerializer, - const.ReplayStorageTypeChoices.cos.value: ReplayStorageTypeCOSSerializer + const.ReplayStorageType.s3.value: ReplayStorageTypeS3Serializer, + const.ReplayStorageType.ceph.value: ReplayStorageTypeCephSerializer, + const.ReplayStorageType.swift.value: ReplayStorageTypeSwiftSerializer, + const.ReplayStorageType.oss.value: ReplayStorageTypeOSSSerializer, + const.ReplayStorageType.azure.value: ReplayStorageTypeAzureSerializer, + const.ReplayStorageType.obs.value: ReplayStorageTypeOBSSerializer, + const.ReplayStorageType.cos.value: ReplayStorageTypeCOSSerializer } @@ -172,7 +172,7 @@ class CommandStorageTypeESSerializer(serializers.Serializer): # mapping command_storage_type_serializer_classes_mapping = { - const.CommandStorageTypeChoices.es.value: CommandStorageTypeESSerializer + const.CommandStorageType.es.value: CommandStorageTypeESSerializer } diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index f0ac5a7c9..4b2e3614e 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -2,28 +2,26 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import BulkModelSerializer -from common.utils import is_uuid -from common.utils import get_request_ip, pretty_string +from common.drf.fields import LabeledChoiceField +from common.utils import get_request_ip, pretty_string, is_uuid from users.serializers import ServiceAccountSerializer from .. import const - -from ..models import ( - Terminal, Status, Task, CommandStorage, ReplayStorage -) +from ..models import Terminal, Status, Task, CommandStorage, ReplayStorage -class StatusSerializer(serializers.ModelSerializer): +class StatSerializer(serializers.ModelSerializer): sessions = serializers.ListSerializer( - child=serializers.CharField(max_length=36), write_only=True + child=serializers.CharField(max_length=36), + write_only=True ) class Meta: + model = Status fields_mini = ['id'] fields_write_only = ['sessions', ] fields_small = fields_mini + fields_write_only + [ 'cpu_load', 'memory_used', 'disk_used', - 'session_online', - 'date_created' + 'session_online', 'date_created' ] fields_fk = ['terminal'] fields = fields_small + fields_fk @@ -32,30 +30,28 @@ class StatusSerializer(serializers.ModelSerializer): "memory_used": {'default': 0}, "disk_used": {'default': 0}, } - model = Status class TerminalSerializer(BulkModelSerializer): session_online = serializers.ReadOnlyField(source='get_online_session_count') is_alive = serializers.BooleanField(read_only=True) is_active = serializers.BooleanField(read_only=True, label='Is active') - status = serializers.ChoiceField( - read_only=True, choices=const.ComponentStatusChoices.choices, - source='latest_status', label=_('Load status') + load = LabeledChoiceField( + read_only=True, choices=const.ComponentLoad.choices, + label=_('Load status') ) - status_display = serializers.CharField(read_only=True, source='latest_status_display') - stat = StatusSerializer(read_only=True, source='latest_stat') + stat = StatSerializer(read_only=True, source='last_stat') class Meta: model = Terminal fields_mini = ['id', 'name'] fields_small = fields_mini + [ - 'type', 'remote_addr', 'http_port', 'ssh_port', - 'session_online', 'command_storage', 'replay_storage', - 'is_accepted', "is_active", 'is_alive', + 'type', 'remote_addr', 'session_online', + 'command_storage', 'replay_storage', + 'is_active', 'is_alive', 'date_created', 'comment', ] - fields_fk = ['status', 'status_display', 'stat'] + fields_fk = ['load', 'stat'] fields = fields_small + fields_fk read_only_fields = ['type', 'date_created'] extra_kwargs = { diff --git a/apps/terminal/startup.py b/apps/terminal/startup.py index 672d31830..1b4d7a7e8 100644 --- a/apps/terminal/startup.py +++ b/apps/terminal/startup.py @@ -9,8 +9,8 @@ from common.db.utils import close_old_connections from common.decorator import Singleton from common.utils import get_disk_usage, get_cpu_load, get_memory_usage, get_logger -from .serializers.terminal import TerminalRegistrationSerializer, StatusSerializer -from .const import TerminalTypeChoices +from .serializers.terminal import TerminalRegistrationSerializer, StatSerializer +from .const import TerminalType from .models import Terminal __all__ = ['CoreTerminal', 'CeleryTerminal'] @@ -51,16 +51,18 @@ class BaseTerminal(object): 'disk_used': get_disk_usage(path=settings.BASE_DIR), 'sessions': [], } - status_serializer = StatusSerializer(data=heartbeat_data) + status_serializer = StatSerializer(data=heartbeat_data) status_serializer.is_valid() status_serializer.validated_data.pop('sessions', None) terminal = self.get_or_register_terminal() status_serializer.validated_data['terminal'] = terminal try: - status_serializer.save() + status = status_serializer.save() + print("Save status ok: ", status) time.sleep(self.interval) except OperationalError: + print("Save status error, close old connections") close_old_connections() def get_or_register_terminal(self): @@ -90,8 +92,8 @@ class CoreTerminal(BaseTerminal): def __init__(self): super().__init__( - suffix_name=TerminalTypeChoices.core.label, - _type=TerminalTypeChoices.core.value + suffix_name=TerminalType.core.label, + _type=TerminalType.core.value ) @@ -99,6 +101,6 @@ class CoreTerminal(BaseTerminal): class CeleryTerminal(BaseTerminal): def __init__(self): super().__init__( - suffix_name=TerminalTypeChoices.celery.label, - _type=TerminalTypeChoices.celery.value + suffix_name=TerminalType.celery.label, + _type=TerminalType.celery.value ) diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index c41ce9bb3..53ef629f2 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- # import os +import time from itertools import groupby, chain +from collections import defaultdict +from django.utils import timezone from django.conf import settings from django.core.files.storage import default_storage import jms_storage @@ -75,16 +78,16 @@ def get_session_replay_url(session): return local_path, url -class ComputeStatUtil: +class ComputeLoadUtil: # system status @staticmethod def _common_compute_system_status(value, thresholds): if thresholds[0] <= value <= thresholds[1]: - return const.ComponentStatusChoices.normal.value + return const.ComponentLoad.normal.value elif thresholds[1] < value <= thresholds[2]: - return const.ComponentStatusChoices.high.value + return const.ComponentLoad.high.value else: - return const.ComponentStatusChoices.critical.value + return const.ComponentLoad.critical.value @classmethod def _compute_system_stat_status(cls, stat): @@ -105,16 +108,16 @@ class ComputeStatUtil: return system_status @classmethod - def compute_component_status(cls, stat): - if not stat: - return const.ComponentStatusChoices.offline + def compute_load(cls, stat): + if not stat or time.time() - stat.date_created.timestamp() > 150: + return const.ComponentLoad.offline system_status_values = cls._compute_system_stat_status(stat).values() - if const.ComponentStatusChoices.critical in system_status_values: - return const.ComponentStatusChoices.critical - elif const.ComponentStatusChoices.high in system_status_values: - return const.ComponentStatusChoices.high + if const.ComponentLoad.critical in system_status_values: + return const.ComponentLoad.critical + elif const.ComponentLoad.high in system_status_values: + return const.ComponentLoad.high else: - return const.ComponentStatusChoices.normal + return const.ComponentLoad.normal class TypedComponentsStatusMetricsUtil(object): @@ -134,31 +137,15 @@ class TypedComponentsStatusMetricsUtil(object): def get_metrics(self): metrics = [] for _tp, components in self.grouped_components: - normal_count = high_count = critical_count = 0 - total_count = offline_count = session_online_total = 0 - + metric = { + 'normal': 0, 'high': 0, 'critical': 0, 'offline': 0, + 'total': 0, 'session_active': 0, 'type': _tp + } for component in components: - total_count += 1 - if not component.is_alive: - offline_count += 1 - continue - if component.is_normal: - normal_count += 1 - elif component.is_high: - high_count += 1 - else: - # critical - critical_count += 1 - session_online_total += component.get_online_session_count() - metrics.append({ - 'total': total_count, - 'normal': normal_count, - 'high': high_count, - 'critical': critical_count, - 'offline': offline_count, - 'session_active': session_online_total, - 'type': _tp, - }) + metric[component.load] += 1 + metric['total'] += 1 + metric['session_active'] += component.get_online_session_count() + metrics.append(metric) return metrics From 037cd90f0968b10e49549f89d13c3dbb40cbf396 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 4 Nov 2022 13:29:28 +0800 Subject: [PATCH 292/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0109_rename_categories_to_types.py | 18 ------------------ .../0110_changesecretrecord_asset.py | 7 ++++++- 2 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 apps/assets/migrations/0109_rename_categories_to_types.py diff --git a/apps/assets/migrations/0109_rename_categories_to_types.py b/apps/assets/migrations/0109_rename_categories_to_types.py deleted file mode 100644 index aa7fcad8f..000000000 --- a/apps/assets/migrations/0109_rename_categories_to_types.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-03 08:44 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0108_auto_20221027_1053'), - ] - - operations = [ - migrations.RenameField( - model_name='accountbackupplan', - old_name='categories', - new_name='types', - ), - ] diff --git a/apps/assets/migrations/0110_changesecretrecord_asset.py b/apps/assets/migrations/0110_changesecretrecord_asset.py index 7a4e862ff..76a3f9872 100644 --- a/apps/assets/migrations/0110_changesecretrecord_asset.py +++ b/apps/assets/migrations/0110_changesecretrecord_asset.py @@ -7,10 +7,15 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('assets', '0109_rename_categories_to_types'), + ('assets', '0109_auto_20221102_2017'), ] operations = [ + migrations.RenameField( + model_name='accountbackupplan', + old_name='categories', + new_name='types', + ), migrations.AddField( model_name='changesecretrecord', name='asset', From 5447ee6c395dd82d89310db38bcbdcdb4a08d5fb Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Fri, 4 Nov 2022 18:46:49 +0800 Subject: [PATCH 293/488] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=94=A8=E6=88=B7-=E8=B5=84=E4=BA=A7=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E7=9A=84=E8=B4=A6=E5=8F=B7=E5=88=97=E8=A1=A8=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=BB=93=E6=9E=84;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/__init__.py | 1 - apps/perms/api/user_permission/accounts.py | 82 +++++++++++++++++++-- apps/perms/api/user_permission/common.py | 84 ---------------------- apps/perms/utils/account.py | 4 +- apps/perms/utils/permission.py | 2 +- 5 files changed, 81 insertions(+), 92 deletions(-) delete mode 100644 apps/perms/api/user_permission/common.py diff --git a/apps/perms/api/user_permission/__init__.py b/apps/perms/api/user_permission/__init__.py index b0db20ee0..55bc108b4 100644 --- a/apps/perms/api/user_permission/__init__.py +++ b/apps/perms/api/user_permission/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -from .common import * from .nodes import * from .assets import * from .nodes_with_assets import * diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index d504ac8f9..70973d988 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -1,13 +1,29 @@ -from rest_framework import generics +from django.shortcuts import get_object_or_404 +from rest_framework.generics import ListAPIView, get_object_or_404 + +from common.permissions import IsValidUser +from common.utils import get_logger, lazyproperty from assets.serializers import AccountSerializer -from perms.utils.account import PermAccountUtil +from perms.hands import User, Asset, Account +from perms import serializers +from perms.models import Action +from perms.utils import PermAccountUtil from .mixin import RoleAdminMixin, RoleUserMixin - -__all__ = ['UserAllGrantedAccountsApi', 'MyAllGrantedAccountsApi'] +logger = get_logger(__name__) -class UserAllGrantedAccountsApi(RoleAdminMixin, generics.ListAPIView): +__all__ = [ + 'UserAllGrantedAccountsApi', + 'MyAllGrantedAccountsApi', + 'UserGrantedAssetAccountsApi', + 'MyGrantedAssetAccountsApi', + 'UserGrantedAssetSpecialAccountsApi', + 'MyGrantedAssetSpecialAccountsApi', +] + + +class UserAllGrantedAccountsApi(RoleAdminMixin, ListAPIView): """ 授权给用户的所有账号列表 """ serializer_class = AccountSerializer filterset_fields = ("name", "username", "privileged", "version") @@ -22,3 +38,59 @@ class UserAllGrantedAccountsApi(RoleAdminMixin, generics.ListAPIView): class MyAllGrantedAccountsApi(RoleUserMixin, UserAllGrantedAccountsApi): """ 授权给我的所有账号列表 """ pass + + +class UserGrantedAssetAccountsApi(ListAPIView): + serializer_class = serializers.AccountsGrantedSerializer + + @lazyproperty + def user(self) -> User: + user_id = self.kwargs.get('pk') + return User.objects.get(id=user_id) + + @lazyproperty + def asset(self): + asset_id = self.kwargs.get('asset_id') + kwargs = {'id': asset_id, 'is_active': True} + asset = get_object_or_404(Asset, **kwargs) + return asset + + def get_queryset(self): + accounts = PermAccountUtil().get_perm_accounts_for_user_asset( + self.user, self.asset, with_actions=True + ) + return accounts + + +class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi): + permission_classes = (IsValidUser,) + + @lazyproperty + def user(self): + return self.request.user + + +class UserGrantedAssetSpecialAccountsApi(ListAPIView): + serializer_class = serializers.AccountsGrantedSerializer + + @lazyproperty + def user(self): + return self.request.user + + def get_queryset(self): + # 构造默认包含的账号,如: @INPUT @USER + accounts = [ + Account.get_input_account(), + Account.get_user_account(self.user.username) + ] + for account in accounts: + account.actions = Action.ALL + return accounts + + +class MyGrantedAssetSpecialAccountsApi(UserGrantedAssetSpecialAccountsApi): + permission_classes = (IsValidUser,) + + @lazyproperty + def user(self): + return self.request.user diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py deleted file mode 100644 index 927ec7443..000000000 --- a/apps/perms/api/user_permission/common.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.shortcuts import get_object_or_404 -from rest_framework.generics import ( - ListAPIView, get_object_or_404 -) -from common.permissions import IsValidUser -from common.utils import get_logger, lazyproperty - -from perms.hands import User, Asset, Account -from perms import serializers -from perms.models import Action -from perms.utils import PermAccountUtil - -logger = get_logger(__name__) - -__all__ = [ - 'UserGrantedAssetAccountsApi', - 'MyGrantedAssetAccountsApi', - 'UserGrantedAssetSpecialAccountsApi', - 'MyGrantedAssetSpecialAccountsApi', -] - - -class UserGrantedAssetAccountsApi(ListAPIView): - serializer_class = serializers.AccountsGrantedSerializer - rbac_perms = { - 'list': 'perms.view_userassets' - } - - @lazyproperty - def user(self) -> User: - user_id = self.kwargs.get('pk') - return User.objects.get(id=user_id) - - @lazyproperty - def asset(self): - asset_id = self.kwargs.get('asset_id') - kwargs = {'id': asset_id, 'is_active': True} - asset = get_object_or_404(Asset, **kwargs) - return asset - - def get_queryset(self): - accounts = PermAccountUtil().get_perm_accounts_for_user_asset( - self.user, self.asset, with_actions=True - ) - return accounts - - -class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi): - permission_classes = (IsValidUser,) - - @lazyproperty - def user(self): - return self.request.user - - -class UserGrantedAssetSpecialAccountsApi(ListAPIView): - serializer_class = serializers.AccountsGrantedSerializer - rbac_perms = { - 'list': 'perms.view_userassets' - } - - @lazyproperty - def user(self): - return self.request.user - - def get_queryset(self): - # 构造默认包含的账号,如: @INPUT @USER - accounts = [ - Account.get_input_account(), - Account.get_user_account(self.user.username) - ] - for account in accounts: - account.actions = Action.ALL - return accounts - - -class MyGrantedAssetSpecialAccountsApi(UserGrantedAssetSpecialAccountsApi): - permission_classes = (IsValidUser,) - - @lazyproperty - def user(self): - return self.request.user diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 63bfcc723..3963e113c 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -39,7 +39,9 @@ class PermAccountUtil(AssetPermissionUtil): for aid in account_ids: aid_actions_map[str(aid)] |= actions account_ids = list(aid_actions_map.keys()) - accounts = Account.objects.filter(id__in=account_ids) + accounts = Account.objects.filter(id__in=account_ids).order_by( + 'asset__name', 'name', 'username' + ) if with_actions: for account in accounts: account.actions = aid_actions_map.get(str(account.id)) diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index e7d88e06d..fd0ea593b 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -52,7 +52,7 @@ class AssetPermissionUtil(object): .values_list('assetpermission_id', flat=True).distinct() perm_ids.update(asset_perm_ids) if with_node: - nodes = asset.get_all_nodes(flat=True) + nodes = asset.get_all_nodes() node_perm_ids = self.get_permissions_for_nodes(nodes, flat=True) perm_ids.update(node_perm_ids) if flat: From dca92a1e04a2780a8a3e0e9e5787738984228361 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 4 Nov 2022 19:18:15 +0800 Subject: [PATCH 294/488] perf: push account (#9020) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/automations/base/manager.py | 12 ++++++++---- .../automations/push_account/host/posix/main.yml | 4 ++-- apps/assets/automations/push_account/manager.py | 2 +- apps/assets/automations/verify_account/manager.py | 2 +- apps/assets/models/automations/push_account.py | 2 +- apps/assets/serializers/asset/common.py | 4 ++-- apps/assets/tasks/push_account.py | 4 ++-- apps/assets/tasks/verify_account.py | 4 ++-- 8 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 758dea52c..c5f334167 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -23,6 +23,7 @@ class PushOrVerifyHostCallbackMixin: execution: callable host_account_mapper: dict ignore_account: bool + need_privilege_account: bool generate_public_key: callable generate_private_key_path: callable @@ -32,7 +33,7 @@ class PushOrVerifyHostCallbackMixin: return host accounts = asset.accounts.all() - if self.ignore_account and account: + if self.need_privilege_account and accounts.count() > 1 and account: accounts = accounts.exclude(id=account.id) if '*' not in self.execution.snapshot['accounts']: @@ -114,9 +115,9 @@ class BasePlaybookManager: method_attr = '{}_method'.format(self.__class__.method_type()) method_enabled = automation and \ - getattr(automation, enabled_attr) and \ - getattr(automation, method_attr) and \ - getattr(automation, method_attr) in self.method_id_meta_mapper + getattr(automation, enabled_attr) and \ + getattr(automation, method_attr) and \ + getattr(automation, method_attr) in self.method_id_meta_mapper if not method_enabled: host['error'] = _('{} disabled'.format(self.__class__.method_type())) @@ -198,6 +199,9 @@ class BasePlaybookManager: result = cb.host_results.get(host) if state == 'ok': self.on_host_success(host, result) + elif state == 'skipped': + # TODO + print('skipped: ', hosts) else: error = hosts.get(host) self.on_host_error(host, error, result) diff --git a/apps/assets/automations/push_account/host/posix/main.yml b/apps/assets/automations/push_account/host/posix/main.yml index afe13b226..e78c57152 100644 --- a/apps/assets/automations/push_account/host/posix/main.yml +++ b/apps/assets/automations/push_account/host/posix/main.yml @@ -2,8 +2,8 @@ gather_facts: no tasks: - name: Add user account.username - ansible.builtin.user: - name: "{{ account.username }}" + ansible.builtin.user: + name: "{{ account.username }}" - name: Set account.username password ansible.builtin.user: diff --git a/apps/assets/automations/push_account/manager.py b/apps/assets/automations/push_account/manager.py index ea5e2193f..f849f3e6e 100644 --- a/apps/assets/automations/push_account/manager.py +++ b/apps/assets/automations/push_account/manager.py @@ -6,7 +6,7 @@ logger = get_logger(__name__) class PushAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager): - ignore_account = True + need_privilege_account = True def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/apps/assets/automations/verify_account/manager.py b/apps/assets/automations/verify_account/manager.py index 5445511ba..fe46bc0ff 100644 --- a/apps/assets/automations/verify_account/manager.py +++ b/apps/assets/automations/verify_account/manager.py @@ -6,7 +6,7 @@ logger = get_logger(__name__) class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager): - ignore_account = False + need_privilege_account = False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/apps/assets/models/automations/push_account.py b/apps/assets/models/automations/push_account.py index b1da1966f..8439041cb 100644 --- a/apps/assets/models/automations/push_account.py +++ b/apps/assets/models/automations/push_account.py @@ -9,7 +9,7 @@ __all__ = ['PushAccountAutomation'] class PushAccountAutomation(BaseAutomation): def save(self, *args, **kwargs): - self.type = AutomationTypes.verify_account + self.type = AutomationTypes.push_account super().save(*args, **kwargs) class Meta: diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index d7f25111c..c44be52ce 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -65,8 +65,8 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) - accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) + accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) class Meta: model = Asset @@ -74,7 +74,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) fields_small = fields_mini + ['is_active', 'comment'] fields_fk = ['domain', 'platform', 'platform'] fields_m2m = [ - 'nodes', 'labels', 'accounts', 'protocols', 'nodes_display', + 'nodes', 'labels', 'protocols', 'accounts', 'nodes_display', ] read_only_fields = [ 'category', 'type', 'specific', diff --git a/apps/assets/tasks/push_account.py b/apps/assets/tasks/push_account.py index 8af71cd3f..cd5de975a 100644 --- a/apps/assets/tasks/push_account.py +++ b/apps/assets/tasks/push_account.py @@ -31,7 +31,7 @@ def push_accounts_to_assets_util(accounts, assets): def push_accounts_to_assets(account_ids, asset_ids): from assets.models import Asset, Account with tmp_to_root_org(): - assets = Asset.objects.get(id=asset_ids) - accounts = Account.objects.get(id=account_ids) + assets = Asset.objects.filter(id__in=asset_ids) + accounts = Account.objects.filter(id__in=account_ids) return push_accounts_to_assets_util(accounts, assets) diff --git a/apps/assets/tasks/verify_account.py b/apps/assets/tasks/verify_account.py index afb98a4a3..2874113d8 100644 --- a/apps/assets/tasks/verify_account.py +++ b/apps/assets/tasks/verify_account.py @@ -30,8 +30,8 @@ def verify_accounts_connectivity_util(accounts, assets, task_name): def verify_accounts_connectivity(account_ids, asset_ids): from assets.models import Asset, Account with tmp_to_root_org(): - assets = Asset.objects.get(id=asset_ids) - accounts = Account.objects.get(id=account_ids) + assets = Asset.objects.filter(id__in=asset_ids) + accounts = Account.objects.filter(id__in=account_ids) task_name = gettext_noop("Verify accounts connectivity") return verify_accounts_connectivity_util(accounts, assets, task_name) From 4405064e78222803c22e91aedf79c2fb4f981b6a Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 4 Nov 2022 20:14:19 +0800 Subject: [PATCH 295/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=20api=20debu?= =?UTF-8?q?g=20timer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/account/base.py | 1 + apps/authentication/middleware.py | 7 ++++ apps/jumpserver/api.py | 26 ++++++++------- apps/jumpserver/middleware.py | 37 ++++++++++++++++++++++ apps/jumpserver/settings/base.py | 2 ++ apps/ops/tasks.py | 2 +- apps/terminal/models/component/terminal.py | 3 +- 7 files changed, 64 insertions(+), 14 deletions(-) diff --git a/apps/assets/serializers/account/base.py b/apps/assets/serializers/account/base.py index 5db43257e..8e03a967e 100644 --- a/apps/assets/serializers/account/base.py +++ b/apps/assets/serializers/account/base.py @@ -31,6 +31,7 @@ class BaseAccountSerializer(BulkOrgResourceModelSerializer): extra_kwargs = { 'secret': {'write_only': True}, 'passphrase': {'write_only': True}, + 'specific': {'label': _('Specific')}, } def validate_private_key(self, private_key): diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index 5b6d7c06f..91b35e78d 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -1,4 +1,5 @@ import base64 +import time from django.shortcuts import redirect, reverse, render from django.utils.deprecation import MiddlewareMixin @@ -132,7 +133,13 @@ class SessionCookieMiddleware(MiddlewareMixin): response.set_cookie('jms_session_expire', value, max_age=age) request.session.pop('auth_session_expiration_required', None) + def process_request(self, request): + print("call process request") + time.sleep(0.8) + def process_response(self, request, response: HttpResponse): + import time + time.sleep(2) self.set_cookie_session_prefix(request, response) self.set_cookie_public_key(request, response) self.set_cookie_session_expire(request, response) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index cee761132..91249ccb6 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -308,14 +308,14 @@ class HealthCheckView(HealthApiMixin): def get_db_status(): t1 = time.time() try: - User.objects.first() + ok = User.objects.first() is not None t2 = time.time() - return True, t2 - t1 - except: - t2 = time.time() - return False, t2 - t1 + return ok, t2 - t1 + except Exception as e: + return False, str(e) - def get_redis_status(self): + @staticmethod + def get_redis_status(): key = 'HEALTH_CHECK' t1 = time.time() @@ -324,24 +324,26 @@ class HealthCheckView(HealthApiMixin): cache.set(key, '1', 10) got = cache.get(key) t2 = time.time() + if value == got: - return True, t2 -t1 - return False, t2 -t1 - except: - t2 = time.time() - return False, t2 - t1 + return True, t2 - t1 + return False, 'Value not match' + except Exception as e: + return False, str(e) def get(self, request): + start = time.time() redis_status, redis_time = self.get_redis_status() db_status, db_time = self.get_db_status() status = all([redis_status, db_status]) + time.sleep(1) data = { 'status': status, 'db_status': db_status, 'db_time': db_time, 'redis_status': redis_status, 'redis_time': redis_time, - 'time': int(time.time()) + 'time': int(time.time()), } return Response(data) diff --git a/apps/jumpserver/middleware.py b/apps/jumpserver/middleware.py index ed0c1bae6..bf7aa7945 100644 --- a/apps/jumpserver/middleware.py +++ b/apps/jumpserver/middleware.py @@ -3,6 +3,9 @@ import os import re import pytz +import time +import json + from django.utils import timezone from django.shortcuts import HttpResponse from django.conf import settings @@ -92,3 +95,37 @@ class RefererCheckMiddleware: return HttpResponseForbidden('CSRF CHECK ERROR') response = self.get_response(request) return response + + +class StartMiddleware: + def __init__(self, get_response): + self.get_response = get_response + if not settings.DEBUG_DEV: + raise MiddlewareNotUsed + + def __call__(self, request): + request._s_time_start = time.time() + response = self.get_response(request) + request._s_time_end = time.time() + if request.path == '/api/health/': + data = response.data + data['pre_middleware_time'] = request._e_time_start - request._s_time_start + data['api_time'] = request._e_time_end - request._e_time_start + data['post_middleware_time'] = request._s_time_end - request._e_time_end + response.content = json.dumps(data) + response.headers['Content-Length'] = str(len(response.content)) + return response + return response + + +class EndMiddleware: + def __init__(self, get_response): + self.get_response = get_response + if not settings.DEBUG_DEV: + raise MiddlewareNotUsed + + def __call__(self, request): + request._e_time_start = time.time() + response = self.get_response(request) + request._e_time_end = time.time() + return response diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 8456d9fa0..24bf7b8b3 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -86,6 +86,7 @@ INSTALLED_APPS = [ ] MIDDLEWARE = [ + 'jumpserver.middleware.StartMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', @@ -105,6 +106,7 @@ MIDDLEWARE = [ 'authentication.middleware.ThirdPartyLoginMiddleware', 'authentication.middleware.SessionCookieMiddleware', 'simple_history.middleware.HistoryRequestMiddleware', + 'jumpserver.middleware.EndMiddleware', ] ROOT_URLCONF = 'jumpserver.urls' diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 24cca604e..97868a884 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -144,7 +144,7 @@ def check_server_performance_period(): ServerPerformanceCheckUtil().check_and_publish() -@shared_task(queue="ansible", verbose_name=_("Hello"), comment="an test shared task") +@shared_task(verbose_name=_("Hello"), comment="an test shared task") def hello(name, callback=None): from users.models import User import time diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index eb68915e4..11a2a9a61 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,4 +1,5 @@ import uuid +import time from django.utils import timezone from django.db import models @@ -34,7 +35,7 @@ class TerminalStatusMixin: def is_alive(self): if not self.last_stat: return False - return self.last_stat.date_created > timezone.now() - timezone.timedelta(seconds=120) + return time.time() - self.last_stat.date_created.timestamp() < 150 class StorageMixin: From 0fb96091ccd07c5baa534201f936976c5be46fb6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 4 Nov 2022 20:15:31 +0800 Subject: [PATCH 296/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/middleware.py | 6 ------ apps/jumpserver/api.py | 1 - 2 files changed, 7 deletions(-) diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index 91b35e78d..8573b086b 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -133,13 +133,7 @@ class SessionCookieMiddleware(MiddlewareMixin): response.set_cookie('jms_session_expire', value, max_age=age) request.session.pop('auth_session_expiration_required', None) - def process_request(self, request): - print("call process request") - time.sleep(0.8) - def process_response(self, request, response: HttpResponse): - import time - time.sleep(2) self.set_cookie_session_prefix(request, response) self.set_cookie_public_key(request, response) self.set_cookie_session_expire(request, response) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 91249ccb6..de51d059c 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -336,7 +336,6 @@ class HealthCheckView(HealthApiMixin): redis_status, redis_time = self.get_redis_status() db_status, db_time = self.get_db_status() status = all([redis_status, db_status]) - time.sleep(1) data = { 'status': status, 'db_status': db_status, From 2705c38ba1568927c70e67298f04ecee388f1cab Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 7 Nov 2022 10:47:06 +0800 Subject: [PATCH 297/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=90=AF=E5=8A=A8=E5=A4=B1=E8=B4=A5=20debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/management/commands/services/services/base.py | 4 +++- apps/common/management/commands/services/utils.py | 1 - apps/jumpserver/api.py | 1 - apps/terminal/startup.py | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/common/management/commands/services/services/base.py b/apps/common/management/commands/services/services/base.py index 7b36c9723..870014474 100644 --- a/apps/common/management/commands/services/services/base.py +++ b/apps/common/management/commands/services/services/base.py @@ -44,7 +44,9 @@ class BaseService(object): if self.is_running: msg = f'{self.name} is running: {self.pid}.' else: - msg = f'{self.name} is stopped.' + msg = '\033[31m{} is stopped.\033[0m\nYou can manual start it to find the error: \n' \ + ' $ cd {}\n' \ + ' $ {}'.format(self.name, self.cwd, ' '.join(self.cmd)) print(msg) # -- log -- diff --git a/apps/common/management/commands/services/utils.py b/apps/common/management/commands/services/utils.py index a5c34d770..afa642a1a 100644 --- a/apps/common/management/commands/services/utils.py +++ b/apps/common/management/commands/services/utils.py @@ -76,7 +76,6 @@ class ServicesUtil(object): def clean_up(self): if not self.EXIT_EVENT.is_set(): self.EXIT_EVENT.set() - self.stop() def show_status(self): diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index de51d059c..59580d178 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -332,7 +332,6 @@ class HealthCheckView(HealthApiMixin): return False, str(e) def get(self, request): - start = time.time() redis_status, redis_time = self.get_redis_status() db_status, db_time = self.get_db_status() status = all([redis_status, db_status]) diff --git a/apps/terminal/startup.py b/apps/terminal/startup.py index 1b4d7a7e8..cc9da471c 100644 --- a/apps/terminal/startup.py +++ b/apps/terminal/startup.py @@ -59,7 +59,6 @@ class BaseTerminal(object): try: status = status_serializer.save() - print("Save status ok: ", status) time.sleep(self.interval) except OperationalError: print("Save status error, close old connections") From 1cc983b2eb3e8b9a19029e5ccc9c06f20f21bda4 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 7 Nov 2022 16:10:26 +0800 Subject: [PATCH 298/488] perf: automation button (#9023) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/asset/asset.py | 5 ++-- apps/assets/automations/base/manager.py | 3 +++ apps/assets/automations/endpoint.py | 10 +++++--- apps/assets/serializers/asset/common.py | 2 +- apps/assets/tasks/__init__.py | 1 + apps/assets/tasks/gather_accounts.py | 34 +++++++++++++++++++++++++ 6 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 apps/assets/tasks/gather_accounts.py diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 43461730f..4e1176d17 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -82,10 +82,11 @@ class AssetsTaskMixin: def perform_assets_task(self, serializer): data = serializer.validated_data assets = data.get('assets', []) + asset_ids = [asset.id for asset in assets] if data['action'] == "refresh": - task = update_assets_hardware_info_manual.delay(assets) + task = update_assets_hardware_info_manual.delay(asset_ids) else: - task = test_assets_connectivity_manual.delay(assets) + task = test_assets_connectivity_manual.delay(asset_ids) return task def perform_create(self, serializer): diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index c5f334167..ab8ea01bf 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -221,6 +221,7 @@ class BasePlaybookManager: else: print(">>> 开始执行任务\n") + self.execution.date_start = timezone.now() for i, runner in enumerate(runners, start=1): if len(runners) > 1: print(">>> 开始执行第 {} 批任务".format(i)) @@ -231,3 +232,5 @@ class BasePlaybookManager: except Exception as e: self.on_runner_failed(runner, e) print('\n') + self.execution.date_finished = timezone.now() + self.execution.save() diff --git a/apps/assets/automations/endpoint.py b/apps/assets/automations/endpoint.py index eb7b2f4ca..2548e9184 100644 --- a/apps/assets/automations/endpoint.py +++ b/apps/assets/automations/endpoint.py @@ -4,16 +4,18 @@ from .gather_accounts.manager import GatherAccountsManager from .verify_account.manager import VerifyAccountManager from .push_account.manager import PushAccountManager from .backup_account.manager import AccountBackupManager +from .ping.manager import PingManager from ..const import AutomationTypes class ExecutionManager: manager_type_mapper = { - AutomationTypes.change_secret: ChangeSecretManager, - AutomationTypes.gather_facts: GatherFactsManager, - AutomationTypes.gather_accounts: GatherAccountsManager, - AutomationTypes.verify_account: VerifyAccountManager, + AutomationTypes.ping: PingManager, AutomationTypes.push_account: PushAccountManager, + AutomationTypes.gather_facts: GatherFactsManager, + AutomationTypes.change_secret: ChangeSecretManager, + AutomationTypes.verify_account: VerifyAccountManager, + AutomationTypes.gather_accounts: GatherAccountsManager, # TODO 后期迁移到自动化策略中 'backup_account': AccountBackupManager, } diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index c44be52ce..a6c4b4fe2 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -77,7 +77,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) 'nodes', 'labels', 'protocols', 'accounts', 'nodes_display', ] read_only_fields = [ - 'category', 'type', 'specific', + 'category', 'type', 'specific', 'info', 'connectivity', 'date_verified', 'created_by', 'date_created', ] diff --git a/apps/assets/tasks/__init__.py b/apps/assets/tasks/__init__.py index c4d56528c..060f4d2d9 100644 --- a/apps/assets/tasks/__init__.py +++ b/apps/assets/tasks/__init__.py @@ -9,3 +9,4 @@ from .gather_facts import * from .nodes_amount import * from .push_account import * from .verify_account import * +from .gather_accounts import * diff --git a/apps/assets/tasks/gather_accounts.py b/apps/assets/tasks/gather_accounts.py new file mode 100644 index 000000000..4e372aca7 --- /dev/null +++ b/apps/assets/tasks/gather_accounts.py @@ -0,0 +1,34 @@ +# ~*~ coding: utf-8 ~*~ +from celery import shared_task +from django.utils.translation import gettext_noop + +from orgs.utils import tmp_to_root_org, org_aware_func +from common.utils import get_logger +from assets.models import Node + +__all__ = ['gather_asset_accounts'] +logger = get_logger(__name__) + + +@org_aware_func("nodes") +def gather_asset_accounts_util(nodes, task_name): + from assets.models import GatherAccountsAutomation + task_name = GatherAccountsAutomation.generate_unique_name(task_name) + + data = { + 'name': task_name, + 'comment': ', '.join([str(i) for i in nodes]) + } + instance = GatherAccountsAutomation.objects.create(**data) + instance.nodes.add(*nodes) + instance.execute() + + +@shared_task(queue="ansible") +def gather_asset_accounts(node_ids, task_name=None): + if task_name is None: + task_name = gettext_noop("Gather assets accounts") + + with tmp_to_root_org(): + nodes = Node.objects.filter(id__in=node_ids) + gather_asset_accounts_util(nodes=nodes, task_name=task_name) From b4f511a7ff79fa40afb0e4f49eb2beef01560da8 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 7 Nov 2022 17:01:28 +0800 Subject: [PATCH 299/488] perf: account backup --- apps/assets/serializers/account/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/account/backup.py b/apps/assets/serializers/account/backup.py index 455ef5bf3..8aa9aa8a7 100644 --- a/apps/assets/serializers/account/backup.py +++ b/apps/assets/serializers/account/backup.py @@ -20,7 +20,7 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode fields = [ 'id', 'name', 'is_periodic', 'interval', 'crontab', 'date_created', 'date_updated', 'created_by', 'periodic_display', 'comment', - 'recipients', 'categories' + 'recipients', 'types' ] extra_kwargs = { 'name': {'required': True}, From a13527c5c85494cbbe5776e00c835e014c7cbc70 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 7 Nov 2022 19:17:02 +0800 Subject: [PATCH 300/488] perf: platform charset --- apps/assets/models/platform.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 2abcf3652..64aeb2da3 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -6,7 +6,6 @@ from common.db.fields import JsonDictTextField from assets.const import Protocol - __all__ = ['Platform', 'PlatformProtocol', 'PlatformAutomation'] @@ -49,11 +48,15 @@ class PlatformAutomation(models.Model): push_account_enabled = models.BooleanField(default=False, verbose_name=_("Push account enabled")) push_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Push account method")) change_secret_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) - change_secret_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) + change_secret_method = models.TextField( + max_length=32, blank=True, null=True, verbose_name=_("Change password method")) verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) - verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + verify_account_method = models.TextField( + max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) gather_accounts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled")) - gather_accounts_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Gather facts method")) + gather_accounts_method = models.TextField( + max_length=32, blank=True, null=True, verbose_name=_("Gather facts method") + ) class Platform(models.Model): @@ -61,10 +64,11 @@ class Platform(models.Model): 对资产提供 约束和默认值 对资产进行抽象 """ - CHARSET_CHOICES = ( - ('utf8', 'UTF-8'), - ('gbk', 'GBK'), - ) + + class CharsetChoices(models.TextChoices): + utf8 = 'utf8', 'UTF-8' + gbk = 'gbk', 'GBK' + name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) category = models.CharField(default='host', max_length=32, verbose_name=_("Category")) type = models.CharField(max_length=32, default='linux', verbose_name=_("Type")) @@ -72,7 +76,9 @@ class Platform(models.Model): internal = models.BooleanField(default=False, verbose_name=_("Internal")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) # 资产有关的 - charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) + charset = models.CharField( + default=CharsetChoices.utf8, choices=CharsetChoices.choices, max_length=8, verbose_name=_("Charset") + ) domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled")) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) # 账号有关的 @@ -103,4 +109,3 @@ class Platform(models.Model): class Meta: verbose_name = _("Platform") # ordering = ('name',) - From 43e1417a25ff39289c1216414effd66857443664 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 7 Nov 2022 19:17:38 +0800 Subject: [PATCH 301/488] perf: update tinker playbook --- .../deploy_applet_host/playbook.yml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 867d58f76..d8b040583 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -14,7 +14,7 @@ RDS_fSingleSessionPerUser: 1 RDS_MaxDisconnectionTime: 60000 RDS_RemoteAppLogoffTimeLimit: 0 - TinkerInstaller: JumpServer-Remoteapp_v0.0.1.exe + TinkerInstaller: Tinker_Installer_v0.0.1.exe tasks: - name: Install RDS-Licensing (RDS) @@ -31,12 +31,12 @@ include_management_tools: yes register: rds_install - - name: Download JumpServer Remoteapp installer (jumpserver) + - name: Download JumpServer Tinker installer (jumpserver) ansible.windows.win_get_url: url: "{{ DownloadHost }}/{{ TinkerInstaller }}" dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" - - name: Install JumpServer Remoteapp agent (jumpserver) + - name: Install JumpServer Tinker (jumpserver) ansible.windows.win_package: path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" arguments: @@ -48,7 +48,7 @@ - name: Set remote-server on the global system path (remote-server) ansible.windows.win_path: elements: - - '%USERPROFILE%\AppData\Local\Programs\JumpServer-Remoteapp\' + - '%USERPROFILE%\AppData\Local\Programs\Tinker\' scope: user - name: Download python-3.10.8 @@ -153,18 +153,18 @@ arguments: - /quiet - - name: Generate component config + - name: Generate tinkerd component config ansible.windows.win_shell: - "remoteapp-server config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} + "tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}" - - name: Install remoteapp-server service + - name: Install tinkerd service ansible.windows.win_shell: - "remoteapp-server service install" + "tinkerd service install" - - name: Start remoteapp-server service + - name: Start tinkerd service ansible.windows.win_shell: - "remoteapp-server service start" + "tinkerd service start" - name: Wait Tinker api health ansible.windows.win_uri: From afe6c8ebbd30b470c180578654ce56f64e61f605 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 7 Nov 2022 20:41:18 +0800 Subject: [PATCH 302/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20applet=20h?= =?UTF-8?q?ost=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/permissions.py | 11 +++- apps/terminal/api/applet/__init__.py | 1 + apps/terminal/api/applet/host.py | 39 +++++------- apps/terminal/api/applet/relation.py | 77 ++++++++++++++++++++++++ apps/terminal/models/applet/host.py | 8 +-- apps/terminal/serializers/applet.py | 2 +- apps/terminal/serializers/applet_host.py | 22 +++++-- apps/terminal/urls/api_urls.py | 6 +- 8 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 apps/terminal/api/applet/relation.py diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 869107a58..1699dff46 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -16,14 +16,13 @@ class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission): """Allows access to valid user, is active and not expired""" def has_permission(self, request, view): - return super(IsValidUser, self).has_permission(request, view) \ + return super().has_permission(request, view) \ and request.user.is_valid class IsValidUserOrConnectionToken(IsValidUser): - def has_permission(self, request, view): - return super(IsValidUserOrConnectionToken, self).has_permission(request, view) \ + return super().has_permission(request, view) \ or self.is_valid_connection_token(request) @staticmethod @@ -42,6 +41,12 @@ class OnlySuperUser(IsValidUser): and request.user.is_superuser +class IsServiceAccount(IsValidUser): + def has_permission(self, request, view): + return super().has_permission(request, view) \ + and request.user.is_service_account + + class WithBootstrapToken(permissions.BasePermission): def has_permission(self, request, view): authorization = request.META.get('HTTP_AUTHORIZATION', '') diff --git a/apps/terminal/api/applet/__init__.py b/apps/terminal/api/applet/__init__.py index b2a4cac34..a950652a4 100644 --- a/apps/terminal/api/applet/__init__.py +++ b/apps/terminal/api/applet/__init__.py @@ -1,2 +1,3 @@ from .applet import * from .host import * +from .relation import * diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index f434b5b89..1a16c3bba 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -2,10 +2,13 @@ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response +from common.permissions import IsServiceAccount from common.drf.api import JMSModelViewSet -from orgs.utils import tmp_to_builtin_org -from terminal import serializers -from terminal.models import AppletHost, Applet, AppletHostDeployment +from terminal.serializers import ( + AppletHostSerializer, AppletHostDeploymentSerializer, + AppletHostStartupSerializer +) +from terminal.models import AppletHost, AppletHostDeployment from terminal.tasks import run_applet_host_deployment @@ -13,37 +16,25 @@ __all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] class AppletHostViewSet(JMSModelViewSet): - serializer_class = serializers.AppletHostSerializer + serializer_class = AppletHostSerializer queryset = AppletHost.objects.all() - rbac_perms = { - 'accounts': 'terminal.view_applethost', - 'reports': '*' - } - @action(methods=['post'], detail=True, serializer_class=serializers.AppletHostReportSerializer) - def reports(self, request, *args, **kwargs): - # 1. Host 和 Terminal 关联 - # 2. 上报 安装的 Applets 每小时 + def get_permissions(self): + if self.action == 'startup': + return [IsServiceAccount()] + return super().get_permissions() + + @action(methods=['post'], detail=True, serializer_class=AppletHostStartupSerializer) + def startup(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - - data = serializer.validated_data instance.check_terminal_binding(request) - instance.check_applets_state(data['applets']) return Response({'msg': 'ok'}) - @action(methods=['get'], detail=True, serializer_class=serializers.AppletHostAccountSerializer) - def accounts(self, request, *args, **kwargs): - host = self.get_object() - with tmp_to_builtin_org(system=1): - accounts = host.accounts.all().filter(privileged=False) - response = self.get_paginated_response_from_queryset(accounts) - return response - class AppletHostDeploymentViewSet(viewsets.ModelViewSet): - serializer_class = serializers.AppletHostDeploymentSerializer + serializer_class = AppletHostDeploymentSerializer queryset = AppletHostDeployment.objects.all() def create(self, request, *args, **kwargs): diff --git a/apps/terminal/api/applet/relation.py b/apps/terminal/api/applet/relation.py new file mode 100644 index 000000000..a613a0197 --- /dev/null +++ b/apps/terminal/api/applet/relation.py @@ -0,0 +1,77 @@ +from typing import Callable + +from django.shortcuts import get_object_or_404 +from rest_framework.request import Request +from rest_framework.decorators import action +from rest_framework.response import Response + +from common.drf.api import JMSModelViewSet +from common.permissions import IsServiceAccount +from orgs.utils import tmp_to_builtin_org +from rbac.permissions import RBACPermission +from terminal.models import AppletHost +from terminal.serializers import ( + AppletHostAccountSerializer, + AppletPublicationSerializer, + AppletHostAppletReportSerializer, +) + + +class HostMixin: + request: Request + permission_denied: Callable + kwargs: dict + rbac_perms = ( + ('list', 'terminal.view_applethost'), + ('retrieve', 'terminal.view_applethost'), + ) + + def get_permissions(self): + if self.kwargs.get('host'): + return [RBACPermission()] + else: + return [IsServiceAccount()] + + def self_host(self): + try: + return self.request.user.terminal.applet_host + except AttributeError: + raise self.permission_denied(self.request, 'User has no applet host') + + def pk_host(self): + return get_object_or_404(AppletHost, id=self.kwargs.get('host')) + + @property + def host(self): + if self.kwargs.get('host'): + return self.pk_host() + else: + return self.self_host() + + +class AppletHostAccountsViewSet(HostMixin, JMSModelViewSet): + serializer_class = AppletHostAccountSerializer + + def get_queryset(self): + with tmp_to_builtin_org(system=1): + queryset = self.host.accounts.all() + return queryset + + +class AppletHostAppletViewSet(HostMixin, JMSModelViewSet): + host: AppletHost + serializer_class = AppletPublicationSerializer + + def get_queryset(self): + queryset = self.host.publications.all() + return queryset + + @action(methods=['post'], detail=False) + def reports(self, request, *args, **kwargs): + serializer = AppletHostAppletReportSerializer(data=request.data, many=True) + serializer.is_valid(raise_exception=True) + data = serializer.validated_data + self.host.check_applets_state(data) + publications = self.host.publications.all() + serializer = AppletPublicationSerializer(publications, many=True) + return Response(serializer.data) diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 073ccc1fb..295c65b6a 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -34,10 +34,10 @@ class AppletHost(Host): return self.name @property - def status(self): - if self.terminal: - return 'online' - return self.terminal.status + def load(self): + if not self.terminal: + return 'offline' + return self.terminal.load def check_terminal_binding(self, request): request_terminal = getattr(request.user, 'terminal', None) diff --git a/apps/terminal/serializers/applet.py b/apps/terminal/serializers/applet.py index b5aa4db68..35af7e07b 100644 --- a/apps/terminal/serializers/applet.py +++ b/apps/terminal/serializers/applet.py @@ -17,7 +17,7 @@ class AppletPublicationSerializer(serializers.ModelSerializer): UNPUBLISHED = 'unpublished', _('Unpublished') NOT_MATCH = 'not_match', _('Not match') - applet = ObjectRelatedField(attrs=('id', 'display_name', 'icon', 'version'), queryset=Applet.objects.all()) + applet = ObjectRelatedField(attrs=('id', 'name', 'display_name', 'icon', 'version'), queryset=Applet.objects.all()) host = ObjectRelatedField(queryset=AppletHost.objects.all()) status = LabeledChoiceField(choices=Status.choices, label=_("Status")) diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py index 10ca442c1..e28a4aa59 100644 --- a/apps/terminal/serializers/applet_host.py +++ b/apps/terminal/serializers/applet_host.py @@ -2,16 +2,18 @@ from rest_framework import serializers from django.utils.translation import gettext_lazy as _ from common.validators import ProjectUniqueValidator -from common.drf.fields import ObjectRelatedField +from common.drf.fields import ObjectRelatedField, LabeledChoiceField from assets.models import Platform, Account from assets.serializers import HostSerializer from ..models import AppletHost, AppletHostDeployment, Applet from .applet import AppletSerializer +from .. import const __all__ = [ 'AppletHostSerializer', 'AppletHostDeploymentSerializer', - 'AppletHostAccountSerializer', 'AppletHostReportSerializer' + 'AppletHostAccountSerializer', 'AppletHostAppletReportSerializer', + 'AppletHostStartupSerializer', ] @@ -34,14 +36,16 @@ class DeployOptionsSerializer(serializers.Serializer): class AppletHostSerializer(HostSerializer): deploy_options = DeployOptionsSerializer(required=False, label=_("Deploy options")) + load = LabeledChoiceField( + read_only=True, label=_('Load status'), choices=const.ComponentLoad.choices, + ) class Meta(HostSerializer.Meta): model = AppletHost fields = HostSerializer.Meta.fields + [ - 'status', 'date_synced', 'deploy_options' + 'load', 'date_synced', 'deploy_options' ] extra_kwargs = { - 'status': {'read_only': True}, 'date_synced': {'read_only': True} } @@ -96,5 +100,11 @@ class AppletHostAccountSerializer(serializers.ModelSerializer): fields = ['id', 'username', 'secret', 'date_updated'] -class AppletHostReportSerializer(serializers.Serializer): - applets = ObjectRelatedField(attrs=('id', 'name', 'version'), queryset=Applet.objects.all(), many=True) +class AppletHostAppletReportSerializer(serializers.Serializer): + id = serializers.UUIDField(read_only=True) + name = serializers.CharField() + version = serializers.CharField() + + +class AppletHostStartupSerializer(serializers.Serializer): + pass diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 9a573acc5..3e39b55ce 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -12,8 +12,8 @@ app_name = 'terminal' router = BulkRouter() router.register(r'sessions', api.SessionViewSet, 'session') -router.register(r'terminals/(?P[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status') -router.register(r'terminals/(?P[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions') +router.register(r'terminals/((?P[^/.]{36})/)?status', api.StatusViewSet, 'terminal-status') +router.register(r'terminals/((?P[^/.]{36})/)?sessions', api.SessionViewSet, 'terminal-sessions') router.register(r'terminals', api.TerminalViewSet, 'terminal') router.register(r'tasks', api.TaskViewSet, 'tasks') router.register(r'commands', api.CommandViewSet, 'command') @@ -25,6 +25,8 @@ router.register(r'session-join-records', api.SessionJoinRecordsViewSet, 'session router.register(r'endpoints', api.EndpointViewSet, 'endpoint') router.register(r'endpoint-rules', api.EndpointRuleViewSet, 'endpoint-rule') router.register(r'applets', api.AppletViewSet, 'applet') +router.register(r'applet-hosts/((?P[^/.]+)/)?accounts', api.AppletHostAccountsViewSet, 'applet-host-account') +router.register(r'applet-hosts/((?P[^/.]+)/)?applets', api.AppletHostAppletViewSet, 'applet-host-applet') router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication') router.register(r'applet-host-deployments', api.AppletHostDeploymentViewSet, 'applet-host-deployment') From df14d0185924c1b7d170ab4fb3e6b39042a7cf2f Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 8 Nov 2022 10:41:06 +0800 Subject: [PATCH 303/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E4=BF=A1?= =?UTF-8?q?=E5=8F=B7=E5=88=9B=E5=BB=BA=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/signal_handlers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/terminal/signal_handlers.py b/apps/terminal/signal_handlers.py index cbbed376b..7ff39c6df 100644 --- a/apps/terminal/signal_handlers.py +++ b/apps/terminal/signal_handlers.py @@ -4,6 +4,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver +from orgs.utils import tmp_to_builtin_org from .models import Applet, AppletHost @@ -13,7 +14,8 @@ def on_applet_host_create(sender, instance, created=False, **kwargs): return applets = Applet.objects.all() instance.applets.set(applets) - instance.generate_accounts() + with tmp_to_builtin_org(system=1): + instance.generate_accounts() @receiver(post_save, sender=Applet) From ba38771d1a5aef29341e041180bc9b1efea45b90 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 8 Nov 2022 13:55:06 +0800 Subject: [PATCH 304/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20applets=20?= =?UTF-8?q?related?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/applet/host.py | 5 +++++ apps/terminal/api/applet/relation.py | 3 ++- apps/terminal/serializers/applet_host.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index 1a16c3bba..e5fb1c754 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -4,6 +4,7 @@ from rest_framework.response import Response from common.permissions import IsServiceAccount from common.drf.api import JMSModelViewSet +from orgs.utils import tmp_to_builtin_org from terminal.serializers import ( AppletHostSerializer, AppletHostDeploymentSerializer, AppletHostStartupSerializer @@ -19,6 +20,10 @@ class AppletHostViewSet(JMSModelViewSet): serializer_class = AppletHostSerializer queryset = AppletHost.objects.all() + def dispatch(self, request, *args, **kwargs): + with tmp_to_builtin_org(system=1): + return super().dispatch(request, *args, **kwargs) + def get_permissions(self): if self.action == 'startup': return [IsServiceAccount()] diff --git a/apps/terminal/api/applet/relation.py b/apps/terminal/api/applet/relation.py index a613a0197..513be6aab 100644 --- a/apps/terminal/api/applet/relation.py +++ b/apps/terminal/api/applet/relation.py @@ -1,6 +1,7 @@ from typing import Callable from django.shortcuts import get_object_or_404 +from django.conf import settings from rest_framework.request import Request from rest_framework.decorators import action from rest_framework.response import Response @@ -27,7 +28,7 @@ class HostMixin: ) def get_permissions(self): - if self.kwargs.get('host'): + if self.kwargs.get('host') and settings.DEBUG: return [RBACPermission()] else: return [IsServiceAccount()] diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py index e28a4aa59..b94d615c8 100644 --- a/apps/terminal/serializers/applet_host.py +++ b/apps/terminal/serializers/applet_host.py @@ -97,7 +97,7 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer): class AppletHostAccountSerializer(serializers.ModelSerializer): class Meta: model = Account - fields = ['id', 'username', 'secret', 'date_updated'] + fields = ['id', 'username', 'secret', 'is_active', 'date_updated'] class AppletHostAppletReportSerializer(serializers.Serializer): From cd93de4c006f89bedf30721bfffa2d1c141ba772 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 8 Nov 2022 14:30:07 +0800 Subject: [PATCH 305/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20Connection?= =?UTF-8?q?=20Token=20API=20=E9=80=BB=E8=BE=91=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/cmd_filter.py | 2 +- apps/authentication/api/connection_token.py | 6 ++---- apps/authentication/models/connection_token.py | 2 +- apps/authentication/serializers/connection_token.py | 4 ++-- apps/perms/utils/account.py | 8 +++++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index be8945c55..7023fdbc6 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -201,7 +201,7 @@ class CommandFilterRule(OrgModelMixin): q |= Q(user_groups__in=set(user_groups)) if account: org_id = account.org_id - q |= Q(accounts__contains=list(account)) |\ + q |= Q(accounts__contains=account.username) | \ Q(accounts__contains=SpecialAccount.ALL.value) if asset: org_id = asset.org_id diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 08b59581e..0c04531d5 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -178,8 +178,6 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): get_object: callable get_serializer: callable perform_create: callable - check_token_permission: callable - create_connection_token: callable @action(methods=['POST'], detail=False, url_path='secret-info/detail') def get_secret_detail(self, request, *args, **kwargs): @@ -277,10 +275,10 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView from perms.utils.account import PermAccountUtil actions, expire_at = PermAccountUtil().validate_permission(user, asset, account_username) if not actions: - error = '' + error = 'No actions' raise PermissionDenied(error) if expire_at < time.time(): - error = '' + error = 'Expired' raise PermissionDenied(error) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 3ed4c2a54..48c61f954 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -85,7 +85,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('No user or invalid user') return is_valid, error - if not self.asset or self.asset.is_active: + if not self.asset or not self.asset.is_active: is_valid = False error = _('No asset or inactive asset') return is_valid, error diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index e809ed78c..6e1f19be1 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -159,7 +159,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): domain = ConnectionTokenDomainSerializer(read_only=True) cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) actions = ActionsField() - expired_at = serializers.IntegerField() + expire_at = serializers.IntegerField() class Meta: model = ConnectionToken @@ -167,5 +167,5 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): 'id', 'secret', 'user', 'asset', 'account_username', 'account', 'protocol', 'domain', 'gateway', 'cmd_filter_rules', - 'actions', 'expired_at', + 'actions', 'expire_at', ] diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 3963e113c..8d8f5e743 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -53,7 +53,9 @@ class PermAccountUtil(AssetPermissionUtil): user, asset, with_actions=True, with_perms=True ) perm = perms.first() - account = accounts.filter(username=account_username).first() - actions = account.actions if account else [] - expire_at = perm.date_expired if perm else time.time() + actions = [] + for account in accounts: + if account.username == account_username: + actions = account.actions + expire_at = perm.date_expired.timestamp() if perm else time.time() return actions, expire_at From e69bb9f83e5271fc7692c323110572b0b8776180 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 8 Nov 2022 17:54:04 +0800 Subject: [PATCH 306/488] perf: applet host accounts should be inactive by default --- apps/terminal/models/applet/host.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 295c65b6a..5a85cbaf8 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -11,7 +11,6 @@ from common.db.models import JMSBaseModel from common.utils import random_string from assets.models import Host - __all__ = ['AppletHost', 'AppletHostDeployment'] @@ -26,7 +25,7 @@ class AppletHost(Host): ) applets = models.ManyToManyField( 'Applet', verbose_name=_('Applet'), - through='AppletPublication', through_fields=('host', 'applet'), + through='AppletPublication', through_fields=('host', 'applet'), ) LOCKING_ORG = '00000000-0000-0000-0000-000000000004' @@ -70,8 +69,8 @@ class AppletHost(Host): status_applets['published'].append(applet) for status, applets in status_applets.items(): - self.publications.filter(applet__in=applets)\ - .exclude(status=status)\ + self.publications.filter(applet__in=applets) \ + .exclude(status=status) \ .update(status=status) @staticmethod @@ -95,7 +94,7 @@ class AppletHost(Host): account = account_model( username=username, secret=password, name=username, asset_id=self.id, secret_type='password', version=1, - org_id=self.LOCKING_ORG + org_id=self.LOCKING_ORG, is_active=False, ) accounts.append(account) bulk_create_with_history(accounts, account_model, batch_size=20) From ce9ebd94ecd0bea81565ddae3a9b61efa9af888d Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 8 Nov 2022 17:54:51 +0800 Subject: [PATCH 307/488] perf: change secret automation api (#9028) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/__init__.py | 1 + apps/assets/api/automations/__init__.py | 3 + apps/assets/api/automations/base.py | 118 +++++++++++++++ apps/assets/api/automations/change_secret.py | 40 +++++ .../assets/api/automations/gather_accounts.py | 0 apps/assets/automations/base/manager.py | 2 +- .../automations/change_secret/manager.py | 8 +- apps/assets/automations/ping/manager.py | 8 +- .../automations/verify_account/manager.py | 4 +- apps/assets/const/account.py | 14 +- apps/assets/const/automation.py | 16 ++ apps/assets/models/automations/__init__.py | 9 +- apps/assets/models/automations/base.py | 2 +- .../models/automations/change_secret.py | 4 +- apps/assets/models/base.py | 14 +- apps/assets/serializers/__init__.py | 2 +- apps/assets/serializers/automation.py | 35 ----- .../serializers/automations/__init__.py | 3 + apps/assets/serializers/automations/base.py | 76 ++++++++++ .../serializers/automations/change_secret.py | 139 ++++++++++++++++++ .../automations/gather_accounts.py | 0 apps/assets/serializers/base.py | 65 +++----- apps/assets/serializers/utils.py | 15 ++ apps/assets/tasks/automation.py | 4 +- apps/assets/urls/api_urls.py | 22 ++- 25 files changed, 488 insertions(+), 116 deletions(-) create mode 100644 apps/assets/api/automations/__init__.py create mode 100644 apps/assets/api/automations/base.py create mode 100644 apps/assets/api/automations/change_secret.py create mode 100644 apps/assets/api/automations/gather_accounts.py delete mode 100644 apps/assets/serializers/automation.py create mode 100644 apps/assets/serializers/automations/__init__.py create mode 100644 apps/assets/serializers/automations/base.py create mode 100644 apps/assets/serializers/automations/change_secret.py create mode 100644 apps/assets/serializers/automations/gather_accounts.py diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index c14d8999f..36f734030 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -6,5 +6,6 @@ from .label import * from .account import * from .node import * from .domain import * +from .automations import * from .gathered_user import * from .favorite_asset import * diff --git a/apps/assets/api/automations/__init__.py b/apps/assets/api/automations/__init__.py new file mode 100644 index 000000000..e4daeda95 --- /dev/null +++ b/apps/assets/api/automations/__init__.py @@ -0,0 +1,3 @@ +from .base import * +from .change_secret import * +from .gather_accounts import * diff --git a/apps/assets/api/automations/base.py b/apps/assets/api/automations/base.py new file mode 100644 index 000000000..1b551f412 --- /dev/null +++ b/apps/assets/api/automations/base.py @@ -0,0 +1,118 @@ +from django.shortcuts import get_object_or_404 +from django.utils.translation import ugettext_lazy as _ +from rest_framework.response import Response +from rest_framework import status, mixins, viewsets + +from orgs.mixins import generics +from assets import serializers +from assets.const import AutomationTypes +from assets.tasks import execute_automation +from assets.models import BaseAutomation, AutomationExecution +from common.const.choices import Trigger + +__all__ = [ + 'AutomationAssetsListApi', 'AutomationRemoveAssetApi', + 'AutomationAddAssetApi', 'AutomationNodeAddRemoveApi', 'AutomationExecutionViewSet' +] + + +class AutomationAssetsListApi(generics.ListAPIView): + serializer_class = serializers.AutomationAssetsSerializer + filter_fields = ("name", "address") + search_fields = filter_fields + + def get_object(self): + pk = self.kwargs.get('pk') + return get_object_or_404(BaseAutomation, pk=pk) + + def get_queryset(self): + instance = self.get_object() + assets = instance.get_all_assets().only( + *self.serializer_class.Meta.only_fields + ) + return assets + + +class AutomationRemoveAssetApi(generics.RetrieveUpdateAPIView): + model = BaseAutomation + serializer_class = serializers.UpdateAssetSerializer + + def update(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.serializer_class(data=request.data) + + if not serializer.is_valid(): + return Response({'error': serializer.errors}) + + assets = serializer.validated_data.get('assets') + if assets: + instance.assets.remove(*tuple(assets)) + return Response({'msg': 'ok'}) + + +class AutomationAddAssetApi(generics.RetrieveUpdateAPIView): + model = BaseAutomation + serializer_class = serializers.UpdateAssetSerializer + + def update(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + assets = serializer.validated_data.get('assets') + if assets: + instance.assets.add(*tuple(assets)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView): + model = BaseAutomation + serializer_class = serializers.UpdateAssetSerializer + + def update(self, request, *args, **kwargs): + action_params = ['add', 'remove'] + action = request.query_params.get('action') + if action not in action_params: + err_info = _("The parameter 'action' must be [{}]".format(','.join(action_params))) + return Response({"error": err_info}) + + instance = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + nodes = serializer.validated_data.get('nodes') + if nodes: + # eg: plan.nodes.add(*tuple(assets)) + getattr(instance.nodes, action)(*tuple(nodes)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class AutomationExecutionViewSet( + mixins.CreateModelMixin, mixins.ListModelMixin, + mixins.RetrieveModelMixin, viewsets.GenericViewSet +): + search_fields = ('trigger',) + filterset_fields = ('trigger', 'automation_id') + serializer_class = serializers.AutomationExecutionSerializer + + def get_queryset(self): + queryset = AutomationExecution.objects.all() + return queryset + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset = queryset.order_by('-date_start') + return queryset + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + automation = serializer.validated_data.get('automation') + tp = serializer.validated_data.get('type') + model = AutomationTypes.get_model(tp) + task = execute_automation.delay( + pid=automation.ok, trigger=Trigger.manual, model=model + ) + return Response({'task': task.id}, status=status.HTTP_201_CREATED) diff --git a/apps/assets/api/automations/change_secret.py b/apps/assets/api/automations/change_secret.py new file mode 100644 index 000000000..944443914 --- /dev/null +++ b/apps/assets/api/automations/change_secret.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import mixins + +from common.utils import get_object_or_none +from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet + +from assets.models import ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution +from assets import serializers + +__all__ = [ + 'ChangeSecretAutomationViewSet', 'ChangeSecretRecordViewSet' +] + + +class ChangeSecretAutomationViewSet(OrgBulkModelViewSet): + model = ChangeSecretAutomation + filter_fields = ('name', 'secret_type', 'secret_strategy') + search_fields = filter_fields + ordering_fields = ('name',) + serializer_class = serializers.ChangeSecretAutomationSerializer + + +class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): + serializer_class = serializers.ChangeSecretRecordSerializer + filter_fields = ['username', 'asset', 'reason', 'execution'] + search_fields = ['username', 'reason', 'asset__hostname'] + + def get_queryset(self): + return ChangeSecretRecord.objects.all() + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + eid = self.request.GET.get('execution_id') + execution = get_object_or_none(AutomationExecution, pk=eid) + if execution: + queryset = queryset.filter(execution=execution) + queryset = queryset.order_by('is_success', '-date_start') + return queryset diff --git a/apps/assets/api/automations/gather_accounts.py b/apps/assets/api/automations/gather_accounts.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index ab8ea01bf..512454d9f 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -47,7 +47,7 @@ class PushOrVerifyHostCallbackMixin: secret = account.secret private_key_path = None - if account.secret_type == SecretType.ssh_key: + if account.secret_type == SecretType.SSH_KEY: private_key_path = self.generate_private_key_path(secret, path_dir) secret = self.generate_public_key(secret) diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index a8b7dd515..fc2ec60fe 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -89,9 +89,9 @@ class ChangeSecretManager(BasePlaybookManager): return self.generate_password() def get_secret(self): - if self.secret_type == SecretType.ssh_key: + if self.secret_type == SecretType.SSH_KEY: secret = self.get_ssh_key() - elif self.secret_type == SecretType.password: + elif self.secret_type == SecretType.PASSWORD: secret = self.get_password() else: raise ValueError("Secret must be set") @@ -99,7 +99,7 @@ class ChangeSecretManager(BasePlaybookManager): def get_kwargs(self, account, secret): kwargs = {} - if self.secret_type != SecretType.ssh_key: + if self.secret_type != SecretType.SSH_KEY: return kwargs kwargs['strategy'] = self.execution.snapshot['ssh_key_change_strategy'] kwargs['exclusive'] = 'yes' if kwargs['strategy'] == SSHKeyStrategy.set else 'no' @@ -143,7 +143,7 @@ class ChangeSecretManager(BasePlaybookManager): self.name_recorder_mapper[h['name']] = recorder private_key_path = None - if self.secret_type == SecretType.ssh_key: + if self.secret_type == SecretType.SSH_KEY: private_key_path = self.generate_private_key_path(new_secret, path_dir) new_secret = self.generate_public_key(new_secret) diff --git a/apps/assets/automations/ping/manager.py b/apps/assets/automations/ping/manager.py index 34c05a8f4..305771f0b 100644 --- a/apps/assets/automations/ping/manager.py +++ b/apps/assets/automations/ping/manager.py @@ -21,14 +21,14 @@ class PingManager(BasePlaybookManager): def on_host_success(self, host, result): asset, account = self.host_asset_and_account_mapper.get(host) - asset.set_connectivity(Connectivity.ok) + asset.set_connectivity(Connectivity.OK) if not account: return - account.set_connectivity(Connectivity.ok) + account.set_connectivity(Connectivity.OK) def on_host_error(self, host, error, result): asset, account = self.host_asset_and_account_mapper.get(host) - asset.set_connectivity(Connectivity.failed) + asset.set_connectivity(Connectivity.FAILED) if not account: return - account.set_connectivity(Connectivity.failed) + account.set_connectivity(Connectivity.FAILED) diff --git a/apps/assets/automations/verify_account/manager.py b/apps/assets/automations/verify_account/manager.py index fe46bc0ff..f261631e5 100644 --- a/apps/assets/automations/verify_account/manager.py +++ b/apps/assets/automations/verify_account/manager.py @@ -18,8 +18,8 @@ class VerifyAccountManager(PushOrVerifyHostCallbackMixin, BasePlaybookManager): def on_host_success(self, host, result): account = self.host_account_mapper.get(host) - account.set_connectivity(Connectivity.ok) + account.set_connectivity(Connectivity.OK) def on_host_error(self, host, error, result): account = self.host_account_mapper.get(host) - account.set_connectivity(Connectivity.failed) + account.set_connectivity(Connectivity.FAILED) diff --git a/apps/assets/const/account.py b/apps/assets/const/account.py index 5ec872134..ebeb855ed 100644 --- a/apps/assets/const/account.py +++ b/apps/assets/const/account.py @@ -3,13 +3,13 @@ from django.utils.translation import ugettext_lazy as _ class Connectivity(TextChoices): - unknown = 'unknown', _('Unknown') - ok = 'ok', _('Ok') - failed = 'failed', _('Failed') + UNKNOWN = 'unknown', _('Unknown') + OK = 'ok', _('Ok') + FAILED = 'failed', _('Failed') class SecretType(TextChoices): - password = 'password', _('Password') - ssh_key = 'ssh_key', _('SSH key') - access_key = 'access_key', _('Access key') - token = 'token', _('Token') + PASSWORD = 'password', _('Password') + SSH_KEY = 'ssh_key', _('SSH key') + ACCESS_KEY = 'access_key', _('Access key') + TOKEN = 'token', _('Token') diff --git a/apps/assets/const/automation.py b/apps/assets/const/automation.py index 6b3b6dbd4..99acefa7a 100644 --- a/apps/assets/const/automation.py +++ b/apps/assets/const/automation.py @@ -17,6 +17,22 @@ class AutomationTypes(TextChoices): verify_account = 'verify_account', _('Verify account') gather_accounts = 'gather_accounts', _('Gather accounts') + @classmethod + def get_type_model(cls, tp): + from assets.models import ( + PingAutomation, GatherFactsAutomation, PushAccountAutomation, + ChangeSecretAutomation, VerifyAccountAutomation, GatherAccountsAutomation, + ) + type_model_dict = { + cls.ping: PingAutomation, + cls.gather_facts: GatherFactsAutomation, + cls.push_account: PushAccountAutomation, + cls.change_secret: ChangeSecretAutomation, + cls.verify_account: VerifyAccountAutomation, + cls.gather_accounts: GatherAccountsAutomation, + } + return type_model_dict.get(tp) + class SecretStrategy(TextChoices): custom = 'specific', _('Specific') diff --git a/apps/assets/models/automations/__init__.py b/apps/assets/models/automations/__init__.py index e579fc10f..82fa19620 100644 --- a/apps/assets/models/automations/__init__.py +++ b/apps/assets/models/automations/__init__.py @@ -1,7 +1,8 @@ -from .change_secret import * -from .discovery_account import * +from .ping import * +from .base import * from .push_account import * from .gather_facts import * -from .gather_accounts import * +from .change_secret import * from .verify_account import * -from .ping import * +from .gather_accounts import * +from .discovery_account import * diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index 5eadca8c4..e814d4128 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -3,7 +3,7 @@ from celery import current_task from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.const.choices import Trigger, Status +from common.const.choices import Trigger from common.mixins.models import CommonModelMixin from common.db.fields import EncryptJsonDictTextField from orgs.mixins.models import OrgModelMixin diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index 81871fb3b..c22b64f51 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -12,7 +12,7 @@ __all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord'] class ChangeSecretAutomation(BaseAutomation): secret_type = models.CharField( choices=SecretType.choices, max_length=16, - default=SecretType.password, verbose_name=_('Secret type') + default=SecretType.PASSWORD, verbose_name=_('Secret type') ) secret_strategy = models.CharField( choices=SecretStrategy.choices, max_length=16, @@ -24,7 +24,7 @@ class ChangeSecretAutomation(BaseAutomation): choices=SSHKeyStrategy.choices, max_length=16, default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy') ) - recipients = models.ManyToManyField('users.User', blank=True, verbose_name=_("Recipient")) + recipients = models.ManyToManyField('users.User', verbose_name=_("Recipient"), blank=True) def save(self, *args, **kwargs): self.type = AutomationTypes.change_secret diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 1194b7957..7920d3798 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -24,7 +24,7 @@ logger = get_logger(__file__) class AbsConnectivity(models.Model): connectivity = models.CharField( - choices=Connectivity.choices, default=Connectivity.unknown, + choices=Connectivity.choices, default=Connectivity.UNKNOWN, max_length=16, verbose_name=_('Connectivity') ) date_verified = models.DateTimeField(null=True, verbose_name=_("Date verified")) @@ -50,7 +50,7 @@ class BaseAccount(JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_("Name")) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) secret_type = models.CharField( - max_length=16, choices=SecretType.choices, default=SecretType.password, verbose_name=_('Secret type') + max_length=16, choices=SecretType.choices, default=SecretType.PASSWORD, verbose_name=_('Secret type') ) secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) privileged = models.BooleanField(verbose_name=_("Privileged"), default=False) @@ -65,25 +65,25 @@ class BaseAccount(JMSOrgBaseModel): @property def specific(self): data = {} - if self.secret_type != SecretType.ssh_key: + if self.secret_type != SecretType.SSH_KEY: return data data['ssh_key_fingerprint'] = self.ssh_key_fingerprint return data @property def private_key(self): - if self.secret_type == SecretType.ssh_key: + if self.secret_type == SecretType.SSH_KEY: return self.secret return None @private_key.setter def private_key(self, value): self.secret = value - self.secret_type = SecretType.ssh_key + self.secret_type = SecretType.SSH_KEY @lazyproperty def public_key(self): - if self.secret_type == SecretType.ssh_key: + if self.secret_type == SecretType.SSH_KEY: return ssh_pubkey_gen(private_key=self.private_key) return None @@ -113,7 +113,7 @@ class BaseAccount(JMSOrgBaseModel): @property def private_key_path(self): - if not self.secret_type != SecretType.ssh_key or not self.secret: + if not self.secret_type != SecretType.SSH_KEY or not self.secret: return None project_dir = settings.PROJECT_DIR tmp_dir = os.path.join(project_dir, 'tmp') diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 252b2dc64..9876e3aa6 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -11,4 +11,4 @@ from .account import * from assets.serializers.account.backup import * from .platform import * from .cagegory import * -from .automation import * +from .automations import * diff --git a/apps/assets/serializers/automation.py b/apps/assets/serializers/automation.py deleted file mode 100644 index 482f95fc8..000000000 --- a/apps/assets/serializers/automation.py +++ /dev/null @@ -1,35 +0,0 @@ -from django.utils.translation import ugettext as _ -from rest_framework import serializers - -from common.utils import get_logger - -from assets.models import ChangeSecretRecord - -logger = get_logger(__file__) - - -class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer): - asset = serializers.SerializerMethodField(label=_('Asset')) - account = serializers.SerializerMethodField(label=_('Account')) - is_success = serializers.SerializerMethodField(label=_('Is success')) - - class Meta: - model = ChangeSecretRecord - fields = [ - 'id', 'asset', 'account', 'old_secret', 'new_secret', - 'status', 'error', 'is_success' - ] - - @staticmethod - def get_asset(instance): - return str(instance.asset) - - @staticmethod - def get_account(instance): - return str(instance.account) - - @staticmethod - def get_is_success(obj): - if obj.status == 'success': - return _("Success") - return _("Failed") diff --git a/apps/assets/serializers/automations/__init__.py b/apps/assets/serializers/automations/__init__.py new file mode 100644 index 000000000..e4daeda95 --- /dev/null +++ b/apps/assets/serializers/automations/__init__.py @@ -0,0 +1,3 @@ +from .base import * +from .change_secret import * +from .gather_accounts import * diff --git a/apps/assets/serializers/automations/base.py b/apps/assets/serializers/automations/base.py new file mode 100644 index 000000000..58c169c13 --- /dev/null +++ b/apps/assets/serializers/automations/base.py @@ -0,0 +1,76 @@ +from django.utils.translation import ugettext as _ +from rest_framework import serializers + +from ops.mixin import PeriodTaskSerializerMixin +from assets.const import AutomationTypes +from assets.models import Asset, BaseAutomation, AutomationExecution +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from common.utils import get_logger + +logger = get_logger(__file__) + +__all__ = [ + 'BaseAutomationSerializer', 'AutomationExecutionSerializer', + 'UpdateAssetSerializer', 'UpdateNodeSerializer', 'AutomationAssetsSerializer', +] + + +class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer): + class Meta: + read_only_fields = [ + 'date_created', 'date_updated', 'created_by', 'periodic_display' + ] + fields = read_only_fields + [ + 'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment', + 'type', 'accounts', 'nodes', 'assets', 'is_active' + ] + extra_kwargs = { + 'name': {'required': True}, + 'periodic_display': {'label': _('Periodic perform')}, + } + + +class AutomationExecutionSerializer(serializers.ModelSerializer): + snapshot = serializers.SerializerMethodField(label=_('Automation snapshot')) + type = serializers.ChoiceField(choices=AutomationTypes.choices, write_only=True, label=_('Type')) + trigger_display = serializers.ReadOnlyField(source='get_trigger_display', label=_('Trigger mode')) + + class Meta: + model = AutomationExecution + fields = [ + 'id', 'automation', 'trigger', 'trigger_display', + 'date_start', 'date_finished', 'snapshot', 'type' + ] + + @staticmethod + def get_snapshot(obj): + tp = obj.snapshot['type'] + snapshot = { + 'type': tp, + 'name': obj.snapshot['name'], + 'comment': obj.snapshot['comment'], + 'accounts': obj.snapshot['accounts'], + 'node_amount': len(obj.snapshot['nodes']), + 'asset_amount': len(obj.snapshot['assets']), + 'type_display': getattr(AutomationTypes, tp).label, + } + return snapshot + + +class UpdateAssetSerializer(serializers.ModelSerializer): + class Meta: + model = BaseAutomation + fields = ['id', 'assets'] + + +class UpdateNodeSerializer(serializers.ModelSerializer): + class Meta: + model = BaseAutomation + fields = ['id', 'nodes'] + + +class AutomationAssetsSerializer(serializers.ModelSerializer): + class Meta: + model = Asset + only_fields = ['id', 'name', 'address'] + fields = tuple(only_fields) diff --git a/apps/assets/serializers/automations/change_secret.py b/apps/assets/serializers/automations/change_secret.py new file mode 100644 index 000000000..104a3837e --- /dev/null +++ b/apps/assets/serializers/automations/change_secret.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +# +from django.utils.translation import ugettext as _ +from rest_framework import serializers + +from assets.serializers.base import AuthValidateMixin +from assets.models import ChangeSecretAutomation, ChangeSecretRecord +from assets.const import DEFAULT_PASSWORD_RULES, SecretType, SecretStrategy +from common.utils import get_logger + +from .base import BaseAutomationSerializer + +logger = get_logger(__file__) + +__all__ = [ + 'ChangeSecretAutomationSerializer', + 'ChangeSecretRecordSerializer', + 'ChangeSecretRecordBackUpSerializer' +] + + +class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializer): + password_rules = serializers.DictField(default=DEFAULT_PASSWORD_RULES) + secret_strategy_display = serializers.ReadOnlyField( + source='get_secret_strategy_display', label=_('Secret strategy') + ) + ssh_key_change_strategy_display = serializers.ReadOnlyField( + source='get_ssh_key_strategy_display', label=_('SSH Key strategy') + ) + + class Meta: + model = ChangeSecretAutomation + read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + [ + 'secret_strategy_display', 'ssh_key_change_strategy_display' + ] + fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [ + 'secret_type', 'secret_strategy', 'secret', 'password_rules', + 'ssh_key_change_strategy', 'passphrase', 'recipients', + ] + extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{ + 'recipients': {'label': _('Recipient'), 'help_text': _( + "Currently only mail sending is supported" + )}, + }} + + def validate_password_rules(self, password_rules): + secret_type = self.initial_secret_type + if secret_type != SecretType.PASSWORD: + return password_rules + + length = password_rules.get('length') + symbol_set = password_rules.get('symbol_set', '') + + try: + length = int(length) + except Exception as e: + logger.error(e) + msg = _("* Please enter the correct password length") + raise serializers.ValidationError(msg) + if length < 6 or length > 30: + msg = _('* Password length range 6-30 bits') + raise serializers.ValidationError(msg) + + if not isinstance(symbol_set, str): + symbol_set = str(symbol_set) + + password_rules = {'length': length, 'symbol_set': ''.join(symbol_set)} + return password_rules + + def validate(self, attrs): + secret_type = attrs.get('secret_type') + secret_strategy = attrs.get('secret_strategy') + if secret_type == SecretType.PASSWORD: + attrs.pop('ssh_key_change_strategy', None) + if secret_strategy == SecretStrategy.custom: + attrs.pop('password_rules', None) + else: + attrs.pop('secret', None) + elif secret_type == SecretType.SSH_KEY: + attrs.pop('password_rules', None) + if secret_strategy != SecretStrategy.custom: + attrs.pop('secret', None) + return attrs + + +class ChangeSecretRecordSerializer(serializers.ModelSerializer): + asset_display = serializers.SerializerMethodField(label=_('Asset display')) + account_display = serializers.SerializerMethodField(label=_('Account display')) + is_success = serializers.SerializerMethodField(label=_('Is success')) + + class Meta: + model = ChangeSecretRecord + fields = [ + 'id', 'asset', 'account', 'date_started', 'date_finished', + 'is_success', 'error', 'execution', 'asset_display', 'account_display' + ] + read_only_fields = fields + + @staticmethod + def get_asset_display(instance): + return str(instance.asset) + + @staticmethod + def get_account_display(instance): + return str(instance.account) + + @staticmethod + def get_is_success(obj): + if obj.status == 'success': + return _("Success") + return _("Failed") + + +class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer): + asset = serializers.SerializerMethodField(label=_('Asset')) + account = serializers.SerializerMethodField(label=_('Account')) + is_success = serializers.SerializerMethodField(label=_('Is success')) + + class Meta: + model = ChangeSecretRecord + fields = [ + 'id', 'asset', 'account', 'old_secret', 'new_secret', + 'status', 'error', 'is_success' + ] + read_only_fields = fields + + @staticmethod + def get_asset(instance): + return str(instance.asset) + + @staticmethod + def get_account(instance): + return str(instance.account) + + @staticmethod + def get_is_success(obj): + if obj.status == 'success': + return _("Success") + return _("Failed") diff --git a/apps/assets/serializers/automations/gather_accounts.py b/apps/assets/serializers/automations/gather_accounts.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 91aa2213a..7b5b62a16 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -1,68 +1,51 @@ # -*- coding: utf-8 -*- # -from io import StringIO - from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.utils import ssh_pubkey_gen, ssh_private_key_gen, validate_ssh_private_key from common.drf.fields import EncryptedField -from .utils import validate_password_for_ansible +from assets.const import SecretType +from .utils import validate_password_for_ansible, validate_ssh_key class AuthValidateMixin(serializers.Serializer): - password = EncryptedField( - label=_('Password'), required=False, allow_blank=True, allow_null=True, - max_length=1024, validators=[validate_password_for_ansible] - ) - private_key = EncryptedField( - label=_('SSH private key'), required=False, allow_blank=True, - allow_null=True, max_length=16384 + secret_type = serializers.CharField(label=_('Secret type'), max_length=16, required=True) + secret = EncryptedField( + label=_('Secret'), required=False, max_length=16384, allow_blank=True, + allow_null=True, write_only=True, ) passphrase = serializers.CharField( allow_blank=True, allow_null=True, required=False, max_length=512, write_only=True, label=_('Key password') ) - def validate_private_key(self, private_key): - if not private_key: - return - passphrase = self.initial_data.get('passphrase') - passphrase = passphrase if passphrase else None - valid = validate_ssh_private_key(private_key, password=passphrase) - if not valid: - raise serializers.ValidationError(_("private key invalid or passphrase error")) + @property + def initial_secret_type(self): + secret_type = self.initial_data.get('secret_type') + return secret_type - private_key = ssh_private_key_gen(private_key, password=passphrase) - string_io = StringIO() - private_key.write_private_key(string_io) - private_key = string_io.getvalue() - return private_key + def validate_secret(self, secret): + if not secret: + return + secret_type = self.initial_secret_type + if secret_type == SecretType.PASSWORD: + validate_password_for_ansible(secret) + return secret + elif secret_type == SecretType.SSH_KEY: + passphrase = self.initial_data.get('passphrase') + passphrase = passphrase if passphrase else None + return validate_ssh_key(secret, passphrase) + else: + return secret @staticmethod def clean_auth_fields(validated_data): - for field in ('password', 'private_key', 'public_key'): + for field in ('secret',): value = validated_data.get(field) if not value: validated_data.pop(field, None) validated_data.pop('passphrase', None) - @staticmethod - def _validate_gen_key(attrs): - private_key = attrs.get('private_key') - if not private_key: - return attrs - - password = attrs.get('passphrase') - username = attrs.get('username') - public_key = ssh_pubkey_gen(private_key, password=password, username=username) - attrs['public_key'] = public_key - return attrs - - def validate(self, attrs): - attrs = self._validate_gen_key(attrs) - return super().validate(attrs) - def create(self, validated_data): self.clean_auth_fields(validated_data) return super().create(validated_data) diff --git a/apps/assets/serializers/utils.py b/apps/assets/serializers/utils.py index 52527e723..0734bc9f1 100644 --- a/apps/assets/serializers/utils.py +++ b/apps/assets/serializers/utils.py @@ -1,6 +1,10 @@ +from io import StringIO + from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from common.utils import ssh_private_key_gen, validate_ssh_private_key + def validate_password_for_ansible(password): """ 校验 Ansible 不支持的特殊字符 """ @@ -15,3 +19,14 @@ def validate_password_for_ansible(password): if '"' in password: raise serializers.ValidationError(_('Password can not contains `"` ')) + +def validate_ssh_key(ssh_key, passphrase=None): + valid = validate_ssh_private_key(ssh_key, password=passphrase) + if not valid: + raise serializers.ValidationError(_("private key invalid or passphrase error")) + + ssh_key = ssh_private_key_gen(ssh_key, password=passphrase) + string_io = StringIO() + ssh_key.write_private_key(string_io) + ssh_key = string_io.getvalue() + return ssh_key diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py index c4d5f5043..873e606b6 100644 --- a/apps/assets/tasks/automation.py +++ b/apps/assets/tasks/automation.py @@ -7,9 +7,9 @@ logger = get_logger(__file__) @shared_task(queue='ansible') -def execute_automation(pid, trigger, mode): +def execute_automation(pid, trigger, model): with tmp_to_root_org(): - instance = get_object_or_none(mode, pk=pid) + instance = get_object_or_none(model, pk=pid) if not instance: logger.error("No automation task found: {}".format(pid)) return diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index f1c286054..d2bf6f258 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -27,17 +27,25 @@ router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') +router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automations') +router.register(r'automation-executions', api.AutomationExecutionViewSet, 'automation-execution') +router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-records') + urlpatterns = [ # path('assets//gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), path('assets//tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'), path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'), path('assets//perm-users/', api.AssetPermUserListApi.as_view(), name='asset-perm-user-list'), - path('assets//perm-users//permissions/', api.AssetPermUserPermissionsListApi.as_view(), name='asset-perm-user-permission-list'), - path('assets//perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), name='asset-perm-user-group-list'), - path('assets//perm-user-groups//permissions/', api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'), + path('assets//perm-users//permissions/', api.AssetPermUserPermissionsListApi.as_view(), + name='asset-perm-user-permission-list'), + path('assets//perm-user-groups/', api.AssetPermUserGroupListApi.as_view(), + name='asset-perm-user-group-list'), + path('assets//perm-user-groups//permissions/', + api.AssetPermUserGroupPermissionsListApi.as_view(), name='asset-perm-user-group-permission-list'), path('accounts/tasks/', api.AccountTaskCreateAPI.as_view(), name='account-task-create'), - path('account-secrets//histories/', api.AccountHistoriesSecretAPI.as_view(), name='account-secret-history'), + path('account-secrets//histories/', api.AccountHistoriesSecretAPI.as_view(), + name='account-secret-history'), path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'), path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'), @@ -52,7 +60,11 @@ urlpatterns = [ path('nodes//tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'), path('gateways//test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), + + path('automation//asset/remove/', api.AutomationRemoveAssetApi.as_view(), name='automation-remove-asset'), + path('automation//asset/add/', api.AutomationAddAssetApi.as_view(), name='automation-add-asset'), + path('automation//nodes/', api.AutomationNodeAddRemoveApi.as_view(), name='automation-add-or-remove-node'), + path('automation//assets/', api.AutomationAssetsListApi.as_view(), name='automation-assets'), ] urlpatterns += router.urls - From 5730265183e3a12af2e3b1a2e68820f870214f82 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 8 Nov 2022 19:18:04 +0800 Subject: [PATCH 308/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20account=20?= =?UTF-8?q?secret=5Ftype=20=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/account/account.py | 6 +++++- apps/assets/serializers/account/base.py | 5 ++++- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index efd8d9060..c6fcd4496 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -2,10 +2,11 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.drf.serializers import SecretReadableMixin -from common.drf.fields import ObjectRelatedField +from common.drf.fields import ObjectRelatedField, LabeledChoiceField from assets.tasks import push_accounts_to_assets from assets.models import Account, AccountTemplate, Asset from .base import BaseAccountSerializer +from assets.const import SecretType class AccountSerializerCreateMixin(serializers.ModelSerializer): @@ -57,6 +58,7 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): required=False, queryset=Asset.objects, label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id') ) + secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type')) class Meta(BaseAccountSerializer.Meta): model = Account @@ -91,6 +93,8 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): class AccountHistorySerializer(serializers.ModelSerializer): + secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type')) + class Meta: model = Account.history.model fields = ['id', 'secret', 'secret_type', 'version', 'history_date', 'history_user'] diff --git a/apps/assets/serializers/account/base.py b/apps/assets/serializers/account/base.py index 8e03a967e..e086da02a 100644 --- a/apps/assets/serializers/account/base.py +++ b/apps/assets/serializers/account/base.py @@ -21,7 +21,10 @@ class BaseAccountSerializer(BulkOrgResourceModelSerializer): class Meta: model = BaseAccount fields_mini = ['id', 'name', 'username'] - fields_small = fields_mini + ['privileged', 'secret_type', 'secret', 'has_secret', 'specific'] + fields_small = fields_mini + [ + 'secret_type', 'secret', 'has_secret', + 'privileged', 'is_active', 'specific', + ] fields_other = ['created_by', 'date_created', 'date_updated', 'comment'] fields = fields_small + fields_other read_only_fields = [ diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 9ba5f0837..bb0411aa6 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:0b396cc9a485f6474d14ca30a1a7ba4f954b07754148b964efbb21519c55b280 -size 102849 +oid sha256:314c29cb8b10aaddbb030bf49af293be23f0153ff1f1c7562946879574ce6de8 +size 102801 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 131862b1a..6ab6fa2be 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -858,7 +858,7 @@ msgstr "校验日期" #: assets/models/base.py:63 msgid "Privileged" -msgstr "特权的" +msgstr "特权账号" #: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:61 #: users/models/group.py:31 users/models/user.py:671 From 0ff8758ea89803d6554cf315ccaf69a704e36669 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 8 Nov 2022 20:33:55 +0800 Subject: [PATCH 309/488] perf: gather account automation api (#9029) Co-authored-by: feng <1304903146@qq.com> --- .../assets/api/automations/gather_accounts.py | 18 +++++++++++++++++ .../automations/gather_accounts.py | 20 +++++++++++++++++++ apps/assets/urls/api_urls.py | 5 +++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/automations/gather_accounts.py b/apps/assets/api/automations/gather_accounts.py index e69de29bb..e7a265f96 100644 --- a/apps/assets/api/automations/gather_accounts.py +++ b/apps/assets/api/automations/gather_accounts.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# +from orgs.mixins.api import OrgBulkModelViewSet + +from assets.models import GatherAccountsAutomation +from assets import serializers + +__all__ = [ + 'GatherAccountsAutomationViewSet', +] + + +class GatherAccountsAutomationViewSet(OrgBulkModelViewSet): + model = GatherAccountsAutomation + filter_fields = ('name',) + search_fields = filter_fields + ordering_fields = ('name',) + serializer_class = serializers.GatherAccountAutomationSerializer diff --git a/apps/assets/serializers/automations/gather_accounts.py b/apps/assets/serializers/automations/gather_accounts.py index e69de29bb..0f6308d49 100644 --- a/apps/assets/serializers/automations/gather_accounts.py +++ b/apps/assets/serializers/automations/gather_accounts.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +from assets.models import GatherAccountsAutomation +from common.utils import get_logger + +from .base import BaseAutomationSerializer + +logger = get_logger(__file__) + +__all__ = [ + 'GatherAccountAutomationSerializer', +] + + +class GatherAccountAutomationSerializer(BaseAutomationSerializer): + class Meta: + model = GatherAccountsAutomation + read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + fields = BaseAutomationSerializer.Meta.fields + read_only_fields + extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index d2bf6f258..a7077aa39 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -27,9 +27,10 @@ router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') -router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automations') +router.register(r'change-secret-automations', api.ChangeSecretAutomationViewSet, 'change-secret-automation') router.register(r'automation-executions', api.AutomationExecutionViewSet, 'automation-execution') -router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-records') +router.register(r'change-secret-records', api.ChangeSecretRecordViewSet, 'change-secret-record') +router.register(r'gather-account-automations', api.GatherAccountsAutomationViewSet, 'gather-account-automation') urlpatterns = [ # path('assets//gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'), From 71846241ae8d48ec65fdabde82d59e7e44e333ee Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 9 Nov 2022 11:43:55 +0800 Subject: [PATCH 310/488] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=E6=8E=88?= =?UTF-8?q?=E6=9D=83=20api=20=E8=BF=94=E5=9B=9E=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/asset_permission.py | 7 +++--- apps/perms/serializers/permission.py | 33 ++++++++------------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 6e2b6e637..20186527d 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -1,17 +1,16 @@ import uuid import logging -from functools import reduce + from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.db import models from django.db.models import F, Q, TextChoices -from collections import defaultdict +from common.utils import lazyproperty, date_expired_default +from common.db.models import BaseCreateUpdateModel, UnionQuerySet from assets.models import Asset, Node, FamilyMixin, Account from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgManager -from common.utils import lazyproperty, date_expired_default -from common.db.models import BaseCreateUpdateModel, UnionQuerySet from .const import Action, SpecialAccount __all__ = [ diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index fd393f990..ff19b9dd6 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -6,10 +6,11 @@ from rest_framework.fields import empty from django.utils.translation import ugettext_lazy as _ from django.db.models import Q +from common.drf.fields import ObjectRelatedField +from orgs.mixins.serializers import BulkOrgResourceModelSerializer from assets.models import Asset, Node from users.models import User, UserGroup from perms.models import AssetPermission, Action -from orgs.mixins.serializers import BulkOrgResourceModelSerializer __all__ = ['AssetPermissionSerializer', 'ActionsField'] @@ -44,18 +45,10 @@ class ActionsDisplayField(ActionsField): class AssetPermissionSerializer(BulkOrgResourceModelSerializer): - users_display = serializers.ListField( - child=serializers.CharField(), label=_('Users display'), required=False - ) - user_groups_display = serializers.ListField( - child=serializers.CharField(), label=_('User groups display'), required=False - ) - assets_display = serializers.ListField( - child=serializers.CharField(), label=_('Assets display'), required=False - ) - nodes_display = serializers.ListField( - child=serializers.CharField(), label=_('Nodes display'), required=False - ) + users = ObjectRelatedField(queryset=User.objects, many=True, required=False) + user_groups = ObjectRelatedField(queryset=UserGroup.objects, many=True, required=False) + assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False) + nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False) actions = ActionsField(required=False, allow_null=True, label=_("Actions")) is_valid = serializers.BooleanField(read_only=True, label=_("Is valid")) is_expired = serializers.BooleanField(read_only=True, label=_('Is expired')) @@ -64,24 +57,16 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): model = AssetPermission fields_mini = ['id', 'name'] fields_small = fields_mini + [ - 'is_active', 'is_expired', 'is_valid', 'actions', - 'accounts', - 'created_by', 'date_created', 'date_expired', + 'accounts', 'is_active', 'is_expired', 'is_valid', + 'actions', 'created_by', 'date_created', 'date_expired', 'date_start', 'comment', 'from_ticket' ] fields_m2m = [ - 'users', 'users_display', 'user_groups', 'user_groups_display', 'assets', - 'assets_display', 'nodes', 'nodes_display', - 'users_amount', 'user_groups_amount', 'assets_amount', - 'nodes_amount', + 'users', 'user_groups', 'assets', 'nodes', ] fields = fields_small + fields_m2m read_only_fields = ['created_by', 'date_created', 'from_ticket'] extra_kwargs = { - 'users_amount': {'label': _('Users amount')}, - 'user_groups_amount': {'label': _('User groups amount')}, - 'assets_amount': {'label': _('Assets amount')}, - 'nodes_amount': {'label': _('Nodes amount')}, 'actions': {'label': _('Actions')}, 'is_expired': {'label': _('Is expired')}, 'is_valid': {'label': _('Is valid')}, From 6ef5154d4db320d732375ec98e20de5cf994a3d5 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 9 Nov 2022 13:34:29 +0800 Subject: [PATCH 311/488] fix: swagger (#9031) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/automations/change_secret.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/automations/change_secret.py b/apps/assets/api/automations/change_secret.py index 944443914..112f0f93b 100644 --- a/apps/assets/api/automations/change_secret.py +++ b/apps/assets/api/automations/change_secret.py @@ -24,8 +24,8 @@ class ChangeSecretAutomationViewSet(OrgBulkModelViewSet): class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): serializer_class = serializers.ChangeSecretRecordSerializer - filter_fields = ['username', 'asset', 'reason', 'execution'] - search_fields = ['username', 'reason', 'asset__hostname'] + filter_fields = ['asset', 'execution_id'] + search_fields = ['asset__hostname'] def get_queryset(self): return ChangeSecretRecord.objects.all() From 3b4e388ed0991c6d855160bd756a0c783b011146 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 15:42:21 +0800 Subject: [PATCH 312/488] perf: gather account api adjustment --- apps/assets/automations/base/manager.py | 1 + apps/assets/models/automations/base.py | 10 +++------- apps/assets/models/automations/gather_accounts.py | 4 ++++ apps/assets/serializers/automations/base.py | 13 +++++++++---- .../serializers/automations/gather_accounts.py | 8 ++++++-- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/assets/automations/base/manager.py b/apps/assets/automations/base/manager.py index 512454d9f..6d1563258 100644 --- a/apps/assets/automations/base/manager.py +++ b/apps/assets/automations/base/manager.py @@ -232,5 +232,6 @@ class BasePlaybookManager: except Exception as e: self.on_runner_failed(runner, e) print('\n') + self.execution.status = 'success' self.execution.date_finished = timezone.now() self.execution.save() diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index e814d4128..fd9bc422b 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -15,12 +15,8 @@ from assets.const import AutomationTypes class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): accounts = models.JSONField(default=list, verbose_name=_("Accounts")) - nodes = models.ManyToManyField( - 'assets.Node', blank=True, verbose_name=_("Nodes") - ) - assets = models.ManyToManyField( - 'assets.Asset', blank=True, verbose_name=_("Assets") - ) + nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes")) + assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets")) type = models.CharField(max_length=16, choices=AutomationTypes.choices, verbose_name=_('Type')) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -92,7 +88,7 @@ class AutomationExecution(OrgModelMixin): 'BaseAutomation', related_name='executions', on_delete=models.CASCADE, verbose_name=_('Automation task') ) - status = models.CharField(max_length=16, default='pending') + status = models.CharField(max_length=16, default='pending', verbose_name=_('Status')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) diff --git a/apps/assets/models/automations/gather_accounts.py b/apps/assets/models/automations/gather_accounts.py index 861031af4..a3aa42383 100644 --- a/apps/assets/models/automations/gather_accounts.py +++ b/apps/assets/models/automations/gather_accounts.py @@ -13,3 +13,7 @@ class GatherAccountsAutomation(BaseAutomation): class Meta: verbose_name = _("Gather asset accounts") + + @property + def executed_amount(self): + return self.executions.count() diff --git a/apps/assets/serializers/automations/base.py b/apps/assets/serializers/automations/base.py index 58c169c13..3ff3f6686 100644 --- a/apps/assets/serializers/automations/base.py +++ b/apps/assets/serializers/automations/base.py @@ -3,9 +3,10 @@ from rest_framework import serializers from ops.mixin import PeriodTaskSerializerMixin from assets.const import AutomationTypes -from assets.models import Asset, BaseAutomation, AutomationExecution +from assets.models import Asset, Node, BaseAutomation, AutomationExecution from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.utils import get_logger +from common.drf.fields import ObjectRelatedField logger = get_logger(__file__) @@ -16,6 +17,9 @@ __all__ = [ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer): + assets = ObjectRelatedField(many=True, required=False, queryset=Asset.objects, label=_('Assets')) + nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes')) + class Meta: read_only_fields = [ 'date_created', 'date_updated', 'created_by', 'periodic_display' @@ -26,6 +30,7 @@ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSe ] extra_kwargs = { 'name': {'required': True}, + 'type': {'read_only': True}, 'periodic_display': {'label': _('Periodic perform')}, } @@ -37,10 +42,10 @@ class AutomationExecutionSerializer(serializers.ModelSerializer): class Meta: model = AutomationExecution - fields = [ - 'id', 'automation', 'trigger', 'trigger_display', - 'date_start', 'date_finished', 'snapshot', 'type' + read_only_fields = [ + 'trigger_display', 'date_start', 'date_finished', 'snapshot', 'status' ] + fields = ['id', 'automation', 'trigger', 'type'] + read_only_fields @staticmethod def get_snapshot(obj): diff --git a/apps/assets/serializers/automations/gather_accounts.py b/apps/assets/serializers/automations/gather_accounts.py index 0f6308d49..6b86fd64c 100644 --- a/apps/assets/serializers/automations/gather_accounts.py +++ b/apps/assets/serializers/automations/gather_accounts.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import ugettext_lazy as _ from assets.models import GatherAccountsAutomation from common.utils import get_logger @@ -15,6 +16,9 @@ __all__ = [ class GatherAccountAutomationSerializer(BaseAutomationSerializer): class Meta: model = GatherAccountsAutomation - read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + ['executed_amount'] fields = BaseAutomationSerializer.Meta.fields + read_only_fields - extra_kwargs = BaseAutomationSerializer.Meta.extra_kwargs + + extra_kwargs = {**BaseAutomationSerializer.Meta.extra_kwargs, **{ + 'executed_amount': {'label': _('Executed amount')} + }} From cf4744791a6e77c764177eb07ea9b44655a81eea Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 15:55:48 +0800 Subject: [PATCH 313/488] fix: automation execution bug --- apps/assets/api/automations/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/automations/base.py b/apps/assets/api/automations/base.py index 1b551f412..845b721c8 100644 --- a/apps/assets/api/automations/base.py +++ b/apps/assets/api/automations/base.py @@ -111,8 +111,8 @@ class AutomationExecutionViewSet( serializer.is_valid(raise_exception=True) automation = serializer.validated_data.get('automation') tp = serializer.validated_data.get('type') - model = AutomationTypes.get_model(tp) + model = AutomationTypes.get_type_model(tp) task = execute_automation.delay( - pid=automation.ok, trigger=Trigger.manual, model=model + pid=automation.pk, trigger=Trigger.manual, model=model ) return Response({'task': task.id}, status=status.HTTP_201_CREATED) From 1b2eda51e33d14c0482ce8d9efd5f69c1853a54b Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 9 Nov 2022 16:14:25 +0800 Subject: [PATCH 314/488] perf: get host applet by name --- apps/terminal/api/applet/relation.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/terminal/api/applet/relation.py b/apps/terminal/api/applet/relation.py index 513be6aab..e31c6ba5c 100644 --- a/apps/terminal/api/applet/relation.py +++ b/apps/terminal/api/applet/relation.py @@ -8,6 +8,7 @@ from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.permissions import IsServiceAccount +from common.utils import is_uuid from orgs.utils import tmp_to_builtin_org from rbac.permissions import RBACPermission from terminal.models import AppletHost @@ -63,6 +64,13 @@ class AppletHostAppletViewSet(HostMixin, JMSModelViewSet): host: AppletHost serializer_class = AppletPublicationSerializer + def get_object(self): + pk = self.kwargs.get('pk') + if not is_uuid(pk): + return self.host.publications.get(applet__name=pk) + else: + return self.host.publications.get(pk=pk) + def get_queryset(self): queryset = self.host.publications.all() return queryset From a5cef743568b4bf4a889690a2ac8a86388edd123 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 9 Nov 2022 18:15:21 +0800 Subject: [PATCH 315/488] perf: serializer (#9034) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/serializers/account/account.py | 1 - apps/assets/serializers/account/backup.py | 15 +++---- apps/assets/serializers/account/base.py | 41 ++---------------- .../serializers/automations/change_secret.py | 42 ++++++++----------- apps/assets/serializers/base.py | 8 ++-- apps/assets/serializers/gathered_user.py | 6 ++- apps/tickets/serializers/flow.py | 15 ++++--- 7 files changed, 47 insertions(+), 81 deletions(-) diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index c6fcd4496..cfd9a52f4 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -58,7 +58,6 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): required=False, queryset=Asset.objects, label=_('Asset'), attrs=('id', 'name', 'address', 'platform_id') ) - secret_type = LabeledChoiceField(choices=SecretType.choices, label=_('Secret type')) class Meta(BaseAccountSerializer.Meta): model = Account diff --git a/apps/assets/serializers/account/backup.py b/apps/assets/serializers/account/backup.py index 8aa9aa8a7..06cf4e2f9 100644 --- a/apps/assets/serializers/account/backup.py +++ b/apps/assets/serializers/account/backup.py @@ -6,6 +6,8 @@ from rest_framework import serializers from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ops.mixin import PeriodTaskSerializerMixin from common.utils import get_logger +from common.const.choices import Trigger +from common.drf.fields import LabeledChoiceField from assets.models import AccountBackupPlan, AccountBackupPlanExecution @@ -32,17 +34,12 @@ class AccountBackupPlanSerializer(PeriodTaskSerializerMixin, BulkOrgResourceMode class AccountBackupPlanExecutionSerializer(serializers.ModelSerializer): - trigger_display = serializers.ReadOnlyField( - source='get_trigger_display', label=_('Trigger mode') - ) + trigger = LabeledChoiceField(choices=Trigger.choices, label=_('Trigger mode')) class Meta: model = AccountBackupPlanExecution - fields = [ - 'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason', - 'is_success', 'plan', 'org_id', 'recipients', 'trigger_display' - ] - read_only_fields = ( + read_only_fields = [ 'id', 'date_start', 'timedelta', 'plan_snapshot', 'trigger', 'reason', 'is_success', 'org_id', 'recipients' - ) + ] + fields = read_only_fields + ['plan'] diff --git a/apps/assets/serializers/account/base.py b/apps/assets/serializers/account/base.py index e086da02a..c0e3553e8 100644 --- a/apps/assets/serializers/account/base.py +++ b/apps/assets/serializers/account/base.py @@ -1,28 +1,19 @@ # -*- coding: utf-8 -*- -from io import StringIO - from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers -from common.utils import validate_ssh_private_key, ssh_private_key_gen -from common.drf.fields import EncryptedField -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from assets.models import BaseAccount +from assets.serializers.base import AuthValidateMixin +from orgs.mixins.serializers import BulkOrgResourceModelSerializer __all__ = ['BaseAccountSerializer'] -class BaseAccountSerializer(BulkOrgResourceModelSerializer): - secret = EncryptedField( - label=_('Secret'), required=False, allow_blank=True, - allow_null=True, max_length=40960 - ) - +class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class Meta: model = BaseAccount fields_mini = ['id', 'name', 'username'] fields_small = fields_mini + [ - 'secret_type', 'secret', 'has_secret', + 'secret_type', 'secret', 'has_secret', 'passphrase', 'privileged', 'is_active', 'specific', ] fields_other = ['created_by', 'date_created', 'date_updated', 'comment'] @@ -32,29 +23,5 @@ class BaseAccountSerializer(BulkOrgResourceModelSerializer): 'date_verified', 'created_by', 'date_created', ] extra_kwargs = { - 'secret': {'write_only': True}, - 'passphrase': {'write_only': True}, 'specific': {'label': _('Specific')}, } - - def validate_private_key(self, private_key): - if not private_key: - return '' - passphrase = self.initial_data.get('passphrase') - passphrase = passphrase if passphrase else None - valid = validate_ssh_private_key(private_key, password=passphrase) - if not valid: - raise serializers.ValidationError(_("private key invalid or passphrase error")) - - private_key = ssh_private_key_gen(private_key, password=passphrase) - string_io = StringIO() - private_key.write_private_key(string_io) - private_key = string_io.getvalue() - return private_key - - def validate_secret(self, value): - secret_type = self.initial_data.get('secret_type') - if secret_type == 'ssh_key': - value = self.validate_private_key(value) - return value - diff --git a/apps/assets/serializers/automations/change_secret.py b/apps/assets/serializers/automations/change_secret.py index 104a3837e..3b9137bc4 100644 --- a/apps/assets/serializers/automations/change_secret.py +++ b/apps/assets/serializers/automations/change_secret.py @@ -3,10 +3,11 @@ from django.utils.translation import ugettext as _ from rest_framework import serializers -from assets.serializers.base import AuthValidateMixin -from assets.models import ChangeSecretAutomation, ChangeSecretRecord -from assets.const import DEFAULT_PASSWORD_RULES, SecretType, SecretStrategy from common.utils import get_logger +from common.drf.fields import LabeledChoiceField, ObjectRelatedField +from assets.serializers.base import AuthValidateMixin +from assets.const import DEFAULT_PASSWORD_RULES, SecretType, SecretStrategy, SSHKeyStrategy +from assets.models import Asset, Account, ChangeSecretAutomation, ChangeSecretRecord, AutomationExecution from .base import BaseAutomationSerializer @@ -20,19 +21,17 @@ __all__ = [ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializer): + secret_strategy = LabeledChoiceField( + choices=SecretStrategy.choices, required=True, label=_('Secret strategy') + ) + ssh_key_change_strategy = LabeledChoiceField( + choices=SSHKeyStrategy.choices, required=False, label=_('SSH Key strategy') + ) password_rules = serializers.DictField(default=DEFAULT_PASSWORD_RULES) - secret_strategy_display = serializers.ReadOnlyField( - source='get_secret_strategy_display', label=_('Secret strategy') - ) - ssh_key_change_strategy_display = serializers.ReadOnlyField( - source='get_ssh_key_strategy_display', label=_('SSH Key strategy') - ) class Meta: model = ChangeSecretAutomation - read_only_fields = BaseAutomationSerializer.Meta.read_only_fields + [ - 'secret_strategy_display', 'ssh_key_change_strategy_display' - ] + read_only_fields = BaseAutomationSerializer.Meta.read_only_fields fields = BaseAutomationSerializer.Meta.fields + read_only_fields + [ 'secret_type', 'secret_strategy', 'secret', 'password_rules', 'ssh_key_change_strategy', 'passphrase', 'recipients', @@ -84,26 +83,21 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ class ChangeSecretRecordSerializer(serializers.ModelSerializer): - asset_display = serializers.SerializerMethodField(label=_('Asset display')) - account_display = serializers.SerializerMethodField(label=_('Account display')) is_success = serializers.SerializerMethodField(label=_('Is success')) + asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset')) + account = ObjectRelatedField(queryset=Account.objects, label=_('Account')) + execution = ObjectRelatedField( + queryset=AutomationExecution.objects, label=_('Automation task execution') + ) class Meta: model = ChangeSecretRecord fields = [ - 'id', 'asset', 'account', 'date_started', 'date_finished', - 'is_success', 'error', 'execution', 'asset_display', 'account_display' + 'id', 'asset', 'account', 'date_started', + 'date_finished', 'is_success', 'error', 'execution', ] read_only_fields = fields - @staticmethod - def get_asset_display(instance): - return str(instance.asset) - - @staticmethod - def get_account_display(instance): - return str(instance.account) - @staticmethod def get_is_success(obj): if obj.status == 'success': diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 7b5b62a16..18432a7e5 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -3,15 +3,17 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.drf.fields import EncryptedField from assets.const import SecretType +from common.drf.fields import EncryptedField, LabeledChoiceField from .utils import validate_password_for_ansible, validate_ssh_key class AuthValidateMixin(serializers.Serializer): - secret_type = serializers.CharField(label=_('Secret type'), max_length=16, required=True) + secret_type = LabeledChoiceField( + choices=SecretType.choices, required=True, label=_('Secret type') + ) secret = EncryptedField( - label=_('Secret'), required=False, max_length=16384, allow_blank=True, + label=_('Secret'), required=False, max_length=40960, allow_blank=True, allow_null=True, write_only=True, ) passphrase = serializers.CharField( diff --git a/apps/assets/serializers/gathered_user.py b/apps/assets/serializers/gathered_user.py index 6cb90f46e..a0b58de45 100644 --- a/apps/assets/serializers/gathered_user.py +++ b/apps/assets/serializers/gathered_user.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- # - from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from ..models import GatheredUser +from common.drf.fields import ObjectRelatedField +from ..models import GatheredUser, Asset class GatheredUserSerializer(OrgResourceModelSerializerMixin): + asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset')) + class Meta: model = GatheredUser fields_mini = ['id'] diff --git a/apps/tickets/serializers/flow.py b/apps/tickets/serializers/flow.py index e8c066100..e949fa8d6 100644 --- a/apps/tickets/serializers/flow.py +++ b/apps/tickets/serializers/flow.py @@ -5,21 +5,24 @@ from rest_framework import serializers from orgs.models import Organization from orgs.utils import get_current_org_id from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from common.drf.fields import LabeledChoiceField from tickets.models import TicketFlow, ApprovalRule -from tickets.const import TicketApprovalStrategy +from tickets.const import TicketApprovalStrategy, TicketType __all__ = ['TicketFlowSerializer'] class TicketFlowApproveSerializer(serializers.ModelSerializer): - strategy_display = serializers.ReadOnlyField(source='get_strategy_display', label=_('Approve strategy')) + strategy = LabeledChoiceField( + choices=TicketApprovalStrategy.choices, required=True, label=_('Approve strategy') + ) assignees_read_only = serializers.SerializerMethodField(label=_('Assignees')) assignees_display = serializers.SerializerMethodField(label=_('Assignees display')) class Meta: model = ApprovalRule fields_small = [ - 'level', 'strategy', 'assignees_read_only', 'assignees_display', 'strategy_display' + 'level', 'strategy', 'assignees_read_only', 'assignees_display', ] fields_m2m = ['assignees', ] fields = fields_small + fields_m2m @@ -46,14 +49,16 @@ class TicketFlowApproveSerializer(serializers.ModelSerializer): class TicketFlowSerializer(OrgResourceModelSerializerMixin): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) + type = LabeledChoiceField( + choices=TicketType.choices, required=True, label=_('Type') + ) rules = TicketFlowApproveSerializer(many=True, required=True) class Meta: model = TicketFlow fields_mini = ['id', ] fields_small = fields_mini + [ - 'type', 'type_display', 'approval_level', 'created_by', 'date_created', 'date_updated', + 'type', 'approval_level', 'created_by', 'date_created', 'date_updated', 'org_id', 'org_name' ] fields = fields_small + ['rules', ] From 8e2c048f0c3d0716d120cbfb29c283118323e32c Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 9 Nov 2022 18:23:00 +0800 Subject: [PATCH 316/488] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81Oracle?= =?UTF-8?q?=E3=80=81MongoDB=E3=80=81SQLServer=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E7=9A=84=E8=87=AA=E5=8A=A8=E5=8C=96=E8=84=9A=E6=9C=AC=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- .../change_secret/database/mongodb/main.yml | 43 ++ .../database/mongodb/manifest.yml | 6 + .../change_secret/database/oracle/main.yml | 45 ++ .../database/oracle/manifest.yml | 6 + .../change_secret/database/sqlserver/main.yml | 47 ++ .../database/sqlserver/manifest.yml | 6 + .../automations/change_secret/manager.py | 2 + .../gather_accounts/database/mongodb/main.yml | 22 + .../database/mongodb/manifest.yml | 6 + .../gather_accounts/database/mysql/main.yml | 4 +- .../gather_accounts/database/oracle/main.yml | 23 + .../database/oracle/manifest.yml | 6 + .../gather_facts/database/mongodb/main.yml | 22 + .../database/mongodb/manifest.yml | 6 + .../gather_facts/database/oracle/main.yml | 23 + .../gather_facts/database/oracle/manifest.yml | 6 + .../ping/database/mongodb/main.yml | 13 + .../ping/database/mongodb/manifest.yml | 6 + .../automations/ping/database/oracle/main.yml | 14 + .../ping/database/oracle/manifest.yml | 6 + .../ping/database/sqlserver/main.yml | 15 + .../ping/database/sqlserver/manifest.yml | 6 + .../push_account/database/mongodb/main.yml | 16 + .../database/mongodb/manifest.yml | 6 + .../push_account/database/oracle/main.yml | 16 + .../push_account/database/oracle/manifest.yml | 6 + .../verify_account/database/mongodb/main.yml | 13 + .../database/mongodb/manifest.yml | 6 + .../verify_account/database/oracle/main.yml | 14 + .../database/oracle/manifest.yml | 6 + .../database/sqlserver/main.yml | 15 + .../database/sqlserver/manifest.yml | 6 + apps/ops/ansible/inventory.py | 3 + apps/ops/ansible/modules/__init__.py | 0 apps/ops/ansible/modules/mongodb_ping.py | 126 ++++++ apps/ops/ansible/modules/mongodb_user.py | 426 ++++++++++++++++++ apps/ops/ansible/modules/oracle_info.py | 261 +++++++++++ apps/ops/ansible/modules/oracle_ping.py | 107 +++++ apps/ops/ansible/modules/oracle_user.py | 215 +++++++++ apps/ops/ansible/modules_utils/__init__.py | 0 .../ansible/modules_utils/oracle_common.py | 94 ++++ 42 files changed, 1668 insertions(+), 3 deletions(-) create mode 100644 apps/assets/automations/change_secret/database/mongodb/main.yml create mode 100644 apps/assets/automations/change_secret/database/mongodb/manifest.yml create mode 100644 apps/assets/automations/change_secret/database/oracle/main.yml create mode 100644 apps/assets/automations/change_secret/database/oracle/manifest.yml create mode 100644 apps/assets/automations/change_secret/database/sqlserver/main.yml create mode 100644 apps/assets/automations/change_secret/database/sqlserver/manifest.yml create mode 100644 apps/assets/automations/gather_accounts/database/mongodb/main.yml create mode 100644 apps/assets/automations/gather_accounts/database/mongodb/manifest.yml create mode 100644 apps/assets/automations/gather_accounts/database/oracle/main.yml create mode 100644 apps/assets/automations/gather_accounts/database/oracle/manifest.yml create mode 100644 apps/assets/automations/gather_facts/database/mongodb/main.yml create mode 100644 apps/assets/automations/gather_facts/database/mongodb/manifest.yml create mode 100644 apps/assets/automations/gather_facts/database/oracle/main.yml create mode 100644 apps/assets/automations/gather_facts/database/oracle/manifest.yml create mode 100644 apps/assets/automations/ping/database/mongodb/main.yml create mode 100644 apps/assets/automations/ping/database/mongodb/manifest.yml create mode 100644 apps/assets/automations/ping/database/oracle/main.yml create mode 100644 apps/assets/automations/ping/database/oracle/manifest.yml create mode 100644 apps/assets/automations/ping/database/sqlserver/main.yml create mode 100644 apps/assets/automations/ping/database/sqlserver/manifest.yml create mode 100644 apps/assets/automations/push_account/database/mongodb/main.yml create mode 100644 apps/assets/automations/push_account/database/mongodb/manifest.yml create mode 100644 apps/assets/automations/push_account/database/oracle/main.yml create mode 100644 apps/assets/automations/push_account/database/oracle/manifest.yml create mode 100644 apps/assets/automations/verify_account/database/mongodb/main.yml create mode 100644 apps/assets/automations/verify_account/database/mongodb/manifest.yml create mode 100644 apps/assets/automations/verify_account/database/oracle/main.yml create mode 100644 apps/assets/automations/verify_account/database/oracle/manifest.yml create mode 100644 apps/assets/automations/verify_account/database/sqlserver/main.yml create mode 100644 apps/assets/automations/verify_account/database/sqlserver/manifest.yml create mode 100644 apps/ops/ansible/modules/__init__.py create mode 100644 apps/ops/ansible/modules/mongodb_ping.py create mode 100644 apps/ops/ansible/modules/mongodb_user.py create mode 100644 apps/ops/ansible/modules/oracle_info.py create mode 100644 apps/ops/ansible/modules/oracle_ping.py create mode 100644 apps/ops/ansible/modules/oracle_user.py create mode 100644 apps/ops/ansible/modules_utils/__init__.py create mode 100644 apps/ops/ansible/modules_utils/oracle_common.py diff --git a/Dockerfile b/Dockerfile index 7e6eac422..9ff8dc4e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,7 +76,7 @@ RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_ ARG VERSION ENV VERSION=$VERSION - +ENV ANSIBLE_LIBRARY=/opt/jumpserver/apps/ops/ansible/modules ADD . . RUN cd utils \ && bash -ixeu build.sh \ diff --git a/apps/assets/automations/change_secret/database/mongodb/main.yml b/apps/assets/automations/change_secret/database/mongodb/main.yml new file mode 100644 index 000000000..02a568e0b --- /dev/null +++ b/apps/assets/automations/change_secret/database/mongodb/main.yml @@ -0,0 +1,43 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test MongoDB connection + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + register: db_info + + - name: Display MongoDB version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change MongoDB password + mongodb_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + db: "{{ jms_asset.specific.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + mongodb_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/database/mongodb/manifest.yml b/apps/assets/automations/change_secret/database/mongodb/manifest.yml new file mode 100644 index 000000000..a59c0033b --- /dev/null +++ b/apps/assets/automations/change_secret/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: change_secret_mongodb +name: Change password for MongoDB +category: database +type: + - mongodb +method: change_secret diff --git a/apps/assets/automations/change_secret/database/oracle/main.yml b/apps/assets/automations/change_secret/database/oracle/main.yml new file mode 100644 index 000000000..c7b20a8db --- /dev/null +++ b/apps/assets/automations/change_secret/database/oracle/main.yml @@ -0,0 +1,45 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test Oracle connection + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + register: db_info + + - name: Display Oracle version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change Oracle password + oracle_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + oracle_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ account.mode }}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/database/oracle/manifest.yml b/apps/assets/automations/change_secret/database/oracle/manifest.yml new file mode 100644 index 000000000..19f109ba6 --- /dev/null +++ b/apps/assets/automations/change_secret/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: change_secret_oracle +name: Change password for Oracle +category: database +type: + - oracle +method: change_secret diff --git a/apps/assets/automations/change_secret/database/sqlserver/main.yml b/apps/assets/automations/change_secret/database/sqlserver/main.yml new file mode 100644 index 000000000..a617a1434 --- /dev/null +++ b/apps/assets/automations/change_secret/database/sqlserver/main.yml @@ -0,0 +1,47 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test SQLServer connection + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version + register: db_info + + - name: SQLServer version + set_fact: + info: + version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}" + - debug: + var: info + + - name: Change SQLServer password + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version" + when: db_info is succeeded + register: change_info + + - name: Verify password + community.general.mssql_script: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/database/sqlserver/manifest.yml b/apps/assets/automations/change_secret/database/sqlserver/manifest.yml new file mode 100644 index 000000000..799c9e623 --- /dev/null +++ b/apps/assets/automations/change_secret/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: change_secret_sqlserver +name: Change password for SQLServer +category: database +type: + - sqlserver +method: change_secret diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index a8b7dd515..35ccba90a 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -155,6 +155,8 @@ class ChangeSecretManager(BasePlaybookManager): 'secret': new_secret, 'private_key_path': private_key_path } + if asset.platform.type == 'oracle': + h['account']['mode'] = 'sysdba' if account.privileged else None inventory_hosts.append(h) method_hosts.append(h['name']) self.method_hosts_mapper[method_attr] = method_hosts diff --git a/apps/assets/automations/gather_accounts/database/mongodb/main.yml b/apps/assets/automations/gather_accounts/database/mongodb/main.yml new file mode 100644 index 000000000..fd7a296b7 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mongodb/main.yml @@ -0,0 +1,22 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + community.mongodb.mongodb_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + filter: users + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.users }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/mongodb/manifest.yml b/apps/assets/automations/gather_accounts/database/mongodb/manifest.yml new file mode 100644 index 000000000..a002848b4 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_mongodb +name: Gather account from MongoDB +category: database +type: + - mongodb +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/database/mysql/main.yml b/apps/assets/automations/gather_accounts/database/mysql/main.yml index 4b166322a..cc934f20f 100644 --- a/apps/assets/automations/gather_accounts/database/mysql/main.yml +++ b/apps/assets/automations/gather_accounts/database/mysql/main.yml @@ -1,7 +1,7 @@ - hosts: mysql gather_facts: no vars: - ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + ansible_python_interpreter: /usr/local/bin/python tasks: - name: Get info @@ -9,7 +9,7 @@ login_user: "{{ jms_account.username }}" login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" - login_port: 1234 + login_port: "{{ jms_asset.port }}" filter: users register: db_info diff --git a/apps/assets/automations/gather_accounts/database/oracle/main.yml b/apps/assets/automations/gather_accounts/database/oracle/main.yml new file mode 100644 index 000000000..a0e20ff7b --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/oracle/main.yml @@ -0,0 +1,23 @@ +- hosts: oralce + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + oracle_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + filter: users + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.users }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/oracle/manifest.yml b/apps/assets/automations/gather_accounts/database/oracle/manifest.yml new file mode 100644 index 000000000..4753f1495 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_oracle +name: Gather account from Oracle +category: database +type: + - oracle +method: gather_accounts diff --git a/apps/assets/automations/gather_facts/database/mongodb/main.yml b/apps/assets/automations/gather_facts/database/mongodb/main.yml new file mode 100644 index 000000000..37ce8bbd3 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/mongodb/main.yml @@ -0,0 +1,22 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + register: db_info + + - name: Define info by set_fact + set_fact: + info: + version: "{{ db_info.server_version }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_facts/database/mongodb/manifest.yml b/apps/assets/automations/gather_facts/database/mongodb/manifest.yml new file mode 100644 index 000000000..9b832e838 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: gather_facts_mongodb +name: Gather facts from MongoDB +category: database +type: + - mongodb +method: gather_facts diff --git a/apps/assets/automations/gather_facts/database/oracle/main.yml b/apps/assets/automations/gather_facts/database/oracle/main.yml new file mode 100644 index 000000000..21ab639a4 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/oracle/main.yml @@ -0,0 +1,23 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + register: db_info + + - name: Define info by set_fact + set_fact: + info: + version: "{{ db_info.server_version }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_facts/database/oracle/manifest.yml b/apps/assets/automations/gather_facts/database/oracle/manifest.yml new file mode 100644 index 000000000..d350579d6 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: gather_facts_oracle +name: Gather facts from Oracle +category: database +type: + - oracle +method: gather_facts diff --git a/apps/assets/automations/ping/database/mongodb/main.yml b/apps/assets/automations/ping/database/mongodb/main.yml new file mode 100644 index 000000000..867c51ace --- /dev/null +++ b/apps/assets/automations/ping/database/mongodb/main.yml @@ -0,0 +1,13 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test MongoDB connection + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" diff --git a/apps/assets/automations/ping/database/mongodb/manifest.yml b/apps/assets/automations/ping/database/mongodb/manifest.yml new file mode 100644 index 000000000..45b90eb72 --- /dev/null +++ b/apps/assets/automations/ping/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: mongodb_ping +name: Ping MongoDB +category: database +type: + - mongodb +method: ping diff --git a/apps/assets/automations/ping/database/oracle/main.yml b/apps/assets/automations/ping/database/oracle/main.yml new file mode 100644 index 000000000..fefad7148 --- /dev/null +++ b/apps/assets/automations/ping/database/oracle/main.yml @@ -0,0 +1,14 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test Oracle connection + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" diff --git a/apps/assets/automations/ping/database/oracle/manifest.yml b/apps/assets/automations/ping/database/oracle/manifest.yml new file mode 100644 index 000000000..3912941c3 --- /dev/null +++ b/apps/assets/automations/ping/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: oracle_ping +name: Ping Oracle +category: database +type: + - oracle +method: ping diff --git a/apps/assets/automations/ping/database/sqlserver/main.yml b/apps/assets/automations/ping/database/sqlserver/main.yml new file mode 100644 index 000000000..839a785a5 --- /dev/null +++ b/apps/assets/automations/ping/database/sqlserver/main.yml @@ -0,0 +1,15 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test SQLServer connection + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version diff --git a/apps/assets/automations/ping/database/sqlserver/manifest.yml b/apps/assets/automations/ping/database/sqlserver/manifest.yml new file mode 100644 index 000000000..b5cd6bf88 --- /dev/null +++ b/apps/assets/automations/ping/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: sqlserver_ping +name: Ping SQLServer +category: database +type: + - sqlserver +method: ping diff --git a/apps/assets/automations/push_account/database/mongodb/main.yml b/apps/assets/automations/push_account/database/mongodb/main.yml new file mode 100644 index 000000000..d516251db --- /dev/null +++ b/apps/assets/automations/push_account/database/mongodb/main.yml @@ -0,0 +1,16 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Add user account.username + mongodb_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + db: "{{ jms_asset.specific.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" diff --git a/apps/assets/automations/push_account/database/mongodb/manifest.yml b/apps/assets/automations/push_account/database/mongodb/manifest.yml new file mode 100644 index 000000000..9de93f5e7 --- /dev/null +++ b/apps/assets/automations/push_account/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_mongodb +name: Push account from MongoDB +category: database +type: + - mongodb +method: push_account diff --git a/apps/assets/automations/push_account/database/oracle/main.yml b/apps/assets/automations/push_account/database/oracle/main.yml new file mode 100644 index 000000000..5812dfee1 --- /dev/null +++ b/apps/assets/automations/push_account/database/oracle/main.yml @@ -0,0 +1,16 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Add user account.username + oracle_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" diff --git a/apps/assets/automations/push_account/database/oracle/manifest.yml b/apps/assets/automations/push_account/database/oracle/manifest.yml new file mode 100644 index 000000000..da1faed6f --- /dev/null +++ b/apps/assets/automations/push_account/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_oracle +name: Push account from Oracle +category: database +type: + - oracle +method: push_account diff --git a/apps/assets/automations/verify_account/database/mongodb/main.yml b/apps/assets/automations/verify_account/database/mongodb/main.yml new file mode 100644 index 000000000..1cf79b694 --- /dev/null +++ b/apps/assets/automations/verify_account/database/mongodb/main.yml @@ -0,0 +1,13 @@ +- hosts: mongdb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" diff --git a/apps/assets/automations/verify_account/database/mongodb/manifest.yml b/apps/assets/automations/verify_account/database/mongodb/manifest.yml new file mode 100644 index 000000000..24cc398c2 --- /dev/null +++ b/apps/assets/automations/verify_account/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_mongodb +name: Verify account from MongoDB +category: database +type: + - mongodb +method: verify_account diff --git a/apps/assets/automations/verify_account/database/oracle/main.yml b/apps/assets/automations/verify_account/database/oracle/main.yml new file mode 100644 index 000000000..ed4091401 --- /dev/null +++ b/apps/assets/automations/verify_account/database/oracle/main.yml @@ -0,0 +1,14 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" diff --git a/apps/assets/automations/verify_account/database/oracle/manifest.yml b/apps/assets/automations/verify_account/database/oracle/manifest.yml new file mode 100644 index 000000000..b58a7f888 --- /dev/null +++ b/apps/assets/automations/verify_account/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_oracle +name: Verify account from Oracle +category: database +type: + - oracle +method: verify_account diff --git a/apps/assets/automations/verify_account/database/sqlserver/main.yml b/apps/assets/automations/verify_account/database/sqlserver/main.yml new file mode 100644 index 000000000..256803702 --- /dev/null +++ b/apps/assets/automations/verify_account/database/sqlserver/main.yml @@ -0,0 +1,15 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version diff --git a/apps/assets/automations/verify_account/database/sqlserver/manifest.yml b/apps/assets/automations/verify_account/database/sqlserver/manifest.yml new file mode 100644 index 000000000..8af52ab0b --- /dev/null +++ b/apps/assets/automations/verify_account/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_sqlserver +name: Verify account from SQLServer +category: database +type: + - sqlserver +method: verify_account diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 1e006ae77..f71801b76 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -112,6 +112,9 @@ class JMSInventory: 'secret': account.secret, 'secret_type': account.secret_type } if account else None } + if host['jms_account'] and asset.platform.type == 'oracle': + host['jms_account']['mode'] = 'sysdba' if account.privileged else None + ansible_config = dict(automation.ansible_config) ansible_connection = ansible_config.get('ansible_connection', 'ssh') host.update(ansible_config) diff --git a/apps/ops/ansible/modules/__init__.py b/apps/ops/ansible/modules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/ansible/modules/mongodb_ping.py b/apps/ops/ansible/modules/mongodb_ping.py new file mode 100644 index 000000000..d018cd852 --- /dev/null +++ b/apps/ops/ansible/modules/mongodb_ping.py @@ -0,0 +1,126 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: mongodb_ping +short_description: Check remote MongoDB server availability +description: +- Simple module to check remote MongoDB server availability. + +requirements: + - "pymongo" +''' + +EXAMPLES = ''' +- name: > + Ping MongoDB server using non-default credentials and SSL + registering the return values into the result variable for future use + mongodb_ping: + login_db: test_db + login_host: jumpserver + login_user: jms + login_password: secret_pass + ssl: True + ssl_ca_certs: "/tmp/ca.crt" + ssl_certfile: "/tmp/tls.key" #cert and key in one file + connection_options: + - "tlsAllowInvalidHostnames=true" +''' + +RETURN = ''' +is_available: + description: MongoDB server availability. + returned: always + type: bool + sample: true +server_version: + description: MongoDB server version. + returned: always + type: str + sample: '4.0.0' +conn_err_msg: + description: Connection error message. + returned: always + type: str + sample: '' +''' + + +from pymongo.errors import PyMongoError +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_common import ( + mongodb_common_argument_spec, + mongo_auth, + get_mongodb_client, +) + + +class MongoDBPing(object): + def __init__(self, module, client): + self.module = module + self.client = client + self.is_available = False + self.conn_err_msg = '' + self.version = '' + + def do(self): + self.get_mongodb_version() + return self.is_available, self.version + + def get_err(self): + return self.conn_err_msg + + def get_mongodb_version(self): + try: + server_info = self.client.server_info() + self.is_available = True + self.version = server_info.get('version', '') + except PyMongoError as err: + self.is_available = False + self.version = '' + self.conn_err_msg = err + + +# ========================================= +# Module execution. +# + + +def main(): + argument_spec = mongodb_common_argument_spec() + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + client = None + result = { + 'changed': False, 'is_available': False, 'server_version': '' + } + try: + client = get_mongodb_client(module, directConnection=True) + client = mongo_auth(module, client, directConnection=True) + except Exception as e: + module.fail_json(msg='Unable to connect to database: %s' % to_native(e)) + + mongodb_ping = MongoDBPing(module, client) + result["is_available"], result["server_version"] = mongodb_ping.do() + conn_err_msg = mongodb_ping.get_err() + if conn_err_msg: + module.fail_json(msg='Unable to connect to database: %s' % conn_err_msg) + + try: + client.close() + except Exception: + pass + + return module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/mongodb_user.py b/apps/ops/ansible/modules/mongodb_user.py new file mode 100644 index 000000000..93493fd58 --- /dev/null +++ b/apps/ops/ansible/modules/mongodb_user.py @@ -0,0 +1,426 @@ +#!/usr/bin/python + +# Modified from ansible_collections.community.mongodb.plugins.modules.mongodb_user + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: mongodb_user +short_description: Adds or removes a user from a MongoDB database +description: + - Adds or removes a user from a MongoDB database. +version_added: "1.0.0" + +extends_documentation_fragment: + - community.mongodb.login_options + - community.mongodb.ssl_options + +options: + replica_set: + description: + - Replica set to connect to (automatically connects to primary for writes). + type: str + database: + description: + - The name of the database to add/remove the user from. + required: true + type: str + aliases: [db] + name: + description: + - The name of the user to add or remove. + required: true + aliases: [user] + type: str + password: + description: + - The password to use for the user. + type: str + aliases: [pass] + roles: + type: list + elements: raw + description: + - > + The database user roles valid values could either be one or more of the following strings: + 'read', 'readWrite', 'dbAdmin', 'userAdmin', 'clusterAdmin', 'readAnyDatabase', 'readWriteAnyDatabase', 'userAdminAnyDatabase', + 'dbAdminAnyDatabase' + - "Or the following dictionary '{ db: DATABASE_NAME, role: ROLE_NAME }'." + - "This param requires pymongo 2.5+. If it is a string, mongodb 2.4+ is also required. If it is a dictionary, mongo 2.6+ is required." + state: + description: + - The database user state. + default: present + choices: [absent, present] + type: str + update_password: + default: always + choices: [always, on_create] + description: + - C(always) will always update passwords and cause the module to return changed. + - C(on_create) will only set the password for newly created users. + - This must be C(always) to use the localhost exception when adding the first admin user. + - This option is effectively ignored when using x.509 certs. It is defaulted to 'on_create' to maintain a \ + a specific module behaviour when the login_database is '$external'. + type: str + create_for_localhost_exception: + type: path + description: + - This is parmeter is only useful for handling special treatment around the localhost exception. + - If C(login_user) is defined, then the localhost exception is not active and this parameter has no effect. + - If this file is NOT present (and C(login_user) is not defined), then touch this file after successfully adding the user. + - If this file is present (and C(login_user) is not defined), then skip this task. + +notes: + - Requires the pymongo Python package on the remote host, version 2.4.2+. This + can be installed using pip or the OS package manager. Newer mongo server versions require newer + pymongo versions. @see http://api.mongodb.org/python/current/installation.html +requirements: + - "pymongo" +author: + - "Elliott Foster (@elliotttf)" + - "Julien Thebault (@Lujeni)" +''' + +EXAMPLES = ''' +- name: Create 'burgers' database user with name 'bob' and password '12345'. + community.mongodb.mongodb_user: + database: burgers + name: bob + password: 12345 + state: present + +- name: Create a database user via SSL (MongoDB must be compiled with the SSL option and configured properly) + community.mongodb.mongodb_user: + database: burgers + name: bob + password: 12345 + state: present + ssl: True + +- name: Delete 'burgers' database user with name 'bob'. + community.mongodb.mongodb_user: + database: burgers + name: bob + state: absent + +- name: Define more users with various specific roles (if not defined, no roles is assigned, and the user will be added via pre mongo 2.2 style) + community.mongodb.mongodb_user: + database: burgers + name: ben + password: 12345 + roles: read + state: present + +- name: Define roles + community.mongodb.mongodb_user: + database: burgers + name: jim + password: 12345 + roles: readWrite,dbAdmin,userAdmin + state: present + +- name: Define roles + community.mongodb.mongodb_user: + database: burgers + name: joe + password: 12345 + roles: readWriteAnyDatabase + state: present + +- name: Add a user to database in a replica set, the primary server is automatically discovered and written to + community.mongodb.mongodb_user: + database: burgers + name: bob + replica_set: belcher + password: 12345 + roles: readWriteAnyDatabase + state: present + +# add a user 'oplog_reader' with read only access to the 'local' database on the replica_set 'belcher'. This is useful for oplog access (MONGO_OPLOG_URL). +# please notice the credentials must be added to the 'admin' database because the 'local' database is not synchronized and can't receive user credentials +# To login with such user, the connection string should be MONGO_OPLOG_URL="mongodb://oplog_reader:oplog_reader_password@server1,server2/local?authSource=admin" +# This syntax requires mongodb 2.6+ and pymongo 2.5+ +- name: Roles as a dictionary + community.mongodb.mongodb_user: + login_user: root + login_password: root_password + database: admin + user: oplog_reader + password: oplog_reader_password + state: present + replica_set: belcher + roles: + - db: local + role: read + +- name: Adding a user with X.509 Member Authentication + community.mongodb.mongodb_user: + login_host: "mongodb-host.test" + login_port: 27001 + login_database: "$external" + database: "admin" + name: "admin" + password: "test" + roles: + - dbAdminAnyDatabase + ssl: true + ssl_ca_certs: "/tmp/ca.crt" + ssl_certfile: "/tmp/tls.key" #cert and key in one file + state: present + auth_mechanism: "MONGODB-X509" + connection_options: + - "tlsAllowInvalidHostnames=true" +''' + +RETURN = ''' +user: + description: The name of the user to add or remove. + returned: success + type: str +''' + +import os +import traceback +from operator import itemgetter + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import binary_type, text_type +from ansible.module_utils._text import to_native, to_bytes +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_common import ( + missing_required_lib, + mongodb_common_argument_spec, + mongo_auth, + PYMONGO_IMP_ERR, + pymongo_found, + get_mongodb_client, +) + + +def user_find(client, user, db_name): + """Check if the user exists. + + Args: + client (cursor): Mongodb cursor on admin database. + user (str): User to check. + db_name (str): User's database. + + Returns: + dict: when user exists, False otherwise. + """ + try: + for mongo_user in client[db_name].command('usersInfo')['users']: + if mongo_user['user'] == user: + # NOTE: there is no 'db' field in mongo 2.4. + if 'db' not in mongo_user: + return mongo_user + # Workaround to make the condition works with AWS DocumentDB, + # since all users are in the admin database. + if mongo_user["db"] in [db_name, "admin"]: + return mongo_user + except Exception as excep: + if hasattr(excep, 'code') and excep.code == 11: # 11=UserNotFound + pass # Allow return False + else: + raise + return False + + +def user_add(module, client, db_name, user, password, roles): + # pymongo's user_add is a _create_or_update_user so we won't know if it was changed or updated + # without reproducing a lot of the logic in database.py of pymongo + db = client[db_name] + + try: + exists = user_find(client, user, db_name) + except Exception as excep: + # We get this exception: "not authorized on admin to execute command" + # when auth is enabled on a new instance. The loalhost exception should + # allow us to create the first user. If the localhost exception does not apply, + # then user creation will also fail with unauthorized. So, ignore Unauthorized here. + if hasattr(excep, 'code') and excep.code == 13: # 13=Unauthorized + exists = False + else: + raise + + if exists: + user_add_db_command = 'updateUser' + if not roles: + roles = None + else: + user_add_db_command = 'createUser' + + user_dict = {} + + if password is not None: + user_dict["pwd"] = password + if roles is not None: + user_dict["roles"] = roles + + db.command(user_add_db_command, user, **user_dict) + + +def user_remove(module, client, db_name, user): + exists = user_find(client, user, db_name) + if exists: + if module.check_mode: + module.exit_json(changed=True, user=user) + db = client[db_name] + db.command("dropUser", user) + else: + module.exit_json(changed=False, user=user) + + +def check_if_roles_changed(uinfo, roles, db_name): + # We must be aware of users which can read the oplog on a replicaset + # Such users must have access to the local DB, but since this DB does not store users credentials + # and is not synchronized among replica sets, the user must be stored on the admin db + # Therefore their structure is the following : + # { + # "_id" : "admin.oplog_reader", + # "user" : "oplog_reader", + # "db" : "admin", # <-- admin DB + # "roles" : [ + # { + # "role" : "read", + # "db" : "local" # <-- local DB + # } + # ] + # } + + def make_sure_roles_are_a_list_of_dict(roles, db_name): + output = list() + for role in roles: + if isinstance(role, (binary_type, text_type)): + new_role = {"role": role, "db": db_name} + output.append(new_role) + else: + output.append(role) + return output + + roles_as_list_of_dict = make_sure_roles_are_a_list_of_dict(roles, db_name) + uinfo_roles = uinfo.get('roles', []) + + if sorted(roles_as_list_of_dict, key=itemgetter('db')) == sorted(uinfo_roles, key=itemgetter('db')): + return False + return True + + +# ========================================= +# Module execution. +# + +def main(): + argument_spec = mongodb_common_argument_spec() + argument_spec.update( + database=dict(required=True, aliases=['db']), + name=dict(required=True, aliases=['user']), + password=dict(aliases=['pass'], no_log=True), + replica_set=dict(default=None), + roles=dict(default=None, type='list', elements='raw'), + state=dict(default='present', choices=['absent', 'present']), + update_password=dict(default="always", choices=["always", "on_create"], no_log=False), + create_for_localhost_exception=dict(default=None, type='path'), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + login_user = module.params['login_user'] + + # Certs don't have a password but we want this module behaviour + if module.params['login_database'] == '$external': + module.params['update_password'] = 'on_create' + + if not pymongo_found: + module.fail_json(msg=missing_required_lib('pymongo'), + exception=PYMONGO_IMP_ERR) + + create_for_localhost_exception = module.params['create_for_localhost_exception'] + b_create_for_localhost_exception = ( + to_bytes(create_for_localhost_exception, errors='surrogate_or_strict') + if create_for_localhost_exception is not None else None + ) + + db_name = module.params['database'] + user = module.params['name'] + password = module.params['password'] + roles = module.params['roles'] or [] + state = module.params['state'] + update_password = module.params['update_password'] + + try: + directConnection = False + if module.params['replica_set'] is None: + directConnection = True + client = get_mongodb_client(module, directConnection=directConnection) + client = mongo_auth(module, client, directConnection=directConnection) + except Exception as e: + module.fail_json(msg='Unable to connect to database: %s' % to_native(e)) + + if state == 'present': + if password is None and update_password == 'always': + module.fail_json(msg='password parameter required when adding a user unless update_password is set to on_create') + + if login_user is None and create_for_localhost_exception is not None: + if os.path.exists(b_create_for_localhost_exception): + try: + client.close() + except Exception: + pass + module.exit_json(changed=False, user=user, skipped=True, msg="The path in create_for_localhost_exception exists.") + + try: + if update_password != 'always': + uinfo = user_find(client, user, db_name) + if uinfo: + password = None + if not check_if_roles_changed(uinfo, roles, db_name): + module.exit_json(changed=False, user=user) + + if module.check_mode: + module.exit_json(changed=True, user=user) + user_add(module, client, db_name, user, password, roles) + except Exception as e: + module.fail_json(msg='Unable to add or update user: %s' % to_native(e), exception=traceback.format_exc()) + finally: + try: + client.close() + except Exception: + pass + # Here we can check password change if mongo provide a query for that : https://jira.mongodb.org/browse/SERVER-22848 + # newuinfo = user_find(client, user, db_name) + # if uinfo['role'] == newuinfo['role'] and CheckPasswordHere: + # module.exit_json(changed=False, user=user) + + if login_user is None and create_for_localhost_exception is not None: + # localhost exception applied. + try: + # touch the file + open(b_create_for_localhost_exception, 'wb').close() + except Exception as e: + module.fail_json( + changed=True, + msg='Added user but unable to touch create_for_localhost_exception file %s: %s' % (create_for_localhost_exception, to_native(e)), + exception=traceback.format_exc() + ) + + elif state == 'absent': + try: + user_remove(module, client, db_name, user) + except Exception as e: + module.fail_json(msg='Unable to remove user: %s' % to_native(e), exception=traceback.format_exc()) + finally: + try: + client.close() + except Exception: + pass + module.exit_json(changed=True, user=user) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/oracle_info.py b/apps/ops/ansible/modules/oracle_info.py new file mode 100644 index 000000000..005206be9 --- /dev/null +++ b/apps/ops/ansible/modules/oracle_info.py @@ -0,0 +1,261 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: oracle_info +short_description: Gather information about Oracle servers +description: +- Gathers information about Oracle servers. + +options: + filter: + description: + - Limit the collected information by comma separated string or YAML list. + - Allowable values are C(version), C(databases), C(settings), C(users). + - By default, collects all subsets. + - You can use '!' before value (for example, C(!users)) to exclude it from the information. + - If you pass including and excluding values to the filter, for example, I(filter=!settings,version), + the excluding values, C(!settings) in this case, will be ignored. + type: list + elements: str + login_db: + description: + - Database name to connect to. + - It makes sense if I(login_user) is allowed to connect to a specific database only. + type: str + exclude_fields: + description: + - List of fields which are not needed to collect. + - "Supports elements: C(db_size). Unsupported elements will be ignored." + type: list + elements: str +''' + +EXAMPLES = r''' +- name: Get Oracle version with non-default credentials + oracle_info: + login_user: mysuperuser + login_password: mysuperpass + login_database: service_name + filter: version + +- name: Collect all info except settings and users by sys + oracle_info: + login_user: sys + login_password: sys_pass + login_database: service_name + filter: "!settings,!users" + exclude_fields: db_size +''' + +RETURN = r''' +version: + description: Database server version. + returned: if not excluded by filter + type: dict + sample: { "version": {"full": "11.2.0.1.0"} } + contains: + full: + description: Full server version. + returned: if not excluded by filter + type: str + sample: "11.2.0.1.0" +databases: + description: Information about databases. + returned: if not excluded by filter + type: dict + sample: + - { "USERS": { "size": 5242880 }, "EXAMPLE": { "size": 104857600 } } + contains: + size: + description: Database size in bytes. + returned: if not excluded by filter + type: dict + sample: { 'size': 656594 } +settings: + description: Global settings (variables) information. + returned: if not excluded by filter + type: dict + sample: + - { "result_cache_mode": "MANUAL", "instance_type": "RDBMS" } +users: + description: Users information. + returned: if not excluded by filter + type: dict + sample: + - { "USERS": { "TEST": { "USERNAME": "TEST", "ACCOUNT_STATUS": "OPEN" } } } +''' + +from ansible.module_utils.basic import AnsibleModule + +from ops.ansible.modules_utils.oracle_common import ( + OracleClient, oracle_common_argument_spec +) + + +class OracleInfo(object): + def __init__(self, module, oracle_client): + self.module = module + self.oracle_client = oracle_client + self.info = { + 'version': {}, 'databases': {}, + 'settings': {}, 'users': {}, + } + + def get_info(self, filter_, exclude_fields): + include_list = [] + exclude_list = [] + + if filter_: + partial_info = {} + + for fi in filter_: + if fi.lstrip('!') not in self.info: + self.module.warn('filter element: %s is not allowable, ignored' % fi) + continue + + if fi[0] == '!': + exclude_list.append(fi.lstrip('!')) + else: + include_list.append(fi) + + if include_list: + self.__collect(exclude_fields, set(include_list)) + + for i in self.info: + if i in include_list: + partial_info[i] = self.info[i] + else: + not_in_exclude_list = list(set(self.info) - set(exclude_list)) + self.__collect(exclude_fields, set(not_in_exclude_list)) + + for i in self.info: + if i not in exclude_list: + partial_info[i] = self.info[i] + return partial_info + else: + self.__collect(exclude_fields, set(self.info)) + return self.info + + def __collect(self, exclude_fields, wanted): + """Collect all possible subsets.""" + if 'version' in wanted: + self.__get_version() + + if 'settings' in wanted: + self.__get_settings() + + if 'databases' in wanted: + self.__get_databases(exclude_fields) + # + if 'users' in wanted: + self.__get_users() + + def __get_version(self): + version_sql = 'SELECT VERSION FROM PRODUCT_COMPONENT_VERSION where ROWNUM=1' + rtn, err = self.oracle_client.execute(version_sql, exception_to_fail=True) + self.info['version'] = {'full': rtn.get('version')} + + def __get_settings(self): + """Get global variables (instance settings).""" + def _set_settings_value(item_dict): + try: + self.info['settings'][item_dict['name']] = item_dict['value'] + except KeyError: + pass + + settings_sql = "SELECT name, value FROM V$PARAMETER" + rtn, err = self.oracle_client.execute(settings_sql, exception_to_fail=True) + + if isinstance(rtn, dict): + _set_settings_value(rtn) + elif isinstance(rtn, list): + for i in rtn: + _set_settings_value(i) + + def __get_users(self): + """Get user info.""" + def _set_users_value(item_dict): + try: + tablespace = item_dict.pop('default_tablespace') + username = item_dict.pop('username') + partial_users = self.info['users'].get(tablespace, {}) + partial_users[username] = item_dict + self.info['users'][tablespace] = partial_users + except KeyError: + pass + + users_sql = "SELECT * FROM dba_users" + rtn, err = self.oracle_client.execute(users_sql, exception_to_fail=True) + if isinstance(rtn, dict): + _set_users_value(rtn) + elif isinstance(rtn, list): + for i in rtn: + _set_users_value(i) + + def __get_databases(self, exclude_fields): + """Get info about databases.""" + def _set_databases_value(item_dict): + try: + tablespace_name = item_dict.pop('tablespace_name') + size = item_dict.get('size') + partial_params = {} + if size: + partial_params['size'] = size + self.info['databases'][tablespace_name] = partial_params + except KeyError: + pass + + database_sql = 'SELECT ' \ + ' tablespace_name, sum(bytes) as "size"' \ + 'FROM dba_data_files GROUP BY tablespace_name' + if exclude_fields and 'db_size' in exclude_fields: + database_sql = "SELECT " \ + " tablespace_name " \ + "FROM dba_data_files GROUP BY tablespace_name" + + rtn, err = self.oracle_client.execute(database_sql, exception_to_fail=True) + if isinstance(rtn, dict): + _set_databases_value(rtn) + elif isinstance(rtn, list): + for i in rtn: + _set_databases_value(i) + + +# =========================================== +# Module execution. +# + + +def main(): + argument_spec = oracle_common_argument_spec() + argument_spec.update( + filter=dict(type='list'), + exclude_fields=dict(type='list'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + filter_ = module.params['filter'] + exclude_fields = module.params['exclude_fields'] + + if filter_: + filter_ = [f.strip() for f in filter_] + + if exclude_fields: + exclude_fields = set([f.strip() for f in exclude_fields]) + + oracle_client = OracleClient(module) + oracle = OracleInfo(module, oracle_client) + + module.exit_json(changed=False, **oracle.get_info(filter_, exclude_fields)) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/oracle_ping.py b/apps/ops/ansible/modules/oracle_ping.py new file mode 100644 index 000000000..df1069d11 --- /dev/null +++ b/apps/ops/ansible/modules/oracle_ping.py @@ -0,0 +1,107 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: oracle_ping +short_description: Check remote Oracle server availability +description: +- Simple module to check remote Oracle server availability. + +requirements: + - "oracledb" +''' + +EXAMPLES = ''' +- name: > + Ping Oracle server using non-default credentials and SSL + registering the return values into the result variable for future use + oracle_ping: + login_host: jumpserver + login_port: 1521 + login_user: jms + login_password: secret_pass + login_database: test_db +''' + +RETURN = ''' +is_available: + description: Oracle server availability. + returned: always + type: bool + sample: true +server_version: + description: Oracle server version. + returned: always + type: str + sample: '4.0.0' +conn_err_msg: + description: Connection error message. + returned: always + type: str + sample: '' +''' + +from ansible.module_utils.basic import AnsibleModule +from ops.ansible.modules_utils.oracle_common import ( + OracleClient, oracle_common_argument_spec +) + + +class OracleDBPing(object): + def __init__(self, module, oracle_client): + self.module = module + self.oracle_client = oracle_client + self.is_available = False + self.conn_err_msg = '' + self.version = '' + + def do(self): + self.get_oracle_version() + return self.is_available, self.version + + def get_err(self): + return self.conn_err_msg + + def get_oracle_version(self): + version_sql = 'SELECT VERSION FROM PRODUCT_COMPONENT_VERSION where ROWNUM=1' + rtn, err = self.oracle_client.execute(version_sql) + if err: + self.conn_err_msg = err + else: + self.version = rtn.get('version') + self.is_available = True + + +# ========================================= +# Module execution. +# + + +def main(): + argument_spec = oracle_common_argument_spec() + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + result = { + 'changed': False, 'is_available': False, 'server_version': '' + } + oracle_client = OracleClient(module) + + oracle_ping = OracleDBPing(module, oracle_client) + result["is_available"], result["server_version"] = oracle_ping.do() + conn_err_msg = oracle_ping.get_err() + oracle_client.close() + if conn_err_msg: + module.fail_json(msg='Unable to connect to database: %s' % conn_err_msg) + + return module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/oracle_user.py b/apps/ops/ansible/modules/oracle_user.py new file mode 100644 index 000000000..9e23fe70c --- /dev/null +++ b/apps/ops/ansible/modules/oracle_user.py @@ -0,0 +1,215 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: oracle_user +short_description: Adds or removes a user from a Oracle database +description: + - Adds or removes a user from a Oracle database. + +options: + authentication_type: + description: + - Authentication type of the user(default password) + required: false + type: str + choices: ['external', 'global', 'no_authentication', 'password'] + default_tablespace: + description: + - The default tablespace for the user + - If not provided, the default is used + required: false + type: str + oracle_home: + description: + - Define the directory into which all Oracle software is installed. + - Define ORACLE_HOME environment variable if set. + type: str + state: + description: + - The database user state. + default: present + choices: [absent, present] + type: str + update_password: + default: always + choices: [always, on_create] + description: + - C(always) will always update passwords and cause the module to return changed. + - C(on_create) will only set the password for newly created users. + type: str + temporary_tablespace: + description: + - The default temporary tablespace for the user + - If not provided, the default is used + required: false + type: str + name: + description: + - The name of the user to add or remove. + required: true + aliases: [user] + type: str + password: + description: + - The password to use for the user. + type: str + aliases: [pass] + +requirements: + - "oracledb" +''' + +EXAMPLES = ''' +- name: Create default tablespace user with name 'jms' and password '123456'. + oracle_user: + hostname: "remote server" + login_database: "helowin" + login_username: "system" + login_password: "123456" + name: "jms" + password: "123456" + +- name: Delete user with name 'jms'. + oracle_user: + hostname: "remote server" + login_database: "helowin" + login_username: "system" + login_password: "123456" + name: "jms" + state: "absent" +''' + +RETURN = ''' +name: + description: The name of the user to add or remove. + returned: success + type: str +''' + +from ansible.module_utils.basic import AnsibleModule + +from ops.ansible.modules_utils.oracle_common import ( + OracleClient, oracle_common_argument_spec +) + + +def user_find(oracle_client, username): + user = None + username = username.upper() + user_find_sql = "select username, " \ + " authentication_type, " \ + " default_tablespace, " \ + " temporary_tablespace " \ + "from dba_users where username='%s'" % username + rtn, err = oracle_client.execute(user_find_sql) + if isinstance(rtn, dict): + user = rtn + return user + + +def user_add( + module, oracle_client, username, password, auth_type, + default_tablespace, temporary_tablespace +): + username = username.upper() + extend_sql = None + user = user_find(oracle_client, username) + auth_type = auth_type.lower() + identified_suffix_map = { + 'external': 'identified externally ', + 'global': 'identified globally ', + 'password': 'identified by "%s" ', + } + if user: + user_sql = "alter user %s " % username + user_sql += identified_suffix_map.get(auth_type, 'no authentication ') % password + + if default_tablespace and default_tablespace.lower() != user['default_tablespace'].lower(): + user_sql += 'default tablespace %s quota unlimited on %s ' % (default_tablespace, default_tablespace) + if temporary_tablespace and temporary_tablespace.lower() != user['temporary_tablespace'].lower(): + user_sql += 'temporary tablespace %s ' % temporary_tablespace + else: + user_sql = "create user %s " % username + user_sql += identified_suffix_map.get(auth_type, 'no authentication ') % password + if default_tablespace: + user_sql += 'default tablespace %s quota unlimited on %s ' % (default_tablespace, default_tablespace) + if temporary_tablespace: + user_sql += 'temporary tablespace %s ' % temporary_tablespace + extend_sql = 'grant connect to %s' % username + + rtn, err = oracle_client.execute(user_sql) + if err: + module.fail_json(msg='Cannot add/edit user %s: %s' % (username, err), changed=False) + else: + if extend_sql: + oracle_client.execute(extend_sql) + module.exit_json(msg='User %s has been created.' % username, changed=True, name=username) + + +def user_remove(module, oracle_client, username): + user = user_find(oracle_client, username) + + if user: + rtn, err = oracle_client.execute('drop user %s cascade' % username) + if err: + module.fail_json(msg='Cannot drop user %s: %s' % (username, err), changed=False) + else: + module.exit_json(msg='User %s dropped.' % username, changed=True, name=username) + else: + module.exit_json(msg="User %s doesn't exist." % username, changed=False, name=username) + + +# ========================================= +# Module execution. +# + +def main(): + argument_spec = oracle_common_argument_spec() + argument_spec.update( + authentication_type=dict( + type='str', required=False, + choices=['external', 'global', 'no_authentication', 'password'] + ), + default_tablespace=dict(required=False, aliases=['db']), + name=dict(required=True, aliases=['user']), + password=dict(aliases=['pass'], no_log=True), + state=dict(type='str', default='present', choices=['absent', 'present']), + update_password=dict(default="always", choices=["always", "on_create"], no_log=False), + temporary_tablespace=dict(type='str', default=None), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + authentication_type = module.params['authentication_type'] or 'password' + default_tablespace = module.params['default_tablespace'] + user = module.params['name'] + password = module.params['password'] + state = module.params['state'] + update_password = module.params['update_password'] + temporary_tablespace = module.params['temporary_tablespace'] + + oracle_client = OracleClient(module) + if state == 'present': + if password is None and update_password == 'always': + module.fail_json( + msg='password parameter required when adding a user unless update_password is set to on_create' + ) + user_add( + module, oracle_client, username=user, password=password, + auth_type=authentication_type, default_tablespace=default_tablespace, + temporary_tablespace=temporary_tablespace + ) + elif state == 'absent': + user_remove(oracle_client) + module.exit_json(changed=True, user=user) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules_utils/__init__.py b/apps/ops/ansible/modules_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/ansible/modules_utils/oracle_common.py b/apps/ops/ansible/modules_utils/oracle_common.py new file mode 100644 index 000000000..c88f04373 --- /dev/null +++ b/apps/ops/ansible/modules_utils/oracle_common.py @@ -0,0 +1,94 @@ +import os + +import oracledb + +from oracledb.exceptions import DatabaseError +from ansible.module_utils._text import to_native + + +def oracle_common_argument_spec(): + """ + Returns a dict containing common options shared across the Oracle modules. + """ + options = dict( + login_user=dict(type='str', required=False), + login_password=dict(type='str', required=False, no_log=True), + login_database=dict(type='str', required=False, default='test'), + login_host=dict(type='str', required=False, default='localhost'), + login_port=dict(type='int', required=False, default=1521), + oracle_home=dict(type='str', required=False), + mode=dict(type='str', required=False), + ) + return options + + +class OracleClient(object): + def __init__(self, module): + self.module = module + self._conn = None + self._cursor = None + self.connect_params = {} + + self.init_params() + + def init_params(self): + params = self.module.params + hostname = params['login_host'] + port = params['login_port'] + service_name = params['login_database'] + username = params['login_user'] + password = params['login_password'] + oracle_home = params['oracle_home'] + mode = params['mode'] + + if oracle_home: + os.environ.setdefault('ORACLE_HOME', oracle_home) + if mode == 'sysdba': + self.connect_params['mode'] = oracledb.SYSDBA + + self.connect_params['host'] = hostname + self.connect_params['port'] = port + self.connect_params['user'] = username + self.connect_params['password'] = password + self.connect_params['service_name'] = service_name + + @property + def cursor(self): + if self._cursor is None: + try: + oracledb.init_oracle_client(lib_dir='/Users/jiangweidong/Downloads/instantclient_19_8') + self._conn = oracledb.connect(**self.connect_params) + self._cursor = self._conn.cursor() + except DatabaseError as err: + self.module.fail_json( + msg="Unable to connect to database: %s, %s" % (to_native(err), self.connect_params) + ) + return self._cursor + + def execute(self, sql, exception_to_fail=False): + sql = sql[:-1] if sql.endswith(';') else sql + result, error = None, None + try: + self.cursor.execute(sql) + sql_header = self.cursor.description or [] + column_names = [description[0].lower() for description in sql_header] + if column_names: + result = [dict(zip(column_names, row)) for row in self.cursor] + result = result[0] if len(result) == 1 else result + else: + result = None + except DatabaseError as err: + error = err + if exception_to_fail and error: + self.module.fail_json(msg='Cannot execute sql: %s' % to_native(error)) + return result, error + + def close(self): + try: + if self._cursor: + self._cursor.close() + if self._conn: + self._conn.close() + except: + pass + From becb10a453142c9e5b02d18ee5d424595a8db183 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 18:31:35 +0800 Subject: [PATCH 317/488] perf: changr secret record api --- apps/assets/api/automations/change_secret.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/automations/change_secret.py b/apps/assets/api/automations/change_secret.py index 112f0f93b..291c56518 100644 --- a/apps/assets/api/automations/change_secret.py +++ b/apps/assets/api/automations/change_secret.py @@ -36,5 +36,5 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): execution = get_object_or_none(AutomationExecution, pk=eid) if execution: queryset = queryset.filter(execution=execution) - queryset = queryset.order_by('is_success', '-date_start') + queryset = queryset.order_by('-date_start') return queryset From 2f611009dc21ca6228e2d07016107eeecc1f1289 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 9 Nov 2022 20:51:43 +0800 Subject: [PATCH 318/488] perf: acl --- apps/acls/serializers/login_acl.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/acls/serializers/login_acl.py b/apps/acls/serializers/login_acl.py index a699ae1ea..536061082 100644 --- a/apps/acls/serializers/login_acl.py +++ b/apps/acls/serializers/login_acl.py @@ -2,7 +2,9 @@ from django.utils.translation import ugettext as _ from rest_framework import serializers from common.drf.serializers import BulkModelSerializer from common.drf.serializers import MethodSerializer +from common.drf.fields import ObjectRelatedField from jumpserver.utils import has_valid_xpack_license +from users.models import User from ..models import LoginACL from .rules import RuleSerializer @@ -12,8 +14,10 @@ common_help_text = _('Format for comma-delimited string, with * indicating a mat class LoginACLSerializer(BulkModelSerializer): - user_display = serializers.ReadOnlyField(source='user.username', label=_('Username')) - reviewers_display = serializers.SerializerMethodField(label=_('Reviewers')) + user = ObjectRelatedField(queryset=User.objects, label=_('User')) + reviewers = ObjectRelatedField( + queryset=User.objects, label=_('Reviewers'), many=True, required=False + ) action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count') rules = MethodSerializer() @@ -22,13 +26,11 @@ class LoginACLSerializer(BulkModelSerializer): model = LoginACL fields_mini = ['id', 'name'] fields_small = fields_mini + [ - 'priority', 'rules', 'action', 'action_display', - 'is_active', 'user', 'user_display', - 'date_created', 'date_updated', 'reviewers_amount', - 'comment', 'created_by' + 'priority', 'rules', 'action', 'action_display', 'is_active', 'user', + 'date_created', 'date_updated', 'reviewers_amount', 'comment', 'created_by', ] - fields_fk = ['user', 'user_display'] - fields_m2m = ['reviewers', 'reviewers_display'] + fields_fk = ['user'] + fields_m2m = ['reviewers'] fields = fields_small + fields_fk + fields_m2m extra_kwargs = { 'priority': {'default': 50}, From 1ffcf8f39ced5c59194b81f51ac1659135237e4e Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 9 Nov 2022 20:58:45 +0800 Subject: [PATCH 319/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/assets/api.py | 18 +++++++++--------- apps/perms/api/user_permission/assets/mixin.py | 9 ++++----- apps/perms/serializers/user_permission.py | 18 +++++++++++------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/apps/perms/api/user_permission/assets/api.py b/apps/perms/api/user_permission/assets/api.py index 9c616adc3..cb9bf53aa 100644 --- a/apps/perms/api/user_permission/assets/api.py +++ b/apps/perms/api/user_permission/assets/api.py @@ -14,16 +14,15 @@ __all__ = [ 'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeApi', 'MyUngroupAssetsAsTreeApi', 'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi', - 'UserGrantedNodeAssetsApi', - 'MyGrantedNodeAssetsApi', + 'UserGrantedNodeAssetsApi', 'MyGrantedNodeAssetsApi', ] logger = get_logger(__name__) class UserDirectGrantedAssetsApi( - AssetRoleAdminMixin, - UserDirectGrantedAssetsQuerysetMixin, AssetsSerializerFormatMixin, ListAPIView + AssetRoleAdminMixin, UserDirectGrantedAssetsQuerysetMixin, + AssetsSerializerFormatMixin, ListAPIView ): """ 直接授权给用户的资产 """ pass @@ -35,8 +34,8 @@ class MyDirectGrantedAssetsApi(AssetRoleUserMixin, UserDirectGrantedAssetsApi): class UserFavoriteGrantedAssetsApi( - AssetRoleAdminMixin, - UserFavoriteGrantedAssetsMixin, AssetsSerializerFormatMixin, ListAPIView + AssetRoleAdminMixin, UserFavoriteGrantedAssetsMixin, + AssetsSerializerFormatMixin, ListAPIView ): """ 用户收藏的授权资产 """ pass @@ -63,8 +62,8 @@ class MyUngroupAssetsAsTreeApi(AssetRoleUserMixin, UserDirectGrantedAssetsAsTree class UserAllGrantedAssetsApi( - AssetRoleAdminMixin, - UserAllGrantedAssetsQuerysetMixin, AssetsSerializerFormatMixin, ListAPIView + AssetRoleAdminMixin, UserAllGrantedAssetsQuerysetMixin, + AssetsSerializerFormatMixin, ListAPIView ): """ 授权给用户的所有资产 """ pass @@ -81,7 +80,8 @@ class MyAllAssetsAsTreeApi(AssetsTreeFormatMixin, MyAllGrantedAssetsApi): class UserGrantedNodeAssetsApi( - AssetRoleAdminMixin, UserGrantedNodeAssetsMixin, AssetsSerializerFormatMixin, ListAPIView + AssetRoleAdminMixin, UserGrantedNodeAssetsMixin, + AssetsSerializerFormatMixin, ListAPIView ): """ 授权给用户的节点资产 """ pass diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index d82e39faa..e7a584ef4 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -1,11 +1,11 @@ from rest_framework.response import Response from rest_framework.request import Request +from common.utils import get_logger from users.models import User from assets.api.mixin import SerializeToTreeNodeMixin -from common.utils import get_logger -from perms.pagination import NodeGrantedAssetPagination, AllGrantedAssetPagination from assets.models import Asset, Node +from perms.pagination import NodeGrantedAssetPagination, AllGrantedAssetPagination from perms import serializers from perms.utils.user_permission import UserGrantedAssetsQueryUtils @@ -21,8 +21,7 @@ class UserDirectGrantedAssetsQuerysetMixin: def get_queryset(self): if getattr(self, 'swagger_fake_view', False): return Asset.objects.none() - user = self.user - assets = UserGrantedAssetsQueryUtils(user) \ + assets = UserGrantedAssetsQueryUtils(self.user) \ .get_direct_granted_assets() \ .prefetch_related('platform') \ .only(*self.only_fields) @@ -32,7 +31,7 @@ class UserDirectGrantedAssetsQuerysetMixin: class UserAllGrantedAssetsQuerysetMixin: only_fields = serializers.AssetGrantedSerializer.Meta.only_fields pagination_class = AllGrantedAssetPagination - ordering_fields = ("name", "address", "port", "cpu_cores") + ordering_fields = ("name", "address") ordering = ('name', ) user: User diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 9b7ac091c..8784a8abb 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -4,14 +4,14 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from common.drf.fields import ObjectRelatedField, LabeledChoiceField from assets.models import Node, Asset, Platform, Account +from assets.const import Category, AllTypes from perms.serializers.permission import ActionsField __all__ = [ - 'NodeGrantedSerializer', - 'AssetGrantedSerializer', - 'ActionsSerializer', - 'AccountsGrantedSerializer' + 'NodeGrantedSerializer', 'AssetGrantedSerializer', + 'ActionsSerializer', 'AccountsGrantedSerializer' ] @@ -20,14 +20,18 @@ class AssetGrantedSerializer(serializers.ModelSerializer): platform = serializers.SlugRelatedField( slug_field='name', queryset=Platform.objects.all(), label=_("Platform") ) + protocols = ObjectRelatedField(read_only=True, many=True) + category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) + type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type')) class Meta: model = Asset only_fields = [ - "id", "name", "address", "protocols", 'domain', - "platform", "comment", "org_id", "is_active" + "id", "name", "address", "protocols", + 'domain', 'platform', + "comment", "org_id", "is_active", ] - fields = only_fields + ['org_name'] + fields = only_fields + ['category', 'type'] + ['org_name'] read_only_fields = fields From ba3f2099e62ceef16568f7b904828155582587ff Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 10 Nov 2022 14:44:23 +0800 Subject: [PATCH 320/488] perf: audits --- apps/acls/serializers/login_asset_acl.py | 6 +-- apps/audits/const.py | 35 +++++++++++++ apps/audits/models.py | 63 ++++-------------------- apps/audits/serializers.py | 33 ++++++------- apps/audits/signal_handlers.py | 56 ++++++++++----------- apps/common/db/fields.py | 2 - apps/common/drf/fields.py | 1 + apps/common/mixins/views.py | 3 +- 8 files changed, 93 insertions(+), 106 deletions(-) diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index 7282bf1a9..6c77eb546 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.models import Organization -from assets.const import Protocol +from common.drf.fields import LabeledChoiceField from acls import models @@ -59,7 +59,7 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): assets = LoginAssetACLAssestsSerializer() accounts = LoginAssetACLAccountsSerializer() reviewers_amount = serializers.IntegerField(read_only=True, source='reviewers.count') - action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) + action = LabeledChoiceField(choices=models.LoginAssetACL.ActionChoices.choices, label=_('Action')) class Meta: model = models.LoginAssetACL @@ -67,7 +67,7 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): fields_small = fields_mini + [ 'users', 'accounts', 'assets', 'is_active', 'date_created', 'date_updated', - 'priority', 'action', 'action_display', 'comment', 'created_by', 'org_id' + 'priority', 'action', 'comment', 'created_by', 'org_id' ] fields_m2m = ['reviewers', 'reviewers_amount'] fields = fields_small + fields_m2m diff --git a/apps/audits/const.py b/apps/audits/const.py index 18033ee78..421e87158 100644 --- a/apps/audits/const.py +++ b/apps/audits/const.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # from django.utils.translation import ugettext_lazy as _ +from django.db.models import TextChoices, IntegerChoices DEFAULT_CITY = _("Unknown") @@ -22,3 +23,37 @@ MODELS_NEED_RECORD = ( # xpack 'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'GatherUserTask', ) + + +class OperateChoices(TextChoices): + mkdir = 'mkdir', _('Mkdir') + rmdir = 'rmdir', _('Rmdir') + delete = 'delete', _('Delete') + upload = 'upload', _('Upload') + rename = 'rename', _('Rename') + symlink = 'symlink', _('Symlink') + download = 'download', _('Download') + + +class ActionChoices(TextChoices): + view = 'view', _('View') + update = 'update', _('Update') + delete = 'delete', _('Delete') + create = 'create', _('Create') + + +class LoginTypeChoices(TextChoices): + web = 'W', _('Web') + terminal = 'T', _('Terminal') + unknown = 'U', _('Unknown') + + +class MFAChoices(IntegerChoices): + disabled = 0, _('Disabled') + enabled = 1, _('Enabled') + unknown = 2, _('-') + + +class LoginStatusChoices(IntegerChoices): + success = True, _('Success') + failed = False, _('Failed') diff --git a/apps/audits/models.py b/apps/audits/models.py index 5dd8eb0a2..fa069801e 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -8,6 +8,9 @@ from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, Organization from orgs.utils import current_org +from .const import ( + OperateChoices, ActionChoices, LoginTypeChoices, MFAChoices, LoginStatusChoices +) __all__ = [ 'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog', @@ -15,30 +18,12 @@ __all__ = [ class FTPLog(OrgModelMixin): - OPERATE_DELETE = 'Delete' - OPERATE_UPLOAD = 'Upload' - OPERATE_DOWNLOAD = 'Download' - OPERATE_RMDIR = 'Rmdir' - OPERATE_RENAME = 'Rename' - OPERATE_MKDIR = 'Mkdir' - OPERATE_SYMLINK = 'Symlink' - - OPERATE_CHOICES = ( - (OPERATE_DELETE, _('Delete')), - (OPERATE_UPLOAD, _('Upload')), - (OPERATE_DOWNLOAD, _('Download')), - (OPERATE_RMDIR, _('Rmdir')), - (OPERATE_RENAME, _('Rename')), - (OPERATE_MKDIR, _('Mkdir')), - (OPERATE_SYMLINK, _('Symlink')) - ) - id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=128, verbose_name=_('User')) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) asset = models.CharField(max_length=1024, verbose_name=_("Asset")) system_user = models.CharField(max_length=128, verbose_name=_("System user")) - operate = models.CharField(max_length=16, verbose_name=_("Operate"), choices=OPERATE_CHOICES) + operate = models.CharField(max_length=16, verbose_name=_("Operate"), choices=OperateChoices.choices) filename = models.CharField(max_length=1024, verbose_name=_("Filename")) is_success = models.BooleanField(default=True, verbose_name=_("Success")) date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) @@ -48,19 +33,9 @@ class FTPLog(OrgModelMixin): class OperateLog(OrgModelMixin): - ACTION_CREATE = 'create' - ACTION_VIEW = 'view' - ACTION_UPDATE = 'update' - ACTION_DELETE = 'delete' - ACTION_CHOICES = ( - (ACTION_CREATE, _("Create")), - (ACTION_VIEW, _("View")), - (ACTION_UPDATE, _("Update")), - (ACTION_DELETE, _("Delete")) - ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=128, verbose_name=_('User')) - action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action")) + action = models.CharField(max_length=16, choices=ActionChoices.choices, verbose_name=_("Action")) resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type")) resource = models.CharField(max_length=128, verbose_name=_("Resource")) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) @@ -97,35 +72,17 @@ class PasswordChangeLog(models.Model): class UserLoginLog(models.Model): - LOGIN_TYPE_CHOICE = ( - ('W', 'Web'), - ('T', 'Terminal'), - ('U', 'Unknown'), - ) - - MFA_DISABLED = 0 - MFA_ENABLED = 1 - MFA_UNKNOWN = 2 - - MFA_CHOICE = ( - (MFA_DISABLED, _('Disabled')), - (MFA_ENABLED, _('Enabled')), - (MFA_UNKNOWN, _('-')), - ) - - STATUS_CHOICE = ( - (True, _('Success')), - (False, _('Failed')) - ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) username = models.CharField(max_length=128, verbose_name=_('Username')) - type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type')) + type = models.CharField(choices=LoginTypeChoices.choices, max_length=2, verbose_name=_('Login type')) ip = models.GenericIPAddressField(verbose_name=_('Login ip')) city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city')) user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent')) - mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA')) + mfa = models.SmallIntegerField(default=MFAChoices.unknown, choices=MFAChoices.choices, verbose_name=_('MFA')) reason = models.CharField(default='', max_length=128, blank=True, verbose_name=_('Reason')) - status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status')) + status = models.BooleanField( + default=LoginStatusChoices.success, choices=LoginStatusChoices.choices, verbose_name=_('Status') + ) datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login')) backend = models.CharField(max_length=32, default='', verbose_name=_('Authentication backend')) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 0f595be25..c262480a5 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -3,39 +3,39 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.drf.serializers import BulkSerializerMixin +from common.drf.fields import LabeledChoiceField from terminal.models import Session from . import models +from .const import ( + ActionChoices, OperateChoices, MFAChoices, LoginStatusChoices, LoginTypeChoices +) class FTPLogSerializer(serializers.ModelSerializer): - operate_display = serializers.ReadOnlyField(source='get_operate_display', label=_('Operate display')) + operate = LabeledChoiceField(choices=OperateChoices.choices, label=_('Operate')) class Meta: model = models.FTPLog fields_mini = ['id'] fields_small = fields_mini + [ 'user', 'remote_addr', 'asset', 'system_user', 'org_id', - 'operate', 'filename', 'operate_display', - 'is_success', - 'date_start', + 'operate', 'filename', 'is_success', 'date_start', ] fields = fields_small class UserLoginLogSerializer(serializers.ModelSerializer): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) - status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display')) - mfa_display = serializers.ReadOnlyField(source='get_mfa_display', label=_('MFA display')) + mfa = LabeledChoiceField(choices=MFAChoices.choices, label=_('MFA')) + type = LabeledChoiceField(choices=LoginTypeChoices.choices, label=_('Type')) + status = LabeledChoiceField(choices=LoginStatusChoices.choices, label=_('Status')) class Meta: model = models.UserLoginLog fields_mini = ['id'] fields_small = fields_mini + [ - 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', - 'mfa', 'mfa_display', 'reason', 'reason_display', 'backend', 'backend_display', - 'status', 'status_display', - 'datetime', + 'username', 'type', 'ip', 'city', 'user_agent', + 'mfa', 'reason', 'reason_display', 'backend', + 'backend_display', 'status', 'datetime', ] fields = fields_small extra_kwargs = { @@ -46,15 +46,14 @@ class UserLoginLogSerializer(serializers.ModelSerializer): class OperateLogSerializer(serializers.ModelSerializer): - action_display = serializers.CharField(source='get_action_display', label=_('Action')) + action = LabeledChoiceField(choices=ActionChoices.choices, label=_('Action')) class Meta: model = models.OperateLog fields_mini = ['id'] fields_small = fields_mini + [ - 'user', 'action', 'action_display', - 'resource_type', 'resource_type_display', 'resource', - 'remote_addr', 'datetime', 'org_id' + 'user', 'action', 'resource_type', 'resource_type_display', + 'resource', 'remote_addr', 'datetime', 'org_id' ] fields = fields_small extra_kwargs = { @@ -66,7 +65,7 @@ class PasswordChangeLogSerializer(serializers.ModelSerializer): class Meta: model = models.PasswordChangeLog fields = ( - 'id', 'user', 'change_by', 'remote_addr', 'datetime' + 'id', 'user', 'change_by', 'remote_addr', 'datetime' ) diff --git a/apps/audits/signal_handlers.py b/apps/audits/signal_handlers.py index f395965c3..34707d30b 100644 --- a/apps/audits/signal_handlers.py +++ b/apps/audits/signal_handlers.py @@ -1,38 +1,34 @@ # -*- coding: utf-8 -*- # -import time - -from django.db.models.signals import ( - post_save, m2m_changed, pre_delete -) -from django.dispatch import receiver from django.conf import settings from django.db import transaction -from django.utils import timezone +from django.dispatch import receiver +from django.utils import timezone, translation from django.utils.functional import LazyObject from django.contrib.auth import BACKEND_SESSION_KEY from django.utils.translation import ugettext_lazy as _ -from django.utils import translation -from rest_framework.renderers import JSONRenderer +from django.db.models.signals import post_save, m2m_changed, pre_delete from rest_framework.request import Request +from rest_framework.renderers import JSONRenderer -from assets.models import Asset -from authentication.signals import post_auth_failed, post_auth_success -from authentication.utils import check_different_city_login_if_need -from jumpserver.utils import current_request -from users.models import User -from users.signals import post_user_change_password -from terminal.models import Session, Command -from .utils import write_login_log, create_operate_log -from . import models, serializers -from .models import OperateLog from orgs.utils import current_org from perms.models import AssetPermission -from terminal.backends.command.serializers import SessionCommandSerializer +from users.models import User +from users.signals import post_user_change_password +from assets.models import Asset +from jumpserver.utils import current_request +from authentication.signals import post_auth_failed, post_auth_success +from authentication.utils import check_different_city_login_if_need +from terminal.models import Session, Command from terminal.serializers import SessionSerializer +from terminal.backends.command.serializers import SessionCommandSerializer from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR from common.utils import get_request_ip, get_logger, get_syslogger from common.utils.encode import data_to_json +from . import models, serializers +from .const import ActionChoices +from .utils import write_login_log, create_operate_log + logger = get_logger(__name__) sys_logger = get_syslogger(__name__) @@ -97,9 +93,9 @@ M2M_NEED_RECORD = { } M2M_ACTION_MAPER = { - POST_ADD: OperateLog.ACTION_CREATE, - POST_REMOVE: OperateLog.ACTION_DELETE, - POST_CLEAR: OperateLog.ACTION_DELETE, + POST_ADD: ActionChoices.create, + POST_REMOVE: ActionChoices.delete, + POST_CLEAR: ActionChoices.delete, } @@ -120,9 +116,9 @@ def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs): resource_type, resource_tmpl_add, resource_tmpl_remove = M2M_NEED_RECORD[sender_name] action = M2M_ACTION_MAPER[action] - if action == OperateLog.ACTION_CREATE: + if action == ActionChoices.create: resource_tmpl = resource_tmpl_add - elif action == OperateLog.ACTION_DELETE: + elif action == ActionChoices.delete: resource_tmpl = resource_tmpl_remove else: return @@ -144,11 +140,11 @@ def on_m2m_changed(sender, action, instance, model, pk_set, **kwargs): model_name: str(obj) })[:128] # `resource` 字段只有 128 个字符长 😔 - to_create.append(OperateLog( + to_create.append(models.OperateLog( user=user, action=action, resource_type=resource_type, resource=resource, remote_addr=remote_addr, org_id=org_id )) - OperateLog.objects.bulk_create(to_create) + models.OperateLog.objects.bulk_create(to_create) @receiver(post_save) @@ -158,15 +154,15 @@ def on_object_created_or_update(sender, instance=None, created=False, update_fie update_fields and 'last_login' in update_fields: return if created: - action = models.OperateLog.ACTION_CREATE + action = ActionChoices.create else: - action = models.OperateLog.ACTION_UPDATE + action = ActionChoices.update create_operate_log(action, sender, instance) @receiver(pre_delete) def on_object_delete(sender, instance=None, **kwargs): - create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance) + create_operate_log(ActionChoices.delete, sender, instance) @receiver(post_user_change_password, sender=User) diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 72c4df898..861eed82f 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -7,7 +7,6 @@ from django.utils.encoding import force_text from django.core.validators import MinValueValidator, MaxValueValidator from common.utils import signer, crypto - __all__ = [ 'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin', 'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', @@ -189,4 +188,3 @@ class PortField(models.IntegerField): 'validators': [MinValueValidator(0), MaxValueValidator(65535)] }) super().__init__(*args, **kwargs) - diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 97f9785f5..c42614a3e 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -21,6 +21,7 @@ __all__ = [ class ReadableHiddenField(serializers.HiddenField): """ 可读的 HiddenField """ + def __init__(self, **kwargs): super().__init__(**kwargs) self.write_only = False diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index 9f824e5e2..8ff0b2cdc 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -9,6 +9,7 @@ from rest_framework.request import Request from common.exceptions import UserConfirmRequired from audits.utils import create_operate_log from audits.models import OperateLog +from audits.const import ActionChoices __all__ = ["PermissionsMixin", "RecordViewLogMixin", "UserConfirmRequiredExceptionMixin"] @@ -40,7 +41,7 @@ class PermissionsMixin(UserPassesTestMixin): class RecordViewLogMixin: - ACTION = OperateLog.ACTION_VIEW + ACTION = ActionChoices.view @staticmethod def get_resource_display(request): From 5494d2fd605c7b3d3b660a421fb25c169e15aa30 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 10 Nov 2022 18:20:39 +0800 Subject: [PATCH 321/488] perf: update applet host deploy --- apps/terminal/automations/deploy_applet_host/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py index 8b415ece4..7845a16d6 100644 --- a/apps/terminal/automations/deploy_applet_host/__init__.py +++ b/apps/terminal/automations/deploy_applet_host/__init__.py @@ -31,12 +31,12 @@ class DeployAppletHostManager: bootstrap_token = settings.BOOTSTRAP_TOKEN host_id = str(self.deployment.host.id) if not base_site_url: - base_site_url = "localhost:8080" + base_site_url = "http://localhost:8080" with open(playbook_src) as f: plays = yaml.safe_load(f) for play in plays: play['vars'].update(self.deployment.host.deploy_options) - play['vars']['DownloadHost'] = base_site_url + '/download/' + play['vars']['DownloadHost'] = base_site_url + '/download' play['vars']['CORE_HOST'] = base_site_url play['vars']['BOOTSTRAP_TOKEN'] = bootstrap_token play['vars']['HOST_ID'] = host_id From cd3c3eeaf2364a0f8c1a2d051ccea526d0e8d981 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 10 Nov 2022 19:11:56 +0800 Subject: [PATCH 322/488] perf: code (#9044) Co-authored-by: feng <1304903146@qq.com> --- apps/acls/serializers/login_acl.py | 3 --- apps/assets/serializers/mixin.py | 11 ----------- 2 files changed, 14 deletions(-) diff --git a/apps/acls/serializers/login_acl.py b/apps/acls/serializers/login_acl.py index 536061082..ed45c8640 100644 --- a/apps/acls/serializers/login_acl.py +++ b/apps/acls/serializers/login_acl.py @@ -53,6 +53,3 @@ class LoginACLSerializer(BulkModelSerializer): def get_rules_serializer(self): return RuleSerializer() - - def get_reviewers_display(self, obj): - return ','.join([str(user) for user in obj.reviewers.all()]) diff --git a/apps/assets/serializers/mixin.py b/apps/assets/serializers/mixin.py index 45943dc2a..e69de29bb 100644 --- a/apps/assets/serializers/mixin.py +++ b/apps/assets/serializers/mixin.py @@ -1,11 +0,0 @@ -from rest_framework import serializers -from django.utils.translation import gettext_lazy as _ - - -class CategoryDisplayMixin(serializers.Serializer): - category_display = serializers.ReadOnlyField( - source='get_category_display', label=_("Category display") - ) - type_display = serializers.ReadOnlyField( - source='get_type_display', label=_("Type display") - ) From f6e403fd8be14f3a8b7671f7ec3ed649ee3cd0f3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 11 Nov 2022 15:04:31 +0800 Subject: [PATCH 323/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20asset=20pe?= =?UTF-8?q?rmission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .isort.cfg | 3 + apps/assets/api/asset/asset.py | 98 +++---- apps/assets/filters.py | 7 +- apps/assets/models/account.py | 16 +- apps/assets/serializers/platform.py | 128 +++++---- apps/authentication/api/connection_token.py | 6 +- .../serializers/connection_token.py | 11 +- apps/authentication/views/dingtalk.py | 35 +-- apps/authentication/views/feishu.py | 29 +- apps/common/db/fields.py | 38 ++- apps/common/drf/fields.py | 86 ++++-- apps/common/drf/metadata.py | 104 ++++---- apps/common/utils/integer.py | 3 + apps/perms/api/asset_permission.py | 7 +- apps/perms/api/user_permission/accounts.py | 3 +- apps/perms/api/user_permission/mixin.py | 6 +- apps/perms/const.py | 69 +++++ apps/perms/locks.py | 4 +- .../migrations/0011_auto_20200721_1739.py | 5 +- apps/perms/models/__init__.py | 2 +- apps/perms/models/asset_permission.py | 164 +----------- apps/perms/models/const.py | 48 ---- apps/perms/models/permed_node.py | 119 +++++++++ apps/perms/serializers/permission.py | 109 ++++---- apps/perms/serializers/user_permission.py | 6 +- apps/perms/utils/account.py | 99 ++++--- apps/perms/utils/permission.py | 8 +- apps/perms/utils/user_permission.py | 9 +- .../migrations/0017_auto_20220623_1027.py | 1 - apps/tickets/models/ticket/apply_asset.py | 7 +- .../tickets/serializers/ticket/apply_asset.py | 4 +- apps/users/serializers/user.py | 247 +++++++++++------- 32 files changed, 835 insertions(+), 646 deletions(-) create mode 100644 .isort.cfg create mode 100644 apps/common/utils/integer.py delete mode 100644 apps/perms/models/const.py create mode 100644 apps/perms/models/permed_node.py diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 000000000..e59d309dc --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,3 @@ +[settings] +line_length=120 +known_first_party=common,users,assets,perms,authentication,jumpserver,notification,ops,orgs,rbac,settings,terminal,tickets diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 4e1176d17..ad04966e3 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -1,89 +1,91 @@ # -*- coding: utf-8 -*- # + import django_filters from rest_framework.decorators import action from rest_framework.response import Response -from common.utils import get_logger -from common.drf.filters import BaseFilterSet -from common.mixins.api import SuggestionMixin -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.mixins import generics from assets import serializers +from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend from assets.models import Asset, Gateway from assets.tasks import ( push_accounts_to_assets, - verify_accounts_connectivity, test_assets_connectivity_manual, update_assets_hardware_info_manual, + verify_accounts_connectivity, ) -from assets.filters import NodeFilterBackend, LabelFilterBackend, IpInFilterBackend +from common.drf.filters import BaseFilterSet +from common.mixins.api import SuggestionMixin +from common.utils import get_logger +from orgs.mixins import generics +from orgs.mixins.api import OrgBulkModelViewSet from ..mixin import NodeFilterMixin logger = get_logger(__file__) __all__ = [ - 'AssetViewSet', 'AssetTaskCreateApi', 'AssetsTaskCreateApi', + "AssetViewSet", + "AssetTaskCreateApi", + "AssetsTaskCreateApi", ] class AssetFilterSet(BaseFilterSet): - type = django_filters.CharFilter(field_name='platform__type', lookup_expr='exact') - category = django_filters.CharFilter(field_name='platform__category', lookup_expr='exact') - hostname = django_filters.CharFilter(field_name='name', lookup_expr='exact') + type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact") + category = django_filters.CharFilter( + field_name="platform__category", lookup_expr="exact" + ) + hostname = django_filters.CharFilter(field_name="name", lookup_expr="exact") class Meta: model = Asset - fields = ['name', 'address', 'is_active', 'type', 'category', 'hostname'] + fields = ["name", "address", "is_active", "type", "category", "hostname"] class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ + model = Asset filterset_class = AssetFilterSet search_fields = ("name", "address") ordering_fields = ("name", "address") - ordering = ('name',) + ordering = ("name",) serializer_classes = ( - ('default', serializers.AssetSerializer), - ('suggestion', serializers.MiniAssetSerializer), - ('platform', serializers.PlatformSerializer), - ('gateways', serializers.GatewayWithAuthSerializer) + ("default", serializers.AssetSerializer), + ("suggestion", serializers.MiniAssetSerializer), + ("platform", serializers.PlatformSerializer), + ("gateways", serializers.GatewayWithAuthSerializer), ) rbac_perms = ( - ('match', 'assets.match_asset'), - ('platform', 'assets.view_platform'), - ('gateways', 'assets.view_gateway') + ("match", "assets.match_asset"), + ("platform", "assets.view_platform"), + ("gateways", "assets.view_gateway"), ) - extra_filter_backends = [ - LabelFilterBackend, - IpInFilterBackend, - NodeFilterBackend - ] + extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend] - @action(methods=['GET'], detail=True, url_path='platform') + @action(methods=["GET"], detail=True, url_path="platform") def platform(self, *args, **kwargs): asset = self.get_object() serializer = self.get_serializer(asset.platform) return Response(serializer.data) - @action(methods=['GET'], detail=True, url_path='gateways') + @action(methods=["GET"], detail=True, url_path="gateways") def gateways(self, *args, **kwargs): asset = self.get_object() if not asset.domain: gateways = Gateway.objects.none() else: - gateways = asset.domain.gateways.filter(protocol='ssh') + gateways = asset.domain.gateways.filter(protocol="ssh") return self.get_paginated_response_from_queryset(gateways) class AssetsTaskMixin: def perform_assets_task(self, serializer): data = serializer.validated_data - assets = data.get('assets', []) + assets = data.get("assets", []) asset_ids = [asset.id for asset in assets] - if data['action'] == "refresh": + if data["action"] == "refresh": task = update_assets_hardware_info_manual.delay(asset_ids) else: task = test_assets_connectivity_manual.delay(asset_ids) @@ -94,9 +96,9 @@ class AssetsTaskMixin: self.set_task_to_serializer_data(serializer, task) def set_task_to_serializer_data(self, serializer, task): - data = getattr(serializer, '_data', {}) + data = getattr(serializer, "_data", {}) data["task"] = task.id - setattr(serializer, '_data', data) + setattr(serializer, "_data", data) class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): @@ -104,18 +106,18 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): serializer_class = serializers.AssetTaskSerializer def create(self, request, *args, **kwargs): - pk = self.kwargs.get('pk') - request.data['asset'] = pk - request.data['assets'] = [pk] + pk = self.kwargs.get("pk") + request.data["asset"] = pk + request.data["assets"] = [pk] return super().create(request, *args, **kwargs) def check_permissions(self, request): - action = request.data.get('action') + action = request.data.get("action") action_perm_require = { - 'refresh': 'assets.refresh_assethardwareinfo', - 'push_account': 'assets.push_assetsystemuser', - 'test': 'assets.test_assetconnectivity', - 'test_account': 'assets.test_assetconnectivity' + "refresh": "assets.refresh_assethardwareinfo", + "push_account": "assets.push_assetsystemuser", + "test": "assets.test_assetconnectivity", + "test_account": "assets.test_assetconnectivity", } perm_required = action_perm_require.get(action) has = self.request.user.has_perm(perm_required) @@ -126,19 +128,19 @@ class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): @staticmethod def perform_asset_task(serializer): data = serializer.validated_data - if data['action'] not in ['push_system_user', 'test_system_user']: + if data["action"] not in ["push_system_user", "test_system_user"]: return - asset = data['asset'] - accounts = data.get('accounts') + asset = data["asset"] + accounts = data.get("accounts") if not accounts: accounts = asset.accounts.all() asset_ids = [asset.id] - account_ids = accounts.values_list('id', flat=True) - if action == 'push_account': + account_ids = accounts.values_list("id", flat=True) + if action == "push_account": task = push_accounts_to_assets.delay(account_ids, asset_ids) - elif action == 'test_account': + elif action == "test_account": task = verify_accounts_connectivity.delay(account_ids, asset_ids) else: task = None @@ -156,9 +158,9 @@ class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView): serializer_class = serializers.AssetsTaskSerializer def check_permissions(self, request): - action = request.data.get('action') + action = request.data.get("action") action_perm_require = { - 'refresh': 'assets.refresh_assethardwareinfo', + "refresh": "assets.refresh_assethardwareinfo", } perm_required = action_perm_require.get(action) has = self.request.user.has_perm(perm_required) diff --git a/apps/assets/filters.py b/apps/assets/filters.py index de2550ceb..f1b869805 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- # from django.db.models import Q +from django_filters import rest_framework as drf_filters from rest_framework import filters from rest_framework.compat import coreapi, coreschema -from django_filters import rest_framework as drf_filters +from assets.utils import get_node_from_request, is_query_node_all_assets from common.drf.filters import BaseFilterSet -from assets.utils import is_query_node_all_assets, get_node_from_request -from .models import Label, Node, Account + +from .models import Account, Label, Node class AssetByNodeFilterBackend(filters.BaseFilterBackend): diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index 9aa007e53..cad5f9ded 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -3,7 +3,8 @@ from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from common.utils import lazyproperty -from .base import BaseAccount, AbsConnectivity + +from .base import AbsConnectivity, BaseAccount __all__ = ['Account', 'AccountTemplate'] @@ -40,9 +41,10 @@ class AccountHistoricalRecords(HistoricalRecords): class Account(AbsConnectivity, BaseAccount): - class InnerAccount(models.TextChoices): - INPUT = '@INPUT', '@INPUT' - USER = '@USER', '@USER' + class AliasAccount(models.TextChoices): + ALL = '@ALL', _('All') + INPUT = '@INPUT', _('Manual input') + USER = '@USER', _('Dynamic user') asset = models.ForeignKey( 'assets.Asset', related_name='accounts', @@ -76,14 +78,14 @@ class Account(AbsConnectivity, BaseAccount): return '{}'.format(self.username) @classmethod - def get_input_account(cls): + def get_manual_account(cls): """ @INPUT 手动登录的账号(any) """ - return cls(name=cls.InnerAccount.INPUT.value, username='') + return cls(name=cls.AliasAccount.INPUT.label, username=cls.AliasAccount.INPUT.value, secret=None) @classmethod def get_user_account(cls, username): """ @USER 动态用户的账号(self) """ - return cls(name=cls.InnerAccount.USER.value, username=username) + return cls(name=cls.AliasAccount.USER.label, username=cls.AliasAccount.USER.value) class AccountTemplate(BaseAccount): diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 3bc02732f..8f8dcb5a3 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -1,61 +1,75 @@ -from rest_framework import serializers from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers from common.drf.fields import LabeledChoiceField from common.drf.serializers import WritableNestedModelSerializer -from ..models import Platform, PlatformProtocol, PlatformAutomation from ..const import Category, AllTypes +from ..models import Platform, PlatformProtocol, PlatformAutomation -__all__ = ['PlatformSerializer', 'PlatformOpsMethodSerializer'] +__all__ = ["PlatformSerializer", "PlatformOpsMethodSerializer"] class ProtocolSettingSerializer(serializers.Serializer): SECURITY_CHOICES = [ - ('any', 'Any'), - ('rdp', 'RDP'), - ('tls', 'TLS'), - ('nla', 'NLA'), + ("any", "Any"), + ("rdp", "RDP"), + ("tls", "TLS"), + ("nla", "NLA"), ] # RDP console = serializers.BooleanField(required=False) - security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any') + security = serializers.ChoiceField(choices=SECURITY_CHOICES, default="any") # SFTP sftp_enabled = serializers.BooleanField(default=True, label=_("SFTP enabled")) - sftp_home = serializers.CharField(default='/tmp', label=_("SFTP home")) + sftp_home = serializers.CharField(default="/tmp", label=_("SFTP home")) # HTTP auto_fill = serializers.BooleanField(default=False, label=_("Auto fill")) - username_selector = serializers.CharField(default='', allow_blank=True, label=_("Username selector")) - password_selector = serializers.CharField(default='', allow_blank=True, label=_("Password selector")) - submit_selector = serializers.CharField(default='', allow_blank=True, label=_("Submit selector")) + username_selector = serializers.CharField( + default="", allow_blank=True, label=_("Username selector") + ) + password_selector = serializers.CharField( + default="", allow_blank=True, label=_("Password selector") + ) + submit_selector = serializers.CharField( + default="", allow_blank=True, label=_("Submit selector") + ) class PlatformAutomationSerializer(serializers.ModelSerializer): class Meta: model = PlatformAutomation fields = [ - 'id', 'ansible_enabled', 'ansible_config', - 'ping_enabled', 'ping_method', - 'gather_facts_enabled', 'gather_facts_method', - 'push_account_enabled', 'push_account_method', - 'change_secret_enabled', 'change_secret_method', - 'verify_account_enabled', 'verify_account_method', - 'gather_accounts_enabled', 'gather_accounts_method', + "id", + "ansible_enabled", + "ansible_config", + "ping_enabled", + "ping_method", + "gather_facts_enabled", + "gather_facts_method", + "push_account_enabled", + "push_account_method", + "change_secret_enabled", + "change_secret_method", + "verify_account_enabled", + "verify_account_method", + "gather_accounts_enabled", + "gather_accounts_method", ] extra_kwargs = { - 'ping_enabled': {'label': '启用资产探测'}, - 'ping_method': {'label': '探测方式'}, - 'gather_facts_enabled': {'label': '启用收集信息'}, - 'gather_facts_method': {'label': '收集信息方式'}, - 'verify_account_enabled': {'label': '启用校验账号'}, - 'verify_account_method': {'label': '校验账号方式'}, - 'push_account_enabled': {'label': '启用推送账号'}, - 'push_account_method': {'label': '推送账号方式'}, - 'change_secret_enabled': {'label': '启用账号改密'}, - 'change_secret_method': {'label': '账号创建改密方式'}, - 'gather_accounts_enabled': {'label': '启用账号收集'}, - 'gather_accounts_method': {'label': '收集账号方式'}, + "ping_enabled": {"label": "启用资产探测"}, + "ping_method": {"label": "探测方式"}, + "gather_facts_enabled": {"label": "启用收集信息"}, + "gather_facts_method": {"label": "收集信息方式"}, + "verify_account_enabled": {"label": "启用校验账号"}, + "verify_account_method": {"label": "校验账号方式"}, + "push_account_enabled": {"label": "启用推送账号"}, + "push_account_method": {"label": "推送账号方式"}, + "change_secret_enabled": {"label": "启用账号改密"}, + "change_secret_method": {"label": "账号创建改密方式"}, + "gather_accounts_enabled": {"label": "启用账号收集"}, + "gather_accounts_method": {"label": "收集账号方式"}, } @@ -66,42 +80,62 @@ class PlatformProtocolsSerializer(serializers.ModelSerializer): class Meta: model = PlatformProtocol fields = [ - 'id', 'name', 'port', 'primary', 'default', - 'required', 'secret_types', 'setting', + "id", + "name", + "port", + "primary", + "default", + "required", + "secret_types", + "setting", ] class PlatformSerializer(WritableNestedModelSerializer): + charset = LabeledChoiceField( + choices=Platform.CharsetChoices.choices, label=_("Charset") + ) type = LabeledChoiceField(choices=AllTypes.choices(), label=_("Type")) category = LabeledChoiceField(choices=Category.choices, label=_("Category")) - protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) - automation = PlatformAutomationSerializer(label=_('Automation'), required=False) + protocols = PlatformProtocolsSerializer( + label=_("Protocols"), many=True, required=False + ) + automation = PlatformAutomationSerializer(label=_("Automation"), required=False) su_method = LabeledChoiceField( - choices=[('sudo', 'sudo su -'), ('su', 'su - ')], - label='切换方式', required=False, default='sudo' + choices=[("sudo", "sudo su -"), ("su", "su - ")], + label="切换方式", + required=False, + default="sudo", ) class Meta: model = Platform - fields_mini = ['id', 'name', 'internal'] + fields_mini = ["id", "name", "internal"] fields_small = fields_mini + [ - 'category', 'type', 'charset', + "category", + "type", + "charset", ] fields = fields_small + [ - 'protocols_enabled', 'protocols', 'domain_enabled', - 'su_enabled', 'su_method', 'automation', 'comment', + "protocols_enabled", + "protocols", + "domain_enabled", + "su_enabled", + "su_method", + "automation", + "comment", ] extra_kwargs = { - 'su_enabled': {'label': '启用切换账号'}, - 'protocols_enabled': {'label': '启用协议'}, - 'domain_enabled': {'label': "启用网域"}, - 'domain_default': {'label': "默认网域"}, + "su_enabled": {"label": "启用切换账号"}, + "protocols_enabled": {"label": "启用协议"}, + "domain_enabled": {"label": "启用网域"}, + "domain_default": {"label": "默认网域"}, } class PlatformOpsMethodSerializer(serializers.Serializer): id = serializers.CharField(read_only=True) - name = serializers.CharField(max_length=50, label=_('Name')) - category = serializers.CharField(max_length=50, label=_('Category')) + name = serializers.CharField(max_length=50, label=_("Name")) + category = serializers.CharField(max_length=50, label=_("Category")) type = serializers.ListSerializer(child=serializers.CharField()) method = serializers.CharField() diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 0c04531d5..0839229b8 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -16,7 +16,7 @@ from rest_framework.request import Request from common.drf.api import JMSModelViewSet from common.http import is_true from orgs.mixins.api import RootOrgViewMixin -from perms.models import Action +from perms.models import ActionChoices from terminal.models import EndpointRule from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, @@ -70,8 +70,8 @@ class RDPFileClientProtocolURLMixin: # 设置磁盘挂载 drives_redirect = is_true(self.request.query_params.get('drives_redirect')) if drives_redirect: - actions = Action.choices_to_value(token.actions) - if actions & Action.UPDOWNLOAD == Action.UPDOWNLOAD: + actions = ActionChoices.choices_to_value(token.actions) + if actions & Action.TRANSFER == Action.TRANSFER: rdp_options['drivestoredirect:s'] = '*' # 设置全屏 diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 6e1f19be1..256661882 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -7,7 +7,7 @@ from common.utils import pretty_string from common.utils.random import random_string from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account from users.models import User -from perms.serializers.permission import ActionsField +from perms.serializers.permission import ActionChoicesField __all__ = [ @@ -158,14 +158,13 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): gateway = ConnectionTokenGatewaySerializer(read_only=True) domain = ConnectionTokenDomainSerializer(read_only=True) cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) - actions = ActionsField() + actions = ActionChoicesField() expire_at = serializers.IntegerField() class Meta: model = ConnectionToken fields = [ - 'id', 'secret', - 'user', 'asset', 'account_username', 'account', 'protocol', - 'domain', 'gateway', 'cmd_filter_rules', - 'actions', 'expire_at', + 'id', 'secret', 'user', 'asset', 'account_username', + 'account', 'protocol', 'domain', 'gateway', + 'cmd_filter_rules', 'actions', 'expire_at', ] diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 0d19d3fcd..340ece19c 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -1,27 +1,28 @@ +from urllib.parse import urlencode + +from django.conf import settings +from django.db.utils import IntegrityError +from django.http.request import HttpRequest from django.http.response import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ -from urllib.parse import urlencode from django.views import View -from django.conf import settings -from django.http.request import HttpRequest -from django.db.utils import IntegrityError -from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.exceptions import APIException +from rest_framework.permissions import AllowAny, IsAuthenticated +from authentication import errors +from authentication.const import ConfirmType +from authentication.mixins import AuthMixin +from authentication.notifications import OAuthBindMessage +from common.mixins.views import PermissionsMixin, UserConfirmRequiredExceptionMixin +from common.permissions import UserConfirmation +from common.sdk.im.dingtalk import URL, DingTalk +from common.utils import FlashMessageUtil, get_logger +from common.utils.common import get_request_ip +from common.utils.django import get_object_or_none, reverse +from common.utils.random import random_string from users.models import User from users.views import UserVerifyPasswordView -from common.utils import get_logger, FlashMessageUtil -from common.utils.random import random_string -from common.utils.django import reverse, get_object_or_none -from common.sdk.im.dingtalk import URL -from common.mixins.views import UserConfirmRequiredExceptionMixin, PermissionsMixin -from common.permissions import UserConfirmation -from authentication import errors -from authentication.mixins import AuthMixin -from authentication.const import ConfirmType -from common.sdk.im.dingtalk import DingTalk -from common.utils.common import get_request_ip -from authentication.notifications import OAuthBindMessage + from .mixins import METAMixin logger = get_logger(__file__) diff --git a/apps/authentication/views/feishu.py b/apps/authentication/views/feishu.py index da7999b95..4fdf6f846 100644 --- a/apps/authentication/views/feishu.py +++ b/apps/authentication/views/feishu.py @@ -1,26 +1,27 @@ +from urllib.parse import urlencode + +from django.conf import settings +from django.db.utils import IntegrityError +from django.http.request import HttpRequest from django.http.response import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ -from urllib.parse import urlencode from django.views import View -from django.conf import settings -from django.http.request import HttpRequest -from django.db.utils import IntegrityError -from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.exceptions import APIException +from rest_framework.permissions import AllowAny, IsAuthenticated -from users.models import User -from users.views import UserVerifyPasswordView -from common.utils import get_logger, FlashMessageUtil -from common.utils.random import random_string -from common.utils.django import reverse, get_object_or_none -from common.mixins.views import UserConfirmRequiredExceptionMixin, PermissionsMixin -from common.permissions import UserConfirmation -from common.sdk.im.feishu import FeiShu, URL -from common.utils.common import get_request_ip from authentication import errors from authentication.const import ConfirmType from authentication.mixins import AuthMixin from authentication.notifications import OAuthBindMessage +from common.mixins.views import PermissionsMixin, UserConfirmRequiredExceptionMixin +from common.permissions import UserConfirmation +from common.sdk.im.feishu import URL, FeiShu +from common.utils import FlashMessageUtil, get_logger +from common.utils.common import get_request_ip +from common.utils.django import get_object_or_none, reverse +from common.utils.random import random_string +from users.models import User +from users.views import UserVerifyPasswordView logger = get_logger(__file__) diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 72c4df898..edca62d5b 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- # import json + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text from django.core.validators import MinValueValidator, MaxValueValidator + from common.utils import signer, crypto @@ -13,7 +15,7 @@ __all__ = [ 'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', 'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField', 'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField', - 'EncryptJsonDictCharField', 'PortField' + 'EncryptJsonDictCharField', 'PortField', 'BitChoices', ] @@ -190,3 +192,37 @@ class PortField(models.IntegerField): }) super().__init__(*args, **kwargs) + +class BitChoices(models.IntegerChoices): + @classmethod + def branches(cls): + return [i for i in cls] + + @classmethod + def tree(cls): + root = [_('All'), cls.branches()] + return cls.render_node(root) + + @classmethod + def render_node(cls, node): + if isinstance(node, BitChoices): + return { + 'id': node.name, + 'label': node.label, + } + else: + name, children = node + return { + 'id': name, + 'label': name, + 'children': [cls.render_node(child) for child in children] + } + + @classmethod + def all(cls): + value = 0 + for c in cls: + value |= c.value + return value + + diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 97f9785f5..1e68265ab 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -1,17 +1,20 @@ # -*- coding: utf-8 -*- # import six - -from rest_framework.fields import ChoiceField -from rest_framework import serializers -from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ObjectDoesNotExist +from django.db.models import IntegerChoices +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers +from rest_framework.fields import ChoiceField from common.utils import decrypt_password __all__ = [ - 'ReadableHiddenField', 'EncryptedField', 'LabeledChoiceField', - 'ObjectRelatedField', + "ReadableHiddenField", + "EncryptedField", + "LabeledChoiceField", + "ObjectRelatedField", + "BitChoicesField", ] @@ -20,14 +23,15 @@ __all__ = [ class ReadableHiddenField(serializers.HiddenField): - """ 可读的 HiddenField """ + """可读的 HiddenField""" + def __init__(self, **kwargs): super().__init__(**kwargs) self.write_only = False def to_representation(self, value): - if hasattr(value, 'id'): - return getattr(value, 'id') + if hasattr(value, "id"): + return getattr(value, "id") return value @@ -35,7 +39,7 @@ class EncryptedField(serializers.CharField): def __init__(self, write_only=None, **kwargs): if write_only is None: write_only = True - kwargs['write_only'] = write_only + kwargs["write_only"] = write_only super().__init__(**kwargs) def to_internal_value(self, value): @@ -54,26 +58,26 @@ class LabeledChoiceField(ChoiceField): if value is None: return value return { - 'value': value, - 'label': self.choice_mapper.get(six.text_type(value), value), + "value": value, + "label": self.choice_mapper.get(six.text_type(value), value), } def to_internal_value(self, data): if isinstance(data, dict): - return data.get('value') + return data.get("value") return super(LabeledChoiceField, self).to_internal_value(data) class ObjectRelatedField(serializers.RelatedField): default_error_messages = { - 'required': _('This field is required.'), - 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'), - 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'), + "required": _("This field is required."), + "does_not_exist": _('Invalid pk "{pk_value}" - object does not exist.'), + "incorrect_type": _("Incorrect type. Expected pk value, received {data_type}."), } def __init__(self, **kwargs): - self.attrs = kwargs.pop('attrs', None) or ('id', 'name') - self.many = kwargs.get('many', False) + self.attrs = kwargs.pop("attrs", None) or ("id", "name") + self.many = kwargs.get("many", False) super().__init__(**kwargs) def to_representation(self, value): @@ -86,13 +90,53 @@ class ObjectRelatedField(serializers.RelatedField): if not isinstance(data, dict): pk = data else: - pk = data.get('id') or data.get('pk') or data.get(self.attrs[0]) + pk = data.get("id") or data.get("pk") or data.get(self.attrs[0]) queryset = self.get_queryset() try: if isinstance(data, bool): raise TypeError return queryset.get(pk=pk) except ObjectDoesNotExist: - self.fail('does_not_exist', pk_value=pk) + self.fail("does_not_exist", pk_value=pk) except (TypeError, ValueError): - self.fail('incorrect_type', data_type=type(pk).__name__) + self.fail("incorrect_type", data_type=type(pk).__name__) + + +class BitChoicesField(serializers.MultipleChoiceField): + """ + 位字段 + """ + + def __init__(self, choice_cls, **kwargs): + assert issubclass(choice_cls, IntegerChoices) + choices = [(c.name, c.label) for c in choice_cls] + self._choice_cls = choice_cls + super().__init__(choices=choices, **kwargs) + + def to_representation(self, value): + return [ + {"value": c.name, "label": c.label} + for c in self._choice_cls + if c.value & value == c.value + ] + + def to_internal_value(self, data): + if not isinstance(data, list): + raise serializers.ValidationError(_("Invalid data type, should be list")) + value = 0 + if not data: + return value + if isinstance(data[0], dict): + data = [d["value"] for d in data] + # 所有的 + if "all" in data: + for c in self._choice_cls: + value |= c.value + return value + + name_value_map = {c.name: c.value for c in self._choice_cls} + for name in data: + if name not in name_value_map: + raise serializers.ValidationError(_("Invalid choice: {}").format(name)) + value |= name_value_map[name] + return value diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index 939c1f314..d16ab2262 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -2,17 +2,15 @@ # from __future__ import unicode_literals -from collections import OrderedDict import datetime -from itertools import chain +from collections import OrderedDict from django.core.exceptions import PermissionDenied from django.http import Http404 from django.utils.encoding import force_text -from rest_framework.fields import empty - -from rest_framework.metadata import SimpleMetadata from rest_framework import exceptions, serializers +from rest_framework.fields import empty +from rest_framework.metadata import SimpleMetadata from rest_framework.request import clone_request @@ -21,9 +19,14 @@ class SimpleMetadataWithFilters(SimpleMetadata): methods = {"PUT", "POST", "GET", "PATCH"} attrs = [ - 'read_only', 'label', 'help_text', - 'min_length', 'max_length', - 'min_value', 'max_value', "write_only", + "read_only", + "label", + "help_text", + "min_length", + "max_length", + "min_value", + "max_value", + "write_only", ] def determine_actions(self, request, view): @@ -32,18 +35,18 @@ class SimpleMetadataWithFilters(SimpleMetadata): the fields that are accepted for 'PUT' and 'POST' methods. """ actions = {} - view.raw_action = getattr(view, 'action', None) + view.raw_action = getattr(view, "action", None) for method in self.methods & set(view.allowed_methods): - if hasattr(view, 'action_map'): + if hasattr(view, "action_map"): view.action = view.action_map.get(method.lower(), view.action) view.request = clone_request(request, method) try: # Test global permissions - if hasattr(view, 'check_permissions'): + if hasattr(view, "check_permissions"): view.check_permissions(view.request) # Test object permissions - if method == 'PUT' and hasattr(view, 'get_object'): + if method == "PUT" and hasattr(view, "get_object"): view.get_object() except (exceptions.APIException, PermissionDenied, Http404): pass @@ -62,64 +65,63 @@ class SimpleMetadataWithFilters(SimpleMetadata): of metadata about it. """ field_info = OrderedDict() - field_info['type'] = self.label_lookup[field] - field_info['required'] = getattr(field, 'required', False) + field_info["type"] = self.label_lookup[field] + field_info["required"] = getattr(field, "required", False) - default = getattr(field, 'default', None) + # Default value + default = getattr(field, "default", None) if default is not None and default != empty: if isinstance(default, (str, int, bool, float, datetime.datetime, list)): - field_info['default'] = default + field_info["default"] = default for attr in self.attrs: value = getattr(field, attr, None) - if value is not None and value != '': + if value is not None and value != "": field_info[attr] = force_text(value, strings_only=True) - if getattr(field, 'child', None): - field_info['child'] = self.get_field_info(field.child) - elif getattr(field, 'fields', None): - field_info['children'] = self.get_serializer_info(field) + if getattr(field, "child", None): + field_info["child"] = self.get_field_info(field.child) + elif getattr(field, "fields", None): + field_info["children"] = self.get_serializer_info(field) - is_related_field = isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) - if not is_related_field and hasattr(field, 'choices'): - field_info['choices'] = [ + is_choice_field = isinstance(field, (serializers.ChoiceField,)) + if is_choice_field and hasattr(field, "choices"): + field_info["choices"] = [ { - 'value': choice_value, - 'label': force_text(choice_name, strings_only=True) + "value": choice_value, + "label": force_text(choice_label, strings_only=True), } - for choice_value, choice_name in dict(field.choices).items() + for choice_value, choice_label in dict(field.choices).items() ] class_name = field.__class__.__name__ - if class_name == 'LabeledChoiceField': - field_info['type'] = 'labeled_choice' - elif class_name == 'ObjectRelatedField': - field_info['type'] = 'object_related_field' - elif class_name == 'ManyRelatedField': + if class_name == "LabeledChoiceField": + field_info["type"] = "labeled_choice" + elif class_name == "ObjectRelatedField": + field_info["type"] = "object_related_field" + elif class_name == "ManyRelatedField": child_relation_class_name = field.child_relation.__class__.__name__ - if child_relation_class_name == 'ObjectRelatedField': - field_info['type'] = 'm2m_related_field' - - # if field.label == '系统平台': - # print("Field: ", class_name, field, field_info) - + if child_relation_class_name == "ObjectRelatedField": + field_info["type"] = "m2m_related_field" return field_info - def get_filters_fields(self, request, view): + @staticmethod + def get_filters_fields(request, view): fields = [] - if hasattr(view, 'get_filter_fields'): + if hasattr(view, "get_filter_fields"): fields = view.get_filter_fields(request) - elif hasattr(view, 'filter_fields'): + elif hasattr(view, "filter_fields"): fields = view.filter_fields - elif hasattr(view, 'filterset_fields'): + elif hasattr(view, "filterset_fields"): fields = view.filterset_fields - elif hasattr(view, 'get_filterset_fields'): + elif hasattr(view, "get_filterset_fields"): fields = view.get_filterset_fields(request) - elif hasattr(view, 'filterset_class'): - fields = list(view.filterset_class.Meta.fields) + \ - list(view.filterset_class.declared_filters.keys()) + elif hasattr(view, "filterset_class"): + fields = list(view.filterset_class.Meta.fields) + list( + view.filterset_class.declared_filters.keys() + ) - if hasattr(view, 'custom_filter_fields'): + if hasattr(view, "custom_filter_fields"): # 不能写 fields += view.custom_filter_fields # 会改变 view 的 filter_fields fields = list(fields) + list(view.custom_filter_fields) @@ -130,14 +132,16 @@ class SimpleMetadataWithFilters(SimpleMetadata): def get_ordering_fields(self, request, view): fields = [] - if hasattr(view, 'get_ordering_fields'): + if hasattr(view, "get_ordering_fields"): fields = view.get_ordering_fields(request) - elif hasattr(view, 'ordering_fields'): + elif hasattr(view, "ordering_fields"): fields = view.ordering_fields return fields def determine_metadata(self, request, view): - metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view) + metadata = super(SimpleMetadataWithFilters, self).determine_metadata( + request, view + ) filterset_fields = self.get_filters_fields(request, view) order_fields = self.get_ordering_fields(request, view) diff --git a/apps/common/utils/integer.py b/apps/common/utils/integer.py new file mode 100644 index 000000000..73f4160c0 --- /dev/null +++ b/apps/common/utils/integer.py @@ -0,0 +1,3 @@ + +def bit(x): + return 2 ** (x - 1) diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py index afadc456c..de15a6c6f 100644 --- a/apps/perms/api/asset_permission.py +++ b/apps/perms/api/asset_permission.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- # -from perms.filters import AssetPermissionFilter -from perms.models import AssetPermission from orgs.mixins.api import OrgBulkModelViewSet from perms import serializers - +from perms.filters import AssetPermissionFilter +from perms.models import AssetPermission __all__ = ['AssetPermissionViewSet'] @@ -18,4 +17,4 @@ class AssetPermissionViewSet(OrgBulkModelViewSet): filterset_class = AssetPermissionFilter search_fields = ('name',) ordering_fields = ('name',) - ordering = ('name', ) + ordering = ('name',) diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 70973d988..692dac8c8 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -6,7 +6,6 @@ from common.utils import get_logger, lazyproperty from assets.serializers import AccountSerializer from perms.hands import User, Asset, Account from perms import serializers -from perms.models import Action from perms.utils import PermAccountUtil from .mixin import RoleAdminMixin, RoleUserMixin @@ -80,7 +79,7 @@ class UserGrantedAssetSpecialAccountsApi(ListAPIView): def get_queryset(self): # 构造默认包含的账号,如: @INPUT @USER accounts = [ - Account.get_input_account(), + Account.get_manual_account(), Account.get_user_account(self.user.username) ] for account in accounts: diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index da9691f38..2a7cbe221 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -3,11 +3,9 @@ from rest_framework.request import Request from common.http import is_true -from common.mixins.api import RoleAdminMixin -from common.mixins.api import RoleUserMixin -from orgs.utils import tmp_to_root_org -from users.models import User +from common.mixins.api import RoleAdminMixin, RoleUserMixin from perms.utils.user_permission import UserGrantedTreeRefreshController +from users.models import User class RebuildTreeMixin: diff --git a/apps/perms/const.py b/apps/perms/const.py index ec51c5a2b..3dd7aad6a 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -1,2 +1,71 @@ # -*- coding: utf-8 -*- # +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from common.utils.integer import bit +from common.db.fields import BitChoices + + +__all__ = ['SpecialAccount', 'ActionChoices'] + + +class ActionChoices(BitChoices): + connect = bit(0), _('Connect') + upload = bit(1), _('Upload') + download = bit(2), _('Download') + copy = bit(3), _('Copy') + paste = bit(4), _('Paste') + + @classmethod + def branches(cls): + return ( + (_('Transfer'), [cls.upload, cls.download]), + (_('Clipboard'), [cls.copy, cls.paste]), + ) + + +# class Action(BitOperationChoice): +# CONNECT = 0b1 +# UPLOAD = 0b1 << 1 +# DOWNLOAD = 0b1 << 2 +# COPY = 0b1 << 3 +# PASTE = 0b1 << 4 +# ALL = 0 << 8 +# TRANSFER = UPLOAD | DOWNLOAD +# CLIPBOARD = COPY | PASTE +# +# DB_CHOICES = ( +# (ALL, _('All')), +# (CONNECT, _('Connect')), +# (UPLOAD, _('Upload file')), +# (DOWNLOAD, _('Download file')), +# (TRANSFER, _("Upload download")), +# (COPY, _('Clipboard copy')), +# (PASTE, _('Clipboard paste')), +# (CLIPBOARD, _('Clipboard copy paste')) +# ) +# +# NAME_MAP = { +# ALL: "all", +# CONNECT: "connect", +# UPLOAD: "upload", +# DOWNLOAD: "download", +# TRANSFER: "transfer", +# COPY: 'copy', +# PASTE: 'paste', +# CLIPBOARD: 'clipboard' +# } +# +# NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} +# CHOICES = [] +# for i, j in DB_CHOICES: +# CHOICES.append((NAME_MAP[i], j)) +# +# @classmethod +# def choices(cls): +# pass +# + +class SpecialAccount(models.TextChoices): + ALL = '@ALL', 'All' diff --git a/apps/perms/locks.py b/apps/perms/locks.py index a6ffa6b98..96c766fb8 100644 --- a/apps/perms/locks.py +++ b/apps/perms/locks.py @@ -5,7 +5,5 @@ class UserGrantedTreeRebuildLock(DistributedLock): name_template = 'perms.user.asset.node.tree.rebuid.' def __init__(self, user_id): - name = self.name_template.format( - user_id=user_id - ) + name = self.name_template.format(user_id=user_id) super().__init__(name=name, release_on_transaction_commit=True) diff --git a/apps/perms/migrations/0011_auto_20200721_1739.py b/apps/perms/migrations/0011_auto_20200721_1739.py index df8b46cde..1dcb33633 100644 --- a/apps/perms/migrations/0011_auto_20200721_1739.py +++ b/apps/perms/migrations/0011_auto_20200721_1739.py @@ -3,13 +3,12 @@ from django.db import migrations, models from django.db.models import F -from perms.models import Action def migrate_asset_permission(apps, schema_editor): # 已有的资产权限默认拥有剪切板复制粘贴动作 - AssetPermission = apps.get_model('perms', 'AssetPermission') - AssetPermission.objects.all().update(actions=F('actions').bitor(Action.CLIPBOARD_COPY_PASTE)) + asset_permission_model = apps.get_model('perms', 'AssetPermission') + asset_permission_model.objects.all().update(actions=F('actions').bitor(24)) class Migration(migrations.Migration): diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py index 9cb0efc76..ee7787f7f 100644 --- a/apps/perms/models/__init__.py +++ b/apps/perms/models/__init__.py @@ -1,5 +1,5 @@ # coding: utf-8 # +from .permed_node import * from .asset_permission import * -from .const import * diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 20186527d..47cb1e8e6 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -1,23 +1,19 @@ -import uuid import logging +import uuid +from django.db import models +from django.db.models import Q from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.db import models -from django.db.models import F, Q, TextChoices -from common.utils import lazyproperty, date_expired_default -from common.db.models import BaseCreateUpdateModel, UnionQuerySet -from assets.models import Asset, Node, FamilyMixin, Account -from orgs.mixins.models import OrgModelMixin +from assets.models import Asset, Account +from common.db.models import UnionQuerySet +from common.utils import date_expired_default from orgs.mixins.models import OrgManager -from .const import Action, SpecialAccount +from orgs.mixins.models import OrgModelMixin +from perms.const import ActionChoices, SpecialAccount -__all__ = [ - 'AssetPermission', 'PermNode', - 'UserAssetGrantedTreeNodeRelation', - 'Action' -] +__all__ = ['AssetPermission', 'ActionChoices'] # 使用场景 logger = logging.getLogger(__name__) @@ -67,9 +63,7 @@ class AssetPermission(OrgModelMixin): ) # 特殊的账号: @ALL, @INPUT @USER 默认包含,将来在全局设置中进行控制. accounts = models.JSONField(default=list, verbose_name=_("Accounts")) - actions = models.IntegerField( - choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_("Actions") - ) + actions = models.IntegerField(default=ActionChoices.connect, verbose_name=_("Actions")) is_active = models.BooleanField(default=True, verbose_name=_('Active')) date_start = models.DateTimeField( default=timezone.now, db_index=True, verbose_name=_("Date start") @@ -133,145 +127,9 @@ class AssetPermission(OrgModelMixin): """ asset_ids = self.get_all_assets(flat=True) q = Q(asset_id__in=asset_ids) - if not self.is_perm_all_accounts: + if SpecialAccount.ALL in self.accounts: q &= Q(username__in=self.accounts) accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') if not flat: return accounts return accounts.values_list('id', flat=True) - - @property - def is_perm_all_accounts(self): - return SpecialAccount.ALL in self.accounts - - @lazyproperty - def users_amount(self): - return self.users.count() - - @lazyproperty - def user_groups_amount(self): - return self.user_groups.count() - - @lazyproperty - def assets_amount(self): - return self.assets.count() - - @lazyproperty - def nodes_amount(self): - return self.nodes.count() - - def users_display(self): - names = [user.username for user in self.users.all()] - return names - - def user_groups_display(self): - names = [group.name for group in self.user_groups.all()] - return names - - def assets_display(self): - names = [asset.name for asset in self.assets.all()] - return names - - def nodes_display(self): - names = [node.full_value for node in self.nodes.all()] - return names - - -class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): - class NodeFrom(TextChoices): - granted = 'granted', 'Direct node granted' - child = 'child', 'Have children node' - asset = 'asset', 'Direct asset granted' - - user = models.ForeignKey('users.User', db_constraint=False, on_delete=models.CASCADE) - node = models.ForeignKey('assets.Node', default=None, on_delete=models.CASCADE, - db_constraint=False, null=False, related_name='granted_node_rels') - node_key = models.CharField(max_length=64, verbose_name=_("Key"), db_index=True) - node_parent_key = models.CharField(max_length=64, default='', verbose_name=_('Parent key'), - db_index=True) - node_from = models.CharField(choices=NodeFrom.choices, max_length=16, db_index=True) - node_assets_amount = models.IntegerField(default=0) - - @property - def key(self): - return self.node_key - - @property - def parent_key(self): - return self.node_parent_key - - @classmethod - def get_node_granted_status(cls, user, key): - ancestor_keys = set(cls.get_node_ancestor_keys(key, with_self=True)) - ancestor_rel_nodes = cls.objects.filter(user=user, node_key__in=ancestor_keys) - - for rel_node in ancestor_rel_nodes: - if rel_node.key == key: - return rel_node.node_from, rel_node - if rel_node.node_from == cls.NodeFrom.granted: - return cls.NodeFrom.granted, None - return '', None - - -class PermNode(Node): - class Meta: - proxy = True - ordering = [] - - # 特殊节点 - UNGROUPED_NODE_KEY = 'ungrouped' - UNGROUPED_NODE_VALUE = _('Ungrouped') - FAVORITE_NODE_KEY = 'favorite' - FAVORITE_NODE_VALUE = _('Favorite') - - node_from = '' - granted_assets_amount = 0 - - annotate_granted_node_rel_fields = { - 'granted_assets_amount': F('granted_node_rels__node_assets_amount'), - 'node_from': F('granted_node_rels__node_from') - } - - def use_granted_assets_amount(self): - self.assets_amount = self.granted_assets_amount - - @classmethod - def get_ungrouped_node(cls, assets_amount): - return cls( - id=cls.UNGROUPED_NODE_KEY, - key=cls.UNGROUPED_NODE_KEY, - value=cls.UNGROUPED_NODE_VALUE, - assets_amount=assets_amount - ) - - @classmethod - def get_favorite_node(cls, assets_amount): - node = cls( - id=cls.FAVORITE_NODE_KEY, - key=cls.FAVORITE_NODE_KEY, - value=cls.FAVORITE_NODE_VALUE, - ) - node.assets_amount = assets_amount - return node - - def get_granted_status(self, user): - status, rel_node = UserAssetGrantedTreeNodeRelation.get_node_granted_status(user, self.key) - self.node_from = status - if rel_node: - self.granted_assets_amount = rel_node.node_assets_amount - return status - - def save(self): - # 这是个只读 Model - raise NotImplementedError - - -class PermedAsset(Asset): - class Meta: - proxy = True - verbose_name = _('Permed asset') - permissions = [ - ('view_myassets', _('Can view my assets')), - ('view_userassets', _('Can view user assets')), - ('view_usergroupassets', _('Can view usergroup assets')), - ] diff --git a/apps/perms/models/const.py b/apps/perms/models/const.py deleted file mode 100644 index 6128418b0..000000000 --- a/apps/perms/models/const.py +++ /dev/null @@ -1,48 +0,0 @@ -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from common.db.models import BitOperationChoice - - -__all__ = ['Action', 'SpecialAccount'] - - -class Action(BitOperationChoice): - ALL = 0xff - CONNECT = 0b1 - UPLOAD = 0b1 << 1 - DOWNLOAD = 0b1 << 2 - CLIPBOARD_COPY = 0b1 << 3 - CLIPBOARD_PASTE = 0b1 << 4 - UPDOWNLOAD = UPLOAD | DOWNLOAD - CLIPBOARD_COPY_PASTE = CLIPBOARD_COPY | CLIPBOARD_PASTE - - DB_CHOICES = ( - (ALL, _('All')), - (CONNECT, _('Connect')), - (UPLOAD, _('Upload file')), - (DOWNLOAD, _('Download file')), - (UPDOWNLOAD, _("Upload download")), - (CLIPBOARD_COPY, _('Clipboard copy')), - (CLIPBOARD_PASTE, _('Clipboard paste')), - (CLIPBOARD_COPY_PASTE, _('Clipboard copy paste')) - ) - - NAME_MAP = { - ALL: "all", - CONNECT: "connect", - UPLOAD: "upload_file", - DOWNLOAD: "download_file", - UPDOWNLOAD: "updownload", - CLIPBOARD_COPY: 'clipboard_copy', - CLIPBOARD_PASTE: 'clipboard_paste', - CLIPBOARD_COPY_PASTE: 'clipboard_copy_paste' - } - - NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} - CHOICES = [] - for i, j in DB_CHOICES: - CHOICES.append((NAME_MAP[i], j)) - - -class SpecialAccount(models.TextChoices): - ALL = '@ALL', 'All' diff --git a/apps/perms/models/permed_node.py b/apps/perms/models/permed_node.py new file mode 100644 index 000000000..ce061297e --- /dev/null +++ b/apps/perms/models/permed_node.py @@ -0,0 +1,119 @@ + +from django.utils.translation import ugettext_lazy as _ +from django.db import models +from django.db.models import F, TextChoices + +from common.utils import lazyproperty +from common.db.models import BaseCreateUpdateModel +from assets.models import Asset, Node, FamilyMixin, Account +from orgs.mixins.models import OrgModelMixin + + +class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, BaseCreateUpdateModel): + class NodeFrom(TextChoices): + granted = 'granted', 'Direct node granted' + child = 'child', 'Have children node' + asset = 'asset', 'Direct asset granted' + + user = models.ForeignKey('users.User', db_constraint=False, on_delete=models.CASCADE) + node = models.ForeignKey('assets.Node', default=None, on_delete=models.CASCADE, + db_constraint=False, null=False, related_name='granted_node_rels') + node_key = models.CharField(max_length=64, verbose_name=_("Key"), db_index=True) + node_parent_key = models.CharField(max_length=64, default='', verbose_name=_('Parent key'), + db_index=True) + node_from = models.CharField(choices=NodeFrom.choices, max_length=16, db_index=True) + node_assets_amount = models.IntegerField(default=0) + + @property + def key(self): + return self.node_key + + @property + def parent_key(self): + return self.node_parent_key + + @classmethod + def get_node_granted_status(cls, user, key): + ancestor_keys = set(cls.get_node_ancestor_keys(key, with_self=True)) + ancestor_rel_nodes = cls.objects.filter(user=user, node_key__in=ancestor_keys) + + for rel_node in ancestor_rel_nodes: + if rel_node.key == key: + return rel_node.node_from, rel_node + if rel_node.node_from == cls.NodeFrom.granted: + return cls.NodeFrom.granted, None + return '', None + + +class PermNode(Node): + class Meta: + proxy = True + ordering = [] + + # 特殊节点 + UNGROUPED_NODE_KEY = 'ungrouped' + UNGROUPED_NODE_VALUE = _('Ungrouped') + FAVORITE_NODE_KEY = 'favorite' + FAVORITE_NODE_VALUE = _('Favorite') + + node_from = '' + granted_assets_amount = 0 + + annotate_granted_node_rel_fields = { + 'granted_assets_amount': F('granted_node_rels__node_assets_amount'), + 'node_from': F('granted_node_rels__node_from') + } + + def use_granted_assets_amount(self): + self.assets_amount = self.granted_assets_amount + + @classmethod + def get_ungrouped_node(cls, assets_amount): + return cls( + id=cls.UNGROUPED_NODE_KEY, + key=cls.UNGROUPED_NODE_KEY, + value=cls.UNGROUPED_NODE_VALUE, + assets_amount=assets_amount + ) + + @classmethod + def get_favorite_node(cls, assets_amount): + node = cls( + id=cls.FAVORITE_NODE_KEY, + key=cls.FAVORITE_NODE_KEY, + value=cls.FAVORITE_NODE_VALUE, + ) + node.assets_amount = assets_amount + return node + + def get_granted_status(self, user): + status, rel_node = UserAssetGrantedTreeNodeRelation.get_node_granted_status(user, self.key) + self.node_from = status + if rel_node: + self.granted_assets_amount = rel_node.node_assets_amount + return status + + def save(self): + # 这是个只读 Model + raise NotImplementedError + + +class PermedAsset(Asset): + class Meta: + proxy = True + verbose_name = _('Permed asset') + permissions = [ + ('view_myassets', _('Can view my assets')), + ('view_userassets', _('Can view user assets')), + ('view_usergroupassets', _('Can view usergroup assets')), + ] + + +class PermedAccount(Account): + @lazyproperty + def actions(self): + return 0 + + class Meta: + proxy = True + verbose_name = _('Permed account') diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index ff19b9dd6..9a31058f6 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -1,75 +1,64 @@ # -*- coding: utf-8 -*- # -from rest_framework import serializers -from rest_framework.fields import empty -from django.utils.translation import ugettext_lazy as _ from django.db.models import Q +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers -from common.drf.fields import ObjectRelatedField -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from assets.models import Asset, Node +from common.drf.fields import BitChoicesField, ObjectRelatedField +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from perms.models import ActionChoices, AssetPermission from users.models import User, UserGroup -from perms.models import AssetPermission, Action -__all__ = ['AssetPermissionSerializer', 'ActionsField'] +__all__ = ["AssetPermissionSerializer", "ActionChoicesField"] -class ActionsField(serializers.MultipleChoiceField): +class ActionChoicesField(BitChoicesField): def __init__(self, **kwargs): - kwargs['choices'] = Action.CHOICES - super().__init__(**kwargs) - - def run_validation(self, data=empty): - data = super(ActionsField, self).run_validation(data) - if isinstance(data, list): - data = Action.choices_to_value(value=data) - return data - - def to_representation(self, value): - return Action.value_to_choices(value) - - def to_internal_value(self, data): - if not self.allow_empty and not data: - self.fail('empty') - if not data: - return data - return Action.choices_to_value(data) - - -class ActionsDisplayField(ActionsField): - def to_representation(self, value): - values = super().to_representation(value) - choices = dict(Action.CHOICES) - return [choices.get(i) for i in values] + super().__init__(ActionChoices, **kwargs) class AssetPermissionSerializer(BulkOrgResourceModelSerializer): users = ObjectRelatedField(queryset=User.objects, many=True, required=False) - user_groups = ObjectRelatedField(queryset=UserGroup.objects, many=True, required=False) + user_groups = ObjectRelatedField( + queryset=UserGroup.objects, many=True, required=False + ) assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False) nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False) - actions = ActionsField(required=False, allow_null=True, label=_("Actions")) + actions = ActionChoicesField(required=False, allow_null=True, label=_("Actions")) is_valid = serializers.BooleanField(read_only=True, label=_("Is valid")) - is_expired = serializers.BooleanField(read_only=True, label=_('Is expired')) + is_expired = serializers.BooleanField(read_only=True, label=_("Is expired")) + accounts = serializers.ListField(label=_("Accounts"), required=False) class Meta: model = AssetPermission - fields_mini = ['id', 'name'] + fields_mini = ["id", "name"] fields_small = fields_mini + [ - 'accounts', 'is_active', 'is_expired', 'is_valid', - 'actions', 'created_by', 'date_created', 'date_expired', - 'date_start', 'comment', 'from_ticket' + "accounts", + "is_active", + "is_expired", + "is_valid", + "actions", + "created_by", + "date_created", + "date_expired", + "date_start", + "comment", + "from_ticket", ] fields_m2m = [ - 'users', 'user_groups', 'assets', 'nodes', + "users", + "user_groups", + "assets", + "nodes", ] fields = fields_small + fields_m2m - read_only_fields = ['created_by', 'date_created', 'from_ticket'] + read_only_fields = ["created_by", "date_created", "from_ticket"] extra_kwargs = { - 'actions': {'label': _('Actions')}, - 'is_expired': {'label': _('Is expired')}, - 'is_valid': {'label': _('Is valid')}, + "actions": {"label": _("Actions")}, + "is_expired": {"label": _("Is expired")}, + "is_valid": {"label": _("Is valid")}, } def __init__(self, *args, **kwargs): @@ -77,7 +66,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): self.set_actions_field() def set_actions_field(self): - actions = self.fields.get('actions') + actions = self.fields.get("actions") if not actions: return choices = actions._choices @@ -86,9 +75,12 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): - """ Perform necessary eager loading of data. """ + """Perform necessary eager loading of data.""" queryset = queryset.prefetch_related( - 'users', 'user_groups', 'assets', 'nodes', + "users", + "user_groups", + "assets", + "nodes", ) return queryset @@ -96,35 +88,34 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): def perform_display_create(instance, **kwargs): # 用户 users_to_set = User.objects.filter( - Q(name__in=kwargs.get('users_display')) | - Q(username__in=kwargs.get('users_display')) + Q(name__in=kwargs.get("users_display")) + | Q(username__in=kwargs.get("users_display")) ).distinct() instance.users.add(*users_to_set) # 用户组 user_groups_to_set = UserGroup.objects.filter( - name__in=kwargs.get('user_groups_display') + name__in=kwargs.get("user_groups_display") ).distinct() instance.user_groups.add(*user_groups_to_set) # 资产 assets_to_set = Asset.objects.filter( - Q(address__in=kwargs.get('assets_display')) | - Q(name__in=kwargs.get('assets_display')) + Q(address__in=kwargs.get("assets_display")) + | Q(name__in=kwargs.get("assets_display")) ).distinct() instance.assets.add(*assets_to_set) # 节点 nodes_to_set = Node.objects.filter( - full_value__in=kwargs.get('nodes_display') + full_value__in=kwargs.get("nodes_display") ).distinct() instance.nodes.add(*nodes_to_set) def create(self, validated_data): display = { - 'users_display': validated_data.pop('users_display', ''), - 'user_groups_display': validated_data.pop('user_groups_display', ''), - 'assets_display': validated_data.pop('assets_display', ''), - 'nodes_display': validated_data.pop('nodes_display', '') + "users_display": validated_data.pop("users_display", ""), + "user_groups_display": validated_data.pop("user_groups_display", ""), + "assets_display": validated_data.pop("assets_display", ""), + "nodes_display": validated_data.pop("nodes_display", ""), } instance = super().create(validated_data) self.perform_display_create(instance, **display) return instance - diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 8784a8abb..09eb97428 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from common.drf.fields import ObjectRelatedField, LabeledChoiceField from assets.models import Node, Asset, Platform, Account from assets.const import Category, AllTypes -from perms.serializers.permission import ActionsField +from perms.serializers.permission import ActionChoicesField __all__ = [ 'NodeGrantedSerializer', 'AssetGrantedSerializer', @@ -45,7 +45,7 @@ class NodeGrantedSerializer(serializers.ModelSerializer): class ActionsSerializer(serializers.Serializer): - actions = ActionsField(read_only=True) + actions = ActionChoicesField(read_only=True) class AccountsGrantedSerializer(serializers.ModelSerializer): @@ -53,7 +53,7 @@ class AccountsGrantedSerializer(serializers.ModelSerializer): # Todo: 添加前端登录逻辑中需要的一些字段,比如:是否需要手动输入密码 # need_manual = serializers.BooleanField(label=_('Need manual input')) - actions = ActionsField(read_only=True) + actions = ActionChoicesField(read_only=True) class Meta: model = Account diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 8d8f5e743..167a3060b 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -1,5 +1,6 @@ import time from collections import defaultdict + from assets.models import Account from .permission import AssetPermissionUtil @@ -8,54 +9,78 @@ __all__ = ['PermAccountUtil'] class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ + @staticmethod + def get_permed_accounts_from_perms(perms, user, asset): + alias_action_bit_mapper = defaultdict(int) + alias_expired_mapper = defaultdict(list) - def get_perm_accounts_for_user(self, user, with_actions=False): - """ 获取授权给用户的所有账号 """ - perms = self.get_permissions_for_user(user) - accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) + for perm in perms: + for alias in perm.accounts: + alias_action_bit_mapper[alias] |= perm.actions + alias_expired_mapper[alias].append(perm.date_expired) + + asset_accounts = asset.accounts.all() + username_account_mapper = {account.username: account for account in asset_accounts} + cleaned_accounts_action_bit = defaultdict(int) + cleaned_accounts_expired = defaultdict(list) + + # @ALL 账号先处理,后面的每个最多映射一个账号 + all_action_bit = alias_action_bit_mapper.pop('@ALL', None) + if all_action_bit: + for account in asset_accounts: + cleaned_accounts_action_bit[account] |= all_action_bit + cleaned_accounts_expired[account].extend(alias_expired_mapper['@ALL']) + + for alias, action_bit in alias_action_bit_mapper.items(): + if alias == '@USER': + if user.username in username_account_mapper: + account = username_account_mapper[user.username] + else: + account = Account.get_user_account(user.username) + elif alias == '@INPUT': + account = Account.get_manual_account() + elif alias in username_account_mapper: + account = username_account_mapper[alias] + else: + account = None + + if account: + cleaned_accounts_action_bit[account] |= action_bit + cleaned_accounts_expired[account].extend(alias_expired_mapper[alias]) + + accounts = [] + for account, action_bit in cleaned_accounts_action_bit.items(): + account.actions = action_bit + account.date_expired = max(cleaned_accounts_expired[account]) + accounts.append(account) return accounts - def get_perm_accounts_for_user_asset(self, user, asset, with_actions=False, with_perms=False): + def get_permed_accounts_for_user(self, user, asset): """ 获取授权给用户某个资产的账号 """ perms = self.get_permissions_for_user_asset(user, asset) - accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) - if with_perms: - return perms, accounts - return accounts - - def get_perm_accounts_for_user_group_asset(self, user_group, asset, with_actions=False): - """ 获取授权给用户组某个资产的账号 """ - perms = self.get_permissions_for_user_group_asset(user_group, asset) - accounts = self.get_perm_accounts_for_permissions(perms, with_actions=with_actions) - return accounts + permed_accounts = self.get_permed_accounts_from_perms(perms, user, asset) + return permed_accounts @staticmethod - def get_perm_accounts_for_permissions(permissions, with_actions=False): + def get_accounts_for_permission(perm, with_actions=False): """ 获取授权规则包含的账号 """ aid_actions_map = defaultdict(int) - for perm in permissions: - account_ids = perm.get_all_accounts(flat=True) - actions = perm.actions - for aid in account_ids: - aid_actions_map[str(aid)] |= actions + # 这里不行,速度太慢, 别情有很多查询 + account_ids = perm.get_all_accounts(flat=True) + actions = perm.actions + for aid in account_ids: + aid_actions_map[str(aid)] |= actions account_ids = list(aid_actions_map.keys()) - accounts = Account.objects.filter(id__in=account_ids).order_by( - 'asset__name', 'name', 'username' - ) - if with_actions: - for account in accounts: - account.actions = aid_actions_map.get(str(account.id)) + accounts = Account.objects.filter(id__in=account_ids) return accounts def validate_permission(self, user, asset, account_username): """ 校验用户有某个资产下某个账号名的权限 """ - perms, accounts = self.get_perm_accounts_for_user_asset( - user, asset, with_actions=True, with_perms=True - ) - perm = perms.first() - actions = [] - for account in accounts: - if account.username == account_username: - actions = account.actions - expire_at = perm.date_expired.timestamp() if perm else time.time() - return actions, expire_at + permed_accounts = self.get_permed_accounts_for_user(user, asset) + accounts_mapper = {account.username: account for account in permed_accounts} + + account = accounts_mapper.get(account_username) + if not account: + return False, None + else: + return account.actions, account.date_expired diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index fd0ea593b..8e4cd9199 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -1,12 +1,6 @@ -import time -from collections import defaultdict - -from django.db.models import Q from common.utils import get_logger -from perms.models import AssetPermission, Action -from perms.hands import Asset, User, UserGroup, Node -from perms.utils.user_permission import get_user_all_asset_perm_ids +from perms.models import AssetPermission logger = get_logger(__file__) diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index 81a2b9a10..fe73913cf 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -19,13 +19,12 @@ from orgs.utils import ( from assets.models import ( Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet ) -from orgs.models import Organization -from perms.models import ( - AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation, -) from users.models import User +from orgs.models import Organization from perms.locks import UserGrantedTreeRebuildLock - +from perms.models import ( + AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation +) NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id') diff --git a/apps/tickets/migrations/0017_auto_20220623_1027.py b/apps/tickets/migrations/0017_auto_20220623_1027.py index 87752a469..ba2351d65 100644 --- a/apps/tickets/migrations/0017_auto_20220623_1027.py +++ b/apps/tickets/migrations/0017_auto_20220623_1027.py @@ -7,7 +7,6 @@ from collections import defaultdict from django.utils import timezone as dj_timezone from django.db import migrations -from perms.models import Action from tickets.const import TicketType pt = re.compile(r'(\w+)\((\w+)\)') diff --git a/apps/tickets/models/ticket/apply_asset.py b/apps/tickets/models/ticket/apply_asset.py index c0ec46cc0..d5f11ee36 100644 --- a/apps/tickets/models/ticket/apply_asset.py +++ b/apps/tickets/models/ticket/apply_asset.py @@ -1,7 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from perms.models import Action from .general import Ticket __all__ = ['ApplyAssetTicket'] @@ -15,15 +14,13 @@ class ApplyAssetTicket(Ticket): # 申请信息 apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets')) apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts')) - apply_actions = models.IntegerField( - choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_('Actions') - ) + apply_actions = models.IntegerField(default=1, verbose_name=_('Actions')) apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) @property def apply_actions_display(self): - return Action.value_to_choices_display(self.apply_actions) + return 'Todo' def get_apply_actions_display(self): return ', '.join(self.apply_actions_display) diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index a2cb94179..26a1fe434 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from perms.serializers.permission import ActionsField +from perms.serializers.permission import ActionChoicesField from perms.models import AssetPermission from orgs.utils import tmp_to_org from assets.models import Asset, Node @@ -16,7 +16,7 @@ asset_or_node_help_text = _("Select at least one asset or node") class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer): - apply_actions = ActionsField(required=True, allow_empty=False) + apply_actions = ActionChoicesField(required=True, allow_empty=False) permission_model = AssetPermission class Meta: diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 833752727..d84a7baa0 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -15,8 +15,10 @@ from ..models import User from ..const import PasswordStrategy __all__ = [ - 'UserSerializer', 'MiniUserSerializer', - 'InviteSerializer', 'ServiceAccountSerializer', + "UserSerializer", + "MiniUserSerializer", + "InviteSerializer", + "ServiceAccountSerializer", ] logger = get_logger(__file__) @@ -25,15 +27,17 @@ logger = get_logger(__file__) class RolesSerializerMixin(serializers.Serializer): system_roles = serializers.ManyRelatedField( child_relation=serializers.PrimaryKeyRelatedField(queryset=Role.system_roles), - label=_('System roles'), + label=_("System roles"), ) org_roles = serializers.ManyRelatedField( required=False, child_relation=serializers.PrimaryKeyRelatedField(queryset=Role.org_roles), - label=_('Org roles'), + label=_("Org roles"), ) - system_roles_display = serializers.SerializerMethodField(label=_('System roles display')) - org_roles_display = serializers.SerializerMethodField(label=_('Org roles display')) + system_roles_display = serializers.SerializerMethodField( + label=_("System roles display") + ) + org_roles_display = serializers.SerializerMethodField(label=_("Org roles display")) @staticmethod def get_system_roles_display(user): @@ -44,20 +48,20 @@ class RolesSerializerMixin(serializers.Serializer): return user.org_roles.display def pop_roles_if_need(self, fields): - request = self.context.get('request') - view = self.context.get('view') + request = self.context.get("request") + view = self.context.get("view") - if not all([request, view, hasattr(view, 'action')]): + if not all([request, view, hasattr(view, "action")]): return fields if request.user.is_anonymous: return fields - action = view.action or 'list' - if action in ('partial_bulk_update', 'bulk_update', 'partial_update', 'update'): - action = 'create' + action = view.action or "list" + if action in ("partial_bulk_update", "bulk_update", "partial_update", "update"): + action = "create" model_cls_field_mapper = { - SystemRoleBinding: ['system_roles', 'system_roles_display'], - OrgRoleBinding: ['org_roles', 'system_roles_display'] + SystemRoleBinding: ["system_roles", "system_roles_display"], + OrgRoleBinding: ["org_roles", "system_roles_display"], } for model_cls, fields_names in model_cls_field_mapper.items(): @@ -75,97 +79,148 @@ class RolesSerializerMixin(serializers.Serializer): return fields -class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializers.ModelSerializer): +class UserSerializer( + RolesSerializerMixin, CommonBulkSerializerMixin, serializers.ModelSerializer +): password_strategy = serializers.ChoiceField( - choices=PasswordStrategy.choices, default=PasswordStrategy.email, required=False, - write_only=True, label=_('Password strategy') + choices=PasswordStrategy.choices, + default=PasswordStrategy.email, + required=False, + write_only=True, + label=_("Password strategy"), + ) + mfa_enabled = serializers.BooleanField(read_only=True, label=_("MFA enabled")) + mfa_force_enabled = serializers.BooleanField( + read_only=True, label=_("MFA force enabled") ) - mfa_enabled = serializers.BooleanField(read_only=True, label=_('MFA enabled')) - mfa_force_enabled = serializers.BooleanField(read_only=True, label=_('MFA force enabled')) mfa_level_display = serializers.ReadOnlyField( - source='get_mfa_level_display', label=_('MFA level display') + source="get_mfa_level_display", label=_("MFA level display") ) - login_blocked = serializers.BooleanField(read_only=True, label=_('Login blocked')) - is_expired = serializers.BooleanField(read_only=True, label=_('Is expired')) + login_blocked = serializers.BooleanField(read_only=True, label=_("Login blocked")) + is_expired = serializers.BooleanField(read_only=True, label=_("Is expired")) can_public_key_auth = serializers.ReadOnlyField( - source='can_use_ssh_key_login', label=_('Can public key authentication') + source="can_use_ssh_key_login", label=_("Can public key authentication") ) password = EncryptedField( - label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024 + label=_("Password"), + required=False, + allow_blank=True, + allow_null=True, + max_length=1024, ) # Todo: 这里看看该怎么搞 # can_update = serializers.SerializerMethodField(label=_('Can update')) # can_delete = serializers.SerializerMethodField(label=_('Can delete')) custom_m2m_fields = { - 'system_roles': [BuiltinRole.system_user], - 'org_roles': [BuiltinRole.org_user] + "system_roles": [BuiltinRole.system_user], + "org_roles": [BuiltinRole.org_user], } class Meta: model = User # mini 是指能识别对象的最小单元 - fields_mini = ['id', 'name', 'username'] + fields_mini = ["id", "name", "username"] # 只能写的字段, 这个虽然无法在框架上生效,但是更多对我们是提醒 fields_write_only = [ - 'password', 'public_key', + "password", + "public_key", ] # small 指的是 不需要计算的直接能从一张表中获取到的数据 - fields_small = fields_mini + fields_write_only + [ - 'email', 'wechat', 'phone', 'mfa_level', 'source', 'source_display', - 'can_public_key_auth', 'need_update_password', - 'mfa_enabled', 'is_service_account', 'is_valid', 'is_expired', 'is_active', # 布尔字段 - 'date_expired', 'date_joined', 'last_login', # 日期字段 - 'created_by', 'comment', # 通用字段 - 'is_wecom_bound', 'is_dingtalk_bound', 'is_feishu_bound', 'is_otp_secret_key_bound', - 'wecom_id', 'dingtalk_id', 'feishu_id' - ] + fields_small = ( + fields_mini + + fields_write_only + + [ + "email", + "wechat", + "phone", + "mfa_level", + "source", + "source_display", + "can_public_key_auth", + "need_update_password", + "mfa_enabled", + "is_service_account", + "is_valid", + "is_expired", + "is_active", # 布尔字段 + "date_expired", + "date_joined", + "last_login", # 日期字段 + "created_by", + "comment", # 通用字段 + "is_wecom_bound", + "is_dingtalk_bound", + "is_feishu_bound", + "is_otp_secret_key_bound", + "wecom_id", + "dingtalk_id", + "feishu_id", + ] + ) # 包含不太常用的字段,可以没有 fields_verbose = fields_small + [ - 'mfa_level_display', 'mfa_force_enabled', 'is_first_login', - 'date_password_last_updated', 'avatar_url', + "mfa_level_display", + "mfa_force_enabled", + "is_first_login", + "date_password_last_updated", + "avatar_url", ] # 外键的字段 fields_fk = [] # 多对多字段 fields_m2m = [ - 'groups', 'groups_display', 'system_roles', 'org_roles', - 'system_roles_display', 'org_roles_display' + "groups", + "groups_display", + "system_roles", + "org_roles", + "system_roles_display", + "org_roles_display", ] # 在serializer 上定义的字段 - fields_custom = ['login_blocked', 'password_strategy'] + fields_custom = ["login_blocked", "password_strategy"] fields = fields_verbose + fields_fk + fields_m2m + fields_custom read_only_fields = [ - 'date_joined', 'last_login', 'created_by', 'is_first_login', - 'wecom_id', 'dingtalk_id', 'feishu_id' + "date_joined", + "last_login", + "created_by", + "is_first_login", + "wecom_id", + "dingtalk_id", + "feishu_id", ] - disallow_self_update_fields = ['is_active'] + disallow_self_update_fields = ["is_active"] extra_kwargs = { - 'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True}, - 'public_key': {'write_only': True}, - 'is_first_login': {'label': _('Is first login'), 'read_only': True}, - 'is_active': {'label': _('Is active')}, - 'is_valid': {'label': _('Is valid')}, - '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}, - 'groups_display': {'label': _('Groups name')}, - 'source_display': {'label': _('Source name')}, - 'org_role_display': {'label': _('Organization role name')}, - 'role_display': {'label': _('Super role name')}, - 'total_role_display': {'label': _('Total role name')}, - 'role': {'default': "User"}, - 'is_wecom_bound': {'label': _('Is wecom bound')}, - 'is_dingtalk_bound': {'label': _('Is dingtalk bound')}, - 'is_feishu_bound': {'label': _('Is feishu bound')}, - 'is_otp_secret_key_bound': {'label': _('Is OTP bound')}, - 'phone': {'validators': [PhoneValidator()]}, - 'system_role_display': {'label': _('System role name')}, + "password": { + "write_only": True, + "required": False, + "allow_null": True, + "allow_blank": True, + }, + "public_key": {"write_only": True}, + "is_first_login": {"label": _("Is first login"), "read_only": True}, + "is_active": {"label": _("Is active")}, + "is_valid": {"label": _("Is valid")}, + "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}, + "groups_display": {"label": _("Groups name")}, + "source_display": {"label": _("Source name")}, + "org_role_display": {"label": _("Organization role name")}, + "role_display": {"label": _("Super role name")}, + "total_role_display": {"label": _("Total role name")}, + "role": {"default": "User"}, + "is_wecom_bound": {"label": _("Is wecom bound")}, + "is_dingtalk_bound": {"label": _("Is dingtalk bound")}, + "is_feishu_bound": {"label": _("Is feishu bound")}, + "is_otp_secret_key_bound": {"label": _("Is OTP bound")}, + "phone": {"validators": [PhoneValidator()]}, + "system_role_display": {"label": _("System role name")}, } def validate_password(self, password): - password_strategy = self.initial_data.get('password_strategy') + password_strategy = self.initial_data.get("password_strategy") if self.instance is None and password_strategy != PasswordStrategy.custom: # 创建用户,使用邮件设置密码 return @@ -176,32 +231,34 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer @staticmethod def change_password_to_raw(attrs): - password = attrs.pop('password', None) + password = attrs.pop("password", None) if password: - attrs['password_raw'] = password + attrs["password_raw"] = password return attrs @staticmethod def clean_auth_fields(attrs): - for field in ('password', 'public_key'): + for field in ("password", "public_key"): value = attrs.get(field) if not value: attrs.pop(field, None) return attrs def check_disallow_self_update_fields(self, attrs): - request = self.context.get('request') + request = self.context.get("request") if not request or not request.user.is_authenticated: return attrs if not self.instance: return attrs if request.user.id != self.instance.id: return attrs - disallow_fields = set(list(attrs.keys())) & set(self.Meta.disallow_self_update_fields) + disallow_fields = set(list(attrs.keys())) & set( + self.Meta.disallow_self_update_fields + ) if not disallow_fields: return attrs # 用户自己不能更新自己的一些字段 - logger.debug('Disallow update self fields: %s', disallow_fields) + logger.debug("Disallow update self fields: %s", disallow_fields) for field in disallow_fields: attrs.pop(field, None) return attrs @@ -210,7 +267,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer attrs = self.check_disallow_self_update_fields(attrs) attrs = self.change_password_to_raw(attrs) attrs = self.clean_auth_fields(attrs) - attrs.pop('password_strategy', None) + attrs.pop("password_strategy", None) return attrs def save_and_set_custom_m2m_fields(self, validated_data, save_handler, created): @@ -219,8 +276,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer roles = validated_data.pop(f, None) if created and not roles: roles = [ - Role.objects.filter(id=role.id).first() - for role in default_roles + Role.objects.filter(id=role.id).first() for role in default_roles ] m2m_values[f] = roles @@ -234,22 +290,26 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer def update(self, instance, validated_data): save_handler = partial(super().update, instance) - instance = self.save_and_set_custom_m2m_fields(validated_data, save_handler, created=False) + instance = self.save_and_set_custom_m2m_fields( + validated_data, save_handler, created=False + ) return instance def create(self, validated_data): save_handler = super().create - instance = self.save_and_set_custom_m2m_fields(validated_data, save_handler, created=True) + instance = self.save_and_set_custom_m2m_fields( + validated_data, save_handler, created=True + ) return instance class UserRetrieveSerializer(UserSerializer): login_confirm_settings = serializers.PrimaryKeyRelatedField( - read_only=True, source='login_confirm_setting.reviewers', many=True + read_only=True, source="login_confirm_setting.reviewers", many=True ) class Meta(UserSerializer.Meta): - fields = UserSerializer.Meta.fields + ['login_confirm_settings'] + fields = UserSerializer.Meta.fields + ["login_confirm_settings"] class MiniUserSerializer(serializers.ModelSerializer): @@ -260,8 +320,10 @@ class MiniUserSerializer(serializers.ModelSerializer): class InviteSerializer(RolesSerializerMixin, serializers.Serializer): users = serializers.PrimaryKeyRelatedField( - queryset=User.get_nature_users(), many=True, label=_('Select users'), - help_text=_('For security, only list several users') + queryset=User.get_nature_users(), + many=True, + label=_("Select users"), + help_text=_("For security, only list several users"), ) system_roles = None system_roles_display = None @@ -271,22 +333,23 @@ class InviteSerializer(RolesSerializerMixin, serializers.Serializer): class ServiceAccountSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', 'name', 'access_key', 'comment'] - read_only_fields = ['access_key'] + fields = ["id", "name", "access_key", "comment"] + read_only_fields = ["access_key"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) from authentication.serializers import AccessKeySerializer - self.fields['access_key'] = AccessKeySerializer(read_only=True) + + self.fields["access_key"] = AccessKeySerializer(read_only=True) def get_username(self): - return self.initial_data.get('name') + return self.initial_data.get("name") def get_email(self): - name = self.initial_data.get('name') + name = self.initial_data.get("name") name_max_length = 128 - len(User.service_account_email_suffix) - name = pretty_string(name, max_length=name_max_length, ellipsis_str='-') - return '{}{}'.format(name, User.service_account_email_suffix) + name = pretty_string(name, max_length=name_max_length, ellipsis_str="-") + return "{}{}".format(name, User.service_account_email_suffix) def validate_name(self, name): email = self.get_email() @@ -296,12 +359,12 @@ class ServiceAccountSerializer(serializers.ModelSerializer): else: users = User.objects.all() if users.filter(email=email) or users.filter(username=username): - raise serializers.ValidationError(_('name not unique'), code='unique') + raise serializers.ValidationError(_("name not unique"), code="unique") return name def create(self, validated_data): - name = validated_data['name'] + name = validated_data["name"] email = self.get_email() - comment = validated_data.get('comment', '') + comment = validated_data.get("comment", "") user, ak = User.create_service_account(name, email, comment) return user From cb82b53791d082f18e287fd14dcba702d042696f Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 11 Nov 2022 16:13:16 +0800 Subject: [PATCH 324/488] perf: automation celery task (#9046) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/automations/base.py | 4 +--- apps/assets/models/automations/base.py | 2 +- apps/assets/tasks/automation.py | 4 +++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/assets/api/automations/base.py b/apps/assets/api/automations/base.py index 845b721c8..1b480cbdc 100644 --- a/apps/assets/api/automations/base.py +++ b/apps/assets/api/automations/base.py @@ -5,7 +5,6 @@ from rest_framework import status, mixins, viewsets from orgs.mixins import generics from assets import serializers -from assets.const import AutomationTypes from assets.tasks import execute_automation from assets.models import BaseAutomation, AutomationExecution from common.const.choices import Trigger @@ -111,8 +110,7 @@ class AutomationExecutionViewSet( serializer.is_valid(raise_exception=True) automation = serializer.validated_data.get('automation') tp = serializer.validated_data.get('type') - model = AutomationTypes.get_type_model(tp) task = execute_automation.delay( - pid=automation.pk, trigger=Trigger.manual, model=model + pid=automation.pk, trigger=Trigger.manual, tp=tp ) return Response({'task': task.id}, status=status.HTTP_201_CREATED) diff --git a/apps/assets/models/automations/base.py b/apps/assets/models/automations/base.py index fd9bc422b..9977f6830 100644 --- a/apps/assets/models/automations/base.py +++ b/apps/assets/models/automations/base.py @@ -47,7 +47,7 @@ class BaseAutomation(CommonModelMixin, PeriodTaskModelMixin, OrgModelMixin): def get_register_task(self): name = f"automation_{self.type}_strategy_period_{str(self.id)[:8]}" task = execute_automation.name - args = (str(self.id), Trigger.timing, self._meta.model) + args = (str(self.id), Trigger.timing, self.type) kwargs = {} return name, task, args, kwargs diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py index 873e606b6..d750fa7cb 100644 --- a/apps/assets/tasks/automation.py +++ b/apps/assets/tasks/automation.py @@ -2,12 +2,14 @@ from celery import shared_task from orgs.utils import tmp_to_root_org, tmp_to_org from common.utils import get_logger, get_object_or_none +from assets.const import AutomationTypes logger = get_logger(__file__) @shared_task(queue='ansible') -def execute_automation(pid, trigger, model): +def execute_automation(pid, trigger, tp): + model = AutomationTypes.get_model(tp) with tmp_to_root_org(): instance = get_object_or_none(model, pk=pid) if not instance: From 9c5b3a03c6e107e83d209eef421d48e28dc058d1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 11 Nov 2022 17:28:13 +0800 Subject: [PATCH 325/488] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=20permission?= =?UTF-8?q?=20actionss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/db/fields.py | 18 ++++++---- apps/common/drf/fields.py | 12 +++++-- apps/common/drf/metadata.py | 62 ++++++++++++++++++++++------------ apps/perms/const.py | 67 ++++++++----------------------------- 4 files changed, 76 insertions(+), 83 deletions(-) diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 22640fb5d..00a77826a 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -2,10 +2,10 @@ # import json -from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import force_text from django.core.validators import MinValueValidator, MaxValueValidator +from django.db import models +from django.utils.encoding import force_text +from django.utils.translation import ugettext_lazy as _ from common.utils import signer, crypto @@ -211,22 +211,28 @@ class BitChoices(models.IntegerChoices): def branches(cls): return [i for i in cls] + @classmethod + def is_tree(cls): + return False + @classmethod def tree(cls): + if not cls.is_tree(): + return [] root = [_("All"), cls.branches()] - return cls.render_node(root) + return [cls.render_node(root)] @classmethod def render_node(cls, node): if isinstance(node, BitChoices): return { - "id": node.name, + "value": node.name, "label": node.label, } else: name, children = node return { - "id": name, + "value": name, "label": name, "children": [cls.render_node(child) for child in children], } diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 1e68265ab..9f2527d8a 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -2,11 +2,11 @@ # import six from django.core.exceptions import ObjectDoesNotExist -from django.db.models import IntegerChoices from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from rest_framework.fields import ChoiceField +from common.db.fields import BitChoices from common.utils import decrypt_password __all__ = [ @@ -15,6 +15,7 @@ __all__ = [ "LabeledChoiceField", "ObjectRelatedField", "BitChoicesField", + "TreeChoicesMixin" ] @@ -102,14 +103,19 @@ class ObjectRelatedField(serializers.RelatedField): self.fail("incorrect_type", data_type=type(pk).__name__) -class BitChoicesField(serializers.MultipleChoiceField): +class TreeChoicesMixin: + tree = [] + + +class BitChoicesField(TreeChoicesMixin, serializers.MultipleChoiceField): """ 位字段 """ def __init__(self, choice_cls, **kwargs): - assert issubclass(choice_cls, IntegerChoices) + assert issubclass(choice_cls, BitChoices) choices = [(c.name, c.label) for c in choice_cls] + self.tree = choice_cls.tree() self._choice_cls = choice_cls super().__init__(choices=choices, **kwargs) diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index d16ab2262..fc9ceb961 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -13,6 +13,8 @@ from rest_framework.fields import empty from rest_framework.metadata import SimpleMetadata from rest_framework.request import clone_request +from common.drf.fields import TreeChoicesMixin + class SimpleMetadataWithFilters(SimpleMetadata): """Override SimpleMetadata, adding info about filters""" @@ -59,13 +61,45 @@ class SimpleMetadataWithFilters(SimpleMetadata): view.request = request return actions + def get_field_type(self, field): + """ + Given a field, return a string representing the type of the field. + """ + tp = self.label_lookup[field] + + class_name = field.__class__.__name__ + if class_name == "LabeledChoiceField": + tp = "labeled_choice" + elif class_name == "ObjectRelatedField": + tp = "object_related_field" + elif class_name == "ManyRelatedField": + child_relation_class_name = field.child_relation.__class__.__name__ + if child_relation_class_name == "ObjectRelatedField": + tp = "m2m_related_field" + return tp + + @staticmethod + def set_choices_field(field, field_info): + field_info["choices"] = [ + { + "value": choice_value, + "label": force_text(choice_label, strings_only=True), + } + for choice_value, choice_label in dict(field.choices).items() + ] + + @staticmethod + def set_tree_field(field, field_info): + field_info["tree"] = field.tree + field_info["type"] = "tree" + def get_field_info(self, field): """ Given an instance of a serializer field, return a dictionary of metadata about it. """ field_info = OrderedDict() - field_info["type"] = self.label_lookup[field] + field_info["type"] = self.get_field_type(field) field_info["required"] = getattr(field, "required", False) # Default value @@ -84,25 +118,10 @@ class SimpleMetadataWithFilters(SimpleMetadata): elif getattr(field, "fields", None): field_info["children"] = self.get_serializer_info(field) - is_choice_field = isinstance(field, (serializers.ChoiceField,)) - if is_choice_field and hasattr(field, "choices"): - field_info["choices"] = [ - { - "value": choice_value, - "label": force_text(choice_label, strings_only=True), - } - for choice_value, choice_label in dict(field.choices).items() - ] - - class_name = field.__class__.__name__ - if class_name == "LabeledChoiceField": - field_info["type"] = "labeled_choice" - elif class_name == "ObjectRelatedField": - field_info["type"] = "object_related_field" - elif class_name == "ManyRelatedField": - child_relation_class_name = field.child_relation.__class__.__name__ - if child_relation_class_name == "ObjectRelatedField": - field_info["type"] = "m2m_related_field" + if isinstance(field, TreeChoicesMixin): + self.set_tree_field(field, field_info) + elif isinstance(field, serializers.ChoiceField): + self.set_choices_field(field, field_info) return field_info @staticmethod @@ -130,7 +149,8 @@ class SimpleMetadataWithFilters(SimpleMetadata): fields = list(fields.keys()) return fields - def get_ordering_fields(self, request, view): + @staticmethod + def get_ordering_fields(request, view): fields = [] if hasattr(view, "get_ordering_fields"): fields = view.get_ordering_fields(request) diff --git a/apps/perms/const.py b/apps/perms/const.py index 3dd7aad6a..b78d6b48b 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -3,69 +3,30 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.utils.integer import bit from common.db.fields import BitChoices +from common.utils.integer import bit - -__all__ = ['SpecialAccount', 'ActionChoices'] +__all__ = ["SpecialAccount", "ActionChoices"] class ActionChoices(BitChoices): - connect = bit(0), _('Connect') - upload = bit(1), _('Upload') - download = bit(2), _('Download') - copy = bit(3), _('Copy') - paste = bit(4), _('Paste') + connect = bit(0), _("Connect") + upload = bit(1), _("Upload") + download = bit(2), _("Download") + copy = bit(3), _("Copy") + paste = bit(4), _("Paste") + + @classmethod + def is_tree(cls): + return True @classmethod def branches(cls): return ( - (_('Transfer'), [cls.upload, cls.download]), - (_('Clipboard'), [cls.copy, cls.paste]), + (_("Transfer"), [cls.upload, cls.download]), + (_("Clipboard"), [cls.copy, cls.paste]), ) -# class Action(BitOperationChoice): -# CONNECT = 0b1 -# UPLOAD = 0b1 << 1 -# DOWNLOAD = 0b1 << 2 -# COPY = 0b1 << 3 -# PASTE = 0b1 << 4 -# ALL = 0 << 8 -# TRANSFER = UPLOAD | DOWNLOAD -# CLIPBOARD = COPY | PASTE -# -# DB_CHOICES = ( -# (ALL, _('All')), -# (CONNECT, _('Connect')), -# (UPLOAD, _('Upload file')), -# (DOWNLOAD, _('Download file')), -# (TRANSFER, _("Upload download")), -# (COPY, _('Clipboard copy')), -# (PASTE, _('Clipboard paste')), -# (CLIPBOARD, _('Clipboard copy paste')) -# ) -# -# NAME_MAP = { -# ALL: "all", -# CONNECT: "connect", -# UPLOAD: "upload", -# DOWNLOAD: "download", -# TRANSFER: "transfer", -# COPY: 'copy', -# PASTE: 'paste', -# CLIPBOARD: 'clipboard' -# } -# -# NAME_MAP_REVERSE = {v: k for k, v in NAME_MAP.items()} -# CHOICES = [] -# for i, j in DB_CHOICES: -# CHOICES.append((NAME_MAP[i], j)) -# -# @classmethod -# def choices(cls): -# pass -# - class SpecialAccount(models.TextChoices): - ALL = '@ALL', 'All' + ALL = "@ALL", "All" From 8b351f49e530f366c3a4371f5288ec55c83adfd1 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 11 Nov 2022 18:16:11 +0800 Subject: [PATCH 326/488] perf: update applet host deployment --- .../automations/deploy_applet_host/playbook.yml | 6 +++--- apps/terminal/models/applet/host.py | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index d8b040583..4f652fcbf 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -177,6 +177,6 @@ delay: 5 - name: Sync all remote applets - ansible.windows.win_shell: > - echo "TODO: Sync all remote applets" - when: Initial + ansible.windows.win_shell: + "tinkerd install all" + diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 5a85cbaf8..990e366df 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -2,14 +2,14 @@ import os from collections import defaultdict from django.db import models -from django.utils.translation import gettext_lazy as _ from django.utils import timezone +from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import ValidationError from simple_history.utils import bulk_create_with_history +from assets.models import Host from common.db.models import JMSBaseModel from common.utils import random_string -from assets.models import Host __all__ = ['AppletHost', 'AppletHostDeployment'] @@ -44,13 +44,11 @@ class AppletHost(Host): raise ValidationError('Request user has no terminal') self.date_synced = timezone.now() - if not self.terminal: + if self.terminal == request_terminal: + self.save(update_fields=['date_synced']) + else: self.terminal = request_terminal self.save(update_fields=['terminal', 'date_synced']) - elif self.terminal and self.terminal != request_terminal: - raise ValidationError('Terminal has been set') - else: - self.save(update_fields=['date_synced']) def check_applets_state(self, applets_value_list): applets = self.applets.all() From b100bbf8388f1249728503f074cf72e212f3d4d7 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 11 Nov 2022 19:15:43 +0800 Subject: [PATCH 327/488] perf: change secret (#9048) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/automations/change_secret.py | 2 +- apps/assets/tasks/automation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/automations/change_secret.py b/apps/assets/api/automations/change_secret.py index 291c56518..3cdea7e65 100644 --- a/apps/assets/api/automations/change_secret.py +++ b/apps/assets/api/automations/change_secret.py @@ -36,5 +36,5 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): execution = get_object_or_none(AutomationExecution, pk=eid) if execution: queryset = queryset.filter(execution=execution) - queryset = queryset.order_by('-date_start') + queryset = queryset.order_by('-date_started') return queryset diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py index d750fa7cb..e288de464 100644 --- a/apps/assets/tasks/automation.py +++ b/apps/assets/tasks/automation.py @@ -9,7 +9,7 @@ logger = get_logger(__file__) @shared_task(queue='ansible') def execute_automation(pid, trigger, tp): - model = AutomationTypes.get_model(tp) + model = AutomationTypes.get_type_model(tp) with tmp_to_root_org(): instance = get_object_or_none(model, pk=pid) if not instance: From 0044f1126223a70a36966b8c012127fcfba7acc4 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 11 Nov 2022 19:20:17 +0800 Subject: [PATCH 328/488] =?UTF-8?q?feat:=20=E6=89=A7=E8=A1=8Cadhoc?= =?UTF-8?q?=E5=92=8Cplaybook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0111_alter_automationexecution_status.py | 18 ++ .../migrations/0015_auto_20221111_1919.py | 28 +++ apps/ops/ansible/utils.py | 2 +- apps/ops/api/__init__.py | 2 + apps/ops/api/adhoc.py | 44 +---- apps/ops/api/celery.py | 15 +- apps/ops/api/job.py | 41 +++++ apps/ops/api/playbook.py | 28 +++ .../ops/migrations/0029_auto_20221111_1919.py | 171 ++++++++++++++++++ apps/ops/models/__init__.py | 1 + apps/ops/models/adhoc.py | 34 +++- apps/ops/models/base.py | 15 +- apps/ops/models/celery.py | 2 +- apps/ops/models/job.py | 154 ++++++++++++++++ apps/ops/models/playbook.py | 44 ++--- apps/ops/serializers/adhoc.py | 81 +++------ apps/ops/serializers/job.py | 29 +++ apps/ops/serializers/playbook.py | 28 +++ apps/ops/tasks.py | 39 +++- apps/ops/urls/api_urls.py | 17 +- .../migrations/0032_auto_20221111_1919.py | 31 ++++ ...22_alter_applyassetticket_apply_actions.py | 18 ++ 22 files changed, 680 insertions(+), 162 deletions(-) create mode 100644 apps/assets/migrations/0111_alter_automationexecution_status.py create mode 100644 apps/audits/migrations/0015_auto_20221111_1919.py create mode 100644 apps/ops/api/job.py create mode 100644 apps/ops/api/playbook.py create mode 100644 apps/ops/migrations/0029_auto_20221111_1919.py create mode 100644 apps/ops/models/job.py create mode 100644 apps/ops/serializers/job.py create mode 100644 apps/ops/serializers/playbook.py create mode 100644 apps/perms/migrations/0032_auto_20221111_1919.py create mode 100644 apps/tickets/migrations/0022_alter_applyassetticket_apply_actions.py diff --git a/apps/assets/migrations/0111_alter_automationexecution_status.py b/apps/assets/migrations/0111_alter_automationexecution_status.py new file mode 100644 index 000000000..5ccaf638f --- /dev/null +++ b/apps/assets/migrations/0111_alter_automationexecution_status.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-11 11:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0110_changesecretrecord_asset'), + ] + + operations = [ + migrations.AlterField( + model_name='automationexecution', + name='status', + field=models.CharField(default='pending', max_length=16, verbose_name='Status'), + ), + ] diff --git a/apps/audits/migrations/0015_auto_20221111_1919.py b/apps/audits/migrations/0015_auto_20221111_1919.py new file mode 100644 index 000000000..b638474ee --- /dev/null +++ b/apps/audits/migrations/0015_auto_20221111_1919.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.14 on 2022-11-11 11:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0014_auto_20220505_1902'), + ] + + operations = [ + migrations.AlterField( + model_name='ftplog', + name='operate', + field=models.CharField(choices=[('mkdir', 'Mkdir'), ('rmdir', 'Rmdir'), ('delete', 'Delete'), ('upload', 'Upload'), ('rename', 'Rename'), ('symlink', 'Symlink'), ('download', 'Download')], max_length=16, verbose_name='Operate'), + ), + migrations.AlterField( + model_name='operatelog', + name='action', + field=models.CharField(choices=[('view', 'View'), ('update', 'Update'), ('delete', 'Delete'), ('create', 'Create')], max_length=16, verbose_name='Action'), + ), + migrations.AlterField( + model_name='userloginlog', + name='status', + field=models.BooleanField(choices=[(1, 'Success'), (0, 'Failed')], default=1, verbose_name='Status'), + ), + ] diff --git a/apps/ops/ansible/utils.py b/apps/ops/ansible/utils.py index 478badc56..daeb98b14 100644 --- a/apps/ops/ansible/utils.py +++ b/apps/ops/ansible/utils.py @@ -3,4 +3,4 @@ from django.conf import settings def get_ansible_task_log_path(task_id): from ops.utils import get_task_log_path - return get_task_log_path(settings.ANSIBLE_LOG_DIR, task_id, level=3) + return get_task_log_path(settings.CELERY_LOG_DIR, task_id, level=2) diff --git a/apps/ops/api/__init__.py b/apps/ops/api/__init__.py index 8eb5356e4..e82f3fb2e 100644 --- a/apps/ops/api/__init__.py +++ b/apps/ops/api/__init__.py @@ -2,3 +2,5 @@ # from .adhoc import * from .celery import * +from .job import * +from .playbook import * diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 71e818fbb..ce7e8ad57 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -1,52 +1,22 @@ # -*- coding: utf-8 -*- # -from django.shortcuts import get_object_or_404 -from rest_framework import viewsets, generics -from rest_framework.views import Response - -from common.drf.serializers import CeleryTaskExecutionSerializer -from ..models import AdHoc, AdHocExecution +from rest_framework import viewsets +from ..models import AdHoc from ..serializers import ( - AdHocSerializer, - AdHocExecutionSerializer, - AdHocDetailSerializer, + AdHocSerializer, AdhocListSerializer, ) __all__ = [ - 'AdHocViewSet', 'AdHocExecutionViewSet' + 'AdHocViewSet' ] class AdHocViewSet(viewsets.ModelViewSet): queryset = AdHoc.objects.all() - serializer_class = AdHocSerializer def get_serializer_class(self): - if self.action == 'retrieve': - return AdHocDetailSerializer - return super().get_serializer_class() - - -class AdHocExecutionViewSet(viewsets.ModelViewSet): - queryset = AdHocExecution.objects.all() - serializer_class = AdHocExecutionSerializer - - def get_queryset(self): - task_id = self.request.query_params.get('task') - adhoc_id = self.request.query_params.get('adhoc') - - if task_id: - task = get_object_or_404(AdHoc, id=task_id) - adhocs = task.adhoc.all() - self.queryset = self.queryset.filter(adhoc__in=adhocs) - - if adhoc_id: - adhoc = get_object_or_404(AdHoc, id=adhoc_id) - self.queryset = self.queryset.filter(adhoc=adhoc) - return self.queryset - - - - + if self.action != 'list': + return AdhocListSerializer + return AdHocSerializer diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 61d13db17..4376a9232 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -98,6 +98,11 @@ class CeleryPeriodTaskViewSet(CommonApiMixin, viewsets.ModelViewSet): return queryset +class CelerySummaryAPIView(generics.RetrieveAPIView): + def get(self, request, *args, **kwargs): + pass + + class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): queryset = CeleryTask.objects.all() serializer_class = CeleryTaskSerializer @@ -107,11 +112,11 @@ class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): class CeleryTaskExecutionViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): serializer_class = CeleryTaskExecutionSerializer http_method_names = ('get', 'head', 'options',) + queryset = CeleryTaskExecution.objects.all() def get_queryset(self): - task_id = self.kwargs.get("task_pk") + task_id = self.request.query_params.get('task_id') if task_id: - task = CeleryTask.objects.get(pk=task_id) - return CeleryTaskExecution.objects.filter(name=task.name) - else: - return CeleryTaskExecution.objects.none() + task = get_object_or_404(CeleryTask, id=task_id) + self.queryset = self.queryset.filter(name=task.name) + return self.queryset diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py new file mode 100644 index 000000000..eaad4514c --- /dev/null +++ b/apps/ops/api/job.py @@ -0,0 +1,41 @@ +from django.shortcuts import get_object_or_404 +from rest_framework import viewsets + +from ops.models import Job, JobExecution +from ops.serializers.job import JobSerializer, JobExecutionSerializer + +__all__ = ['JobViewSet', 'JobExecutionViewSet'] + +from ops.tasks import run_ops_job, run_ops_job_executions + + +class JobViewSet(viewsets.ModelViewSet): + serializer_class = JobSerializer + queryset = Job.objects.all() + + def get_queryset(self): + return self.queryset.filter(instant=False) + + def perform_create(self, serializer): + instance = serializer.save() + if instance.instant: + run_ops_job.delay(instance.id) + + +class JobExecutionViewSet(viewsets.ModelViewSet): + serializer_class = JobExecutionSerializer + queryset = JobExecution.objects.all() + http_method_names = ('get', 'post', 'head', 'options',) + + def perform_create(self, serializer): + instance = serializer.save() + run_ops_job_executions.delay(instance.id) + + def get_queryset(self): + job_id = self.request.query_params.get('job_id') + job_type = self.request.query_params.get('type') + if job_id: + self.queryset = self.queryset.filter(job_id=job_id) + if job_type: + self.queryset = self.queryset.filter(job__type=job_type) + return self.queryset diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py new file mode 100644 index 000000000..2cb846c0e --- /dev/null +++ b/apps/ops/api/playbook.py @@ -0,0 +1,28 @@ +import os +import zipfile + +from django.conf import settings +from rest_framework import viewsets +from ..models import Playbook +from ..serializers.playbook import PlaybookSerializer + +__all__ = ["PlaybookViewSet"] + + +def unzip_playbook(src, dist): + fz = zipfile.ZipFile(src, 'r') + for file in fz.namelist(): + fz.extract(file, dist) + + +class PlaybookViewSet(viewsets.ModelViewSet): + queryset = Playbook.objects.all() + serializer_class = PlaybookSerializer + + def perform_create(self, serializer): + instance = serializer.save() + src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name) + dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__()) + if os.path.exists(dest_path): + os.makedirs(dest_path) + unzip_playbook(src_path, dest_path) diff --git a/apps/ops/migrations/0029_auto_20221111_1919.py b/apps/ops/migrations/0029_auto_20221111_1919.py new file mode 100644 index 000000000..072e59867 --- /dev/null +++ b/apps/ops/migrations/0029_auto_20221111_1919.py @@ -0,0 +1,171 @@ +# Generated by Django 3.2.14 on 2022-11-11 11:19 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0111_alter_automationexecution_status'), + ('ops', '0028_celerytask_last_published_time'), + ] + + operations = [ + migrations.CreateModel( + name='Job', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, null=True, verbose_name='Name')), + ('instant', models.BooleanField(default=False)), + ('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')), + ('module', models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, null=True, verbose_name='Module')), + ('type', models.CharField(choices=[('adhoc', 'Adhoc'), ('playbook', 'Playbook')], default='adhoc', max_length=128, verbose_name='Type')), + ('runas', models.CharField(default='root', max_length=128, verbose_name='Runas')), + ('runas_policy', models.CharField(choices=[('privileged_only', 'Privileged Only'), ('privileged_first', 'Privileged First'), ('skip', 'Skip')], default='skip', max_length=128, verbose_name='Runas policy')), + ('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')), + ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='JobExecution', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('task_id', models.UUIDField(null=True)), + ('status', models.CharField(default='running', max_length=16, verbose_name='Status')), + ('result', models.JSONField(blank=True, null=True, verbose_name='Result')), + ('summary', models.JSONField(default=dict, verbose_name='Summary')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('job', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='ops.job')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterUniqueTogether( + name='playbooktemplate', + unique_together=None, + ), + migrations.RemoveField( + model_name='adhoc', + name='account', + ), + migrations.RemoveField( + model_name='adhoc', + name='account_policy', + ), + migrations.RemoveField( + model_name='adhoc', + name='assets', + ), + migrations.RemoveField( + model_name='adhoc', + name='crontab', + ), + migrations.RemoveField( + model_name='adhoc', + name='date_last_run', + ), + migrations.RemoveField( + model_name='adhoc', + name='interval', + ), + migrations.RemoveField( + model_name='adhoc', + name='is_periodic', + ), + migrations.RemoveField( + model_name='adhoc', + name='last_execution', + ), + migrations.RemoveField( + model_name='adhoc', + name='org_id', + ), + migrations.RemoveField( + model_name='playbook', + name='account', + ), + migrations.RemoveField( + model_name='playbook', + name='account_policy', + ), + migrations.RemoveField( + model_name='playbook', + name='assets', + ), + migrations.RemoveField( + model_name='playbook', + name='comment', + ), + migrations.RemoveField( + model_name='playbook', + name='crontab', + ), + migrations.RemoveField( + model_name='playbook', + name='date_last_run', + ), + migrations.RemoveField( + model_name='playbook', + name='interval', + ), + migrations.RemoveField( + model_name='playbook', + name='is_periodic', + ), + migrations.RemoveField( + model_name='playbook', + name='last_execution', + ), + migrations.RemoveField( + model_name='playbook', + name='org_id', + ), + migrations.RemoveField( + model_name='playbook', + name='template', + ), + migrations.AlterField( + model_name='adhoc', + name='module', + field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, verbose_name='Module'), + ), + migrations.AlterField( + model_name='playbook', + name='name', + field=models.CharField(max_length=128, null=True, verbose_name='Name'), + ), + migrations.AlterField( + model_name='playbook', + name='path', + field=models.FileField(upload_to='playbooks/'), + ), + migrations.DeleteModel( + name='PlaybookExecution', + ), + migrations.DeleteModel( + name='PlaybookTemplate', + ), + migrations.AddField( + model_name='job', + name='playbook', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbook', verbose_name='Playbook'), + ), + ] diff --git a/apps/ops/models/__init__.py b/apps/ops/models/__init__.py index 93b630dd6..b6edb768d 100644 --- a/apps/ops/models/__init__.py +++ b/apps/ops/models/__init__.py @@ -4,3 +4,4 @@ from .adhoc import * from .celery import * from .playbook import * +from .job import * diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 22fd0f054..e94223fb9 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -1,29 +1,43 @@ # ~*~ coding: utf-8 ~*~ import os.path +import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.db.models import BaseCreateUpdateModel from common.utils import get_logger from .base import BaseAnsibleJob, BaseAnsibleExecution from ..ansible import AdHocRunner __all__ = ["AdHoc", "AdHocExecution"] - logger = get_logger(__file__) -class AdHoc(BaseAnsibleJob): - pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all') - module = models.CharField(max_length=128, default='shell', verbose_name=_('Module')) - args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) - last_execution = models.ForeignKey('AdHocExecution', verbose_name=_("Last execution"), - on_delete=models.SET_NULL, null=True, blank=True) +class AdHoc(BaseCreateUpdateModel): + class Modules(models.TextChoices): + shell = 'shell', _('Shell') + winshell = 'win_shell', _('Powershell') - def get_register_task(self): - from ops.tasks import run_adhoc - return "run_adhoc_{}".format(self.id), run_adhoc, (str(self.id),), {} + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + pattern = models.CharField(max_length=1024, verbose_name=_("Pattern"), default='all') + module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, + verbose_name=_('Module')) + args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) + owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + + @property + def row_count(self): + if len(self.args) == 0: + return 0 + count = str(self.args).count('\n') + return count + 1 + + @property + def size(self): + return len(self.args) def __str__(self): return "{}: {}".format(self.module, self.args) diff --git a/apps/ops/models/base.py b/apps/ops/models/base.py index a37982673..3d6d1438d 100644 --- a/apps/ops/models/base.py +++ b/apps/ops/models/base.py @@ -17,7 +17,8 @@ class BaseAnsibleJob(PeriodTaskModelMixin, JMSOrgBaseModel): assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) account = models.CharField(max_length=128, default='root', verbose_name=_('Account')) account_policy = models.CharField(max_length=128, default='root', verbose_name=_('Account policy')) - last_execution = models.ForeignKey('BaseAnsibleExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True) + last_execution = models.ForeignKey('BaseAnsibleExecution', verbose_name=_("Last execution"), + on_delete=models.SET_NULL, null=True) date_last_run = models.DateTimeField(null=True, verbose_name=_('Date last run')) class Meta: @@ -118,12 +119,6 @@ class BaseAnsibleExecution(models.Model): def is_success(self): return self.status == 'success' - @property - def time_cost(self): - if self.date_finished and self.date_start: - return (self.date_finished - self.date_start).total_seconds() - return None - @property def short_id(self): return str(self.id).split('-')[-1] @@ -134,4 +129,8 @@ class BaseAnsibleExecution(models.Model): return self.date_finished - self.date_start return None - + @property + def time_cost(self): + if self.date_finished and self.date_start: + return (self.date_finished - self.date_start).total_seconds() + return None diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 55f05129f..01251f8b2 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -19,7 +19,7 @@ class CeleryTask(models.Model): def meta(self): task = app.tasks.get(self.name, None) return { - "verbose_name": getattr(task, 'verbose_name', None), + "display_name": getattr(task, 'verbose_name', None), "comment": getattr(task, 'comment', None), "queue": getattr(task, 'queue', 'default') } diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py new file mode 100644 index 000000000..9199ab902 --- /dev/null +++ b/apps/ops/models/job.py @@ -0,0 +1,154 @@ +import os +import uuid +import logging + +from django.conf import settings +from django.db import models +from django.utils.translation import gettext_lazy as _ +from django.utils import timezone +from celery import current_task + +from common.db.models import BaseCreateUpdateModel + +__all__ = ["Job", "JobExecution"] + +from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner + + +class Job(BaseCreateUpdateModel): + class Types(models.TextChoices): + adhoc = 'adhoc', _('Adhoc') + playbook = 'playbook', _('Playbook') + + class RunasPolicies(models.TextChoices): + privileged_only = 'privileged_only', _('Privileged Only') + privileged_first = 'privileged_first', _('Privileged First') + skip = 'skip', _('Skip') + + class Modules(models.TextChoices): + shell = 'shell', _('Shell') + winshell = 'win_shell', _('Powershell') + + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, null=True, verbose_name=_('Name')) + instant = models.BooleanField(default=False) + args = models.CharField(max_length=1024, default='', verbose_name=_('Args'), null=True, blank=True) + module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, + verbose_name=_('Module'), null=True) + playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL) + type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Type")) + owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + assets = models.ManyToManyField('assets.Asset', verbose_name=_("Assets")) + runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas')) + runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip, + verbose_name=_('Runas policy')) + + @property + def inventory(self): + return JMSInventory(self.assets.all(), self.runas_policy, self.runas) + + def create_execution(self): + return self.executions.create() + + +class JobExecution(BaseCreateUpdateModel): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + task_id = models.UUIDField(null=True) + status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') + job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='executions', null=True) + result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) + summary = models.JSONField(default=dict, verbose_name=_('Summary')) + creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) + date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) + + def get_runner(self): + inv = self.job.inventory + inv.write_to_file(self.inventory_path) + + if self.job.type == 'adhoc': + runner = AdHocRunner( + self.inventory_path, self.job.module, module_args=self.job.args, + pattern="all", project_dir=self.private_dir + ) + elif self.job.type == 'playbook': + runner = PlaybookRunner( + self.inventory_path, self.job.playbook.work_path + ) + else: + raise Exception("unsupported job type") + return runner + + @property + def short_id(self): + return str(self.id).split('-')[-1] + + @property + def timedelta(self): + if self.date_start and self.date_finished: + return self.date_finished - self.date_start + return None + + @property + def is_finished(self): + return self.status in ['success', 'failed'] + + @property + def is_success(self): + return self.status == 'success' + + @property + def time_cost(self): + if self.date_finished and self.date_start: + return (self.date_finished - self.date_start).total_seconds() + return None + + @property + def inventory_path(self): + return os.path.join(self.private_dir, 'inventory', 'hosts') + + @property + def private_dir(self): + uniq = self.date_created.strftime('%Y%m%d_%H%M%S') + '_' + self.short_id + job_name = self.job.name if self.job.name else 'instant' + return os.path.join(settings.ANSIBLE_DIR, job_name, uniq) + + def set_error(self, error): + this = self.__class__.objects.get(id=self.id) # 重新获取一次,避免数据库超时连接超时 + this.status = 'failed' + this.summary['error'] = str(error) + this.finish_task() + + def set_result(self, cb): + status_mapper = { + 'successful': 'success', + } + this = self.__class__.objects.get(id=self.id) + this.status = status_mapper.get(cb.status, cb.status) + this.summary = cb.summary + this.result = cb.result + this.finish_task() + + def finish_task(self): + self.date_finished = timezone.now() + self.save(update_fields=['result', 'status', 'summary', 'date_finished']) + + def set_celery_id(self): + if not current_task: + return + task_id = current_task.request.root_id + self.task_id = task_id + + def start(self, **kwargs): + self.date_start = timezone.now() + self.set_celery_id() + self.save() + runner = self.get_runner() + try: + cb = runner.run(**kwargs) + self.set_result(cb) + return cb + except Exception as e: + logging.error(e, exc_info=True) + self.set_error(e) diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index a0c11db3b..6e0155288 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -1,39 +1,19 @@ +import os.path +import uuid + +from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ -from orgs.mixins.models import JMSOrgBaseModel -from .base import BaseAnsibleExecution, BaseAnsibleJob +from common.db.models import BaseCreateUpdateModel -class PlaybookTemplate(JMSOrgBaseModel): - name = models.CharField(max_length=128, verbose_name=_("Name")) - path = models.FilePathField(verbose_name=_("Path")) - comment = models.TextField(verbose_name=_("Comment"), blank=True) - - def __str__(self): - return self.name - - class Meta: - ordering = ['name'] - verbose_name = _("Playbook template") - unique_together = [('org_id', 'name')] - - -class Playbook(BaseAnsibleJob): - path = models.FilePathField(max_length=1024, verbose_name=_("Playbook")) +class Playbook(BaseCreateUpdateModel): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name'), null=True) + path = models.FileField(upload_to='playbooks/') owner = models.ForeignKey('users.User', verbose_name=_("Owner"), on_delete=models.SET_NULL, null=True) - comment = models.TextField(blank=True, verbose_name=_("Comment")) - template = models.ForeignKey('PlaybookTemplate', verbose_name=_("Template"), on_delete=models.SET_NULL, null=True) - last_execution = models.ForeignKey('PlaybookExecution', verbose_name=_("Last execution"), on_delete=models.SET_NULL, null=True, blank=True) - def get_register_task(self): - name = "automation_strategy_period_{}".format(str(self.id)[:8]) - task = execute_automation_strategy.name - args = (str(self.id), Trigger.timing) - kwargs = {} - return name, task, args, kwargs - - -class PlaybookExecution(BaseAnsibleExecution): - task = models.ForeignKey('Playbook', verbose_name=_("Task"), on_delete=models.CASCADE) - path = models.FilePathField(max_length=1024, verbose_name=_("Run dir")) + @property + def work_path(self): + return os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__()) diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 5df047bfa..2120c8c50 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -1,11 +1,33 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from rest_framework import serializers -from django.shortcuts import reverse +import datetime + +from rest_framework import serializers + +from common.drf.fields import ReadableHiddenField from ..models import AdHoc, AdHocExecution +class AdHocSerializer(serializers.ModelSerializer): + owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) + row_count = serializers.IntegerField(read_only=True) + size = serializers.IntegerField(read_only=True) + + class Meta: + model = AdHoc + fields = ["id", "name", "module", "owner", "row_count", "size", "date_created", "date_updated"] + + +class AdhocListSerializer(AdHocSerializer): + row_count = serializers.IntegerField(read_only=True) + size = serializers.IntegerField(read_only=True) + + class Meta: + model = AdHoc + fields = ["id", "name", "module", "row_count", "size", "args", "owner", "date_created", "date_updated"] + + class AdHocExecutionSerializer(serializers.ModelSerializer): stat = serializers.SerializerMethodField() last_success = serializers.ListField(source='success_hosts') @@ -49,26 +71,6 @@ class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer): ] -class AdHocSerializer(serializers.ModelSerializer): - tasks = serializers.ListField() - - class Meta: - model = AdHoc - fields_mini = ['id'] - fields_small = fields_mini + [ - 'tasks', "pattern", "args", "date_created", - ] - fields_fk = ["last_execution"] - fields_m2m = ["assets"] - fields = fields_small + fields_fk + fields_m2m - read_only_fields = [ - 'date_created' - ] - extra_kwargs = { - "become": {'write_only': True} - } - - class AdHocExecutionNestSerializer(serializers.ModelSerializer): last_success = serializers.ListField(source='success_hosts') last_failure = serializers.DictField(source='failed_hosts') @@ -80,38 +82,3 @@ class AdHocExecutionNestSerializer(serializers.ModelSerializer): 'last_success', 'last_failure', 'last_run', 'timedelta', 'is_finished', 'is_success' ) - - -class AdHocDetailSerializer(AdHocSerializer): - latest_execution = AdHocExecutionNestSerializer(allow_null=True) - task_name = serializers.CharField(source='task.name') - - class Meta(AdHocSerializer.Meta): - fields = AdHocSerializer.Meta.fields + [ - 'latest_execution', 'created_by', 'task_name' - ] - - -# class CommandExecutionSerializer(serializers.ModelSerializer): -# result = serializers.JSONField(read_only=True) -# log_url = serializers.SerializerMethodField() -# -# class Meta: -# model = CommandExecution -# fields_mini = ['id'] -# fields_small = fields_mini + [ -# 'command', 'result', 'log_url', -# 'is_finished', 'date_created', 'date_finished' -# ] -# fields_m2m = ['hosts'] -# fields = fields_small + fields_m2m -# read_only_fields = [ -# 'result', 'is_finished', 'log_url', 'date_created', -# 'date_finished' -# ] -# ref_name = 'OpsCommandExecution' -# -# @staticmethod -# def get_log_url(obj): -# return reverse('api-ops:celery-task-log', kwargs={'pk': obj.id}) - diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py new file mode 100644 index 000000000..64f26dd81 --- /dev/null +++ b/apps/ops/serializers/job.py @@ -0,0 +1,29 @@ +from django.db import transaction +from rest_framework import serializers + +from common.drf.fields import ReadableHiddenField +from ops.models import Job, JobExecution + +_all_ = [] + + +class JobSerializer(serializers.ModelSerializer): + owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) + + class Meta: + model = Job + fields = [ + "id", "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", + "date_created", + "date_updated" + ] + + +class JobExecutionSerializer(serializers.ModelSerializer): + class Meta: + model = JobExecution + read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created', + 'is_success', 'task_id', 'short_id'] + fields = read_only_fields + [ + "job" + ] diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py new file mode 100644 index 000000000..7ca165501 --- /dev/null +++ b/apps/ops/serializers/playbook.py @@ -0,0 +1,28 @@ +import os + +from rest_framework import serializers + +from common.drf.fields import ReadableHiddenField +from ops.models import Playbook + + +def parse_playbook_name(path): + file_name = os.path.split(path)[-1] + return file_name.split(".")[-2] + + +class PlaybookSerializer(serializers.ModelSerializer): + owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) + + def create(self, validated_data): + name = validated_data.get('name') + if not name: + path = validated_data.get('path').name + validated_data['name'] = parse_playbook_name(path) + return super().create(validated_data) + + class Meta: + model = Playbook + fields = [ + "id", "name", "path", "date_created", "owner", "date_updated" + ] diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 97868a884..e802970c7 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -2,6 +2,7 @@ import os import random import subprocess +import time from django.conf import settings from celery import shared_task, subtask @@ -21,7 +22,7 @@ from .celery.utils import ( create_or_update_celery_periodic_tasks, get_celery_periodic_task, disable_celery_periodic_task, delete_celery_periodic_task ) -from .models import CeleryTaskExecution, AdHoc, Playbook +from .models import CeleryTaskExecution, Playbook, Job, JobExecution from .notifications import ServerPerformanceCheckUtil logger = get_logger(__file__) @@ -31,6 +32,33 @@ def rerun_task(): pass +@shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) +def run_ops_job(job_id, **kwargs): + job = get_object_or_none(Job, id=job_id) + execution = job.create_execution() + try: + execution.start() + except SoftTimeLimitExceeded: + execution.set_error('Run timeout') + logger.error("Run adhoc timeout") + except Exception as e: + execution.set_error(e) + logger.error("Start adhoc execution error: {}".format(e)) + + +@shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task execution")) +def run_ops_job_executions(execution_id, **kwargs): + execution = get_object_or_none(JobExecution, id=execution_id) + try: + execution.start() + except SoftTimeLimitExceeded: + execution.set_error('Run timeout') + logger.error("Run adhoc timeout") + except Exception as e: + execution.set_error(e) + logger.error("Start adhoc execution error: {}".format(e)) + + @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) def run_adhoc(tid, **kwargs): """ @@ -156,18 +184,23 @@ def hello(name, callback=None): return gettext("Hello") -@shared_task(verbose_name="Hello Error", comment="an test shared task error") +@shared_task(verbose_name=_("Hello Error"), comment="an test shared task error") def hello_error(): raise Exception("must be error") -@shared_task(verbose_name="Hello Random", comment="some time error and some time success") +@shared_task(verbose_name=_("Hello Random"), comment="some time error and some time success") def hello_random(): i = random.randint(0, 1) if i == 1: raise Exception("must be error") +@shared_task(verbose_name="Hello Running", comment="an task running 1m") +def hello_running(sec=60): + time.sleep(sec) + + @shared_task def hello_callback(result): print(result) diff --git a/apps/ops/urls/api_urls.py b/apps/ops/urls/api_urls.py index 1edbd16e7..a8b71734f 100644 --- a/apps/ops/urls/api_urls.py +++ b/apps/ops/urls/api_urls.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals from django.urls import path from rest_framework.routers import DefaultRouter from rest_framework_bulk.routes import BulkRouter -from rest_framework_nested import routers from .. import api @@ -13,23 +12,25 @@ app_name = "ops" router = DefaultRouter() bulk_router = BulkRouter() -router.register(r'adhoc', api.AdHocViewSet, 'adhoc') -router.register(r'adhoc-executions', api.AdHocExecutionViewSet, 'execution') +router.register(r'adhocs', api.AdHocViewSet, 'adhoc') +router.register(r'playbooks', api.PlaybookViewSet, 'playbook') +router.register(r'jobs', api.JobViewSet, 'job') +router.register(r'job-executions', api.JobExecutionViewSet, 'job-execution') + router.register(r'celery/period-tasks', api.CeleryPeriodTaskViewSet, 'celery-period-task') router.register(r'tasks', api.CeleryTaskViewSet, 'task') - -task_router = routers.NestedDefaultRouter(router, r'tasks', lookup='task') -task_router.register(r'executions', api.CeleryTaskExecutionViewSet, 'task-execution') +router.register(r'task-executions', api.CeleryTaskExecutionViewSet, 'task-executions') urlpatterns = [ + path('ansible/job-execution//log/', api.AnsibleTaskLogApi.as_view(), name='job-execution-log'), + path('celery/task//task-execution//log/', api.CeleryTaskExecutionLogApi.as_view(), name='celery-task-execution-log'), path('celery/task//task-execution//result/', api.CeleryResultApi.as_view(), name='celery-task-execution-result'), - path('ansible/task-execution//log/', api.AnsibleTaskLogApi.as_view(), name='ansible-task-log'), ] -urlpatterns += (router.urls + bulk_router.urls + task_router.urls) +urlpatterns += (router.urls + bulk_router.urls) diff --git a/apps/perms/migrations/0032_auto_20221111_1919.py b/apps/perms/migrations/0032_auto_20221111_1919.py new file mode 100644 index 000000000..3f3c56533 --- /dev/null +++ b/apps/perms/migrations/0032_auto_20221111_1919.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.14 on 2022-11-11 11:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0111_alter_automationexecution_status'), + ('perms', '0031_auto_20220816_1600'), + ] + + operations = [ + migrations.CreateModel( + name='PermedAccount', + fields=[ + ], + options={ + 'verbose_name': 'Permed account', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('assets.account',), + ), + migrations.AlterField( + model_name='assetpermission', + name='actions', + field=models.IntegerField(default=0, verbose_name='Actions'), + ), + ] diff --git a/apps/tickets/migrations/0022_alter_applyassetticket_apply_actions.py b/apps/tickets/migrations/0022_alter_applyassetticket_apply_actions.py new file mode 100644 index 000000000..96f645e0d --- /dev/null +++ b/apps/tickets/migrations/0022_alter_applyassetticket_apply_actions.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-11 11:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0021_auto_20220921_1814'), + ] + + operations = [ + migrations.AlterField( + model_name='applyassetticket', + name='apply_actions', + field=models.IntegerField(default=1, verbose_name='Actions'), + ), + ] From 4f135bc3494ab043e1f9c617613c9b701d250464 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 14 Nov 2022 14:03:58 +0800 Subject: [PATCH 329/488] =?UTF-8?q?pref:=20=E6=B7=BB=E5=8A=A0=20perm=20tok?= =?UTF-8?q?en?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 16 ++++++++-------- apps/perms/models/__init__.py | 3 ++- .../models/{permed_node.py => perm_node.py} | 0 apps/perms/models/perm_token.py | 14 ++++++++++++++ 4 files changed, 24 insertions(+), 9 deletions(-) rename apps/perms/models/{permed_node.py => perm_node.py} (100%) create mode 100644 apps/perms/models/perm_token.py diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 0839229b8..c214f93ab 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -1,31 +1,31 @@ -import os -import abc -import json -import time import base64 +import json +import os +import time import urllib.parse + from django.http import HttpResponse from django.shortcuts import get_object_or_404 -from rest_framework.request import Request from rest_framework import status -from rest_framework.exceptions import PermissionDenied from rest_framework.decorators import action -from rest_framework.response import Response +from rest_framework.exceptions import PermissionDenied from rest_framework.request import Request +from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.http import is_true from orgs.mixins.api import RootOrgViewMixin from perms.models import ActionChoices from terminal.models import EndpointRule +from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, SuperConnectionTokenSerializer, ConnectionTokenDisplaySerializer, ) -from ..models import ConnectionToken __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] + # ExtraActionApiMixin diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py index ee7787f7f..9041990f2 100644 --- a/apps/perms/models/__init__.py +++ b/apps/perms/models/__init__.py @@ -1,5 +1,6 @@ # coding: utf-8 # -from .permed_node import * from .asset_permission import * +from .perm_node import * +from .perm_token import * diff --git a/apps/perms/models/permed_node.py b/apps/perms/models/perm_node.py similarity index 100% rename from apps/perms/models/permed_node.py rename to apps/perms/models/perm_node.py diff --git a/apps/perms/models/perm_token.py b/apps/perms/models/perm_token.py new file mode 100644 index 000000000..857cb9dd7 --- /dev/null +++ b/apps/perms/models/perm_token.py @@ -0,0 +1,14 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class PermToken(models.Model): + user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User')) + asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) + account = models.CharField(max_length=128, verbose_name=_('Account')) + secret = models.CharField(max_length=1024, verbose_name=_('Secret')) + protocol = models.CharField(max_length=32, verbose_name=_('Protocol')) + connect_method = models.CharField(max_length=32, verbose_name=_('Connect method')) + + class Meta: + abstract = True From 8e1312e8ce13dbd04cb6cd8c7536fd18f4de0ca5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 14 Nov 2022 14:44:18 +0800 Subject: [PATCH 330/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20perm=20tok?= =?UTF-8?q?en?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 3 --- .../serializers/connection_token.py | 23 +++++++++++++------ apps/perms/const.py | 5 ++++ apps/perms/models/perm_token.py | 7 ++++++ apps/perms/utils/account.py | 6 +++-- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index c214f93ab..b531c6560 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -282,9 +282,6 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView raise PermissionDenied(error) -# SuperConnectionToken - - class SuperConnectionTokenViewSet(ConnectionTokenViewSet): serializer_classes = { 'default': SuperConnectionTokenSerializer, diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 256661882..5011d73a6 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,14 +1,13 @@ +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account, Platform from authentication.models import ConnectionToken from common.utils import pretty_string from common.utils.random import random_string -from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account -from users.models import User +from orgs.mixins.serializers import OrgResourceModelSerializerMixin from perms.serializers.permission import ActionChoicesField - +from users.models import User __all__ = [ 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer', @@ -86,7 +85,6 @@ class ConnectionTokenDisplaySerializer(ConnectionTokenSerializer): class SuperConnectionTokenSerializer(ConnectionTokenSerializer): - class Meta(ConnectionTokenSerializer.Meta): read_only_fields = [ 'validity', 'user_display', 'system_user_display', @@ -104,6 +102,7 @@ class SuperConnectionTokenSerializer(ConnectionTokenSerializer): class ConnectionTokenUserSerializer(serializers.ModelSerializer): """ User """ + class Meta: model = User fields = ['id', 'name', 'username', 'email'] @@ -111,6 +110,7 @@ class ConnectionTokenUserSerializer(serializers.ModelSerializer): class ConnectionTokenAssetSerializer(serializers.ModelSerializer): """ Asset """ + class Meta: model = Asset fields = ['id', 'name', 'address', 'protocols', 'org_id'] @@ -118,6 +118,7 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer): class ConnectionTokenAccountSerializer(serializers.ModelSerializer): """ Account """ + class Meta: model = Account fields = [ @@ -127,6 +128,7 @@ class ConnectionTokenAccountSerializer(serializers.ModelSerializer): class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): """ Gateway """ + class Meta: model = Gateway fields = ['id', 'ip', 'port', 'username', 'password', 'private_key'] @@ -143,6 +145,7 @@ class ConnectionTokenDomainSerializer(serializers.ModelSerializer): class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): """ Command filter rule """ + class Meta: model = CommandFilterRule fields = [ @@ -151,12 +154,18 @@ class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): ] +class ConnectionTokenPlatform(serializers.ModelSerializer): + class Meta: + model = Platform + fields = ['id', 'name', 'org_id'] + + class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) + platform = ConnectionTokenPlatform(read_only=True) account = ConnectionTokenAccountSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) - domain = ConnectionTokenDomainSerializer(read_only=True) cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() diff --git a/apps/perms/const.py b/apps/perms/const.py index b78d6b48b..769ce70eb 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -27,6 +27,11 @@ class ActionChoices(BitChoices): (_("Clipboard"), [cls.copy, cls.paste]), ) + @classmethod + def has_perm(cls, action_name, total): + action_value = getattr(cls, action_name) + return action_value & total == action_value + class SpecialAccount(models.TextChoices): ALL = "@ALL", "All" diff --git a/apps/perms/models/perm_token.py b/apps/perms/models/perm_token.py index 857cb9dd7..368750c63 100644 --- a/apps/perms/models/perm_token.py +++ b/apps/perms/models/perm_token.py @@ -3,12 +3,19 @@ from django.utils.translation import gettext_lazy as _ class PermToken(models.Model): + """ + 1. 用完失效 + 2. 仅用于授权,不用于认证 + 3. 存 redis 就行 + 4. 有效期 5 分钟 + """ user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name=_('User')) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) account = models.CharField(max_length=128, verbose_name=_('Account')) secret = models.CharField(max_length=1024, verbose_name=_('Secret')) protocol = models.CharField(max_length=32, verbose_name=_('Protocol')) connect_method = models.CharField(max_length=32, verbose_name=_('Connect method')) + actions = models.IntegerField(verbose_name=_('Actions')) class Meta: abstract = True diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 167a3060b..baaedb8fc 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -1,4 +1,3 @@ -import time from collections import defaultdict from assets.models import Account @@ -9,6 +8,7 @@ __all__ = ['PermAccountUtil'] class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ + @staticmethod def get_permed_accounts_from_perms(perms, user, asset): alias_action_bit_mapper = defaultdict(int) @@ -75,7 +75,9 @@ class PermAccountUtil(AssetPermissionUtil): return accounts def validate_permission(self, user, asset, account_username): - """ 校验用户有某个资产下某个账号名的权限 """ + """ 校验用户有某个资产下某个账号名的权限 + :param account_username: 可能是 @USER @INPUT 的 + """ permed_accounts = self.get_permed_accounts_for_user(user, asset) accounts_mapper = {account.username: account for account in permed_accounts} From d554e92d022e5807db01139c51f0d9d04bdaba39 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 14 Nov 2022 18:48:21 +0800 Subject: [PATCH 331/488] perf: add applets deployment --- apps/terminal/api/applet/host.py | 20 +- .../deploy_applet_host/__init__.py | 99 +++++-- .../deploy_applet_host/install_all.yml | 8 + .../deploy_applet_host/install_applet.yml | 11 + .../deploy_applet_host/playbook.yml | 265 +++++++++--------- apps/terminal/models/applet/host.py | 10 + apps/terminal/serializers/applet_host.py | 26 +- apps/terminal/tasks.py | 20 +- 8 files changed, 278 insertions(+), 181 deletions(-) create mode 100644 apps/terminal/automations/deploy_applet_host/install_all.yml create mode 100644 apps/terminal/automations/deploy_applet_host/install_applet.yml diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index e5fb1c754..72321cc37 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -2,16 +2,15 @@ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response -from common.permissions import IsServiceAccount from common.drf.api import JMSModelViewSet +from common.permissions import IsServiceAccount from orgs.utils import tmp_to_builtin_org +from terminal.models import AppletHost, AppletHostDeployment from terminal.serializers import ( AppletHostSerializer, AppletHostDeploymentSerializer, - AppletHostStartupSerializer + AppletHostStartupSerializer, AppletHostDeployAppletSerializer ) -from terminal.models import AppletHost, AppletHostDeployment -from terminal.tasks import run_applet_host_deployment - +from terminal.tasks import run_applet_host_deployment, run_applet_host_deployment_install_applet __all__ = ['AppletHostViewSet', 'AppletHostDeploymentViewSet'] @@ -41,6 +40,9 @@ class AppletHostViewSet(JMSModelViewSet): class AppletHostDeploymentViewSet(viewsets.ModelViewSet): serializer_class = AppletHostDeploymentSerializer queryset = AppletHostDeployment.objects.all() + rbac_perms = ( + ('applets', 'terminal.view_AppletHostDeployment'), + ) def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) @@ -49,3 +51,11 @@ class AppletHostDeploymentViewSet(viewsets.ModelViewSet): task = run_applet_host_deployment.delay(instance.id) return Response({'task': str(task.id)}, status=201) + @action(methods=['post'], detail=False, serializer_class=AppletHostDeployAppletSerializer) + def applets(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + applet_id = serializer.validated_data.get('applet_id') + instance = serializer.save() + task = run_applet_host_deployment_install_applet.delay(instance.id, applet_id) + return Response({'task': str(task.id)}, status=201) diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py index 2946a6410..be5847aa7 100644 --- a/apps/terminal/automations/deploy_applet_host/__init__.py +++ b/apps/terminal/automations/deploy_applet_host/__init__.py @@ -1,22 +1,23 @@ -import os import datetime -import shutil +import os import yaml -from django.utils import timezone from django.conf import settings +from django.utils import timezone -from common.utils import get_logger from common.db.utils import safe_db_connection +from common.utils import get_logger from ops.ansible import PlaybookRunner, JMSInventory +from terminal.models import Applet, AppletHostDeployment logger = get_logger(__name__) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) class DeployAppletHostManager: - def __init__(self, deployment): + def __init__(self, deployment: AppletHostDeployment, applet: Applet = None): self.deployment = deployment + self.applet = applet self.run_dir = self.get_run_dir() @staticmethod @@ -25,29 +26,56 @@ class DeployAppletHostManager: now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") return os.path.join(base, now) - def generate_playbook(self): - playbook_src = os.path.join(CURRENT_DIR, "playbook.yml") - base_site_url = settings.BASE_SITE_URL + def run(self, **kwargs): + self._run(self._run_initial_deploy, **kwargs) + + def install_applet(self, **kwargs): + self._run(self._run_install_applet, **kwargs) + + def _run_initial_deploy(self, **kwargs): + playbook = self.generate_initial_playbook + return self._run_playbook(playbook, **kwargs) + + def _run_install_applet(self, **kwargs): + if self.applet: + generate_playbook = self.generate_install_applet_playbook + else: + generate_playbook = self.generate_install_all_playbook + return self._run_playbook(generate_playbook, **kwargs) + + def generate_initial_playbook(self): + site_url = settings.SITE_URL bootstrap_token = settings.BOOTSTRAP_TOKEN host_id = str(self.deployment.host.id) - if not base_site_url: - base_site_url = "http://localhost:8080" - with open(playbook_src) as f: - plays = yaml.safe_load(f) - for play in plays: - play["vars"].update(self.deployment.host.deploy_options) - play["vars"]["DownloadHost"] = base_site_url + "/download" - play["vars"]["CORE_HOST"] = base_site_url - play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token - play["vars"]["HOST_ID"] = host_id - play["vars"]["HOST_NAME"] = self.deployment.host.name + if not site_url: + site_url = "http://localhost:8080" + options = self.deployment.host.deploy_options - playbook_dir = os.path.join(self.run_dir, "playbook") - playbook_dst = os.path.join(playbook_dir, "main.yml") - os.makedirs(playbook_dir, exist_ok=True) - with open(playbook_dst, "w") as f: - yaml.safe_dump(plays, f) - return playbook_dst + def handler(plays): + for play in plays: + play["vars"].update(options) + play["vars"]["DownloadHost"] = site_url + "/download" + play["vars"]["CORE_HOST"] = site_url + play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token + play["vars"]["HOST_ID"] = host_id + play["vars"]["HOST_NAME"] = self.deployment.host.name + + return self._generate_playbook("playbook.yml", handler) + + def generate_install_all_playbook(self): + return self._generate_playbook("install_all.yml") + + def generate_install_applet_playbook(self): + applet_name = self.applet.name + options = self.deployment.host.deploy_options + + def handler(plays): + for play in plays: + play["vars"].update(options) + play["vars"]["applet_name"] = applet_name + return plays + + return self._generate_playbook("install_applet.yml", handler) def generate_inventory(self): inventory = JMSInventory( @@ -58,18 +86,31 @@ class DeployAppletHostManager: inventory.write_to_file(inventory_path) return inventory_path - def _run(self, **kwargs): + def _generate_playbook(self, playbook_template_name, plays_handler: callable = None): + playbook_src = os.path.join(CURRENT_DIR, playbook_template_name) + with open(playbook_src) as f: + plays = yaml.safe_load(f) + if plays_handler: + plays = plays_handler(plays) + playbook_dir = os.path.join(self.run_dir, "playbook") + playbook_dst = os.path.join(playbook_dir, "main.yml") + os.makedirs(playbook_dir, exist_ok=True) + with open(playbook_dst, "w") as f: + yaml.safe_dump(plays, f) + return playbook_dst + + def _run_playbook(self, generate_playbook: callable, **kwargs): inventory = self.generate_inventory() - playbook = self.generate_playbook() + playbook = generate_playbook() runner = PlaybookRunner( inventory=inventory, playbook=playbook, project_dir=self.run_dir ) return runner.run(**kwargs) - def run(self, **kwargs): + def _run(self, cb_func: callable, **kwargs): try: self.deployment.date_start = timezone.now() - cb = self._run(**kwargs) + cb = cb_func(**kwargs) self.deployment.status = cb.status except Exception as e: logger.error("Error: {}".format(e)) diff --git a/apps/terminal/automations/deploy_applet_host/install_all.yml b/apps/terminal/automations/deploy_applet_host/install_all.yml new file mode 100644 index 000000000..bf3da06b4 --- /dev/null +++ b/apps/terminal/automations/deploy_applet_host/install_all.yml @@ -0,0 +1,8 @@ +--- + +- hosts: all + + tasks: + - name: Install all applets + ansible.windows.win_shell: + "tinkerd install all" diff --git a/apps/terminal/automations/deploy_applet_host/install_applet.yml b/apps/terminal/automations/deploy_applet_host/install_applet.yml new file mode 100644 index 000000000..5c216773f --- /dev/null +++ b/apps/terminal/automations/deploy_applet_host/install_applet.yml @@ -0,0 +1,11 @@ +--- + +- hosts: all + vars: + applet_name: chrome + + tasks: + - name: Install applet + ansible.windows.win_shell: + "tinkerd install --name {{ applet_name }}" + when: applet_name != 'all' diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 4f652fcbf..3fec8999c 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -3,7 +3,6 @@ - hosts: all vars: DownloadHost: https://demo.jumpserver.org/download - Initial: 0 HOST_NAME: test HOST_ID: 00000000-0000-0000-0000-000000000000 CORE_HOST: https://demo.jumpserver.org @@ -17,166 +16,166 @@ TinkerInstaller: Tinker_Installer_v0.0.1.exe tasks: - - name: Install RDS-Licensing (RDS) - ansible.windows.win_feature: - name: RDS-Licensing - state: present - include_management_tools: yes - when: RDS_Licensing + - name: Install RDS-Licensing (RDS) + ansible.windows.win_feature: + name: RDS-Licensing + state: present + include_management_tools: yes + when: RDS_Licensing - - name: Install RDS-RD-Server (RDS) - ansible.windows.win_feature: - name: RDS-RD-Server - state: present - include_management_tools: yes - register: rds_install + - name: Install RDS-RD-Server (RDS) + ansible.windows.win_feature: + name: RDS-RD-Server + state: present + include_management_tools: yes + register: rds_install - - name: Download JumpServer Tinker installer (jumpserver) - ansible.windows.win_get_url: + - name: Download JumpServer Tinker installer (jumpserver) + ansible.windows.win_get_url: url: "{{ DownloadHost }}/{{ TinkerInstaller }}" dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" - - name: Install JumpServer Tinker (jumpserver) - ansible.windows.win_package: - path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" - arguments: - - /VERYSILENT - - /SUPPRESSMSGBOXES - - /NORESTART - state: present + - name: Install JumpServer Tinker (jumpserver) + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" + arguments: + - /VERYSILENT + - /SUPPRESSMSGBOXES + - /NORESTART + state: present - - name: Set remote-server on the global system path (remote-server) - ansible.windows.win_path: - elements: - - '%USERPROFILE%\AppData\Local\Programs\Tinker\' - scope: user + - name: Set remote-server on the global system path (remote-server) + ansible.windows.win_path: + elements: + - '%USERPROFILE%\AppData\Local\Programs\Tinker\' + scope: user - - name: Download python-3.10.8 - ansible.windows.win_get_url: - url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" - dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" + - name: Download python-3.10.8 + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" + dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" - - name: Install the python-3.10.8 - ansible.windows.win_package: - path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" - product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}' - arguments: - - /quiet - - InstallAllUsers=1 - - PrependPath=1 - - Include_test=0 - - Include_launcher=0 - state: present - register: win_install_python + - name: Install the python-3.10.8 + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" + product_id: '{371d0d73-d418-4ffe-b280-58c3e7987525}' + arguments: + - /quiet + - InstallAllUsers=1 + - PrependPath=1 + - Include_test=0 + - Include_launcher=0 + state: present + register: win_install_python - - name: Reboot if installing requires it - ansible.windows.win_reboot: - post_reboot_delay: 10 - test_command: whoami - when: rds_install.reboot_required or win_install_python.reboot_required + - name: Reboot if installing requires it + ansible.windows.win_reboot: + post_reboot_delay: 10 + test_command: whoami + when: rds_install.reboot_required or win_install_python.reboot_required - - name: Set RDS LicenseServer (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: LicenseServers - data: "{{ RDS_LicenseServer }}" - type: string + - name: Set RDS LicenseServer (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: LicenseServers + data: "{{ RDS_LicenseServer }}" + type: string - - name: Set RDS LicensingMode (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: LicensingMode - data: "{{ RDS_LicensingMode }}" - type: dword + - name: Set RDS LicensingMode (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: LicensingMode + data: "{{ RDS_LicensingMode }}" + type: dword - - name: Set RDS fSingleSessionPerUser (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: fSingleSessionPerUser - data: "{{ RDS_fSingleSessionPerUser }}" - type: dword + - name: Set RDS fSingleSessionPerUser (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: fSingleSessionPerUser + data: "{{ RDS_fSingleSessionPerUser }}" + type: dword - - name: Set RDS MaxDisconnectionTime (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: MaxDisconnectionTime - data: "{{ RDS_MaxDisconnectionTime }}" - type: dword - when: RDS_MaxDisconnectionTime >= 60000 + - name: Set RDS MaxDisconnectionTime (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: MaxDisconnectionTime + data: "{{ RDS_MaxDisconnectionTime }}" + type: dword + when: RDS_MaxDisconnectionTime >= 60000 - - name: Set RDS RemoteAppLogoffTimeLimit (regedit) - ansible.windows.win_regedit: - path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services - name: RemoteAppLogoffTimeLimit - data: "{{ RDS_RemoteAppLogoffTimeLimit }}" - type: dword + - name: Set RDS RemoteAppLogoffTimeLimit (regedit) + ansible.windows.win_regedit: + path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services + name: RemoteAppLogoffTimeLimit + data: "{{ RDS_RemoteAppLogoffTimeLimit }}" + type: dword - - name: Download pip packages - ansible.windows.win_get_url: - url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip" - dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" + - name: Download pip packages + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip" + dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" - - name: Unzip pip_packages - community.windows.win_unzip: - src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" - dest: "{{ ansible_env.TEMP }}" + - name: Unzip pip_packages + community.windows.win_unzip: + src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" + dest: "{{ ansible_env.TEMP }}" - - name: Install python requirements offline - ansible.windows.win_shell: > + - name: Install python requirements offline + ansible.windows.win_shell: > pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt' --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1' - - name: Download chromedriver (chrome) - ansible.windows.win_get_url: - url: "{{ DownloadHost }}/chromedriver_win32.107.zip" - dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" + - name: Download chromedriver (chrome) + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/chromedriver_win32.107.zip" + dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" - - name: Unzip chromedriver (chrome) - community.windows.win_unzip: - src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" - dest: C:\Program Files\JumpServer\drivers + - name: Unzip chromedriver (chrome) + community.windows.win_unzip: + src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" + dest: C:\Program Files\JumpServer\drivers - - name: Set chromedriver on the global system path (chrome) - ansible.windows.win_path: - elements: - - 'C:\Program Files\JumpServer\drivers' + - name: Set chromedriver on the global system path (chrome) + ansible.windows.win_path: + elements: + - 'C:\Program Files\JumpServer\drivers' - - name: Download chrome msi package (chrome) - ansible.windows.win_get_url: - url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi" - dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi" + - name: Download chrome msi package (chrome) + ansible.windows.win_get_url: + url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi" + dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi" - - name: Install chrome (chrome) - ansible.windows.win_package: - path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi" - state: present - arguments: - - /quiet + - name: Install chrome (chrome) + ansible.windows.win_package: + path: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi" + state: present + arguments: + - /quiet - - name: Generate tinkerd component config - ansible.windows.win_shell: - "tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} + - name: Generate tinkerd component config + ansible.windows.win_shell: + "tinkerd config --hostname {{ HOST_NAME }} --core_host {{ CORE_HOST }} --token {{ BOOTSTRAP_TOKEN }} --host_id {{ HOST_ID }}" - - name: Install tinkerd service - ansible.windows.win_shell: - "tinkerd service install" + - name: Install tinkerd service + ansible.windows.win_shell: + "tinkerd service install" - - name: Start tinkerd service - ansible.windows.win_shell: - "tinkerd service start" + - name: Start tinkerd service + ansible.windows.win_shell: + "tinkerd service start" - - name: Wait Tinker api health - ansible.windows.win_uri: - url: http://localhost:6068/api/health/ - status_code: 200 - method: GET - register: _result - until: _result.status_code == 200 - retries: 30 - delay: 5 + - name: Wait Tinker api health + ansible.windows.win_uri: + url: http://localhost:6068/api/health/ + status_code: 200 + method: GET + register: _result + until: _result.status_code == 200 + retries: 30 + delay: 5 - - name: Sync all remote applets - ansible.windows.win_shell: - "tinkerd install all" + - name: Sync all remote applets + ansible.windows.win_shell: + "tinkerd install all" diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 990e366df..3e510a3e3 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -110,3 +110,13 @@ class AppletHostDeployment(JMSBaseModel): from ...automations.deploy_applet_host import DeployAppletHostManager manager = DeployAppletHostManager(self) manager.run(**kwargs) + + def install_applet(self, applet_id, **kwargs): + from ...automations.deploy_applet_host import DeployAppletHostManager + from .applet import Applet + if applet_id: + applet = Applet.objects.get(id=applet_id) + else: + applet = None + manager = DeployAppletHostManager(self, applet=applet) + manager.install_applet(**kwargs) diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py index b94d615c8..2a291da73 100644 --- a/apps/terminal/serializers/applet_host.py +++ b/apps/terminal/serializers/applet_host.py @@ -1,19 +1,18 @@ -from rest_framework import serializers from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers -from common.validators import ProjectUniqueValidator -from common.drf.fields import ObjectRelatedField, LabeledChoiceField from assets.models import Platform, Account from assets.serializers import HostSerializer -from ..models import AppletHost, AppletHostDeployment, Applet +from common.drf.fields import LabeledChoiceField +from common.validators import ProjectUniqueValidator from .applet import AppletSerializer from .. import const - +from ..models import AppletHost, AppletHostDeployment __all__ = [ 'AppletHostSerializer', 'AppletHostDeploymentSerializer', 'AppletHostAccountSerializer', 'AppletHostAppletReportSerializer', - 'AppletHostStartupSerializer', + 'AppletHostStartupSerializer', 'AppletHostDeployAppletSerializer' ] @@ -29,7 +28,8 @@ class DeployOptionsSerializer(serializers.Serializer): RDS_Licensing = serializers.BooleanField(default=False, label=_("RDS Licensing")) RDS_LicenseServer = serializers.CharField(default='127.0.0.1', label=_('RDS License Server'), max_length=1024) RDS_LicensingMode = serializers.ChoiceField(choices=LICENSE_MODE_CHOICES, default=4, label=_('RDS Licensing Mode')) - RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1, label=_("RDS fSingleSessionPerUser")) + RDS_fSingleSessionPerUser = serializers.ChoiceField(choices=SESSION_PER_USER, default=1, + label=_("RDS fSingleSessionPerUser")) RDS_MaxDisconnectionTime = serializers.IntegerField(default=60000, label=_("RDS Max Disconnection Time")) RDS_RemoteAppLogoffTimeLimit = serializers.IntegerField(default=0, label=_("RDS Remote App Logoff Time Limit")) @@ -94,6 +94,18 @@ class AppletHostDeploymentSerializer(serializers.ModelSerializer): fields = fields_mini + ['comment'] + read_only_fields +class AppletHostDeployAppletSerializer(AppletHostDeploymentSerializer): + applet_id = serializers.UUIDField(write_only=True, allow_null=True, required=False) + + class Meta(AppletHostDeploymentSerializer.Meta): + fields = AppletHostDeploymentSerializer.Meta.fields + ['applet_id'] + + def create(self, validated_data): + applet_id = validated_data.pop('applet_id', None) + deployment = super().create(validated_data) + return deployment + + class AppletHostAccountSerializer(serializers.ModelSerializer): class Meta: model = Account diff --git a/apps/terminal/tasks.py b/apps/terminal/tasks.py index 396369d16..4e67d1fc7 100644 --- a/apps/terminal/tasks.py +++ b/apps/terminal/tasks.py @@ -1,26 +1,25 @@ # -*- coding: utf-8 -*- # +import datetime import os import subprocess -import datetime from celery import shared_task from celery.utils.log import get_task_logger -from django.utils import timezone from django.core.files.storage import default_storage +from django.utils import timezone from common.utils import get_log_keep_day from ops.celery.decorator import ( register_as_period_task, after_app_ready_start, after_app_shutdown_clean_periodic ) -from .models import ( - Status, Session, Command, Task, AppletHost, - AppletHostDeployment -) from orgs.utils import tmp_to_builtin_org from .backends import server_replay_storage +from .models import ( + Status, Session, Command, Task, AppletHostDeployment +) from .utils import find_session_replay_local CACHE_REFRESH_INTERVAL = 10 @@ -57,7 +56,7 @@ def clean_orphan_session(): @shared_task -@register_as_period_task(interval=3600*24) +@register_as_period_task(interval=3600 * 24) @after_app_ready_start @after_app_shutdown_clean_periodic def clean_expired_session_period(): @@ -114,3 +113,10 @@ def run_applet_host_deployment(did): with tmp_to_builtin_org(system=1): deployment = AppletHostDeployment.objects.get(id=did) deployment.start() + + +@shared_task +def run_applet_host_deployment_install_applet(did, applet_id): + with tmp_to_builtin_org(system=1): + deployment = AppletHostDeployment.objects.get(id=did) + deployment.install_applet(applet_id) From 83ef013708c2a012e32f2109ac972ab30e3d56e7 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Mon, 14 Nov 2022 20:25:54 +0800 Subject: [PATCH 332/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20actions=20b?= =?UTF-8?q?it=20=E4=BB=8E1=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/const.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/perms/const.py b/apps/perms/const.py index 769ce70eb..3c8f9ee21 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -10,11 +10,11 @@ __all__ = ["SpecialAccount", "ActionChoices"] class ActionChoices(BitChoices): - connect = bit(0), _("Connect") - upload = bit(1), _("Upload") - download = bit(2), _("Download") - copy = bit(3), _("Copy") - paste = bit(4), _("Paste") + connect = bit(1), _("Connect") + upload = bit(2), _("Upload") + download = bit(3), _("Download") + copy = bit(4), _("Copy") + paste = bit(5), _("Paste") @classmethod def is_tree(cls): From 63b32ae903942e868f4401f550982932a2f1437d Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Nov 2022 10:43:21 +0800 Subject: [PATCH 333/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=8E=88?= =?UTF-8?q?=E6=9D=83=20api=EF=BC=8C=E5=8E=BB=E6=8E=89=E4=B8=8D=E7=94=A8?= =?UTF-8?q?=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/accounts.py | 57 +--------------- apps/perms/serializers/user_permission.py | 12 ++-- apps/perms/urls/asset_permission.py | 43 ++++++------ apps/tickets/api/ticket.py | 22 ++----- apps/tickets/models/ticket/apply_asset.py | 10 +-- .../tickets/serializers/ticket/apply_asset.py | 66 +++++++------------ apps/tickets/serializers/ticket/common.py | 6 +- apps/tickets/serializers/ticket/ticket.py | 58 ++++++---------- 8 files changed, 87 insertions(+), 187 deletions(-) diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 692dac8c8..f5773179c 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -3,42 +3,18 @@ from rest_framework.generics import ListAPIView, get_object_or_404 from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty -from assets.serializers import AccountSerializer -from perms.hands import User, Asset, Account from perms import serializers +from perms.hands import User, Asset from perms.utils import PermAccountUtil -from .mixin import RoleAdminMixin, RoleUserMixin logger = get_logger(__name__) - __all__ = [ - 'UserAllGrantedAccountsApi', - 'MyAllGrantedAccountsApi', 'UserGrantedAssetAccountsApi', 'MyGrantedAssetAccountsApi', - 'UserGrantedAssetSpecialAccountsApi', - 'MyGrantedAssetSpecialAccountsApi', ] -class UserAllGrantedAccountsApi(RoleAdminMixin, ListAPIView): - """ 授权给用户的所有账号列表 """ - serializer_class = AccountSerializer - filterset_fields = ("name", "username", "privileged", "version") - search_fields = filterset_fields - - def get_queryset(self): - util = PermAccountUtil() - accounts = util.get_perm_accounts_for_user(self.user) - return accounts - - -class MyAllGrantedAccountsApi(RoleUserMixin, UserAllGrantedAccountsApi): - """ 授权给我的所有账号列表 """ - pass - - class UserGrantedAssetAccountsApi(ListAPIView): serializer_class = serializers.AccountsGrantedSerializer @@ -55,9 +31,8 @@ class UserGrantedAssetAccountsApi(ListAPIView): return asset def get_queryset(self): - accounts = PermAccountUtil().get_perm_accounts_for_user_asset( - self.user, self.asset, with_actions=True - ) + util = PermAccountUtil() + accounts = util.get_permed_accounts_for_user(self.user, self.asset) return accounts @@ -67,29 +42,3 @@ class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi): @lazyproperty def user(self): return self.request.user - - -class UserGrantedAssetSpecialAccountsApi(ListAPIView): - serializer_class = serializers.AccountsGrantedSerializer - - @lazyproperty - def user(self): - return self.request.user - - def get_queryset(self): - # 构造默认包含的账号,如: @INPUT @USER - accounts = [ - Account.get_manual_account(), - Account.get_user_account(self.user.username) - ] - for account in accounts: - account.actions = Action.ALL - return accounts - - -class MyGrantedAssetSpecialAccountsApi(UserGrantedAssetSpecialAccountsApi): - permission_classes = (IsValidUser,) - - @lazyproperty - def user(self): - return self.request.user diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 09eb97428..6cee0e793 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers -from common.drf.fields import ObjectRelatedField, LabeledChoiceField -from assets.models import Node, Asset, Platform, Account from assets.const import Category, AllTypes +from assets.models import Node, Asset, Platform, Account +from common.drf.fields import ObjectRelatedField, LabeledChoiceField from perms.serializers.permission import ActionChoicesField __all__ = [ @@ -49,13 +49,9 @@ class ActionsSerializer(serializers.Serializer): class AccountsGrantedSerializer(serializers.ModelSerializer): - """ 授权的账号序列类 """ - - # Todo: 添加前端登录逻辑中需要的一些字段,比如:是否需要手动输入密码 - # need_manual = serializers.BooleanField(label=_('Need manual input')) actions = ActionChoicesField(read_only=True) class Meta: model = Account - fields = ['id', 'name', 'username', 'actions'] + fields = ['id', 'name', 'username', 'secret_type', 'has_secret', 'actions'] read_only_fields = fields diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 99605372d..f402372df 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -7,10 +7,14 @@ from .. import api router = BulkRouter() router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') -router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, 'asset-permissions-users-relation') -router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation') -router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation') -router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation') +router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, + 'asset-permissions-users-relation') +router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, + 'asset-permissions-user-groups-relation') +router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, + 'asset-permissions-assets-relation') +router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, + 'asset-permissions-nodes-relation') user_permission_urlpatterns = [ # 以 serializer 格式返回 @@ -34,18 +38,22 @@ user_permission_urlpatterns = [ path('/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'), path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'), # 以 Tree Node 的数据格式返回 - path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), name='user-nodes-children-as-tree'), + path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), + name='user-nodes-children-as-tree'), # 部分调用位置 # - 普通用户 -> 我的资产 -> 展开节点 时调用 path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'), # 此接口会返回整棵树 # 普通用户 -> 命令执行 -> 左侧树 - path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'), + path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), + name='my-nodes-with-assets-as-tree'), # 主要用于 luna 页面,带资产的节点树 - path('/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'), - path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'), + path('/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='user-nodes-children-with-assets-as-tree'), + path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='my-nodes-children-with-assets-as-tree'), # 查询授权树上某个节点的所有资产 path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), @@ -59,16 +67,10 @@ user_permission_urlpatterns = [ path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), - # 获取授权给用户的所有账号 - path('/accounts/', api.UserAllGrantedAccountsApi.as_view(), name='user-accounts'), - path('accounts/', api.MyAllGrantedAccountsApi.as_view(), name='my-accounts'), - # 获取授权给用户某个资产的所有账号 - path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), + path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), + name='user-asset-accounts'), path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), - # 用户登录资产的特殊账号, @INPUT, @USER 等 - path('/assets/special-accounts/', api.UserGrantedAssetSpecialAccountsApi.as_view(), name='user-special-accounts'), - path('assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'), ] user_group_permission_urlpatterns = [ @@ -76,11 +78,14 @@ user_group_permission_urlpatterns = [ path('/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), path('/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), path('/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'), - path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'), - path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), + path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), + name='user-group-nodes-children-as-tree'), + path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), + name='user-group-node-assets'), # 获取所有和资产-用户组关联的账号列表 - path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), name='user-group-asset-accounts'), + path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), + name='user-group-asset-accounts'), ] permission_urlpatterns = [ diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 12b1cab1b..6216f49be 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -2,22 +2,20 @@ # from rest_framework import viewsets from rest_framework.decorators import action -from rest_framework.response import Response from rest_framework.exceptions import MethodNotAllowed +from rest_framework.response import Response from common.const.http import POST, PUT, PATCH from common.mixins.api import CommonApiMixin from orgs.utils import tmp_to_root_org - from rbac.permissions import RBACPermission - -from tickets import serializers from tickets import filters -from tickets.permissions.ticket import IsAssignee, IsApplicant +from tickets import serializers from tickets.models import ( Ticket, ApplyAssetTicket, ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket ) +from tickets.permissions.ticket import IsAssignee, IsApplicant __all__ = [ 'TicketViewSet', 'ApplyAssetTicketViewSet', @@ -27,10 +25,8 @@ __all__ = [ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): - serializer_class = serializers.TicketDisplaySerializer + serializer_class = serializers.TicketSerializer serializer_classes = { - 'list': serializers.TicketListSerializer, - 'open': serializers.TicketApplySerializer, 'approve': serializers.TicketApproveSerializer } model = Ticket @@ -40,8 +36,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): 'title', 'type', 'status' ] ordering_fields = ( - 'title', 'status', 'state', - 'action_display', 'date_created', 'serial_num', + 'title', 'status', 'state', 'action_display', + 'date_created', 'serial_num', ) ordering = ('-date_created',) rbac_perms = { @@ -98,11 +94,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): class ApplyAssetTicketViewSet(TicketViewSet): - serializer_class = serializers.ApplyAssetDisplaySerializer - serializer_classes = { - 'open': serializers.ApplyAssetSerializer, - 'approve': serializers.ApproveAssetSerializer - } + serializer_class = serializers.ApplyAssetSerializer model = ApplyAssetTicket filterset_class = filters.ApplyAssetTicketFilter diff --git a/apps/tickets/models/ticket/apply_asset.py b/apps/tickets/models/ticket/apply_asset.py index d5f11ee36..1e46cc130 100644 --- a/apps/tickets/models/ticket/apply_asset.py +++ b/apps/tickets/models/ticket/apply_asset.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from perms.const import ActionChoices from .general import Ticket __all__ = ['ApplyAssetTicket'] @@ -14,13 +15,6 @@ class ApplyAssetTicket(Ticket): # 申请信息 apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets')) apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts')) - apply_actions = models.IntegerField(default=1, verbose_name=_('Actions')) + apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.all()) apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) - - @property - def apply_actions_display(self): - return 'Todo' - - def get_apply_actions_display(self): - return ', '.join(self.apply_actions_display) diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 26a1fe434..69b4371f4 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -1,40 +1,40 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from perms.serializers.permission import ActionChoicesField -from perms.models import AssetPermission -from orgs.utils import tmp_to_org from assets.models import Asset, Node - +from common.drf.fields import ObjectRelatedField +from perms.models import AssetPermission +from perms.serializers.permission import ActionChoicesField from tickets.models import ApplyAssetTicket +from .common import BaseApplyAssetSerializer from .ticket import TicketApplySerializer -from .common import BaseApplyAssetApplicationSerializer -__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer', 'ApproveAssetSerializer'] +__all__ = ['ApplyAssetSerializer'] asset_or_node_help_text = _("Select at least one asset or node") -class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer): - apply_actions = ActionChoicesField(required=True, allow_empty=False) +class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer): + apply_assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Apply assets')) + apply_nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False, label=_('Apply nodes')) + apply_actions = ActionChoicesField(required=False, allow_null=True, label=_("Apply actions")) permission_model = AssetPermission - class Meta: + class Meta(TicketApplySerializer.Meta): model = ApplyAssetTicket + fields_mini = ['id', 'title'] writeable_fields = [ - 'id', 'title', 'type', 'apply_nodes', 'apply_assets', + 'id', 'title', 'apply_nodes', 'apply_assets', 'apply_accounts', 'apply_actions', 'org_id', 'comment', 'apply_date_start', 'apply_date_expired' ] - fields = TicketApplySerializer.Meta.fields + writeable_fields + [ - 'apply_permission_name', 'apply_actions_display' - ] + fields = TicketApplySerializer.Meta.fields + writeable_fields + ['apply_permission_name', ] read_only_fields = list(set(fields) - set(writeable_fields)) ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs extra_kwargs = { - 'apply_nodes': {'required': False, 'allow_empty': True}, - 'apply_assets': {'required': False, 'allow_empty': True}, - 'apply_accounts': {'required': False, 'allow_empty': True}, + 'apply_nodes': {'required': False}, + 'apply_assets': {'required': False}, + 'apply_accounts': {'required': False}, } extra_kwargs.update(ticket_extra_kwargs) @@ -45,9 +45,11 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria return self.filter_many_to_many_field(Asset, assets) def validate(self, attrs): + attrs['type'] = 'apply_asset' attrs = super().validate(attrs) if self.is_final_approval and ( - not attrs.get('apply_nodes') and not attrs.get('apply_assets') + not attrs.get('apply_nodes') + and not attrs.get('apply_assets') ): raise serializers.ValidationError({ 'apply_nodes': asset_or_node_help_text, @@ -56,29 +58,7 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria return attrs - -class ApproveAssetSerializer(ApplyAssetSerializer): - class Meta(ApplyAssetSerializer.Meta): - read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + [ - 'title', 'type' - ] - - -class ApplyAssetDisplaySerializer(ApplyAssetSerializer): - apply_nodes = serializers.SerializerMethodField() - apply_assets = serializers.SerializerMethodField() - - class Meta: - model = ApplyAssetSerializer.Meta.model - fields = ApplyAssetSerializer.Meta.fields - read_only_fields = fields - - @staticmethod - def get_apply_nodes(instance): - with tmp_to_org(instance.org_id): - return instance.apply_nodes.values_list('id', flat=True) - - @staticmethod - def get_apply_assets(instance): - with tmp_to_org(instance.org_id): - return instance.apply_assets.values_list('id', flat=True) + @classmethod + def setup_eager_loading(cls, queryset): + queryset = queryset.prefetch_related('apply_nodes', 'apply_assets') + return queryset diff --git a/apps/tickets/serializers/ticket/common.py b/apps/tickets/serializers/ticket/common.py index f38f97a43..7cbaea697 100644 --- a/apps/tickets/serializers/ticket/common.py +++ b/apps/tickets/serializers/ticket/common.py @@ -1,12 +1,12 @@ -from django.db.transaction import atomic from django.db.models import Model +from django.db.transaction import atomic from django.utils.translation import ugettext as _ from rest_framework import serializers from orgs.utils import tmp_to_org from tickets.models import Ticket -__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetApplicationSerializer'] +__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetSerializer'] def get_default_permission_name(ticket): @@ -34,7 +34,7 @@ class DefaultPermissionName(object): return self.default -class BaseApplyAssetApplicationSerializer(serializers.Serializer): +class BaseApplyAssetSerializer(serializers.Serializer): permission_model: Model @property diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index 461e61870..f201c2edc 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -3,31 +3,35 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from orgs.models import Organization +from common.drf.fields import LabeledChoiceField from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from orgs.models import Organization +from tickets.const import TicketType, TicketStatus, TicketState from tickets.models import Ticket, TicketFlow -from tickets.const import TicketType __all__ = [ - 'TicketDisplaySerializer', 'TicketApplySerializer', 'TicketListSerializer', 'TicketApproveSerializer' + 'TicketApplySerializer', 'TicketApproveSerializer', 'TicketSerializer', ] class TicketSerializer(OrgResourceModelSerializerMixin): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) - status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display')) + type = LabeledChoiceField(choices=TicketType.choices, read_only=True, label=_('Type')) + status = LabeledChoiceField(choices=TicketStatus.choices, read_only=True, label=_('Status')) + state = LabeledChoiceField(choices=TicketState.choices, read_only=True, label=_("State")) class Meta: model = Ticket fields_mini = ['id', 'title'] fields_small = fields_mini + [ - 'type', 'type_display', 'status', 'status_display', - 'state', 'approval_step', 'rel_snapshot', 'comment', + 'type', 'status', 'state', 'approval_step', 'comment', 'date_created', 'date_updated', 'org_id', 'rel_snapshot', 'process_map', 'org_name', 'serial_num' ] fields_fk = ['applicant', ] fields = fields_small + fields_fk + extra_kwargs = { + 'type': {'required': True} + } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -41,43 +45,20 @@ class TicketSerializer(OrgResourceModelSerializerMixin): choices.pop(TicketType.general, None) tp._choices = choices - -class TicketListSerializer(TicketSerializer): - class Meta: - model = Ticket - fields = [ - 'id', 'title', 'serial_num', 'type', 'type_display', 'status', - 'state', 'rel_snapshot', 'date_created', 'rel_snapshot' - ] - read_only_fields = fields - - -class TicketDisplaySerializer(TicketSerializer): - class Meta: - model = Ticket - fields = TicketSerializer.Meta.fields - read_only_fields = fields + @classmethod + def setup_eager_loading(cls, queryset): + queryset = queryset.prefetch_related('ticket_steps') + return queryset class TicketApproveSerializer(TicketSerializer): - class Meta: - model = Ticket + class Meta(TicketSerializer.Meta): fields = TicketSerializer.Meta.fields read_only_fields = fields class TicketApplySerializer(TicketSerializer): - org_id = serializers.CharField( - required=True, max_length=36, - allow_blank=True, label=_("Organization") - ) - - class Meta: - model = Ticket - fields = TicketSerializer.Meta.fields - extra_kwargs = { - 'type': {'required': True} - } + org_id = serializers.CharField(required=True, max_length=36, allow_blank=True, label=_("Organization")) @staticmethod def validate_org_id(org_id): @@ -91,10 +72,13 @@ class TicketApplySerializer(TicketSerializer): if self.instance: return attrs + print("Attrs: ", attrs) + ticket_type = attrs.get('type') org_id = attrs.get('org_id') - flow = TicketFlow.get_org_related_flows(org_id=org_id)\ + flow = TicketFlow.get_org_related_flows(org_id=org_id) \ .filter(type=ticket_type).first() + if flow: attrs['flow'] = flow else: From 582a8e0c52e2b29a6f63642c5dde97fc67973f97 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 15 Nov 2022 14:59:22 +0800 Subject: [PATCH 334/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20BitChoicesF?= =?UTF-8?q?ield=20to=5Frepresentation=20swagger=20=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/fields.py | 7 +++++++ apps/perms/serializers/permission.py | 2 +- apps/perms/urls/asset_permission.py | 7 +++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 9f2527d8a..c666d9b76 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -120,6 +120,13 @@ class BitChoicesField(TreeChoicesMixin, serializers.MultipleChoiceField): super().__init__(choices=choices, **kwargs) def to_representation(self, value): + if isinstance(value, list) and len(value) == 1: + # Swagger 会使用 field.choices.keys() 迭代传递进来 + return [ + {"value": c.name, "label": c.label} + for c in self._choice_cls + if c.name == value[0] + ] return [ {"value": c.name, "label": c.label} for c in self._choice_cls diff --git a/apps/perms/serializers/permission.py b/apps/perms/serializers/permission.py index 9a31058f6..fc2bf1cf3 100644 --- a/apps/perms/serializers/permission.py +++ b/apps/perms/serializers/permission.py @@ -16,7 +16,7 @@ __all__ = ["AssetPermissionSerializer", "ActionChoicesField"] class ActionChoicesField(BitChoicesField): def __init__(self, **kwargs): - super().__init__(ActionChoices, **kwargs) + super().__init__(choice_cls=ActionChoices, **kwargs) class AssetPermissionSerializer(BulkOrgResourceModelSerializer): diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index f402372df..cdf502c0f 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -65,12 +65,15 @@ user_permission_urlpatterns = [ # 收藏的资产 path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), - path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), + path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), + name='my-ungrouped-assets'), # 获取授权给用户某个资产的所有账号 path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), - path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), + path('/((?P[^/.]+)/)?assets//accounts/', + api.UserGrantedAssetAccountsApi.as_view(), + name='my-asset-accounts'), ] user_group_permission_urlpatterns = [ From 8e123304ad680d93e6f89dd9ccfdbbb0cd0150ae Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Nov 2022 15:26:31 +0800 Subject: [PATCH 335/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20perms=20ac?= =?UTF-8?q?counts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/integer.py | 6 +- apps/perms/api/user_permission/accounts.py | 3 + apps/perms/api/user_permission/assets/api.py | 7 +- apps/perms/utils/user_permission.py | 81 ++++++++++---------- 4 files changed, 53 insertions(+), 44 deletions(-) diff --git a/apps/common/utils/integer.py b/apps/common/utils/integer.py index 73f4160c0..c87e939f0 100644 --- a/apps/common/utils/integer.py +++ b/apps/common/utils/integer.py @@ -1,3 +1,5 @@ - def bit(x): - return 2 ** (x - 1) + if x == 0: + return 0 + else: + return 2 ** (x - 1) diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index f5773179c..4d3687c0a 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -17,6 +17,9 @@ __all__ = [ class UserGrantedAssetAccountsApi(ListAPIView): serializer_class = serializers.AccountsGrantedSerializer + rbac_perms = ( + ('list', 'perms.view_userassets'), + ) @lazyproperty def user(self) -> User: diff --git a/apps/perms/api/user_permission/assets/api.py b/apps/perms/api/user_permission/assets/api.py index cb9bf53aa..0fc3047fd 100644 --- a/apps/perms/api/user_permission/assets/api.py +++ b/apps/perms/api/user_permission/assets/api.py @@ -1,12 +1,12 @@ -from rest_framework.generics import ListAPIView from django.conf import settings +from rest_framework.generics import ListAPIView from common.utils import get_logger -from ..mixin import AssetRoleAdminMixin, AssetRoleUserMixin from .mixin import ( UserAllGrantedAssetsQuerysetMixin, UserDirectGrantedAssetsQuerysetMixin, UserFavoriteGrantedAssetsMixin, UserGrantedNodeAssetsMixin, AssetsSerializerFormatMixin, AssetsTreeFormatMixin, ) +from ..mixin import AssetRoleAdminMixin, AssetRoleUserMixin __all__ = [ 'UserDirectGrantedAssetsApi', 'MyDirectGrantedAssetsApi', @@ -14,7 +14,8 @@ __all__ = [ 'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeApi', 'MyUngroupAssetsAsTreeApi', 'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi', - 'UserGrantedNodeAssetsApi', 'MyGrantedNodeAssetsApi', + 'UserGrantedNodeAssetsApi', + 'MyGrantedNodeAssetsApi', ] logger = get_logger(__name__) diff --git a/apps/perms/utils/user_permission.py b/apps/perms/utils/user_permission.py index fe73913cf..92a42a401 100644 --- a/apps/perms/utils/user_permission.py +++ b/apps/perms/utils/user_permission.py @@ -1,30 +1,31 @@ +import time from collections import defaultdict from typing import List, Tuple -import time -from django.core.cache import cache from django.conf import settings +from django.core.cache import cache from django.db.models import Q, QuerySet from django.utils.translation import gettext as _ -from common.db.models import output_as_string, UnionQuerySet -from common.utils.common import lazyproperty, timeit +from assets.models import ( + Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet +) from assets.utils import NodeAssetsUtil -from common.utils import get_logger +from common.db.models import output_as_string, UnionQuerySet from common.decorator import on_transaction_commit +from common.utils import get_logger +from common.utils.common import lazyproperty, timeit +from orgs.models import Organization from orgs.utils import ( tmp_to_org, current_org, ensure_in_real_or_default_org, tmp_to_root_org ) -from assets.models import ( - Asset, FavoriteAsset, AssetQuerySet, NodeQuerySet -) -from users.models import User -from orgs.models import Organization from perms.locks import UserGrantedTreeRebuildLock from perms.models import ( AssetPermission, PermNode, UserAssetGrantedTreeNodeRelation ) +from users.models import User + NodeFrom = UserAssetGrantedTreeNodeRelation.NodeFrom NODE_ONLY_FIELDS = ('id', 'key', 'parent_key', 'org_id') @@ -119,8 +120,7 @@ class UserGrantedTreeRefreshController: key = cls.key_template.format(user_id=user_id) p.srem(key, *org_ids) p.execute() - logger.info(f'Remove orgs from users built tree: users:{user_ids} ' - f'orgs:{org_ids}') + logger.info(f'Remove orgs from users built tree: users:{user_ids} orgs:{org_ids}') @classmethod def add_need_refresh_orgs_for_users(cls, org_ids, user_ids): @@ -205,28 +205,30 @@ class UserGrantedTreeRefreshController: user = self.user with tmp_to_root_org(): - UserAssetGrantedTreeNodeRelation.objects.filter(user=user)\ - .exclude(org_id__in=self.org_ids)\ + UserAssetGrantedTreeNodeRelation.objects.filter(user=user) \ + .exclude(org_id__in=self.org_ids) \ .delete() - if force or self.have_need_refresh_orgs(): - with UserGrantedTreeRebuildLock(user_id=user.id): - if force: - orgs = self.orgs - self.set_all_orgs_as_built() - else: - orgs = self.get_need_refresh_orgs_and_fill_up() + if not force and not self.have_need_refresh_orgs(): + return - for org in orgs: - with tmp_to_org(org): - t_start = time.time() - logger.info(f'Rebuild user tree: user={self.user} org={current_org}') - utils = UserGrantedTreeBuildUtils(user) - utils.rebuild_user_granted_tree() - logger.info( - f'Rebuild user tree ok: cost={time.time() - t_start} ' - f'user={self.user} org={current_org}' - ) + with UserGrantedTreeRebuildLock(user_id=user.id): + if force: + orgs = self.orgs + self.set_all_orgs_as_built() + else: + orgs = self.get_need_refresh_orgs_and_fill_up() + + for org in orgs: + with tmp_to_org(org): + t_start = time.time() + logger.info(f'Rebuild user tree: user={self.user} org={current_org}') + utils = UserGrantedTreeBuildUtils(user) + utils.rebuild_user_granted_tree() + logger.info( + f'Rebuild user tree ok: cost={time.time() - t_start} ' + f'user={self.user} org={current_org}' + ) class UserGrantedUtilsBase: @@ -427,8 +429,8 @@ class UserGrantedTreeBuildUtils(UserGrantedUtilsBase): for node_id, asset_id in node_asset_pairs: if node_id not in node_id_key_mapper: continue - nkey = node_id_key_mapper[node_id] - nodekey_assetsid_mapper[nkey].add(asset_id) + node_key = node_id_key_mapper[node_id] + nodekey_assetsid_mapper[node_key].add(asset_id) util = NodeAssetsUtil(nodes, nodekey_assetsid_mapper) util.generate() @@ -604,7 +606,10 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase): def get_top_level_nodes(self): nodes = self.get_special_nodes() real_nodes = self.get_indirect_granted_node_children('') - nodes.extend(self.sort(real_nodes)) + nodes.extend(real_nodes) + if len(real_nodes) == 1: + children = self.get_node_children(real_nodes[0].key) + nodes.extend(children) return nodes def get_ungrouped_node(self): @@ -649,11 +654,9 @@ class UserGrantedNodesQueryUtils(UserGrantedUtilsBase): :param with_special: :return: """ - nodes = PermNode.objects.filter( - granted_node_rels__user=self.user - ).annotate( - **PermNode.annotate_granted_node_rel_fields - ).distinct() + nodes = PermNode.objects.filter(granted_node_rels__user=self.user) \ + .annotate(**PermNode.annotate_granted_node_rel_fields) \ + .distinct() key_to_node_mapper = {} nodes_descendant_q = Q() From 9d0e2b287216484763c1e8ef05d54c1637896a24 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Nov 2022 15:47:32 +0800 Subject: [PATCH 336/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20accounts?= =?UTF-8?q?=20list=20url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/urls/asset_permission.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index cdf502c0f..1273edaac 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -69,11 +69,8 @@ user_permission_urlpatterns = [ name='my-ungrouped-assets'), # 获取授权给用户某个资产的所有账号 - path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), + path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), - path('/((?P[^/.]+)/)?assets//accounts/', - api.UserGrantedAssetAccountsApi.as_view(), - name='my-asset-accounts'), ] user_group_permission_urlpatterns = [ From ef637e91b9b938af0555147aeb27978a141d2d68 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 15 Nov 2022 16:01:01 +0800 Subject: [PATCH 337/488] perf: add host deployment task --- apps/terminal/api/applet/host.py | 2 ++ .../0059_applethostdeployment_task.py | 18 ++++++++++++++++++ apps/terminal/models/applet/host.py | 5 +++++ apps/terminal/serializers/applet_host.py | 2 +- 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 apps/terminal/migrations/0059_applethostdeployment_task.py diff --git a/apps/terminal/api/applet/host.py b/apps/terminal/api/applet/host.py index 72321cc37..4542f3b8d 100644 --- a/apps/terminal/api/applet/host.py +++ b/apps/terminal/api/applet/host.py @@ -49,6 +49,7 @@ class AppletHostDeploymentViewSet(viewsets.ModelViewSet): serializer.is_valid(raise_exception=True) instance = serializer.save() task = run_applet_host_deployment.delay(instance.id) + instance.save_task(task.id) return Response({'task': str(task.id)}, status=201) @action(methods=['post'], detail=False, serializer_class=AppletHostDeployAppletSerializer) @@ -58,4 +59,5 @@ class AppletHostDeploymentViewSet(viewsets.ModelViewSet): applet_id = serializer.validated_data.get('applet_id') instance = serializer.save() task = run_applet_host_deployment_install_applet.delay(instance.id, applet_id) + instance.save_task(task.id) return Response({'task': str(task.id)}, status=201) diff --git a/apps/terminal/migrations/0059_applethostdeployment_task.py b/apps/terminal/migrations/0059_applethostdeployment_task.py new file mode 100644 index 000000000..5f455c9c6 --- /dev/null +++ b/apps/terminal/migrations/0059_applethostdeployment_task.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-15 05:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0058_auto_20221103_1624'), + ] + + operations = [ + migrations.AddField( + model_name='applethostdeployment', + name='task', + field=models.UUIDField(null=True, verbose_name='Task'), + ), + ] diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 3e510a3e3..83d9e3a41 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -105,6 +105,7 @@ class AppletHostDeployment(JMSBaseModel): date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + task = models.UUIDField(null=True, verbose_name=_('Task')) def start(self, **kwargs): from ...automations.deploy_applet_host import DeployAppletHostManager @@ -120,3 +121,7 @@ class AppletHostDeployment(JMSBaseModel): applet = None manager = DeployAppletHostManager(self, applet=applet) manager.install_applet(**kwargs) + + def save_task(self, task): + self.task = task + self.save(update_fields=['task']) diff --git a/apps/terminal/serializers/applet_host.py b/apps/terminal/serializers/applet_host.py index 2a291da73..c81258892 100644 --- a/apps/terminal/serializers/applet_host.py +++ b/apps/terminal/serializers/applet_host.py @@ -86,7 +86,7 @@ class HostAppletSerializer(AppletSerializer): class AppletHostDeploymentSerializer(serializers.ModelSerializer): class Meta: model = AppletHostDeployment - fields_mini = ['id', 'host', 'status'] + fields_mini = ['id', 'host', 'status', 'task'] read_only_fields = [ 'status', 'date_created', 'date_updated', 'date_start', 'date_finished' From a5fa5fd262d1ee5385014e1f149117168ea1e94e Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 15 Nov 2022 16:07:42 +0800 Subject: [PATCH 338/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E7=9A=84=E8=B5=84=E4=BA=A7=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/accounts.py | 24 ++++++++++------------ apps/perms/urls/asset_permission.py | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 4d3687c0a..39ac942b2 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -1,8 +1,8 @@ from django.shortcuts import get_object_or_404 from rest_framework.generics import ListAPIView, get_object_or_404 -from common.permissions import IsValidUser -from common.utils import get_logger, lazyproperty +from common.exceptions import JMSObjectDoesNotExist +from common.utils import get_logger, lazyproperty, is_uuid from perms import serializers from perms.hands import User, Asset from perms.utils import PermAccountUtil @@ -11,20 +11,26 @@ logger = get_logger(__name__) __all__ = [ 'UserGrantedAssetAccountsApi', - 'MyGrantedAssetAccountsApi', ] class UserGrantedAssetAccountsApi(ListAPIView): serializer_class = serializers.AccountsGrantedSerializer rbac_perms = ( + ('GET', 'perms.view_userassets'), ('list', 'perms.view_userassets'), ) @lazyproperty def user(self) -> User: - user_id = self.kwargs.get('pk') - return User.objects.get(id=user_id) + query_user = self.kwargs.get('user') + if is_uuid(query_user): + user = User.objects.get(id=query_user) + elif query_user == 'my': + user = self.request.user + else: + raise JMSObjectDoesNotExist(object_name=_('User')) + return user @lazyproperty def asset(self): @@ -37,11 +43,3 @@ class UserGrantedAssetAccountsApi(ListAPIView): util = PermAccountUtil() accounts = util.get_permed_accounts_for_user(self.user, self.asset) return accounts - - -class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi): - permission_classes = (IsValidUser,) - - @lazyproperty - def user(self): - return self.request.user diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 1273edaac..96730d5b3 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -70,7 +70,7 @@ user_permission_urlpatterns = [ # 获取授权给用户某个资产的所有账号 path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), - name='user-asset-accounts'), + name='user-granted-asset-accounts'), ] user_group_permission_urlpatterns = [ From 73290f4ed0ed2894ea18d94edaf8ccad9259759a Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Nov 2022 16:24:53 +0800 Subject: [PATCH 339/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/accounts.py | 20 +----- apps/perms/api/user_permission/mixin.py | 43 ++++++++++++ apps/perms/urls/api_urls.py | 5 +- apps/perms/urls/asset_permission.py | 74 -------------------- apps/perms/urls/user_permission.py | 80 ++++++++++++++++++++++ 5 files changed, 129 insertions(+), 93 deletions(-) create mode 100644 apps/perms/urls/user_permission.py diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 4d3687c0a..eb20086e7 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -1,31 +1,25 @@ from django.shortcuts import get_object_or_404 from rest_framework.generics import ListAPIView, get_object_or_404 -from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty from perms import serializers -from perms.hands import User, Asset +from perms.hands import Asset from perms.utils import PermAccountUtil +from .mixin import SelfOrPKUserMixin logger = get_logger(__name__) __all__ = [ 'UserGrantedAssetAccountsApi', - 'MyGrantedAssetAccountsApi', ] -class UserGrantedAssetAccountsApi(ListAPIView): +class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): serializer_class = serializers.AccountsGrantedSerializer rbac_perms = ( ('list', 'perms.view_userassets'), ) - @lazyproperty - def user(self) -> User: - user_id = self.kwargs.get('pk') - return User.objects.get(id=user_id) - @lazyproperty def asset(self): asset_id = self.kwargs.get('asset_id') @@ -37,11 +31,3 @@ class UserGrantedAssetAccountsApi(ListAPIView): util = PermAccountUtil() accounts = util.get_permed_accounts_for_user(self.user, self.asset) return accounts - - -class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi): - permission_classes = (IsValidUser,) - - @lazyproperty - def user(self): - return self.request.user diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 2a7cbe221..5b64804e0 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- # +from django.shortcuts import get_object_or_404 from rest_framework.request import Request from common.http import is_true from common.mixins.api import RoleAdminMixin, RoleUserMixin from perms.utils.user_permission import UserGrantedTreeRefreshController +from rbac.permissions import RBACPermission from users.models import User @@ -34,3 +36,44 @@ class AssetRoleUserMixin(RebuildTreeMixin, RoleUserMixin): ('get_tree', 'perms.view_myassets'), ('GET', 'perms.view_myassets'), ) + + +class SelfOrPKUserMixin: + kwargs: dict + request: Request + permission_classes = (RBACPermission,) + + @property + def self_rbac_perms(self): + return ( + ('list', 'perms.view_myassets'), + ('retrieve', 'perms.view_myassets'), + ('get_tree', 'perms.view_myassets'), + ('GET', 'perms.view_myassets'), + ) + + @property + def admin_rbac_perms(self): + return ( + ('list', 'perms.view_userassets'), + ('retrieve', 'perms.view_userassets'), + ('get_tree', 'perms.view_userassets'), + ('GET', 'perms.view_userassets'), + ) + + def get_rbac_perms(self): + if self.request_user_is_self(): + return self.self_rbac_perms + else: + return self.admin_rbac_perms + + def request_user_is_self(self): + print("user is: ", self.kwargs) + return self.kwargs.get('user') in ['my', 'self'] + + @property + def user(self): + if self.request_user_is_self(): + return self.request.user + else: + return get_object_or_404(User, pk=self.kwargs.get('user')) diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 568c226ee..9a4b3f10a 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -1,8 +1,9 @@ # coding:utf-8 from .asset_permission import asset_permission_urlpatterns +from .user_permission import user_permission_urlpatterns app_name = 'perms' -urlpatterns = [] -urlpatterns += asset_permission_urlpatterns +urlpatterns = asset_permission_urlpatterns \ + + user_permission_urlpatterns diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 1273edaac..41ffe444a 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -16,78 +16,6 @@ router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRe router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation') -user_permission_urlpatterns = [ - # 以 serializer 格式返回 - path('/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'), - path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'), - # Tree Node 的数据格式返回 - path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'), - path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'), - path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'), - - # 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点 - # 以 serializer 格式返回 - path('/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), - path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'), - # 以 Tree Node 的数据格式返回 - path('/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'), - path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'), - - # 一层一层的获取用户授权的节点, - # 以 Serializer 的数据格式返回 - path('/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'), - path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'), - # 以 Tree Node 的数据格式返回 - path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), - name='user-nodes-children-as-tree'), - # 部分调用位置 - # - 普通用户 -> 我的资产 -> 展开节点 时调用 - path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'), - - # 此接口会返回整棵树 - # 普通用户 -> 命令执行 -> 左侧树 - path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), - name='my-nodes-with-assets-as-tree'), - - # 主要用于 luna 页面,带资产的节点树 - path('/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), - name='user-nodes-children-with-assets-as-tree'), - path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), - name='my-nodes-children-with-assets-as-tree'), - - # 查询授权树上某个节点的所有资产 - path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), - path('nodes//assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'), - - # 未分组的资产 - path('/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), - path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), - - # 收藏的资产 - path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), - path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), - name='my-ungrouped-assets'), - - # 获取授权给用户某个资产的所有账号 - path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), - name='user-asset-accounts'), -] - -user_group_permission_urlpatterns = [ - # 查询某个用户组授权的资产和资产组 - path('/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), - path('/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), - path('/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'), - path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), - name='user-group-nodes-children-as-tree'), - path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), - name='user-group-node-assets'), - - # 获取所有和资产-用户组关联的账号列表 - path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), - name='user-group-asset-accounts'), -] - permission_urlpatterns = [ # 授权规则中授权的资产 path('/assets/all/', api.AssetPermissionAllAssetListApi.as_view(), name='asset-permission-all-assets'), @@ -97,8 +25,6 @@ permission_urlpatterns = [ asset_permission_urlpatterns = [ # Assets - path('users/', include(user_permission_urlpatterns)), - path('user-groups/', include(user_group_permission_urlpatterns)), path('asset-permissions/', include(permission_urlpatterns)), ] diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py new file mode 100644 index 000000000..7b5f66897 --- /dev/null +++ b/apps/perms/urls/user_permission.py @@ -0,0 +1,80 @@ +from django.urls import path, include + +from .. import api + +user_permission_urlpatterns = [ + # 以 serializer 格式返回 + path('/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'), + path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'), + # Tree Node 的数据格式返回 + path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'), + path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'), + path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'), + + # 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点 + # 以 serializer 格式返回 + path('/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), + path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'), + # 以 Tree Node 的数据格式返回 + path('/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'), + path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'), + + # 一层一层的获取用户授权的节点, + # 以 Serializer 的数据格式返回 + path('/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'), + path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'), + # 以 Tree Node 的数据格式返回 + path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), + name='user-nodes-children-as-tree'), + # 部分调用位置 + # - 普通用户 -> 我的资产 -> 展开节点 时调用 + path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'), + + # 此接口会返回整棵树 + # 普通用户 -> 命令执行 -> 左侧树 + path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), + name='my-nodes-with-assets-as-tree'), + + # 主要用于 luna 页面,带资产的节点树 + path('/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='user-nodes-children-with-assets-as-tree'), + path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='my-nodes-children-with-assets-as-tree'), + + # 查询授权树上某个节点的所有资产 + path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), + path('nodes//assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'), + + # 未分组的资产 + path('/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), + path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), + + # 收藏的资产 + path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), + path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), + name='my-ungrouped-assets'), + + # 获取授权给用户某个资产的所有账号 + path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), + name='user-asset-accounts'), +] + +user_group_permission_urlpatterns = [ + # 查询某个用户组授权的资产和资产组 + path('/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), + path('/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), + path('/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'), + path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), + name='user-group-nodes-children-as-tree'), + path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), + name='user-group-node-assets'), + + # 获取所有和资产-用户组关联的账号列表 + path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), + name='user-group-asset-accounts'), +] + +user_permission_urlpatterns = [ + path('users/', include(user_permission_urlpatterns)), + path('user-groups/', include(user_group_permission_urlpatterns)), +] From ed26c7f575ac2369c839f97f933e1502b50018b1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Nov 2022 16:27:56 +0800 Subject: [PATCH 340/488] perf: reslove conflict --- apps/perms/api/user_permission/accounts.py | 22 +--------------------- apps/perms/api/user_permission/mixin.py | 1 - 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 3b264f102..257ff8c31 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -1,12 +1,7 @@ from django.shortcuts import get_object_or_404 from rest_framework.generics import ListAPIView, get_object_or_404 -<<<<<<< HEAD -from common.utils import get_logger, lazyproperty -======= -from common.exceptions import JMSObjectDoesNotExist -from common.utils import get_logger, lazyproperty, is_uuid ->>>>>>> 0d3c5dddf9c838c5dcd28c20b6a8498088e45ce2 +from common.utils import get_logger from perms import serializers from perms.hands import Asset from perms.utils import PermAccountUtil @@ -26,21 +21,6 @@ class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): ('list', 'perms.view_userassets'), ) - @lazyproperty -<<<<<<< HEAD -======= - def user(self) -> User: - query_user = self.kwargs.get('user') - if is_uuid(query_user): - user = User.objects.get(id=query_user) - elif query_user == 'my': - user = self.request.user - else: - raise JMSObjectDoesNotExist(object_name=_('User')) - return user - - @lazyproperty ->>>>>>> 0d3c5dddf9c838c5dcd28c20b6a8498088e45ce2 def asset(self): asset_id = self.kwargs.get('asset_id') kwargs = {'id': asset_id, 'is_active': True} diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 5b64804e0..510ed9f1a 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -68,7 +68,6 @@ class SelfOrPKUserMixin: return self.admin_rbac_perms def request_user_is_self(self): - print("user is: ", self.kwargs) return self.kwargs.get('user') in ['my', 'self'] @property From 7061ce7c97fdb0d578a6cf7a0f4338f9c2bf24b2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Nov 2022 16:29:31 +0800 Subject: [PATCH 341/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20user=20gran?= =?UTF-8?q?ted=20asset=20account?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/accounts.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 257ff8c31..a67f5bd8f 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -16,11 +16,8 @@ __all__ = [ class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): serializer_class = serializers.AccountsGrantedSerializer - rbac_perms = ( - ('GET', 'perms.view_userassets'), - ('list', 'perms.view_userassets'), - ) + @property def asset(self): asset_id = self.kwargs.get('asset_id') kwargs = {'id': asset_id, 'is_active': True} From c63c000b101edcb6d4c2d3e5b1ef42bfe4bf1673 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 15 Nov 2022 16:29:40 +0800 Subject: [PATCH 342/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E8=AE=A4=E8=AF=86=E6=98=BE=E7=A4=BA=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/tasks/automation.py | 3 +- apps/assets/tasks/backup.py | 3 +- apps/assets/tasks/gather_accounts.py | 3 +- apps/assets/tasks/gather_facts.py | 5 +- apps/assets/tasks/nodes_amount.py | 5 +- apps/assets/tasks/ping.py | 5 +- apps/assets/tasks/push_account.py | 3 +- apps/assets/tasks/verify_account.py | 3 +- apps/common/tasks.py | 5 +- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 1455 +++++++++++++++----------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 1451 +++++++++++++------------ apps/notifications/notifications.py | 3 +- apps/ops/api/celery.py | 4 +- apps/ops/models/celery.py | 3 + apps/ops/models/playbook.py | 2 +- apps/ops/tasks.py | 114 +- apps/orgs/tasks.py | 3 +- 19 files changed, 1674 insertions(+), 1404 deletions(-) diff --git a/apps/assets/tasks/automation.py b/apps/assets/tasks/automation.py index e288de464..60f01836f 100644 --- a/apps/assets/tasks/automation.py +++ b/apps/assets/tasks/automation.py @@ -1,4 +1,5 @@ from celery import shared_task +from django.utils.translation import gettext_lazy as _ from orgs.utils import tmp_to_root_org, tmp_to_org from common.utils import get_logger, get_object_or_none @@ -7,7 +8,7 @@ from assets.const import AutomationTypes logger = get_logger(__file__) -@shared_task(queue='ansible') +@shared_task(queue='ansible', verbose_name=_('Execute automation')) def execute_automation(pid, trigger, tp): model = AutomationTypes.get_type_model(tp) with tmp_to_root_org(): diff --git a/apps/assets/tasks/backup.py b/apps/assets/tasks/backup.py index 5d4e91011..a82a6abd1 100644 --- a/apps/assets/tasks/backup.py +++ b/apps/assets/tasks/backup.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # from celery import shared_task +from django.utils.translation import gettext_lazy as _ from common.utils import get_object_or_none, get_logger from orgs.utils import tmp_to_org, tmp_to_root_org @@ -9,7 +10,7 @@ from assets.models import AccountBackupPlan logger = get_logger(__file__) -@shared_task +@shared_task(verbose_name=_('Execute account backup plan')) def execute_account_backup_plan(pid, trigger): with tmp_to_root_org(): plan = get_object_or_none(AccountBackupPlan, pk=pid) diff --git a/apps/assets/tasks/gather_accounts.py b/apps/assets/tasks/gather_accounts.py index 4e372aca7..5e20bfe73 100644 --- a/apps/assets/tasks/gather_accounts.py +++ b/apps/assets/tasks/gather_accounts.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ from celery import shared_task from django.utils.translation import gettext_noop +from django.utils.translation import gettext_lazy as _ from orgs.utils import tmp_to_root_org, org_aware_func from common.utils import get_logger @@ -24,7 +25,7 @@ def gather_asset_accounts_util(nodes, task_name): instance.execute() -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_('Gather asset accounts')) def gather_asset_accounts(node_ids, task_name=None): if task_name is None: task_name = gettext_noop("Gather assets accounts") diff --git a/apps/assets/tasks/gather_facts.py b/apps/assets/tasks/gather_facts.py index 805f8b336..b3196abf5 100644 --- a/apps/assets/tasks/gather_facts.py +++ b/apps/assets/tasks/gather_facts.py @@ -2,6 +2,7 @@ # from celery import shared_task from django.utils.translation import gettext_noop +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger from orgs.utils import org_aware_func, tmp_to_root_org @@ -40,7 +41,7 @@ def update_assets_hardware_info_util(assets=None, nodes=None, task_name=None): instance.execute() -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_('Manually update the hardware information of assets')) def update_assets_hardware_info_manual(asset_ids): from assets.models import Asset with tmp_to_root_org(): @@ -49,7 +50,7 @@ def update_assets_hardware_info_manual(asset_ids): update_assets_hardware_info_util(assets=assets, task_name=task_name) -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_('Manually update the hardware information of assets under a node')) def update_node_assets_hardware_info_manual(node_id): from assets.models import Node with tmp_to_root_org(): diff --git a/apps/assets/tasks/nodes_amount.py b/apps/assets/tasks/nodes_amount.py index c6ad2e8ba..f8d8d38a4 100644 --- a/apps/assets/tasks/nodes_amount.py +++ b/apps/assets/tasks/nodes_amount.py @@ -10,11 +10,10 @@ from common.utils.lock import AcquireFailed from common.utils import get_logger from common.const.crontab import CRONTAB_AT_AM_TWO - logger = get_logger(__file__) -@shared_task +@shared_task(verbose_name=_('Check the amount of assets under the node')) def check_node_assets_amount_task(org_id=None): if org_id is None: orgs = Organization.objects.all() @@ -32,6 +31,6 @@ def check_node_assets_amount_task(org_id=None): @register_as_period_task(crontab=CRONTAB_AT_AM_TWO) -@shared_task +@shared_task(verbose_name=_('Periodic check the amount of assets under the node')) def check_node_assets_amount_period_task(): check_node_assets_amount_task() diff --git a/apps/assets/tasks/ping.py b/apps/assets/tasks/ping.py index f1bfc93d9..817f64b64 100644 --- a/apps/assets/tasks/ping.py +++ b/apps/assets/tasks/ping.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ from celery import shared_task from django.utils.translation import gettext_noop +from django.utils.translation import gettext_lazy as _ from common.utils import get_logger from orgs.utils import org_aware_func, tmp_to_root_org @@ -29,7 +30,7 @@ def test_asset_connectivity_util(assets, task_name=None): instance.execute() -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_('Manually test the connectivity of a asset')) def test_assets_connectivity_manual(asset_ids): from assets.models import Asset with tmp_to_root_org(): @@ -39,7 +40,7 @@ def test_assets_connectivity_manual(asset_ids): test_asset_connectivity_util(assets, task_name=task_name) -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_('Manually test the connectivity of assets under a node')) def test_node_assets_connectivity_manual(node_id): from assets.models import Node with tmp_to_root_org(): diff --git a/apps/assets/tasks/push_account.py b/apps/assets/tasks/push_account.py index cd5de975a..c2c7156e8 100644 --- a/apps/assets/tasks/push_account.py +++ b/apps/assets/tasks/push_account.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext_noop from common.utils import get_logger from orgs.utils import org_aware_func, tmp_to_root_org +from django.utils.translation import ugettext_lazy as _ logger = get_logger(__file__) __all__ = [ @@ -27,7 +28,7 @@ def push_accounts_to_assets_util(accounts, assets): instance.execute() -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_('Push accounts to assets')) def push_accounts_to_assets(account_ids, asset_ids): from assets.models import Asset, Account with tmp_to_root_org(): diff --git a/apps/assets/tasks/verify_account.py b/apps/assets/tasks/verify_account.py index 2874113d8..4538f2b2d 100644 --- a/apps/assets/tasks/verify_account.py +++ b/apps/assets/tasks/verify_account.py @@ -1,5 +1,6 @@ from celery import shared_task from django.utils.translation import gettext_noop +from django.utils.translation import ugettext as _ from common.utils import get_logger from orgs.utils import org_aware_func, tmp_to_root_org @@ -26,7 +27,7 @@ def verify_accounts_connectivity_util(accounts, assets, task_name): instance.execute() -@shared_task(queue="ansible") +@shared_task(queue="ansible", verbose_name=_('Verify asset account availability')) def verify_accounts_connectivity(account_ids, asset_ids): from assets.models import Asset, Account with tmp_to_root_org(): diff --git a/apps/common/tasks.py b/apps/common/tasks.py index b9c7caf07..45828492b 100644 --- a/apps/common/tasks.py +++ b/apps/common/tasks.py @@ -1,5 +1,6 @@ import os +from django.utils.translation import ugettext_lazy as _ from django.core.mail import send_mail, EmailMultiAlternatives from django.conf import settings from celery import shared_task @@ -9,7 +10,7 @@ from .utils import get_logger logger = get_logger(__file__) -@shared_task +@shared_task(verbose_name=_("Send email")) def send_mail_async(*args, **kwargs): """ Using celery to send email async @@ -36,7 +37,7 @@ def send_mail_async(*args, **kwargs): logger.error("Sending mail error: {}".format(e)) -@shared_task +@shared_task(verbose_name=_("Send email attachment")) def send_mail_attachment_async(subject, message, recipient_list, attachment_list=None): if attachment_list is None: attachment_list = [] diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 093842b71..bb9486afd 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07f1cfd07039142f4847b4139586bf815467f266119eae57476c073130f0ac92 -size 118098 +oid sha256:0b54b29587fa79fd51a8e1836eba016c2a64419dc0981bac65daa356f6e180f2 +size 117154 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 59f5db0eb..1ef36810d 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-03 16:00+0800\n" +"POT-Creation-Date: 2022-11-15 15:52+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,20 +22,21 @@ msgstr "" msgid "Acls" msgstr "Acls" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:58 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:57 assets/models/cmd_filter.py:25 +#: assets/models/base.py:50 assets/models/cmd_filter.py:25 #: assets/models/domain.py:24 assets/models/group.py:20 -#: assets/models/label.py:17 assets/models/platform.py:22 -#: assets/models/platform.py:68 assets/serializers/asset/common.py:86 -#: assets/serializers/platform.py:104 ops/mixin.py:20 ops/models/playbook.py:9 -#: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 +#: assets/models/label.py:17 assets/models/platform.py:21 +#: assets/models/platform.py:72 assets/serializers/asset/common.py:86 +#: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 +#: ops/models/job.py:33 ops/models/playbook.py:13 orgs/models.py:70 +#: perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 #: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 -#: terminal/models/component/terminal.py:100 users/forms/profile.py:33 +#: terminal/models/component/terminal.py:82 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 #: xpack/plugins/cloud/models.py:30 msgid "Name" @@ -53,26 +54,25 @@ msgstr "1-100、低い値は最初に一致します" #: acls/models/base.py:31 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:74 terminal/models/session/sharing.py:28 +#: perms/models/asset_permission.py:67 terminal/models/session/sharing.py:28 #: tickets/const.py:38 msgid "Active" msgstr "アクティブ" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:100 assets/models/automations/base.py:26 -#: assets/models/backup.py:30 assets/models/base.py:65 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:22 +#: assets/models/backup.py:29 assets/models/base.py:58 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 -#: assets/models/platform.py:73 ops/models/playbook.py:11 -#: ops/models/playbook.py:25 orgs/models.py:74 -#: perms/models/asset_permission.py:84 rbac/models/role.py:37 +#: assets/models/platform.py:77 orgs/models.py:74 +#: perms/models/asset_permission.py:77 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 -#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:104 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 -#: terminal/models/component/terminal.py:114 tickets/models/comment.py:32 +#: terminal/models/component/terminal.py:93 tickets/models/comment.py:32 #: tickets/models/ticket/general.py:288 users/models/group.py:16 #: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 @@ -95,12 +95,12 @@ msgid "Login confirm" msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 -#: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:62 audits/models.py:87 -#: authentication/models/connection_token.py:22 -#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:58 -#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 -#: terminal/backends/command/models.py:20 +#: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 +#: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 +#: audits/models.py:79 authentication/models/connection_token.py:22 +#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:53 +#: perms/models/perm_token.py:12 rbac/builtin.py:120 +#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 #: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -114,14 +114,14 @@ msgid "Rule" msgstr "ルール" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:62 -#: assets/models/cmd_filter.py:81 audits/models.py:63 audits/serializers.py:49 +#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:77 +#: assets/models/cmd_filter.py:81 audits/models.py:50 audits/serializers.py:69 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "アクション" #: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32 -#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:86 +#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86 msgid "Reviewers" msgstr "レビュー担当者" @@ -129,19 +129,25 @@ msgstr "レビュー担当者" msgid "Login acl" msgstr "ログインacl" -#: acls/models/login_asset_acl.py:21 assets/models/account.py:59 +#: acls/models/login_asset_acl.py:21 assets/models/account.py:61 +#: assets/serializers/automations/change_secret.py:88 +#: assets/serializers/automations/change_secret.py:110 #: authentication/models/connection_token.py:33 ops/models/base.py:18 -#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 -#: xpack/plugins/cloud/serializers/task.py:65 +#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "アカウント" -#: acls/models/login_asset_acl.py:22 assets/models/account.py:49 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:51 #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 -#: assets/serializers/account/account.py:58 assets/serializers/label.py:30 -#: audits/models.py:39 authentication/models/connection_token.py:26 -#: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 +#: assets/serializers/account/account.py:59 +#: assets/serializers/automations/change_secret.py:87 +#: assets/serializers/automations/change_secret.py:109 +#: assets/serializers/gathered_user.py:11 assets/serializers/label.py:30 +#: audits/models.py:33 authentication/models/connection_token.py:26 +#: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 +#: terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 #: terminal/models/session/session.py:32 terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:200 @@ -158,14 +164,14 @@ msgstr "ログインasset acl" msgid "Login asset confirm" msgstr "ログイン資産の確認" -#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:13 +#: acls/serializers/login_acl.py:16 acls/serializers/login_asset_acl.py:14 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" -#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 -#: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 -#: assets/models/base.py:58 assets/models/gathered_user.py:15 -#: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 +#: acls/serializers/login_asset_acl.py:22 +#: acls/serializers/login_asset_acl.py:64 assets/models/_user.py:34 +#: assets/models/base.py:51 assets/models/gathered_user.py:15 +#: audits/models.py:95 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 @@ -177,7 +183,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること msgid "Username" msgstr "ユーザー名" -#: acls/serializers/login_asset_acl.py:25 +#: acls/serializers/login_asset_acl.py:29 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -187,7 +193,7 @@ msgstr "" "192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" "db8:1a:1110:::/64 (ドメイン名サポート)" -#: acls/serializers/login_asset_acl.py:32 acls/serializers/rules/rules.py:33 +#: acls/serializers/login_asset_acl.py:38 acls/serializers/rules/rules.py:33 #: assets/models/asset/common.py:92 assets/models/domain.py:65 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 @@ -196,12 +202,12 @@ msgstr "" msgid "IP" msgstr "IP" -#: acls/serializers/login_asset_acl.py:36 -#: assets/serializers/gathered_user.py:22 settings/serializers/terminal.py:7 +#: acls/serializers/login_asset_acl.py:44 +#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 msgid "Hostname" msgstr "ホスト名" -#: acls/serializers/login_asset_acl.py:43 +#: acls/serializers/login_asset_acl.py:51 msgid "" "Format for comma-delimited string, with * indicating a match all. Protocol " "options: {}" @@ -209,12 +215,12 @@ msgstr "" "コンマ区切り文字列の形式。* はすべて一致することを示します。プロトコルオプ" "ション: {}" -#: acls/serializers/login_asset_acl.py:84 +#: acls/serializers/login_asset_acl.py:108 #: tickets/serializers/ticket/ticket.py:86 msgid "The organization `{}` does not exist" msgstr "組織 '{}'は存在しません" -#: acls/serializers/login_asset_acl.py:89 +#: acls/serializers/login_asset_acl.py:114 msgid "None of the reviewers belong to Organization `{}`" msgstr "いずれのレビューアも組織 '{}' に属していません" @@ -242,23 +248,25 @@ msgid "Applications" msgstr "アプリケーション" #: applications/models.py:12 assets/models/label.py:20 -#: assets/models/platform.py:69 assets/serializers/asset/common.py:62 -#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:76 -#: assets/serializers/platform.py:105 +#: assets/models/platform.py:73 assets/serializers/asset/common.py:62 +#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 +#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:24 #: tickets/models/ticket/apply_application.py:14 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" msgstr "カテゴリ" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:74 -#: assets/models/platform.py:70 assets/serializers/asset/common.py:63 -#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24 +#: assets/models/automations/base.py:20 assets/models/cmd_filter.py:74 +#: assets/models/platform.py:74 assets/serializers/asset/common.py:63 +#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 +#: audits/serializers.py:40 ops/models/job.py:39 +#: perms/serializers/user_permission.py:25 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 -#: tickets/models/ticket/general.py:273 +#: tickets/models/ticket/general.py:273 tickets/serializers/flow.py:53 #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" @@ -277,6 +285,11 @@ msgstr "アプリケーション" msgid "Can match application" msgstr "アプリケーションを一致させることができます" +#: assets/api/automations/base.py:76 +#: xpack/plugins/change_auth_plan/api/asset.py:94 +msgid "The parameter 'action' must be [{}]" +msgstr "パラメータ 'action' は [{}] でなければなりません。" + #: assets/api/domain.py:52 msgid "Number required" msgstr "必要な数" @@ -297,13 +310,13 @@ msgstr "削除に失敗し、ノードにアセットが含まれています。 msgid "App assets" msgstr "アプリ資産" -#: assets/automations/base/manager.py:122 +#: assets/automations/base/manager.py:123 #, fuzzy #| msgid "Disabled" msgid "{} disabled" msgstr "無効" -#: assets/const/account.py:6 audits/const.py:5 +#: assets/const/account.py:6 audits/const.py:6 audits/const.py:63 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/utils.py:84 msgid "Unknown" @@ -313,19 +326,21 @@ msgstr "不明" msgid "Ok" msgstr "OK" -#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:19 +#: assets/const/account.py:8 +#: assets/serializers/automations/change_secret.py:105 +#: assets/serializers/automations/change_secret.py:133 audits/const.py:74 +#: common/const/choices.py:19 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 msgid "Failed" msgstr "失敗しました" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/base.py:52 assets/models/domain.py:71 -#: assets/serializers/base.py:15 audits/signal_handlers.py:50 +#: assets/models/domain.py:71 audits/signal_handlers.py:46 #: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:94 +#: users/forms/profile.py:22 users/serializers/user.py:105 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -337,19 +352,18 @@ msgstr "失敗しました" msgid "Password" msgstr "パスワード" -#: assets/const/account.py:13 assets/models/base.py:53 +#: assets/const/account.py:13 #, fuzzy #| msgid "SSH Key" msgid "SSH key" msgstr "SSHキー" -#: assets/const/account.py:14 assets/models/base.py:54 -#: authentication/models/access_key.py:31 +#: assets/const/account.py:14 authentication/models/access_key.py:31 msgid "Access key" msgstr "アクセスキー" #: assets/const/account.py:15 assets/models/_user.py:38 -#: assets/models/base.py:55 authentication/models/sso_token.py:13 +#: authentication/models/sso_token.py:13 msgid "Token" msgstr "トークン" @@ -387,31 +401,31 @@ msgstr "パスワード/キーの確認" msgid "Gather accounts" msgstr "アカウントを集める" -#: assets/const/automation.py:22 +#: assets/const/automation.py:38 assets/serializers/account/base.py:26 msgid "Specific" msgstr "" -#: assets/const/automation.py:23 ops/const.py:20 +#: assets/const/automation.py:39 ops/const.py:20 #: xpack/plugins/change_auth_plan/models/base.py:28 msgid "All assets use the same random password" msgstr "すべての資産は同じランダムパスワードを使用します" -#: assets/const/automation.py:24 ops/const.py:21 +#: assets/const/automation.py:40 ops/const.py:21 #: xpack/plugins/change_auth_plan/models/base.py:29 msgid "All assets use different random password" msgstr "すべての資産は異なるランダムパスワードを使用します" -#: assets/const/automation.py:28 ops/const.py:13 +#: assets/const/automation.py:44 ops/const.py:13 #: xpack/plugins/change_auth_plan/models/asset.py:30 msgid "Append SSH KEY" msgstr "追加" -#: assets/const/automation.py:29 ops/const.py:14 +#: assets/const/automation.py:45 ops/const.py:14 #: xpack/plugins/change_auth_plan/models/asset.py:31 msgid "Empty and append SSH KEY" msgstr "すべてクリアして追加" -#: assets/const/automation.py:30 ops/const.py:15 +#: assets/const/automation.py:46 ops/const.py:15 #: xpack/plugins/change_auth_plan/models/asset.py:32 msgid "Replace (The key generated by JumpServer) " msgstr "置換(JumpServerによって生成された鍵)" @@ -439,7 +453,8 @@ msgstr "データベース" msgid "Cloud service" msgstr "クラウドセンター" -#: assets/const/category.py:15 terminal/models/applet/applet.py:18 +#: assets/const/category.py:15 audits/const.py:61 +#: terminal/models/applet/applet.py:18 msgid "Web" msgstr "" @@ -485,7 +500,6 @@ msgid "Admin user" msgstr "管理ユーザー" #: assets/models/_user.py:36 assets/models/domain.py:72 -#: assets/serializers/base.py:19 #: xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 @@ -499,11 +513,12 @@ msgstr "SSH秘密鍵" msgid "SSH public key" msgstr "SSHパブリックキー" -#: assets/models/_user.py:41 assets/models/automations/base.py:96 +#: assets/models/_user.py:41 assets/models/automations/base.py:92 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:53 orgs/models.py:73 perms/models/asset_permission.py:82 -#: users/models/group.py:18 users/models/user.py:927 +#: ops/models/base.py:54 ops/models/job.py:62 orgs/models.py:73 +#: perms/models/asset_permission.py:75 users/models/group.py:18 +#: users/models/user.py:927 msgid "Date created" msgstr "作成された日付" @@ -512,10 +527,10 @@ msgstr "作成された日付" msgid "Date updated" msgstr "更新日" -#: assets/models/_user.py:43 assets/models/base.py:66 +#: assets/models/_user.py:43 assets/models/base.py:59 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 -#: orgs/models.py:71 perms/models/asset_permission.py:81 +#: orgs/models.py:71 perms/models/asset_permission.py:74 #: users/models/user.py:710 users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 msgid "Created by" @@ -526,7 +541,7 @@ msgid "Username same with user" msgstr "ユーザーと同じユーザー名" #: assets/models/_user.py:48 assets/models/domain.py:67 -#: authentication/models/connection_token.py:29 +#: authentication/models/connection_token.py:29 perms/models/perm_token.py:16 #: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 #: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" @@ -540,7 +555,7 @@ msgstr "オートプッシュ" msgid "Sudo" msgstr "すど" -#: assets/models/_user.py:51 +#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:29 msgid "Shell" msgstr "シェル" @@ -568,7 +583,7 @@ msgstr "ユーザースイッチ" msgid "Switch from" msgstr "から切り替え" -#: assets/models/_user.py:65 audits/models.py:40 +#: assets/models/_user.py:65 audits/models.py:34 #: terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:36 #: xpack/plugins/change_auth_plan/models/app.py:35 @@ -580,47 +595,64 @@ msgstr "システムユーザー" msgid "Can match system user" msgstr "システムユーザーに一致できます" -#: assets/models/account.py:53 +#: assets/models/account.py:45 common/db/fields.py:222 +#: settings/serializers/terminal.py:12 +msgid "All" +msgstr "すべて" + +#: assets/models/account.py:46 +#, fuzzy +#| msgid "Manually input" +msgid "Manual input" +msgstr "手動入力" + +#: assets/models/account.py:47 +#, fuzzy +#| msgid "Dynamic code" +msgid "Dynamic user" +msgstr "動的コード" + +#: assets/models/account.py:55 #, fuzzy #| msgid "Switch from" msgid "Su from" msgstr "から切り替え" -#: assets/models/account.py:55 settings/serializers/auth/cas.py:18 +#: assets/models/account.py:57 settings/serializers/auth/cas.py:18 #: terminal/models/applet/applet.py:22 msgid "Version" msgstr "バージョン" -#: assets/models/account.py:65 +#: assets/models/account.py:67 msgid "Can view asset account secret" msgstr "資産アカウントの秘密を表示できます" -#: assets/models/account.py:66 +#: assets/models/account.py:68 msgid "Can change asset account secret" msgstr "資産口座の秘密を変更できます" -#: assets/models/account.py:67 +#: assets/models/account.py:69 msgid "Can view asset history account" msgstr "資産履歴アカウントを表示できます" -#: assets/models/account.py:68 +#: assets/models/account.py:70 msgid "Can view asset history account secret" msgstr "資産履歴アカウントパスワードを表示できます" -#: assets/models/account.py:91 assets/serializers/account/account.py:13 +#: assets/models/account.py:93 assets/serializers/account/account.py:15 #, fuzzy #| msgid "Account name" msgid "Account template" msgstr "アカウント名" #: assets/models/asset/common.py:82 assets/models/domain.py:66 -#: assets/models/platform.py:23 settings/serializers/auth/radius.py:15 +#: assets/models/platform.py:22 settings/serializers/auth/radius.py:15 #: settings/serializers/auth/sms.py:57 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "ポート" -#: assets/models/asset/common.py:93 assets/models/platform.py:104 +#: assets/models/asset/common.py:93 assets/models/platform.py:110 #: assets/serializers/asset/common.py:65 #: perms/serializers/user_permission.py:21 #: xpack/plugins/cloud/serializers/account_attrs.py:172 @@ -632,17 +664,19 @@ msgstr "プラットフォーム" msgid "Domain" msgstr "ドメイン" -#: assets/models/asset/common.py:97 assets/models/automations/base.py:19 -#: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 +#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 +#: assets/serializers/asset/common.py:66 +#: assets/serializers/automations/base.py:21 +#: perms/models/asset_permission.py:62 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "ノード" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:25 -#: assets/models/base.py:64 assets/models/cmd_filter.py:39 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:21 +#: assets/models/base.py:57 assets/models/cmd_filter.py:39 #: assets/models/domain.py:70 assets/models/label.py:21 -#: terminal/models/applet/applet.py:25 users/serializers/user.py:147 +#: terminal/models/applet/applet.py:25 users/serializers/user.py:202 msgid "Is active" msgstr "アクティブです。" @@ -676,8 +710,8 @@ msgstr "ノードにアセットを追加する" msgid "Move asset to node" msgstr "アセットをノードに移動する" -#: assets/models/asset/web.py:9 audits/models.py:111 -#: terminal/serializers/applet_host.py:24 +#: assets/models/asset/web.py:9 audits/const.py:67 +#: terminal/serializers/applet_host.py:26 msgid "Disabled" msgstr "無効" @@ -695,97 +729,111 @@ msgstr "" msgid "Autofill" msgstr "自動" -#: assets/models/asset/web.py:14 assets/serializers/platform.py:29 +#: assets/models/asset/web.py:14 assets/serializers/platform.py:30 #, fuzzy #| msgid "Username attr" msgid "Username selector" msgstr "ユーザー名のプロパティ" -#: assets/models/asset/web.py:15 assets/serializers/platform.py:30 +#: assets/models/asset/web.py:15 assets/serializers/platform.py:33 #, fuzzy #| msgid "Password rules" msgid "Password selector" msgstr "パスワードルール" -#: assets/models/asset/web.py:16 assets/serializers/platform.py:31 +#: assets/models/asset/web.py:16 assets/serializers/platform.py:36 msgid "Submit selector" msgstr "" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 -#: rbac/tree.py:37 +#: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 +#: perms/serializers/permission.py:32 rbac/tree.py:37 msgid "Accounts" msgstr "アカウント" -#: assets/models/automations/base.py:22 assets/serializers/domain.py:29 -#: ops/models/base.py:17 +#: assets/models/automations/base.py:19 +#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 +#: ops/models/base.py:17 ops/models/job.py:41 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "資産" -#: assets/models/automations/base.py:86 assets/models/automations/base.py:93 +#: assets/models/automations/base.py:82 assets/models/automations/base.py:89 #, fuzzy #| msgid "Automatic managed" msgid "Automation task" msgstr "自動管理" -#: assets/models/automations/base.py:97 assets/models/backup.py:77 -#: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:102 +#: assets/models/automations/base.py:91 audits/models.py:115 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:57 +#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 +#: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 +#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:223 +msgid "Status" +msgstr "ステータス" + +#: assets/models/automations/base.py:93 assets/models/backup.py:76 +#: audits/models.py:40 ops/models/base.py:55 ops/models/job.py:63 +#: perms/models/asset_permission.py:69 terminal/models/applet/host.py:105 #: terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 -#: tickets/models/ticket/apply_asset.py:21 +#: tickets/models/ticket/apply_asset.py:18 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:199 #: xpack/plugins/gathered_user/models.py:71 msgid "Date start" msgstr "開始日" -#: assets/models/automations/base.py:98 -#: assets/models/automations/change_secret.py:58 ops/models/base.py:55 -#: terminal/models/applet/host.py:103 +#: assets/models/automations/base.py:94 +#: assets/models/automations/change_secret.py:59 ops/models/base.py:56 +#: ops/models/job.py:64 terminal/models/applet/host.py:106 msgid "Date finished" msgstr "終了日" -#: assets/models/automations/base.py:100 +#: assets/models/automations/base.py:96 +#: assets/serializers/automations/base.py:39 #, fuzzy #| msgid "Relation snapshot" msgid "Automation snapshot" msgstr "製造オーダスナップショット" -#: assets/models/automations/base.py:104 assets/models/backup.py:88 -#: assets/serializers/account/backup.py:36 +#: assets/models/automations/base.py:100 assets/models/backup.py:87 +#: assets/serializers/account/backup.py:37 +#: assets/serializers/automations/base.py:41 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "トリガーモード" -#: assets/models/automations/base.py:108 +#: assets/models/automations/base.py:104 +#: assets/serializers/automations/change_secret.py:90 #, fuzzy #| msgid "Command execution" msgid "Automation task execution" msgstr "コマンド実行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:60 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:53 +#: assets/serializers/account/account.py:95 assets/serializers/base.py:13 #, fuzzy #| msgid "Secret key" msgid "Secret type" msgstr "秘密キー" #: assets/models/automations/change_secret.py:19 +#: assets/serializers/automations/change_secret.py:25 #, fuzzy #| msgid "SSH Key strategy" msgid "Secret strategy" msgstr "SSHキー戦略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:56 assets/models/base.py:62 -#: assets/serializers/account/base.py:17 -#: authentication/models/connection_token.py:34 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:55 +#: assets/serializers/base.py:16 authentication/models/connection_token.py:34 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 +#: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "ひみつ" @@ -800,8 +848,9 @@ msgstr "パスワードルール" msgid "SSH key change strategy" msgstr "SSHキー戦略" -#: assets/models/automations/change_secret.py:27 assets/models/backup.py:28 -#: assets/serializers/account/backup.py:28 +#: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 +#: assets/serializers/account/backup.py:30 +#: assets/serializers/automations/change_secret.py:40 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 #: xpack/plugins/change_auth_plan/serializers/base.py:45 @@ -814,25 +863,25 @@ msgstr "受信者" msgid "Change secret automation" msgstr "秘密を改める" -#: assets/models/automations/change_secret.py:55 +#: assets/models/automations/change_secret.py:56 #, fuzzy #| msgid "Secret" msgid "Old secret" msgstr "ひみつ" -#: assets/models/automations/change_secret.py:57 +#: assets/models/automations/change_secret.py:58 #, fuzzy #| msgid "Date start" msgid "Date started" msgstr "開始日" -#: assets/models/automations/change_secret.py:60 common/const/choices.py:20 +#: assets/models/automations/change_secret.py:61 common/const/choices.py:20 #, fuzzy #| msgid "WeCom Error" msgid "Error" msgstr "企業微信エラー" -#: assets/models/automations/change_secret.py:63 +#: assets/models/automations/change_secret.py:64 #, fuzzy #| msgid "Change auth" msgid "Change secret record" @@ -845,6 +894,7 @@ msgid "Discovery account automation" msgstr "パスワード/キーの確認" #: assets/models/automations/gather_accounts.py:15 +#: assets/tasks/gather_accounts.py:28 #, fuzzy #| msgid "Gather assets users" msgid "Gather asset accounts" @@ -874,24 +924,24 @@ msgstr "サービスアカウントです" msgid "Verify asset account" msgstr "パスワード/キーの確認" -#: assets/models/backup.py:38 assets/models/backup.py:96 +#: assets/models/backup.py:37 assets/models/backup.py:95 msgid "Account backup plan" msgstr "アカウントバックアップ計画" -#: assets/models/backup.py:80 +#: assets/models/backup.py:79 #: authentication/templates/authentication/_msg_oauth_bind.html:11 -#: notifications/notifications.py:187 +#: notifications/notifications.py:186 #: xpack/plugins/change_auth_plan/models/base.py:111 #: xpack/plugins/change_auth_plan/models/base.py:200 #: xpack/plugins/gathered_user/models.py:74 msgid "Time" msgstr "時間" -#: assets/models/backup.py:84 +#: assets/models/backup.py:83 msgid "Account backup snapshot" msgstr "アカウントのバックアップスナップショット" -#: assets/models/backup.py:91 audits/models.py:127 +#: assets/models/backup.py:90 audits/models.py:110 #: terminal/models/session/sharing.py:108 #: xpack/plugins/change_auth_plan/models/base.py:197 #: xpack/plugins/change_auth_plan/serializers/asset.py:171 @@ -899,29 +949,32 @@ msgstr "アカウントのバックアップスナップショット" msgid "Reason" msgstr "理由" -#: assets/models/backup.py:93 terminal/serializers/session.py:36 +#: assets/models/backup.py:92 +#: assets/serializers/automations/change_secret.py:86 +#: assets/serializers/automations/change_secret.py:111 +#: terminal/serializers/session.py:36 #: xpack/plugins/change_auth_plan/models/base.py:198 #: xpack/plugins/change_auth_plan/serializers/asset.py:173 msgid "Is success" msgstr "成功は" -#: assets/models/backup.py:100 +#: assets/models/backup.py:99 msgid "Account backup execution" msgstr "アカウントバックアップの実行" -#: assets/models/base.py:29 assets/serializers/domain.py:42 +#: assets/models/base.py:28 assets/serializers/domain.py:42 msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:31 authentication/models/temp_token.py:12 +#: assets/models/base.py:30 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:63 +#: assets/models/base.py:56 msgid "Privileged" msgstr "" -#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:61 +#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:56 #: users/models/group.py:31 users/models/user.py:671 msgid "User group" msgstr "ユーザーグループ" @@ -992,7 +1045,7 @@ msgstr "テストゲートウェイ" msgid "Unable to connect to port {port} on {address}" msgstr "{ip} でポート {port} に接続できません" -#: assets/models/domain.py:145 authentication/middleware.py:75 +#: assets/models/domain.py:145 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "認証に失敗しました" @@ -1021,7 +1074,7 @@ msgstr "収集ユーザー" msgid "Asset group" msgstr "資産グループ" -#: assets/models/group.py:34 assets/models/platform.py:20 +#: assets/models/group.py:34 assets/models/platform.py:19 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" msgstr "デフォルト" @@ -1054,7 +1107,7 @@ msgstr "新しいノード" msgid "empty" msgstr "空" -#: assets/models/node.py:552 perms/models/asset_permission.py:190 +#: assets/models/node.py:552 perms/models/perm_node.py:21 msgid "Key" msgstr "キー" @@ -1062,7 +1115,7 @@ msgstr "キー" msgid "Full value" msgstr "フルバリュー" -#: assets/models/node.py:557 perms/models/asset_permission.py:191 +#: assets/models/node.py:557 perms/models/perm_node.py:22 msgid "Parent key" msgstr "親キー" @@ -1075,60 +1128,60 @@ msgstr "ノード" msgid "Can match node" msgstr "ノードを一致させることができます" -#: assets/models/platform.py:21 +#: assets/models/platform.py:20 #, fuzzy #| msgid "MFA required" msgid "Required" msgstr "MFAが必要" -#: assets/models/platform.py:24 users/templates/users/reset_password.html:29 +#: assets/models/platform.py:23 users/templates/users/reset_password.html:29 msgid "Setting" msgstr "設定" -#: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37 -#: terminal/serializers/applet_host.py:25 +#: assets/models/platform.py:42 audits/const.py:68 settings/models.py:37 +#: terminal/serializers/applet_host.py:27 msgid "Enabled" msgstr "有効化" -#: assets/models/platform.py:44 +#: assets/models/platform.py:43 msgid "Ansible config" msgstr "" -#: assets/models/platform.py:45 +#: assets/models/platform.py:44 #, fuzzy #| msgid "MFA enabled" msgid "Ping enabled" msgstr "MFA有効化" -#: assets/models/platform.py:46 +#: assets/models/platform.py:45 msgid "Ping method" msgstr "" -#: assets/models/platform.py:47 assets/models/platform.py:55 +#: assets/models/platform.py:46 assets/models/platform.py:56 #, fuzzy #| msgid "Gather assets users" msgid "Gather facts enabled" msgstr "資産ユーザーの収集" -#: assets/models/platform.py:48 assets/models/platform.py:56 +#: assets/models/platform.py:47 assets/models/platform.py:58 #, fuzzy #| msgid "Gather assets users" msgid "Gather facts method" msgstr "資産ユーザーの収集" -#: assets/models/platform.py:49 +#: assets/models/platform.py:48 #, fuzzy #| msgid "Create account successfully" msgid "Push account enabled" msgstr "アカウントを正常に作成" -#: assets/models/platform.py:50 +#: assets/models/platform.py:49 #, fuzzy #| msgid "Create account successfully" msgid "Push account method" msgstr "アカウントを正常に作成" -#: assets/models/platform.py:51 +#: assets/models/platform.py:50 #, fuzzy #| msgid "Change Password" msgid "Change password enabled" @@ -1146,47 +1199,47 @@ msgstr "パスワードの変更" msgid "Verify account enabled" msgstr "サービスアカウントキー" -#: assets/models/platform.py:54 +#: assets/models/platform.py:55 #, fuzzy #| msgid "Verify auth" msgid "Verify account method" msgstr "パスワード/キーの確認" -#: assets/models/platform.py:71 tickets/models/ticket/general.py:298 +#: assets/models/platform.py:75 tickets/models/ticket/general.py:298 msgid "Meta" msgstr "メタ" -#: assets/models/platform.py:72 +#: assets/models/platform.py:76 msgid "Internal" msgstr "内部" -#: assets/models/platform.py:75 +#: assets/models/platform.py:80 assets/serializers/platform.py:96 msgid "Charset" msgstr "シャーセット" -#: assets/models/platform.py:76 +#: assets/models/platform.py:82 #, fuzzy #| msgid "Domain name" msgid "Domain enabled" msgstr "ドメイン名" -#: assets/models/platform.py:77 +#: assets/models/platform.py:83 #, fuzzy #| msgid "Protocols" msgid "Protocols enabled" msgstr "プロトコル" -#: assets/models/platform.py:79 +#: assets/models/platform.py:85 #, fuzzy #| msgid "MFA enabled" msgid "Su enabled" msgstr "MFA有効化" -#: assets/models/platform.py:80 +#: assets/models/platform.py:86 msgid "SU method" msgstr "" -#: assets/models/platform.py:82 assets/serializers/platform.py:78 +#: assets/models/platform.py:88 assets/serializers/platform.py:103 #, fuzzy #| msgid "Automatic managed" msgid "Automation" @@ -1209,7 +1262,7 @@ msgstr "" "{} -アカウントバックアップの通過タスクが完了しました。詳細は添付ファイルをご" "覧ください" -#: assets/notifications.py:19 +#: assets/notifications.py:20 msgid "" "{} - The account backup passage task has been completed: the encryption " "password has not been set - please go to personal information -> file " @@ -1219,46 +1272,55 @@ msgstr "" "されていません-個人情報にアクセスしてください-> ファイル暗号化パスワードを設" "定してください暗号化パスワード" -#: assets/serializers/account/account.py:16 +#: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 +msgid "Notification of implementation result of encryption change plan" +msgstr "暗号化変更プランの実装結果の通知" + +#: assets/notifications.py:41 +#: xpack/plugins/change_auth_plan/notifications.py:18 +msgid "" +"{} - The encryption change task has been completed. See the attachment for " +"details" +msgstr "{} -暗号化変更タスクが完了しました。詳細は添付ファイルをご覧ください" + +#: assets/notifications.py:42 +#: xpack/plugins/change_auth_plan/notifications.py:19 +msgid "" +"{} - The encryption change task has been completed: the encryption password " +"has not been set - please go to personal information -> file encryption " +"password to set the encryption password" +msgstr "" +"{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" +"情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" + +#: assets/serializers/account/account.py:18 msgid "Push now" msgstr "" -#: assets/serializers/account/account.py:18 +#: assets/serializers/account/account.py:20 #, fuzzy #| msgid "Secret" msgid "Has secret" msgstr "ひみつ" -#: assets/serializers/account/account.py:25 +#: assets/serializers/account/account.py:27 msgid "Account template not found" msgstr "" -#: assets/serializers/account/backup.py:27 ops/mixin.py:102 +#: assets/serializers/account/backup.py:29 +#: assets/serializers/automations/base.py:34 ops/mixin.py:102 #: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定期的なパフォーマンス" -#: assets/serializers/account/backup.py:29 +#: assets/serializers/account/backup.py:31 +#: assets/serializers/automations/change_secret.py:41 #: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/account/base.py:39 assets/serializers/base.py:34 -msgid "private key invalid or passphrase error" -msgstr "秘密鍵が無効またはpassphraseエラー" - -#: assets/serializers/account/template.py:16 common/drf/fields.py:69 -#: tickets/serializers/ticket/common.py:58 -#: xpack/plugins/change_auth_plan/serializers/asset.py:64 -#: xpack/plugins/change_auth_plan/serializers/asset.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:70 -#: xpack/plugins/change_auth_plan/serializers/asset.py:101 -#: xpack/plugins/cloud/serializers/account_attrs.py:56 -msgid "This field is required." -msgstr "このフィールドは必須です。" - -#: assets/serializers/asset/common.py:69 assets/serializers/platform.py:77 +#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 #: xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "プロトコル" @@ -1345,7 +1407,37 @@ msgstr "資産番号" msgid "IP/Host" msgstr "ホスト" -#: assets/serializers/base.py:24 +#: assets/serializers/automations/change_secret.py:28 +#: xpack/plugins/change_auth_plan/models/asset.py:50 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +msgid "SSH Key strategy" +msgstr "SSHキー戦略" + +#: assets/serializers/automations/change_secret.py:57 +#: xpack/plugins/change_auth_plan/serializers/base.py:58 +msgid "* Please enter the correct password length" +msgstr "* 正しいパスワードの長さを入力してください" + +#: assets/serializers/automations/change_secret.py:60 +#: xpack/plugins/change_auth_plan/serializers/base.py:61 +msgid "* Password length range 6-30 bits" +msgstr "* パスワードの長さの範囲6-30ビット" + +#: assets/serializers/automations/change_secret.py:104 +#: assets/serializers/automations/change_secret.py:132 audits/const.py:73 +#: audits/models.py:39 common/const/choices.py:18 +#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 +#: xpack/plugins/change_auth_plan/serializers/asset.py:189 +msgid "Success" +msgstr "成功" + +#: assets/serializers/automations/gather_accounts.py:23 +#, fuzzy +#| msgid "Executed times" +msgid "Executed amount" +msgstr "実行時間" + +#: assets/serializers/base.py:21 msgid "Key password" msgstr "キーパスワード" @@ -1360,7 +1452,6 @@ msgid "Types" msgstr "タイプ" #: assets/serializers/domain.py:14 assets/serializers/label.py:12 -#: perms/serializers/permission.py:83 msgid "Assets amount" msgstr "資産額" @@ -1368,15 +1459,10 @@ msgstr "資産額" msgid "Gateways count" msgstr "ゲートウェイ数" -#: assets/serializers/label.py:13 assets/serializers/mixin.py:7 +#: assets/serializers/label.py:13 msgid "Category display" msgstr "カテゴリ表示" -#: assets/serializers/mixin.py:10 audits/serializers.py:27 -#: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 -msgid "Type display" -msgstr "タイプ表示" - #: assets/serializers/node.py:17 msgid "value" msgstr "値" @@ -1407,54 +1493,104 @@ msgstr "SFTPルート" msgid "Auto fill" msgstr "自動" -#: assets/serializers/platform.py:64 +#: assets/serializers/platform.py:78 msgid "Primary" msgstr "" -#: assets/serializers/utils.py:11 +#: assets/serializers/utils.py:15 msgid "Password can not contains `{{` " msgstr "パスワードには '{{' を含まない" -#: assets/serializers/utils.py:14 +#: assets/serializers/utils.py:18 msgid "Password can not contains `'` " msgstr "パスワードには `'` を含まない" -#: assets/serializers/utils.py:16 +#: assets/serializers/utils.py:20 msgid "Password can not contains `\"` " msgstr "パスワードには `\"` を含まない" -#: assets/tasks/gather_facts.py:25 +#: assets/serializers/utils.py:26 +msgid "private key invalid or passphrase error" +msgstr "秘密鍵が無効またはpassphraseエラー" + +#: assets/tasks/automation.py:11 +#, fuzzy +#| msgid "Verify auth" +msgid "Execute automation" +msgstr "パスワード/キーの確認" + +#: assets/tasks/backup.py:13 +#, fuzzy +#| msgid "Account backup plan" +msgid "Execute account backup plan" +msgstr "アカウントバックアップ計画" + +#: assets/tasks/gather_accounts.py:31 +#, fuzzy +#| msgid "Gather assets users" +msgid "Gather assets accounts" +msgstr "資産ユーザーの収集" + +#: assets/tasks/gather_facts.py:26 msgid "Update some assets hardware info. " msgstr "一部の資産ハードウェア情報を更新します。" -#: assets/tasks/gather_facts.py:48 +#: assets/tasks/gather_facts.py:44 +#, fuzzy +#| msgid "Update node asset hardware information: " +msgid "Manually update the hardware information of assets" +msgstr "ノード資産のハードウェア情報を更新します。" + +#: assets/tasks/gather_facts.py:49 msgid "Update assets hardware info: " msgstr "資産のハードウェア情報を更新する:" -#: assets/tasks/gather_facts.py:58 +#: assets/tasks/gather_facts.py:53 +msgid "Manually update the hardware information of assets under a node" +msgstr "" + +#: assets/tasks/gather_facts.py:59 msgid "Update node asset hardware information: " msgstr "ノード資産のハードウェア情報を更新します。" -#: assets/tasks/nodes_amount.py:29 +#: assets/tasks/nodes_amount.py:16 +msgid "Check the amount of assets under the node" +msgstr "" + +#: assets/tasks/nodes_amount.py:28 msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "" "セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ" "ん" -#: assets/tasks/ping.py:20 assets/tasks/ping.py:38 +#: assets/tasks/nodes_amount.py:34 +msgid "Periodic check the amount of assets under the node" +msgstr "" + +#: assets/tasks/ping.py:21 assets/tasks/ping.py:39 #, fuzzy #| msgid "Test assets connectivity. " msgid "Test assets connectivity " msgstr "資産の接続性をテストします。" -#: assets/tasks/ping.py:48 +#: assets/tasks/ping.py:33 +#, fuzzy +#| msgid "Can test asset connectivity" +msgid "Manually test the connectivity of a asset" +msgstr "資産接続をテストできます" + +#: assets/tasks/ping.py:43 +msgid "Manually test the connectivity of assets under a node" +msgstr "" + +#: assets/tasks/ping.py:49 #, fuzzy #| msgid "Test if the assets under the node are connectable: " msgid "Test if the assets under the node are connectable " msgstr "ノードの下のアセットが接続可能かどうかをテストします。" -#: assets/tasks/push_account.py:36 +#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:31 #, fuzzy #| msgid "Create account successfully" msgid "Push accounts to assets" @@ -1476,7 +1612,13 @@ msgstr "セキュリティのために、ユーザー {} をプッシュしな msgid "No assets matched, stop task" msgstr "一致する資産がない、タスクを停止" -#: assets/tasks/verify_account.py:36 +#: assets/tasks/verify_account.py:30 +#, fuzzy +#| msgid "Verify auth" +msgid "Verify asset account availability" +msgstr "パスワード/キーの確認" + +#: assets/tasks/verify_account.py:37 #, fuzzy #| msgid "Test account connectivity: " msgid "Verify accounts connectivity" @@ -1486,278 +1628,257 @@ msgstr "テストアカウント接続:" msgid "Audits" msgstr "監査" -#: audits/models.py:27 audits/models.py:59 +#: audits/const.py:44 +msgid "Mkdir" +msgstr "Mkdir" + +#: audits/const.py:45 +msgid "Rmdir" +msgstr "Rmdir" + +#: audits/const.py:46 audits/const.py:56 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:226 msgid "Delete" msgstr "削除" -#: audits/models.py:28 +#: audits/const.py:47 perms/const.py:14 msgid "Upload" msgstr "アップロード" -#: audits/models.py:29 -msgid "Download" -msgstr "ダウンロード" - -#: audits/models.py:30 -msgid "Rmdir" -msgstr "Rmdir" - -#: audits/models.py:31 +#: audits/const.py:48 msgid "Rename" msgstr "名前の変更" -#: audits/models.py:32 -msgid "Mkdir" -msgstr "Mkdir" - -#: audits/models.py:33 +#: audits/const.py:49 msgid "Symlink" msgstr "Symlink" -#: audits/models.py:38 audits/models.py:66 audits/models.py:89 -#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 -msgid "Remote addr" -msgstr "リモートaddr" +#: audits/const.py:50 perms/const.py:15 +msgid "Download" +msgstr "ダウンロード" -#: audits/models.py:41 -msgid "Operate" -msgstr "操作" +#: audits/const.py:54 rbac/tree.py:224 +msgid "View" +msgstr "表示" -#: audits/models.py:42 -msgid "Filename" -msgstr "ファイル名" +#: audits/const.py:55 rbac/tree.py:225 templates/_csv_import_export.html:18 +#: templates/_csv_update_modal.html:6 +msgid "Update" +msgstr "更新" -#: audits/models.py:43 audits/models.py:117 common/const/choices.py:18 -#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 -#: xpack/plugins/change_auth_plan/serializers/asset.py:189 -msgid "Success" -msgstr "成功" - -#: audits/models.py:47 -msgid "File transfer log" -msgstr "ファイル転送ログ" - -#: audits/models.py:56 +#: audits/const.py:57 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:223 msgid "Create" msgstr "作成" -#: audits/models.py:57 rbac/tree.py:224 -msgid "View" -msgstr "表示" +#: audits/const.py:62 terminal/models/applet/host.py:24 +#: terminal/models/component/terminal.py:159 +msgid "Terminal" +msgstr "ターミナル" -#: audits/models.py:58 rbac/tree.py:225 templates/_csv_import_export.html:18 -#: templates/_csv_update_modal.html:6 -msgid "Update" -msgstr "更新" +#: audits/const.py:69 +msgid "-" +msgstr "-" -#: audits/models.py:64 audits/serializers.py:61 +#: audits/models.py:31 audits/models.py:55 audits/models.py:82 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 +msgid "Remote addr" +msgstr "リモートaddr" + +#: audits/models.py:36 audits/serializers.py:19 +msgid "Operate" +msgstr "操作" + +#: audits/models.py:38 +msgid "Filename" +msgstr "ファイル名" + +#: audits/models.py:43 +msgid "File transfer log" +msgstr "ファイル転送ログ" + +#: audits/models.py:52 audits/serializers.py:85 msgid "Resource Type" msgstr "リソースタイプ" -#: audits/models.py:65 +#: audits/models.py:53 msgid "Resource" msgstr "リソース" -#: audits/models.py:67 audits/models.py:90 +#: audits/models.py:58 audits/models.py:84 #: terminal/backends/command/serializers.py:40 msgid "Datetime" msgstr "時間" -#: audits/models.py:82 +#: audits/models.py:74 msgid "Operate log" msgstr "ログの操作" -#: audits/models.py:88 +#: audits/models.py:80 msgid "Change by" msgstr "による変更" -#: audits/models.py:96 +#: audits/models.py:90 msgid "Password change log" msgstr "パスワード変更ログ" -#: audits/models.py:113 -msgid "-" -msgstr "-" - -#: audits/models.py:122 +#: audits/models.py:97 msgid "Login type" msgstr "ログインタイプ" -#: audits/models.py:123 tickets/models/ticket/login_confirm.py:10 +#: audits/models.py:99 tickets/models/ticket/login_confirm.py:10 msgid "Login ip" msgstr "ログインIP" -#: audits/models.py:124 +#: audits/models.py:101 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/models/ticket/login_confirm.py:11 msgid "Login city" msgstr "ログイン都市" -#: audits/models.py:125 audits/serializers.py:42 +#: audits/models.py:104 audits/serializers.py:62 msgid "User agent" msgstr "ユーザーエージェント" -#: audits/models.py:126 +#: audits/models.py:107 audits/serializers.py:39 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:688 #: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" -#: audits/models.py:128 ops/models/base.py:48 -#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:101 -#: terminal/models/component/status.py:33 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 -#: xpack/plugins/cloud/models.py:223 -msgid "Status" -msgstr "ステータス" - -#: audits/models.py:129 +#: audits/models.py:117 msgid "Date login" msgstr "日付ログイン" -#: audits/models.py:130 audits/serializers.py:44 +#: audits/models.py:119 audits/serializers.py:64 msgid "Authentication backend" msgstr "認証バックエンド" -#: audits/models.py:169 +#: audits/models.py:160 msgid "User login log" msgstr "ユーザーログインログ" -#: audits/serializers.py:12 -msgid "Operate display" -msgstr "ディスプレイを操作する" - -#: audits/serializers.py:28 tickets/serializers/ticket/ticket.py:18 -msgid "Status display" -msgstr "ステータス表示" - -#: audits/serializers.py:29 -msgid "MFA display" -msgstr "MFAディスプレイ" - -#: audits/serializers.py:43 +#: audits/serializers.py:63 msgid "Reason display" msgstr "理由表示" -#: audits/signal_handlers.py:49 +#: audits/signal_handlers.py:45 msgid "SSH Key" msgstr "SSHキー" -#: audits/signal_handlers.py:51 +#: audits/signal_handlers.py:47 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:52 +#: audits/signal_handlers.py:48 msgid "Auth Token" msgstr "認証トークン" -#: audits/signal_handlers.py:53 authentication/notifications.py:73 +#: audits/signal_handlers.py:49 authentication/notifications.py:73 #: authentication/views/login.py:73 authentication/views/wecom.py:178 #: notifications/backends/__init__.py:11 users/models/user.py:724 msgid "WeCom" msgstr "企業微信" -#: audits/signal_handlers.py:54 authentication/views/feishu.py:144 +#: audits/signal_handlers.py:50 authentication/views/feishu.py:145 #: authentication/views/login.py:85 notifications/backends/__init__.py:14 #: users/models/user.py:726 msgid "FeiShu" msgstr "本を飛ばす" -#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179 +#: audits/signal_handlers.py:51 authentication/views/dingtalk.py:180 #: authentication/views/login.py:79 notifications/backends/__init__.py:12 #: users/models/user.py:725 msgid "DingTalk" msgstr "DingTalk" -#: audits/signal_handlers.py:56 authentication/models/temp_token.py:16 +#: audits/signal_handlers.py:52 authentication/models/temp_token.py:16 msgid "Temporary token" msgstr "仮パスワード" -#: audits/signal_handlers.py:68 +#: audits/signal_handlers.py:64 msgid "User and Group" msgstr "ユーザーとグループ" -#: audits/signal_handlers.py:69 +#: audits/signal_handlers.py:65 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} に参加 {UserGroup}" -#: audits/signal_handlers.py:70 +#: audits/signal_handlers.py:66 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} のそばを通る {UserGroup}" -#: audits/signal_handlers.py:73 +#: audits/signal_handlers.py:69 msgid "Node and Asset" msgstr "ノードと資産" -#: audits/signal_handlers.py:74 +#: audits/signal_handlers.py:70 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 追加 {Asset}" -#: audits/signal_handlers.py:75 +#: audits/signal_handlers.py:71 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 削除 {Asset}" -#: audits/signal_handlers.py:78 +#: audits/signal_handlers.py:74 msgid "User asset permissions" msgstr "ユーザー資産の権限" -#: audits/signal_handlers.py:79 +#: audits/signal_handlers.py:75 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 追加 {User}" -#: audits/signal_handlers.py:80 +#: audits/signal_handlers.py:76 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 削除 {User}" -#: audits/signal_handlers.py:83 +#: audits/signal_handlers.py:79 msgid "User group asset permissions" msgstr "ユーザーグループの資産権限" -#: audits/signal_handlers.py:84 +#: audits/signal_handlers.py:80 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 追加 {UserGroup}" -#: audits/signal_handlers.py:85 +#: audits/signal_handlers.py:81 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:88 perms/models/asset_permission.py:90 +#: audits/signal_handlers.py:84 perms/models/asset_permission.py:83 msgid "Asset permission" msgstr "資産権限" -#: audits/signal_handlers.py:89 +#: audits/signal_handlers.py:85 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 追加 {Asset}" -#: audits/signal_handlers.py:90 +#: audits/signal_handlers.py:86 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 削除 {Asset}" -#: audits/signal_handlers.py:93 +#: audits/signal_handlers.py:89 msgid "Node permission" msgstr "ノード権限" -#: audits/signal_handlers.py:94 +#: audits/signal_handlers.py:90 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 追加 {Node}" -#: audits/signal_handlers.py:95 +#: audits/signal_handlers.py:91 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 削除 {Node}" @@ -1980,12 +2101,12 @@ msgstr "企業の微信はすでにバインドされています" msgid "WeCom is not bound" msgstr "企業の微信をバインドしていません" -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:242 -#: authentication/views/dingtalk.py:296 +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 +#: authentication/views/dingtalk.py:297 msgid "DingTalk is not bound" msgstr "DingTalkはバインドされていません" -#: authentication/errors/mfa.py:33 authentication/views/feishu.py:203 +#: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 msgid "FeiShu is not bound" msgstr "本を飛ばすは拘束されていません" @@ -2078,7 +2199,7 @@ msgstr "電話番号を設定して有効にする" msgid "Clear phone number to disable" msgstr "無効にする電話番号をクリアする" -#: authentication/middleware.py:76 settings/utils/ldap.py:652 +#: authentication/middleware.py:77 settings/utils/ldap.py:652 msgid "Authentication failed (before login check failed): {}" msgstr "認証に失敗しました (ログインチェックが失敗する前): {}" @@ -2100,9 +2221,9 @@ msgid "Asset display" msgstr "アセット名" #: authentication/models/connection_token.py:36 -#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:79 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:72 #: tickets/models/ticket/apply_application.py:29 -#: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 +#: tickets/models/ticket/apply_asset.py:19 users/models/user.py:707 msgid "Date expired" msgstr "期限切れの日付" @@ -2168,17 +2289,17 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" -#: authentication/serializers/connection_token.py:20 +#: authentication/serializers/connection_token.py:19 #: xpack/plugins/cloud/models.py:36 msgid "Validity" msgstr "有効性" -#: authentication/serializers/connection_token.py:21 +#: authentication/serializers/connection_token.py:20 msgid "Expired time" msgstr "期限切れ時間" -#: authentication/serializers/token.py:79 perms/serializers/permission.py:60 -#: perms/serializers/permission.py:87 users/serializers/user.py:148 +#: authentication/serializers/token.py:79 perms/serializers/permission.py:30 +#: perms/serializers/permission.py:61 users/serializers/user.py:203 msgid "Is valid" msgstr "有効です" @@ -2265,7 +2386,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 ops/tasks.py:147 ops/tasks.py:153 ops/tasks.py:156 +#: jumpserver/conf.py:390 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2418,73 +2539,73 @@ msgstr "コピー成功" msgid "LAN" msgstr "ローカルエリアネットワーク" -#: authentication/views/dingtalk.py:41 +#: authentication/views/dingtalk.py:42 msgid "DingTalk Error, Please contact your system administrator" msgstr "DingTalkエラー、システム管理者に連絡してください" -#: authentication/views/dingtalk.py:44 +#: authentication/views/dingtalk.py:45 msgid "DingTalk Error" msgstr "DingTalkエラー" -#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:51 +#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 #: authentication/views/wecom.py:56 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "システム設定が正しくありません。管理者に連絡してください" -#: authentication/views/dingtalk.py:80 +#: authentication/views/dingtalk.py:81 msgid "DingTalk is already bound" msgstr "DingTalkはすでにバインドされています" -#: authentication/views/dingtalk.py:148 authentication/views/wecom.py:148 +#: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 msgid "Invalid user_id" msgstr "無効なuser_id" -#: authentication/views/dingtalk.py:164 +#: authentication/views/dingtalk.py:165 msgid "DingTalk query user failed" msgstr "DingTalkクエリユーザーが失敗しました" -#: authentication/views/dingtalk.py:173 +#: authentication/views/dingtalk.py:174 msgid "The DingTalk is already bound to another user" msgstr "DingTalkはすでに別のユーザーにバインドされています" -#: authentication/views/dingtalk.py:180 +#: authentication/views/dingtalk.py:181 msgid "Binding DingTalk successfully" msgstr "DingTalkのバインドに成功" -#: authentication/views/dingtalk.py:236 authentication/views/dingtalk.py:290 +#: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 msgid "Failed to get user from DingTalk" msgstr "DingTalkからユーザーを取得できませんでした" -#: authentication/views/dingtalk.py:243 authentication/views/dingtalk.py:297 +#: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 msgid "Please login with a password and then bind the DingTalk" msgstr "パスワードでログインし、DingTalkをバインドしてください" -#: authentication/views/feishu.py:39 +#: authentication/views/feishu.py:40 msgid "FeiShu Error" msgstr "FeiShuエラー" -#: authentication/views/feishu.py:87 +#: authentication/views/feishu.py:88 msgid "FeiShu is already bound" msgstr "FeiShuはすでにバインドされています" -#: authentication/views/feishu.py:129 +#: authentication/views/feishu.py:130 msgid "FeiShu query user failed" msgstr "FeiShuクエリユーザーが失敗しました" -#: authentication/views/feishu.py:138 +#: authentication/views/feishu.py:139 msgid "The FeiShu is already bound to another user" msgstr "FeiShuはすでに別のユーザーにバインドされています" -#: authentication/views/feishu.py:145 +#: authentication/views/feishu.py:146 msgid "Binding FeiShu successfully" msgstr "本を飛ばすのバインドに成功" -#: authentication/views/feishu.py:197 +#: authentication/views/feishu.py:198 msgid "Failed to get user from FeiShu" msgstr "本を飛ばすからユーザーを取得できませんでした" -#: authentication/views/feishu.py:204 +#: authentication/views/feishu.py:205 msgid "Please login with a password and then bind the FeiShu" msgstr "パスワードでログインしてから本を飛ばすをバインドしてください" @@ -2588,31 +2709,31 @@ msgstr "キャンセル" msgid "ugettext_lazy" msgstr "ugettext_lazy" -#: common/db/fields.py:80 +#: common/db/fields.py:93 msgid "Marshal dict data to char field" msgstr "チャーフィールドへのマーシャルディクトデータ" -#: common/db/fields.py:84 +#: common/db/fields.py:97 msgid "Marshal dict data to text field" msgstr "テキストフィールドへのマーシャルディクトデータ" -#: common/db/fields.py:96 +#: common/db/fields.py:109 msgid "Marshal list data to char field" msgstr "元帥リストデータをチャーフィールドに" -#: common/db/fields.py:100 +#: common/db/fields.py:113 msgid "Marshal list data to text field" msgstr "マーシャルリストデータをテキストフィールドに" -#: common/db/fields.py:104 +#: common/db/fields.py:117 msgid "Marshal data to char field" msgstr "チャーフィールドへのマーシャルデータ" -#: common/db/fields.py:108 +#: common/db/fields.py:121 msgid "Marshal data to text field" msgstr "テキストフィールドへのマーシャルデータ" -#: common/db/fields.py:150 +#: common/db/fields.py:163 msgid "Encrypt field using Secret Key" msgstr "Secret Keyを使用したフィールドの暗号化" @@ -2624,17 +2745,36 @@ msgstr "によって更新" msgid "Object" msgstr "オブジェクト" -#: common/drf/fields.py:70 +#: common/drf/fields.py:74 tickets/serializers/ticket/common.py:58 +#: xpack/plugins/change_auth_plan/serializers/asset.py:64 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:101 +#: xpack/plugins/cloud/serializers/account_attrs.py:56 +msgid "This field is required." +msgstr "このフィールドは必須です。" + +#: common/drf/fields.py:75 #, fuzzy, python-brace-format #| msgid "%s object does not exist." msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "%s オブジェクトは存在しません。" -#: common/drf/fields.py:71 +#: common/drf/fields.py:76 #, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "" +#: common/drf/fields.py:131 +msgid "Invalid data type, should be list" +msgstr "" + +#: common/drf/fields.py:146 +#, fuzzy +#| msgid "Invalid ip" +msgid "Invalid choice: {}" +msgstr "無効なIP" + #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" msgstr "ファイルの内容がオーバーフローしました (最大長 '{}' バイト)" @@ -2694,15 +2834,15 @@ msgstr "は破棄されます" msgid "discard time" msgstr "時間を捨てる" -#: common/mixins/views.py:52 +#: common/mixins/views.py:58 msgid "Export all" msgstr "すべてエクスポート" -#: common/mixins/views.py:54 +#: common/mixins/views.py:60 msgid "Export only selected items" msgstr "選択項目のみエクスポート" -#: common/mixins/views.py:59 +#: common/mixins/views.py:65 #, python-format msgid "Export filtered: %s" msgstr "検索のエクスポート: %s" @@ -2759,6 +2899,16 @@ msgstr "確認コードが正しくありません" msgid "Please wait {} seconds before sending" msgstr "{} 秒待ってから送信してください" +#: common/tasks.py:13 +#, fuzzy +#| msgid "Send user" +msgid "Send email" +msgstr "ユーザーを送信" + +#: common/tasks.py:40 +msgid "Send email attachment" +msgstr "" + #: common/utils/ip/geoip/utils.py:26 msgid "Invalid ip" msgstr "無効なIP" @@ -2842,6 +2992,10 @@ msgstr "メール" msgid "Site message" msgstr "サイトメッセージ" +#: notifications/notifications.py:46 +msgid "Publish the station message" +msgstr "" + #: ops/ansible/inventory.py:75 #, fuzzy #| msgid "Account unavailable" @@ -2915,103 +3069,108 @@ msgstr "{} から {} までの範囲" msgid "Require periodic or regularly perform setting" msgstr "定期的または定期的に設定を行う必要があります" -#: ops/models/adhoc.py:18 +#: ops/models/adhoc.py:21 ops/models/job.py:30 +#, fuzzy +#| msgid "PowerShell" +msgid "Powershell" +msgstr "PowerShell" + +#: ops/models/adhoc.py:25 msgid "Pattern" msgstr "パターン" -#: ops/models/adhoc.py:19 +#: ops/models/adhoc.py:27 ops/models/job.py:37 msgid "Module" msgstr "" -#: ops/models/adhoc.py:20 ops/models/celery.py:45 +#: ops/models/adhoc.py:28 ops/models/celery.py:48 ops/models/job.py:35 #: terminal/models/component/task.py:17 msgid "Args" msgstr "アルグ" -#: ops/models/adhoc.py:21 ops/models/base.py:20 ops/models/playbook.py:27 -#, fuzzy -#| msgid "Command execution" -msgid "Last execution" -msgstr "コマンド実行" - -#: ops/models/adhoc.py:36 -msgid "Adhoc" -msgstr "" - -#: ops/models/adhoc.py:54 -msgid "AdHoc execution" -msgstr "アドホックエキューション" - -#: ops/models/base.py:16 ops/models/base.py:52 +#: ops/models/adhoc.py:29 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:40 ops/models/job.py:61 #: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "作成者" +#: ops/models/adhoc.py:50 ops/models/job.py:20 +msgid "Adhoc" +msgstr "" + +#: ops/models/adhoc.py:68 +msgid "AdHoc execution" +msgstr "アドホックエキューション" + #: ops/models/base.py:19 #, fuzzy #| msgid "Account key" msgid "Account policy" msgstr "アカウントキー" -#: ops/models/base.py:21 +#: ops/models/base.py:20 +#, fuzzy +#| msgid "Command execution" +msgid "Last execution" +msgstr "コマンド実行" + +#: ops/models/base.py:22 #, fuzzy #| msgid "Date last sync" msgid "Date last run" msgstr "最終同期日" -#: ops/models/base.py:50 xpack/plugins/cloud/models.py:169 +#: ops/models/base.py:51 ops/models/job.py:59 xpack/plugins/cloud/models.py:169 msgid "Result" msgstr "結果" -#: ops/models/base.py:51 +#: ops/models/base.py:52 ops/models/job.py:60 msgid "Summary" msgstr "" -#: ops/models/celery.py:46 terminal/models/component/task.py:18 +#: ops/models/celery.py:49 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "クワーグ" -#: ops/models/celery.py:47 tickets/models/comment.py:13 +#: ops/models/celery.py:50 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 msgid "State" msgstr "状態" -#: ops/models/celery.py:48 terminal/models/session/sharing.py:111 +#: ops/models/celery.py:51 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "終了" -#: ops/models/playbook.py:10 -msgid "Path" -msgstr "" - -#: ops/models/playbook.py:18 -msgid "Playbook template" -msgstr "" - -#: ops/models/playbook.py:23 +#: ops/models/job.py:21 ops/models/job.py:38 msgid "Playbook" msgstr "" -#: ops/models/playbook.py:24 -msgid "Owner" +#: ops/models/job.py:24 +msgid "Privileged Only" msgstr "" -#: ops/models/playbook.py:26 settings/serializers/auth/sms.py:64 -msgid "Template" -msgstr "テンプレート" +#: ops/models/job.py:25 +msgid "Privileged First" +msgstr "" -#: ops/models/playbook.py:38 ops/signal_handlers.py:63 -#: terminal/models/component/task.py:26 -#: xpack/plugins/gathered_user/models.py:68 -msgid "Task" -msgstr "タスク" +#: ops/models/job.py:26 +msgid "Skip" +msgstr "" -#: ops/models/playbook.py:39 +#: ops/models/job.py:42 +msgid "Runas" +msgstr "" + +#: ops/models/job.py:44 #, fuzzy -#| msgid "Run user" -msgid "Run dir" -msgstr "ユーザーの実行" +#| msgid "Account key" +msgid "Runas policy" +msgstr "アカウントキー" + +#: ops/models/playbook.py:15 +msgid "Owner" +msgstr "" #: ops/notifications.py:17 msgid "Server performance" @@ -3041,26 +3200,47 @@ msgstr "{max_threshold}%: => {value} を超える使用メモリ" msgid "CPU load more than {max_threshold}: => {value}" msgstr "{max_threshold} を超えるCPUロード: => {value}" -#: ops/tasks.py:34 +#: ops/signal_handlers.py:63 terminal/models/component/task.py:26 +#: xpack/plugins/gathered_user/models.py:68 +msgid "Task" +msgstr "タスク" + +#: ops/tasks.py:27 #, fuzzy #| msgid "Run asset" msgid "Run ansible task" msgstr "アセットの実行" -#: ops/tasks.py:58 +#: ops/tasks.py:41 #, fuzzy -#| msgid "Run command" -msgid "Run ansible command" -msgstr "実行コマンド" +#| msgid "Run asset" +msgid "Run ansible task execution" +msgstr "アセットの実行" -#: ops/tasks.py:80 -msgid "Clean task history period" -msgstr "クリーンなタスク履歴期間" +#: ops/tasks.py:54 +msgid "Periodic clear celery tasks" +msgstr "" -#: ops/tasks.py:93 +#: ops/tasks.py:56 msgid "Clean celery log period" msgstr "きれいなセロリログ期間" +#: ops/tasks.py:73 +#, fuzzy +#| msgid "Clean celery log period" +msgid "Clear celery periodic tasks" +msgstr "きれいなセロリログ期間" + +#: ops/tasks.py:96 +msgid "Create or update periodic tasks" +msgstr "" + +#: ops/tasks.py:104 +#, fuzzy +#| msgid "Periodic perform" +msgid "Periodic check service performance" +msgstr "定期的なパフォーマンス" + #: ops/templates/ops/celery_task_log.html:4 msgid "Task log" msgstr "タスクログ" @@ -3125,76 +3305,86 @@ msgstr "グローバル組織を表示できます" msgid "Can view all joined org" msgstr "参加しているすべての組織を表示できます" +#: orgs/tasks.py:9 +#, fuzzy +#| msgid "Global organization name" +msgid "Refresh organization cache" +msgstr "グローバル組織名" + #: perms/apps.py:9 msgid "App permissions" msgstr "アプリの権限" -#: perms/models/asset_permission.py:72 perms/serializers/permission.py:59 -#: perms/serializers/permission.py:85 -#: tickets/models/ticket/apply_application.py:26 -#: tickets/models/ticket/apply_asset.py:19 -msgid "Actions" -msgstr "アクション" - -#: perms/models/asset_permission.py:83 -msgid "From ticket" -msgstr "チケットから" - -#: perms/models/asset_permission.py:224 -msgid "Ungrouped" -msgstr "グループ化されていません" - -#: perms/models/asset_permission.py:226 -msgid "Favorite" -msgstr "お気に入り" - -#: perms/models/asset_permission.py:273 -msgid "Permed asset" -msgstr "許可された資産" - -#: perms/models/asset_permission.py:275 -msgid "Can view my assets" -msgstr "私の資産を見ることができます" - -#: perms/models/asset_permission.py:276 -msgid "Can view user assets" -msgstr "ユーザー資産を表示できます" - -#: perms/models/asset_permission.py:277 -msgid "Can view usergroup assets" -msgstr "ユーザーグループの資産を表示できます" - -#: perms/models/const.py:20 settings/serializers/terminal.py:12 -msgid "All" -msgstr "すべて" - -#: perms/models/const.py:21 +#: perms/const.py:13 msgid "Connect" msgstr "接続" -#: perms/models/const.py:22 -msgid "Upload file" -msgstr "ファイルのアップロード" +#: perms/const.py:16 +#, fuzzy +#| msgid "Copy link" +msgid "Copy" +msgstr "リンクのコピー" -#: perms/models/const.py:23 -msgid "Download file" -msgstr "ファイルのダウンロード" +#: perms/const.py:17 +msgid "Paste" +msgstr "" -#: perms/models/const.py:24 -msgid "Upload download" -msgstr "ダウンロードのアップロード" +#: perms/const.py:26 +msgid "Transfer" +msgstr "" -#: perms/models/const.py:25 -msgid "Clipboard copy" +#: perms/const.py:27 +#, fuzzy +#| msgid "Clipboard copy" +msgid "Clipboard" msgstr "クリップボードのコピー" -#: perms/models/const.py:26 -msgid "Clipboard paste" -msgstr "クリップボードペースト" +#: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 +#: perms/serializers/permission.py:29 perms/serializers/permission.py:59 +#: tickets/models/ticket/apply_application.py:26 +#: tickets/models/ticket/apply_asset.py:17 +msgid "Actions" +msgstr "アクション" -#: perms/models/const.py:27 -msgid "Clipboard copy paste" -msgstr "クリップボードコピーペースト" +#: perms/models/asset_permission.py:76 +msgid "From ticket" +msgstr "チケットから" + +#: perms/models/perm_node.py:55 +msgid "Ungrouped" +msgstr "グループ化されていません" + +#: perms/models/perm_node.py:57 +msgid "Favorite" +msgstr "お気に入り" + +#: perms/models/perm_node.py:104 +msgid "Permed asset" +msgstr "許可された資産" + +#: perms/models/perm_node.py:106 +msgid "Can view my assets" +msgstr "私の資産を見ることができます" + +#: perms/models/perm_node.py:107 +msgid "Can view user assets" +msgstr "ユーザー資産を表示できます" + +#: perms/models/perm_node.py:108 +msgid "Can view usergroup assets" +msgstr "ユーザーグループの資産を表示できます" + +#: perms/models/perm_node.py:119 +#, fuzzy +#| msgid "Gather account" +msgid "Permed account" +msgstr "アカウントを集める" + +#: perms/models/perm_token.py:17 +#, fuzzy +#| msgid "Connect timeout" +msgid "Connect method" +msgstr "接続タイムアウト" #: perms/notifications.py:12 perms/notifications.py:44 msgid "today" @@ -3216,40 +3406,11 @@ msgstr "資産権限の有効期限が近づいています" msgid "asset permissions of organization {}" msgstr "組織 {} の資産権限" -#: perms/serializers/permission.py:48 -msgid "Users display" -msgstr "ユーザー表示" - -#: perms/serializers/permission.py:51 -msgid "User groups display" -msgstr "ユーザーグループの表示" - -#: perms/serializers/permission.py:54 -msgid "Assets display" -msgstr "資産表示" - -#: perms/serializers/permission.py:57 -msgid "Nodes display" -msgstr "ノード表示" - -#: perms/serializers/permission.py:61 perms/serializers/permission.py:86 -#: users/serializers/user.py:89 users/serializers/user.py:150 +#: perms/serializers/permission.py:31 perms/serializers/permission.py:60 +#: users/serializers/user.py:100 users/serializers/user.py:205 msgid "Is expired" msgstr "期限切れです" -#: perms/serializers/permission.py:81 rbac/serializers/role.py:26 -#: users/serializers/group.py:34 -msgid "Users amount" -msgstr "ユーザー数" - -#: perms/serializers/permission.py:82 -msgid "User groups amount" -msgstr "ユーザーグループの量" - -#: perms/serializers/permission.py:84 -msgid "Nodes amount" -msgstr "ノード量" - #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 #, python-format @@ -3266,7 +3427,7 @@ msgstr "" msgid "If you have any question, please contact the administrator" msgstr "質問があったら、管理者に連絡して下さい" -#: perms/utils/user_permission.py:623 rbac/tree.py:57 +#: perms/utils/user_permission.py:622 rbac/tree.py:57 msgid "My assets" msgstr "私の資産" @@ -3399,6 +3560,10 @@ msgstr "パーマ" msgid "Scope display" msgstr "スコープ表示" +#: rbac/serializers/role.py:26 users/serializers/group.py:34 +msgid "Users amount" +msgstr "ユーザー数" + #: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 msgid "Display name" msgstr "表示名" @@ -3887,6 +4052,10 @@ msgstr "元の番号(Src id)" msgid "Business type(Service id)" msgstr "ビジネス・タイプ(Service id)" +#: settings/serializers/auth/sms.py:64 +msgid "Template" +msgstr "テンプレート" + #: settings/serializers/auth/sms.py:65 #, python-brace-format msgid "" @@ -4667,8 +4836,8 @@ msgstr "期限切れです。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" @@ -4689,34 +4858,34 @@ msgid "" " " msgstr "" "\n" -" クリックしてください リンク パスワードの更新\n" +" クリックしてください リンク パスワードの更新\n" " " #: templates/_message.html:43 #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" -" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" +" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" " " #: templates/_message.html:56 #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" +" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -4890,7 +5059,7 @@ msgid "Timestamp" msgstr "タイムスタンプ" #: terminal/backends/command/serializers.py:41 -#: terminal/models/component/terminal.py:105 +#: terminal/models/component/terminal.py:87 msgid "Remote Address" msgstr "リモートアドレス" @@ -4932,45 +5101,41 @@ msgstr "" msgid "Hosts" msgstr "ホスト" -#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:28 +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 #, fuzzy #| msgid "Apply assets" msgid "Applet" msgstr "資産の適用" -#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:36 +#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 #, fuzzy #| msgid "More login options" msgid "Deploy options" msgstr "その他のログインオプション" -#: terminal/models/applet/host.py:20 +#: terminal/models/applet/host.py:19 msgid "Inited" msgstr "" -#: terminal/models/applet/host.py:21 +#: terminal/models/applet/host.py:20 #, fuzzy #| msgid "Date finished" msgid "Date inited" msgstr "終了日" -#: terminal/models/applet/host.py:22 +#: terminal/models/applet/host.py:21 #, fuzzy #| msgid "Date sync" msgid "Date synced" msgstr "日付の同期" -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:183 -msgid "Terminal" -msgstr "ターミナル" - -#: terminal/models/applet/host.py:99 +#: terminal/models/applet/host.py:102 #, fuzzy #| msgid "Host" msgid "Hosting" msgstr "ホスト" -#: terminal/models/applet/host.py:100 +#: terminal/models/applet/host.py:103 msgid "Initial" msgstr "" @@ -4979,12 +5144,10 @@ msgid "HTTPS Port" msgstr "HTTPS ポート" #: terminal/models/component/endpoint.py:15 -#: terminal/models/component/terminal.py:107 msgid "HTTP Port" msgstr "HTTP ポート" #: terminal/models/component/endpoint.py:16 -#: terminal/models/component/terminal.py:106 msgid "SSH Port" msgstr "SSH ポート" @@ -5032,31 +5195,31 @@ msgstr "IP グループ" msgid "Endpoint rule" msgstr "エンドポイントルール" -#: terminal/models/component/status.py:18 +#: terminal/models/component/status.py:14 msgid "Session Online" msgstr "セッションオンライン" -#: terminal/models/component/status.py:19 +#: terminal/models/component/status.py:15 msgid "CPU Load" msgstr "CPUロード" -#: terminal/models/component/status.py:20 +#: terminal/models/component/status.py:16 msgid "Memory Used" msgstr "使用メモリ" -#: terminal/models/component/status.py:21 +#: terminal/models/component/status.py:17 msgid "Disk Used" msgstr "使用済みディスク" -#: terminal/models/component/status.py:22 +#: terminal/models/component/status.py:18 msgid "Connections" msgstr "接続" -#: terminal/models/component/status.py:23 +#: terminal/models/component/status.py:19 msgid "Threads" msgstr "スレッド" -#: terminal/models/component/status.py:24 +#: terminal/models/component/status.py:20 msgid "Boot Time" msgstr "ブート時間" @@ -5065,20 +5228,20 @@ msgid "Default storage" msgstr "デフォルトのストレージ" #: terminal/models/component/storage.py:136 -#: terminal/models/component/terminal.py:108 +#: terminal/models/component/terminal.py:88 msgid "Command storage" msgstr "コマンドストレージ" #: terminal/models/component/storage.py:196 -#: terminal/models/component/terminal.py:109 +#: terminal/models/component/terminal.py:89 msgid "Replay storage" msgstr "再生ストレージ" -#: terminal/models/component/terminal.py:103 +#: terminal/models/component/terminal.py:85 msgid "type" msgstr "タイプ" -#: terminal/models/component/terminal.py:185 +#: terminal/models/component/terminal.py:161 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" @@ -5216,42 +5379,46 @@ msgstr "電話が設定されていない" msgid "Icon" msgstr "" -#: terminal/serializers/applet_host.py:20 +#: terminal/serializers/applet_host.py:22 #, fuzzy #| msgid "Session" msgid "Per Session" msgstr "セッション" -#: terminal/serializers/applet_host.py:21 +#: terminal/serializers/applet_host.py:23 msgid "Per Device" msgstr "" -#: terminal/serializers/applet_host.py:27 +#: terminal/serializers/applet_host.py:29 #, fuzzy #| msgid "License" msgid "RDS Licensing" msgstr "ライセンス" -#: terminal/serializers/applet_host.py:28 +#: terminal/serializers/applet_host.py:30 msgid "RDS License Server" msgstr "" -#: terminal/serializers/applet_host.py:29 +#: terminal/serializers/applet_host.py:31 msgid "RDS Licensing Mode" msgstr "" -#: terminal/serializers/applet_host.py:30 +#: terminal/serializers/applet_host.py:32 msgid "RDS fSingleSessionPerUser" msgstr "" -#: terminal/serializers/applet_host.py:31 +#: terminal/serializers/applet_host.py:33 msgid "RDS Max Disconnection Time" msgstr "" -#: terminal/serializers/applet_host.py:32 +#: terminal/serializers/applet_host.py:34 msgid "RDS Remote App Logoff Time Limit" msgstr "" +#: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 +msgid "Load status" +msgstr "ロードステータス" + #: terminal/serializers/endpoint.py:12 msgid "Oracle port" msgstr "" @@ -5373,11 +5540,7 @@ msgstr "Docタイプ" msgid "Ignore Certificate Verification" msgstr "証明書の検証を無視する" -#: terminal/serializers/terminal.py:44 -msgid "Load status" -msgstr "ロードステータス" - -#: terminal/serializers/terminal.py:81 terminal/serializers/terminal.py:89 +#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 msgid "Not found" msgstr "見つかりません" @@ -5498,11 +5661,11 @@ msgstr "ボディ" msgid "Approve level" msgstr "レベルを承認する" -#: tickets/models/flow.py:25 tickets/serializers/flow.py:15 +#: tickets/models/flow.py:25 tickets/serializers/flow.py:17 msgid "Approve strategy" msgstr "戦略を承認する" -#: tickets/models/flow.py:30 tickets/serializers/flow.py:16 +#: tickets/models/flow.py:30 tickets/serializers/flow.py:19 msgid "Assignees" msgstr "アシニーズ" @@ -5519,7 +5682,7 @@ msgid "Ticket session relation" msgstr "チケットセッションの関係" #: tickets/models/ticket/apply_application.py:11 -#: tickets/models/ticket/apply_asset.py:13 +#: tickets/models/ticket/apply_asset.py:12 msgid "Permission name" msgstr "認可ルール名" @@ -5531,20 +5694,20 @@ msgstr "アプリケーションの適用" msgid "Apply system users" msgstr "システムユーザーの適用" -#: tickets/models/ticket/apply_asset.py:9 +#: tickets/models/ticket/apply_asset.py:8 #: tickets/serializers/ticket/apply_asset.py:15 msgid "Select at least one asset or node" msgstr "少なくとも1つのアセットまたはノードを選択します。" -#: tickets/models/ticket/apply_asset.py:14 +#: tickets/models/ticket/apply_asset.py:13 msgid "Apply nodes" msgstr "ノードの適用" -#: tickets/models/ticket/apply_asset.py:16 +#: tickets/models/ticket/apply_asset.py:15 msgid "Apply assets" msgstr "資産の適用" -#: tickets/models/ticket/apply_asset.py:17 +#: tickets/models/ticket/apply_asset.py:16 #, fuzzy #| msgid "Application account" msgid "Apply accounts" @@ -5654,15 +5817,15 @@ msgstr "チケットが処理されました。プロセッサー- {}" msgid "Ticket has processed - {} ({})" msgstr "チケットが処理済み- {} ({})" -#: tickets/serializers/flow.py:17 +#: tickets/serializers/flow.py:20 msgid "Assignees display" msgstr "受付者名" -#: tickets/serializers/flow.py:43 +#: tickets/serializers/flow.py:46 msgid "Please select the Assignees" msgstr "受付をお選びください" -#: tickets/serializers/flow.py:69 +#: tickets/serializers/flow.py:74 msgid "The current organization type already exists" msgstr "現在の組織タイプは既に存在します。" @@ -5683,6 +5846,14 @@ msgstr "有効期限は開始日より大きくする必要があります" msgid "Permission named `{}` already exists" msgstr "'{}'という名前の権限は既に存在します" +#: tickets/serializers/ticket/ticket.py:17 +msgid "Type display" +msgstr "タイプ表示" + +#: tickets/serializers/ticket/ticket.py:18 +msgid "Status display" +msgstr "ステータス表示" + #: tickets/serializers/ticket/ticket.py:101 msgid "The ticket flow `{}` does not exist" msgstr "チケットフロー '{}'が存在しない" @@ -5858,7 +6029,7 @@ msgstr "強制有効" msgid "Local" msgstr "ローカル" -#: users/models/user.py:677 users/serializers/user.py:149 +#: users/models/user.py:677 users/serializers/user.py:204 msgid "Is service account" msgstr "サービスアカウントです" @@ -5965,105 +6136,105 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする msgid "The newly set password is inconsistent" msgstr "新しく設定されたパスワードが一致しない" -#: users/serializers/profile.py:149 users/serializers/user.py:146 +#: users/serializers/profile.py:149 users/serializers/user.py:201 msgid "Is first login" msgstr "最初のログインです" -#: users/serializers/user.py:28 +#: users/serializers/user.py:30 msgid "System roles" msgstr "システムの役割" -#: users/serializers/user.py:33 +#: users/serializers/user.py:35 msgid "Org roles" msgstr "組織ロール" -#: users/serializers/user.py:35 +#: users/serializers/user.py:38 msgid "System roles display" msgstr "システムロール表示" -#: users/serializers/user.py:36 +#: users/serializers/user.py:40 msgid "Org roles display" msgstr "組織ロール表示" -#: users/serializers/user.py:81 +#: users/serializers/user.py:90 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" msgstr "パスワード戦略" -#: users/serializers/user.py:83 +#: users/serializers/user.py:92 msgid "MFA enabled" msgstr "MFA有効化" -#: users/serializers/user.py:84 +#: users/serializers/user.py:94 msgid "MFA force enabled" msgstr "MFAフォース有効化" -#: users/serializers/user.py:86 +#: users/serializers/user.py:97 msgid "MFA level display" msgstr "MFAレベル表示" -#: users/serializers/user.py:88 +#: users/serializers/user.py:99 msgid "Login blocked" msgstr "ログインブロック" -#: users/serializers/user.py:91 +#: users/serializers/user.py:102 msgid "Can public key authentication" msgstr "公開鍵認証が可能" -#: users/serializers/user.py:151 +#: users/serializers/user.py:206 msgid "Avatar url" msgstr "アバターURL" -#: users/serializers/user.py:153 +#: users/serializers/user.py:208 msgid "Groups name" msgstr "グループ名" -#: users/serializers/user.py:154 +#: users/serializers/user.py:209 msgid "Source name" msgstr "ソース名" -#: users/serializers/user.py:155 +#: users/serializers/user.py:210 msgid "Organization role name" msgstr "組織の役割名" -#: users/serializers/user.py:156 +#: users/serializers/user.py:211 msgid "Super role name" msgstr "スーパーロール名" -#: users/serializers/user.py:157 +#: users/serializers/user.py:212 msgid "Total role name" msgstr "合計ロール名" -#: users/serializers/user.py:159 +#: users/serializers/user.py:214 msgid "Is wecom bound" msgstr "企業の微信をバインドしているかどうか" -#: users/serializers/user.py:160 +#: users/serializers/user.py:215 msgid "Is dingtalk bound" msgstr "ピンをバインドしているかどうか" -#: users/serializers/user.py:161 +#: users/serializers/user.py:216 msgid "Is feishu bound" msgstr "飛本を縛ったかどうか" -#: users/serializers/user.py:162 +#: users/serializers/user.py:217 msgid "Is OTP bound" msgstr "仮想MFAがバインドされているか" -#: users/serializers/user.py:164 +#: users/serializers/user.py:219 msgid "System role name" msgstr "システムロール名" -#: users/serializers/user.py:263 +#: users/serializers/user.py:325 msgid "Select users" msgstr "ユーザーの選択" -#: users/serializers/user.py:264 +#: users/serializers/user.py:326 msgid "For security, only list several users" msgstr "セキュリティのために、複数のユーザーのみをリストします" -#: users/serializers/user.py:299 +#: users/serializers/user.py:362 msgid "name not unique" msgstr "名前が一意ではない" @@ -6317,10 +6488,6 @@ msgstr "パスワードの成功をリセットし、ログインページに戻 msgid "XPACK" msgstr "XPack" -#: xpack/plugins/change_auth_plan/api/asset.py:94 -msgid "The parameter 'action' must be [{}]" -msgstr "パラメータ 'action' は [{}] でなければなりません。" - #: xpack/plugins/change_auth_plan/meta.py:9 #: xpack/plugins/change_auth_plan/models/asset.py:124 msgid "Change auth plan" @@ -6349,11 +6516,6 @@ msgstr "改密計画タスクの適用" msgid "Password cannot be set to blank, exit. " msgstr "パスワードを空白に設定することはできません。" -#: xpack/plugins/change_auth_plan/models/asset.py:50 -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 -msgid "SSH Key strategy" -msgstr "SSHキー戦略" - #: xpack/plugins/change_auth_plan/models/asset.py:68 msgid "Asset change auth plan" msgstr "資産変更のオースプラン" @@ -6407,25 +6569,6 @@ msgstr "パスワード/キーの保存" msgid "Step" msgstr "ステップ" -#: xpack/plugins/change_auth_plan/notifications.py:8 -msgid "Notification of implementation result of encryption change plan" -msgstr "暗号化変更プランの実装結果の通知" - -#: xpack/plugins/change_auth_plan/notifications.py:18 -msgid "" -"{} - The encryption change task has been completed. See the attachment for " -"details" -msgstr "{} -暗号化変更タスクが完了しました。詳細は添付ファイルをご覧ください" - -#: xpack/plugins/change_auth_plan/notifications.py:19 -msgid "" -"{} - The encryption change task has been completed: the encryption password " -"has not been set - please go to personal information -> file encryption " -"password to set the encryption password" -msgstr "" -"{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" -"情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" - #: xpack/plugins/change_auth_plan/serializers/asset.py:30 msgid "Change Password" msgstr "パスワードの変更" @@ -6438,14 +6581,6 @@ msgstr "SSHキーの変更" msgid "Run times" msgstr "実行時間" -#: xpack/plugins/change_auth_plan/serializers/base.py:58 -msgid "* Please enter the correct password length" -msgstr "* 正しいパスワードの長さを入力してください" - -#: xpack/plugins/change_auth_plan/serializers/base.py:61 -msgid "* Password length range 6-30 bits" -msgstr "* パスワードの長さの範囲6-30ビット" - #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 msgid "After many attempts to change the secret, it still failed" msgstr "秘密を変更しようとする多くの試みの後、それはまだ失敗しました" @@ -6462,6 +6597,22 @@ msgstr "ホストへの接続に失敗しました" msgid "Data could not be sent to remote" msgstr "データをリモートに送信できませんでした" +#: xpack/plugins/change_auth_plan/tasks.py:13 +#, fuzzy +#| msgid "Asset change auth plan task" +msgid "Execute change authentication task" +msgstr "資産改密計画タスク" + +#: xpack/plugins/change_auth_plan/tasks.py:24 +#, fuzzy +#| msgid "Asset change auth plan task" +msgid "Start change authentication task" +msgstr "資産改密計画タスク" + +#: xpack/plugins/change_auth_plan/tasks.py:36 +msgid "Test the validity of the change authentication plan " +msgstr "" + #: xpack/plugins/cloud/api.py:40 msgid "Test connection successful" msgstr "テスト接続成功" @@ -6997,11 +7148,11 @@ msgstr "テーマ" msgid "Interface setting" msgstr "インターフェイスの設定" -#: xpack/plugins/license/api.py:53 +#: xpack/plugins/license/api.py:50 msgid "License import successfully" msgstr "ライセンスのインポートに成功" -#: xpack/plugins/license/api.py:54 +#: xpack/plugins/license/api.py:51 msgid "License is invalid" msgstr "ライセンスが無効です" @@ -7026,9 +7177,61 @@ msgid "Community edition" msgstr "コミュニティ版" #, fuzzy -#~| msgid "Verify auth" -#~ msgid "Account automation" -#~ msgstr "パスワード/キーの確認" +#~| msgid "Run command" +#~ msgid "Run ansible command" +#~ msgstr "実行コマンド" + +#~ msgid "Clean task history period" +#~ msgstr "クリーンなタスク履歴期間" + +#, fuzzy +#~| msgid "WeCom Error" +#~ msgid "Hello Error" +#~ msgstr "企業微信エラー" + +#~ msgid "Operate display" +#~ msgstr "ディスプレイを操作する" + +#~ msgid "MFA display" +#~ msgstr "MFAディスプレイ" + +#, fuzzy +#~| msgid "Run user" +#~ msgid "Run dir" +#~ msgstr "ユーザーの実行" + +#~ msgid "Upload file" +#~ msgstr "ファイルのアップロード" + +#~ msgid "Download file" +#~ msgstr "ファイルのダウンロード" + +#~ msgid "Upload download" +#~ msgstr "ダウンロードのアップロード" + +#~ msgid "Clipboard paste" +#~ msgstr "クリップボードペースト" + +#~ msgid "Clipboard copy paste" +#~ msgstr "クリップボードコピーペースト" + +#~ msgid "Users display" +#~ msgstr "ユーザー表示" + +#~ msgid "User groups display" +#~ msgstr "ユーザーグループの表示" + +#~ msgid "Assets display" +#~ msgstr "資産表示" + +#~ msgid "Nodes display" +#~ msgstr "ノード表示" + +#~ msgid "User groups amount" +#~ msgstr "ユーザーグループの量" + +#~ msgid "Nodes amount" +#~ msgstr "ノード量" #~ msgid "The asset {} system platform {} does not support run Ansible tasks" #~ msgstr "" @@ -7333,66 +7536,54 @@ msgstr "コミュニティ版" #~ msgid "Asset and SystemUser" #~ msgstr "資産およびシステム・ユーザー" -#, python-brace-format #~ msgid "{Asset} ADD {SystemUser}" #~ msgstr "{Asset} 追加 {SystemUser}" -#, python-brace-format #~ msgid "{Asset} REMOVE {SystemUser}" #~ msgstr "{Asset} 削除 {SystemUser}" #~ msgid "Asset permission and SystemUser" #~ msgstr "資産権限とSystemUser" -#, python-brace-format #~ msgid "{AssetPermission} ADD {SystemUser}" #~ msgstr "{AssetPermission} 追加 {SystemUser}" -#, python-brace-format #~ msgid "{AssetPermission} REMOVE {SystemUser}" #~ msgstr "{AssetPermission} 削除 {SystemUser}" #~ msgid "User application permissions" #~ msgstr "ユーザーアプリケーションの権限" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {User}" #~ msgstr "{ApplicationPermission} 追加 {User}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {User}" #~ msgstr "{ApplicationPermission} 削除 {User}" #~ msgid "User group application permissions" #~ msgstr "ユーザーグループアプリケーションの権限" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {UserGroup}" #~ msgstr "{ApplicationPermission} 追加 {UserGroup}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {UserGroup}" #~ msgstr "{ApplicationPermission} 削除 {UserGroup}" #~ msgid "Application permission" #~ msgstr "申請許可" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {Application}" #~ msgstr "{ApplicationPermission} 追加 {Application}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {Application}" #~ msgstr "{ApplicationPermission} 削除 {Application}" #~ msgid "Application permission and SystemUser" #~ msgstr "アプリケーション権限とSystemUser" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {SystemUser}" #~ msgstr "{ApplicationPermission} 追加 {SystemUser}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {SystemUser}" #~ msgstr "{ApplicationPermission} 削除 {SystemUser}" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index bb0411aa6..c1becdb02 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:314c29cb8b10aaddbb030bf49af293be23f0153ff1f1c7562946879574ce6de8 -size 102801 +oid sha256:bf423289503715e2a574bce56bf6b1b323e0355ef18dbd1c8de37c66c0fb5b25 +size 104080 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 6ab6fa2be..596c39c50 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-03 16:00+0800\n" +"POT-Creation-Date: 2022-11-15 15:52+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -21,20 +21,21 @@ msgstr "" msgid "Acls" msgstr "访问控制" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:48 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:58 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:57 assets/models/cmd_filter.py:25 +#: assets/models/base.py:50 assets/models/cmd_filter.py:25 #: assets/models/domain.py:24 assets/models/group.py:20 -#: assets/models/label.py:17 assets/models/platform.py:22 -#: assets/models/platform.py:68 assets/serializers/asset/common.py:86 -#: assets/serializers/platform.py:104 ops/mixin.py:20 ops/models/playbook.py:9 -#: orgs/models.py:70 perms/models/asset_permission.py:56 rbac/models/role.py:29 +#: assets/models/label.py:17 assets/models/platform.py:21 +#: assets/models/platform.py:72 assets/serializers/asset/common.py:86 +#: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 +#: ops/models/job.py:33 ops/models/playbook.py:13 orgs/models.py:70 +#: perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 #: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 -#: terminal/models/component/terminal.py:100 users/forms/profile.py:33 +#: terminal/models/component/terminal.py:82 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 #: xpack/plugins/cloud/models.py:30 msgid "Name" @@ -52,26 +53,25 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)" #: acls/models/base.py:31 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:74 terminal/models/session/sharing.py:28 +#: perms/models/asset_permission.py:67 terminal/models/session/sharing.py:28 #: tickets/const.py:38 msgid "Active" msgstr "激活中" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:100 assets/models/automations/base.py:26 -#: assets/models/backup.py:30 assets/models/base.py:65 +#: assets/models/asset/common.py:100 assets/models/automations/base.py:22 +#: assets/models/backup.py:29 assets/models/base.py:58 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/domain.py:25 assets/models/domain.py:69 #: assets/models/group.py:23 assets/models/label.py:22 -#: assets/models/platform.py:73 ops/models/playbook.py:11 -#: ops/models/playbook.py:25 orgs/models.py:74 -#: perms/models/asset_permission.py:84 rbac/models/role.py:37 +#: assets/models/platform.py:77 orgs/models.py:74 +#: perms/models/asset_permission.py:77 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 -#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:104 +#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 -#: terminal/models/component/terminal.py:114 tickets/models/comment.py:32 +#: terminal/models/component/terminal.py:93 tickets/models/comment.py:32 #: tickets/models/ticket/general.py:288 users/models/group.py:16 #: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 @@ -94,12 +94,12 @@ msgid "Login confirm" msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 -#: assets/models/cmd_filter.py:28 assets/models/label.py:15 audits/models.py:37 -#: audits/models.py:62 audits/models.py:87 -#: authentication/models/connection_token.py:22 -#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:58 -#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 -#: terminal/backends/command/models.py:20 +#: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 +#: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 +#: audits/models.py:79 authentication/models/connection_token.py:22 +#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:53 +#: perms/models/perm_token.py:12 rbac/builtin.py:120 +#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 #: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -113,14 +113,14 @@ msgid "Rule" msgstr "规则" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:17 acls/serializers/login_asset_acl.py:62 -#: assets/models/cmd_filter.py:81 audits/models.py:63 audits/serializers.py:49 +#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:77 +#: assets/models/cmd_filter.py:81 audits/models.py:50 audits/serializers.py:69 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" msgstr "动作" #: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32 -#: acls/serializers/login_acl.py:16 assets/models/cmd_filter.py:86 +#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86 msgid "Reviewers" msgstr "审批人" @@ -128,19 +128,25 @@ msgstr "审批人" msgid "Login acl" msgstr "登录访问控制" -#: acls/models/login_asset_acl.py:21 assets/models/account.py:59 +#: acls/models/login_asset_acl.py:21 assets/models/account.py:61 +#: assets/serializers/automations/change_secret.py:88 +#: assets/serializers/automations/change_secret.py:110 #: authentication/models/connection_token.py:33 ops/models/base.py:18 -#: terminal/models/session/session.py:34 xpack/plugins/cloud/models.py:87 -#: xpack/plugins/cloud/serializers/task.py:65 +#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "账号" -#: acls/models/login_asset_acl.py:22 assets/models/account.py:49 +#: acls/models/login_asset_acl.py:22 assets/models/account.py:51 #: assets/models/asset/common.py:83 assets/models/asset/common.py:227 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 -#: assets/serializers/account/account.py:58 assets/serializers/label.py:30 -#: audits/models.py:39 authentication/models/connection_token.py:26 -#: perms/models/asset_permission.py:64 terminal/backends/command/models.py:21 +#: assets/serializers/account/account.py:59 +#: assets/serializers/automations/change_secret.py:87 +#: assets/serializers/automations/change_secret.py:109 +#: assets/serializers/gathered_user.py:11 assets/serializers/label.py:30 +#: audits/models.py:33 authentication/models/connection_token.py:26 +#: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 +#: terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 #: terminal/models/session/session.py:32 terminal/notifications.py:90 #: xpack/plugins/change_auth_plan/models/asset.py:200 @@ -157,14 +163,14 @@ msgstr "登录资产访问控制" msgid "Login asset confirm" msgstr "登录资产复核" -#: acls/serializers/login_acl.py:11 acls/serializers/login_asset_acl.py:13 +#: acls/serializers/login_acl.py:16 acls/serializers/login_asset_acl.py:14 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " -#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:18 -#: acls/serializers/login_asset_acl.py:52 assets/models/_user.py:34 -#: assets/models/base.py:58 assets/models/gathered_user.py:15 -#: audits/models.py:121 authentication/forms.py:25 authentication/forms.py:27 +#: acls/serializers/login_asset_acl.py:22 +#: acls/serializers/login_asset_acl.py:64 assets/models/_user.py:34 +#: assets/models/base.py:51 assets/models/gathered_user.py:15 +#: audits/models.py:95 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 @@ -176,7 +182,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " msgid "Username" msgstr "用户名" -#: acls/serializers/login_asset_acl.py:25 +#: acls/serializers/login_asset_acl.py:29 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -185,7 +191,7 @@ msgstr "" "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" -#: acls/serializers/login_asset_acl.py:32 acls/serializers/rules/rules.py:33 +#: acls/serializers/login_asset_acl.py:38 acls/serializers/rules/rules.py:33 #: assets/models/asset/common.py:92 assets/models/domain.py:65 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 @@ -194,23 +200,23 @@ msgstr "" msgid "IP" msgstr "IP" -#: acls/serializers/login_asset_acl.py:36 -#: assets/serializers/gathered_user.py:22 settings/serializers/terminal.py:7 +#: acls/serializers/login_asset_acl.py:44 +#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 msgid "Hostname" msgstr "主机名" -#: acls/serializers/login_asset_acl.py:43 +#: acls/serializers/login_asset_acl.py:51 msgid "" "Format for comma-delimited string, with * indicating a match all. Protocol " "options: {}" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" -#: acls/serializers/login_asset_acl.py:84 +#: acls/serializers/login_asset_acl.py:108 #: tickets/serializers/ticket/ticket.py:86 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" -#: acls/serializers/login_asset_acl.py:89 +#: acls/serializers/login_asset_acl.py:114 msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" @@ -237,23 +243,25 @@ msgid "Applications" msgstr "应用管理" #: applications/models.py:12 assets/models/label.py:20 -#: assets/models/platform.py:69 assets/serializers/asset/common.py:62 -#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:76 -#: assets/serializers/platform.py:105 +#: assets/models/platform.py:73 assets/serializers/asset/common.py:62 +#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 +#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:24 #: tickets/models/ticket/apply_application.py:14 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" msgstr "类别" #: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:24 assets/models/cmd_filter.py:74 -#: assets/models/platform.py:70 assets/serializers/asset/common.py:63 -#: assets/serializers/platform.py:75 terminal/models/applet/applet.py:24 +#: assets/models/automations/base.py:20 assets/models/cmd_filter.py:74 +#: assets/models/platform.py:74 assets/serializers/asset/common.py:63 +#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 +#: audits/serializers.py:40 ops/models/job.py:39 +#: perms/serializers/user_permission.py:25 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 -#: tickets/models/ticket/general.py:273 +#: tickets/models/ticket/general.py:273 tickets/serializers/flow.py:53 #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" @@ -272,6 +280,11 @@ msgstr "应用程序" msgid "Can match application" msgstr "匹配应用" +#: assets/api/automations/base.py:76 +#: xpack/plugins/change_auth_plan/api/asset.py:94 +msgid "The parameter 'action' must be [{}]" +msgstr "参数 'action' 必须是 [{}]" + #: assets/api/domain.py:52 msgid "Number required" msgstr "需要为数字" @@ -292,11 +305,11 @@ msgstr "删除失败,节点包含资产" msgid "App assets" msgstr "资产管理" -#: assets/automations/base/manager.py:122 +#: assets/automations/base/manager.py:123 msgid "{} disabled" msgstr "{} 已禁用" -#: assets/const/account.py:6 audits/const.py:5 +#: assets/const/account.py:6 audits/const.py:6 audits/const.py:63 #: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 #: common/utils/ip/utils.py:84 msgid "Unknown" @@ -306,19 +319,21 @@ msgstr "未知" msgid "Ok" msgstr "成功" -#: assets/const/account.py:8 audits/models.py:118 common/const/choices.py:19 +#: assets/const/account.py:8 +#: assets/serializers/automations/change_secret.py:105 +#: assets/serializers/automations/change_secret.py:133 audits/const.py:74 +#: common/const/choices.py:19 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 msgid "Failed" msgstr "失败" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/base.py:52 assets/models/domain.py:71 -#: assets/serializers/base.py:15 audits/signal_handlers.py:50 +#: assets/models/domain.py:71 audits/signal_handlers.py:46 #: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:94 +#: users/forms/profile.py:22 users/serializers/user.py:105 #: users/templates/users/_msg_user_created.html:13 #: users/templates/users/user_password_verify.html:18 #: xpack/plugins/change_auth_plan/models/base.py:42 @@ -330,17 +345,16 @@ msgstr "失败" msgid "Password" msgstr "密码" -#: assets/const/account.py:13 assets/models/base.py:53 +#: assets/const/account.py:13 msgid "SSH key" msgstr "SSH 密钥" -#: assets/const/account.py:14 assets/models/base.py:54 -#: authentication/models/access_key.py:31 +#: assets/const/account.py:14 authentication/models/access_key.py:31 msgid "Access key" msgstr "Access key" #: assets/const/account.py:15 assets/models/_user.py:38 -#: assets/models/base.py:55 authentication/models/sso_token.py:13 +#: authentication/models/sso_token.py:13 msgid "Token" msgstr "Token" @@ -368,31 +382,31 @@ msgstr "验证密钥" msgid "Gather accounts" msgstr "收集账号" -#: assets/const/automation.py:22 +#: assets/const/automation.py:38 assets/serializers/account/base.py:26 msgid "Specific" msgstr "指定" -#: assets/const/automation.py:23 ops/const.py:20 +#: assets/const/automation.py:39 ops/const.py:20 #: xpack/plugins/change_auth_plan/models/base.py:28 msgid "All assets use the same random password" msgstr "使用相同的随机密码" -#: assets/const/automation.py:24 ops/const.py:21 +#: assets/const/automation.py:40 ops/const.py:21 #: xpack/plugins/change_auth_plan/models/base.py:29 msgid "All assets use different random password" msgstr "使用不同的随机密码" -#: assets/const/automation.py:28 ops/const.py:13 +#: assets/const/automation.py:44 ops/const.py:13 #: xpack/plugins/change_auth_plan/models/asset.py:30 msgid "Append SSH KEY" msgstr "追加" -#: assets/const/automation.py:29 ops/const.py:14 +#: assets/const/automation.py:45 ops/const.py:14 #: xpack/plugins/change_auth_plan/models/asset.py:31 msgid "Empty and append SSH KEY" msgstr "清空所有并添加" -#: assets/const/automation.py:30 ops/const.py:15 +#: assets/const/automation.py:46 ops/const.py:15 #: xpack/plugins/change_auth_plan/models/asset.py:32 msgid "Replace (The key generated by JumpServer) " msgstr "替换 (由 JumpServer 生成的密钥)" @@ -418,7 +432,8 @@ msgstr "数据库" msgid "Cloud service" msgstr "云服务" -#: assets/const/category.py:15 terminal/models/applet/applet.py:18 +#: assets/const/category.py:15 audits/const.py:61 +#: terminal/models/applet/applet.py:18 msgid "Web" msgstr "Web" @@ -460,7 +475,6 @@ msgid "Admin user" msgstr "特权用户" #: assets/models/_user.py:36 assets/models/domain.py:72 -#: assets/serializers/base.py:19 #: xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 @@ -474,11 +488,12 @@ msgstr "SSH 密钥" msgid "SSH public key" msgstr "SSH 公钥" -#: assets/models/_user.py:41 assets/models/automations/base.py:96 +#: assets/models/_user.py:41 assets/models/automations/base.py:92 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:53 orgs/models.py:73 perms/models/asset_permission.py:82 -#: users/models/group.py:18 users/models/user.py:927 +#: ops/models/base.py:54 ops/models/job.py:62 orgs/models.py:73 +#: perms/models/asset_permission.py:75 users/models/group.py:18 +#: users/models/user.py:927 msgid "Date created" msgstr "创建日期" @@ -487,10 +502,10 @@ msgstr "创建日期" msgid "Date updated" msgstr "更新日期" -#: assets/models/_user.py:43 assets/models/base.py:66 +#: assets/models/_user.py:43 assets/models/base.py:59 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 -#: orgs/models.py:71 perms/models/asset_permission.py:81 +#: orgs/models.py:71 perms/models/asset_permission.py:74 #: users/models/user.py:710 users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 msgid "Created by" @@ -501,7 +516,7 @@ msgid "Username same with user" msgstr "用户名与用户相同" #: assets/models/_user.py:48 assets/models/domain.py:67 -#: authentication/models/connection_token.py:29 +#: authentication/models/connection_token.py:29 perms/models/perm_token.py:16 #: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 #: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" @@ -515,7 +530,7 @@ msgstr "自动推送" msgid "Sudo" msgstr "Sudo" -#: assets/models/_user.py:51 +#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:29 msgid "Shell" msgstr "Shell" @@ -543,7 +558,7 @@ msgstr "用户切换" msgid "Switch from" msgstr "切换自" -#: assets/models/_user.py:65 audits/models.py:40 +#: assets/models/_user.py:65 audits/models.py:34 #: terminal/backends/command/models.py:22 #: terminal/backends/command/serializers.py:36 #: xpack/plugins/change_auth_plan/models/app.py:35 @@ -555,43 +570,60 @@ msgstr "系统用户" msgid "Can match system user" msgstr "可以匹配系统用户" -#: assets/models/account.py:53 +#: assets/models/account.py:45 common/db/fields.py:222 +#: settings/serializers/terminal.py:12 +msgid "All" +msgstr "全部" + +#: assets/models/account.py:46 +#, fuzzy +#| msgid "Manually input" +msgid "Manual input" +msgstr "手动输入" + +#: assets/models/account.py:47 +#, fuzzy +#| msgid "Dynamic code" +msgid "Dynamic user" +msgstr "动态码" + +#: assets/models/account.py:55 msgid "Su from" msgstr "切换自" -#: assets/models/account.py:55 settings/serializers/auth/cas.py:18 +#: assets/models/account.py:57 settings/serializers/auth/cas.py:18 #: terminal/models/applet/applet.py:22 msgid "Version" msgstr "版本" -#: assets/models/account.py:65 +#: assets/models/account.py:67 msgid "Can view asset account secret" msgstr "可以查看资产账号密码" -#: assets/models/account.py:66 +#: assets/models/account.py:68 msgid "Can change asset account secret" msgstr "可以更改资产账号密码" -#: assets/models/account.py:67 +#: assets/models/account.py:69 msgid "Can view asset history account" msgstr "可以查看资产历史账号" -#: assets/models/account.py:68 +#: assets/models/account.py:70 msgid "Can view asset history account secret" msgstr "可以查看资产历史账号密码" -#: assets/models/account.py:91 assets/serializers/account/account.py:13 +#: assets/models/account.py:93 assets/serializers/account/account.py:15 msgid "Account template" msgstr "账号模版" #: assets/models/asset/common.py:82 assets/models/domain.py:66 -#: assets/models/platform.py:23 settings/serializers/auth/radius.py:15 +#: assets/models/platform.py:22 settings/serializers/auth/radius.py:15 #: settings/serializers/auth/sms.py:57 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "端口" -#: assets/models/asset/common.py:93 assets/models/platform.py:104 +#: assets/models/asset/common.py:93 assets/models/platform.py:110 #: assets/serializers/asset/common.py:65 #: perms/serializers/user_permission.py:21 #: xpack/plugins/cloud/serializers/account_attrs.py:172 @@ -603,17 +635,19 @@ msgstr "资产平台" msgid "Domain" msgstr "网域" -#: assets/models/asset/common.py:97 assets/models/automations/base.py:19 -#: assets/serializers/asset/common.py:66 perms/models/asset_permission.py:67 +#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 +#: assets/serializers/asset/common.py:66 +#: assets/serializers/automations/base.py:21 +#: perms/models/asset_permission.py:62 #: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "节点" -#: assets/models/asset/common.py:98 assets/models/automations/base.py:25 -#: assets/models/base.py:64 assets/models/cmd_filter.py:39 +#: assets/models/asset/common.py:98 assets/models/automations/base.py:21 +#: assets/models/base.py:57 assets/models/cmd_filter.py:39 #: assets/models/domain.py:70 assets/models/label.py:21 -#: terminal/models/applet/applet.py:25 users/serializers/user.py:147 +#: terminal/models/applet/applet.py:25 users/serializers/user.py:202 msgid "Is active" msgstr "激活" @@ -647,8 +681,8 @@ msgstr "添加资产到节点" msgid "Move asset to node" msgstr "移动资产到节点" -#: assets/models/asset/web.py:9 audits/models.py:111 -#: terminal/serializers/applet_host.py:24 +#: assets/models/asset/web.py:9 audits/const.py:67 +#: terminal/serializers/applet_host.py:26 msgid "Disabled" msgstr "禁用" @@ -664,83 +698,97 @@ msgstr "" msgid "Autofill" msgstr "自动填充" -#: assets/models/asset/web.py:14 assets/serializers/platform.py:29 +#: assets/models/asset/web.py:14 assets/serializers/platform.py:30 msgid "Username selector" msgstr "用户名选择器" -#: assets/models/asset/web.py:15 assets/serializers/platform.py:30 +#: assets/models/asset/web.py:15 assets/serializers/platform.py:33 msgid "Password selector" msgstr "密码选择器" -#: assets/models/asset/web.py:16 assets/serializers/platform.py:31 +#: assets/models/asset/web.py:16 assets/serializers/platform.py:36 msgid "Submit selector" msgstr "提交按钮选择器" #: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 -#: assets/serializers/asset/common.py:68 perms/models/asset_permission.py:70 -#: rbac/tree.py:37 +#: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 +#: perms/serializers/permission.py:32 rbac/tree.py:37 msgid "Accounts" msgstr "账号管理" -#: assets/models/automations/base.py:22 assets/serializers/domain.py:29 -#: ops/models/base.py:17 +#: assets/models/automations/base.py:19 +#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 +#: ops/models/base.py:17 ops/models/job.py:41 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" -#: assets/models/automations/base.py:86 assets/models/automations/base.py:93 +#: assets/models/automations/base.py:82 assets/models/automations/base.py:89 msgid "Automation task" msgstr "自动化任务" -#: assets/models/automations/base.py:97 assets/models/backup.py:77 -#: audits/models.py:44 ops/models/base.py:54 -#: perms/models/asset_permission.py:76 terminal/models/applet/host.py:102 +#: assets/models/automations/base.py:91 audits/models.py:115 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:57 +#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 +#: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 +#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 +#: xpack/plugins/cloud/models.py:223 +msgid "Status" +msgstr "状态" + +#: assets/models/automations/base.py:93 assets/models/backup.py:76 +#: audits/models.py:40 ops/models/base.py:55 ops/models/job.py:63 +#: perms/models/asset_permission.py:69 terminal/models/applet/host.py:105 #: terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 -#: tickets/models/ticket/apply_asset.py:21 +#: tickets/models/ticket/apply_asset.py:18 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:199 #: xpack/plugins/gathered_user/models.py:71 msgid "Date start" msgstr "开始日期" -#: assets/models/automations/base.py:98 -#: assets/models/automations/change_secret.py:58 ops/models/base.py:55 -#: terminal/models/applet/host.py:103 +#: assets/models/automations/base.py:94 +#: assets/models/automations/change_secret.py:59 ops/models/base.py:56 +#: ops/models/job.py:64 terminal/models/applet/host.py:106 msgid "Date finished" msgstr "结束日期" -#: assets/models/automations/base.py:100 +#: assets/models/automations/base.py:96 +#: assets/serializers/automations/base.py:39 msgid "Automation snapshot" msgstr "自动化快照" -#: assets/models/automations/base.py:104 assets/models/backup.py:88 -#: assets/serializers/account/backup.py:36 +#: assets/models/automations/base.py:100 assets/models/backup.py:87 +#: assets/serializers/account/backup.py:37 +#: assets/serializers/automations/base.py:41 #: xpack/plugins/change_auth_plan/models/base.py:121 #: xpack/plugins/change_auth_plan/serializers/base.py:78 msgid "Trigger mode" msgstr "触发模式" -#: assets/models/automations/base.py:108 +#: assets/models/automations/base.py:104 +#: assets/serializers/automations/change_secret.py:90 msgid "Automation task execution" msgstr "自动化任务执行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:60 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:53 +#: assets/serializers/account/account.py:95 assets/serializers/base.py:13 msgid "Secret type" msgstr "密文类型" #: assets/models/automations/change_secret.py:19 +#: assets/serializers/automations/change_secret.py:25 msgid "Secret strategy" msgstr "密钥策略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:56 assets/models/base.py:62 -#: assets/serializers/account/base.py:17 -#: authentication/models/connection_token.py:34 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:55 +#: assets/serializers/base.py:16 authentication/models/connection_token.py:34 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 -#: settings/serializers/auth/radius.py:17 +#: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 msgid "Secret" msgstr "密钥" @@ -753,8 +801,9 @@ msgstr "密码规则" msgid "SSH key change strategy" msgstr "SSH 密钥策略" -#: assets/models/automations/change_secret.py:27 assets/models/backup.py:28 -#: assets/serializers/account/backup.py:28 +#: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 +#: assets/serializers/account/backup.py:30 +#: assets/serializers/automations/change_secret.py:40 #: xpack/plugins/change_auth_plan/models/app.py:40 #: xpack/plugins/change_auth_plan/models/asset.py:63 #: xpack/plugins/change_auth_plan/serializers/base.py:45 @@ -765,19 +814,19 @@ msgstr "收件人" msgid "Change secret automation" msgstr "自动化改密" -#: assets/models/automations/change_secret.py:55 +#: assets/models/automations/change_secret.py:56 msgid "Old secret" msgstr "原来密码" -#: assets/models/automations/change_secret.py:57 +#: assets/models/automations/change_secret.py:58 msgid "Date started" msgstr "开始日期" -#: assets/models/automations/change_secret.py:60 common/const/choices.py:20 +#: assets/models/automations/change_secret.py:61 common/const/choices.py:20 msgid "Error" msgstr "错误" -#: assets/models/automations/change_secret.py:63 +#: assets/models/automations/change_secret.py:64 msgid "Change secret record" msgstr "改密记录" @@ -786,10 +835,9 @@ msgid "Discovery account automation" msgstr "自动化账号发现" #: assets/models/automations/gather_accounts.py:15 -#, fuzzy -#| msgid "Gather asset facts" +#: assets/tasks/gather_accounts.py:28 msgid "Gather asset accounts" -msgstr "收集资产信息" +msgstr "收集资产账号" #: assets/models/automations/gather_facts.py:15 msgid "Gather asset facts" @@ -813,24 +861,24 @@ msgstr "服务账号" msgid "Verify asset account" msgstr "验证密钥" -#: assets/models/backup.py:38 assets/models/backup.py:96 +#: assets/models/backup.py:37 assets/models/backup.py:95 msgid "Account backup plan" msgstr "账号备份计划" -#: assets/models/backup.py:80 +#: assets/models/backup.py:79 #: authentication/templates/authentication/_msg_oauth_bind.html:11 -#: notifications/notifications.py:187 +#: notifications/notifications.py:186 #: xpack/plugins/change_auth_plan/models/base.py:111 #: xpack/plugins/change_auth_plan/models/base.py:200 #: xpack/plugins/gathered_user/models.py:74 msgid "Time" msgstr "时间" -#: assets/models/backup.py:84 +#: assets/models/backup.py:83 msgid "Account backup snapshot" msgstr "账号备份快照" -#: assets/models/backup.py:91 audits/models.py:127 +#: assets/models/backup.py:90 audits/models.py:110 #: terminal/models/session/sharing.py:108 #: xpack/plugins/change_auth_plan/models/base.py:197 #: xpack/plugins/change_auth_plan/serializers/asset.py:171 @@ -838,29 +886,32 @@ msgstr "账号备份快照" msgid "Reason" msgstr "原因" -#: assets/models/backup.py:93 terminal/serializers/session.py:36 +#: assets/models/backup.py:92 +#: assets/serializers/automations/change_secret.py:86 +#: assets/serializers/automations/change_secret.py:111 +#: terminal/serializers/session.py:36 #: xpack/plugins/change_auth_plan/models/base.py:198 #: xpack/plugins/change_auth_plan/serializers/asset.py:173 msgid "Is success" msgstr "是否成功" -#: assets/models/backup.py:100 +#: assets/models/backup.py:99 msgid "Account backup execution" msgstr "账号备份执行" -#: assets/models/base.py:29 assets/serializers/domain.py:42 +#: assets/models/base.py:28 assets/serializers/domain.py:42 msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:31 authentication/models/temp_token.py:12 +#: assets/models/base.py:30 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:63 +#: assets/models/base.py:56 msgid "Privileged" msgstr "特权账号" -#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:61 +#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:56 #: users/models/group.py:31 users/models/user.py:671 msgid "User group" msgstr "用户组" @@ -930,7 +981,7 @@ msgstr "测试网关" msgid "Unable to connect to port {port} on {address}" msgstr "无法连接到 {address} 上的端口 {port}" -#: assets/models/domain.py:145 authentication/middleware.py:75 +#: assets/models/domain.py:145 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "认证失败" @@ -959,7 +1010,7 @@ msgstr "收集用户" msgid "Asset group" msgstr "资产组" -#: assets/models/group.py:34 assets/models/platform.py:20 +#: assets/models/group.py:34 assets/models/platform.py:19 #: xpack/plugins/cloud/providers/nutanix.py:30 msgid "Default" msgstr "默认" @@ -992,7 +1043,7 @@ msgstr "新节点" msgid "empty" msgstr "空" -#: assets/models/node.py:552 perms/models/asset_permission.py:190 +#: assets/models/node.py:552 perms/models/perm_node.py:21 msgid "Key" msgstr "键" @@ -1000,7 +1051,7 @@ msgstr "键" msgid "Full value" msgstr "全称" -#: assets/models/node.py:557 perms/models/asset_permission.py:191 +#: assets/models/node.py:557 perms/models/perm_node.py:22 msgid "Parent key" msgstr "ssh私钥" @@ -1013,48 +1064,48 @@ msgstr "节点" msgid "Can match node" msgstr "可以匹配节点" -#: assets/models/platform.py:21 +#: assets/models/platform.py:20 msgid "Required" msgstr "必需的" -#: assets/models/platform.py:24 users/templates/users/reset_password.html:29 +#: assets/models/platform.py:23 users/templates/users/reset_password.html:29 msgid "Setting" msgstr "设置" -#: assets/models/platform.py:43 audits/models.py:112 settings/models.py:37 -#: terminal/serializers/applet_host.py:25 +#: assets/models/platform.py:42 audits/const.py:68 settings/models.py:37 +#: terminal/serializers/applet_host.py:27 msgid "Enabled" msgstr "启用" -#: assets/models/platform.py:44 +#: assets/models/platform.py:43 msgid "Ansible config" msgstr "Ansible 配置" -#: assets/models/platform.py:45 +#: assets/models/platform.py:44 msgid "Ping enabled" msgstr "探测资产" -#: assets/models/platform.py:46 +#: assets/models/platform.py:45 msgid "Ping method" msgstr "探测方式" -#: assets/models/platform.py:47 assets/models/platform.py:55 +#: assets/models/platform.py:46 assets/models/platform.py:56 msgid "Gather facts enabled" msgstr "收集资产信息" -#: assets/models/platform.py:48 assets/models/platform.py:56 +#: assets/models/platform.py:47 assets/models/platform.py:58 msgid "Gather facts method" msgstr "收集资产信息方式" -#: assets/models/platform.py:49 +#: assets/models/platform.py:48 msgid "Push account enabled" msgstr "推送账号" -#: assets/models/platform.py:50 +#: assets/models/platform.py:49 msgid "Push account method" msgstr "推送方式" -#: assets/models/platform.py:51 +#: assets/models/platform.py:50 msgid "Change password enabled" msgstr "更改密码" @@ -1066,39 +1117,39 @@ msgstr "改密方式" msgid "Verify account enabled" msgstr "校验账号" -#: assets/models/platform.py:54 +#: assets/models/platform.py:55 msgid "Verify account method" msgstr "验证z" -#: assets/models/platform.py:71 tickets/models/ticket/general.py:298 +#: assets/models/platform.py:75 tickets/models/ticket/general.py:298 msgid "Meta" msgstr "元数据" -#: assets/models/platform.py:72 +#: assets/models/platform.py:76 msgid "Internal" msgstr "内部的" -#: assets/models/platform.py:75 +#: assets/models/platform.py:80 assets/serializers/platform.py:96 msgid "Charset" msgstr "编码" -#: assets/models/platform.py:76 +#: assets/models/platform.py:82 msgid "Domain enabled" msgstr "支持网域" -#: assets/models/platform.py:77 +#: assets/models/platform.py:83 msgid "Protocols enabled" msgstr "协议支持" -#: assets/models/platform.py:79 +#: assets/models/platform.py:85 msgid "Su enabled" msgstr "账号切换" -#: assets/models/platform.py:80 +#: assets/models/platform.py:86 msgid "SU method" msgstr "切换方式" -#: assets/models/platform.py:82 assets/serializers/platform.py:78 +#: assets/models/platform.py:88 assets/serializers/platform.py:103 msgid "Automation" msgstr "自动化" @@ -1117,7 +1168,7 @@ msgid "" "for details" msgstr "{} - 账号备份任务已完成, 详情见附件" -#: assets/notifications.py:19 +#: assets/notifications.py:20 msgid "" "{} - The account backup passage task has been completed: the encryption " "password has not been set - please go to personal information -> file " @@ -1126,44 +1177,53 @@ msgstr "" "{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" "置加密密码" -#: assets/serializers/account/account.py:16 +#: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 +msgid "Notification of implementation result of encryption change plan" +msgstr "改密计划任务结果通知" + +#: assets/notifications.py:41 +#: xpack/plugins/change_auth_plan/notifications.py:18 +msgid "" +"{} - The encryption change task has been completed. See the attachment for " +"details" +msgstr "{} - 改密任务已完成, 详情见附件" + +#: assets/notifications.py:42 +#: xpack/plugins/change_auth_plan/notifications.py:19 +msgid "" +"{} - The encryption change task has been completed: the encryption password " +"has not been set - please go to personal information -> file encryption " +"password to set the encryption password" +msgstr "" +"{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" +"密密码" + +#: assets/serializers/account/account.py:18 msgid "Push now" msgstr "立刻推送" -#: assets/serializers/account/account.py:18 +#: assets/serializers/account/account.py:20 msgid "Has secret" msgstr "存在密码" -#: assets/serializers/account/account.py:25 +#: assets/serializers/account/account.py:27 msgid "Account template not found" msgstr "账号模版没有发现" -#: assets/serializers/account/backup.py:27 ops/mixin.py:102 +#: assets/serializers/account/backup.py:29 +#: assets/serializers/automations/base.py:34 ops/mixin.py:102 #: settings/serializers/auth/ldap.py:65 #: xpack/plugins/change_auth_plan/serializers/base.py:43 msgid "Periodic perform" msgstr "定时执行" -#: assets/serializers/account/backup.py:29 +#: assets/serializers/account/backup.py:31 +#: assets/serializers/automations/change_secret.py:41 #: xpack/plugins/change_auth_plan/serializers/base.py:46 msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/account/base.py:39 assets/serializers/base.py:34 -msgid "private key invalid or passphrase error" -msgstr "密钥不合法或密钥密码错误" - -#: assets/serializers/account/template.py:16 common/drf/fields.py:69 -#: tickets/serializers/ticket/common.py:58 -#: xpack/plugins/change_auth_plan/serializers/asset.py:64 -#: xpack/plugins/change_auth_plan/serializers/asset.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:70 -#: xpack/plugins/change_auth_plan/serializers/asset.py:101 -#: xpack/plugins/cloud/serializers/account_attrs.py:56 -msgid "This field is required." -msgstr "该字段是必填项。" - -#: assets/serializers/asset/common.py:69 assets/serializers/platform.py:77 +#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 #: xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "协议组" @@ -1244,7 +1304,37 @@ msgstr "资产编号" msgid "IP/Host" msgstr "IP/主机名" -#: assets/serializers/base.py:24 +#: assets/serializers/automations/change_secret.py:28 +#: xpack/plugins/change_auth_plan/models/asset.py:50 +#: xpack/plugins/change_auth_plan/serializers/asset.py:33 +msgid "SSH Key strategy" +msgstr "SSH 密钥策略" + +#: assets/serializers/automations/change_secret.py:57 +#: xpack/plugins/change_auth_plan/serializers/base.py:58 +msgid "* Please enter the correct password length" +msgstr "* 请输入正确的密码长度" + +#: assets/serializers/automations/change_secret.py:60 +#: xpack/plugins/change_auth_plan/serializers/base.py:61 +msgid "* Password length range 6-30 bits" +msgstr "* 密码长度范围 6-30 位" + +#: assets/serializers/automations/change_secret.py:104 +#: assets/serializers/automations/change_secret.py:132 audits/const.py:73 +#: audits/models.py:39 common/const/choices.py:18 +#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 +#: xpack/plugins/change_auth_plan/serializers/asset.py:189 +msgid "Success" +msgstr "成功" + +#: assets/serializers/automations/gather_accounts.py:23 +#, fuzzy +#| msgid "Executed times" +msgid "Executed amount" +msgstr "执行次数" + +#: assets/serializers/base.py:21 msgid "Key password" msgstr "密钥密码" @@ -1257,7 +1347,6 @@ msgid "Types" msgstr "类型" #: assets/serializers/domain.py:14 assets/serializers/label.py:12 -#: perms/serializers/permission.py:83 msgid "Assets amount" msgstr "资产数量" @@ -1265,15 +1354,10 @@ msgstr "资产数量" msgid "Gateways count" msgstr "网关数量" -#: assets/serializers/label.py:13 assets/serializers/mixin.py:7 +#: assets/serializers/label.py:13 msgid "Category display" msgstr "类别名称" -#: assets/serializers/mixin.py:10 audits/serializers.py:27 -#: tickets/serializers/flow.py:49 tickets/serializers/ticket/ticket.py:17 -msgid "Type display" -msgstr "类型名称" - #: assets/serializers/node.py:17 msgid "value" msgstr "值" @@ -1298,56 +1382,90 @@ msgstr "SFTP 根路径" msgid "Auto fill" msgstr "自动填充" -#: assets/serializers/platform.py:64 +#: assets/serializers/platform.py:78 msgid "Primary" msgstr "主要的" -#: assets/serializers/utils.py:11 +#: assets/serializers/utils.py:15 msgid "Password can not contains `{{` " msgstr "密码不能包含 `{{` 字符" -#: assets/serializers/utils.py:14 +#: assets/serializers/utils.py:18 msgid "Password can not contains `'` " msgstr "密码不能包含 `'` 字符" -#: assets/serializers/utils.py:16 +#: assets/serializers/utils.py:20 msgid "Password can not contains `\"` " msgstr "密码不能包含 `\"` 字符" -#: assets/tasks/gather_facts.py:25 +#: assets/serializers/utils.py:26 +msgid "private key invalid or passphrase error" +msgstr "密钥不合法或密钥密码错误" + +#: assets/tasks/automation.py:11 +msgid "Execute automation" +msgstr "执行自动化" + +#: assets/tasks/backup.py:13 +msgid "Execute account backup plan" +msgstr "执行账号备份计划" + +#: assets/tasks/gather_accounts.py:31 +msgid "Gather assets accounts" +msgstr "收集资产账号" + +#: assets/tasks/gather_facts.py:26 msgid "Update some assets hardware info. " msgstr "更新资产硬件信息. " -#: assets/tasks/gather_facts.py:48 +#: assets/tasks/gather_facts.py:44 +msgid "Manually update the hardware information of assets" +msgstr "手动更新节点资产硬件信息: " + +#: assets/tasks/gather_facts.py:49 msgid "Update assets hardware info: " msgstr "更新资产硬件信息: " -#: assets/tasks/gather_facts.py:58 -msgid "Update node asset hardware information: " -msgstr "更新节点资产硬件信息: " +#: assets/tasks/gather_facts.py:53 +msgid "Manually update the hardware information of assets under a node" +msgstr "手动更新节点下的资产硬件信息" -#: assets/tasks/nodes_amount.py:29 +#: assets/tasks/gather_facts.py:59 +msgid "Update node asset hardware information" +msgstr "更新节点资产硬件信息" + +#: assets/tasks/nodes_amount.py:16 +msgid "Check the amount of assets under the node" +msgstr "校准节点下的资产数量" + +#: assets/tasks/nodes_amount.py:28 msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "自检程序已经在运行,不能重复启动" -#: assets/tasks/ping.py:20 assets/tasks/ping.py:38 -#, fuzzy -#| msgid "Test assets connectivity. " +#: assets/tasks/nodes_amount.py:34 +msgid "Periodic check the amount of assets under the node" +msgstr "定时校准节点下的资产数量" + +#: assets/tasks/ping.py:21 assets/tasks/ping.py:39 msgid "Test assets connectivity " msgstr "测试资产可连接性. " -#: assets/tasks/ping.py:48 -#, fuzzy -#| msgid "Test if the assets under the node are connectable: " +#: assets/tasks/ping.py:33 +msgid "Manually test the connectivity of a asset" +msgstr "手动测试资产连接性" + +#: assets/tasks/ping.py:43 +msgid "Manually test the connectivity of assets under a node" +msgstr "手动测试节点下的资产可连接性" + +#: assets/tasks/ping.py:49 msgid "Test if the assets under the node are connectable " msgstr "测试节点下资产是否可连接: " -#: assets/tasks/push_account.py:36 -#, fuzzy -#| msgid "Push account method" +#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:31 msgid "Push accounts to assets" -msgstr "推送方式" +msgstr "推送账号到资产" #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" @@ -1365,9 +1483,11 @@ msgstr "为了安全,禁止推送用户 {}" msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" -#: assets/tasks/verify_account.py:36 -#, fuzzy -#| msgid "Test account connectivity: " +#: assets/tasks/verify_account.py:30 +msgid "Verify asset account availability" +msgstr "验证资产账号的有效性" + +#: assets/tasks/verify_account.py:37 msgid "Verify accounts connectivity" msgstr "测试账号可连接性: " @@ -1375,278 +1495,257 @@ msgstr "测试账号可连接性: " msgid "Audits" msgstr "日志审计" -#: audits/models.py:27 audits/models.py:59 +#: audits/const.py:44 +msgid "Mkdir" +msgstr "创建目录" + +#: audits/const.py:45 +msgid "Rmdir" +msgstr "删除目录" + +#: audits/const.py:46 audits/const.py:56 #: authentication/templates/authentication/_access_key_modal.html:65 #: rbac/tree.py:226 msgid "Delete" msgstr "删除" -#: audits/models.py:28 +#: audits/const.py:47 perms/const.py:14 msgid "Upload" msgstr "上传文件" -#: audits/models.py:29 -msgid "Download" -msgstr "下载文件" - -#: audits/models.py:30 -msgid "Rmdir" -msgstr "删除目录" - -#: audits/models.py:31 +#: audits/const.py:48 msgid "Rename" msgstr "重命名" -#: audits/models.py:32 -msgid "Mkdir" -msgstr "创建目录" - -#: audits/models.py:33 +#: audits/const.py:49 msgid "Symlink" msgstr "建立软链接" -#: audits/models.py:38 audits/models.py:66 audits/models.py:89 -#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 -msgid "Remote addr" -msgstr "远端地址" +#: audits/const.py:50 perms/const.py:15 +msgid "Download" +msgstr "下载文件" -#: audits/models.py:41 -msgid "Operate" -msgstr "操作" +#: audits/const.py:54 rbac/tree.py:224 +msgid "View" +msgstr "查看" -#: audits/models.py:42 -msgid "Filename" -msgstr "文件名" +#: audits/const.py:55 rbac/tree.py:225 templates/_csv_import_export.html:18 +#: templates/_csv_update_modal.html:6 +msgid "Update" +msgstr "更新" -#: audits/models.py:43 audits/models.py:117 common/const/choices.py:18 -#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 -#: xpack/plugins/change_auth_plan/serializers/asset.py:189 -msgid "Success" -msgstr "成功" - -#: audits/models.py:47 -msgid "File transfer log" -msgstr "文件管理" - -#: audits/models.py:56 +#: audits/const.py:57 #: authentication/templates/authentication/_access_key_modal.html:22 #: rbac/tree.py:223 msgid "Create" msgstr "创建" -#: audits/models.py:57 rbac/tree.py:224 -msgid "View" -msgstr "查看" +#: audits/const.py:62 terminal/models/applet/host.py:24 +#: terminal/models/component/terminal.py:159 +msgid "Terminal" +msgstr "终端" -#: audits/models.py:58 rbac/tree.py:225 templates/_csv_import_export.html:18 -#: templates/_csv_update_modal.html:6 -msgid "Update" -msgstr "更新" +#: audits/const.py:69 +msgid "-" +msgstr "-" -#: audits/models.py:64 audits/serializers.py:61 +#: audits/models.py:31 audits/models.py:55 audits/models.py:82 +#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 +msgid "Remote addr" +msgstr "远端地址" + +#: audits/models.py:36 audits/serializers.py:19 +msgid "Operate" +msgstr "操作" + +#: audits/models.py:38 +msgid "Filename" +msgstr "文件名" + +#: audits/models.py:43 +msgid "File transfer log" +msgstr "文件管理" + +#: audits/models.py:52 audits/serializers.py:85 msgid "Resource Type" msgstr "资源类型" -#: audits/models.py:65 +#: audits/models.py:53 msgid "Resource" msgstr "资源" -#: audits/models.py:67 audits/models.py:90 +#: audits/models.py:58 audits/models.py:84 #: terminal/backends/command/serializers.py:40 msgid "Datetime" msgstr "日期" -#: audits/models.py:82 +#: audits/models.py:74 msgid "Operate log" msgstr "操作日志" -#: audits/models.py:88 +#: audits/models.py:80 msgid "Change by" msgstr "修改者" -#: audits/models.py:96 +#: audits/models.py:90 msgid "Password change log" msgstr "改密日志" -#: audits/models.py:113 -msgid "-" -msgstr "-" - -#: audits/models.py:122 +#: audits/models.py:97 msgid "Login type" msgstr "登录方式" -#: audits/models.py:123 tickets/models/ticket/login_confirm.py:10 +#: audits/models.py:99 tickets/models/ticket/login_confirm.py:10 msgid "Login ip" msgstr "登录IP" -#: audits/models.py:124 +#: audits/models.py:101 #: authentication/templates/authentication/_msg_different_city.html:11 #: tickets/models/ticket/login_confirm.py:11 msgid "Login city" msgstr "登录城市" -#: audits/models.py:125 audits/serializers.py:42 +#: audits/models.py:104 audits/serializers.py:62 msgid "User agent" msgstr "用户代理" -#: audits/models.py:126 +#: audits/models.py:107 audits/serializers.py:39 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: users/forms/profile.py:65 users/models/user.py:688 #: users/serializers/profile.py:126 msgid "MFA" msgstr "MFA" -#: audits/models.py:128 ops/models/base.py:48 -#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:101 -#: terminal/models/component/status.py:33 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 -#: xpack/plugins/cloud/models.py:223 -msgid "Status" -msgstr "状态" - -#: audits/models.py:129 +#: audits/models.py:117 msgid "Date login" msgstr "登录日期" -#: audits/models.py:130 audits/serializers.py:44 +#: audits/models.py:119 audits/serializers.py:64 msgid "Authentication backend" msgstr "认证方式" -#: audits/models.py:169 +#: audits/models.py:160 msgid "User login log" msgstr "用户登录日志" -#: audits/serializers.py:12 -msgid "Operate display" -msgstr "操作名称" - -#: audits/serializers.py:28 tickets/serializers/ticket/ticket.py:18 -msgid "Status display" -msgstr "状态名称" - -#: audits/serializers.py:29 -msgid "MFA display" -msgstr "MFA名称" - -#: audits/serializers.py:43 +#: audits/serializers.py:63 msgid "Reason display" msgstr "原因描述" -#: audits/signal_handlers.py:49 +#: audits/signal_handlers.py:45 msgid "SSH Key" msgstr "SSH 密钥" -#: audits/signal_handlers.py:51 +#: audits/signal_handlers.py:47 msgid "SSO" msgstr "SSO" -#: audits/signal_handlers.py:52 +#: audits/signal_handlers.py:48 msgid "Auth Token" msgstr "认证令牌" -#: audits/signal_handlers.py:53 authentication/notifications.py:73 +#: audits/signal_handlers.py:49 authentication/notifications.py:73 #: authentication/views/login.py:73 authentication/views/wecom.py:178 #: notifications/backends/__init__.py:11 users/models/user.py:724 msgid "WeCom" msgstr "企业微信" -#: audits/signal_handlers.py:54 authentication/views/feishu.py:144 +#: audits/signal_handlers.py:50 authentication/views/feishu.py:145 #: authentication/views/login.py:85 notifications/backends/__init__.py:14 #: users/models/user.py:726 msgid "FeiShu" msgstr "飞书" -#: audits/signal_handlers.py:55 authentication/views/dingtalk.py:179 +#: audits/signal_handlers.py:51 authentication/views/dingtalk.py:180 #: authentication/views/login.py:79 notifications/backends/__init__.py:12 #: users/models/user.py:725 msgid "DingTalk" msgstr "钉钉" -#: audits/signal_handlers.py:56 authentication/models/temp_token.py:16 +#: audits/signal_handlers.py:52 authentication/models/temp_token.py:16 msgid "Temporary token" msgstr "临时密码" -#: audits/signal_handlers.py:68 +#: audits/signal_handlers.py:64 msgid "User and Group" msgstr "用户与用户组" -#: audits/signal_handlers.py:69 +#: audits/signal_handlers.py:65 #, python-brace-format msgid "{User} JOINED {UserGroup}" msgstr "{User} 加入 {UserGroup}" -#: audits/signal_handlers.py:70 +#: audits/signal_handlers.py:66 #, python-brace-format msgid "{User} LEFT {UserGroup}" msgstr "{User} 离开 {UserGroup}" -#: audits/signal_handlers.py:73 +#: audits/signal_handlers.py:69 msgid "Node and Asset" msgstr "节点与资产" -#: audits/signal_handlers.py:74 +#: audits/signal_handlers.py:70 #, python-brace-format msgid "{Node} ADD {Asset}" msgstr "{Node} 添加 {Asset}" -#: audits/signal_handlers.py:75 +#: audits/signal_handlers.py:71 #, python-brace-format msgid "{Node} REMOVE {Asset}" msgstr "{Node} 移除 {Asset}" -#: audits/signal_handlers.py:78 +#: audits/signal_handlers.py:74 msgid "User asset permissions" msgstr "用户资产授权" -#: audits/signal_handlers.py:79 +#: audits/signal_handlers.py:75 #, python-brace-format msgid "{AssetPermission} ADD {User}" msgstr "{AssetPermission} 添加 {User}" -#: audits/signal_handlers.py:80 +#: audits/signal_handlers.py:76 #, python-brace-format msgid "{AssetPermission} REMOVE {User}" msgstr "{AssetPermission} 移除 {User}" -#: audits/signal_handlers.py:83 +#: audits/signal_handlers.py:79 msgid "User group asset permissions" msgstr "用户组资产授权" -#: audits/signal_handlers.py:84 +#: audits/signal_handlers.py:80 #, python-brace-format msgid "{AssetPermission} ADD {UserGroup}" msgstr "{AssetPermission} 添加 {UserGroup}" -#: audits/signal_handlers.py:85 +#: audits/signal_handlers.py:81 #, python-brace-format msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:88 perms/models/asset_permission.py:90 +#: audits/signal_handlers.py:84 perms/models/asset_permission.py:83 msgid "Asset permission" msgstr "资产授权" -#: audits/signal_handlers.py:89 +#: audits/signal_handlers.py:85 #, python-brace-format msgid "{AssetPermission} ADD {Asset}" msgstr "{AssetPermission} 添加 {Asset}" -#: audits/signal_handlers.py:90 +#: audits/signal_handlers.py:86 #, python-brace-format msgid "{AssetPermission} REMOVE {Asset}" msgstr "{AssetPermission} 移除 {Asset}" -#: audits/signal_handlers.py:93 +#: audits/signal_handlers.py:89 msgid "Node permission" msgstr "节点授权" -#: audits/signal_handlers.py:94 +#: audits/signal_handlers.py:90 #, python-brace-format msgid "{AssetPermission} ADD {Node}" msgstr "{AssetPermission} 添加 {Node}" -#: audits/signal_handlers.py:95 +#: audits/signal_handlers.py:91 #, python-brace-format msgid "{AssetPermission} REMOVE {Node}" msgstr "{AssetPermission} 移除 {Node}" @@ -1857,12 +1956,12 @@ msgstr "企业微信已经绑定" msgid "WeCom is not bound" msgstr "没有绑定企业微信" -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:242 -#: authentication/views/dingtalk.py:296 +#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 +#: authentication/views/dingtalk.py:297 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" -#: authentication/errors/mfa.py:33 authentication/views/feishu.py:203 +#: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 msgid "FeiShu is not bound" msgstr "没有绑定飞书" @@ -1954,7 +2053,7 @@ msgstr "设置手机号码启用" msgid "Clear phone number to disable" msgstr "清空手机号码禁用" -#: authentication/middleware.py:76 settings/utils/ldap.py:652 +#: authentication/middleware.py:77 settings/utils/ldap.py:652 msgid "Authentication failed (before login check failed): {}" msgstr "认证失败(登录前检查失败): {}" @@ -1976,9 +2075,9 @@ msgid "Asset display" msgstr "资产名称" #: authentication/models/connection_token.py:36 -#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:79 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:72 #: tickets/models/ticket/apply_application.py:29 -#: tickets/models/ticket/apply_asset.py:22 users/models/user.py:707 +#: tickets/models/ticket/apply_asset.py:19 users/models/user.py:707 msgid "Date expired" msgstr "失效日期" @@ -2042,17 +2141,17 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" -#: authentication/serializers/connection_token.py:20 +#: authentication/serializers/connection_token.py:19 #: xpack/plugins/cloud/models.py:36 msgid "Validity" msgstr "有效" -#: authentication/serializers/connection_token.py:21 +#: authentication/serializers/connection_token.py:20 msgid "Expired time" msgstr "过期时间" -#: authentication/serializers/token.py:79 perms/serializers/permission.py:60 -#: perms/serializers/permission.py:87 users/serializers/user.py:148 +#: authentication/serializers/token.py:79 perms/serializers/permission.py:30 +#: perms/serializers/permission.py:61 users/serializers/user.py:203 msgid "Is valid" msgstr "账号是否有效" @@ -2139,7 +2238,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 ops/tasks.py:147 ops/tasks.py:153 ops/tasks.py:156 +#: jumpserver/conf.py:390 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2283,73 +2382,73 @@ msgstr "复制成功" msgid "LAN" msgstr "局域网" -#: authentication/views/dingtalk.py:41 +#: authentication/views/dingtalk.py:42 msgid "DingTalk Error, Please contact your system administrator" msgstr "钉钉错误,请联系系统管理员" -#: authentication/views/dingtalk.py:44 +#: authentication/views/dingtalk.py:45 msgid "DingTalk Error" msgstr "钉钉错误" -#: authentication/views/dingtalk.py:56 authentication/views/feishu.py:51 +#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 #: authentication/views/wecom.py:56 msgid "" "The system configuration is incorrect. Please contact your administrator" msgstr "企业配置错误,请联系系统管理员" -#: authentication/views/dingtalk.py:80 +#: authentication/views/dingtalk.py:81 msgid "DingTalk is already bound" msgstr "钉钉已经绑定" -#: authentication/views/dingtalk.py:148 authentication/views/wecom.py:148 +#: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 msgid "Invalid user_id" msgstr "无效的 user_id" -#: authentication/views/dingtalk.py:164 +#: authentication/views/dingtalk.py:165 msgid "DingTalk query user failed" msgstr "钉钉查询用户失败" -#: authentication/views/dingtalk.py:173 +#: authentication/views/dingtalk.py:174 msgid "The DingTalk is already bound to another user" msgstr "该钉钉已经绑定其他用户" -#: authentication/views/dingtalk.py:180 +#: authentication/views/dingtalk.py:181 msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:236 authentication/views/dingtalk.py:290 +#: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:243 authentication/views/dingtalk.py:297 +#: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 msgid "Please login with a password and then bind the DingTalk" msgstr "请使用密码登录,然后绑定钉钉" -#: authentication/views/feishu.py:39 +#: authentication/views/feishu.py:40 msgid "FeiShu Error" msgstr "飞书错误" -#: authentication/views/feishu.py:87 +#: authentication/views/feishu.py:88 msgid "FeiShu is already bound" msgstr "飞书已经绑定" -#: authentication/views/feishu.py:129 +#: authentication/views/feishu.py:130 msgid "FeiShu query user failed" msgstr "飞书查询用户失败" -#: authentication/views/feishu.py:138 +#: authentication/views/feishu.py:139 msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" -#: authentication/views/feishu.py:145 +#: authentication/views/feishu.py:146 msgid "Binding FeiShu successfully" msgstr "绑定 飞书 成功" -#: authentication/views/feishu.py:197 +#: authentication/views/feishu.py:198 msgid "Failed to get user from FeiShu" msgstr "从飞书获取用户失败" -#: authentication/views/feishu.py:204 +#: authentication/views/feishu.py:205 msgid "Please login with a password and then bind the FeiShu" msgstr "请使用密码登录,然后绑定飞书" @@ -2453,31 +2552,31 @@ msgstr "取消" msgid "ugettext_lazy" msgstr "ugettext_lazy" -#: common/db/fields.py:80 +#: common/db/fields.py:93 msgid "Marshal dict data to char field" msgstr "编码 dict 为 char" -#: common/db/fields.py:84 +#: common/db/fields.py:97 msgid "Marshal dict data to text field" msgstr "编码 dict 为 text" -#: common/db/fields.py:96 +#: common/db/fields.py:109 msgid "Marshal list data to char field" msgstr "编码 list 为 char" -#: common/db/fields.py:100 +#: common/db/fields.py:113 msgid "Marshal list data to text field" msgstr "编码 list 为 text" -#: common/db/fields.py:104 +#: common/db/fields.py:117 msgid "Marshal data to char field" msgstr "编码数据为 char" -#: common/db/fields.py:108 +#: common/db/fields.py:121 msgid "Marshal data to text field" msgstr "编码数据为 text" -#: common/db/fields.py:150 +#: common/db/fields.py:163 msgid "Encrypt field using Secret Key" msgstr "加密的字段" @@ -2489,16 +2588,35 @@ msgstr "更新人" msgid "Object" msgstr "对象" -#: common/drf/fields.py:70 +#: common/drf/fields.py:74 tickets/serializers/ticket/common.py:58 +#: xpack/plugins/change_auth_plan/serializers/asset.py:64 +#: xpack/plugins/change_auth_plan/serializers/asset.py:67 +#: xpack/plugins/change_auth_plan/serializers/asset.py:70 +#: xpack/plugins/change_auth_plan/serializers/asset.py:101 +#: xpack/plugins/cloud/serializers/account_attrs.py:56 +msgid "This field is required." +msgstr "该字段是必填项。" + +#: common/drf/fields.py:75 #, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "{pk_value} 对象不存在" -#: common/drf/fields.py:71 +#: common/drf/fields.py:76 #, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "不正确的类型。期望 pk 值,收到 {data_type} 类型。" +#: common/drf/fields.py:131 +msgid "Invalid data type, should be list" +msgstr "" + +#: common/drf/fields.py:146 +#, fuzzy +#| msgid "Invalid ip" +msgid "Invalid choice: {}" +msgstr "无效IP" + #: common/drf/parsers/base.py:17 msgid "The file content overflowed (The maximum length `{}` bytes)" msgstr "文件内容太大 (最大长度 `{}` 字节)" @@ -2558,15 +2676,15 @@ msgstr "忽略的" msgid "discard time" msgstr "忽略时间" -#: common/mixins/views.py:52 +#: common/mixins/views.py:58 msgid "Export all" msgstr "导出所有" -#: common/mixins/views.py:54 +#: common/mixins/views.py:60 msgid "Export only selected items" msgstr "仅导出选择项" -#: common/mixins/views.py:59 +#: common/mixins/views.py:65 #, python-format msgid "Export filtered: %s" msgstr "导出搜素: %s" @@ -2623,6 +2741,14 @@ msgstr "验证码错误" msgid "Please wait {} seconds before sending" msgstr "请在 {} 秒后发送" +#: common/tasks.py:13 +msgid "Send email" +msgstr "发送邮件" + +#: common/tasks.py:40 +msgid "Send email attachment" +msgstr "发送邮件附件" + #: common/utils/ip/geoip/utils.py:26 msgid "Invalid ip" msgstr "无效IP" @@ -2699,6 +2825,10 @@ msgstr "邮件" msgid "Site message" msgstr "站内信" +#: notifications/notifications.py:46 +msgid "Publish the station message" +msgstr "发布站内信" + #: ops/ansible/inventory.py:75 msgid "No account available" msgstr "没有账号可以使用" @@ -2764,96 +2894,107 @@ msgstr "输入在 {} - {} 范围之间" msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" -#: ops/models/adhoc.py:18 +#: ops/models/adhoc.py:21 ops/models/job.py:30 +#, fuzzy +#| msgid "PowerShell" +msgid "Powershell" +msgstr "PowerShell" + +#: ops/models/adhoc.py:25 msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:19 +#: ops/models/adhoc.py:27 ops/models/job.py:37 msgid "Module" msgstr "" -#: ops/models/adhoc.py:20 ops/models/celery.py:45 +#: ops/models/adhoc.py:28 ops/models/celery.py:48 ops/models/job.py:35 #: terminal/models/component/task.py:17 msgid "Args" msgstr "参数" -#: ops/models/adhoc.py:21 ops/models/base.py:20 ops/models/playbook.py:27 -msgid "Last execution" -msgstr "最后执行" - -#: ops/models/adhoc.py:36 -msgid "Adhoc" -msgstr "" - -#: ops/models/adhoc.py:54 -msgid "AdHoc execution" -msgstr "任务执行" - -#: ops/models/base.py:16 ops/models/base.py:52 +#: ops/models/adhoc.py:29 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:40 ops/models/job.py:61 #: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "创建者" +#: ops/models/adhoc.py:50 ops/models/job.py:20 +msgid "Adhoc" +msgstr "" + +#: ops/models/adhoc.py:68 +msgid "AdHoc execution" +msgstr "任务执行" + #: ops/models/base.py:19 msgid "Account policy" msgstr "账号策略" -#: ops/models/base.py:21 +#: ops/models/base.py:20 +msgid "Last execution" +msgstr "最后执行" + +#: ops/models/base.py:22 msgid "Date last run" msgstr "最后执行日期" -#: ops/models/base.py:50 xpack/plugins/cloud/models.py:169 +#: ops/models/base.py:51 ops/models/job.py:59 xpack/plugins/cloud/models.py:169 msgid "Result" msgstr "结果" -#: ops/models/base.py:51 +#: ops/models/base.py:52 ops/models/job.py:60 msgid "Summary" msgstr "汇总" -#: ops/models/celery.py:46 terminal/models/component/task.py:18 +#: ops/models/celery.py:49 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "其它参数" -#: ops/models/celery.py:47 tickets/models/comment.py:13 +#: ops/models/celery.py:50 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 msgid "State" msgstr "状态" -#: ops/models/celery.py:48 terminal/models/session/sharing.py:111 +#: ops/models/celery.py:51 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "结束" -#: ops/models/playbook.py:10 -msgid "Path" -msgstr "路径" - -#: ops/models/playbook.py:18 -msgid "Playbook template" -msgstr "Playbook 模版" - -#: ops/models/playbook.py:23 +#: ops/models/job.py:21 ops/models/job.py:38 msgid "Playbook" msgstr "Playbook" -#: ops/models/playbook.py:24 +#: ops/models/job.py:24 +#, fuzzy +#| msgid "Privileged" +msgid "Privileged Only" +msgstr "特权账号" + +#: ops/models/job.py:25 +#, fuzzy +#| msgid "Privileged" +msgid "Privileged First" +msgstr "特权账号" + +#: ops/models/job.py:26 +msgid "Skip" +msgstr "" + +#: ops/models/job.py:42 +msgid "Runas" +msgstr "" + +#: ops/models/job.py:44 +#, fuzzy +#| msgid "Account policy" +msgid "Runas policy" +msgstr "账号策略" + +#: ops/models/playbook.py:15 msgid "Owner" msgstr "Owner" -#: ops/models/playbook.py:26 settings/serializers/auth/sms.py:64 -msgid "Template" -msgstr "模板" - -#: ops/models/playbook.py:38 ops/signal_handlers.py:63 -#: terminal/models/component/task.py:26 -#: xpack/plugins/gathered_user/models.py:68 -msgid "Task" -msgstr "任务" - -#: ops/models/playbook.py:39 -msgid "Run dir" -msgstr "运行目录" - #: ops/notifications.py:17 msgid "Server performance" msgstr "监控告警" @@ -2882,21 +3023,38 @@ msgstr "内存使用率超过 {max_threshold}%: => {value}" msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" -#: ops/tasks.py:34 +#: ops/signal_handlers.py:63 terminal/models/component/task.py:26 +#: xpack/plugins/gathered_user/models.py:68 +msgid "Task" +msgstr "任务" + +#: ops/tasks.py:27 msgid "Run ansible task" msgstr "运行 ansible 任务" -#: ops/tasks.py:58 -msgid "Run ansible command" -msgstr "运行 ansible 命令" +#: ops/tasks.py:41 +msgid "Run ansible task execution" +msgstr "运行 ansible 任务" -#: ops/tasks.py:80 -msgid "Clean task history period" -msgstr "定期清除任务历史" +#: ops/tasks.py:54 +msgid "Periodic clear celery tasks" +msgstr "定时清理 Celery 任务" -#: ops/tasks.py:93 +#: ops/tasks.py:56 msgid "Clean celery log period" -msgstr "定期清除Celery日志" +msgstr "定期清理 Celery 日志" + +#: ops/tasks.py:73 +msgid "Clear celery periodic tasks" +msgstr "清理 Celery 定时任务" + +#: ops/tasks.py:96 +msgid "Create or update periodic tasks" +msgstr "创建或更新定时任务" + +#: ops/tasks.py:104 +msgid "Periodic check service performance" +msgstr "定时检查服务性能" #: ops/templates/ops/celery_task_log.html:4 msgid "Task log" @@ -2961,76 +3119,84 @@ msgstr "可以查看全局组织" msgid "Can view all joined org" msgstr "可以查看所有加入的组织" +#: orgs/tasks.py:9 +msgid "Refresh organization cache" +msgstr "刷新组织缓存" + #: perms/apps.py:9 msgid "App permissions" msgstr "授权管理" -#: perms/models/asset_permission.py:72 perms/serializers/permission.py:59 -#: perms/serializers/permission.py:85 -#: tickets/models/ticket/apply_application.py:26 -#: tickets/models/ticket/apply_asset.py:19 -msgid "Actions" -msgstr "动作" - -#: perms/models/asset_permission.py:83 -msgid "From ticket" -msgstr "来自工单" - -#: perms/models/asset_permission.py:224 -msgid "Ungrouped" -msgstr "未分组" - -#: perms/models/asset_permission.py:226 -msgid "Favorite" -msgstr "收藏夹" - -#: perms/models/asset_permission.py:273 -msgid "Permed asset" -msgstr "授权的资产" - -#: perms/models/asset_permission.py:275 -msgid "Can view my assets" -msgstr "可以查看我的资产" - -#: perms/models/asset_permission.py:276 -msgid "Can view user assets" -msgstr "可以查看用户授权的资产" - -#: perms/models/asset_permission.py:277 -msgid "Can view usergroup assets" -msgstr "可以查看用户组授权的资产" - -#: perms/models/const.py:20 settings/serializers/terminal.py:12 -msgid "All" -msgstr "全部" - -#: perms/models/const.py:21 +#: perms/const.py:13 msgid "Connect" msgstr "连接" -#: perms/models/const.py:22 -msgid "Upload file" -msgstr "上传文件" +#: perms/const.py:16 +#, fuzzy +#| msgid "Copy link" +msgid "Copy" +msgstr "复制链接" -#: perms/models/const.py:23 -msgid "Download file" -msgstr "下载文件" +#: perms/const.py:17 +msgid "Paste" +msgstr "" -#: perms/models/const.py:24 -msgid "Upload download" -msgstr "上传下载" +#: perms/const.py:26 +msgid "Transfer" +msgstr "" -#: perms/models/const.py:25 -msgid "Clipboard copy" +#: perms/const.py:27 +#, fuzzy +#| msgid "Clipboard copy" +msgid "Clipboard" msgstr "剪贴板复制" -#: perms/models/const.py:26 -msgid "Clipboard paste" -msgstr "剪贴板粘贴" +#: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 +#: perms/serializers/permission.py:29 perms/serializers/permission.py:59 +#: tickets/models/ticket/apply_application.py:26 +#: tickets/models/ticket/apply_asset.py:17 +msgid "Actions" +msgstr "动作" -#: perms/models/const.py:27 -msgid "Clipboard copy paste" -msgstr "剪贴板复制粘贴" +#: perms/models/asset_permission.py:76 +msgid "From ticket" +msgstr "来自工单" + +#: perms/models/perm_node.py:55 +msgid "Ungrouped" +msgstr "未分组" + +#: perms/models/perm_node.py:57 +msgid "Favorite" +msgstr "收藏夹" + +#: perms/models/perm_node.py:104 +msgid "Permed asset" +msgstr "授权的资产" + +#: perms/models/perm_node.py:106 +msgid "Can view my assets" +msgstr "可以查看我的资产" + +#: perms/models/perm_node.py:107 +msgid "Can view user assets" +msgstr "可以查看用户授权的资产" + +#: perms/models/perm_node.py:108 +msgid "Can view usergroup assets" +msgstr "可以查看用户组授权的资产" + +#: perms/models/perm_node.py:119 +#, fuzzy +#| msgid "Create account" +msgid "Permed account" +msgstr "收集账号" + +#: perms/models/perm_token.py:17 +#, fuzzy +#| msgid "Connect timeout" +msgid "Connect method" +msgstr "连接超时时间" #: perms/notifications.py:12 perms/notifications.py:44 msgid "today" @@ -3052,40 +3218,11 @@ msgstr "资产授权规则将要过期" msgid "asset permissions of organization {}" msgstr "组织 ({}) 的资产授权" -#: perms/serializers/permission.py:48 -msgid "Users display" -msgstr "用户名称" - -#: perms/serializers/permission.py:51 -msgid "User groups display" -msgstr "用户组名称" - -#: perms/serializers/permission.py:54 -msgid "Assets display" -msgstr "资产名称" - -#: perms/serializers/permission.py:57 -msgid "Nodes display" -msgstr "节点名称" - -#: perms/serializers/permission.py:61 perms/serializers/permission.py:86 -#: users/serializers/user.py:89 users/serializers/user.py:150 +#: perms/serializers/permission.py:31 perms/serializers/permission.py:60 +#: users/serializers/user.py:100 users/serializers/user.py:205 msgid "Is expired" msgstr "已过期" -#: perms/serializers/permission.py:81 rbac/serializers/role.py:26 -#: users/serializers/group.py:34 -msgid "Users amount" -msgstr "用户数量" - -#: perms/serializers/permission.py:82 -msgid "User groups amount" -msgstr "用户组数量" - -#: perms/serializers/permission.py:84 -msgid "Nodes amount" -msgstr "节点数量" - #: perms/templates/perms/_msg_item_permissions_expire.html:7 #: perms/templates/perms/_msg_permed_items_expire.html:7 #, python-format @@ -3102,7 +3239,7 @@ msgstr "" msgid "If you have any question, please contact the administrator" msgstr "如果有疑问或需求,请联系系统管理员" -#: perms/utils/user_permission.py:623 rbac/tree.py:57 +#: perms/utils/user_permission.py:622 rbac/tree.py:57 msgid "My assets" msgstr "我的资产" @@ -3234,6 +3371,10 @@ msgstr "权限" msgid "Scope display" msgstr "范围名称" +#: rbac/serializers/role.py:26 users/serializers/group.py:34 +msgid "Users amount" +msgstr "用户数量" + #: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 msgid "Display name" msgstr "显示名称" @@ -3722,6 +3863,10 @@ msgstr "原始号码(Src id)" msgid "Business type(Service id)" msgstr "业务类型(Service id)" +#: settings/serializers/auth/sms.py:64 +msgid "Template" +msgstr "模板" + #: settings/serializers/auth/sms.py:65 #, python-brace-format msgid "" @@ -4473,13 +4618,13 @@ msgstr "过期。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" -" 您的密码已经过期,请点击 链接 更新密码\n" +" 您的密码已经过期,请点击 链接 更新密码\n" " " #: templates/_message.html:30 @@ -4503,8 +4648,8 @@ msgstr "" #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" @@ -4516,13 +4661,13 @@ msgstr "" #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" +" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -4691,7 +4836,7 @@ msgid "Timestamp" msgstr "时间戳" #: terminal/backends/command/serializers.py:41 -#: terminal/models/component/terminal.py:105 +#: terminal/models/component/terminal.py:87 msgid "Remote Address" msgstr "远端地址" @@ -4731,39 +4876,35 @@ msgstr "标签" msgid "Hosts" msgstr "主机" -#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:28 +#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 msgid "Applet" msgstr "远程应用" -#: terminal/models/applet/host.py:19 terminal/serializers/applet_host.py:36 +#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 #, fuzzy #| msgid "More login options" msgid "Deploy options" msgstr "其他方式登录" -#: terminal/models/applet/host.py:20 +#: terminal/models/applet/host.py:19 msgid "Inited" msgstr "" -#: terminal/models/applet/host.py:21 +#: terminal/models/applet/host.py:20 #, fuzzy #| msgid "Date finished" msgid "Date inited" msgstr "结束日期" -#: terminal/models/applet/host.py:22 +#: terminal/models/applet/host.py:21 msgid "Date synced" msgstr "最后同步日期" -#: terminal/models/applet/host.py:25 terminal/models/component/terminal.py:183 -msgid "Terminal" -msgstr "终端" - -#: terminal/models/applet/host.py:99 +#: terminal/models/applet/host.py:102 msgid "Hosting" msgstr "主机" -#: terminal/models/applet/host.py:100 +#: terminal/models/applet/host.py:103 msgid "Initial" msgstr "" @@ -4772,12 +4913,10 @@ msgid "HTTPS Port" msgstr "HTTPS 端口" #: terminal/models/component/endpoint.py:15 -#: terminal/models/component/terminal.py:107 msgid "HTTP Port" msgstr "HTTP 端口" #: terminal/models/component/endpoint.py:16 -#: terminal/models/component/terminal.py:106 msgid "SSH Port" msgstr "SSH 端口" @@ -4825,31 +4964,31 @@ msgstr "IP 组" msgid "Endpoint rule" msgstr "端点规则" -#: terminal/models/component/status.py:18 +#: terminal/models/component/status.py:14 msgid "Session Online" msgstr "在线会话" -#: terminal/models/component/status.py:19 +#: terminal/models/component/status.py:15 msgid "CPU Load" msgstr "CPU负载" -#: terminal/models/component/status.py:20 +#: terminal/models/component/status.py:16 msgid "Memory Used" msgstr "内存使用" -#: terminal/models/component/status.py:21 +#: terminal/models/component/status.py:17 msgid "Disk Used" msgstr "磁盘使用" -#: terminal/models/component/status.py:22 +#: terminal/models/component/status.py:18 msgid "Connections" msgstr "连接数" -#: terminal/models/component/status.py:23 +#: terminal/models/component/status.py:19 msgid "Threads" msgstr "线程数" -#: terminal/models/component/status.py:24 +#: terminal/models/component/status.py:20 msgid "Boot Time" msgstr "运行时间" @@ -4858,20 +4997,20 @@ msgid "Default storage" msgstr "默认存储" #: terminal/models/component/storage.py:136 -#: terminal/models/component/terminal.py:108 +#: terminal/models/component/terminal.py:88 msgid "Command storage" msgstr "命令存储" #: terminal/models/component/storage.py:196 -#: terminal/models/component/terminal.py:109 +#: terminal/models/component/terminal.py:89 msgid "Replay storage" msgstr "录像存储" -#: terminal/models/component/terminal.py:103 +#: terminal/models/component/terminal.py:85 msgid "type" msgstr "类型" -#: terminal/models/component/terminal.py:185 +#: terminal/models/component/terminal.py:161 msgid "Can view terminal config" msgstr "可以查看终端配置" @@ -5003,38 +5142,42 @@ msgstr "不匹配" msgid "Icon" msgstr "图标" -#: terminal/serializers/applet_host.py:20 +#: terminal/serializers/applet_host.py:22 msgid "Per Session" msgstr "按会话" -#: terminal/serializers/applet_host.py:21 +#: terminal/serializers/applet_host.py:23 msgid "Per Device" msgstr "按设备" -#: terminal/serializers/applet_host.py:27 +#: terminal/serializers/applet_host.py:29 msgid "RDS Licensing" msgstr "部署 RDS 许可服务" -#: terminal/serializers/applet_host.py:28 +#: terminal/serializers/applet_host.py:30 msgid "RDS License Server" msgstr "RDS 许可服务主机" -#: terminal/serializers/applet_host.py:29 +#: terminal/serializers/applet_host.py:31 msgid "RDS Licensing Mode" msgstr "RDS 许可模式" -#: terminal/serializers/applet_host.py:30 +#: terminal/serializers/applet_host.py:32 msgid "RDS fSingleSessionPerUser" msgstr "RDS 会话用户数" -#: terminal/serializers/applet_host.py:31 +#: terminal/serializers/applet_host.py:33 msgid "RDS Max Disconnection Time" msgstr "RDS 会话断开时间" -#: terminal/serializers/applet_host.py:32 +#: terminal/serializers/applet_host.py:34 msgid "RDS Remote App Logoff Time Limit" msgstr "RDS 远程应用注销时间" +#: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 +msgid "Load status" +msgstr "负载状态" + #: terminal/serializers/endpoint.py:12 msgid "Oracle port" msgstr "" @@ -5154,11 +5297,7 @@ msgstr "文档类型" msgid "Ignore Certificate Verification" msgstr "忽略证书认证" -#: terminal/serializers/terminal.py:44 -msgid "Load status" -msgstr "负载状态" - -#: terminal/serializers/terminal.py:81 terminal/serializers/terminal.py:89 +#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 msgid "Not found" msgstr "没有发现" @@ -5278,11 +5417,11 @@ msgstr "内容" msgid "Approve level" msgstr "审批级别" -#: tickets/models/flow.py:25 tickets/serializers/flow.py:15 +#: tickets/models/flow.py:25 tickets/serializers/flow.py:17 msgid "Approve strategy" msgstr "审批策略" -#: tickets/models/flow.py:30 tickets/serializers/flow.py:16 +#: tickets/models/flow.py:30 tickets/serializers/flow.py:19 msgid "Assignees" msgstr "受理人" @@ -5299,7 +5438,7 @@ msgid "Ticket session relation" msgstr "工单会话" #: tickets/models/ticket/apply_application.py:11 -#: tickets/models/ticket/apply_asset.py:13 +#: tickets/models/ticket/apply_asset.py:12 msgid "Permission name" msgstr "授权规则名称" @@ -5311,20 +5450,20 @@ msgstr "申请应用" msgid "Apply system users" msgstr "申请的系统用户" -#: tickets/models/ticket/apply_asset.py:9 +#: tickets/models/ticket/apply_asset.py:8 #: tickets/serializers/ticket/apply_asset.py:15 msgid "Select at least one asset or node" msgstr "资产或者节点至少选择一项" -#: tickets/models/ticket/apply_asset.py:14 +#: tickets/models/ticket/apply_asset.py:13 msgid "Apply nodes" msgstr "申请节点" -#: tickets/models/ticket/apply_asset.py:16 +#: tickets/models/ticket/apply_asset.py:15 msgid "Apply assets" msgstr "申请资产" -#: tickets/models/ticket/apply_asset.py:17 +#: tickets/models/ticket/apply_asset.py:16 msgid "Apply accounts" msgstr "申请账号" @@ -5428,15 +5567,15 @@ msgstr "你的工单已被处理, 处理人 - {}" msgid "Ticket has processed - {} ({})" msgstr "你的工单已被处理, 处理人 - {} ({})" -#: tickets/serializers/flow.py:17 +#: tickets/serializers/flow.py:20 msgid "Assignees display" msgstr "受理人名称" -#: tickets/serializers/flow.py:43 +#: tickets/serializers/flow.py:46 msgid "Please select the Assignees" msgstr "请选择受理人" -#: tickets/serializers/flow.py:69 +#: tickets/serializers/flow.py:74 msgid "The current organization type already exists" msgstr "当前组织已存在该类型" @@ -5457,6 +5596,14 @@ msgstr "过期时间要大于开始时间" msgid "Permission named `{}` already exists" msgstr "授权名称 `{}` 已存在" +#: tickets/serializers/ticket/ticket.py:17 +msgid "Type display" +msgstr "类型名称" + +#: tickets/serializers/ticket/ticket.py:18 +msgid "Status display" +msgstr "状态名称" + #: tickets/serializers/ticket/ticket.py:101 msgid "The ticket flow `{}` does not exist" msgstr "工单流程 `{}` 不存在" @@ -5630,7 +5777,7 @@ msgstr "强制启用" msgid "Local" msgstr "数据库" -#: users/models/user.py:677 users/serializers/user.py:149 +#: users/models/user.py:677 users/serializers/user.py:204 msgid "Is service account" msgstr "服务账号" @@ -5737,105 +5884,105 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:149 users/serializers/user.py:146 +#: users/serializers/profile.py:149 users/serializers/user.py:201 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:28 +#: users/serializers/user.py:30 msgid "System roles" msgstr "系统角色" -#: users/serializers/user.py:33 +#: users/serializers/user.py:35 msgid "Org roles" msgstr "组织角色" -#: users/serializers/user.py:35 +#: users/serializers/user.py:38 msgid "System roles display" msgstr "系统角色显示" -#: users/serializers/user.py:36 +#: users/serializers/user.py:40 msgid "Org roles display" msgstr "组织角色显示" -#: users/serializers/user.py:81 +#: users/serializers/user.py:90 #: xpack/plugins/change_auth_plan/models/base.py:35 #: xpack/plugins/change_auth_plan/serializers/base.py:27 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:83 +#: users/serializers/user.py:92 msgid "MFA enabled" msgstr "MFA 已启用" -#: users/serializers/user.py:84 +#: users/serializers/user.py:94 msgid "MFA force enabled" msgstr "强制 MFA" -#: users/serializers/user.py:86 +#: users/serializers/user.py:97 msgid "MFA level display" msgstr "MFA 等级名称" -#: users/serializers/user.py:88 +#: users/serializers/user.py:99 msgid "Login blocked" msgstr "登录被阻塞" -#: users/serializers/user.py:91 +#: users/serializers/user.py:102 msgid "Can public key authentication" msgstr "能否公钥认证" -#: users/serializers/user.py:151 +#: users/serializers/user.py:206 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:153 +#: users/serializers/user.py:208 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:154 +#: users/serializers/user.py:209 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:155 +#: users/serializers/user.py:210 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:156 +#: users/serializers/user.py:211 msgid "Super role name" msgstr "超级角色名称" -#: users/serializers/user.py:157 +#: users/serializers/user.py:212 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:159 +#: users/serializers/user.py:214 msgid "Is wecom bound" msgstr "是否绑定了企业微信" -#: users/serializers/user.py:160 +#: users/serializers/user.py:215 msgid "Is dingtalk bound" msgstr "是否绑定了钉钉" -#: users/serializers/user.py:161 +#: users/serializers/user.py:216 msgid "Is feishu bound" msgstr "是否绑定了飞书" -#: users/serializers/user.py:162 +#: users/serializers/user.py:217 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" -#: users/serializers/user.py:164 +#: users/serializers/user.py:219 msgid "System role name" msgstr "系统角色名称" -#: users/serializers/user.py:263 +#: users/serializers/user.py:325 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:264 +#: users/serializers/user.py:326 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:299 +#: users/serializers/user.py:362 msgid "name not unique" msgstr "名称重复" @@ -6076,10 +6223,6 @@ msgstr "重置密码成功,返回到登录页面" msgid "XPACK" msgstr "XPack" -#: xpack/plugins/change_auth_plan/api/asset.py:94 -msgid "The parameter 'action' must be [{}]" -msgstr "参数 'action' 必须是 [{}]" - #: xpack/plugins/change_auth_plan/meta.py:9 #: xpack/plugins/change_auth_plan/models/asset.py:124 msgid "Change auth plan" @@ -6108,11 +6251,6 @@ msgstr "应用改密计划任务" msgid "Password cannot be set to blank, exit. " msgstr "密码不能设置为空, 退出. " -#: xpack/plugins/change_auth_plan/models/asset.py:50 -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 -msgid "SSH Key strategy" -msgstr "SSH 密钥策略" - #: xpack/plugins/change_auth_plan/models/asset.py:68 msgid "Asset change auth plan" msgstr "资产改密计划" @@ -6166,25 +6304,6 @@ msgstr "保存密码/密钥" msgid "Step" msgstr "步骤" -#: xpack/plugins/change_auth_plan/notifications.py:8 -msgid "Notification of implementation result of encryption change plan" -msgstr "改密计划任务结果通知" - -#: xpack/plugins/change_auth_plan/notifications.py:18 -msgid "" -"{} - The encryption change task has been completed. See the attachment for " -"details" -msgstr "{} - 改密任务已完成, 详情见附件" - -#: xpack/plugins/change_auth_plan/notifications.py:19 -msgid "" -"{} - The encryption change task has been completed: the encryption password " -"has not been set - please go to personal information -> file encryption " -"password to set the encryption password" -msgstr "" -"{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" -"密密码" - #: xpack/plugins/change_auth_plan/serializers/asset.py:30 msgid "Change Password" msgstr "更改密码" @@ -6197,14 +6316,6 @@ msgstr "修改 SSH Key" msgid "Run times" msgstr "执行次数" -#: xpack/plugins/change_auth_plan/serializers/base.py:58 -msgid "* Please enter the correct password length" -msgstr "* 请输入正确的密码长度" - -#: xpack/plugins/change_auth_plan/serializers/base.py:61 -msgid "* Password length range 6-30 bits" -msgstr "* 密码长度范围 6-30 位" - #: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 msgid "After many attempts to change the secret, it still failed" msgstr "多次尝试改密后, 依然失败" @@ -6221,6 +6332,18 @@ msgstr "连接主机失败" msgid "Data could not be sent to remote" msgstr "无法将数据发送到远程" +#: xpack/plugins/change_auth_plan/tasks.py:13 +msgid "Execute change authentication task" +msgstr "执行资产改密计划任务" + +#: xpack/plugins/change_auth_plan/tasks.py:24 +msgid "Start change authentication task" +msgstr "开始资产改密计划任务" + +#: xpack/plugins/change_auth_plan/tasks.py:36 +msgid "Test the validity of the change authentication plan " +msgstr "测试资产改密结果" + #: xpack/plugins/cloud/api.py:40 msgid "Test connection successful" msgstr "测试成功" @@ -6753,11 +6876,11 @@ msgstr "主题" msgid "Interface setting" msgstr "界面设置" -#: xpack/plugins/license/api.py:53 +#: xpack/plugins/license/api.py:50 msgid "License import successfully" msgstr "许可证导入成功" -#: xpack/plugins/license/api.py:54 +#: xpack/plugins/license/api.py:51 msgid "License is invalid" msgstr "无效的许可证" @@ -6781,8 +6904,64 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" -#~ msgid "Account automation" -#~ msgstr "账号自动化" +#~ msgid "Run ansible command" +#~ msgstr "运行 ansible 命令" + +#~ msgid "Clean task history period" +#~ msgstr "定期清除任务历史" + +#, fuzzy +#~| msgid "WeCom Error" +#~ msgid "Hello Error" +#~ msgstr "企业微信错误" + +#~ msgid "Operate display" +#~ msgstr "操作名称" + +#~ msgid "MFA display" +#~ msgstr "MFA名称" + +#~ msgid "Path" +#~ msgstr "路径" + +#~ msgid "Playbook template" +#~ msgstr "Playbook 模版" + +#~ msgid "Run dir" +#~ msgstr "运行目录" + +#~ msgid "Upload file" +#~ msgstr "上传文件" + +#~ msgid "Download file" +#~ msgstr "下载文件" + +#~ msgid "Upload download" +#~ msgstr "上传下载" + +#~ msgid "Clipboard paste" +#~ msgstr "剪贴板粘贴" + +#~ msgid "Clipboard copy paste" +#~ msgstr "剪贴板复制粘贴" + +#~ msgid "Users display" +#~ msgstr "用户名称" + +#~ msgid "User groups display" +#~ msgstr "用户组名称" + +#~ msgid "Assets display" +#~ msgstr "资产名称" + +#~ msgid "Nodes display" +#~ msgstr "节点名称" + +#~ msgid "User groups amount" +#~ msgstr "用户组数量" + +#~ msgid "Nodes amount" +#~ msgstr "节点数量" #~ msgid "The asset {} system platform {} does not support run Ansible tasks" #~ msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" @@ -7069,66 +7248,54 @@ msgstr "社区版" #~ msgid "Asset and SystemUser" #~ msgstr "资产与系统用户" -#, python-brace-format #~ msgid "{Asset} ADD {SystemUser}" #~ msgstr "{Asset} 添加 {SystemUser}" -#, python-brace-format #~ msgid "{Asset} REMOVE {SystemUser}" #~ msgstr "{Asset} 移除 {SystemUser}" #~ msgid "Asset permission and SystemUser" #~ msgstr "资产授权与系统用户" -#, python-brace-format #~ msgid "{AssetPermission} ADD {SystemUser}" #~ msgstr "{AssetPermission} 添加 {SystemUser}" -#, python-brace-format #~ msgid "{AssetPermission} REMOVE {SystemUser}" #~ msgstr "{AssetPermission} 移除 {SystemUser}" #~ msgid "User application permissions" #~ msgstr "用户应用授权" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {User}" #~ msgstr "{ApplicationPermission} 添加 {User}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {User}" #~ msgstr "{ApplicationPermission} 移除 {User}" #~ msgid "User group application permissions" #~ msgstr "用户组应用授权" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {UserGroup}" #~ msgstr "{ApplicationPermission} 添加 {UserGroup}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {UserGroup}" #~ msgstr "{ApplicationPermission} 移除 {UserGroup}" #~ msgid "Application permission" #~ msgstr "应用授权" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {Application}" #~ msgstr "{ApplicationPermission} 添加 {Application}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {Application}" #~ msgstr "{ApplicationPermission} 移除 {Application}" #~ msgid "Application permission and SystemUser" #~ msgstr "应用授权与系统用户" -#, python-brace-format #~ msgid "{ApplicationPermission} ADD {SystemUser}" #~ msgstr "{ApplicationPermission} 添加 {SystemUser}" -#, python-brace-format #~ msgid "{ApplicationPermission} REMOVE {SystemUser}" #~ msgstr "{ApplicationPermission} 移除 {SystemUser}" diff --git a/apps/notifications/notifications.py b/apps/notifications/notifications.py index 481a4bc08..55f1cdbad 100644 --- a/apps/notifications/notifications.py +++ b/apps/notifications/notifications.py @@ -16,7 +16,6 @@ from .models import SystemMsgSubscription, UserMsgSubscription __all__ = ('SystemMessage', 'UserMessage', 'system_msgs', 'Message') - system_msgs = [] user_msgs = [] @@ -44,7 +43,7 @@ class MessageType(type): return clz -@shared_task +@shared_task(verbose_name=_('Publish the station message')) def publish_task(msg): msg.publish() diff --git a/apps/ops/api/celery.py b/apps/ops/api/celery.py index 4376a9232..8d58c1981 100644 --- a/apps/ops/api/celery.py +++ b/apps/ops/api/celery.py @@ -104,10 +104,12 @@ class CelerySummaryAPIView(generics.RetrieveAPIView): class CeleryTaskViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): - queryset = CeleryTask.objects.all() serializer_class = CeleryTaskSerializer http_method_names = ('get', 'head', 'options',) + def get_queryset(self): + return CeleryTask.objects.exclude(name__startswith='celery') + class CeleryTaskExecutionViewSet(CommonApiMixin, viewsets.ReadOnlyModelViewSet): serializer_class = CeleryTaskExecutionSerializer diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 01251f8b2..1b444ded1 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -37,6 +37,9 @@ class CeleryTask(models.Model): return "yellow" return "green" + class Meta: + ordering = ('name',) + class CeleryTaskExecution(models.Model): LOG_DIR = os.path.join(settings.PROJECT_DIR, 'data', 'celery') diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index 6e0155288..10be7bd06 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -16,4 +16,4 @@ class Playbook(BaseCreateUpdateModel): @property def work_path(self): - return os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__()) + return os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__(), "main.yaml") diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index e802970c7..c350cdb6b 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -1,19 +1,15 @@ # coding: utf-8 import os -import random import subprocess -import time from django.conf import settings -from celery import shared_task, subtask -from celery import signals +from celery import shared_task from celery.exceptions import SoftTimeLimitExceeded from django.utils import timezone -from django.utils.translation import ugettext_lazy as _, gettext +from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, get_object_or_none, get_log_keep_day -from orgs.utils import tmp_to_root_org, tmp_to_org from .celery.decorator import ( register_as_period_task, after_app_shutdown_clean_periodic, after_app_ready_start @@ -22,16 +18,12 @@ from .celery.utils import ( create_or_update_celery_periodic_tasks, get_celery_periodic_task, disable_celery_periodic_task, delete_celery_periodic_task ) -from .models import CeleryTaskExecution, Playbook, Job, JobExecution +from .models import CeleryTaskExecution, Job, JobExecution from .notifications import ServerPerformanceCheckUtil logger = get_logger(__file__) -def rerun_task(): - pass - - @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) def run_ops_job(job_id, **kwargs): job = get_object_or_none(Job, id=job_id) @@ -59,64 +51,7 @@ def run_ops_job_executions(execution_id, **kwargs): logger.error("Start adhoc execution error: {}".format(e)) -@shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) -def run_adhoc(tid, **kwargs): - """ - :param tid: is the tasks serialized data - :param callback: callback function name - :return: - """ - with tmp_to_root_org(): - task = get_object_or_none(AdHoc, id=tid) - if not task: - logger.error("No task found") - return - with tmp_to_org(task.org): - execution = task.create_execution() - try: - execution.start(**kwargs) - except SoftTimeLimitExceeded: - execution.set_error('Run timeout') - logger.error("Run adhoc timeout") - except Exception as e: - execution.set_error(e) - logger.error("Start adhoc execution error: {}".format(e)) - - -@shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible command")) -def run_playbook(pid, **kwargs): - with tmp_to_root_org(): - task = get_object_or_none(Playbook, id=pid) - if not task: - logger.error("No task found") - return - - with tmp_to_org(task.org): - execution = task.create_execution() - try: - execution.start(**kwargs) - except SoftTimeLimitExceeded: - execution.set_error('Run timeout') - logger.error("Run playbook timeout") - except Exception as e: - execution.set_error(e) - logger.error("Run playbook execution error: {}".format(e)) - - -@shared_task -@after_app_shutdown_clean_periodic -@register_as_period_task(interval=3600 * 24, description=_("Clean task history period")) -def clean_tasks_adhoc_period(): - logger.debug("Start clean task adhoc and run history") - tasks = Task.objects.all() - for task in tasks: - adhoc = task.adhoc.all().order_by('-date_created')[5:] - for ad in adhoc: - ad.execution.all().delete() - ad.delete() - - -@shared_task +@shared_task(verbose_name=_('Periodic clear celery tasks')) @after_app_shutdown_clean_periodic @register_as_period_task(interval=3600 * 24, description=_("Clean celery log period")) def clean_celery_tasks_period(): @@ -135,7 +70,7 @@ def clean_celery_tasks_period(): subprocess.call(command, shell=True) -@shared_task +@shared_task(verbose_name=_('Clear celery periodic tasks')) @after_app_ready_start def clean_celery_periodic_tasks(): """清除celery定时任务""" @@ -158,7 +93,7 @@ def clean_celery_periodic_tasks(): logger.info('Clean task failure: {}'.format(task)) -@shared_task +@shared_task(verbose_name=_('Create or update periodic tasks')) @after_app_ready_start def create_or_update_registered_periodic_tasks(): from .celery.decorator import get_register_period_tasks @@ -166,42 +101,7 @@ def create_or_update_registered_periodic_tasks(): create_or_update_celery_periodic_tasks(task) -@shared_task +@shared_task(verbose_name=_("Periodic check service performance")) @register_as_period_task(interval=3600) def check_server_performance_period(): ServerPerformanceCheckUtil().check_and_publish() - - -@shared_task(verbose_name=_("Hello"), comment="an test shared task") -def hello(name, callback=None): - from users.models import User - import time - - count = User.objects.count() - print(gettext("Hello") + ': ' + name) - print("Count: ", count) - time.sleep(1) - return gettext("Hello") - - -@shared_task(verbose_name=_("Hello Error"), comment="an test shared task error") -def hello_error(): - raise Exception("must be error") - - -@shared_task(verbose_name=_("Hello Random"), comment="some time error and some time success") -def hello_random(): - i = random.randint(0, 1) - if i == 1: - raise Exception("must be error") - - -@shared_task(verbose_name="Hello Running", comment="an task running 1m") -def hello_running(sec=60): - time.sleep(sec) - - -@shared_task -def hello_callback(result): - print(result) - print("Hello callback") diff --git a/apps/orgs/tasks.py b/apps/orgs/tasks.py index 6b6ec9e0d..04992f52a 100644 --- a/apps/orgs/tasks.py +++ b/apps/orgs/tasks.py @@ -1,11 +1,12 @@ from celery import shared_task +from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger logger = get_logger(__file__) -@shared_task +@shared_task(verbose_name=_("Refresh organization cache")) def refresh_org_cache_task(*fields): from .caches import OrgResourceStatisticsCache OrgResourceStatisticsCache.refresh(*fields) From 7bfa21260c3e1a308dfa14f206097d3c78823436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Tue, 15 Nov 2022 17:24:56 +0800 Subject: [PATCH 343/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20Dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 96 ++++++++++++++++++++++++++++----------------------- entrypoint.sh | 4 ++- 2 files changed, 56 insertions(+), 44 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7e6eac422..b228185bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,53 +1,66 @@ +FROM python:3.8-slim as stage-build +ARG TARGETARCH + +ARG VERSION +ENV VERSION=$VERSION + +WORKDIR /opt/jumpserver +ADD . . +RUN cd utils && bash -ixeu build.sh + FROM python:3.8-slim +ARG TARGETARCH MAINTAINER JumpServer Team ARG BUILD_DEPENDENCIES=" \ - g++ \ - make \ - pkg-config" + g++ \ + make \ + pkg-config" ARG DEPENDENCIES=" \ - default-libmysqlclient-dev \ - freetds-dev \ - libpq-dev \ - libffi-dev \ - libldap2-dev \ - libsasl2-dev \ - libxml2-dev \ - libxmlsec1-dev \ - libxmlsec1-openssl \ - libaio-dev \ - openssh-client \ - sshpass" + default-libmysqlclient-dev \ + freetds-dev \ + libpq-dev \ + libffi-dev \ + libjpeg-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libxmlsec1-openssl \ + libaio-dev \ + openssh-client \ + sshpass" ARG TOOLS=" \ - curl \ - default-mysql-client \ - iproute2 \ - iputils-ping \ - locales \ - procps \ - redis-tools \ - telnet \ - vim \ - unzip \ - wget" + ca-certificates \ + curl \ + default-mysql-client \ + iputils-ping \ + locales \ + procps \ + redis-tools \ + telnet \ + vim \ + unzip \ + wget" -RUN sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ + sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ + && rm -f /etc/apt/apt.conf.d/docker-clean \ + && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ && apt-get -y install --no-install-recommends ${TOOLS} \ - && localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 \ - && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && mkdir -p /root/.ssh/ \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ && rm -rf /var/lib/apt/lists/* -ARG TARGETARCH ARG ORACLE_LIB_MAJOR=19 ARG ORACLE_LIB_MINOR=10 ENV ORACLE_FILE="instantclient-basiclite-linux.${TARGETARCH:-amd64}-${ORACLE_LIB_MAJOR}.${ORACLE_LIB_MINOR}.0.0.0dbru.zip" @@ -68,21 +81,18 @@ ARG PIP_MIRROR=https://pypi.douban.com/simple ENV PIP_MIRROR=$PIP_MIRROR ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR -# 因为以 jms 或者 jumpserver 开头的 mirror 上可能没有 -RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_MIRROR} \ - && pip install --no-cache-dir $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ - && pip install --no-cache-dir -r requirements/requirements.txt -i ${PIP_MIRROR} \ - && rm -rf ~/.cache/pip -ARG VERSION -ENV VERSION=$VERSION +RUN --mount=type=cache,target=/root/.cache/pip \ + set -ex \ + && pip config set global.index-url ${PIP_MIRROR} \ + && pip install --upgrade pip \ + && pip install --upgrade setuptools wheel \ + && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ + && pip install -r requirements/requirements.txt -ADD . . -RUN cd utils \ - && bash -ixeu build.sh \ - && mv ../release/jumpserver /opt/jumpserver \ - && rm -rf /tmp/build \ - && echo > /opt/jumpserver/config.yml +COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver +RUN echo > /opt/jumpserver/config.yml \ + && rm -rf /tmp/build WORKDIR /opt/jumpserver VOLUME /opt/jumpserver/data diff --git a/entrypoint.sh b/entrypoint.sh index fe81b1470..58ed0b104 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -11,6 +11,9 @@ action="${1-start}" service="${2-all}" trap cleanup EXIT + +rm -f /opt/jumpserver/tmp/*.pid + if [[ "$action" == "bash" || "$action" == "sh" ]];then bash elif [[ "$action" == "sleep" ]];then @@ -19,4 +22,3 @@ elif [[ "$action" == "sleep" ]];then else python jms "${action}" "${service}" fi - From 3e31c9ed7fd2f4f1680d28567357f553da50c61d Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 15 Nov 2022 17:49:28 +0800 Subject: [PATCH 344/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E7=9A=84=E8=B5=84=E4=BA=A7=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/accounts.py | 7 ++---- apps/perms/api/user_permission/mixin.py | 28 +++++++++++++--------- apps/perms/utils/account.py | 12 +++++----- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 257ff8c31..31f73d037 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -1,7 +1,7 @@ from django.shortcuts import get_object_or_404 from rest_framework.generics import ListAPIView, get_object_or_404 -from common.utils import get_logger +from common.utils import get_logger, lazyproperty from perms import serializers from perms.hands import Asset from perms.utils import PermAccountUtil @@ -16,11 +16,8 @@ __all__ = [ class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): serializer_class = serializers.AccountsGrantedSerializer - rbac_perms = ( - ('GET', 'perms.view_userassets'), - ('list', 'perms.view_userassets'), - ) + @lazyproperty def asset(self): asset_id = self.kwargs.get('asset_id') kwargs = {'id': asset_id, 'is_active': True} diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 510ed9f1a..9ff8ed0f1 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -2,8 +2,11 @@ # from django.shortcuts import get_object_or_404 from rest_framework.request import Request +from django.utils.translation import ugettext_lazy as _ from common.http import is_true +from common.utils import is_uuid +from common.exceptions import JMSObjectDoesNotExist from common.mixins.api import RoleAdminMixin, RoleUserMixin from perms.utils.user_permission import UserGrantedTreeRefreshController from rbac.permissions import RBACPermission @@ -43,6 +46,12 @@ class SelfOrPKUserMixin: request: Request permission_classes = (RBACPermission,) + def get_rbac_perms(self): + if self.request_user_is_self(): + return self.self_rbac_perms + else: + return self.admin_rbac_perms + @property def self_rbac_perms(self): return ( @@ -61,18 +70,15 @@ class SelfOrPKUserMixin: ('GET', 'perms.view_userassets'), ) - def get_rbac_perms(self): - if self.request_user_is_self(): - return self.self_rbac_perms - else: - return self.admin_rbac_perms - - def request_user_is_self(self): - return self.kwargs.get('user') in ['my', 'self'] - @property def user(self): if self.request_user_is_self(): - return self.request.user + user = self.request.user + elif is_uuid(self.kwargs.get('user')): + user = get_object_or_404(User, pk=self.kwargs.get('user')) else: - return get_object_or_404(User, pk=self.kwargs.get('user')) + raise JMSObjectDoesNotExist(object_name=_('User')) + return user + + def request_user_is_self(self): + return self.kwargs.get('user') in ['my', 'self'] diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index baaedb8fc..1ce1504b0 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -9,6 +9,12 @@ __all__ = ['PermAccountUtil'] class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ + def get_permed_accounts_for_user(self, user, asset): + """ 获取授权给用户某个资产的账号 """ + perms = self.get_permissions_for_user_asset(user, asset) + permed_accounts = self.get_permed_accounts_from_perms(perms, user, asset) + return permed_accounts + @staticmethod def get_permed_accounts_from_perms(perms, user, asset): alias_action_bit_mapper = defaultdict(int) @@ -55,12 +61,6 @@ class PermAccountUtil(AssetPermissionUtil): accounts.append(account) return accounts - def get_permed_accounts_for_user(self, user, asset): - """ 获取授权给用户某个资产的账号 """ - perms = self.get_permissions_for_user_asset(user, asset) - permed_accounts = self.get_permed_accounts_from_perms(perms, user, asset) - return permed_accounts - @staticmethod def get_accounts_for_permission(perm, with_actions=False): """ 获取授权规则包含的账号 """ From fd54cbc7772dfae66fdf7315a6cca07c2bbef4a4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Nov 2022 19:23:44 +0800 Subject: [PATCH 345/488] fix: action choices --- apps/perms/const.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/perms/const.py b/apps/perms/const.py index 3c8f9ee21..9a7fdb3aa 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -10,11 +10,11 @@ __all__ = ["SpecialAccount", "ActionChoices"] class ActionChoices(BitChoices): - connect = bit(1), _("Connect") - upload = bit(2), _("Upload") - download = bit(3), _("Download") - copy = bit(4), _("Copy") - paste = bit(5), _("Paste") + connect = bit(0), _("Connect") + upload = bit(1), _("Upload") + download = bit(2), _("Download") + copy = bit(3), _("Copy") + paste = bit(4), _("Paste") @classmethod def is_tree(cls): @@ -23,6 +23,7 @@ class ActionChoices(BitChoices): @classmethod def branches(cls): return ( + cls.connect, (_("Transfer"), [cls.upload, cls.download]), (_("Clipboard"), [cls.copy, cls.paste]), ) From e220b8174d1255e435256c48d711aed08e719452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Tue, 15 Nov 2022 22:18:15 +0800 Subject: [PATCH 346/488] =?UTF-8?q?perf:=20=E9=85=8D=E7=BD=AE=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index b228185bc..6d5affc2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,18 +61,15 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ && rm -rf /var/lib/apt/lists/* -ARG ORACLE_LIB_MAJOR=19 -ARG ORACLE_LIB_MINOR=10 -ENV ORACLE_FILE="instantclient-basiclite-linux.${TARGETARCH:-amd64}-${ORACLE_LIB_MAJOR}.${ORACLE_LIB_MINOR}.0.0.0dbru.zip" +ARG DOWNLOAD_URL=https://download.jumpserver.org RUN mkdir -p /opt/oracle/ \ && cd /opt/oracle/ \ - && wget https://download.jumpserver.org/files/oracle/${ORACLE_FILE} \ - && unzip instantclient-basiclite-linux.${TARGETARCH-amd64}-19.10.0.0.0dbru.zip \ - && mv instantclient_${ORACLE_LIB_MAJOR}_${ORACLE_LIB_MINOR} instantclient \ - && echo "/opt/oracle/instantclient" > /etc/ld.so.conf.d/oracle-instantclient.conf \ + && wget ${DOWNLOAD_URL}/public/instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \ + && unzip instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip \ + && sh -c "echo /opt/oracle/instantclient_19_10 > /etc/ld.so.conf.d/oracle-instantclient.conf" \ && ldconfig \ - && rm -f ${ORACLE_FILE} + && rm -f instantclient-basiclite-linux.${TARGETARCH}-19.10.0.0.0.zip WORKDIR /tmp/build COPY ./requirements ./requirements From e8fb6d538001122d4eb243cd2beda0f1ee884029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Wed, 16 Nov 2022 08:58:16 +0800 Subject: [PATCH 347/488] =?UTF-8?q?perf:=20=E9=85=8D=E7=BD=AE=20apt=20?= =?UTF-8?q?=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6d5affc2f..2f65985b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,8 +45,10 @@ ARG TOOLS=" \ unzip \ wget" +ARG APT_MIRROR=http://mirrors.ustc.edu.cn + RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ - sed -i 's@http://.*.debian.org@http://mirrors.ustc.edu.cn@g' /etc/apt/sources.list \ + sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \ && rm -f /etc/apt/apt.conf.d/docker-clean \ && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && apt-get update \ From e118ed655b4d79f90883c378ab64e23079b5886e Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 16 Nov 2022 11:29:02 +0800 Subject: [PATCH 348/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E8=A7=84=E5=88=99=E8=8E=B7=E5=8F=96=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E7=9A=84=E8=B4=A6=E5=8F=B7API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_group_permission.py | 15 ------- apps/perms/api/user_permission/accounts.py | 6 +-- apps/perms/const.py | 6 +-- apps/perms/models/asset_permission.py | 6 +-- apps/perms/serializers/user_permission.py | 4 +- apps/perms/urls/user_permission.py | 9 ++-- apps/perms/utils/account.py | 51 +++++++++------------- 7 files changed, 33 insertions(+), 64 deletions(-) diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index dedd90a3c..48f94f6f7 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -19,7 +19,6 @@ __all__ = [ 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodeChildrenAsTreeApi', - 'UserGroupGrantedAssetAccountsApi', ] @@ -191,17 +190,3 @@ class UserGroupGrantedNodeChildrenAsTreeApi(SerializeToTreeNodeMixin, ListAPIVie nodes = self.get_nodes() nodes = self.serialize_nodes(nodes) return Response(data=nodes) - - -class UserGroupGrantedAssetAccountsApi(uapi.UserGrantedAssetAccountsApi): - - @lazyproperty - def user_group(self): - group_id = self.kwargs.get('pk') - return UserGroup.objects.get(id=group_id) - - def get_queryset(self): - accounts = PermAccountUtil().get_perm_accounts_for_user_group_asset( - self.user_group, self.asset, with_actions=True - ) - return accounts diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 31f73d037..96c09667f 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -10,12 +10,12 @@ from .mixin import SelfOrPKUserMixin logger = get_logger(__name__) __all__ = [ - 'UserGrantedAssetAccountsApi', + 'UserPermedAssetAccountsApi', ] -class UserGrantedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): - serializer_class = serializers.AccountsGrantedSerializer +class UserPermedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): + serializer_class = serializers.AccountsPermedSerializer @lazyproperty def asset(self): diff --git a/apps/perms/const.py b/apps/perms/const.py index 3c8f9ee21..7994e56ed 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from common.db.fields import BitChoices from common.utils.integer import bit -__all__ = ["SpecialAccount", "ActionChoices"] +__all__ = ["ActionChoices"] class ActionChoices(BitChoices): @@ -31,7 +31,3 @@ class ActionChoices(BitChoices): def has_perm(cls, action_name, total): action_value = getattr(cls, action_name) return action_value & total == action_value - - -class SpecialAccount(models.TextChoices): - ALL = "@ALL", "All" diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 47cb1e8e6..1b9f068b0 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -11,7 +11,7 @@ from common.db.models import UnionQuerySet from common.utils import date_expired_default from orgs.mixins.models import OrgManager from orgs.mixins.models import OrgModelMixin -from perms.const import ActionChoices, SpecialAccount +from perms.const import ActionChoices __all__ = ['AssetPermission', 'ActionChoices'] @@ -37,7 +37,7 @@ class AssetPermissionQuerySet(models.QuerySet): def filter_by_accounts(self, accounts): q = Q(accounts__contains=list(accounts)) | \ - Q(accounts__contains=SpecialAccount.ALL.value) + Q(accounts__contains=Account.AliasAccount.ALL.value) return self.filter(q) @@ -127,7 +127,7 @@ class AssetPermission(OrgModelMixin): """ asset_ids = self.get_all_assets(flat=True) q = Q(asset_id__in=asset_ids) - if SpecialAccount.ALL in self.accounts: + if Account.AliasAccount.ALL in self.accounts: q &= Q(username__in=self.accounts) accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') if not flat: diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 6cee0e793..dc9fb475f 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -11,7 +11,7 @@ from perms.serializers.permission import ActionChoicesField __all__ = [ 'NodeGrantedSerializer', 'AssetGrantedSerializer', - 'ActionsSerializer', 'AccountsGrantedSerializer' + 'ActionsSerializer', 'AccountsPermedSerializer' ] @@ -48,7 +48,7 @@ class ActionsSerializer(serializers.Serializer): actions = ActionChoicesField(read_only=True) -class AccountsGrantedSerializer(serializers.ModelSerializer): +class AccountsPermedSerializer(serializers.ModelSerializer): actions = ActionChoicesField(read_only=True) class Meta: diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index 7b5f66897..c7413dbbc 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -55,8 +55,9 @@ user_permission_urlpatterns = [ name='my-ungrouped-assets'), # 获取授权给用户某个资产的所有账号 - path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), - name='user-asset-accounts'), + # user params: ['my', 'self'] or user.id + path('/assets//accounts/', api.UserPermedAssetAccountsApi.as_view(), + name='user-permed-asset-accounts'), ] user_group_permission_urlpatterns = [ @@ -68,10 +69,6 @@ user_group_permission_urlpatterns = [ name='user-group-nodes-children-as-tree'), path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), - - # 获取所有和资产-用户组关联的账号列表 - path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), - name='user-group-asset-accounts'), ] user_permission_urlpatterns = [ diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index 1ce1504b0..a9c2bc279 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -9,6 +9,19 @@ __all__ = ['PermAccountUtil'] class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ + def validate_permission(self, user, asset, account_username): + """ 校验用户有某个资产下某个账号名的权限 + :param user: User + :param asset: Asset + :param account_username: 可能是 @USER @INPUT 字符串 + """ + permed_accounts = self.get_permed_accounts_for_user(user, asset) + accounts_mapper = {account.username: account for account in permed_accounts} + + account = accounts_mapper.get(account_username) + actions, date_expired = (account.actions, account.date_expired) if account else (False, None) + return actions, date_expired + def get_permed_accounts_for_user(self, user, asset): """ 获取授权给用户某个资产的账号 """ perms = self.get_permissions_for_user_asset(user, asset) @@ -17,6 +30,7 @@ class PermAccountUtil(AssetPermissionUtil): @staticmethod def get_permed_accounts_from_perms(perms, user, asset): + # alias: is a collection of account usernames and special accounts [@ALL, @INPUT, @USER] alias_action_bit_mapper = defaultdict(int) alias_expired_mapper = defaultdict(list) @@ -27,23 +41,26 @@ class PermAccountUtil(AssetPermissionUtil): asset_accounts = asset.accounts.all() username_account_mapper = {account.username: account for account in asset_accounts} + cleaned_accounts_action_bit = defaultdict(int) cleaned_accounts_expired = defaultdict(list) # @ALL 账号先处理,后面的每个最多映射一个账号 - all_action_bit = alias_action_bit_mapper.pop('@ALL', None) + all_action_bit = alias_action_bit_mapper.pop(Account.AliasAccount.ALL, None) if all_action_bit: for account in asset_accounts: cleaned_accounts_action_bit[account] |= all_action_bit - cleaned_accounts_expired[account].extend(alias_expired_mapper['@ALL']) + cleaned_accounts_expired[account].extend( + alias_expired_mapper[Account.AliasAccount.ALL] + ) for alias, action_bit in alias_action_bit_mapper.items(): - if alias == '@USER': + if alias == Account.AliasAccount.USER: if user.username in username_account_mapper: account = username_account_mapper[user.username] else: account = Account.get_user_account(user.username) - elif alias == '@INPUT': + elif alias == Account.AliasAccount.INPUT: account = Account.get_manual_account() elif alias in username_account_mapper: account = username_account_mapper[alias] @@ -60,29 +77,3 @@ class PermAccountUtil(AssetPermissionUtil): account.date_expired = max(cleaned_accounts_expired[account]) accounts.append(account) return accounts - - @staticmethod - def get_accounts_for_permission(perm, with_actions=False): - """ 获取授权规则包含的账号 """ - aid_actions_map = defaultdict(int) - # 这里不行,速度太慢, 别情有很多查询 - account_ids = perm.get_all_accounts(flat=True) - actions = perm.actions - for aid in account_ids: - aid_actions_map[str(aid)] |= actions - account_ids = list(aid_actions_map.keys()) - accounts = Account.objects.filter(id__in=account_ids) - return accounts - - def validate_permission(self, user, asset, account_username): - """ 校验用户有某个资产下某个账号名的权限 - :param account_username: 可能是 @USER @INPUT 的 - """ - permed_accounts = self.get_permed_accounts_for_user(user, asset) - accounts_mapper = {account.username: account for account in permed_accounts} - - account = accounts_mapper.get(account_username) - if not account: - return False, None - else: - return account.actions, account.date_expired From 7ac9681f0dfb8ccfd108d1921772ca54447d90f9 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 16 Nov 2022 14:27:50 +0800 Subject: [PATCH 349/488] perf: asyncio ws task log --- apps/ops/ws.py | 107 +++++++++++++++------------------- requirements/requirements.txt | 1 + 2 files changed, 49 insertions(+), 59 deletions(-) diff --git a/apps/ops/ws.py b/apps/ops/ws.py index 94d71d90d..473093ba2 100644 --- a/apps/ops/ws.py +++ b/apps/ops/ws.py @@ -1,18 +1,18 @@ -import time +import asyncio import os -import threading -import json -from channels.generic.websocket import JsonWebsocketConsumer -from common.utils import get_logger +import aiofiles +from channels.generic.websocket import AsyncJsonWebsocketConsumer + from common.db.utils import close_old_connections -from .celery.utils import get_celery_task_log_path +from common.utils import get_logger from .ansible.utils import get_ansible_task_log_path +from .celery.utils import get_celery_task_log_path logger = get_logger(__name__) -class TaskLogWebsocket(JsonWebsocketConsumer): +class TaskLogWebsocket(AsyncJsonWebsocketConsumer): disconnected = False log_types = { @@ -20,70 +20,59 @@ class TaskLogWebsocket(JsonWebsocketConsumer): 'ansible': get_ansible_task_log_path } - def connect(self): + async def connect(self): user = self.scope["user"] if user.is_authenticated: - self.accept() + await self.accept() else: - self.close() + await self.close() - def get_log_path(self, task_id): - func = self.log_types.get(self.log_type) + def get_log_path(self, task_id, log_type): + func = self.log_types.get(log_type) if func: return func(task_id) - def receive(self, text_data=None, bytes_data=None, **kwargs): - data = json.loads(text_data) - task_id = data.get('task') - self.log_type = data.get('type', 'celery') - if task_id: - self.handle_task(task_id) + async def receive_json(self, content, **kwargs): + task_id = content.get('task') + task_typ = content.get('type', 'celery') + log_path = self.get_log_path(task_id, task_typ) + await self.async_handle_task(task_id, log_path) - def wait_util_log_path_exist(self, task_id): - log_path = self.get_log_path(task_id) + async def async_handle_task(self, task_id, log_path): + logger.info("Task id: {}".format(task_id)) while not self.disconnected: if not os.path.exists(log_path): - self.send_json({'message': '.', 'task': task_id}) - time.sleep(0.5) - continue - self.send_json({'message': '\r\n'}) - try: - logger.debug('Task log path: {}'.format(log_path)) - task_log_f = open(log_path, 'rb') - return task_log_f - except OSError: - return None - - def read_log_file(self, task_id): - task_log_f = self.wait_util_log_path_exist(task_id) - if not task_log_f: - logger.debug('Task log file is None: {}'.format(task_id)) - return - - task_end_mark = [] - while not self.disconnected: - data = task_log_f.read(4096) - if data: - data = data.replace(b'\n', b'\r\n') - self.send_json( - {'message': data.decode(errors='ignore'), 'task': task_id} - ) - if data.find(b'succeeded in') != -1: - task_end_mark.append(1) - if data.find(bytes(task_id, 'utf8')) != -1: - task_end_mark.append(1) - elif len(task_end_mark) == 2: - logger.debug('Task log end: {}'.format(task_id)) + await self.send_json({'message': '.', 'task': task_id}) + await asyncio.sleep(0.5) + else: + await self.send_task_log(task_id, log_path) break - time.sleep(0.2) - task_log_f.close() - def handle_task(self, task_id): - logger.info("Task id: {}".format(task_id)) - thread = threading.Thread(target=self.read_log_file, args=(task_id,)) - thread.start() + async def send_task_log(self, task_id, log_path): + await self.send_json({'message': '\r\n'}) + try: + logger.debug('Task log path: {}'.format(log_path)) + task_end_mark = [] + async with aiofiles.open(log_path, 'rb') as task_log_f: + while not self.disconnected: + data = await task_log_f.read(4096) + if data: + data = data.replace(b'\n', b'\r\n') + await self.send_json( + {'message': data.decode(errors='ignore'), 'task': task_id} + ) + if data.find(b'succeeded in') != -1: + task_end_mark.append(1) + if data.find(bytes(task_id, 'utf8')) != -1: + task_end_mark.append(1) + elif len(task_end_mark) == 2: + logger.debug('Task log end: {}'.format(task_id)) + break + await asyncio.sleep(0.2) + except OSError as e: + logger.warn('Task log path open failed: {}'.format(e)) - def disconnect(self, close_code): + async def disconnect(self, close_code): self.disconnected = True - self.close() + await self.close() close_old_connections() diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8af2fcb0f..659add611 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,3 +1,4 @@ +aiofiles==22.1.0 amqp==5.0.9 ansible==6.4.0 ansible-runner==2.2.1 From 0959b55b53ff6bb6b01704b91492c98883365d43 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Wed, 16 Nov 2022 15:04:46 +0800 Subject: [PATCH 350/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 104 +++++++++++++++----------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 106 +++++++++++++++------------ apps/ops/models/celery.py | 30 ++++++-- apps/ops/models/job.py | 12 +-- apps/ops/serializers/celery.py | 9 ++- 7 files changed, 160 insertions(+), 109 deletions(-) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index bb9486afd..dadb2b00b 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b54b29587fa79fd51a8e1836eba016c2a64419dc0981bac65daa356f6e180f2 -size 117154 +oid sha256:ed394901fe9f21307ca1590cb77df861fef9d68f1f2d45bf21b495fb9b56e596 +size 117024 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 1ef36810d..3ec5142f7 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-15 15:52+0800\n" +"POT-Creation-Date: 2022-11-15 20:31+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -30,8 +30,8 @@ msgstr "Acls" #: assets/models/label.py:17 assets/models/platform.py:21 #: assets/models/platform.py:72 assets/serializers/asset/common.py:86 #: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 -#: ops/models/job.py:33 ops/models/playbook.py:13 orgs/models.py:70 -#: perms/models/asset_permission.py:51 rbac/models/role.py:29 +#: ops/models/celery.py:15 ops/models/job.py:33 ops/models/playbook.py:13 +#: orgs/models.py:70 perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 @@ -216,7 +216,7 @@ msgstr "" "ション: {}" #: acls/serializers/login_asset_acl.py:108 -#: tickets/serializers/ticket/ticket.py:86 +#: tickets/serializers/ticket/ticket.py:67 msgid "The organization `{}` does not exist" msgstr "組織 '{}'は存在しません" @@ -267,6 +267,7 @@ msgstr "カテゴリ" #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 tickets/serializers/flow.py:53 +#: tickets/serializers/ticket/ticket.py:18 #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" @@ -711,7 +712,7 @@ msgid "Move asset to node" msgstr "アセットをノードに移動する" #: assets/models/asset/web.py:9 audits/const.py:67 -#: terminal/serializers/applet_host.py:26 +#: terminal/serializers/applet_host.py:25 msgid "Disabled" msgstr "無効" @@ -769,17 +770,17 @@ msgstr "自動管理" #: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:57 #: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 #: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 -#: xpack/plugins/cloud/models.py:223 +#: tickets/models/ticket/general.py:281 tickets/serializers/ticket/ticket.py:19 +#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:223 msgid "Status" msgstr "ステータス" #: assets/models/automations/base.py:93 assets/models/backup.py:76 -#: audits/models.py:40 ops/models/base.py:55 ops/models/job.py:63 -#: perms/models/asset_permission.py:69 terminal/models/applet/host.py:105 -#: terminal/models/session/session.py:43 +#: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 +#: ops/models/job.py:63 perms/models/asset_permission.py:69 +#: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 -#: tickets/models/ticket/apply_asset.py:18 +#: tickets/models/ticket/apply_asset.py:19 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:199 #: xpack/plugins/gathered_user/models.py:71 @@ -788,7 +789,8 @@ msgstr "開始日" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/job.py:64 terminal/models/applet/host.py:106 +#: ops/models/celery.py:60 ops/models/job.py:64 +#: terminal/models/applet/host.py:106 msgid "Date finished" msgstr "終了日" @@ -1139,7 +1141,7 @@ msgid "Setting" msgstr "設定" #: assets/models/platform.py:42 audits/const.py:68 settings/models.py:37 -#: terminal/serializers/applet_host.py:27 +#: terminal/serializers/applet_host.py:26 msgid "Enabled" msgstr "有効化" @@ -2223,7 +2225,7 @@ msgstr "アセット名" #: authentication/models/connection_token.py:36 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:72 #: tickets/models/ticket/apply_application.py:29 -#: tickets/models/ticket/apply_asset.py:19 users/models/user.py:707 +#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:707 msgid "Date expired" msgstr "期限切れの日付" @@ -2765,11 +2767,11 @@ msgstr "%s オブジェクトは存在しません。" msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "" -#: common/drf/fields.py:131 +#: common/drf/fields.py:138 msgid "Invalid data type, should be list" msgstr "" -#: common/drf/fields.py:146 +#: common/drf/fields.py:153 #, fuzzy #| msgid "Invalid ip" msgid "Invalid choice: {}" @@ -3083,7 +3085,7 @@ msgstr "パターン" msgid "Module" msgstr "" -#: ops/models/adhoc.py:28 ops/models/celery.py:48 ops/models/job.py:35 +#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:35 #: terminal/models/component/task.py:17 msgid "Args" msgstr "アルグ" @@ -3128,20 +3130,27 @@ msgstr "結果" msgid "Summary" msgstr "" -#: ops/models/celery.py:49 terminal/models/component/task.py:18 +#: ops/models/celery.py:55 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "クワーグ" -#: ops/models/celery.py:50 tickets/models/comment.py:13 +#: ops/models/celery.py:56 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 +#: tickets/serializers/ticket/ticket.py:20 msgid "State" msgstr "状態" -#: ops/models/celery.py:51 terminal/models/session/sharing.py:111 +#: ops/models/celery.py:57 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "終了" +#: ops/models/celery.py:58 +#, fuzzy +#| msgid "Date finished" +msgid "Date published" +msgstr "終了日" + #: ops/models/job.py:21 ops/models/job.py:38 msgid "Playbook" msgstr "" @@ -3200,7 +3209,8 @@ msgstr "{max_threshold}%: => {value} を超える使用メモリ" msgid "CPU load more than {max_threshold}: => {value}" msgstr "{max_threshold} を超えるCPUロード: => {value}" -#: ops/signal_handlers.py:63 terminal/models/component/task.py:26 +#: ops/signal_handlers.py:63 terminal/models/applet/host.py:108 +#: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "タスク" @@ -3271,7 +3281,7 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:72 +#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:61 msgid "Organization" msgstr "組織" @@ -3342,7 +3352,7 @@ msgstr "クリップボードのコピー" #: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 #: perms/serializers/permission.py:29 perms/serializers/permission.py:59 #: tickets/models/ticket/apply_application.py:26 -#: tickets/models/ticket/apply_asset.py:17 +#: tickets/models/ticket/apply_asset.py:18 msgid "Actions" msgstr "アクション" @@ -3427,7 +3437,7 @@ msgstr "" msgid "If you have any question, please contact the administrator" msgstr "質問があったら、管理者に連絡して下さい" -#: perms/utils/user_permission.py:622 rbac/tree.py:57 +#: perms/utils/user_permission.py:627 rbac/tree.py:57 msgid "My assets" msgstr "私の資産" @@ -5379,27 +5389,27 @@ msgstr "電話が設定されていない" msgid "Icon" msgstr "" -#: terminal/serializers/applet_host.py:22 +#: terminal/serializers/applet_host.py:21 #, fuzzy #| msgid "Session" msgid "Per Session" msgstr "セッション" -#: terminal/serializers/applet_host.py:23 +#: terminal/serializers/applet_host.py:22 msgid "Per Device" msgstr "" -#: terminal/serializers/applet_host.py:29 +#: terminal/serializers/applet_host.py:28 #, fuzzy #| msgid "License" msgid "RDS Licensing" msgstr "ライセンス" -#: terminal/serializers/applet_host.py:30 +#: terminal/serializers/applet_host.py:29 msgid "RDS License Server" msgstr "" -#: terminal/serializers/applet_host.py:31 +#: terminal/serializers/applet_host.py:30 msgid "RDS Licensing Mode" msgstr "" @@ -5682,7 +5692,7 @@ msgid "Ticket session relation" msgstr "チケットセッションの関係" #: tickets/models/ticket/apply_application.py:11 -#: tickets/models/ticket/apply_asset.py:12 +#: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" msgstr "認可ルール名" @@ -5694,20 +5704,22 @@ msgstr "アプリケーションの適用" msgid "Apply system users" msgstr "システムユーザーの適用" -#: tickets/models/ticket/apply_asset.py:8 -#: tickets/serializers/ticket/apply_asset.py:15 +#: tickets/models/ticket/apply_asset.py:9 +#: tickets/serializers/ticket/apply_asset.py:14 msgid "Select at least one asset or node" msgstr "少なくとも1つのアセットまたはノードを選択します。" -#: tickets/models/ticket/apply_asset.py:13 +#: tickets/models/ticket/apply_asset.py:14 +#: tickets/serializers/ticket/apply_asset.py:19 msgid "Apply nodes" msgstr "ノードの適用" -#: tickets/models/ticket/apply_asset.py:15 +#: tickets/models/ticket/apply_asset.py:16 +#: tickets/serializers/ticket/apply_asset.py:18 msgid "Apply assets" msgstr "資産の適用" -#: tickets/models/ticket/apply_asset.py:16 +#: tickets/models/ticket/apply_asset.py:17 #, fuzzy #| msgid "Application account" msgid "Apply accounts" @@ -5833,6 +5845,12 @@ msgstr "現在の組織タイプは既に存在します。" msgid "Processor" msgstr "プロセッサ" +#: tickets/serializers/ticket/apply_asset.py:20 +#, fuzzy +#| msgid "Apply applications" +msgid "Apply actions" +msgstr "アプリケーションの適用" + #: tickets/serializers/ticket/common.py:15 #: tickets/serializers/ticket/common.py:77 msgid "Created by ticket ({}-{})" @@ -5846,15 +5864,7 @@ msgstr "有効期限は開始日より大きくする必要があります" msgid "Permission named `{}` already exists" msgstr "'{}'という名前の権限は既に存在します" -#: tickets/serializers/ticket/ticket.py:17 -msgid "Type display" -msgstr "タイプ表示" - -#: tickets/serializers/ticket/ticket.py:18 -msgid "Status display" -msgstr "ステータス表示" - -#: tickets/serializers/ticket/ticket.py:101 +#: tickets/serializers/ticket/ticket.py:85 msgid "The ticket flow `{}` does not exist" msgstr "チケットフロー '{}'が存在しない" @@ -7176,6 +7186,12 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#~ msgid "Type display" +#~ msgstr "タイプ表示" + +#~ msgid "Status display" +#~ msgstr "ステータス表示" + #, fuzzy #~| msgid "Run command" #~ msgid "Run ansible command" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c1becdb02..d8b94b86b 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:bf423289503715e2a574bce56bf6b1b323e0355ef18dbd1c8de37c66c0fb5b25 -size 104080 +oid sha256:37b75571d3f4b3da6cfb5edad89bbebcd041e2b59b7c2d5642adb571116a68b1 +size 103936 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 596c39c50..76b225870 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-15 15:52+0800\n" +"POT-Creation-Date: 2022-11-15 20:31+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -29,8 +29,8 @@ msgstr "访问控制" #: assets/models/label.py:17 assets/models/platform.py:21 #: assets/models/platform.py:72 assets/serializers/asset/common.py:86 #: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 -#: ops/models/job.py:33 ops/models/playbook.py:13 orgs/models.py:70 -#: perms/models/asset_permission.py:51 rbac/models/role.py:29 +#: ops/models/celery.py:15 ops/models/job.py:33 ops/models/playbook.py:13 +#: orgs/models.py:70 perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 @@ -212,7 +212,7 @@ msgid "" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" #: acls/serializers/login_asset_acl.py:108 -#: tickets/serializers/ticket/ticket.py:86 +#: tickets/serializers/ticket/ticket.py:67 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" @@ -262,6 +262,7 @@ msgstr "类别" #: tickets/models/comment.py:26 tickets/models/flow.py:57 #: tickets/models/ticket/apply_application.py:17 #: tickets/models/ticket/general.py:273 tickets/serializers/flow.py:53 +#: tickets/serializers/ticket/ticket.py:18 #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 msgid "Type" @@ -682,7 +683,7 @@ msgid "Move asset to node" msgstr "移动资产到节点" #: assets/models/asset/web.py:9 audits/const.py:67 -#: terminal/serializers/applet_host.py:26 +#: terminal/serializers/applet_host.py:25 msgid "Disabled" msgstr "禁用" @@ -732,17 +733,17 @@ msgstr "自动化任务" #: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:57 #: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 #: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:171 -#: xpack/plugins/cloud/models.py:223 +#: tickets/models/ticket/general.py:281 tickets/serializers/ticket/ticket.py:19 +#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:223 msgid "Status" msgstr "状态" #: assets/models/automations/base.py:93 assets/models/backup.py:76 -#: audits/models.py:40 ops/models/base.py:55 ops/models/job.py:63 -#: perms/models/asset_permission.py:69 terminal/models/applet/host.py:105 -#: terminal/models/session/session.py:43 +#: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 +#: ops/models/job.py:63 perms/models/asset_permission.py:69 +#: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 -#: tickets/models/ticket/apply_asset.py:18 +#: tickets/models/ticket/apply_asset.py:19 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:199 #: xpack/plugins/gathered_user/models.py:71 @@ -751,7 +752,8 @@ msgstr "开始日期" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/job.py:64 terminal/models/applet/host.py:106 +#: ops/models/celery.py:60 ops/models/job.py:64 +#: terminal/models/applet/host.py:106 msgid "Date finished" msgstr "结束日期" @@ -1073,7 +1075,7 @@ msgid "Setting" msgstr "设置" #: assets/models/platform.py:42 audits/const.py:68 settings/models.py:37 -#: terminal/serializers/applet_host.py:27 +#: terminal/serializers/applet_host.py:26 msgid "Enabled" msgstr "启用" @@ -1431,7 +1433,9 @@ msgid "Manually update the hardware information of assets under a node" msgstr "手动更新节点下的资产硬件信息" #: assets/tasks/gather_facts.py:59 -msgid "Update node asset hardware information" +#, fuzzy +#| msgid "Update node asset hardware information" +msgid "Update node asset hardware information: " msgstr "更新节点资产硬件信息" #: assets/tasks/nodes_amount.py:16 @@ -2077,7 +2081,7 @@ msgstr "资产名称" #: authentication/models/connection_token.py:36 #: authentication/models/temp_token.py:13 perms/models/asset_permission.py:72 #: tickets/models/ticket/apply_application.py:29 -#: tickets/models/ticket/apply_asset.py:19 users/models/user.py:707 +#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:707 msgid "Date expired" msgstr "失效日期" @@ -2607,11 +2611,11 @@ msgstr "{pk_value} 对象不存在" msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "不正确的类型。期望 pk 值,收到 {data_type} 类型。" -#: common/drf/fields.py:131 +#: common/drf/fields.py:138 msgid "Invalid data type, should be list" msgstr "" -#: common/drf/fields.py:146 +#: common/drf/fields.py:153 #, fuzzy #| msgid "Invalid ip" msgid "Invalid choice: {}" @@ -2908,7 +2912,7 @@ msgstr "模式" msgid "Module" msgstr "" -#: ops/models/adhoc.py:28 ops/models/celery.py:48 ops/models/job.py:35 +#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:35 #: terminal/models/component/task.py:17 msgid "Args" msgstr "参数" @@ -2947,20 +2951,25 @@ msgstr "结果" msgid "Summary" msgstr "汇总" -#: ops/models/celery.py:49 terminal/models/component/task.py:18 +#: ops/models/celery.py:55 terminal/models/component/task.py:18 msgid "Kwargs" msgstr "其它参数" -#: ops/models/celery.py:50 tickets/models/comment.py:13 +#: ops/models/celery.py:56 tickets/models/comment.py:13 #: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 +#: tickets/serializers/ticket/ticket.py:20 msgid "State" msgstr "状态" -#: ops/models/celery.py:51 terminal/models/session/sharing.py:111 +#: ops/models/celery.py:57 terminal/models/session/sharing.py:111 #: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 msgid "Finished" msgstr "结束" +#: ops/models/celery.py:58 +msgid "Date published" +msgstr "发布日期" + #: ops/models/job.py:21 ops/models/job.py:38 msgid "Playbook" msgstr "Playbook" @@ -3023,7 +3032,8 @@ msgstr "内存使用率超过 {max_threshold}%: => {value}" msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" -#: ops/signal_handlers.py:63 terminal/models/component/task.py:26 +#: ops/signal_handlers.py:63 terminal/models/applet/host.py:108 +#: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "任务" @@ -3085,7 +3095,7 @@ msgstr "组织管理" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:72 +#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:61 msgid "Organization" msgstr "组织" @@ -3154,7 +3164,7 @@ msgstr "剪贴板复制" #: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 #: perms/serializers/permission.py:29 perms/serializers/permission.py:59 #: tickets/models/ticket/apply_application.py:26 -#: tickets/models/ticket/apply_asset.py:17 +#: tickets/models/ticket/apply_asset.py:18 msgid "Actions" msgstr "动作" @@ -3239,7 +3249,7 @@ msgstr "" msgid "If you have any question, please contact the administrator" msgstr "如果有疑问或需求,请联系系统管理员" -#: perms/utils/user_permission.py:622 rbac/tree.py:57 +#: perms/utils/user_permission.py:627 rbac/tree.py:57 msgid "My assets" msgstr "我的资产" @@ -5142,23 +5152,23 @@ msgstr "不匹配" msgid "Icon" msgstr "图标" -#: terminal/serializers/applet_host.py:22 +#: terminal/serializers/applet_host.py:21 msgid "Per Session" msgstr "按会话" -#: terminal/serializers/applet_host.py:23 +#: terminal/serializers/applet_host.py:22 msgid "Per Device" msgstr "按设备" -#: terminal/serializers/applet_host.py:29 +#: terminal/serializers/applet_host.py:28 msgid "RDS Licensing" msgstr "部署 RDS 许可服务" -#: terminal/serializers/applet_host.py:30 +#: terminal/serializers/applet_host.py:29 msgid "RDS License Server" msgstr "RDS 许可服务主机" -#: terminal/serializers/applet_host.py:31 +#: terminal/serializers/applet_host.py:30 msgid "RDS Licensing Mode" msgstr "RDS 许可模式" @@ -5438,7 +5448,7 @@ msgid "Ticket session relation" msgstr "工单会话" #: tickets/models/ticket/apply_application.py:11 -#: tickets/models/ticket/apply_asset.py:12 +#: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" msgstr "授权规则名称" @@ -5450,20 +5460,22 @@ msgstr "申请应用" msgid "Apply system users" msgstr "申请的系统用户" -#: tickets/models/ticket/apply_asset.py:8 -#: tickets/serializers/ticket/apply_asset.py:15 +#: tickets/models/ticket/apply_asset.py:9 +#: tickets/serializers/ticket/apply_asset.py:14 msgid "Select at least one asset or node" msgstr "资产或者节点至少选择一项" -#: tickets/models/ticket/apply_asset.py:13 +#: tickets/models/ticket/apply_asset.py:14 +#: tickets/serializers/ticket/apply_asset.py:19 msgid "Apply nodes" msgstr "申请节点" -#: tickets/models/ticket/apply_asset.py:15 +#: tickets/models/ticket/apply_asset.py:16 +#: tickets/serializers/ticket/apply_asset.py:18 msgid "Apply assets" msgstr "申请资产" -#: tickets/models/ticket/apply_asset.py:16 +#: tickets/models/ticket/apply_asset.py:17 msgid "Apply accounts" msgstr "申请账号" @@ -5583,6 +5595,12 @@ msgstr "当前组织已存在该类型" msgid "Processor" msgstr "处理人" +#: tickets/serializers/ticket/apply_asset.py:20 +#, fuzzy +#| msgid "Apply applications" +msgid "Apply actions" +msgstr "申请应用" + #: tickets/serializers/ticket/common.py:15 #: tickets/serializers/ticket/common.py:77 msgid "Created by ticket ({}-{})" @@ -5596,15 +5614,7 @@ msgstr "过期时间要大于开始时间" msgid "Permission named `{}` already exists" msgstr "授权名称 `{}` 已存在" -#: tickets/serializers/ticket/ticket.py:17 -msgid "Type display" -msgstr "类型名称" - -#: tickets/serializers/ticket/ticket.py:18 -msgid "Status display" -msgstr "状态名称" - -#: tickets/serializers/ticket/ticket.py:101 +#: tickets/serializers/ticket/ticket.py:85 msgid "The ticket flow `{}` does not exist" msgstr "工单流程 `{}` 不存在" @@ -6904,6 +6914,12 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Type display" +#~ msgstr "类型名称" + +#~ msgid "Status display" +#~ msgstr "状态名称" + #~ msgid "Run ansible command" #~ msgstr "运行 ansible 命令" diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 1b444ded1..6e53f868d 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -12,18 +12,24 @@ from ops.celery import app class CeleryTask(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) - name = models.CharField(max_length=1024) + name = models.CharField(max_length=1024, verbose_name=_('Name')) last_published_time = models.DateTimeField(null=True) @property def meta(self): task = app.tasks.get(self.name, None) return { - "display_name": getattr(task, 'verbose_name', None), - "comment": getattr(task, 'comment', None), + "comment": getattr(task, 'verbose_name', None), "queue": getattr(task, 'queue', 'default') } + @property + def summary(self): + executions = CeleryTaskExecution.objects.filter(name=self.name) + total = executions.count() + success = executions.filter(state='SUCCESS').count() + return {'total': total, 'success': success} + @property def state(self): last_five_executions = CeleryTaskExecution.objects.filter(name=self.name).order_by('-date_published')[:5] @@ -49,9 +55,21 @@ class CeleryTaskExecution(models.Model): kwargs = models.JSONField(verbose_name=_("Kwargs")) state = models.CharField(max_length=16, verbose_name=_("State")) is_finished = models.BooleanField(default=False, verbose_name=_("Finished")) - date_published = models.DateTimeField(auto_now_add=True) - date_start = models.DateTimeField(null=True) - date_finished = models.DateTimeField(null=True) + date_published = models.DateTimeField(auto_now_add=True, verbose_name=_('Date published')) + date_start = models.DateTimeField(null=True, verbose_name=_('Date start')) + date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished')) + + @property + def time_cost(self): + if self.date_finished and self.date_start: + return (self.date_finished - self.date_start).total_seconds() + return None + + @property + def timedelta(self): + if self.date_start and self.date_finished: + return self.date_finished - self.date_start + return None def __str__(self): return "{}: {}".format(self.name, self.id) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 9199ab902..650c701dd 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -84,6 +84,12 @@ class JobExecution(BaseCreateUpdateModel): def short_id(self): return str(self.id).split('-')[-1] + @property + def time_cost(self): + if self.date_finished and self.date_start: + return (self.date_finished - self.date_start).total_seconds() + return None + @property def timedelta(self): if self.date_start and self.date_finished: @@ -98,12 +104,6 @@ class JobExecution(BaseCreateUpdateModel): def is_success(self): return self.status == 'success' - @property - def time_cost(self): - if self.date_finished and self.date_start: - return (self.date_finished - self.date_start).total_seconds() - return None - @property def inventory_path(self): return os.path.join(self.private_dir, 'inventory', 'hosts') diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py index 351d63213..8d75d227f 100644 --- a/apps/ops/serializers/celery.py +++ b/apps/ops/serializers/celery.py @@ -30,14 +30,15 @@ class CeleryPeriodTaskSerializer(serializers.ModelSerializer): class CeleryTaskSerializer(serializers.ModelSerializer): class Meta: model = CeleryTask - fields = [ - 'id', 'name', 'meta', 'state', 'last_published_time', - ] + read_only_fields = ['id', 'name', 'meta', 'summary', 'state', 'last_published_time'] + fields = read_only_fields class CeleryTaskExecutionSerializer(serializers.ModelSerializer): class Meta: model = CeleryTaskExecution fields = [ - "id", "name", "args", "kwargs", "state", "is_finished", "date_published", "date_start", "date_finished" + "id", "name", "args", "kwargs", "time_cost", "timedelta", "state", "is_finished", "date_published", + "date_start", + "date_finished" ] From 896b59b1bd281f202da066f074c74489b68ed4ad Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 16 Nov 2022 15:07:35 +0800 Subject: [PATCH 351/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=94=A8=E6=88=B7=E6=94=B6=E8=97=8F=E7=9A=84=E8=B5=84?= =?UTF-8?q?=E4=BA=A7API=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset/common.py | 2 +- apps/perms/serializers/user_permission.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index a6c4b4fe2..179760922 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -15,7 +15,7 @@ from ...const import Category, AllTypes __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', - 'AssetTaskSerializer', 'AssetsTaskSerializer', + 'AssetTaskSerializer', 'AssetsTaskSerializer', 'AssetProtocolsSerializer', ] diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index dc9fb475f..d95a14f99 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from assets.const import Category, AllTypes +from assets.serializers.asset.common import AssetProtocolsSerializer from assets.models import Node, Asset, Platform, Account from common.drf.fields import ObjectRelatedField, LabeledChoiceField from perms.serializers.permission import ActionChoicesField @@ -17,17 +18,15 @@ __all__ = [ class AssetGrantedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 """ - platform = serializers.SlugRelatedField( - slug_field='name', queryset=Platform.objects.all(), label=_("Platform") - ) - protocols = ObjectRelatedField(read_only=True, many=True) + platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) + protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category')) type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type')) class Meta: model = Asset only_fields = [ - "id", "name", "address", "protocols", + "id", "name", "address", 'domain', 'platform', "comment", "org_id", "is_active", ] From 21d24ae4bc35bdbee4dfe8c6a24597df66997886 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Wed, 16 Nov 2022 16:23:39 +0800 Subject: [PATCH 352/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=8E=88=E6=9D=83=E8=A7=84=E5=88=99=E6=97=B6=E4=B8=8D?= =?UTF-8?q?=E5=8C=85=E5=90=ABactions=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/fields.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index c666d9b76..9742f639c 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -4,7 +4,7 @@ import six from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from rest_framework.fields import ChoiceField +from rest_framework.fields import ChoiceField, empty from common.db.fields import BitChoices from common.utils import decrypt_password @@ -153,3 +153,15 @@ class BitChoicesField(TreeChoicesMixin, serializers.MultipleChoiceField): raise serializers.ValidationError(_("Invalid choice: {}").format(name)) value |= name_value_map[name] return value + + def run_validation(self, data=empty): + """ + 备注: + 创建授权规则不包含 actions 字段时, 会使用默认值(AssetPermission 中设置), + 会直接使用 ['connect', '...'] 等字段保存到数据库,导致类型错误 + 这里将获取到的值再执行一下 to_internal_value 方法, 转化为内部值 + """ + data = super().run_validation(data) + value = self.to_internal_value(data) + self.run_validators(value) + return value From ec462e09e0846f8a9b1fb01114c14f728978de15 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 16 Nov 2022 19:35:16 +0800 Subject: [PATCH 353/488] fix: action --- apps/common/drf/fields.py | 2 ++ apps/common/utils/integer.py | 7 +++---- apps/perms/const.py | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index 9742f639c..270c8d91a 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -162,6 +162,8 @@ class BitChoicesField(TreeChoicesMixin, serializers.MultipleChoiceField): 这里将获取到的值再执行一下 to_internal_value 方法, 转化为内部值 """ data = super().run_validation(data) + if isinstance(data, int): + return data value = self.to_internal_value(data) self.run_validators(value) return value diff --git a/apps/common/utils/integer.py b/apps/common/utils/integer.py index c87e939f0..9e657a6dd 100644 --- a/apps/common/utils/integer.py +++ b/apps/common/utils/integer.py @@ -1,5 +1,4 @@ def bit(x): - if x == 0: - return 0 - else: - return 2 ** (x - 1) + if x < 1: + raise ValueError("x must be greater than 1") + return 2 ** (x - 1) diff --git a/apps/perms/const.py b/apps/perms/const.py index 99495369c..cecaa5e3a 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -10,11 +10,11 @@ __all__ = ["ActionChoices"] class ActionChoices(BitChoices): - connect = bit(0), _("Connect") - upload = bit(1), _("Upload") - download = bit(2), _("Download") - copy = bit(3), _("Copy") - paste = bit(4), _("Paste") + connect = bit(1), _("Connect") + upload = bit(2), _("Upload") + download = bit(3), _("Download") + copy = bit(4), _("Copy") + paste = bit(5), _("Paste") @classmethod def is_tree(cls): From fb653f93db92bccf1d860a7b65995dffc4afe088 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 16 Nov 2022 21:05:15 +0800 Subject: [PATCH 354/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20connect=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/component/endpoint.py | 36 ++++++++++------ apps/terminal/const.py | 55 ++++++++++++++++++++++++- apps/terminal/serializers/terminal.py | 8 +++- 3 files changed, 83 insertions(+), 16 deletions(-) diff --git a/apps/terminal/api/component/endpoint.py b/apps/terminal/api/component/endpoint.py index 364cd803e..c81cbc1c3 100644 --- a/apps/terminal/api/component/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -1,17 +1,18 @@ -from rest_framework.decorators import action -from rest_framework.response import Response -from rest_framework import status -from rest_framework.request import Request - -from common.drf.api import JMSBulkModelViewSet -from common.permissions import IsValidUserOrConnectionToken -from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 -from assets.models import Asset -from orgs.utils import tmp_to_root_org -from terminal.models import Session, Endpoint, EndpointRule -from terminal import serializers +from django.utils.translation import ugettext_lazy as _ +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.generics import ListAPIView +from rest_framework.request import Request +from rest_framework.response import Response +from assets.models import Asset +from common.drf.api import JMSBulkModelViewSet +from common.permissions import IsValidUser +from common.permissions import IsValidUserOrConnectionToken +from orgs.utils import tmp_to_root_org +from terminal import serializers +from terminal.models import Session, Endpoint, EndpointRule __all__ = ['EndpointViewSet', 'EndpointRuleViewSet'] @@ -97,3 +98,14 @@ class EndpointRuleViewSet(JMSBulkModelViewSet): search_fields = filterset_fields serializer_class = serializers.EndpointRuleSerializer queryset = EndpointRule.objects.all() + + +class ConnectMethodListApi(ListAPIView): + permission_classes = (IsValidUser,) + serializer_class = serializers.ProtocolConnectMethodsSerializer + + def get_queryset(self): + protocol = self.request.query_params.get('protocol') + if not protocol: + return [] + return Protocol.objects.filter(name=protocol) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index acea15238..d44303f9f 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -4,6 +4,9 @@ from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ +from assets.const import Protocol + + # Replay & Command Storage Choices # -------------------------------- @@ -48,11 +51,59 @@ class TerminalType(TextChoices): lion = 'lion', 'Lion' core = 'core', 'Core' celery = 'celery', 'Celery' - magnus = 'magnus', 'Magnus' - razor = 'razor', 'Razor' + magnus = 'magnus', 'Magnus' + razor = 'razor', 'Razor' tinker = 'tinker', 'Tinker' @classmethod def types(cls): return set(dict(cls.choices).keys()) + +class NativeClient: + # Koko + ssh = 'ssh', 'ssh' + putty = 'putty', 'PuTTY' + xshell = 'xshell', 'Xshell' + + # Magnus + mysql = 'mysql', 'MySQL Client' + psql = 'psql', 'psql' + sqlplus = 'sqlplus', 'sqlplus' + redis = 'redis-cli', 'redis-cli' + + # Razor + mstsc = 'mstsc', 'Remote Desktop' + + @classmethod + def commands(cls, name, os): + return { + 'ssh': 'ssh {username}@{hostname} -p {port}', + 'putty': 'putty -ssh {username}@{hostname} -P {port}', + 'xshell': '-url ssh://root:passwd@192.168.10.100', + 'mysql': 'mysql -h {hostname} -P {port} -u {username} -p', + 'psql': { + 'default': 'psql -h {hostname} -p {port} -U {username} -W', + 'windows': 'psql /h {hostname} /p {port} /U {username} -W', + }, + 'sqlplus': 'sqlplus {username}/{password}@{hostname}:{port}', + 'redis': 'redis-cli -h {hostname} -p {port} -a {password}', + 'mstsc': 'mstsc /v:{hostname}:{port}', + } + + +class ConnectMethod(TextChoices): + web_cli = 'web_cli', _('Web CLI') + web_gui = 'web_gui', _('Web GUI') + native_client = 'native_client', _('Native Client') + + @classmethod + def methods(cls): + return { + Protocol.ssh: [cls.web_cli, cls.native_client], + Protocol.rdp: ([cls.web_gui], [cls.native_client]), + Protocol.vnc: [cls.web_gui], + Protocol.telnet: [cls.web_cli, cls.native_client], + Protocol.mysql: [cls.web_cli, cls.web_gui, cls.native_client], + Protocol.sqlserver: [cls.web_cli, cls.web_gui], + } diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index 4b2e3614e..f12990618 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -1,8 +1,8 @@ -from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers -from common.drf.serializers import BulkModelSerializer from common.drf.fields import LabeledChoiceField +from common.drf.serializers import BulkModelSerializer from common.utils import get_request_ip, pretty_string, is_uuid from users.serializers import ServiceAccountSerializer from .. import const @@ -133,3 +133,7 @@ class TerminalRegistrationSerializer(serializers.ModelSerializer): instance.replay_storage = ReplayStorage.default().name instance.save() return instance + + +class ConnectMethodSerializer(serializers.Serializer): + name = serializers.CharField(max_length=128) From 43fee40c46848eefbd3def2bc0f9a2c080e58c50 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Wed, 16 Nov 2022 21:06:14 +0800 Subject: [PATCH 355/488] =?UTF-8?q?feat:=20=E4=BD=9C=E4=B8=9A=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A2=9E=E5=8A=A0=E5=8F=82=E6=95=B0=E5=92=8C=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E8=B7=AF=E5=BE=84=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/ja/LC_MESSAGES/django.mo | 2 +- apps/locale/ja/LC_MESSAGES/django.po | 75 +++++++++++-------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 75 +++++++++++-------- apps/ops/ansible/callback.py | 1 + apps/ops/ansible/runner.py | 4 +- .../ops/migrations/0030_auto_20221116_1811.py | 42 +++++++++++ .../ops/migrations/0031_auto_20221116_2024.py | 28 +++++++ apps/ops/models/common.py | 4 + apps/ops/models/job.py | 10 ++- apps/ops/serializers/job.py | 4 + 11 files changed, 184 insertions(+), 65 deletions(-) create mode 100644 apps/ops/migrations/0030_auto_20221116_1811.py create mode 100644 apps/ops/migrations/0031_auto_20221116_2024.py create mode 100644 apps/ops/models/common.py diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index dadb2b00b..3e19617aa 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed394901fe9f21307ca1590cb77df861fef9d68f1f2d45bf21b495fb9b56e596 +oid sha256:adfa9c01178d5f6490e616f62d41c71974d42f9e3bd078fcf1b3c7124384df0b size 117024 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 3ec5142f7..dddb5b792 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-15 20:31+0800\n" +"POT-Creation-Date: 2022-11-16 20:11+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -30,7 +30,7 @@ msgstr "Acls" #: assets/models/label.py:17 assets/models/platform.py:21 #: assets/models/platform.py:72 assets/serializers/asset/common.py:86 #: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 -#: ops/models/celery.py:15 ops/models/job.py:33 ops/models/playbook.py:13 +#: ops/models/celery.py:15 ops/models/job.py:34 ops/models/playbook.py:13 #: orgs/models.py:70 perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 @@ -98,9 +98,10 @@ msgstr "ログイン確認" #: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 #: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 #: audits/models.py:79 authentication/models/connection_token.py:22 -#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:53 -#: perms/models/perm_token.py:12 rbac/builtin.py:120 -#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20 +#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:80 +#: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 +#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 +#: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 #: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -250,7 +251,7 @@ msgstr "アプリケーション" #: applications/models.py:12 assets/models/label.py:20 #: assets/models/platform.py:73 assets/serializers/asset/common.py:62 #: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 -#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:24 +#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:23 #: tickets/models/ticket/apply_application.py:14 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" @@ -260,8 +261,8 @@ msgstr "カテゴリ" #: assets/models/automations/base.py:20 assets/models/cmd_filter.py:74 #: assets/models/platform.py:74 assets/serializers/asset/common.py:63 #: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 -#: audits/serializers.py:40 ops/models/job.py:39 -#: perms/serializers/user_permission.py:25 terminal/models/applet/applet.py:24 +#: audits/serializers.py:40 ops/models/job.py:42 +#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 @@ -517,7 +518,7 @@ msgstr "SSHパブリックキー" #: assets/models/_user.py:41 assets/models/automations/base.py:92 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:54 ops/models/job.py:62 orgs/models.py:73 +#: ops/models/base.py:54 ops/models/job.py:69 orgs/models.py:73 #: perms/models/asset_permission.py:75 users/models/group.py:18 #: users/models/user.py:927 msgid "Date created" @@ -556,7 +557,7 @@ msgstr "オートプッシュ" msgid "Sudo" msgstr "すど" -#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:29 +#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:30 msgid "Shell" msgstr "シェル" @@ -754,7 +755,7 @@ msgstr "アカウント" #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 -#: ops/models/base.py:17 ops/models/job.py:41 +#: ops/models/base.py:17 ops/models/job.py:44 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" @@ -767,7 +768,7 @@ msgid "Automation task" msgstr "自動管理" #: assets/models/automations/base.py:91 audits/models.py:115 -#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:57 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:64 #: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 #: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 #: tickets/models/ticket/general.py:281 tickets/serializers/ticket/ticket.py:19 @@ -777,7 +778,7 @@ msgstr "ステータス" #: assets/models/automations/base.py:93 assets/models/backup.py:76 #: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 -#: ops/models/job.py:63 perms/models/asset_permission.py:69 +#: ops/models/job.py:70 perms/models/asset_permission.py:69 #: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:19 @@ -789,7 +790,7 @@ msgstr "開始日" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/celery.py:60 ops/models/job.py:64 +#: ops/models/celery.py:60 ops/models/job.py:71 #: terminal/models/applet/host.py:106 msgid "Date finished" msgstr "終了日" @@ -1323,7 +1324,7 @@ msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" #: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 -#: xpack/plugins/cloud/models.py:109 +#: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "プロトコル" @@ -3071,7 +3072,7 @@ msgstr "{} から {} までの範囲" msgid "Require periodic or regularly perform setting" msgstr "定期的または定期的に設定を行う必要があります" -#: ops/models/adhoc.py:21 ops/models/job.py:30 +#: ops/models/adhoc.py:21 ops/models/job.py:31 #, fuzzy #| msgid "PowerShell" msgid "Powershell" @@ -3081,22 +3082,22 @@ msgstr "PowerShell" msgid "Pattern" msgstr "パターン" -#: ops/models/adhoc.py:27 ops/models/job.py:37 +#: ops/models/adhoc.py:27 ops/models/job.py:38 msgid "Module" msgstr "" -#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:35 +#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:36 #: terminal/models/component/task.py:17 msgid "Args" msgstr "アルグ" #: ops/models/adhoc.py:29 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:40 ops/models/job.py:61 +#: ops/models/job.py:43 ops/models/job.py:68 #: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "作成者" -#: ops/models/adhoc.py:50 ops/models/job.py:20 +#: ops/models/adhoc.py:50 ops/models/job.py:21 msgid "Adhoc" msgstr "" @@ -3122,11 +3123,11 @@ msgstr "コマンド実行" msgid "Date last run" msgstr "最終同期日" -#: ops/models/base.py:51 ops/models/job.py:59 xpack/plugins/cloud/models.py:169 +#: ops/models/base.py:51 ops/models/job.py:66 xpack/plugins/cloud/models.py:169 msgid "Result" msgstr "結果" -#: ops/models/base.py:52 ops/models/job.py:60 +#: ops/models/base.py:52 ops/models/job.py:67 msgid "Summary" msgstr "" @@ -3151,32 +3152,46 @@ msgstr "終了" msgid "Date published" msgstr "終了日" -#: ops/models/job.py:21 ops/models/job.py:38 +#: ops/models/job.py:22 ops/models/job.py:41 msgid "Playbook" msgstr "" -#: ops/models/job.py:24 +#: ops/models/job.py:25 msgid "Privileged Only" msgstr "" -#: ops/models/job.py:25 +#: ops/models/job.py:26 msgid "Privileged First" msgstr "" -#: ops/models/job.py:26 +#: ops/models/job.py:27 msgid "Skip" msgstr "" -#: ops/models/job.py:42 +#: ops/models/job.py:39 +msgid "Chdir" +msgstr "" + +#: ops/models/job.py:40 +msgid "Timeout (Seconds)" +msgstr "" + +#: ops/models/job.py:45 msgid "Runas" msgstr "" -#: ops/models/job.py:44 +#: ops/models/job.py:47 #, fuzzy #| msgid "Account key" msgid "Runas policy" msgstr "アカウントキー" +#: ops/models/job.py:48 +#, fuzzy +#| msgid "Disable" +msgid "Variables" +msgstr "無効化" + #: ops/models/playbook.py:15 msgid "Owner" msgstr "" @@ -3339,11 +3354,11 @@ msgstr "リンクのコピー" msgid "Paste" msgstr "" -#: perms/const.py:26 +#: perms/const.py:27 msgid "Transfer" msgstr "" -#: perms/const.py:27 +#: perms/const.py:28 #, fuzzy #| msgid "Clipboard copy" msgid "Clipboard" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index d8b94b86b..4451b285c 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:37b75571d3f4b3da6cfb5edad89bbebcd041e2b59b7c2d5642adb571116a68b1 -size 103936 +oid sha256:eeaa813f4ea052a1cd85b8ae5addfde6b088fd21a0261f8724d62823835512a2 +size 104043 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 76b225870..686c70a31 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-15 20:31+0800\n" +"POT-Creation-Date: 2022-11-16 20:11+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -29,7 +29,7 @@ msgstr "访问控制" #: assets/models/label.py:17 assets/models/platform.py:21 #: assets/models/platform.py:72 assets/serializers/asset/common.py:86 #: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 -#: ops/models/celery.py:15 ops/models/job.py:33 ops/models/playbook.py:13 +#: ops/models/celery.py:15 ops/models/job.py:34 ops/models/playbook.py:13 #: orgs/models.py:70 perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 @@ -97,9 +97,10 @@ msgstr "登录复核" #: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 #: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 #: audits/models.py:79 authentication/models/connection_token.py:22 -#: authentication/models/sso_token.py:15 perms/models/asset_permission.py:53 -#: perms/models/perm_token.py:12 rbac/builtin.py:120 -#: rbac/models/rolebinding.py:41 terminal/backends/command/models.py:20 +#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:80 +#: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 +#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 +#: terminal/backends/command/models.py:20 #: terminal/backends/command/serializers.py:13 #: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 #: terminal/notifications.py:91 terminal/notifications.py:139 @@ -245,7 +246,7 @@ msgstr "应用管理" #: applications/models.py:12 assets/models/label.py:20 #: assets/models/platform.py:73 assets/serializers/asset/common.py:62 #: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 -#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:24 +#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:23 #: tickets/models/ticket/apply_application.py:14 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" @@ -255,8 +256,8 @@ msgstr "类别" #: assets/models/automations/base.py:20 assets/models/cmd_filter.py:74 #: assets/models/platform.py:74 assets/serializers/asset/common.py:63 #: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 -#: audits/serializers.py:40 ops/models/job.py:39 -#: perms/serializers/user_permission.py:25 terminal/models/applet/applet.py:24 +#: audits/serializers.py:40 ops/models/job.py:42 +#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 @@ -492,7 +493,7 @@ msgstr "SSH 公钥" #: assets/models/_user.py:41 assets/models/automations/base.py:92 #: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:54 ops/models/job.py:62 orgs/models.py:73 +#: ops/models/base.py:54 ops/models/job.py:69 orgs/models.py:73 #: perms/models/asset_permission.py:75 users/models/group.py:18 #: users/models/user.py:927 msgid "Date created" @@ -531,7 +532,7 @@ msgstr "自动推送" msgid "Sudo" msgstr "Sudo" -#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:29 +#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:30 msgid "Shell" msgstr "Shell" @@ -719,7 +720,7 @@ msgstr "账号管理" #: assets/models/automations/base.py:19 #: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 -#: ops/models/base.py:17 ops/models/job.py:41 +#: ops/models/base.py:17 ops/models/job.py:44 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" @@ -730,7 +731,7 @@ msgid "Automation task" msgstr "自动化任务" #: assets/models/automations/base.py:91 audits/models.py:115 -#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:57 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:64 #: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 #: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 #: tickets/models/ticket/general.py:281 tickets/serializers/ticket/ticket.py:19 @@ -740,7 +741,7 @@ msgstr "状态" #: assets/models/automations/base.py:93 assets/models/backup.py:76 #: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 -#: ops/models/job.py:63 perms/models/asset_permission.py:69 +#: ops/models/job.py:70 perms/models/asset_permission.py:69 #: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 #: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:19 @@ -752,7 +753,7 @@ msgstr "开始日期" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/celery.py:60 ops/models/job.py:64 +#: ops/models/celery.py:60 ops/models/job.py:71 #: terminal/models/applet/host.py:106 msgid "Date finished" msgstr "结束日期" @@ -1226,7 +1227,7 @@ msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" #: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 -#: xpack/plugins/cloud/models.py:109 +#: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "协议组" @@ -2898,7 +2899,7 @@ msgstr "输入在 {} - {} 范围之间" msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" -#: ops/models/adhoc.py:21 ops/models/job.py:30 +#: ops/models/adhoc.py:21 ops/models/job.py:31 #, fuzzy #| msgid "PowerShell" msgid "Powershell" @@ -2908,22 +2909,22 @@ msgstr "PowerShell" msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:27 ops/models/job.py:37 +#: ops/models/adhoc.py:27 ops/models/job.py:38 msgid "Module" msgstr "" -#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:35 +#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:36 #: terminal/models/component/task.py:17 msgid "Args" msgstr "参数" #: ops/models/adhoc.py:29 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:40 ops/models/job.py:61 +#: ops/models/job.py:43 ops/models/job.py:68 #: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "创建者" -#: ops/models/adhoc.py:50 ops/models/job.py:20 +#: ops/models/adhoc.py:50 ops/models/job.py:21 msgid "Adhoc" msgstr "" @@ -2943,11 +2944,11 @@ msgstr "最后执行" msgid "Date last run" msgstr "最后执行日期" -#: ops/models/base.py:51 ops/models/job.py:59 xpack/plugins/cloud/models.py:169 +#: ops/models/base.py:51 ops/models/job.py:66 xpack/plugins/cloud/models.py:169 msgid "Result" msgstr "结果" -#: ops/models/base.py:52 ops/models/job.py:60 +#: ops/models/base.py:52 ops/models/job.py:67 msgid "Summary" msgstr "汇总" @@ -2970,36 +2971,50 @@ msgstr "结束" msgid "Date published" msgstr "发布日期" -#: ops/models/job.py:21 ops/models/job.py:38 +#: ops/models/job.py:22 ops/models/job.py:41 msgid "Playbook" msgstr "Playbook" -#: ops/models/job.py:24 +#: ops/models/job.py:25 #, fuzzy #| msgid "Privileged" msgid "Privileged Only" msgstr "特权账号" -#: ops/models/job.py:25 +#: ops/models/job.py:26 #, fuzzy #| msgid "Privileged" msgid "Privileged First" msgstr "特权账号" -#: ops/models/job.py:26 +#: ops/models/job.py:27 msgid "Skip" msgstr "" -#: ops/models/job.py:42 +#: ops/models/job.py:39 +msgid "Chdir" +msgstr "执行路径" + +#: ops/models/job.py:40 +msgid "Timeout (Seconds)" +msgstr "超时时间(秒)" + +#: ops/models/job.py:45 msgid "Runas" msgstr "" -#: ops/models/job.py:44 +#: ops/models/job.py:47 #, fuzzy #| msgid "Account policy" msgid "Runas policy" msgstr "账号策略" +#: ops/models/job.py:48 +#, fuzzy +#| msgid "Disable" +msgid "Variables" +msgstr "禁用" + #: ops/models/playbook.py:15 msgid "Owner" msgstr "Owner" @@ -3151,11 +3166,11 @@ msgstr "复制链接" msgid "Paste" msgstr "" -#: perms/const.py:26 +#: perms/const.py:27 msgid "Transfer" msgstr "" -#: perms/const.py:27 +#: perms/const.py:28 #, fuzzy #| msgid "Clipboard copy" msgid "Clipboard" diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 3f794a194..7d2b1f39d 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -5,6 +5,7 @@ class DefaultCallback: STATUS_MAPPER = { 'successful': 'success', 'failure': 'failed', + 'failed': 'failed', 'running': 'running', 'pending': 'pending', 'unknown': 'unknown' diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index fbf3245ae..13d56bd00 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -13,7 +13,7 @@ class AdHocRunner: "reboot", 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top' ] - def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/'): + def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars={}): self.id = uuid.uuid4() self.inventory = inventory self.pattern = pattern @@ -22,6 +22,7 @@ class AdHocRunner: self.project_dir = project_dir self.cb = DefaultCallback() self.runner = None + self.extra_vars = extra_vars def check_module(self): if self.module not in self.cmd_modules_choices: @@ -38,6 +39,7 @@ class AdHocRunner: os.mkdir(self.project_dir, 0o755) ansible_runner.run( + extravars=self.extra_vars, host_pattern=self.pattern, private_data_dir=self.project_dir, inventory=self.inventory, diff --git a/apps/ops/migrations/0030_auto_20221116_1811.py b/apps/ops/migrations/0030_auto_20221116_1811.py new file mode 100644 index 000000000..3118f26ca --- /dev/null +++ b/apps/ops/migrations/0030_auto_20221116_1811.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.14 on 2022-11-16 10:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0029_auto_20221111_1919'), + ] + + operations = [ + migrations.AlterModelOptions( + name='celerytask', + options={'ordering': ('name',)}, + ), + migrations.AddField( + model_name='job', + name='variables', + field=models.JSONField(default=dict, verbose_name='Variables'), + ), + migrations.AlterField( + model_name='celerytask', + name='name', + field=models.CharField(max_length=1024, verbose_name='Name'), + ), + migrations.AlterField( + model_name='celerytaskexecution', + name='date_finished', + field=models.DateTimeField(null=True, verbose_name='Date finished'), + ), + migrations.AlterField( + model_name='celerytaskexecution', + name='date_published', + field=models.DateTimeField(auto_now_add=True, verbose_name='Date published'), + ), + migrations.AlterField( + model_name='celerytaskexecution', + name='date_start', + field=models.DateTimeField(null=True, verbose_name='Date start'), + ), + ] diff --git a/apps/ops/migrations/0031_auto_20221116_2024.py b/apps/ops/migrations/0031_auto_20221116_2024.py new file mode 100644 index 000000000..5c132e974 --- /dev/null +++ b/apps/ops/migrations/0031_auto_20221116_2024.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.14 on 2022-11-16 12:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0030_auto_20221116_1811'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='chdir', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Chdir'), + ), + migrations.AddField( + model_name='job', + name='comment', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), + ), + migrations.AddField( + model_name='job', + name='timeout', + field=models.IntegerField(default=60, verbose_name='Timeout (Seconds)'), + ), + ] diff --git a/apps/ops/models/common.py b/apps/ops/models/common.py new file mode 100644 index 000000000..9df754798 --- /dev/null +++ b/apps/ops/models/common.py @@ -0,0 +1,4 @@ +# 内置环境变量 +BUILTIN_VARIABLES = { + +} diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 650c701dd..b330a4bae 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -1,3 +1,4 @@ +import json import os import uuid import logging @@ -35,6 +36,8 @@ class Job(BaseCreateUpdateModel): args = models.CharField(max_length=1024, default='', verbose_name=_('Args'), null=True, blank=True) module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, verbose_name=_('Module'), null=True) + chdir = models.CharField(default="", max_length=1024, verbose_name=_('Chdir'), null=True, blank=True) + timeout = models.IntegerField(default=60, verbose_name=_('Timeout (Seconds)')) playbook = models.ForeignKey('ops.Playbook', verbose_name=_("Playbook"), null=True, on_delete=models.SET_NULL) type = models.CharField(max_length=128, choices=Types.choices, default=Types.adhoc, verbose_name=_("Type")) owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) @@ -42,6 +45,8 @@ class Job(BaseCreateUpdateModel): runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas')) runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip, verbose_name=_('Runas policy')) + variables = models.JSONField(default=dict, verbose_name=_('Variables')) + comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) @property def inventory(self): @@ -50,6 +55,9 @@ class Job(BaseCreateUpdateModel): def create_execution(self): return self.executions.create() + def get_variables(self): + return json.loads(self.variables) + class JobExecution(BaseCreateUpdateModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) @@ -70,7 +78,7 @@ class JobExecution(BaseCreateUpdateModel): if self.job.type == 'adhoc': runner = AdHocRunner( self.inventory_path, self.job.module, module_args=self.job.args, - pattern="all", project_dir=self.private_dir + pattern="all", project_dir=self.private_dir, extra_vars=self.job.get_variables() ) elif self.job.type == 'playbook': runner = PlaybookRunner( diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 64f26dd81..9b8823bcb 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -14,6 +14,10 @@ class JobSerializer(serializers.ModelSerializer): model = Job fields = [ "id", "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", + "variables", + "timeout", + "chdir", + "comment", "date_created", "date_updated" ] From 8a1a7d9e1313b003dd45706b1edb383294ba3d44 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 17 Nov 2022 11:03:55 +0800 Subject: [PATCH 356/488] fix: remove unused serializer_class --- apps/terminal/api/component/endpoint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/terminal/api/component/endpoint.py b/apps/terminal/api/component/endpoint.py index c81cbc1c3..8f770d75f 100644 --- a/apps/terminal/api/component/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -102,7 +102,8 @@ class EndpointRuleViewSet(JMSBulkModelViewSet): class ConnectMethodListApi(ListAPIView): permission_classes = (IsValidUser,) - serializer_class = serializers.ProtocolConnectMethodsSerializer + + # serializer_class = serializers.ProtocolConnectMethodsSerializer def get_queryset(self): protocol = self.request.query_params.get('protocol') From 4591b03e170a2393bb607dff448dd3f1afe7e341 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 17 Nov 2022 11:46:35 +0800 Subject: [PATCH 357/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20terminal?= =?UTF-8?q?=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/const.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index d44303f9f..1b39763e0 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -76,8 +76,14 @@ class NativeClient: mstsc = 'mstsc', 'Remote Desktop' @classmethod - def commands(cls, name, os): - return { + def get_native_clients(cls, p, os='windows'): + clients = { + '' + } + + @classmethod + def get_launch_command(cls, name, os='windows'): + commands = { 'ssh': 'ssh {username}@{hostname} -p {port}', 'putty': 'putty -ssh {username}@{hostname} -P {port}', 'xshell': '-url ssh://root:passwd@192.168.10.100', @@ -90,12 +96,25 @@ class NativeClient: 'redis': 'redis-cli -h {hostname} -p {port} -a {password}', 'mstsc': 'mstsc /v:{hostname}:{port}', } + command = commands.get(name) + if isinstance(command, dict): + command = command.get(os, command.get('default')) + return command + + +class RemoteAppMethod: + @classmethod + def get_remote_app_methods(cls, protocol): + from .models import Applet + applets = Applet.objects.filter(protocol=protocol) + return applets class ConnectMethod(TextChoices): web_cli = 'web_cli', _('Web CLI') web_gui = 'web_gui', _('Web GUI') native_client = 'native_client', _('Native Client') + remote_app = 'remote_app', _('Remote App') @classmethod def methods(cls): @@ -104,6 +123,14 @@ class ConnectMethod(TextChoices): Protocol.rdp: ([cls.web_gui], [cls.native_client]), Protocol.vnc: [cls.web_gui], Protocol.telnet: [cls.web_cli, cls.native_client], + Protocol.mysql: [cls.web_cli, cls.web_gui, cls.native_client], Protocol.sqlserver: [cls.web_cli, cls.web_gui], + Protocol.oracle: [cls.web_cli, cls.web_gui], + Protocol.postgresql: [cls.web_cli, cls.web_gui], + Protocol.redis: [cls.web_cli, cls.web_gui, cls.native_client], + Protocol.mongodb: [cls.web_cli, cls.web_gui], + + Protocol.k8s: [cls.web_cli], + Protocol.http: [], } From 73c21558648c7e5cc7145d4d93aa32766cf29b9c Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 17 Nov 2022 13:52:10 +0800 Subject: [PATCH 358/488] perf: ticket serializer (#9075) Co-authored-by: feng <1304903146@qq.com> --- apps/tickets/serializers/ticket/apply_asset.py | 12 ++++-------- apps/tickets/serializers/ticket/common.py | 5 +++-- apps/tickets/serializers/ticket/ticket.py | 15 ++++++--------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 69b4371f4..0593da3c6 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -22,18 +22,14 @@ class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer): class Meta(TicketApplySerializer.Meta): model = ApplyAssetTicket - fields_mini = ['id', 'title'] writeable_fields = [ - 'id', 'title', 'apply_nodes', 'apply_assets', - 'apply_accounts', 'apply_actions', 'org_id', 'comment', - 'apply_date_start', 'apply_date_expired' + 'apply_nodes', 'apply_assets', 'apply_accounts', + 'apply_actions', 'apply_date_start', 'apply_date_expired' ] - fields = TicketApplySerializer.Meta.fields + writeable_fields + ['apply_permission_name', ] - read_only_fields = list(set(fields) - set(writeable_fields)) + read_only_fields = TicketApplySerializer.Meta.read_only_fields + ['apply_permission_name', ] + fields = TicketApplySerializer.Meta.fields_small + writeable_fields + read_only_fields ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs extra_kwargs = { - 'apply_nodes': {'required': False}, - 'apply_assets': {'required': False}, 'apply_accounts': {'required': False}, } extra_kwargs.update(ticket_extra_kwargs) diff --git a/apps/tickets/serializers/ticket/common.py b/apps/tickets/serializers/ticket/common.py index 7cbaea697..1af361693 100644 --- a/apps/tickets/serializers/ticket/common.py +++ b/apps/tickets/serializers/ticket/common.py @@ -75,10 +75,11 @@ class BaseApplyAssetSerializer(serializers.Serializer): def create(self, validated_data): instance = super().create(validated_data) name = _('Created by ticket ({}-{})').format(instance.title, str(instance.id)[:4]) - with tmp_to_org(instance.org_id): + org_id = instance.org_id + with tmp_to_org(org_id): if not self.permission_model.objects.filter(name=name).exists(): instance.apply_permission_name = name - instance.save() + instance.save(update_fields=['apply_permission_name']) return instance raise serializers.ValidationError(_('Permission named `{}` already exists'.format(name))) diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index f201c2edc..7bb168791 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -22,13 +22,12 @@ class TicketSerializer(OrgResourceModelSerializerMixin): class Meta: model = Ticket fields_mini = ['id', 'title'] - fields_small = fields_mini + [ - 'type', 'status', 'state', 'approval_step', 'comment', - 'date_created', 'date_updated', 'org_id', 'rel_snapshot', - 'process_map', 'org_name', 'serial_num' + fields_small = fields_mini + ['org_id', 'comment'] + read_only_fields = [ + 'serial_num', 'process_map', 'approval_step', 'type', 'state', 'applicant', + 'status', 'date_created', 'date_updated', 'org_name', 'rel_snapshot' ] - fields_fk = ['applicant', ] - fields = fields_small + fields_fk + fields = fields_small + read_only_fields extra_kwargs = { 'type': {'required': True} } @@ -72,8 +71,6 @@ class TicketApplySerializer(TicketSerializer): if self.instance: return attrs - print("Attrs: ", attrs) - ticket_type = attrs.get('type') org_id = attrs.get('org_id') flow = TicketFlow.get_org_related_flows(org_id=org_id) \ @@ -81,7 +78,7 @@ class TicketApplySerializer(TicketSerializer): if flow: attrs['flow'] = flow + return attrs else: error = _('The ticket flow `{}` does not exist'.format(ticket_type)) raise serializers.ValidationError(error) - return attrs From 3a62abf381cc512694913d63153c4f89ccac00a4 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 17 Nov 2022 15:07:23 +0800 Subject: [PATCH 359/488] perf: ticket action (#9090) Co-authored-by: feng <1304903146@qq.com> --- apps/perms/const.py | 4 ++++ apps/tickets/models/ticket/apply_asset.py | 3 +++ apps/tickets/models/ticket/general.py | 2 ++ apps/tickets/serializers/ticket/apply_asset.py | 3 +-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/perms/const.py b/apps/perms/const.py index cecaa5e3a..8c1cdc908 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -32,3 +32,7 @@ class ActionChoices(BitChoices): def has_perm(cls, action_name, total): action_value = getattr(cls, action_name) return action_value & total == action_value + + @classmethod + def display(cls, value): + return ', '.join([c.label for c in cls if c.value & value == c.value]) diff --git a/apps/tickets/models/ticket/apply_asset.py b/apps/tickets/models/ticket/apply_asset.py index 1e46cc130..2fde56125 100644 --- a/apps/tickets/models/ticket/apply_asset.py +++ b/apps/tickets/models/ticket/apply_asset.py @@ -18,3 +18,6 @@ class ApplyAssetTicket(Ticket): apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.all()) apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) + + def get_apply_actions_display(self): + return ActionChoices.display(self.apply_actions) diff --git a/apps/tickets/models/ticket/general.py b/apps/tickets/models/ticket/general.py index e58ffa04a..9a2f5424d 100644 --- a/apps/tickets/models/ticket/general.py +++ b/apps/tickets/models/ticket/general.py @@ -398,6 +398,8 @@ class Ticket(StatusMixin, CommonModelMixin): value = self.rel_snapshot[name] elif isinstance(field, related.ManyToManyField): value = ', '.join(self.rel_snapshot[name]) + elif isinstance(value, list): + value = ', '.join(value) return value def get_local_snapshot(self): diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 0593da3c6..9eeb43998 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -44,8 +44,7 @@ class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer): attrs['type'] = 'apply_asset' attrs = super().validate(attrs) if self.is_final_approval and ( - not attrs.get('apply_nodes') - and not attrs.get('apply_assets') + not attrs.get('apply_nodes') and not attrs.get('apply_assets') ): raise serializers.ValidationError({ 'apply_nodes': asset_or_node_help_text, From ef3654ffa4b8891a7f1ef41ee00f0a8bdf822f48 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 17 Nov 2022 16:02:22 +0800 Subject: [PATCH 360/488] perf: update applet delopyment task --- apps/jumpserver/conf.py | 18 +++++++------ apps/jumpserver/settings/base.py | 6 +++-- .../deploy_applet_host/__init__.py | 8 +++++- .../deploy_applet_host/playbook.yml | 26 +++++++++---------- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index ca6ca4589..14c758301 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -7,23 +7,22 @@ 2. 程序需要, 用户不需要更改的写到settings中 3. 程序需要, 用户需要更改的写到本config中 """ +import base64 +import copy +import errno +import json +import logging import os import re import sys import types -import errno -import json -import yaml -import copy -import base64 -import logging from importlib import import_module from urllib.parse import urljoin, urlparse -from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT +import yaml from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ - +from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) @@ -499,6 +498,9 @@ class Config(dict): 'FORGOT_PASSWORD_URL': '', 'HEALTH_CHECK_TOKEN': '', + + # Applet 等软件的下载地址 + 'APPLET_DOWNLOAD_HOST': '', } def __init__(self, *args): diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 24bf7b8b3..7aac0e505 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -1,4 +1,5 @@ import os + from django.urls import reverse_lazy from .. import const @@ -36,6 +37,9 @@ DEBUG_DEV = CONFIG.DEBUG_DEV # Absolute url for some case, for example email link SITE_URL = CONFIG.SITE_URL +# Absolute url for downloading applet +APPLET_DOWNLOAD_HOST = CONFIG.APPLET_DOWNLOAD_HOST + # https://docs.djangoproject.com/en/4.1/ref/settings/ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') @@ -313,7 +317,6 @@ PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ] - GMSSL_ENABLED = CONFIG.GMSSL_ENABLED GM_HASHER = 'common.hashers.PBKDF2SM3PasswordHasher' if GMSSL_ENABLED: @@ -329,4 +332,3 @@ if os.environ.get('DEBUG_TOOLBAR', False): DEBUG_TOOLBAR_PANELS = [ 'debug_toolbar.panels.profiling.ProfilingPanel', ] - diff --git a/apps/terminal/automations/deploy_applet_host/__init__.py b/apps/terminal/automations/deploy_applet_host/__init__.py index be5847aa7..c5e903f35 100644 --- a/apps/terminal/automations/deploy_applet_host/__init__.py +++ b/apps/terminal/automations/deploy_applet_host/__init__.py @@ -45,20 +45,26 @@ class DeployAppletHostManager: def generate_initial_playbook(self): site_url = settings.SITE_URL + download_host = settings.APPLET_DOWNLOAD_HOST bootstrap_token = settings.BOOTSTRAP_TOKEN host_id = str(self.deployment.host.id) if not site_url: site_url = "http://localhost:8080" + if not download_host: + download_host = site_url options = self.deployment.host.deploy_options + site_url = site_url.rstrip("/") + download_host = download_host.rstrip("/") def handler(plays): for play in plays: play["vars"].update(options) - play["vars"]["DownloadHost"] = site_url + "/download" + play["vars"]["APPLET_DOWNLOAD_HOST"] = download_host play["vars"]["CORE_HOST"] = site_url play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token play["vars"]["HOST_ID"] = host_id play["vars"]["HOST_NAME"] = self.deployment.host.name + return plays return self._generate_playbook("playbook.yml", handler) diff --git a/apps/terminal/automations/deploy_applet_host/playbook.yml b/apps/terminal/automations/deploy_applet_host/playbook.yml index 3fec8999c..3d86ea52a 100644 --- a/apps/terminal/automations/deploy_applet_host/playbook.yml +++ b/apps/terminal/automations/deploy_applet_host/playbook.yml @@ -2,7 +2,7 @@ - hosts: all vars: - DownloadHost: https://demo.jumpserver.org/download + APPLET_DOWNLOAD_HOST: https://demo.jumpserver.org HOST_NAME: test HOST_ID: 00000000-0000-0000-0000-000000000000 CORE_HOST: https://demo.jumpserver.org @@ -32,7 +32,7 @@ - name: Download JumpServer Tinker installer (jumpserver) ansible.windows.win_get_url: - url: "{{ DownloadHost }}/{{ TinkerInstaller }}" + url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/{{ TinkerInstaller }}" dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}" - name: Install JumpServer Tinker (jumpserver) @@ -52,7 +52,7 @@ - name: Download python-3.10.8 ansible.windows.win_get_url: - url: "{{ DownloadHost }}/python-3.10.8-amd64.exe" + url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/python-3.10.8-amd64.exe" dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe" - name: Install the python-3.10.8 @@ -112,27 +112,27 @@ - name: Download pip packages ansible.windows.win_get_url: - url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip" - dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" + url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/pip_packages.zip" + dest: "{{ ansible_env.TEMP }}\\pip_packages.zip" - name: Unzip pip_packages community.windows.win_unzip: - src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip" - dest: "{{ ansible_env.TEMP }}" + src: "{{ ansible_env.TEMP }}\\pip_packages.zip" + dest: "{{ ansible_env.TEMP }}\\pip_packages" - name: Install python requirements offline ansible.windows.win_shell: > - pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt' - --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1' + pip install -r '{{ ansible_env.TEMP }}\pip_packages\requirements.txt' + --no-index --find-links='{{ ansible_env.TEMP }}\pip_packages' - name: Download chromedriver (chrome) ansible.windows.win_get_url: - url: "{{ DownloadHost }}/chromedriver_win32.107.zip" - dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" + url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chromedriver_win32.zip" + dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip" - name: Unzip chromedriver (chrome) community.windows.win_unzip: - src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip" + src: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip" dest: C:\Program Files\JumpServer\drivers - name: Set chromedriver on the global system path (chrome) @@ -142,7 +142,7 @@ - name: Download chrome msi package (chrome) ansible.windows.win_get_url: - url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi" + url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/googlechromestandaloneenterprise64.msi" dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi" - name: Install chrome (chrome) From 30f37d9ebfa8c834931b08b2f7d68d99fdf8602a Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 17 Nov 2022 16:38:46 +0800 Subject: [PATCH 361/488] perf: deployment task log order by date --- apps/terminal/models/applet/host.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/terminal/models/applet/host.py b/apps/terminal/models/applet/host.py index 83d9e3a41..020c4d98a 100644 --- a/apps/terminal/models/applet/host.py +++ b/apps/terminal/models/applet/host.py @@ -107,6 +107,9 @@ class AppletHostDeployment(JMSBaseModel): comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) task = models.UUIDField(null=True, verbose_name=_('Task')) + class Meta: + ordering = ('-date_start',) + def start(self, **kwargs): from ...automations.deploy_applet_host import DeployAppletHostManager manager = DeployAppletHostManager(self) From 71122312cf87cdb2bede8b116f8de88058965b85 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:34:52 +0800 Subject: [PATCH 362/488] perf: ticket optimization (#9094) Co-authored-by: feng <1304903146@qq.com> --- apps/perms/const.py | 2 +- apps/tickets/api/ticket.py | 4 ++++ apps/tickets/serializers/ticket/apply_asset.py | 8 +++++++- apps/tickets/serializers/ticket/command_confirm.py | 8 ++++---- apps/tickets/serializers/ticket/login_asset_confirm.py | 5 ++--- apps/tickets/serializers/ticket/login_confirm.py | 7 +++---- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/perms/const.py b/apps/perms/const.py index 8c1cdc908..e9af9f25d 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -35,4 +35,4 @@ class ActionChoices(BitChoices): @classmethod def display(cls, value): - return ', '.join([c.label for c in cls if c.value & value == c.value]) + return ', '.join([str(c.label) for c in cls if c.value & value == c.value]) diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 6216f49be..a256c51b5 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -97,6 +97,10 @@ class ApplyAssetTicketViewSet(TicketViewSet): serializer_class = serializers.ApplyAssetSerializer model = ApplyAssetTicket filterset_class = filters.ApplyAssetTicketFilter + serializer_classes = { + 'open': serializers.ApplyAssetSerializer, + 'approve': serializers.ApproveAssetSerializer + } class ApplyLoginTicketViewSet(TicketViewSet): diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 9eeb43998..4e7fd0f6c 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -9,7 +9,7 @@ from tickets.models import ApplyAssetTicket from .common import BaseApplyAssetSerializer from .ticket import TicketApplySerializer -__all__ = ['ApplyAssetSerializer'] +__all__ = ['ApplyAssetSerializer', 'ApproveAssetSerializer'] asset_or_node_help_text = _("Select at least one asset or node") @@ -57,3 +57,9 @@ class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer): def setup_eager_loading(cls, queryset): queryset = queryset.prefetch_related('apply_nodes', 'apply_assets') return queryset + + +class ApproveAssetSerializer(ApplyAssetSerializer): + class Meta(ApplyAssetSerializer.Meta): + read_only_fields = TicketApplySerializer.Meta.fields_small + \ + ApplyAssetSerializer.Meta.read_only_fields diff --git a/apps/tickets/serializers/ticket/command_confirm.py b/apps/tickets/serializers/ticket/command_confirm.py index fced49976..5b3a39b0e 100644 --- a/apps/tickets/serializers/ticket/command_confirm.py +++ b/apps/tickets/serializers/ticket/command_confirm.py @@ -9,8 +9,8 @@ __all__ = [ class ApplyCommandConfirmSerializer(TicketApplySerializer): class Meta: model = ApplyCommandTicket - fields = TicketApplySerializer.Meta.fields + [ - 'apply_run_user', 'apply_run_asset', 'apply_run_account', - 'apply_run_command', 'apply_from_session', 'apply_from_cmd_filter', - 'apply_from_cmd_filter_rule' + writeable_fields = [ + 'apply_run_user', 'apply_run_asset', 'apply_run_account', 'apply_run_command', + 'apply_from_session', 'apply_from_cmd_filter', 'apply_from_cmd_filter_rule' ] + fields = TicketApplySerializer.Meta.fields + writeable_fields diff --git a/apps/tickets/serializers/ticket/login_asset_confirm.py b/apps/tickets/serializers/ticket/login_asset_confirm.py index 43d54d327..4d3db5fc6 100644 --- a/apps/tickets/serializers/ticket/login_asset_confirm.py +++ b/apps/tickets/serializers/ticket/login_asset_confirm.py @@ -9,6 +9,5 @@ __all__ = [ class LoginAssetConfirmSerializer(TicketApplySerializer): class Meta: model = ApplyLoginAssetTicket - fields = TicketApplySerializer.Meta.fields + [ - 'apply_login_user', 'apply_login_asset', 'apply_login_account' - ] + writeable_fields = ['apply_login_user', 'apply_login_asset', 'apply_login_account'] + fields = TicketApplySerializer.Meta.fields + writeable_fields diff --git a/apps/tickets/serializers/ticket/login_confirm.py b/apps/tickets/serializers/ticket/login_confirm.py index e760c653f..128ac5971 100644 --- a/apps/tickets/serializers/ticket/login_confirm.py +++ b/apps/tickets/serializers/ticket/login_confirm.py @@ -7,8 +7,7 @@ __all__ = [ class LoginConfirmSerializer(TicketApplySerializer): - class Meta: + class Meta(TicketApplySerializer.Meta): model = ApplyLoginTicket - fields = TicketApplySerializer.Meta.fields + [ - 'apply_login_ip', 'apply_login_city', 'apply_login_datetime' - ] + writeable_fields = ['apply_login_ip', 'apply_login_city', 'apply_login_datetime'] + fields = TicketApplySerializer.Meta.fields + writeable_fields From 543d61442ccfa4a30ee7444eb1639bac4fff23cb Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 17 Nov 2022 18:04:38 +0800 Subject: [PATCH 363/488] perf: ticket accounts --- apps/perms/const.py | 1 - apps/perms/models/asset_permission.py | 12 +++++------- apps/tickets/handlers/apply_asset.py | 13 +++++++------ apps/tickets/handlers/login_confirm.py | 16 ---------------- apps/tickets/models/ticket/general.py | 16 +++++++++------- 5 files changed, 21 insertions(+), 37 deletions(-) diff --git a/apps/perms/const.py b/apps/perms/const.py index e9af9f25d..50141f386 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -from django.db import models from django.utils.translation import ugettext_lazy as _ from common.db.fields import BitChoices diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 1b9f068b0..6f48c05d1 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -64,17 +64,15 @@ class AssetPermission(OrgModelMixin): # 特殊的账号: @ALL, @INPUT @USER 默认包含,将来在全局设置中进行控制. accounts = models.JSONField(default=list, verbose_name=_("Accounts")) actions = models.IntegerField(default=ActionChoices.connect, verbose_name=_("Actions")) - is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_start = models.DateTimeField( - default=timezone.now, db_index=True, verbose_name=_("Date start") - ) + date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) date_expired = models.DateTimeField( default=date_expired_default, db_index=True, verbose_name=_('Date expired') ) - created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) - from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket')) comment = models.TextField(verbose_name=_('Comment'), blank=True) + is_active = models.BooleanField(default=True, verbose_name=_('Active')) + from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)() diff --git a/apps/tickets/handlers/apply_asset.py b/apps/tickets/handlers/apply_asset.py index 3f2051120..2ad50b6df 100644 --- a/apps/tickets/handlers/apply_asset.py +++ b/apps/tickets/handlers/apply_asset.py @@ -14,7 +14,6 @@ class Handler(BaseHandler): if is_finished: self._create_asset_permission() - # permission def _create_asset_permission(self): org_id = self.ticket.org_id with tmp_to_org(org_id): @@ -27,6 +26,7 @@ class Handler(BaseHandler): apply_permission_name = self.ticket.apply_permission_name apply_actions = self.ticket.apply_actions + apply_accounts = self.ticket.apply_accounts apply_date_start = self.ticket.apply_date_start apply_date_expired = self.ticket.apply_date_expired permission_created_by = '{}:{}'.format( @@ -46,19 +46,20 @@ class Handler(BaseHandler): ) permission_data = { - 'id': self.ticket.id, - 'name': apply_permission_name, 'from_ticket': True, - 'comment': str(permission_comment), - 'created_by': permission_created_by, + 'id': self.ticket.id, 'actions': apply_actions, + 'accounts': apply_accounts, + 'name': apply_permission_name, 'date_start': apply_date_start, 'date_expired': apply_date_expired, + 'comment': str(permission_comment), + 'created_by': permission_created_by, } with tmp_to_org(self.ticket.org_id): asset_permission = AssetPermission.objects.create(**permission_data) - asset_permission.users.add(self.ticket.applicant) asset_permission.nodes.set(apply_nodes) asset_permission.assets.set(apply_assets) + asset_permission.users.add(self.ticket.applicant) return asset_permission diff --git a/apps/tickets/handlers/login_confirm.py b/apps/tickets/handlers/login_confirm.py index ad33ce476..e6498314e 100644 --- a/apps/tickets/handlers/login_confirm.py +++ b/apps/tickets/handlers/login_confirm.py @@ -1,22 +1,6 @@ -from django.utils.translation import ugettext as _ from tickets.models import ApplyLoginTicket from .base import BaseHandler class Handler(BaseHandler): ticket: ApplyLoginTicket - - def _construct_meta_body_of_open(self): - apply_login_ip = self.ticket.apply_login_ip - apply_login_city = self.ticket.apply_login_city - apply_login_datetime = self.ticket.apply_login_datetime - applied_body = ''' - {}: {} - {}: {} - {}: {} - '''.format( - _("Applied login IP"), apply_login_ip, - _("Applied login city"), apply_login_city, - _("Applied login datetime"), apply_login_datetime, - ) - return applied_body diff --git a/apps/tickets/models/ticket/general.py b/apps/tickets/models/ticket/general.py index 9a2f5424d..d02d8eccc 100644 --- a/apps/tickets/models/ticket/general.py +++ b/apps/tickets/models/ticket/general.py @@ -24,7 +24,9 @@ from tickets.handlers import get_ticket_handler from tickets.errors import AlreadyClosed from ..flow import TicketFlow -__all__ = ['Ticket', 'TicketStep', 'TicketAssignee', 'SuperTicket', 'SubTicketManager'] +__all__ = [ + 'Ticket', 'TicketStep', 'TicketAssignee', 'SuperTicket', 'SubTicketManager' +] class TicketStep(CommonModelMixin): @@ -282,19 +284,19 @@ class Ticket(StatusMixin, CommonModelMixin): ) # 申请人 applicant = models.ForeignKey( - 'users.User', related_name='applied_tickets', on_delete=models.SET_NULL, - null=True, verbose_name=_("Applicant") + 'users.User', related_name='applied_tickets', null=True, + on_delete=models.SET_NULL, verbose_name=_("Applicant") ) - comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) flow = models.ForeignKey( - 'TicketFlow', related_name='tickets', on_delete=models.SET_NULL, - null=True, verbose_name=_('TicketFlow') + 'TicketFlow', related_name='tickets', null=True, + on_delete=models.SET_NULL, verbose_name=_('TicketFlow') ) approval_step = models.SmallIntegerField( default=TicketLevel.one, choices=TicketLevel.choices, verbose_name=_('Approval step') ) - serial_num = models.CharField(_('Serial number'), max_length=128, unique=True, null=True) + comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) rel_snapshot = models.JSONField(verbose_name=_('Relation snapshot'), default=dict) + serial_num = models.CharField(_('Serial number'), max_length=128, unique=True, null=True) meta = models.JSONField(encoder=ModelJSONFieldEncoder, default=dict, verbose_name=_("Meta")) org_id = models.CharField( max_length=36, blank=True, default='', verbose_name=_('Organization'), db_index=True From 49a4ceba8558db943798fa5c14790197686679e5 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 17 Nov 2022 19:20:54 +0800 Subject: [PATCH 364/488] perf: ticket --- apps/acls/models/login_acl.py | 15 ++++++++------- apps/acls/models/login_asset_acl.py | 4 ++-- apps/tickets/models/ticket/command_confirm.py | 2 +- apps/tickets/models/ticket/general.py | 19 +++++++++---------- .../models/ticket/login_asset_confirm.py | 6 ++---- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index 6fcff0231..71f202b15 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -53,12 +53,13 @@ class LoginACL(BaseACL): @staticmethod def match(user, ip): - acls = LoginACL.filter_acl(user) - if not acls: + acl_qs = LoginACL.filter_acl(user) + if not acl_qs: return - for acl in acls: - if acl.is_action(LoginACL.ActionChoices.confirm) and not acl.reviewers.exists(): + for acl in acl_qs: + if acl.is_action(LoginACL.ActionChoices.confirm) and \ + not acl.reviewers.exists(): continue ip_group = acl.rules.get('ip_group') time_periods = acl.rules.get('time_period') @@ -79,12 +80,12 @@ class LoginACL(BaseACL): login_datetime = local_now_display() data = { 'title': title, - 'type': const.TicketType.login_confirm, 'applicant': self.user, - 'apply_login_city': login_city, 'apply_login_ip': login_ip, - 'apply_login_datetime': login_datetime, 'org_id': Organization.ROOT_ID, + 'apply_login_city': login_city, + 'apply_login_datetime': login_datetime, + 'type': const.TicketType.login_confirm, } ticket = ApplyLoginTicket.objects.create(**data) assignees = self.reviewers.all() diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 37bea242a..2ad9363e5 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -86,12 +86,12 @@ class LoginAssetACL(BaseACL, OrgModelMixin): title = _('Login asset confirm') + ' ({})'.format(user) data = { 'title': title, - 'type': TicketType.login_asset_confirm, + 'org_id': org_id, 'applicant': user, 'apply_login_user': user, 'apply_login_asset': asset, 'apply_login_account': str(account), - 'org_id': org_id, + 'type': TicketType.login_asset_confirm, } ticket = ApplyLoginAssetTicket.objects.create(**data) ticket.open_by_system(assignees) diff --git a/apps/tickets/models/ticket/command_confirm.py b/apps/tickets/models/ticket/command_confirm.py index 601102ba3..ac70eaadb 100644 --- a/apps/tickets/models/ticket/command_confirm.py +++ b/apps/tickets/models/ticket/command_confirm.py @@ -10,8 +10,8 @@ class ApplyCommandTicket(Ticket): null=True, verbose_name=_('Run user') ) apply_run_asset = models.CharField(max_length=128, verbose_name=_('Run asset')) - apply_run_account = models.CharField(max_length=128, default='', verbose_name=_('Run account')) apply_run_command = models.CharField(max_length=4096, verbose_name=_('Run command')) + apply_run_account = models.CharField(max_length=128, default='', verbose_name=_('Run account')) apply_from_session = models.ForeignKey( 'terminal.Session', on_delete=models.SET_NULL, null=True, verbose_name=_("Session") diff --git a/apps/tickets/models/ticket/general.py b/apps/tickets/models/ticket/general.py index d02d8eccc..605990040 100644 --- a/apps/tickets/models/ticket/general.py +++ b/apps/tickets/models/ticket/general.py @@ -206,11 +206,11 @@ class StatusMixin: step_info = { 'state': state, - 'approval_level': step.level, 'assignees': assignee_ids, + 'processor': processor_id, + 'approval_level': step.level, 'assignees_display': assignees_display, 'approval_date': str(step.date_updated), - 'processor': processor_id, 'processor_display': processor_display } process_map.append(step_info) @@ -226,15 +226,15 @@ class StatusMixin: org_id = self.flow.org_id flow_rules = self.flow.rules.order_by('level') for rule in flow_rules: - step = TicketStep.objects.create(ticket=self, level=rule.level) assignees = rule.get_assignees(org_id=org_id) assignees = self.exclude_applicant(assignees, self.applicant) + step = TicketStep.objects.create(ticket=self, level=rule.level) step_assignees = [TicketAssignee(step=step, assignee=user) for user in assignees] TicketAssignee.objects.bulk_create(step_assignees) def create_process_steps_by_assignees(self, assignees): - assignees = self.exclude_applicant(assignees, self.applicant) step = TicketStep.objects.create(ticket=self, level=1) + assignees = self.exclude_applicant(assignees, self.applicant) ticket_assignees = [TicketAssignee(step=step, assignee=user) for user in assignees] TicketAssignee.objects.bulk_create(ticket_assignees) @@ -250,14 +250,13 @@ class StatusMixin: @property def processor(self): processor = self.current_step.ticket_assignees \ - .exclude(state=StepState.pending) \ - .first() + .exclude(state=StepState.pending).first() return processor.assignee if processor else None def has_current_assignee(self, assignee): return self.ticket_steps.filter( + level=self.approval_step, ticket_assignees__assignee=assignee, - level=self.approval_step ).exists() def has_all_assignee(self, assignee): @@ -326,7 +325,7 @@ class Ticket(StatusMixin, CommonModelMixin): @classmethod def get_user_related_tickets(cls, user): queries = Q(applicant=user) | Q(ticket_steps__ticket_assignees__assignee=user) - tickets = cls.objects.all().filter(queries).distinct() + tickets = cls.objects.filter(queries).distinct() return tickets def get_current_ticket_flow_approve(self): @@ -405,12 +404,12 @@ class Ticket(StatusMixin, CommonModelMixin): return value def get_local_snapshot(self): + snapshot = {} + excludes = ['ticket_ptr'] fields = self._meta._forward_fields_map json_data = json.dumps(model_to_dict(self), cls=ModelJSONFieldEncoder) data = json.loads(json_data) - snapshot = {} local_fields = self._meta.local_fields + self._meta.local_many_to_many - excludes = ['ticket_ptr'] item_names = [field.name for field in local_fields if field.name not in excludes] for name in item_names: field = fields[name] diff --git a/apps/tickets/models/ticket/login_asset_confirm.py b/apps/tickets/models/ticket/login_asset_confirm.py index 2b97cd7a2..8761bc7fe 100644 --- a/apps/tickets/models/ticket/login_asset_confirm.py +++ b/apps/tickets/models/ticket/login_asset_confirm.py @@ -8,12 +8,10 @@ __all__ = ['ApplyLoginAssetTicket'] class ApplyLoginAssetTicket(Ticket): apply_login_user = models.ForeignKey( - 'users.User', on_delete=models.SET_NULL, null=True, - verbose_name=_('Login user'), + 'users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Login user'), ) apply_login_asset = models.ForeignKey( - 'assets.Asset', on_delete=models.SET_NULL, null=True, - verbose_name=_('Login asset'), + 'assets.Asset', on_delete=models.SET_NULL, null=True, verbose_name=_('Login asset'), ) apply_login_account = models.CharField( max_length=128, default='', verbose_name=_('Login account') From 24ed11e2a574442a754e4157d1b9e856a57e10c1 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Thu, 17 Nov 2022 20:10:13 +0800 Subject: [PATCH 365/488] =?UTF-8?q?feat:=20=E8=BF=90=E8=A1=8Cjob=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/adhoc.py | 9 ++----- .../ops/migrations/0032_auto_20221117_1848.py | 27 +++++++++++++++++++ apps/ops/models/job.py | 9 +++---- apps/ops/serializers/adhoc.py | 9 ------- apps/ops/serializers/job.py | 4 +-- 5 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 apps/ops/migrations/0032_auto_20221117_1848.py diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index ce7e8ad57..9889349df 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -4,7 +4,7 @@ from rest_framework import viewsets from ..models import AdHoc from ..serializers import ( - AdHocSerializer, AdhocListSerializer, + AdHocSerializer ) __all__ = [ @@ -14,9 +14,4 @@ __all__ = [ class AdHocViewSet(viewsets.ModelViewSet): queryset = AdHoc.objects.all() - - def get_serializer_class(self): - if self.action != 'list': - return AdhocListSerializer - return AdHocSerializer - + serializer_class = AdHocSerializer diff --git a/apps/ops/migrations/0032_auto_20221117_1848.py b/apps/ops/migrations/0032_auto_20221117_1848.py new file mode 100644 index 000000000..ae18c5280 --- /dev/null +++ b/apps/ops/migrations/0032_auto_20221117_1848.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.14 on 2022-11-17 10:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0031_auto_20221116_2024'), + ] + + operations = [ + migrations.RemoveField( + model_name='job', + name='variables', + ), + migrations.AddField( + model_name='job', + name='parameters_define', + field=models.JSONField(default=dict, verbose_name='Parameters define'), + ), + migrations.AddField( + model_name='jobexecution', + name='parameters', + field=models.JSONField(default=dict, verbose_name='Parameters'), + ), + ] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index b330a4bae..8897d6107 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -45,7 +45,7 @@ class Job(BaseCreateUpdateModel): runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas')) runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip, verbose_name=_('Runas policy')) - variables = models.JSONField(default=dict, verbose_name=_('Variables')) + parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define')) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) @property @@ -55,15 +55,13 @@ class Job(BaseCreateUpdateModel): def create_execution(self): return self.executions.create() - def get_variables(self): - return json.loads(self.variables) - class JobExecution(BaseCreateUpdateModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) task_id = models.UUIDField(null=True) status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='executions', null=True) + parameters = models.JSONField(default=dict, verbose_name=_('Parameters')) result = models.JSONField(blank=True, null=True, verbose_name=_('Result')) summary = models.JSONField(default=dict, verbose_name=_('Summary')) creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) @@ -74,11 +72,12 @@ class JobExecution(BaseCreateUpdateModel): def get_runner(self): inv = self.job.inventory inv.write_to_file(self.inventory_path) + extra_vars = json.loads(self.parameters) if self.job.type == 'adhoc': runner = AdHocRunner( self.inventory_path, self.job.module, module_args=self.job.args, - pattern="all", project_dir=self.private_dir, extra_vars=self.job.get_variables() + pattern="all", project_dir=self.private_dir, extra_vars=extra_vars, ) elif self.job.type == 'playbook': runner = PlaybookRunner( diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 2120c8c50..f5d8d4780 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -14,15 +14,6 @@ class AdHocSerializer(serializers.ModelSerializer): row_count = serializers.IntegerField(read_only=True) size = serializers.IntegerField(read_only=True) - class Meta: - model = AdHoc - fields = ["id", "name", "module", "owner", "row_count", "size", "date_created", "date_updated"] - - -class AdhocListSerializer(AdHocSerializer): - row_count = serializers.IntegerField(read_only=True) - size = serializers.IntegerField(read_only=True) - class Meta: model = AdHoc fields = ["id", "name", "module", "row_count", "size", "args", "owner", "date_created", "date_updated"] diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 9b8823bcb..2771e2a7f 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -14,7 +14,7 @@ class JobSerializer(serializers.ModelSerializer): model = Job fields = [ "id", "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", - "variables", + "parameters_define", "timeout", "chdir", "comment", @@ -29,5 +29,5 @@ class JobExecutionSerializer(serializers.ModelSerializer): read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created', 'is_success', 'task_id', 'short_id'] fields = read_only_fields + [ - "job" + "job", "parameters" ] From 04ee7ee0e7117c81ecf5aaebc830a54ae2a056aa Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 17 Nov 2022 20:48:50 +0800 Subject: [PATCH 366/488] =?UTF-8?q?pref:=20=E5=90=8E=E7=AB=AF=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20connect=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/component/endpoint.py | 14 -- apps/terminal/api/component/terminal.py | 39 +++-- apps/terminal/const.py | 178 ++++++++++++++++----- apps/terminal/models/component/terminal.py | 17 +- apps/terminal/serializers/terminal.py | 4 +- apps/terminal/urls/api_urls.py | 11 +- 6 files changed, 180 insertions(+), 83 deletions(-) diff --git a/apps/terminal/api/component/endpoint.py b/apps/terminal/api/component/endpoint.py index 8f770d75f..d406b51f7 100644 --- a/apps/terminal/api/component/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -2,13 +2,11 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext_lazy as _ from rest_framework import status from rest_framework.decorators import action -from rest_framework.generics import ListAPIView from rest_framework.request import Request from rest_framework.response import Response from assets.models import Asset from common.drf.api import JMSBulkModelViewSet -from common.permissions import IsValidUser from common.permissions import IsValidUserOrConnectionToken from orgs.utils import tmp_to_root_org from terminal import serializers @@ -98,15 +96,3 @@ class EndpointRuleViewSet(JMSBulkModelViewSet): search_fields = filterset_fields serializer_class = serializers.EndpointRuleSerializer queryset = EndpointRule.objects.all() - - -class ConnectMethodListApi(ListAPIView): - permission_classes = (IsValidUser,) - - # serializer_class = serializers.ProtocolConnectMethodsSerializer - - def get_queryset(self): - protocol = self.request.query_params.get('protocol') - if not protocol: - return [] - return Protocol.objects.filter(name=protocol) diff --git a/apps/terminal/api/component/terminal.py b/apps/terminal/api/component/terminal.py index 3133db6eb..e3aa4afb3 100644 --- a/apps/terminal/api/component/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -1,26 +1,24 @@ # -*- coding: utf-8 -*- # import logging -import uuid -from django.core.cache import cache -from rest_framework import generics -from rest_framework.views import APIView, Response -from rest_framework import status from django.conf import settings from django.utils.translation import gettext_lazy as _ +from rest_framework import generics +from rest_framework import status +from rest_framework.views import APIView, Response -from common.exceptions import JMSException from common.drf.api import JMSBulkModelViewSet -from common.utils import get_object_or_none, get_request_ip +from common.exceptions import JMSException +from common.permissions import IsValidUser from common.permissions import WithBootstrapToken -from terminal.models import Terminal from terminal import serializers -from terminal import exceptions +from terminal.const import TerminalType +from terminal.models import Terminal __all__ = [ - 'TerminalViewSet', 'TerminalConfig', - 'TerminalRegistrationApi', + 'TerminalViewSet', 'TerminalConfig', + 'TerminalRegistrationApi', 'ConnectMethodListApi' ] logger = logging.getLogger(__file__) @@ -72,3 +70,22 @@ class TerminalRegistrationApi(generics.CreateAPIView): data = {"error": "service account registration disabled"} return Response(data=data, status=status.HTTP_400_BAD_REQUEST) return super().create(request, *args, **kwargs) + + +class ConnectMethodListApi(generics.ListAPIView): + serializer_class = serializers.ConnectMethodSerializer + permission_classes = [IsValidUser] + + def get_queryset(self): + user_agent = self.request.META['HTTP_USER_AGENT'].lower() + if 'macintosh' in user_agent: + os = 'macos' + elif 'windows' in user_agent: + os = 'windows' + else: + os = 'linux' + return TerminalType.get_protocols_connect_methods(os) + + def list(self, request, *args, **kwargs): + queryset = self.get_queryset() + return Response(queryset) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 1b39763e0..288975866 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from collections import defaultdict from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ @@ -43,24 +44,12 @@ class ComponentLoad(TextChoices): return set(dict(cls.choices).keys()) -class TerminalType(TextChoices): - koko = 'koko', 'KoKo' - guacamole = 'guacamole', 'Guacamole' - omnidb = 'omnidb', 'OmniDB' - xrdp = 'xrdp', 'Xrdp' - lion = 'lion', 'Lion' - core = 'core', 'Core' - celery = 'celery', 'Celery' - magnus = 'magnus', 'Magnus' - razor = 'razor', 'Razor' - tinker = 'tinker', 'Tinker' - - @classmethod - def types(cls): - return set(dict(cls.choices).keys()) +class HttpMethod(TextChoices): + web_gui = 'web_gui', 'Web GUI' + web_cli = 'web_cli', 'Web CLI' -class NativeClient: +class NativeClient(TextChoices): # Koko ssh = 'ssh', 'ssh' putty = 'putty', 'PuTTY' @@ -71,15 +60,42 @@ class NativeClient: psql = 'psql', 'psql' sqlplus = 'sqlplus', 'sqlplus' redis = 'redis-cli', 'redis-cli' + mongodb = 'mongo', 'mongo' # Razor mstsc = 'mstsc', 'Remote Desktop' @classmethod - def get_native_clients(cls, p, os='windows'): + def get_native_clients(cls): clients = { - '' + Protocol.ssh: { + 'default': [cls.ssh], + 'windows': [cls.putty], + }, + Protocol.rdp: [cls.mstsc], + Protocol.mysql: [cls.mysql], + Protocol.oracle: [cls.sqlplus], + Protocol.postgresql: [cls.psql], + Protocol.redis: [cls.redis], + Protocol.mongodb: [cls.mongodb], } + return clients + + @classmethod + def get_native_methods(cls, os='windows'): + clients_map = cls.get_native_clients() + methods = defaultdict(list) + + for protocol, _clients in clients_map.items(): + if isinstance(_clients, dict): + _clients = _clients.get(os, _clients['default']) + for client in _clients: + methods[protocol].append({ + 'value': client.value, + 'label': client.label, + 'type': 'native', + }) + return methods @classmethod def get_launch_command(cls, name, os='windows'): @@ -104,33 +120,113 @@ class NativeClient: class RemoteAppMethod: @classmethod - def get_remote_app_methods(cls, protocol): + def get_remote_app_methods(cls): from .models import Applet - applets = Applet.objects.filter(protocol=protocol) - return applets + applets = Applet.objects.all() + methods = defaultdict(list) + for applet in applets: + for protocol in applet.protocols: + methods[protocol].append({ + 'value': applet.name, + 'label': applet.display_name, + 'icon': applet.icon, + 'type': 'remote_app', + }) + return methods -class ConnectMethod(TextChoices): - web_cli = 'web_cli', _('Web CLI') - web_gui = 'web_gui', _('Web GUI') - native_client = 'native_client', _('Native Client') - remote_app = 'remote_app', _('Remote App') +class TerminalType(TextChoices): + koko = 'koko', 'KoKo' + guacamole = 'guacamole', 'Guacamole' + omnidb = 'omnidb', 'OmniDB' + xrdp = 'xrdp', 'Xrdp' + lion = 'lion', 'Lion' + core = 'core', 'Core' + celery = 'celery', 'Celery' + magnus = 'magnus', 'Magnus' + razor = 'razor', 'Razor' + tinker = 'tinker', 'Tinker' @classmethod - def methods(cls): + def types(cls): + return set(dict(cls.choices).keys()) + + @classmethod + def protocols(cls): return { - Protocol.ssh: [cls.web_cli, cls.native_client], - Protocol.rdp: ([cls.web_gui], [cls.native_client]), - Protocol.vnc: [cls.web_gui], - Protocol.telnet: [cls.web_cli, cls.native_client], - - Protocol.mysql: [cls.web_cli, cls.web_gui, cls.native_client], - Protocol.sqlserver: [cls.web_cli, cls.web_gui], - Protocol.oracle: [cls.web_cli, cls.web_gui], - Protocol.postgresql: [cls.web_cli, cls.web_gui], - Protocol.redis: [cls.web_cli, cls.web_gui, cls.native_client], - Protocol.mongodb: [cls.web_cli, cls.web_gui], - - Protocol.k8s: [cls.web_cli], - Protocol.http: [], + cls.koko: { + 'http_method': HttpMethod.web_cli, + 'listen': [Protocol.ssh, Protocol.http], + 'support': [ + Protocol.ssh, Protocol.telnet, + Protocol.mysql, Protocol.postgresql, + Protocol.oracle, Protocol.sqlserver, + Protocol.mariadb, Protocol.redis, + Protocol.mongodb, + ], + 'match': 'm2m' + }, + cls.omnidb: { + 'http_method': HttpMethod.web_gui, + 'listen': [Protocol.http], + 'support': [ + Protocol.mysql, Protocol.postgresql, Protocol.oracle, + Protocol.sqlserver, Protocol.mariadb + ], + 'match': 'm2m' + }, + cls.lion: { + 'http_method': HttpMethod.web_gui, + 'listen': [Protocol.http], + 'support': [Protocol.rdp, Protocol.vnc], + 'match': 'm2m' + }, + cls.magnus: { + 'listen': [], + 'support': [ + Protocol.mysql, Protocol.postgresql, Protocol.oracle, + Protocol.mariadb + ], + 'match': 'map' + }, + cls.razor: { + 'listen': [Protocol.rdp], + 'support': [Protocol.rdp], + 'match': 'map' + } } + + @classmethod + def get_protocols_connect_methods(cls, os): + methods = defaultdict(list) + native_methods = NativeClient.get_native_methods(os) + remote_app_methods = RemoteAppMethod.get_remote_app_methods() + + for component, component_protocol in cls.protocols().items(): + component_methods = defaultdict(list) + support = component_protocol['support'] + + for protocol in support: + if component_protocol['match'] == 'map': + listen = [protocol] + else: + listen = component_protocol['listen'] + + for listen_protocol in listen: + if listen_protocol == Protocol.http: + web_protocol = component_protocol['http_method'] + component_methods[protocol.value].append({ + 'value': web_protocol.value, + 'label': web_protocol.label, + 'type': 'web', + }) + + # Native method + component_methods[protocol.value].extend(native_methods[listen_protocol]) + component_methods[protocol.value].extend(remote_app_methods[listen_protocol]) + + for protocol, _methods in component_methods.items(): + for method in _methods: + method['component'] = component.value + methods[protocol].extend(_methods) + return methods diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index 11a2a9a61..1d65ad32a 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,19 +1,16 @@ -import uuid import time +import uuid -from django.utils import timezone -from django.db import models -from django.core.cache import cache -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty -from users.models import User from orgs.utils import tmp_to_root_org -from terminal.const import TerminalType as TypeChoices, ComponentLoad as StatusChoice +from terminal.const import TerminalType as TypeChoices +from users.models import User from ..session import Session - logger = get_logger(__file__) @@ -87,7 +84,8 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): remote_addr = models.CharField(max_length=128, blank=True, verbose_name=_('Remote Address')) command_storage = models.CharField(max_length=128, verbose_name=_("Command storage"), default='default') replay_storage = models.CharField(max_length=128, verbose_name=_("Replay storage"), default='default') - user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE) + user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, + on_delete=models.CASCADE) is_deleted = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -160,4 +158,3 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): permissions = ( ('view_terminalconfig', _('Can view terminal config')), ) - diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index f12990618..df32d89c2 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -136,4 +136,6 @@ class TerminalRegistrationSerializer(serializers.ModelSerializer): class ConnectMethodSerializer(serializers.Serializer): - name = serializers.CharField(max_length=128) + value = serializers.CharField(max_length=128) + label = serializers.CharField(max_length=128) + group = serializers.CharField(max_length=128) diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 3e39b55ce..fb7087850 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -31,7 +31,6 @@ router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication') router.register(r'applet-host-deployments', api.AppletHostDeploymentViewSet, 'applet-host-deployment') - urlpatterns = [ path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'), path('terminal-registrations/', api.TerminalRegistrationApi.as_view(), name='terminal-registration'), @@ -44,10 +43,13 @@ urlpatterns = [ path('tasks/kill-session-for-ticket/', api.KillSessionForTicketAPI.as_view(), name='kill-session-for-ticket'), path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'), path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"), - path('replay-storages//test-connective/', api.ReplayStorageTestConnectiveApi.as_view(), name='replay-storage-test-connective'), - path('command-storages//test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'), + path('replay-storages//test-connective/', api.ReplayStorageTestConnectiveApi.as_view(), + name='replay-storage-test-connective'), + path('command-storages//test-connective/', api.CommandStorageTestConnectiveApi.as_view(), + name='command-storage-test-connective'), # components path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'), + path('components/connect-methods/', api.ConnectMethodListApi.as_view(), name='connect-methods'), ] old_version_urlpatterns = [ @@ -55,6 +57,3 @@ old_version_urlpatterns = [ ] urlpatterns += router.urls + old_version_urlpatterns - - - From 223814f8976fea2410f118b7708ad062d94de5f8 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:30:31 +0800 Subject: [PATCH 367/488] perf: migrate (#9098) Co-authored-by: feng <1304903146@qq.com> --- .../0033_alter_assetpermission_actions.py | 18 +++++++++++++++++ ...0060_alter_applethostdeployment_options.py | 17 ++++++++++++++++ apps/tickets/api/__init__.py | 4 ++-- apps/tickets/api/assignee.py | 0 apps/tickets/api/comment.py | 2 +- apps/tickets/api/flow.py | 1 - apps/tickets/api/relation.py | 8 ++++---- apps/tickets/api/super_ticket.py | 3 +-- apps/tickets/api/ticket.py | 14 ++++++------- apps/tickets/const.py | 20 +++++++++---------- apps/tickets/filters.py | 2 +- apps/tickets/handlers/apply_asset.py | 2 +- ...23_alter_applyassetticket_apply_actions.py | 18 +++++++++++++++++ .../models/ticket/apply_application.py | 13 ++++-------- apps/tickets/models/ticket/general.py | 10 +++++----- apps/tickets/notifications.py | 6 +++--- apps/tickets/serializers/__init__.py | 2 +- apps/tickets/serializers/comment.py | 6 +++--- apps/tickets/serializers/flow.py | 3 ++- apps/tickets/serializers/ticket/__init__.py | 4 ++-- .../tickets/serializers/ticket/apply_asset.py | 4 ++-- apps/tickets/serializers/ticket/ticket.py | 6 +++--- apps/tickets/urls/api_urls.py | 11 +++++----- apps/tickets/views/approve.py | 8 ++++---- 24 files changed, 114 insertions(+), 68 deletions(-) create mode 100644 apps/perms/migrations/0033_alter_assetpermission_actions.py create mode 100644 apps/terminal/migrations/0060_alter_applethostdeployment_options.py delete mode 100644 apps/tickets/api/assignee.py create mode 100644 apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py diff --git a/apps/perms/migrations/0033_alter_assetpermission_actions.py b/apps/perms/migrations/0033_alter_assetpermission_actions.py new file mode 100644 index 000000000..cfa39f6e3 --- /dev/null +++ b/apps/perms/migrations/0033_alter_assetpermission_actions.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-18 02:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0032_auto_20221111_1919'), + ] + + operations = [ + migrations.AlterField( + model_name='assetpermission', + name='actions', + field=models.IntegerField(default=1, verbose_name='Actions'), + ), + ] diff --git a/apps/terminal/migrations/0060_alter_applethostdeployment_options.py b/apps/terminal/migrations/0060_alter_applethostdeployment_options.py new file mode 100644 index 000000000..c38e2ba29 --- /dev/null +++ b/apps/terminal/migrations/0060_alter_applethostdeployment_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.14 on 2022-11-18 02:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0059_applethostdeployment_task'), + ] + + operations = [ + migrations.AlterModelOptions( + name='applethostdeployment', + options={'ordering': ('-date_start',)}, + ), + ] diff --git a/apps/tickets/api/__init__.py b/apps/tickets/api/__init__.py index bec6f21d1..645133d8e 100644 --- a/apps/tickets/api/__init__.py +++ b/apps/tickets/api/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from .ticket import * from .flow import * +from .ticket import * from .comment import * -from .super_ticket import * from .relation import * +from .super_ticket import * diff --git a/apps/tickets/api/assignee.py b/apps/tickets/api/assignee.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/tickets/api/comment.py b/apps/tickets/api/comment.py index 1515f7c9b..e9eaf4ac3 100644 --- a/apps/tickets/api/comment.py +++ b/apps/tickets/api/comment.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # - from rest_framework import viewsets, mixins + from common.exceptions import JMSException from common.utils import lazyproperty from rbac.permissions import RBACPermission diff --git a/apps/tickets/api/flow.py b/apps/tickets/api/flow.py index b45479187..303af6d3f 100644 --- a/apps/tickets/api/flow.py +++ b/apps/tickets/api/flow.py @@ -9,7 +9,6 @@ __all__ = ['TicketFlowViewSet'] class TicketFlowViewSet(JMSBulkModelViewSet): serializer_class = serializers.TicketFlowSerializer - filterset_fields = ['id', 'type'] search_fields = ['id', 'type'] diff --git a/apps/tickets/api/relation.py b/apps/tickets/api/relation.py index 5061e6c00..c442c04ab 100644 --- a/apps/tickets/api/relation.py +++ b/apps/tickets/api/relation.py @@ -1,13 +1,13 @@ -from rest_framework.mixins import CreateModelMixin from rest_framework import views -from rest_framework.response import Response from rest_framework import status +from rest_framework.response import Response +from rest_framework.mixins import CreateModelMixin +from orgs.utils import tmp_to_root_org from common.drf.api import JMSGenericViewSet +from terminal.serializers import SessionSerializer from tickets.models import TicketSession from tickets.serializers import TicketSessionRelationSerializer -from terminal.serializers import SessionSerializer -from orgs.utils import tmp_to_root_org class TicketSessionRelationViewSet(CreateModelMixin, JMSGenericViewSet): diff --git a/apps/tickets/api/super_ticket.py b/apps/tickets/api/super_ticket.py index 32c4a56c0..ebe4c9142 100644 --- a/apps/tickets/api/super_ticket.py +++ b/apps/tickets/api/super_ticket.py @@ -1,9 +1,8 @@ from rest_framework.generics import RetrieveDestroyAPIView from orgs.utils import tmp_to_root_org -from ..serializers import SuperTicketSerializer from ..models import Ticket - +from ..serializers import SuperTicketSerializer __all__ = ['SuperTicketStatusAPI'] diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index a256c51b5..8cf58bd6e 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -2,13 +2,13 @@ # from rest_framework import viewsets from rest_framework.decorators import action -from rest_framework.exceptions import MethodNotAllowed from rest_framework.response import Response +from rest_framework.exceptions import MethodNotAllowed -from common.const.http import POST, PUT, PATCH -from common.mixins.api import CommonApiMixin from orgs.utils import tmp_to_root_org from rbac.permissions import RBACPermission +from common.mixins.api import CommonApiMixin +from common.const.http import POST, PUT, PATCH from tickets import filters from tickets import serializers from tickets.models import ( @@ -94,9 +94,9 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): class ApplyAssetTicketViewSet(TicketViewSet): - serializer_class = serializers.ApplyAssetSerializer model = ApplyAssetTicket filterset_class = filters.ApplyAssetTicketFilter + serializer_class = serializers.ApplyAssetSerializer serializer_classes = { 'open': serializers.ApplyAssetSerializer, 'approve': serializers.ApproveAssetSerializer @@ -104,18 +104,18 @@ class ApplyAssetTicketViewSet(TicketViewSet): class ApplyLoginTicketViewSet(TicketViewSet): - serializer_class = serializers.LoginConfirmSerializer model = ApplyLoginTicket filterset_class = filters.ApplyLoginTicketFilter + serializer_class = serializers.LoginConfirmSerializer class ApplyLoginAssetTicketViewSet(TicketViewSet): - serializer_class = serializers.LoginAssetConfirmSerializer model = ApplyLoginAssetTicket filterset_class = filters.ApplyLoginAssetTicketFilter + serializer_class = serializers.LoginAssetConfirmSerializer class ApplyCommandTicketViewSet(TicketViewSet): - serializer_class = serializers.ApplyCommandConfirmSerializer model = ApplyCommandTicket filterset_class = filters.ApplyCommandTicketFilter + serializer_class = serializers.ApplyCommandConfirmSerializer diff --git a/apps/tickets/const.py b/apps/tickets/const.py index f52c74930..ccd044bbe 100644 --- a/apps/tickets/const.py +++ b/apps/tickets/const.py @@ -6,18 +6,18 @@ TICKET_DETAIL_URL = '/ui/#/tickets/tickets/{id}?type={type}' class TicketType(TextChoices): general = 'general', _("General") - login_confirm = 'login_confirm', _("Login confirm") apply_asset = 'apply_asset', _('Apply for asset') - login_asset_confirm = 'login_asset_confirm', _('Login asset confirm') + login_confirm = 'login_confirm', _("Login confirm") command_confirm = 'command_confirm', _('Command confirm') + login_asset_confirm = 'login_asset_confirm', _('Login asset confirm') class TicketState(TextChoices): pending = 'pending', _('Open') - approved = 'approved', _('Approved') - rejected = 'rejected', _('Rejected') closed = 'closed', _("Cancel") reopen = 'reopen', _("Reopen") + approved = 'approved', _('Approved') + rejected = 'rejected', _('Rejected') class TicketStatus(TextChoices): @@ -27,23 +27,23 @@ class TicketStatus(TextChoices): class StepState(TextChoices): pending = 'pending', _('Pending') - approved = 'approved', _('Approved') - rejected = 'rejected', _('Rejected') closed = 'closed', _("Closed") reopen = 'reopen', _("Reopen") + approved = 'approved', _('Approved') + rejected = 'rejected', _('Rejected') class StepStatus(TextChoices): - pending = 'pending', _('Pending') active = 'active', _('Active') closed = 'closed', _("Closed") + pending = 'pending', _('Pending') class TicketAction(TextChoices): open = 'open', _("Open") close = 'close', _("Close") - approve = 'approve', _('Approve') reject = 'reject', _('Reject') + approve = 'approve', _('Approve') class TicketLevel(IntegerChoices): @@ -52,7 +52,7 @@ class TicketLevel(IntegerChoices): class TicketApprovalStrategy(TextChoices): - super_admin = 'super_admin', _("Super admin") org_admin = 'org_admin', _("Org admin") - super_org_admin = 'super_org_admin', _("Super admin and org admin") custom_user = 'custom_user', _("Custom user") + super_admin = 'super_admin', _("Super admin") + super_org_admin = 'super_org_admin', _("Super admin and org admin") diff --git a/apps/tickets/filters.py b/apps/tickets/filters.py index 536f06b56..e7cd5944c 100644 --- a/apps/tickets/filters.py +++ b/apps/tickets/filters.py @@ -1,5 +1,5 @@ -from django_filters import rest_framework as filters from django.db.models import Subquery, OuterRef +from django_filters import rest_framework as filters from common.drf.filters import BaseFilterSet diff --git a/apps/tickets/handlers/apply_asset.py b/apps/tickets/handlers/apply_asset.py index 2ad50b6df..f2b2ee842 100644 --- a/apps/tickets/handlers/apply_asset.py +++ b/apps/tickets/handlers/apply_asset.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext as _ -from perms.models import AssetPermission from orgs.utils import tmp_to_org +from perms.models import AssetPermission from tickets.models import ApplyAssetTicket from .base import BaseHandler diff --git a/apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py b/apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py new file mode 100644 index 000000000..2c4224c80 --- /dev/null +++ b/apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-18 03:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0022_alter_applyassetticket_apply_actions'), + ] + + operations = [ + migrations.AlterField( + model_name='applyassetticket', + name='apply_actions', + field=models.IntegerField(default=31, verbose_name='Actions'), + ), + ] diff --git a/apps/tickets/models/ticket/apply_application.py b/apps/tickets/models/ticket/apply_application.py index 115d45a9f..3c1f4b6f7 100644 --- a/apps/tickets/models/ticket/apply_application.py +++ b/apps/tickets/models/ticket/apply_application.py @@ -1,7 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from perms.models import Action from .general import Ticket __all__ = ['ApplyApplicationTicket'] @@ -23,14 +22,10 @@ class ApplyApplicationTicket(Ticket): 'assets.SystemUser', verbose_name=_('Apply system users'), ) apply_actions = models.IntegerField( - choices=Action.DB_CHOICES, default=Action.ALL, verbose_name=_('Actions') + choices=[ + (255, 'All'), (1, 'Connect'), (2, 'Upload file'), (4, 'Download file'), (6, 'Upload download'), + (8, 'Clipboard copy'), (16, 'Clipboard paste'), (24, 'Clipboard copy paste') + ], default=255, verbose_name=_('Actions') ) apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) - - @property - def apply_actions_display(self): - return Action.value_to_choices_display(self.apply_actions) - - def get_apply_actions_display(self): - return ', '.join(self.apply_actions_display) diff --git a/apps/tickets/models/ticket/general.py b/apps/tickets/models/ticket/general.py index 605990040..271b87166 100644 --- a/apps/tickets/models/ticket/general.py +++ b/apps/tickets/models/ticket/general.py @@ -5,23 +5,23 @@ from typing import Callable from django.db import models from django.db.models import Q -from django.utils.translation import ugettext_lazy as _ +from django.forms import model_to_dict from django.db.utils import IntegrityError from django.db.models.fields import related -from django.forms import model_to_dict +from django.utils.translation import ugettext_lazy as _ +from orgs.utils import tmp_to_org +from orgs.models import Organization from common.exceptions import JMSException from common.utils.timezone import as_current_tz from common.mixins.models import CommonModelMixin from common.db.encoder import ModelJSONFieldEncoder -from orgs.models import Organization -from orgs.utils import tmp_to_org from tickets.const import ( TicketType, TicketStatus, TicketState, TicketLevel, StepState, StepStatus ) -from tickets.handlers import get_ticket_handler from tickets.errors import AlreadyClosed +from tickets.handlers import get_ticket_handler from ..flow import TicketFlow __all__ = [ diff --git a/apps/tickets/notifications.py b/apps/tickets/notifications.py index 26997b0dc..7e971f4cf 100644 --- a/apps/tickets/notifications.py +++ b/apps/tickets/notifications.py @@ -1,11 +1,11 @@ -from urllib.parse import urljoin import json +from urllib.parse import urljoin from django.conf import settings from django.core.cache import cache from django.shortcuts import reverse -from django.template.loader import render_to_string from django.forms import model_to_dict +from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from notifications.notifications import UserMessage @@ -94,9 +94,9 @@ class BaseTicketMessage(UserMessage): class TicketAppliedToAssigneeMessage(BaseTicketMessage): def __init__(self, user, ticket): + self._token = None self.ticket = ticket super().__init__(user) - self._token = None @property def token(self): diff --git a/apps/tickets/serializers/__init__.py b/apps/tickets/serializers/__init__.py index 26a4b9aa6..645133d8e 100644 --- a/apps/tickets/serializers/__init__.py +++ b/apps/tickets/serializers/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -from .ticket import * from .flow import * +from .ticket import * from .comment import * from .relation import * from .super_ticket import * diff --git a/apps/tickets/serializers/comment.py b/apps/tickets/serializers/comment.py index 6cb8ab9d5..6fa9fa567 100644 --- a/apps/tickets/serializers/comment.py +++ b/apps/tickets/serializers/comment.py @@ -1,6 +1,7 @@ from rest_framework import serializers -from ..models import Comment + from common.drf.fields import ReadableHiddenField +from ..models import Comment __all__ = ['CommentSerializer'] @@ -23,8 +24,7 @@ class CommentSerializer(serializers.ModelSerializer): model = Comment fields_mini = ['id'] fields_small = fields_mini + [ - 'body', 'user_display', - 'date_created', 'date_updated' + 'body', 'user_display', 'date_created', 'date_updated' ] fields_fk = ['ticket', 'user', ] fields = fields_small + fields_fk diff --git a/apps/tickets/serializers/flow.py b/apps/tickets/serializers/flow.py index e949fa8d6..f48e16501 100644 --- a/apps/tickets/serializers/flow.py +++ b/apps/tickets/serializers/flow.py @@ -1,6 +1,7 @@ +from rest_framework import serializers from django.db.transaction import atomic from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers + from orgs.models import Organization from orgs.utils import get_current_org_id diff --git a/apps/tickets/serializers/ticket/__init__.py b/apps/tickets/serializers/ticket/__init__.py index 7b1bdfe13..73fc3b122 100644 --- a/apps/tickets/serializers/ticket/__init__.py +++ b/apps/tickets/serializers/ticket/__init__.py @@ -1,6 +1,6 @@ +from .common import * from .ticket import * from .apply_asset import * from .login_confirm import * -from .login_asset_confirm import * from .command_confirm import * -from .common import * +from .login_asset_confirm import * diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 4e7fd0f6c..cc4b6aa9a 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -1,10 +1,10 @@ -from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ from assets.models import Asset, Node -from common.drf.fields import ObjectRelatedField from perms.models import AssetPermission from perms.serializers.permission import ActionChoicesField +from common.drf.fields import ObjectRelatedField from tickets.models import ApplyAssetTicket from .common import BaseApplyAssetSerializer from .ticket import TicketApplySerializer diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index 7bb168791..dbdf89cc0 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ from common.drf.fields import LabeledChoiceField -from orgs.mixins.serializers import OrgResourceModelSerializerMixin from orgs.models import Organization -from tickets.const import TicketType, TicketStatus, TicketState +from orgs.mixins.serializers import OrgResourceModelSerializerMixin from tickets.models import Ticket, TicketFlow +from tickets.const import TicketType, TicketStatus, TicketState __all__ = [ 'TicketApplySerializer', 'TicketApproveSerializer', 'TicketSerializer', diff --git a/apps/tickets/urls/api_urls.py b/apps/tickets/urls/api_urls.py index 6602e207c..9cc72d815 100644 --- a/apps/tickets/urls/api_urls.py +++ b/apps/tickets/urls/api_urls.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # from django.urls import path - from rest_framework_bulk.routes import BulkRouter from .. import api @@ -10,16 +9,16 @@ app_name = 'tickets' router = BulkRouter() router.register('tickets', api.TicketViewSet, 'ticket') -router.register('apply-asset-tickets', api.ApplyAssetTicketViewSet, 'apply-asset-ticket') -router.register('apply-login-tickets', api.ApplyLoginTicketViewSet, 'apply-login-ticket') -router.register('apply-login-asset-tickets', api.ApplyLoginAssetTicketViewSet, 'apply-login-asset-ticket') -router.register('apply-command-tickets', api.ApplyCommandTicketViewSet, 'apply-command-ticket') router.register('flows', api.TicketFlowViewSet, 'flows') router.register('comments', api.CommentViewSet, 'comment') +router.register('apply-asset-tickets', api.ApplyAssetTicketViewSet, 'apply-asset-ticket') +router.register('apply-login-tickets', api.ApplyLoginTicketViewSet, 'apply-login-ticket') +router.register('apply-command-tickets', api.ApplyCommandTicketViewSet, 'apply-command-ticket') +router.register('apply-login-asset-tickets', api.ApplyLoginAssetTicketViewSet, 'apply-login-asset-ticket') router.register('ticket-session-relation', api.TicketSessionRelationViewSet, 'ticket-session-relation') urlpatterns = [ - path('tickets//session/', api.TicketSessionApi.as_view(), name='ticket-sesion'), + path('tickets//session/', api.TicketSessionApi.as_view(), name='ticket-session'), path('super-tickets//status/', api.SuperTicketStatusAPI.as_view(), name='super-ticket-status'), ] urlpatterns += router.urls diff --git a/apps/tickets/views/approve.py b/apps/tickets/views/approve.py index d742437f8..a3a265005 100644 --- a/apps/tickets/views/approve.py +++ b/apps/tickets/views/approve.py @@ -2,18 +2,18 @@ # from __future__ import unicode_literals -from django.views.generic.base import TemplateView -from django.shortcuts import redirect, reverse from django.core.cache import cache +from django.shortcuts import redirect, reverse +from django.views.generic.base import TemplateView from django.utils.translation import ugettext as _ from orgs.utils import tmp_to_root_org +from tickets.const import TicketType +from tickets.errors import AlreadyClosed from tickets.models import ( Ticket, ApplyAssetTicket, ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket ) -from tickets.const import TicketType -from tickets.errors import AlreadyClosed from common.utils import get_logger, FlashMessageUtil logger = get_logger(__name__) From d0de36358ca9d335df3f5f69d676a36d22646452 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 18 Nov 2022 18:46:48 +0800 Subject: [PATCH 368/488] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ops/migrations/0033_auto_20221118_1431.py | 28 ++++++++++++ apps/ops/models/job.py | 45 ++++++++++++++++++- apps/ops/serializers/job.py | 12 ++--- apps/ops/tasks.py | 2 +- 4 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 apps/ops/migrations/0033_auto_20221118_1431.py diff --git a/apps/ops/migrations/0033_auto_20221118_1431.py b/apps/ops/migrations/0033_auto_20221118_1431.py new file mode 100644 index 000000000..70518eee6 --- /dev/null +++ b/apps/ops/migrations/0033_auto_20221118_1431.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.14 on 2022-11-18 06:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0032_auto_20221117_1848'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='crontab', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform'), + ), + migrations.AddField( + model_name='job', + name='interval', + field=models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform'), + ), + migrations.AddField( + model_name='job', + name='is_periodic', + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 8897d6107..d5542970d 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -9,14 +9,16 @@ from django.utils.translation import gettext_lazy as _ from django.utils import timezone from celery import current_task +from common.const.choices import Trigger from common.db.models import BaseCreateUpdateModel __all__ = ["Job", "JobExecution"] from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner +from ops.mixin import PeriodTaskModelMixin -class Job(BaseCreateUpdateModel): +class Job(BaseCreateUpdateModel, PeriodTaskModelMixin): class Types(models.TextChoices): adhoc = 'adhoc', _('Adhoc') playbook = 'playbook', _('Playbook') @@ -48,6 +50,42 @@ class Job(BaseCreateUpdateModel): parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define')) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) + @property + def last_execution(self): + return self.executions.last() + + @property + def date_last_run(self): + return self.last_execution.date_created if self.last_execution else None + + @property + def summary(self): + summary = { + "total": 0, + "success": 0, + } + for execution in self.executions.all(): + summary["total"] += 1 + if execution.is_success: + summary["success"] += 1 + return summary + + @property + def average_time_cost(self): + total_cost = 0 + finished_count = self.executions.filter(status__in=['success', 'failed']).count() + for execution in self.executions.filter(status__in=['success', 'failed']).all(): + total_cost += execution.time_cost + return total_cost / finished_count if finished_count else 0 + + def get_register_task(self): + from ..tasks import run_ops_job + name = "run_ops_job_period_{}".format(str(self.id)[:8]) + task = run_ops_job.name + args = (str(self.id),) + kwargs = {} + return name, task, args, kwargs + @property def inventory(self): return JMSInventory(self.assets.all(), self.runas_policy, self.runas) @@ -72,7 +110,10 @@ class JobExecution(BaseCreateUpdateModel): def get_runner(self): inv = self.job.inventory inv.write_to_file(self.inventory_path) - extra_vars = json.loads(self.parameters) + if isinstance(self.parameters, str): + extra_vars = json.loads(self.parameters) + else: + extra_vars = {} if self.job.type == 'adhoc': runner = AdHocRunner( diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 2771e2a7f..389b92ce2 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -2,24 +2,26 @@ from django.db import transaction from rest_framework import serializers from common.drf.fields import ReadableHiddenField +from ops.mixin import PeriodTaskSerializerMixin from ops.models import Job, JobExecution _all_ = [] -class JobSerializer(serializers.ModelSerializer): +class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin): owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) class Meta: model = Job - fields = [ - "id", "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", + read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost"] + fields = read_only_fields + [ + "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", "parameters_define", "timeout", "chdir", "comment", - "date_created", - "date_updated" + "summary", + "is_periodic", "interval", "crontab" ] diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index c350cdb6b..841f759ff 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -25,7 +25,7 @@ logger = get_logger(__file__) @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) -def run_ops_job(job_id, **kwargs): +def run_ops_job(job_id): job = get_object_or_none(Job, id=job_id) execution = job.create_execution() try: From fc1b6c9db222165a942df3d27f26a8de929accff Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 18 Nov 2022 19:29:19 +0800 Subject: [PATCH 369/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20endpoint?= =?UTF-8?q?=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/component/endpoint.py | 7 +--- apps/terminal/const.py | 49 +++++++++++++------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/terminal/api/component/endpoint.py b/apps/terminal/api/component/endpoint.py index d406b51f7..f5252c85f 100644 --- a/apps/terminal/api/component/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -23,8 +23,7 @@ class SmartEndpointViewMixin: target_instance: None target_protocol: None - @action(methods=['get'], detail=False, permission_classes=[IsValidUserOrConnectionToken], - url_path='smart') + @action(methods=['get'], detail=False, permission_classes=[IsValidUserOrConnectionToken]) def smart(self, request, *args, **kwargs): self.target_instance = self.get_target_instance() self.target_protocol = self.get_target_protocol() @@ -57,12 +56,12 @@ class SmartEndpointViewMixin: asset_id = request.GET.get('asset_id') session_id = request.GET.get('session_id') token_id = request.GET.get('token') + if token_id: from authentication.models import ConnectionToken token = ConnectionToken.objects.filter(id=token_id).first() if token and token.asset: asset_id = token.asset.id - if asset_id: pk, model = asset_id, Asset elif session_id: @@ -77,8 +76,6 @@ class SmartEndpointViewMixin: def get_target_protocol(self): protocol = None - if isinstance(self.target_instance, Application) and self.target_instance.is_type(Application.APP_TYPE.oracle): - protocol = self.target_instance.get_target_protocol_for_oracle() if not protocol: protocol = self.request.GET.get('protocol') return protocol diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 288975866..3f653aaf7 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -51,12 +51,12 @@ class HttpMethod(TextChoices): class NativeClient(TextChoices): # Koko - ssh = 'ssh', 'ssh' + ssh = 'ssh', 'SSH' putty = 'putty', 'PuTTY' xshell = 'xshell', 'Xshell' # Magnus - mysql = 'mysql', 'MySQL Client' + mysql = 'mysql', 'mysql' psql = 'psql', 'psql' sqlplus = 'sqlplus', 'sqlplus' redis = 'redis-cli', 'redis-cli' @@ -82,7 +82,7 @@ class NativeClient(TextChoices): return clients @classmethod - def get_native_methods(cls, os='windows'): + def get_methods(cls, os='windows'): clients_map = cls.get_native_clients() methods = defaultdict(list) @@ -118,9 +118,9 @@ class NativeClient(TextChoices): return command -class RemoteAppMethod: +class AppletMethod: @classmethod - def get_remote_app_methods(cls): + def get_methods(cls): from .models import Applet applets = Applet.objects.all() methods = defaultdict(list) @@ -130,7 +130,6 @@ class RemoteAppMethod: 'value': applet.name, 'label': applet.display_name, 'icon': applet.icon, - 'type': 'remote_app', }) return methods @@ -153,21 +152,21 @@ class TerminalType(TextChoices): @classmethod def protocols(cls): - return { + protocols = { cls.koko: { - 'http_method': HttpMethod.web_cli, + 'web_method': HttpMethod.web_cli, 'listen': [Protocol.ssh, Protocol.http], 'support': [ Protocol.ssh, Protocol.telnet, Protocol.mysql, Protocol.postgresql, Protocol.oracle, Protocol.sqlserver, Protocol.mariadb, Protocol.redis, - Protocol.mongodb, + Protocol.mongodb, Protocol.k8s, ], 'match': 'm2m' }, cls.omnidb: { - 'http_method': HttpMethod.web_gui, + 'web_method': HttpMethod.web_gui, 'listen': [Protocol.http], 'support': [ Protocol.mysql, Protocol.postgresql, Protocol.oracle, @@ -176,7 +175,7 @@ class TerminalType(TextChoices): 'match': 'm2m' }, cls.lion: { - 'http_method': HttpMethod.web_gui, + 'web_method': HttpMethod.web_gui, 'listen': [Protocol.http], 'support': [Protocol.rdp, Protocol.vnc], 'match': 'm2m' @@ -193,17 +192,17 @@ class TerminalType(TextChoices): 'listen': [Protocol.rdp], 'support': [Protocol.rdp], 'match': 'map' - } + }, } + return protocols @classmethod def get_protocols_connect_methods(cls, os): methods = defaultdict(list) - native_methods = NativeClient.get_native_methods(os) - remote_app_methods = RemoteAppMethod.get_remote_app_methods() + native_methods = NativeClient.get_methods(os) + applet_methods = AppletMethod.get_methods() for component, component_protocol in cls.protocols().items(): - component_methods = defaultdict(list) support = component_protocol['support'] for protocol in support: @@ -214,19 +213,23 @@ class TerminalType(TextChoices): for listen_protocol in listen: if listen_protocol == Protocol.http: - web_protocol = component_protocol['http_method'] - component_methods[protocol.value].append({ + web_protocol = component_protocol['web_method'] + methods[protocol.value].append({ 'value': web_protocol.value, 'label': web_protocol.label, 'type': 'web', + 'component': component.value, }) # Native method - component_methods[protocol.value].extend(native_methods[listen_protocol]) - component_methods[protocol.value].extend(remote_app_methods[listen_protocol]) + methods[protocol.value].extend([ + {'component': component.value, 'type': 'native', **method} + for method in native_methods[listen_protocol] + ]) - for protocol, _methods in component_methods.items(): - for method in _methods: - method['component'] = component.value - methods[protocol].extend(_methods) + for protocol, applet_methods in applet_methods.items(): + for method in applet_methods: + method['type'] = 'applet' + method['component'] = cls.tinker.value + methods[protocol].extend(applet_methods) return methods From 94526e44f1de7d6d73c8c2220ddb21f6c49ae7d9 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 21 Nov 2022 15:18:09 +0800 Subject: [PATCH 370/488] perf: change secret timedelta --- apps/assets/models/automations/change_secret.py | 6 ++++++ apps/assets/serializers/automations/change_secret.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/assets/models/automations/change_secret.py b/apps/assets/models/automations/change_secret.py index c22b64f51..ecc0e98d4 100644 --- a/apps/assets/models/automations/change_secret.py +++ b/apps/assets/models/automations/change_secret.py @@ -65,3 +65,9 @@ class ChangeSecretRecord(JMSBaseModel): def __str__(self): return self.account.__str__() + + @property + def timedelta(self): + if self.date_started and self.date_finished: + return self.date_finished - self.date_started + return None diff --git a/apps/assets/serializers/automations/change_secret.py b/apps/assets/serializers/automations/change_secret.py index 3b9137bc4..85fad5855 100644 --- a/apps/assets/serializers/automations/change_secret.py +++ b/apps/assets/serializers/automations/change_secret.py @@ -93,8 +93,8 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer): class Meta: model = ChangeSecretRecord fields = [ - 'id', 'asset', 'account', 'date_started', - 'date_finished', 'is_success', 'error', 'execution', + 'id', 'asset', 'account', 'date_started', 'date_finished', + 'timedelta', 'is_success', 'error', 'execution', ] read_only_fields = fields From f39a3a34e40b9cc7fa72ed7036d6d00a454fb34c Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 21 Nov 2022 16:23:32 +0800 Subject: [PATCH 371/488] perf: change secret ignore secret type --- .../assets/serializers/automations/change_secret.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/assets/serializers/automations/change_secret.py b/apps/assets/serializers/automations/change_secret.py index 85fad5855..b0149334d 100644 --- a/apps/assets/serializers/automations/change_secret.py +++ b/apps/assets/serializers/automations/change_secret.py @@ -42,6 +42,19 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ )}, }} + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_secret_type_choices() + + def set_secret_type_choices(self): + secret_type = self.fields.get('secret_type') + if not secret_type: + return + choices = secret_type._choices + choices.pop(SecretType.ACCESS_KEY, None) + choices.pop(SecretType.TOKEN, None) + secret_type._choices = choices + def validate_password_rules(self, password_rules): secret_type = self.initial_secret_type if secret_type != SecretType.PASSWORD: From 4b26fb3e6e1f5934edd58e94102b39ab9e6b7e91 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 21 Nov 2022 19:54:00 +0800 Subject: [PATCH 372/488] fix: jms upgrade_db bug --- apps/ops/signal_handlers.py | 26 ++++++++++++++------------ apps/orgs/signal_handlers/common.py | 3 ++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/ops/signal_handlers.py b/apps/ops/signal_handlers.py index dd49a4d94..965bd494c 100644 --- a/apps/ops/signal_handlers.py +++ b/apps/ops/signal_handlers.py @@ -1,14 +1,16 @@ import ast +from celery import signals from django.db import transaction +from django.core.cache import cache from django.dispatch import receiver +from django.db.utils import ProgrammingError from django.utils import translation, timezone from django.utils.translation import gettext as _ -from django.core.cache import cache -from celery import signals, current_app -from common.db.utils import close_old_connections, get_logger from common.signals import django_ready +from common.db.utils import close_old_connections, get_logger + from .celery import app from .models import CeleryTaskExecution, CeleryTask @@ -23,15 +25,15 @@ def sync_registered_tasks(*args, **kwargs): with transaction.atomic(): try: db_tasks = CeleryTask.objects.all() - except Exception as e: - return - celery_task_names = [key for key in app.tasks] - db_task_names = db_tasks.values_list('name', flat=True) + celery_task_names = [key for key in app.tasks] + db_task_names = db_tasks.values_list('name', flat=True) - db_tasks.exclude(name__in=celery_task_names).delete() - not_in_db_tasks = set(celery_task_names) - set(db_task_names) - tasks_to_create = [CeleryTask(name=name) for name in not_in_db_tasks] - CeleryTask.objects.bulk_create(tasks_to_create) + db_tasks.exclude(name__in=celery_task_names).delete() + not_in_db_tasks = set(celery_task_names) - set(db_task_names) + tasks_to_create = [CeleryTask(name=name) for name in not_in_db_tasks] + CeleryTask.objects.bulk_create(tasks_to_create) + except ProgrammingError: + pass @signals.before_task_publish.connect @@ -45,7 +47,7 @@ def before_task_publish(headers=None, **kwargs): @signals.task_prerun.connect def on_celery_task_pre_run(task_id='', **kwargs): # 更新状态 - CeleryTaskExecution.objects.filter(id=task_id)\ + CeleryTaskExecution.objects.filter(id=task_id) \ .update(state='RUNNING', date_start=timezone.now()) # 关闭之前的数据库连接 close_old_connections() diff --git a/apps/orgs/signal_handlers/common.py b/apps/orgs/signal_handlers/common.py index 2136b28a0..666f68fa1 100644 --- a/apps/orgs/signal_handlers/common.py +++ b/apps/orgs/signal_handlers/common.py @@ -7,6 +7,7 @@ from functools import partial import django.db.utils from django.dispatch import receiver from django.conf import settings +from django.db.utils import ProgrammingError, OperationalError from django.utils.functional import LazyObject from django.db.models.signals import post_save, pre_delete, m2m_changed @@ -48,7 +49,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs): if settings.DEBUG: try: set_to_default_org() - except django.db.utils.OperationalError: + except (ProgrammingError, OperationalError): pass def keep_subscribe_org_mapping(): From 436cb7b6e142304e5b1435753af8948b6de694c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Tue, 22 Nov 2022 10:15:55 +0800 Subject: [PATCH 373/488] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Dockerfile?= =?UTF-8?q?.loong64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 +- Dockerfile.loong64 | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.loong64 diff --git a/Dockerfile b/Dockerfile index 2f65985b8..28dedbba9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,10 +57,11 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ && apt-get -y install --no-install-recommends ${TOOLS} \ && mkdir -p /root/.ssh/ \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ - && sed -i "s@# alias l@alias l@g" ~/.bashrc \ && echo "set mouse-=a" > ~/.vimrc \ && echo "no" | dpkg-reconfigure dash \ && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ + && sed -i "s@# export @export @g" ~/.bashrc \ + && sed -i "s@# alias @alias @g" ~/.bashrc \ && rm -rf /var/lib/apt/lists/* ARG DOWNLOAD_URL=https://download.jumpserver.org diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 new file mode 100644 index 000000000..580792776 --- /dev/null +++ b/Dockerfile.loong64 @@ -0,0 +1,96 @@ +FROM python:3.8-slim as stage-build +ARG TARGETARCH + +ARG VERSION +ENV VERSION=$VERSION + +WORKDIR /opt/jumpserver +ADD . . +RUN cd utils && bash -ixeu build.sh + +FROM python:3.8-slim +ARG TARGETARCH +MAINTAINER JumpServer Team + +ARG BUILD_DEPENDENCIES=" \ + g++ \ + make \ + pkg-config" + +ARG DEPENDENCIES=" \ + default-libmysqlclient-dev \ + freetds-dev \ + libpq-dev \ + libffi-dev \ + libjpeg-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxmlsec1-dev \ + libxmlsec1-openssl \ + libaio-dev \ + openssh-client \ + sshpass" + +ARG TOOLS=" \ + ca-certificates \ + curl \ + default-mysql-client \ + iputils-ping \ + locales \ + netcat \ + redis-server \ + telnet \ + vim \ + unzip \ + wget" + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core \ + set -ex \ + && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && apt-get update \ + && apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \ + && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ + && apt-get -y install --no-install-recommends ${TOOLS} \ + && mkdir -p /root/.ssh/ \ + && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config \ + && echo "set mouse-=a" > ~/.vimrc \ + && echo "no" | dpkg-reconfigure dash \ + && echo "zh_CN.UTF-8" | dpkg-reconfigure locales \ + && sed -i "s@# export @export @g" ~/.bashrc \ + && sed -i "s@# alias @alias @g" ~/.bashrc \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp/build +COPY ./requirements ./requirements + +ARG PIP_MIRROR=https://pypi.douban.com/simple +ENV PIP_MIRROR=$PIP_MIRROR +ARG PIP_JMS_MIRROR=https://pypi.douban.com/simple +ENV PIP_JMS_MIRROR=$PIP_JMS_MIRROR + +RUN --mount=type=cache,target=/root/.cache/pip \ + set -ex \ + && pip config set global.index-url ${PIP_MIRROR} \ + && pip install --upgrade pip \ + && pip install --upgrade setuptools wheel \ + && pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-36.0.1-cp38-cp38-linux_loongarch64.whl \ + && pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp38-cp38-linux_loongarch64.whl \ + && pip install $(grep 'PyNaCl' requirements/requirements.txt) \ + && GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true pip install grpcio \ + && pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \ + && pip install -r requirements/requirements.txt + +COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver +RUN echo > /opt/jumpserver/config.yml \ + && rm -rf /tmp/build + +WORKDIR /opt/jumpserver +VOLUME /opt/jumpserver/data +VOLUME /opt/jumpserver/logs + +ENV LANG=zh_CN.UTF-8 + +EXPOSE 8070 +EXPOSE 8080 +ENTRYPOINT ["./entrypoint.sh"] From d543c3efe7c9beedff88083233a4691a37b5ccb1 Mon Sep 17 00:00:00 2001 From: "Jiangjie.Bai" Date: Tue, 22 Nov 2022 11:05:52 +0800 Subject: [PATCH 374/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20favorite-as?= =?UTF-8?q?sets=20Serializer=20=E7=BC=BA=E5=B0=91=20protocols=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/user_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index d95a14f99..1d795d650 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -30,7 +30,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): 'domain', 'platform', "comment", "org_id", "is_active", ] - fields = only_fields + ['category', 'type'] + ['org_name'] + fields = only_fields + ['protocols', 'category', 'type'] + ['org_name'] read_only_fields = fields From 873b81e639dc98d7c55ba608f600341b8c7f6a50 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 22 Nov 2022 11:36:48 +0800 Subject: [PATCH 375/488] perf: ticket migrate --- ...23_alter_applyassetticket_apply_actions.py | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py b/apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py index 2c4224c80..f401fe298 100644 --- a/apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py +++ b/apps/tickets/migrations/0023_alter_applyassetticket_apply_actions.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('tickets', '0022_alter_applyassetticket_apply_actions'), ] @@ -15,4 +14,73 @@ class Migration(migrations.Migration): name='apply_actions', field=models.IntegerField(default=31, verbose_name='Actions'), ), + migrations.AlterField( + model_name='approvalrule', + name='strategy', + field=models.CharField( + choices=[ + ('org_admin', 'Org admin'), ('custom_user', 'Custom user'), + ('super_admin', 'Super admin'), ('super_org_admin', 'Super admin and org admin') + ], default='super_admin', max_length=64, + verbose_name='Approve strategy'), + ), + migrations.AlterField( + model_name='ticket', + name='state', + field=models.CharField( + choices=[ + ('pending', 'Open'), ('closed', 'Cancel'), + ('reopen', 'Reopen'), ('approved', 'Approved'), + ('rejected', 'Rejected') + ], default='pending', max_length=16, verbose_name='State'), + ), + migrations.AlterField( + model_name='ticket', + name='type', + field=models.CharField( + choices=[ + ('general', 'General'), ('apply_asset', 'Apply for asset'), + ('login_confirm', 'Login confirm'), ('command_confirm', 'Command confirm'), + ('login_asset_confirm', 'Login asset confirm') + ], + default='general', max_length=64, verbose_name='Type'), + ), + migrations.AlterField( + model_name='ticketassignee', + name='state', + field=models.CharField( + choices=[ + ('pending', 'Open'), ('closed', 'Cancel'), + ('reopen', 'Reopen'), ('approved', 'Approved'), + ('rejected', 'Rejected') + ], default='pending', max_length=64), + ), + migrations.AlterField( + model_name='ticketflow', + name='type', + field=models.CharField( + choices=[ + ('general', 'General'), ('apply_asset', 'Apply for asset'), + ('login_confirm', 'Login confirm'), ('command_confirm', 'Command confirm'), + ('login_asset_confirm', 'Login asset confirm') + ], + default='general', max_length=64, verbose_name='Type'), + ), + migrations.AlterField( + model_name='ticketstep', + name='state', + field=models.CharField( + choices=[ + ('pending', 'Pending'), ('closed', 'Closed'), + ('reopen', 'Reopen'), ('approved', 'Approved'), + ('rejected', 'Rejected') + ], default='pending', max_length=64, verbose_name='State'), + ), + migrations.AlterField( + model_name='ticketstep', + name='status', + field=models.CharField( + choices=[('active', 'Active'), ('closed', 'Closed'), ('pending', 'Pending')], + default='pending', max_length=16), + ), ] From 1a204618f71ca8d2aaceb7f1341cd8959be86789 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:33:09 +0800 Subject: [PATCH 376/488] [v3] perf: migrate gateway to asset (#8928) * perf: migrate gateway to asset * perf: asset discriminate gateway Co-authored-by: feng626 <1304903146@qq.com> --- apps/assets/api/asset/asset.py | 10 +- apps/assets/api/asset/host.py | 1 - apps/assets/api/domain.py | 24 +- apps/assets/const/__init__.py | 2 + apps/assets/const/host.py | 4 +- .../migrations/0112_gateway_to_asset.py | 73 ++++++ apps/assets/models/asset/common.py | 2 +- apps/assets/models/asset/host.py | 9 +- apps/assets/models/base.py | 4 +- apps/assets/models/domain.py | 238 ++++++++++-------- apps/assets/serializers/domain.py | 25 +- .../serializers/connection_token.py | 6 +- apps/orgs/api.py | 4 +- apps/orgs/caches.py | 4 +- apps/orgs/signal_handlers/cache.py | 3 +- 15 files changed, 258 insertions(+), 151 deletions(-) create mode 100644 apps/assets/migrations/0112_gateway_to_asset.py diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index ad04966e3..f6cc509b3 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -6,13 +6,11 @@ from rest_framework.decorators import action from rest_framework.response import Response from assets import serializers +from assets.models import Asset from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend -from assets.models import Asset, Gateway from assets.tasks import ( - push_accounts_to_assets, - test_assets_connectivity_manual, - update_assets_hardware_info_manual, - verify_accounts_connectivity, + push_accounts_to_assets, test_assets_connectivity_manual, + update_assets_hardware_info_manual, verify_accounts_connectivity, ) from common.drf.filters import BaseFilterSet from common.mixins.api import SuggestionMixin @@ -74,7 +72,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): def gateways(self, *args, **kwargs): asset = self.get_object() if not asset.domain: - gateways = Gateway.objects.none() + gateways = Asset.objects.none() else: gateways = asset.domain.gateways.filter(protocol="ssh") return self.get_paginated_response_from_queryset(gateways) diff --git a/apps/assets/api/asset/host.py b/apps/assets/api/asset/host.py index 3094e15a8..fbc2e997c 100644 --- a/apps/assets/api/asset/host.py +++ b/apps/assets/api/asset/host.py @@ -1,4 +1,3 @@ - from assets.models import Host from assets.serializers import HostSerializer from .asset import AssetViewSet diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 39f2b44f9..bb705322c 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -7,21 +7,20 @@ from rest_framework.serializers import ValidationError from common.utils import get_logger from orgs.mixins.api import OrgBulkModelViewSet -from ..models import Domain, Gateway +from ..models import Domain, Host from .. import serializers - logger = get_logger(__file__) __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"] class DomainViewSet(OrgBulkModelViewSet): model = Domain - filterset_fields = ("name", ) + filterset_fields = ("name",) search_fields = filterset_fields serializer_class = serializers.DomainSerializer ordering_fields = ('name',) - ordering = ('name', ) + ordering = ('name',) def get_serializer_class(self): if self.request.query_params.get('gateway'): @@ -30,21 +29,26 @@ class DomainViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet): - model = Gateway - filterset_fields = ("domain__name", "name", "username", "domain") - search_fields = ("domain__name", "name", "username", ) + filterset_fields = ("domain__name", "name", "domain") + search_fields = ("domain__name",) serializer_class = serializers.GatewaySerializer + def get_queryset(self): + queryset = Host.get_gateway_queryset() + return queryset + class GatewayTestConnectionApi(SingleObjectMixin, APIView): - queryset = Gateway.objects.all() - object = None rbac_perms = { 'POST': 'assets.test_gateway' } + def get_queryset(self): + queryset = Host.get_gateway_queryset() + return queryset + def post(self, request, *args, **kwargs): - self.object = self.get_object(Gateway.objects.all()) + self.object = self.get_object() local_port = self.request.data.get('port') or self.object.port try: local_port = int(local_port) diff --git a/apps/assets/const/__init__.py b/apps/assets/const/__init__.py index 81115b412..bc30f388d 100644 --- a/apps/assets/const/__init__.py +++ b/apps/assets/const/__init__.py @@ -1,3 +1,5 @@ +from .base import * +from .host import * from .types import * from .account import * from .protocol import * diff --git a/apps/assets/const/host.py b/apps/assets/const/host.py index 371ab3688..8be44db6f 100644 --- a/apps/assets/const/host.py +++ b/apps/assets/const/host.py @@ -1,5 +1,7 @@ from .base import BaseType +GATEWAY_NAME = 'Gateway' + class HostTypes(BaseType): LINUX = 'linux', 'Linux' @@ -67,7 +69,7 @@ class HostTypes(BaseType): return { cls.LINUX: [ {'name': 'Linux'}, - {'name': 'Gateway'} + {'name': GATEWAY_NAME} ], cls.UNIX: [ {'name': 'Unix'}, diff --git a/apps/assets/migrations/0112_gateway_to_asset.py b/apps/assets/migrations/0112_gateway_to_asset.py new file mode 100644 index 000000000..da43b3a84 --- /dev/null +++ b/apps/assets/migrations/0112_gateway_to_asset.py @@ -0,0 +1,73 @@ +# Generated by Django 3.2.13 on 2022-09-29 11:03 + +from django.db import migrations +from assets.const.host import GATEWAY_NAME + + +def _create_account_obj(secret, secret_type, gateway, asset, account_model): + return account_model( + asset=asset, + secret=secret, + org_id=gateway.org_id, + secret_type=secret_type, + username=gateway.username, + name=f'{gateway.name}-{secret_type}-{GATEWAY_NAME.lower()}', + ) + + +def migrate_gateway_to_asset(apps, schema_editor): + db_alias = schema_editor.connection.alias + gateway_model = apps.get_model('assets', 'Gateway') + platform_model = apps.get_model('assets', 'Platform') + gateway_platform = platform_model.objects.using(db_alias).get(name=GATEWAY_NAME) + + print('>>> migrate gateway to asset') + asset_dict = {} + host_model = apps.get_model('assets', 'Host') + asset_model = apps.get_model('assets', 'Asset') + protocol_model = apps.get_model('assets', 'Protocol') + gateways = gateway_model.objects.all() + for gateway in gateways: + comment = gateway.comment if gateway.comment else '' + data = { + 'comment': comment, + 'name': f'{gateway.name}-{GATEWAY_NAME.lower()}', + 'address': gateway.ip, + 'domain': gateway.domain, + 'org_id': gateway.org_id, + 'is_active': gateway.is_active, + 'platform': gateway_platform, + } + asset = asset_model.objects.using(db_alias).create(**data) + asset_dict[gateway.id] = asset + protocol_model.objects.using(db_alias).create(name='ssh', port=gateway.port, asset=asset) + hosts = [host_model(asset_ptr=asset) for asset in asset_dict.values()] + host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True) + + print('>>> migrate gateway to account') + accounts = [] + account_model = apps.get_model('assets', 'Account') + for gateway in gateways: + password = gateway.password + private_key = gateway.private_key + asset = asset_dict[gateway.id] + if password: + accounts.append(_create_account_obj( + password, 'password', gateway, asset, account_model + )) + + if private_key: + accounts.append(_create_account_obj( + private_key, 'ssh_key', gateway, asset, account_model + )) + account_model.objects.using(db_alias).bulk_create(accounts) + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0111_alter_automationexecution_status'), + ] + + operations = [ + migrations.RunPython(migrate_gateway_to_asset), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 8ea75bc2a..c9baf8818 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- # -import logging import uuid +import logging from collections import defaultdict from django.db import models diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 4ce4be5c9..46aeed4f3 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -1,6 +1,13 @@ -from assets.const import Category +from assets.const import GATEWAY_NAME from .common import Asset class Host(Asset): pass + + @classmethod + def get_gateway_queryset(cls): + queryset = cls.objects.filter( + platform__name=GATEWAY_NAME + ) + return queryset diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 7920d3798..90fb384e6 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -6,10 +6,10 @@ import sshpubkeys from hashlib import md5 from django.db import models -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.utils import timezone from django.db.models import QuerySet +from django.utils.translation import ugettext_lazy as _ from common.utils import ( ssh_key_string_to_obj, ssh_key_gen, get_logger, diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 4abe8aa68..bf33caed6 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -1,22 +1,24 @@ # -*- coding: utf-8 -*- # -import socket import uuid +import socket import random - -from django.core.cache import cache import paramiko + from django.db import models +from django.core.cache import cache +from django.db.models.query import QuerySet from django.utils.translation import ugettext_lazy as _ -from common.utils import get_logger, lazyproperty from common.db import fields +from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin from .base import BaseAccount +from ..const import SecretType, GATEWAY_NAME logger = get_logger(__file__) -__all__ = ['Domain', 'Gateway'] +__all__ = ['Domain', 'GatewayMixin'] class Domain(OrgModelMixin): @@ -33,12 +35,9 @@ class Domain(OrgModelMixin): def __str__(self): return self.name - def has_gateway(self): - return self.gateway_set.filter(is_active=True).exists() - @lazyproperty def gateways(self): - return self.gateway_set.filter(is_active=True) + return self.assets.filter(platform__name=GATEWAY_NAME, is_active=True) def select_gateway(self): return self.random_gateway() @@ -53,18 +52,141 @@ class Domain(OrgModelMixin): return random.choice(self.gateways) -class Gateway(BaseAccount): - UNCONNECTIVE_KEY_TMPL = 'asset_unconnective_gateway_{}' - UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}' - UNCONNECTIVE_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5 +class GatewayMixin: + id: uuid.UUID + port: int + address: str + accounts: QuerySet + private_key_path: str + private_key_obj: paramiko.RSAKey + UNCONNECTED_KEY_TMPL = 'asset_unconnective_gateway_{}' + UNCONNECTED_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}' + UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5 + def set_unconnected(self): + unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id) + unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id) + unconnected_silence_period = cache.get( + unconnected_silence_period_key, self.UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE + ) + cache.set(unconnected_silence_period_key, unconnected_silence_period * 2) + cache.set(unconnected_key, unconnected_silence_period, unconnected_silence_period) + + def set_connective(self): + unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id) + unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id) + + cache.delete(unconnected_key) + cache.delete(unconnected_silence_period_key) + + def get_is_unconnected(self): + unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id) + return cache.get(unconnected_key, False) + + @property + def is_connective(self): + return not self.get_is_unconnected() + + @is_connective.setter + def is_connective(self, value): + if value: + self.set_connective() + else: + self.set_unconnected() + + def test_connective(self, local_port=None): + # TODO 走ansible runner + if local_port is None: + local_port = self.port + + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + proxy = paramiko.SSHClient() + proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + try: + proxy.connect(self.address, port=self.port, + username=self.username, + password=self.password, + pkey=self.private_key_obj) + except(paramiko.AuthenticationException, + paramiko.BadAuthenticationType, + paramiko.SSHException, + paramiko.ChannelException, + paramiko.ssh_exception.NoValidConnectionsError, + socket.gaierror) as e: + err = str(e) + if err.startswith('[Errno None] Unable to connect to port'): + err = _('Unable to connect to port {port} on {address}') + err = err.format(port=self.port, ip=self.address) + elif err == 'Authentication failed.': + err = _('Authentication failed') + elif err == 'Connect failed': + err = _('Connect failed') + self.is_connective = False + return False, err + + try: + sock = proxy.get_transport().open_channel( + 'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0) + ) + client.connect("127.0.0.1", port=local_port, + username=self.username, + password=self.password, + key_filename=self.private_key_path, + sock=sock, + timeout=5) + except (paramiko.SSHException, + paramiko.ssh_exception.SSHException, + paramiko.ChannelException, + paramiko.AuthenticationException, + TimeoutError) as e: + + err = getattr(e, 'text', str(e)) + if err == 'Connect failed': + err = _('Connect failed') + self.is_connective = False + return False, err + finally: + client.close() + self.is_connective = True + return True, None + + @lazyproperty + def username(self): + account = self.accounts.all().first() + if account: + return account.username + logger.error(f'Gateway {self} has no account') + return '' + + def get_secret(self, secret_type): + account = self.accounts.filter(secret_type=secret_type).first() + if account: + return account.secret + logger.error(f'Gateway {self} has no {secret_type} account') + + @lazyproperty + def password(self): + secret_type = SecretType.PASSWORD + return self.get_secret(secret_type) + + @lazyproperty + def private_key(self): + secret_type = SecretType.SSH_KEY + return self.get_secret(secret_type) + + +class Gateway(BaseAccount): class Protocol(models.TextChoices): ssh = 'ssh', 'SSH' name = models.CharField(max_length=128, verbose_name='Name') ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) port = models.IntegerField(default=22, verbose_name=_('Port')) - protocol = models.CharField(choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")) + protocol = models.CharField( + choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") + ) domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) @@ -85,91 +207,3 @@ class Gateway(BaseAccount): permissions = [ ('test_gateway', _('Test gateway')) ] - - def set_unconnective(self): - unconnective_key = self.UNCONNECTIVE_KEY_TMPL.format(self.id) - unconnective_silence_period_key = self.UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL.format(self.id) - - unconnective_silence_period = cache.get(unconnective_silence_period_key, - self.UNCONNECTIVE_SILENCE_PERIOD_BEGIN_VALUE) - cache.set(unconnective_silence_period_key, unconnective_silence_period * 2) - cache.set(unconnective_key, unconnective_silence_period, unconnective_silence_period) - - def set_connective(self): - unconnective_key = self.UNCONNECTIVE_KEY_TMPL.format(self.id) - unconnective_silence_period_key = self.UNCONNECTIVE_SILENCE_PERIOD_KEY_TMPL.format(self.id) - - cache.delete(unconnective_key) - cache.delete(unconnective_silence_period_key) - - def get_is_unconnective(self): - unconnective_key = self.UNCONNECTIVE_KEY_TMPL.format(self.id) - return cache.get(unconnective_key, False) - - @property - def is_connective(self): - return not self.get_is_unconnective() - - @is_connective.setter - def is_connective(self, value): - if value: - self.set_connective() - else: - self.set_unconnective() - - def test_connective(self, local_port=None): - if local_port is None: - local_port = self.port - - client = paramiko.SSHClient() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - proxy = paramiko.SSHClient() - proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - try: - proxy.connect(self.ip, port=self.port, - username=self.username, - password=self.password, - pkey=self.private_key_obj) - except(paramiko.AuthenticationException, - paramiko.BadAuthenticationType, - paramiko.SSHException, - paramiko.ChannelException, - paramiko.ssh_exception.NoValidConnectionsError, - socket.gaierror) as e: - err = str(e) - if err.startswith('[Errno None] Unable to connect to port'): - err = _('Unable to connect to port {port} on {address}') - err = err.format(port=self.port, ip=self.ip) - elif err == 'Authentication failed.': - err = _('Authentication failed') - elif err == 'Connect failed': - err = _('Connect failed') - self.is_connective = False - return False, err - - try: - sock = proxy.get_transport().open_channel( - 'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0) - ) - client.connect("127.0.0.1", port=local_port, - username=self.username, - password=self.password, - key_filename=self.private_key_file, - sock=sock, - timeout=5) - except (paramiko.SSHException, - paramiko.ssh_exception.SSHException, - paramiko.ChannelException, - paramiko.AuthenticationException, - TimeoutError) as e: - - err = getattr(e, 'text', str(e)) - if err == 'Connect failed': - err = _('Connect failed') - self.is_connective = False - return False, err - finally: - client.close() - self.is_connective = True - return True, None diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 82fd9433f..37d17c814 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -3,11 +3,9 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from common.validators import alphanumeric from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin -from ..models import Domain, Gateway -from .base import AuthValidateMixin +from ..models import Domain, Asset class DomainSerializer(BulkOrgResourceModelSerializer): @@ -35,32 +33,23 @@ class DomainSerializer(BulkOrgResourceModelSerializer): @staticmethod def get_gateway_count(obj): - return obj.gateway_set.all().count() + return obj.gateways.count() -class GatewaySerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): +class GatewaySerializer(BulkOrgResourceModelSerializer): is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) class Meta: - model = Gateway - fields_mini = ['id', 'username'] - fields_write_only = [ - 'password', 'private_key', 'public_key', 'passphrase' - ] - fields_small = fields_mini + fields_write_only + [ - 'ip', 'port', 'protocol', + model = Asset + fields_mini = ['id'] + fields_small = fields_mini + [ + 'address', 'port', 'protocol', 'is_active', 'is_connective', 'date_created', 'date_updated', 'created_by', 'comment', ] fields_fk = ['domain'] fields = fields_small + fields_fk - extra_kwargs = { - 'username': {"validators": [alphanumeric]}, - 'password': {'write_only': True}, - 'private_key': {"write_only": True}, - 'public_key': {"write_only": True}, - } class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 5011d73a6..fd3895921 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account, Platform +from assets.models import Asset, Domain, CommandFilterRule, Account, Platform from authentication.models import ConnectionToken from common.utils import pretty_string from common.utils.random import random_string @@ -130,8 +130,8 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): """ Gateway """ class Meta: - model = Gateway - fields = ['id', 'ip', 'port', 'username', 'password', 'private_key'] + model = Asset + fields = ['id', 'address', 'port', 'username', 'password', 'private_key'] class ConnectionTokenDomainSerializer(serializers.ModelSerializer): diff --git a/apps/orgs/api.py b/apps/orgs/api.py index 16fde4d69..da9b1530f 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -14,7 +14,7 @@ from .serializers import ( ) from users.models import User, UserGroup from assets.models import ( - Asset, Domain, Label, Node, Gateway, + Asset, Domain, Label, Node, CommandFilter, CommandFilterRule, GatheredUser ) from perms.models import AssetPermission @@ -27,7 +27,7 @@ logger = get_logger(__file__) # 部分 org 相关的 model,需要清空这些数据之后才能删除该组织 org_related_models = [ - User, UserGroup, Asset, Label, Domain, Gateway, Node, Label, + User, UserGroup, Asset, Label, Domain, Node, Label, CommandFilter, CommandFilterRule, GatheredUser, AssetPermission, ] diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index 5df387c91..a17cd832b 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -6,7 +6,7 @@ from orgs.utils import current_org, tmp_to_org from common.cache import Cache, IntegerField from common.utils import get_logger from users.models import UserGroup, User -from assets.models import Node, Domain, Gateway, Asset, Account +from assets.models import Node, Domain, Asset, Account from terminal.models import Session from perms.models import AssetPermission @@ -54,7 +54,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache): nodes_amount = IntegerField(queryset=Node.objects) accounts_amount = IntegerField(queryset=Account.objects) domains_amount = IntegerField(queryset=Domain.objects) - gateways_amount = IntegerField(queryset=Gateway.objects) + # gateways_amount = IntegerField(queryset=Gateway.objects) asset_perms_amount = IntegerField(queryset=AssetPermission.objects) total_count_online_users = IntegerField() diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 1e3343931..b3e06362d 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -8,7 +8,7 @@ from users.models import UserGroup, User from users.signals import pre_user_leave_org from terminal.models import Session from rbac.models import OrgRoleBinding, SystemRoleBinding, RoleBinding -from assets.models import Asset, Domain, Gateway +from assets.models import Asset, Domain from orgs.caches import OrgResourceStatisticsCache from orgs.utils import current_org from common.utils import get_logger @@ -75,7 +75,6 @@ def on_user_delete_refresh_cache(sender, instance, **kwargs): class OrgResourceStatisticsRefreshUtil: model_cache_field_mapper = { AssetPermission: ['asset_perms_amount'], - Gateway: ['gateways_amount'], Domain: ['domains_amount'], Node: ['nodes_amount'], Asset: ['assets_amount'], From 779161d79a789a1d7f59810bfdd04b030e37b9de Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 22 Nov 2022 21:54:40 +0800 Subject: [PATCH 377/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connection?= =?UTF-8?q?=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 48 +++++++++---- .../migrations/0012_auto_20220816_1629.py | 2 +- .../migrations/0014_auto_20221122_2152.py | 29 ++++++++ .../authentication/models/connection_token.py | 72 +++++++++++++------ .../serializers/connection_token.py | 61 ++++++---------- apps/common/utils/common.py | 3 +- apps/common/utils/geoip/GeoLite2-City.mmdb | 3 + apps/jumpserver/context_processor.py | 2 +- apps/perms/api/perm_token.py | 5 ++ apps/perms/utils/account.py | 4 +- .../migrations/0024_auto_20221121_1800.py | 48 +++++++++++++ 11 files changed, 195 insertions(+), 82 deletions(-) create mode 100644 apps/authentication/migrations/0014_auto_20221122_2152.py create mode 100644 apps/common/utils/geoip/GeoLite2-City.mmdb create mode 100644 apps/perms/api/perm_token.py create mode 100644 apps/tickets/migrations/0024_auto_20221121_1800.py diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index b531c6560..86ddbcc2d 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -6,6 +6,7 @@ import urllib.parse from django.http import HttpResponse from django.shortcuts import get_object_or_404 +from django.utils import timezone from rest_framework import status from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied @@ -15,6 +16,7 @@ from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.http import is_true from orgs.mixins.api import RootOrgViewMixin +from orgs.utils import tmp_to_root_org from perms.models import ActionChoices from terminal.models import EndpointRule from ..models import ConnectionToken @@ -257,6 +259,10 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView 'get_client_protocol_url': 'authentication.add_connectiontoken', } + def dispatch(self, request, *args, **kwargs): + with tmp_to_root_org(): + return super().dispatch(request, *args, **kwargs) + def get_queryset(self): return ConnectionToken.objects.filter(user=self.request.user) @@ -264,22 +270,36 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView return self.request.user def perform_create(self, serializer): - user = self.get_user(serializer) - asset = serializer.validated_data.get('asset') - account_username = serializer.validated_data.get('account_username') - self.validate_asset_permission(user, asset, account_username) - return super(ConnectionTokenViewSet, self).perform_create(serializer) + self.validate_serializer(serializer) + return super().perform_create(serializer) - @staticmethod - def validate_asset_permission(user, asset, account_username): + def validate_serializer(self, serializer): from perms.utils.account import PermAccountUtil - actions, expire_at = PermAccountUtil().validate_permission(user, asset, account_username) - if not actions: - error = 'No actions' - raise PermissionDenied(error) - if expire_at < time.time(): - error = 'Expired' - raise PermissionDenied(error) + + data = serializer.validated_data + user = self.get_user(serializer) + asset = data.get('asset') + login = data.get('login') + data['org_id'] = asset.org_id + data['user'] = user + + util = PermAccountUtil() + permed_account = util.validate_permission(user, asset, login) + + if not permed_account or not permed_account.actions: + msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( + user, asset, login + ) + raise PermissionDenied(msg) + + if permed_account.date_expired < timezone.now(): + raise PermissionDenied('Expired') + + if permed_account.has_secret: + serializer.validated_data['secret'] = '' + if permed_account.username != '@INPUT': + serializer.validated_data['username'] = '' + return permed_account class SuperConnectionTokenViewSet(ConnectionTokenViewSet): diff --git a/apps/authentication/migrations/0012_auto_20220816_1629.py b/apps/authentication/migrations/0012_auto_20220816_1629.py index 8310c4fbb..07c9559ce 100644 --- a/apps/authentication/migrations/0012_auto_20220816_1629.py +++ b/apps/authentication/migrations/0012_auto_20220816_1629.py @@ -16,7 +16,7 @@ def migrate_system_user_to_account(apps, schema_editor): count += len(connection_tokens) updated = [] for connection_token in connection_tokens: - connection_token.account_username = connection_token.system_user.username + connection_token.account = connection_token.system_user.username updated.append(connection_token) connection_token_model.objects.bulk_update(updated, ['account_username']) diff --git a/apps/authentication/migrations/0014_auto_20221122_2152.py b/apps/authentication/migrations/0014_auto_20221122_2152.py new file mode 100644 index 000000000..6421b7f0b --- /dev/null +++ b/apps/authentication/migrations/0014_auto_20221122_2152.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.14 on 2022-11-22 13:52 + +import common.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0013_connectiontoken_protocol'), + ] + + operations = [ + migrations.RenameField( + model_name='connectiontoken', + old_name='account_username', + new_name='login' + ), + migrations.AddField( + model_name='connectiontoken', + name='username', + field=models.CharField(default='', max_length=128, verbose_name='Username'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='secret', + field=common.db.fields.EncryptCharField(default='', max_length=128, verbose_name='Secret'), + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 48c61f954..dd2e3d38f 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -2,13 +2,15 @@ import time from datetime import timedelta from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.conf import settings -from orgs.mixins.models import OrgModelMixin - from django.db import models -from common.utils import lazyproperty +from django.conf import settings +from rest_framework.exceptions import PermissionDenied + +from orgs.mixins.models import OrgModelMixin +from common.utils import lazyproperty, pretty_string from common.utils.timezone import as_current_tz from common.db.models import JMSBaseModel +from common.db.fields import EncryptCharField from assets.const import Protocol @@ -25,13 +27,14 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): 'assets.Asset', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('Asset'), ) + login = models.CharField(max_length=128, verbose_name=_("Login account")) + username = models.CharField(max_length=128, default='', verbose_name=_("Username")) + secret = EncryptCharField(max_length=64, default='', verbose_name=_("Secret")) protocol = models.CharField( choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - account_username = models.CharField(max_length=128, default='', verbose_name=_("Account")) - secret = models.CharField(max_length=64, default='', verbose_name=_("Secret")) date_expired = models.DateTimeField( default=date_expired_default, verbose_name=_("Date expired") ) @@ -59,9 +62,10 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): seconds = 0 return int(seconds) - @classmethod - def get_default_date_expired(cls): - return date_expired_default() + def save(self, *args, **kwargs): + self.asset_display = pretty_string(self.asset, max_length=128) + self.user_display = pretty_string(self.user, max_length=128) + return super().save(*args, **kwargs) def expire(self): self.date_expired = timezone.now() @@ -69,7 +73,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def renewal(self): """ 续期 Token,将来支持用户自定义创建 token 后,续期策略要修改 """ - self.date_expired = self.get_default_date_expired() + self.date_expired = date_expired_default() self.save() # actions 和 expired_at 在 check_valid() 中赋值 @@ -89,28 +93,52 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('No asset or inactive asset') return is_valid, error - if not self.account_username: + if not self.login: is_valid = False error = _('No account') return is_valid, error - actions, expire_at = PermAccountUtil().validate_permission( - self.user, self.asset, self.account_username + + permed_account = PermAccountUtil().validate_permission( + self.user, self.asset, self.login ) - if not actions or expire_at < time.time(): - is_valid = False - error = _('User has no permission to access asset or permission expired') - return is_valid, error - self.actions = actions - self.expire_at = expire_at + if not permed_account or not permed_account.actions: + msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( + self.user, self.asset, self.login + ) + raise PermissionDenied(msg) + + if permed_account.date_expired < timezone.now(): + raise PermissionDenied('Expired') + is_valid, error = True, '' return is_valid, error @lazyproperty - def account(self): + def platform(self): + return self.asset.platform + + @lazyproperty + def accounts(self): if not self.asset: return None - account = self.asset.accounts.filter(username=self.account_username).first() - return account + + data = [] + if self.login == '@INPUT': + data.append({ + 'name': self.login, + 'username': self.username, + 'secret_type': 'password', + 'secret': self.secret + }) + else: + accounts = self.asset.accounts.filter(username=self.login) + for account in accounts: + data.append({ + 'username': account.uesrname, + 'secret_type': account.secret_type, + 'secret': account.secret if account.secret else self.secret + }) + return data @lazyproperty def domain(self): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 5011d73a6..14aae463e 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -2,9 +2,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from assets.models import Asset, Gateway, Domain, CommandFilterRule, Account, Platform +from assets.serializers import PlatformSerializer from authentication.models import ConnectionToken -from common.utils import pretty_string -from common.utils.random import random_string from orgs.mixins.serializers import OrgResourceModelSerializerMixin from perms.serializers.permission import ActionChoicesField from users.models import User @@ -16,6 +15,8 @@ __all__ = [ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): + username = serializers.CharField(max_length=128, label=_("Input username"), + allow_null=True, allow_blank=True) is_valid = serializers.BooleanField(read_only=True, label=_('Validity')) expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) @@ -23,9 +24,10 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields_mini = ['id'] fields_small = fields_mini + [ - 'secret', 'account_username', 'date_expired', - 'date_created', 'date_updated', - 'created_by', 'updated_by', 'org_id', 'org_name', + 'protocol', 'login', 'secret', 'username', + 'date_expired', 'date_created', + 'date_updated', 'created_by', + 'updated_by', 'org_id', 'org_name', ] fields_fk = [ 'user', 'asset', @@ -45,32 +47,6 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): def get_user(self, attrs): return self.get_request_user() - def validate(self, attrs): - fields_attrs = self.construct_internal_fields_attrs(attrs) - attrs.update(fields_attrs) - return attrs - - def construct_internal_fields_attrs(self, attrs): - asset = attrs.get('asset') or '' - asset_display = pretty_string(str(asset), max_length=128) - user = self.get_user(attrs) - user_display = pretty_string(str(user), max_length=128) - secret = attrs.get('secret') or random_string(16) - date_expired = attrs.get('date_expired') or ConnectionToken.get_default_date_expired() - org_id = asset.org_id - if not isinstance(asset, Asset): - error = '' - raise serializers.ValidationError(error) - attrs = { - 'user': user, - 'secret': secret, - 'user_display': user_display, - 'asset_display': asset_display, - 'date_expired': date_expired, - 'org_id': org_id, - } - return attrs - class ConnectionTokenDisplaySerializer(ConnectionTokenSerializer): class Meta(ConnectionTokenSerializer.Meta): @@ -122,7 +98,7 @@ class ConnectionTokenAccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = [ - 'id', 'name', 'username', 'secret_type', 'secret', 'version' + 'username', 'secret_type', 'secret', ] @@ -154,26 +130,31 @@ class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): ] -class ConnectionTokenPlatform(serializers.ModelSerializer): - class Meta: +class ConnectionTokenPlatform(PlatformSerializer): + class Meta(PlatformSerializer.Meta): model = Platform - fields = ['id', 'name', 'org_id'] + + def get_field_names(self, declared_fields, info): + names = super().get_field_names(declared_fields, info) + names = [n for n in names if n not in ['automation']] + return names class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) platform = ConnectionTokenPlatform(read_only=True) - account = ConnectionTokenAccountSerializer(read_only=True) + accounts = ConnectionTokenAccountSerializer(read_only=True, many=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) - cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) + # cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() class Meta: model = ConnectionToken fields = [ - 'id', 'secret', 'user', 'asset', 'account_username', - 'account', 'protocol', 'domain', 'gateway', - 'cmd_filter_rules', 'actions', 'expire_at', + 'id', 'secret', 'user', 'asset', 'login', + 'accounts', 'protocol', 'domain', 'gateway', + 'actions', 'expire_at', + 'platform', ] diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 6f1e34b0a..03338e9aa 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -344,7 +344,7 @@ def get_file_by_arch(dir, filename): return file_path -def pretty_string(data: str, max_length=128, ellipsis_str='...'): +def pretty_string(data, max_length=128, ellipsis_str='...'): """ params: data: abcdefgh @@ -353,6 +353,7 @@ def pretty_string(data: str, max_length=128, ellipsis_str='...'): return: ab...gh """ + data = str(data) if len(data) < max_length: return data remain_length = max_length - len(ellipsis_str) diff --git a/apps/common/utils/geoip/GeoLite2-City.mmdb b/apps/common/utils/geoip/GeoLite2-City.mmdb new file mode 100644 index 000000000..c3b9d8bac --- /dev/null +++ b/apps/common/utils/geoip/GeoLite2-City.mmdb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:860b4d38beff81667c64da41c026a7dd28c3c93a28ae61fefaa7c26875f35638 +size 73906864 diff --git a/apps/jumpserver/context_processor.py b/apps/jumpserver/context_processor.py index 414144d39..84c5a4bc2 100644 --- a/apps/jumpserver/context_processor.py +++ b/apps/jumpserver/context_processor.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _ default_interface = dict(( ('logo_logout', static('img/logo.png')), - ('logo_index', static('img/logo_text.png')), + ('logo_index', static('img/logo_text_white.png')), ('login_image', static('img/login_image.jpg')), ('favicon', static('img/facio.ico')), ('login_title', _('JumpServer Open Source Bastion Host')), diff --git a/apps/perms/api/perm_token.py b/apps/perms/api/perm_token.py new file mode 100644 index 000000000..63cf08062 --- /dev/null +++ b/apps/perms/api/perm_token.py @@ -0,0 +1,5 @@ +from rest_framework.viewsets import ModelViewSet + + +class PermTokenViewSet(ModelViewSet): + pass diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index a9c2bc279..b394bfbc4 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -17,10 +17,8 @@ class PermAccountUtil(AssetPermissionUtil): """ permed_accounts = self.get_permed_accounts_for_user(user, asset) accounts_mapper = {account.username: account for account in permed_accounts} - account = accounts_mapper.get(account_username) - actions, date_expired = (account.actions, account.date_expired) if account else (False, None) - return actions, date_expired + return account def get_permed_accounts_for_user(self, user, asset): """ 获取授权给用户某个资产的账号 """ diff --git a/apps/tickets/migrations/0024_auto_20221121_1800.py b/apps/tickets/migrations/0024_auto_20221121_1800.py new file mode 100644 index 000000000..acd203ef6 --- /dev/null +++ b/apps/tickets/migrations/0024_auto_20221121_1800.py @@ -0,0 +1,48 @@ +# Generated by Django 3.2.14 on 2022-11-21 10:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0023_alter_applyassetticket_apply_actions'), + ] + + operations = [ + migrations.AlterField( + model_name='approvalrule', + name='strategy', + field=models.CharField(choices=[('org_admin', 'Org admin'), ('custom_user', 'Custom user'), ('super_admin', 'Super admin'), ('super_org_admin', 'Super admin and org admin')], default='super_admin', max_length=64, verbose_name='Approve strategy'), + ), + migrations.AlterField( + model_name='ticket', + name='state', + field=models.CharField(choices=[('pending', 'Open'), ('closed', 'Cancel'), ('reopen', 'Reopen'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='pending', max_length=16, verbose_name='State'), + ), + migrations.AlterField( + model_name='ticket', + name='type', + field=models.CharField(choices=[('general', 'General'), ('apply_asset', 'Apply for asset'), ('login_confirm', 'Login confirm'), ('command_confirm', 'Command confirm'), ('login_asset_confirm', 'Login asset confirm')], default='general', max_length=64, verbose_name='Type'), + ), + migrations.AlterField( + model_name='ticketassignee', + name='state', + field=models.CharField(choices=[('pending', 'Open'), ('closed', 'Cancel'), ('reopen', 'Reopen'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='pending', max_length=64), + ), + migrations.AlterField( + model_name='ticketflow', + name='type', + field=models.CharField(choices=[('general', 'General'), ('apply_asset', 'Apply for asset'), ('login_confirm', 'Login confirm'), ('command_confirm', 'Command confirm'), ('login_asset_confirm', 'Login asset confirm')], default='general', max_length=64, verbose_name='Type'), + ), + migrations.AlterField( + model_name='ticketstep', + name='state', + field=models.CharField(choices=[('pending', 'Pending'), ('closed', 'Closed'), ('reopen', 'Reopen'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='pending', max_length=64, verbose_name='State'), + ), + migrations.AlterField( + model_name='ticketstep', + name='status', + field=models.CharField(choices=[('active', 'Active'), ('closed', 'Closed'), ('pending', 'Pending')], default='pending', max_length=16), + ), + ] From abfd472a0a32c1a2f7d2812078feaa5eed926669 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 23 Nov 2022 16:11:17 +0800 Subject: [PATCH 378/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 44 +++++------- .../migrations/0014_auto_20221122_2152.py | 5 ++ .../authentication/models/connection_token.py | 68 +++++++++---------- .../serializers/connection_token.py | 16 ++--- apps/perms/const.py | 12 +++- 5 files changed, 72 insertions(+), 73 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 86ddbcc2d..1636c2cb1 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -28,9 +28,6 @@ from ..serializers import ( __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] -# ExtraActionApiMixin - - class RDPFileClientProtocolURLMixin: request: Request get_serializer: callable @@ -72,8 +69,7 @@ class RDPFileClientProtocolURLMixin: # 设置磁盘挂载 drives_redirect = is_true(self.request.query_params.get('drives_redirect')) if drives_redirect: - actions = ActionChoices.choices_to_value(token.actions) - if actions & Action.TRANSFER == Action.TRANSFER: + if ActionChoices.contains(token.actions, ActionChoices.transfer()): rdp_options['drivestoredirect:s'] = '*' # 设置全屏 @@ -181,22 +177,10 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): get_serializer: callable perform_create: callable - @action(methods=['POST'], detail=False, url_path='secret-info/detail') - def get_secret_detail(self, request, *args, **kwargs): - """ 非常重要的 api, 在逻辑层再判断一下 rbac 权限, 双重保险 """ - rbac_perm = 'authentication.view_connectiontokensecret' - if not request.user.has_perm(rbac_perm): - raise PermissionDenied('Not allow to view secret') - token_id = request.data.get('token') or '' - token = get_object_or_404(ConnectionToken, pk=token_id) - self.check_token_permission(token) - serializer = self.get_serializer(instance=token) - return Response(serializer.data, status=status.HTTP_200_OK) - @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') def get_rdp_file(self, request, *args, **kwargs): token = self.create_connection_token() - self.check_token_permission(token) + token.is_valid() filename, content = self.get_rdp_file_info(token) filename = '{}.rdp'.format(filename) response = HttpResponse(content, content_type='application/octet-stream') @@ -206,7 +190,7 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): @action(methods=['POST', 'GET'], detail=False, url_path='client-url') def get_client_protocol_url(self, request, *args, **kwargs): token = self.create_connection_token() - self.check_token_permission(token) + token.is_valid() try: protocol_data = self.get_client_protocol_data(token) except ValueError as e: @@ -224,12 +208,6 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): instance.expire() return Response(status=status.HTTP_204_NO_CONTENT) - @staticmethod - def check_token_permission(token: ConnectionToken): - is_valid, error = token.check_permission() - if not is_valid: - raise PermissionDenied(error) - def create_connection_token(self): data = self.request.query_params if self.request.method == 'GET' else self.request.data serializer = self.get_serializer(data=data) @@ -259,6 +237,18 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView 'get_client_protocol_url': 'authentication.add_connectiontoken', } + @action(methods=['POST'], detail=False, url_path='secret') + def get_secret_detail(self, request, *args, **kwargs): + """ 非常重要的 api, 在逻辑层再判断一下 rbac 权限, 双重保险 """ + rbac_perm = 'authentication.view_connectiontokensecret' + if not request.user.has_perm(rbac_perm): + raise PermissionDenied('Not allow to view secret') + token_id = request.data.get('token') or '' + token = get_object_or_404(ConnectionToken, pk=token_id) + token.is_valid() + serializer = self.get_serializer(instance=token) + return Response(serializer.data, status=status.HTTP_200_OK) + def dispatch(self, request, *args, **kwargs): with tmp_to_root_org(): return super().dispatch(request, *args, **kwargs) @@ -296,9 +286,9 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView raise PermissionDenied('Expired') if permed_account.has_secret: - serializer.validated_data['secret'] = '' + data['secret'] = '' if permed_account.username != '@INPUT': - serializer.validated_data['username'] = '' + data['username'] = '' return permed_account diff --git a/apps/authentication/migrations/0014_auto_20221122_2152.py b/apps/authentication/migrations/0014_auto_20221122_2152.py index 6421b7f0b..b198295a5 100644 --- a/apps/authentication/migrations/0014_auto_20221122_2152.py +++ b/apps/authentication/migrations/0014_auto_20221122_2152.py @@ -16,6 +16,11 @@ class Migration(migrations.Migration): old_name='account_username', new_name='login' ), + migrations.AlterField( + model_name='connectiontoken', + name='login', + field=models.CharField(max_length=128, verbose_name='Login account'), + ), migrations.AddField( model_name='connectiontoken', name='username', diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index dd2e3d38f..058d07581 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -46,10 +46,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): ('view_connectiontokensecret', _('Can view connection token secret')) ] - @property - def is_valid(self): - return not self.is_expired - @property def is_expired(self): return self.date_expired < timezone.now() @@ -76,69 +72,71 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): self.date_expired = date_expired_default() self.save() - # actions 和 expired_at 在 check_valid() 中赋值 - actions = expire_at = None + @lazyproperty + def permed_account(self): + from perms.utils import PermAccountUtil + permed_account = PermAccountUtil().validate_permission( + self.user, self.asset, self.login + ) + return permed_account - def check_permission(self): - from perms.utils.account import PermAccountUtil + @lazyproperty + def actions(self): + return self.permed_account.actions + + @lazyproperty + def expire_at(self): + return self.permed_account.date_expired.timestamp() + + def is_valid(self): if self.is_expired: - is_valid = False error = _('Connection token expired at: {}').format(as_current_tz(self.date_expired)) - return is_valid, error + raise PermissionDenied(error) if not self.user or not self.user.is_valid: - is_valid = False error = _('No user or invalid user') - return is_valid, error + raise PermissionDenied(error) if not self.asset or not self.asset.is_active: is_valid = False error = _('No asset or inactive asset') return is_valid, error if not self.login: - is_valid = False error = _('No account') - return is_valid, error + raise PermissionDenied(error) - permed_account = PermAccountUtil().validate_permission( - self.user, self.asset, self.login - ) - if not permed_account or not permed_account.actions: + if not self.permed_account or not self.permed_account.actions: msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( self.user, self.asset, self.login ) raise PermissionDenied(msg) - if permed_account.date_expired < timezone.now(): + if self.permed_account.date_expired < timezone.now(): raise PermissionDenied('Expired') - - is_valid, error = True, '' - return is_valid, error + return True @lazyproperty def platform(self): return self.asset.platform @lazyproperty - def accounts(self): + def account(self): if not self.asset: return None - data = [] - if self.login == '@INPUT': - data.append({ + account = self.asset.accounts.filter(name=self.login).first() + if self.login == '@INPUT' or not account: + return { 'name': self.login, 'username': self.username, 'secret_type': 'password', 'secret': self.secret - }) + } else: - accounts = self.asset.accounts.filter(username=self.login) - for account in accounts: - data.append({ - 'username': account.uesrname, - 'secret_type': account.secret_type, - 'secret': account.secret if account.secret else self.secret - }) - return data + return { + 'name': account.name, + 'username': account.username, + 'secret_type': account.secret_type, + 'secret': account.secret_type or self.secret + } @lazyproperty def domain(self): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 3acf1333b..77981cd4a 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -17,7 +17,6 @@ __all__ = [ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): username = serializers.CharField(max_length=128, label=_("Input username"), allow_null=True, allow_blank=True) - is_valid = serializers.BooleanField(read_only=True, label=_('Validity')) expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) class Meta: @@ -25,7 +24,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): fields_mini = ['id'] fields_small = fields_mini + [ 'protocol', 'login', 'secret', 'username', - 'date_expired', 'date_created', + 'actions', 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', ] @@ -34,7 +33,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): ] read_only_fields = [ # 普通 Token 不支持指定 user - 'user', 'is_valid', 'expire_time', + 'user', 'expire_time', 'user_display', 'asset_display', ] fields = fields_small + fields_fk + read_only_fields @@ -98,7 +97,7 @@ class ConnectionTokenAccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = [ - 'username', 'secret_type', 'secret', + 'name', 'username', 'secret_type', 'secret', ] @@ -144,7 +143,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) platform = ConnectionTokenPlatform(read_only=True) - accounts = ConnectionTokenAccountSerializer(read_only=True, many=True) + account = ConnectionTokenAccountSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) # cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) actions = ActionChoicesField() @@ -153,8 +152,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'secret', 'user', 'asset', 'login', - 'accounts', 'protocol', 'domain', 'gateway', - 'actions', 'expire_at', - 'platform', + 'id', 'secret', 'user', 'asset', 'account', + 'protocol', 'domain', 'gateway', + 'actions', 'expire_at', 'platform', ] diff --git a/apps/perms/const.py b/apps/perms/const.py index 50141f386..690fb2742 100644 --- a/apps/perms/const.py +++ b/apps/perms/const.py @@ -28,8 +28,16 @@ class ActionChoices(BitChoices): ) @classmethod - def has_perm(cls, action_name, total): - action_value = getattr(cls, action_name) + def transfer(cls): + return cls.upload | cls.download + + @classmethod + def clipboard(cls): + return cls.copy | cls.paste + + @classmethod + def contains(cls, total, action): + action_value = getattr(cls, action) return action_value & total == action_value @classmethod From 3d6609ec8c81a8a11d0b121068edafbc6ffd0541 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 23 Nov 2022 18:36:42 +0800 Subject: [PATCH 379/488] perf: gateway --- apps/assets/models/asset/host.py | 1 - apps/assets/models/domain.py | 5 +- apps/assets/serializers/domain.py | 113 ++++++++++++++---- .../0015_alter_connectiontoken_login.py | 18 +++ 4 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 apps/authentication/migrations/0015_alter_connectiontoken_login.py diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 46aeed4f3..a1dbb6de3 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -3,7 +3,6 @@ from .common import Asset class Host(Asset): - pass @classmethod def get_gateway_queryset(cls): diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index bf33caed6..248e02f20 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -13,8 +13,9 @@ from django.utils.translation import ugettext_lazy as _ from common.db import fields from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin +from assets.models import Host from .base import BaseAccount -from ..const import SecretType, GATEWAY_NAME +from ..const import SecretType logger = get_logger(__file__) @@ -37,7 +38,7 @@ class Domain(OrgModelMixin): @lazyproperty def gateways(self): - return self.assets.filter(platform__name=GATEWAY_NAME, is_active=True) + return Host.get_gateway_queryset().filter(domain=self, is_active=True) def select_gateway(self): return self.random_gateway() diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 37d17c814..64c8c032c 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -1,28 +1,33 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers +from rest_framework.generics import get_object_or_404 from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin -from ..models import Domain, Asset +from common.drf.fields import ObjectRelatedField, EncryptedField +from assets.const import SecretType +from ..models import Domain, Asset, Account +from ..serializers import HostSerializer +from .utils import validate_password_for_ansible, validate_ssh_key class DomainSerializer(BulkOrgResourceModelSerializer): asset_count = serializers.SerializerMethodField(label=_('Assets amount')) gateway_count = serializers.SerializerMethodField(label=_('Gateways count')) + assets = ObjectRelatedField( + many=True, required=False, queryset=Asset.objects, label=_('Asset') + ) class Meta: model = Domain fields_mini = ['id', 'name'] - fields_small = fields_mini + [ - 'comment', 'date_created' - ] - fields_m2m = [ - 'asset_count', 'assets', 'gateway_count', - ] - fields = fields_small + fields_m2m - read_only_fields = ('asset_count', 'gateway_count', 'date_created') + fields_small = fields_mini + ['comment'] + fields_m2m = ['assets'] + read_only_fields = ['asset_count', 'gateway_count', 'date_created'] + fields = fields_small + fields_m2m + read_only_fields + extra_kwargs = { 'assets': {'required': False, 'label': _('Assets')}, } @@ -36,20 +41,86 @@ class DomainSerializer(BulkOrgResourceModelSerializer): return obj.gateways.count() -class GatewaySerializer(BulkOrgResourceModelSerializer): - is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) +class GatewaySerializer(HostSerializer): + password = EncryptedField( + label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, + validators=[validate_password_for_ansible], write_only=True + ) + private_key = EncryptedField( + label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, + max_length=16384, write_only=True + ) + passphrase = serializers.CharField( + label=_('Key password'), allow_blank=True, allow_null=True, required=False, write_only=True, + max_length=512, + ) + username = serializers.CharField( + label=_('Username'), allow_blank=True, max_length=128, required=True, + ) - class Meta: - model = Asset - fields_mini = ['id'] - fields_small = fields_mini + [ - 'address', 'port', 'protocol', - 'is_active', 'is_connective', - 'date_created', 'date_updated', - 'created_by', 'comment', + class Meta(HostSerializer.Meta): + fields = HostSerializer.Meta.fields + [ + 'username', 'password', 'private_key', 'passphrase' ] - fields_fk = ['domain'] - fields = fields_small + fields_fk + + def validate_private_key(self, secret): + if not secret: + return + passphrase = self.initial_data.get('passphrase') + passphrase = passphrase if passphrase else None + validate_ssh_key(secret, passphrase) + return secret + + @staticmethod + def clean_auth_fields(validated_data): + username = validated_data.pop('username', None) + password = validated_data.pop('password', None) + private_key = validated_data.pop('private_key', None) + validated_data.pop('passphrase', None) + return username, password, private_key + + @staticmethod + def create_accounts(instance, username, password, private_key): + account_name = f'{instance.name}-{_("Gateway")}' + account_data = { + 'privileged': True, + 'name': account_name, + 'username': username, + 'asset_id': instance.id, + 'created_by': instance.created_by + } + if password: + Account.objects.create( + **account_data, secret=password, secret_type=SecretType.PASSWORD + ) + if private_key: + Account.objects.create( + **account_data, secret=private_key, secret_type=SecretType.SSH_KEY + ) + + @staticmethod + def update_accounts(instance, username, password, private_key): + accounts = instance.accounts.filter(username=username) + if password: + account = get_object_or_404(accounts, SecretType.PASSWORD) + account.secret = password + account.save() + if private_key: + account = get_object_or_404(accounts, SecretType.SSH_KEY) + account.secret = private_key + account.save() + + def create(self, validated_data): + auth_fields = self.clean_auth_fields(validated_data) + instance = super().create(validated_data) + self.create_accounts(instance, *auth_fields) + return instance + + def update(self, instance, validated_data): + auth_fields = self.clean_auth_fields(validated_data) + instance = super().update(instance, validated_data) + self.update_accounts(instance, *auth_fields) + return instance class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): diff --git a/apps/authentication/migrations/0015_alter_connectiontoken_login.py b/apps/authentication/migrations/0015_alter_connectiontoken_login.py new file mode 100644 index 000000000..f2c6abecb --- /dev/null +++ b/apps/authentication/migrations/0015_alter_connectiontoken_login.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-23 02:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0014_auto_20221122_2152'), + ] + + operations = [ + migrations.AlterField( + model_name='connectiontoken', + name='login', + field=models.CharField(max_length=128, verbose_name='Login account'), + ), + ] From d44d475cae1af6e90c9e937ed4f40cba82cc6d43 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:39:05 +0800 Subject: [PATCH 380/488] perf: gateway (#9115) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/models/asset/host.py | 1 - apps/assets/models/domain.py | 5 +- apps/assets/serializers/domain.py | 113 ++++++++++++++---- .../0015_alter_connectiontoken_login.py | 18 +++ 4 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 apps/authentication/migrations/0015_alter_connectiontoken_login.py diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index 46aeed4f3..a1dbb6de3 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -3,7 +3,6 @@ from .common import Asset class Host(Asset): - pass @classmethod def get_gateway_queryset(cls): diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index bf33caed6..248e02f20 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -13,8 +13,9 @@ from django.utils.translation import ugettext_lazy as _ from common.db import fields from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin +from assets.models import Host from .base import BaseAccount -from ..const import SecretType, GATEWAY_NAME +from ..const import SecretType logger = get_logger(__file__) @@ -37,7 +38,7 @@ class Domain(OrgModelMixin): @lazyproperty def gateways(self): - return self.assets.filter(platform__name=GATEWAY_NAME, is_active=True) + return Host.get_gateway_queryset().filter(domain=self, is_active=True) def select_gateway(self): return self.random_gateway() diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 37d17c814..64c8c032c 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -1,28 +1,33 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers +from rest_framework.generics import get_object_or_404 from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin -from ..models import Domain, Asset +from common.drf.fields import ObjectRelatedField, EncryptedField +from assets.const import SecretType +from ..models import Domain, Asset, Account +from ..serializers import HostSerializer +from .utils import validate_password_for_ansible, validate_ssh_key class DomainSerializer(BulkOrgResourceModelSerializer): asset_count = serializers.SerializerMethodField(label=_('Assets amount')) gateway_count = serializers.SerializerMethodField(label=_('Gateways count')) + assets = ObjectRelatedField( + many=True, required=False, queryset=Asset.objects, label=_('Asset') + ) class Meta: model = Domain fields_mini = ['id', 'name'] - fields_small = fields_mini + [ - 'comment', 'date_created' - ] - fields_m2m = [ - 'asset_count', 'assets', 'gateway_count', - ] - fields = fields_small + fields_m2m - read_only_fields = ('asset_count', 'gateway_count', 'date_created') + fields_small = fields_mini + ['comment'] + fields_m2m = ['assets'] + read_only_fields = ['asset_count', 'gateway_count', 'date_created'] + fields = fields_small + fields_m2m + read_only_fields + extra_kwargs = { 'assets': {'required': False, 'label': _('Assets')}, } @@ -36,20 +41,86 @@ class DomainSerializer(BulkOrgResourceModelSerializer): return obj.gateways.count() -class GatewaySerializer(BulkOrgResourceModelSerializer): - is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) +class GatewaySerializer(HostSerializer): + password = EncryptedField( + label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, + validators=[validate_password_for_ansible], write_only=True + ) + private_key = EncryptedField( + label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, + max_length=16384, write_only=True + ) + passphrase = serializers.CharField( + label=_('Key password'), allow_blank=True, allow_null=True, required=False, write_only=True, + max_length=512, + ) + username = serializers.CharField( + label=_('Username'), allow_blank=True, max_length=128, required=True, + ) - class Meta: - model = Asset - fields_mini = ['id'] - fields_small = fields_mini + [ - 'address', 'port', 'protocol', - 'is_active', 'is_connective', - 'date_created', 'date_updated', - 'created_by', 'comment', + class Meta(HostSerializer.Meta): + fields = HostSerializer.Meta.fields + [ + 'username', 'password', 'private_key', 'passphrase' ] - fields_fk = ['domain'] - fields = fields_small + fields_fk + + def validate_private_key(self, secret): + if not secret: + return + passphrase = self.initial_data.get('passphrase') + passphrase = passphrase if passphrase else None + validate_ssh_key(secret, passphrase) + return secret + + @staticmethod + def clean_auth_fields(validated_data): + username = validated_data.pop('username', None) + password = validated_data.pop('password', None) + private_key = validated_data.pop('private_key', None) + validated_data.pop('passphrase', None) + return username, password, private_key + + @staticmethod + def create_accounts(instance, username, password, private_key): + account_name = f'{instance.name}-{_("Gateway")}' + account_data = { + 'privileged': True, + 'name': account_name, + 'username': username, + 'asset_id': instance.id, + 'created_by': instance.created_by + } + if password: + Account.objects.create( + **account_data, secret=password, secret_type=SecretType.PASSWORD + ) + if private_key: + Account.objects.create( + **account_data, secret=private_key, secret_type=SecretType.SSH_KEY + ) + + @staticmethod + def update_accounts(instance, username, password, private_key): + accounts = instance.accounts.filter(username=username) + if password: + account = get_object_or_404(accounts, SecretType.PASSWORD) + account.secret = password + account.save() + if private_key: + account = get_object_or_404(accounts, SecretType.SSH_KEY) + account.secret = private_key + account.save() + + def create(self, validated_data): + auth_fields = self.clean_auth_fields(validated_data) + instance = super().create(validated_data) + self.create_accounts(instance, *auth_fields) + return instance + + def update(self, instance, validated_data): + auth_fields = self.clean_auth_fields(validated_data) + instance = super().update(instance, validated_data) + self.update_accounts(instance, *auth_fields) + return instance class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): diff --git a/apps/authentication/migrations/0015_alter_connectiontoken_login.py b/apps/authentication/migrations/0015_alter_connectiontoken_login.py new file mode 100644 index 000000000..f2c6abecb --- /dev/null +++ b/apps/authentication/migrations/0015_alter_connectiontoken_login.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-23 02:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0014_auto_20221122_2152'), + ] + + operations = [ + migrations.AlterField( + model_name='connectiontoken', + name='login', + field=models.CharField(max_length=128, verbose_name='Login account'), + ), + ] From fa948f7327e817e7db11c14f88abdc1e8bac1f80 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Thu, 24 Nov 2022 00:50:37 +0800 Subject: [PATCH 381/488] =?UTF-8?q?feat:=20job=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=BB=84=E7=BB=87=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/job.py | 24 ++++++------ apps/ops/migrations/0034_job_org_id.py | 18 +++++++++ .../migrations/0035_jobexecution_org_id.py | 18 +++++++++ apps/ops/models/job.py | 8 ++-- apps/ops/serializers/job.py | 5 +-- apps/ops/tasks.py | 37 ++++++++++--------- 6 files changed, 74 insertions(+), 36 deletions(-) create mode 100644 apps/ops/migrations/0034_job_org_id.py create mode 100644 apps/ops/migrations/0035_jobexecution_org_id.py diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index eaad4514c..86a52f373 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -1,4 +1,3 @@ -from django.shortcuts import get_object_or_404 from rest_framework import viewsets from ops.models import Job, JobExecution @@ -7,14 +6,17 @@ from ops.serializers.job import JobSerializer, JobExecutionSerializer __all__ = ['JobViewSet', 'JobExecutionViewSet'] from ops.tasks import run_ops_job, run_ops_job_executions +from orgs.mixins.api import OrgBulkModelViewSet -class JobViewSet(viewsets.ModelViewSet): +class JobViewSet(OrgBulkModelViewSet): serializer_class = JobSerializer - queryset = Job.objects.all() + model = Job + permission_classes = () def get_queryset(self): - return self.queryset.filter(instant=False) + query_set = super().get_queryset() + return query_set.filter(instant=False) def perform_create(self, serializer): instance = serializer.save() @@ -22,20 +24,20 @@ class JobViewSet(viewsets.ModelViewSet): run_ops_job.delay(instance.id) -class JobExecutionViewSet(viewsets.ModelViewSet): +class JobExecutionViewSet(OrgBulkModelViewSet): serializer_class = JobExecutionSerializer - queryset = JobExecution.objects.all() http_method_names = ('get', 'post', 'head', 'options',) + # filter_fields = ('type',) + permission_classes = () + model = JobExecution def perform_create(self, serializer): instance = serializer.save() run_ops_job_executions.delay(instance.id) def get_queryset(self): + query_set = super().get_queryset() job_id = self.request.query_params.get('job_id') - job_type = self.request.query_params.get('type') if job_id: - self.queryset = self.queryset.filter(job_id=job_id) - if job_type: - self.queryset = self.queryset.filter(job__type=job_type) - return self.queryset + self.queryset = query_set.filter(job_id=job_id) + return query_set diff --git a/apps/ops/migrations/0034_job_org_id.py b/apps/ops/migrations/0034_job_org_id.py new file mode 100644 index 000000000..07926cec3 --- /dev/null +++ b/apps/ops/migrations/0034_job_org_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-23 09:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0033_auto_20221118_1431'), + ] + + operations = [ + migrations.AddField( + model_name='job', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + ] diff --git a/apps/ops/migrations/0035_jobexecution_org_id.py b/apps/ops/migrations/0035_jobexecution_org_id.py new file mode 100644 index 000000000..1161d10e3 --- /dev/null +++ b/apps/ops/migrations/0035_jobexecution_org_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-23 10:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0034_job_org_id'), + ] + + operations = [ + migrations.AddField( + model_name='jobexecution', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + ] diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index d5542970d..f2e7eaa4b 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -9,16 +9,14 @@ from django.utils.translation import gettext_lazy as _ from django.utils import timezone from celery import current_task -from common.const.choices import Trigger -from common.db.models import BaseCreateUpdateModel - __all__ = ["Job", "JobExecution"] from ops.ansible import JMSInventory, AdHocRunner, PlaybookRunner from ops.mixin import PeriodTaskModelMixin +from orgs.mixins.models import JMSOrgBaseModel -class Job(BaseCreateUpdateModel, PeriodTaskModelMixin): +class Job(JMSOrgBaseModel, PeriodTaskModelMixin): class Types(models.TextChoices): adhoc = 'adhoc', _('Adhoc') playbook = 'playbook', _('Playbook') @@ -94,7 +92,7 @@ class Job(BaseCreateUpdateModel, PeriodTaskModelMixin): return self.executions.create() -class JobExecution(BaseCreateUpdateModel): +class JobExecution(JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) task_id = models.UUIDField(null=True) status = models.CharField(max_length=16, verbose_name=_('Status'), default='running') diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 389b92ce2..e5d76f85b 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -1,14 +1,13 @@ -from django.db import transaction from rest_framework import serializers - from common.drf.fields import ReadableHiddenField from ops.mixin import PeriodTaskSerializerMixin from ops.models import Job, JobExecution +from orgs.mixins.serializers import BulkOrgResourceModelSerializer _all_ = [] -class JobSerializer(serializers.ModelSerializer, PeriodTaskSerializerMixin): +class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) class Meta: diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 841f759ff..51541dd32 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -10,6 +10,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, get_object_or_none, get_log_keep_day +from orgs.utils import tmp_to_org from .celery.decorator import ( register_as_period_task, after_app_shutdown_clean_periodic, after_app_ready_start @@ -27,28 +28,30 @@ logger = get_logger(__file__) @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task")) def run_ops_job(job_id): job = get_object_or_none(Job, id=job_id) - execution = job.create_execution() - try: - execution.start() - except SoftTimeLimitExceeded: - execution.set_error('Run timeout') - logger.error("Run adhoc timeout") - except Exception as e: - execution.set_error(e) - logger.error("Start adhoc execution error: {}".format(e)) + with tmp_to_org(job.org): + execution = job.create_execution() + try: + execution.start() + except SoftTimeLimitExceeded: + execution.set_error('Run timeout') + logger.error("Run adhoc timeout") + except Exception as e: + execution.set_error(e) + logger.error("Start adhoc execution error: {}".format(e)) @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task execution")) def run_ops_job_executions(execution_id, **kwargs): execution = get_object_or_none(JobExecution, id=execution_id) - try: - execution.start() - except SoftTimeLimitExceeded: - execution.set_error('Run timeout') - logger.error("Run adhoc timeout") - except Exception as e: - execution.set_error(e) - logger.error("Start adhoc execution error: {}".format(e)) + with tmp_to_org(execution.org): + try: + execution.start() + except SoftTimeLimitExceeded: + execution.set_error('Run timeout') + logger.error("Run adhoc timeout") + except Exception as e: + execution.set_error(e) + logger.error("Start adhoc execution error: {}".format(e)) @shared_task(verbose_name=_('Periodic clear celery tasks')) From 9e41ad076418ee012285c4bf0873bf8795ef13a4 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Thu, 24 Nov 2022 15:25:09 +0800 Subject: [PATCH 382/488] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E6=9B=B4=E6=96=B0=E7=95=8C=E9=9D=A2=E6=B8=85=E7=A9=BA?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E7=9A=84=E7=A7=98=E9=92=A5=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/base.py | 2 +- apps/assets/serializers/account/account.py | 4 +++- apps/assets/serializers/base.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 90fb384e6..3609ca270 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -83,7 +83,7 @@ class BaseAccount(JMSOrgBaseModel): @lazyproperty def public_key(self): - if self.secret_type == SecretType.SSH_KEY: + if self.secret_type == SecretType.SSH_KEY and self.private_key: return ssh_pubkey_gen(private_key=self.private_key) return None diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index cfd9a52f4..c7dad4f5b 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -72,7 +72,9 @@ class AccountSerializer(AccountSerializerCreateMixin, BaseAccountSerializer): def __init__(self, *args, data=None, **kwargs): super().__init__(*args, data=data, **kwargs) if data and 'name' not in data: - data['name'] = data.get('username') + username = data.get('username') + if username is not None: + data['name'] = username if hasattr(self, 'initial_data') and \ not getattr(self, 'initial_data', None): delattr(self, 'initial_data') diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 18432a7e5..9641ce786 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -28,7 +28,7 @@ class AuthValidateMixin(serializers.Serializer): def validate_secret(self, secret): if not secret: - return + return '' secret_type = self.initial_secret_type if secret_type == SecretType.PASSWORD: validate_password_for_ansible(secret) @@ -44,7 +44,7 @@ class AuthValidateMixin(serializers.Serializer): def clean_auth_fields(validated_data): for field in ('secret',): value = validated_data.get(field) - if not value: + if value is None: validated_data.pop(field, None) validated_data.pop('passphrase', None) From 45741610091b017d75121b32dbc45a9e54d92890 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 24 Nov 2022 16:44:15 +0800 Subject: [PATCH 383/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=8E=88=E6=9D=83=E8=A7=84=E5=88=99=E7=9A=84=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E5=88=97=E8=A1=A8=E4=B8=BA=E7=A9=BA=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/asset/common.py | 11 ----------- apps/assets/models/cmd_filter.py | 4 ++-- apps/perms/models/asset_permission.py | 2 +- apps/perms/urls/api_urls.py | 3 +-- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index c9baf8818..f611044cb 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -211,17 +211,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): tree_node = TreeNode(**data) return tree_node - def filter_accounts(self, account_names=None): - from perms.models import AssetPermission - if account_names is None: - return self.accounts.all() - if AssetPermission.SpecialAccount.ALL in account_names: - return self.accounts.all() - # queries = Q(name__in=account_names) | Q(username__in=account_names) - queries = Q(username__in=account_names) - accounts = self.accounts.filter(queries) - return accounts - class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset") diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 7023fdbc6..5b9b1ad85 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -183,7 +183,7 @@ class CommandFilterRule(OrgModelMixin): cls, user_id=None, user_group_id=None, account=None, asset_id=None, org_id=None ): - from perms.models.const import SpecialAccount + from assets.models import Account user_groups = [] user = get_object_or_none(User, pk=user_id) if user: @@ -202,7 +202,7 @@ class CommandFilterRule(OrgModelMixin): if account: org_id = account.org_id q |= Q(accounts__contains=account.username) | \ - Q(accounts__contains=SpecialAccount.ALL.value) + Q(accounts__contains=Account.AliasAccount.ALL) if asset: org_id = asset.org_id q |= Q(assets=asset) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 6f48c05d1..aa61ffe14 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -125,7 +125,7 @@ class AssetPermission(OrgModelMixin): """ asset_ids = self.get_all_assets(flat=True) q = Q(asset_id__in=asset_ids) - if Account.AliasAccount.ALL in self.accounts: + if Account.AliasAccount.ALL not in self.accounts: q &= Q(username__in=self.accounts) accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') if not flat: diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 9a4b3f10a..5fce2cb39 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -5,5 +5,4 @@ from .user_permission import user_permission_urlpatterns app_name = 'perms' -urlpatterns = asset_permission_urlpatterns \ - + user_permission_urlpatterns +urlpatterns = asset_permission_urlpatterns + user_permission_urlpatterns From 99f5c02d844c97e1b3ff0ee008550ee34da6a925 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 24 Nov 2022 17:04:27 +0800 Subject: [PATCH 384/488] =?UTF-8?q?perf:=20=E8=8E=B7=E5=8F=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E7=9A=84=E8=B4=A6=E5=8F=B7=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20has=5Fusername=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/base.py | 4 ++++ apps/perms/serializers/user_permission.py | 8 ++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 90fb384e6..cc6ea17fe 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -62,6 +62,10 @@ class BaseAccount(JMSOrgBaseModel): def has_secret(self): return bool(self.secret) + @property + def has_username(self): + return bool(self.username) + @property def specific(self): data = {} diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 1d795d650..3b60d25bb 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -12,7 +12,7 @@ from perms.serializers.permission import ActionChoicesField __all__ = [ 'NodeGrantedSerializer', 'AssetGrantedSerializer', - 'ActionsSerializer', 'AccountsPermedSerializer' + 'AccountsPermedSerializer' ] @@ -43,14 +43,10 @@ class NodeGrantedSerializer(serializers.ModelSerializer): read_only_fields = fields -class ActionsSerializer(serializers.Serializer): - actions = ActionChoicesField(read_only=True) - - class AccountsPermedSerializer(serializers.ModelSerializer): actions = ActionChoicesField(read_only=True) class Meta: model = Account - fields = ['id', 'name', 'username', 'secret_type', 'has_secret', 'actions'] + fields = ['id', 'name', 'has_username', 'username', 'has_secret', 'secret_type', 'actions'] read_only_fields = fields From 276f64479477c5ac37e0fa78323b247b066d4700 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 24 Nov 2022 21:21:25 +0800 Subject: [PATCH 385/488] perf: gateway (#9121) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/domain.py | 3 ++- apps/assets/serializers/domain.py | 45 ++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index bb705322c..b98cd7273 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -1,5 +1,5 @@ # ~*~ coding: utf-8 ~*~ - +from django.db.models import F from django.views.generic.detail import SingleObjectMixin from django.utils.translation import ugettext as _ from rest_framework.views import APIView, Response @@ -29,6 +29,7 @@ class DomainViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet): + perm_model = Host filterset_fields = ("domain__name", "name", "domain") search_fields = ("domain__name",) serializer_class = serializers.GatewaySerializer diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 64c8c032c..9e495d104 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -4,12 +4,13 @@ from rest_framework import serializers from rest_framework.generics import get_object_or_404 from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from common.drf.serializers import SecretReadableMixin +from orgs.mixins.serializers import BulkOrgResourceModelSerializer, OrgResourceSerializerMixin +from common.drf.serializers import SecretReadableMixin, WritableNestedModelSerializer from common.drf.fields import ObjectRelatedField, EncryptedField -from assets.const import SecretType -from ..models import Domain, Asset, Account -from ..serializers import HostSerializer +from assets.models import Platform, Node +from assets.const import SecretType, GATEWAY_NAME +from ..serializers import AssetProtocolsSerializer +from ..models import Domain, Asset, Account, Host from .utils import validate_password_for_ansible, validate_ssh_key @@ -41,7 +42,7 @@ class DomainSerializer(BulkOrgResourceModelSerializer): return obj.gateways.count() -class GatewaySerializer(HostSerializer): +class GatewaySerializer(BulkOrgResourceModelSerializer, WritableNestedModelSerializer): password = EncryptedField( label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, validators=[validate_password_for_ansible], write_only=True @@ -55,13 +56,27 @@ class GatewaySerializer(HostSerializer): max_length=512, ) username = serializers.CharField( - label=_('Username'), allow_blank=True, max_length=128, required=True, + label=_('Username'), allow_blank=True, max_length=128, required=True, write_only=True ) + username_display = serializers.SerializerMethodField(label=_('Username')) + protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) - class Meta(HostSerializer.Meta): - fields = HostSerializer.Meta.fields + [ - 'username', 'password', 'private_key', 'passphrase' + class Meta: + model = Host + fields_mini = ['id', 'name', 'address'] + fields_small = fields_mini + ['is_active', 'comment'] + fields = fields_small + ['domain', 'protocols'] + [ + 'username', 'password', 'private_key', 'passphrase', 'username_display' ] + extra_kwargs = { + 'name': {'label': _("Name")}, + 'address': {'label': _('Address')}, + } + + @staticmethod + def get_username_display(obj): + account = obj.accounts.order_by('-privileged').first() + return account.username if account else '' def validate_private_key(self, secret): if not secret: @@ -79,6 +94,15 @@ class GatewaySerializer(HostSerializer): validated_data.pop('passphrase', None) return username, password, private_key + @staticmethod + def generate_default_data(): + platform = Platform.objects.get(name=GATEWAY_NAME, internal=True) + # node = Node.objects.all().order_by('date_created').first() + data = { + 'platform': platform, + } + return data + @staticmethod def create_accounts(instance, username, password, private_key): account_name = f'{instance.name}-{_("Gateway")}' @@ -112,6 +136,7 @@ class GatewaySerializer(HostSerializer): def create(self, validated_data): auth_fields = self.clean_auth_fields(validated_data) + validated_data.update(self.generate_default_data()) instance = super().create(validated_data) self.create_accounts(instance, *auth_fields) return instance From 608e0c9f26b81e734a7a7b57433fc4b0dbdb82ae Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 25 Nov 2022 18:06:22 +0800 Subject: [PATCH 386/488] feat: support ed25519 key --- apps/assets/models/base.py | 24 +++---- apps/assets/serializers/utils.py | 11 +--- apps/common/utils/encode.py | 107 ++++++++++++++++++++++--------- 3 files changed, 89 insertions(+), 53 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index cc6ea17fe..70c6b54e6 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -1,23 +1,22 @@ # -*- coding: utf-8 -*- # -import io import os -import sshpubkeys from hashlib import md5 -from django.db import models +import sshpubkeys from django.conf import settings -from django.utils import timezone +from django.db import models from django.db.models import QuerySet +from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from assets.const import Connectivity, SecretType +from common.db import fields from common.utils import ( ssh_key_string_to_obj, ssh_key_gen, get_logger, - random_string, ssh_pubkey_gen, lazyproperty + random_string, lazyproperty, parse_ssh_public_key_str ) -from common.db import fields from orgs.mixins.models import JMSOrgBaseModel -from assets.const import Connectivity, SecretType logger = get_logger(__file__) @@ -88,7 +87,7 @@ class BaseAccount(JMSOrgBaseModel): @lazyproperty def public_key(self): if self.secret_type == SecretType.SSH_KEY: - return ssh_pubkey_gen(private_key=self.private_key) + return parse_ssh_public_key_str(self.private_key) return None @property @@ -97,7 +96,7 @@ class BaseAccount(JMSOrgBaseModel): public_key = self.public_key elif self.private_key: try: - public_key = ssh_pubkey_gen(private_key=self.private_key) + public_key = parse_ssh_public_key_str(self.private_key) except IOError as e: return str(e) else: @@ -129,12 +128,9 @@ class BaseAccount(JMSOrgBaseModel): return key_path def get_private_key(self): - if not self.private_key_obj: + if not self.private_key: return None - string_io = io.StringIO() - self.private_key_obj.write_private_key(string_io) - private_key = string_io.getvalue() - return private_key + return self.private_key @property def public_key_obj(self): diff --git a/apps/assets/serializers/utils.py b/apps/assets/serializers/utils.py index 0734bc9f1..770710843 100644 --- a/apps/assets/serializers/utils.py +++ b/apps/assets/serializers/utils.py @@ -1,9 +1,7 @@ -from io import StringIO - from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.utils import ssh_private_key_gen, validate_ssh_private_key +from common.utils import validate_ssh_private_key, parse_ssh_private_key_str def validate_password_for_ansible(password): @@ -24,9 +22,4 @@ def validate_ssh_key(ssh_key, passphrase=None): valid = validate_ssh_private_key(ssh_key, password=passphrase) if not valid: raise serializers.ValidationError(_("private key invalid or passphrase error")) - - ssh_key = ssh_private_key_gen(ssh_key, password=passphrase) - string_io = StringIO() - ssh_key.write_private_key(string_io) - ssh_key = string_io.getvalue() - return ssh_key + return parse_ssh_private_key_str(ssh_key, passphrase) diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index 2bf02ac4c..db5aee795 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -1,24 +1,23 @@ # -*- coding: utf-8 -*- # -import re -import json -from six import string_types import base64 -import os -import time import hashlib +import json +import os +import re +import time from io import StringIO -from itertools import chain import paramiko import sshpubkeys +from cryptography.hazmat.primitives import serialization +from django.conf import settings +from django.core.serializers.json import DjangoJSONEncoder from itsdangerous import ( TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, BadSignature, SignatureExpired ) -from django.conf import settings -from django.core.serializers.json import DjangoJSONEncoder -from django.db.models.fields.files import FileField +from six import string_types from .http import http_date @@ -69,22 +68,19 @@ class Signer(metaclass=Singleton): return None +_supported_paramiko_ssh_key_types = (paramiko.RSAKey, paramiko.DSSKey, paramiko.Ed25519Key) + + def ssh_key_string_to_obj(text, password=None): key = None - try: - key = paramiko.RSAKey.from_private_key(StringIO(text), password=password) - except paramiko.SSHException: - pass - else: - return key - - try: - key = paramiko.DSSKey.from_private_key(StringIO(text), password=password) - except paramiko.SSHException: - pass - else: - return key - + for ssh_key_type in _supported_paramiko_ssh_key_types: + if not isinstance(ssh_key_type, paramiko.PKey): + continue + try: + key = ssh_key_type.from_private_key(StringIO(text), password=password) + return key + except paramiko.SSHException: + pass return key @@ -137,17 +133,68 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h def validate_ssh_private_key(text, password=None): - if isinstance(text, bytes): + if isinstance(text, str): try: - text = text.decode("utf-8") + text = text.encode("utf-8") + except UnicodeDecodeError: + return False + if isinstance(password, str): + try: + password = password.encode("utf-8") except UnicodeDecodeError: return False - key = ssh_key_string_to_obj(text, password=password) - if key is None: - return False - else: - return True + key = parse_ssh_private_key_str(text, password=password) + return bool(key) + + +def parse_ssh_private_key_str(text: bytes, password=None) -> str: + private_key = _parse_ssh_private_key(text, password=password) + if private_key is None: + return "" + private_key_bytes = private_key.private_bytes(serialization.Encoding.PEM, + serialization.PrivateFormat.OpenSSH, + serialization.NoEncryption()) + return private_key_bytes.decode('utf-8') + + +def parse_ssh_public_key_str(text: bytes = "", password=None) -> str: + private_key = _parse_ssh_private_key(text, password=password) + if private_key is None: + return "" + public_key_bytes = private_key.public_key().public_bytes(serialization.Encoding.OpenSSH, + serialization.PublicFormat.OpenSSH) + return public_key_bytes.decode('utf-8') + + +def _parse_ssh_private_key(text, password=None): + """ + text: bytes + password: str + return:private key types: + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ed25519.Ed25519PrivateKey, + """ + if isinstance(text, str): + try: + text = text.encode("utf-8") + except UnicodeDecodeError: + return None + if password is not None: + if isinstance(password, str): + try: + password = password.encode("utf-8") + except UnicodeDecodeError: + return None + + try: + private_key = serialization.load_ssh_private_key(text, password=password) + return private_key + except (ValueError, TypeError): + pass + return None def validate_ssh_public_key(text): From 0f35b3dd582b44bb6ba25cb684fe45da816b500d Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 25 Nov 2022 23:09:55 +0800 Subject: [PATCH 387/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/mixin.py | 3 -- apps/assets/models/asset/common.py | 4 -- apps/authentication/api/connection_token.py | 17 +++--- apps/authentication/api/perm_token.py | 0 apps/authentication/api/temp_token.py | 2 +- .../migrations/0015_auto_20221125_2240.py | 49 ++++++++++++++++++ .../authentication/models/connection_token.py | 7 +-- .../serializers/connection_token.py | 10 ++-- apps/perms/api/perm_token.py | 5 -- apps/static/img/logo_text_white.png | Bin 8320 -> 5027 bytes 10 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 apps/authentication/api/perm_token.py create mode 100644 apps/authentication/migrations/0015_auto_20221125_2240.py diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 9452a76f5..f7f788e72 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -68,9 +68,6 @@ class SerializeToTreeNodeMixin: 'data': { 'id': asset.id, 'name': asset.name, - 'address': asset.address, - 'protocols': asset.protocols_as_list, - 'platform': asset.platform.id, 'org_name': asset.org_name }, } diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index c9baf8818..c7012bc60 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -160,10 +160,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): return 0 return self.primary_protocol.port - @property - def protocols_as_list(self): - return [{'name': p.name, 'port': p.port} for p in self.protocols.all()] - @lazyproperty def type(self): return self.platform.type diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 1636c2cb1..59b0b7593 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -15,8 +15,8 @@ from rest_framework.response import Response from common.drf.api import JMSModelViewSet from common.http import is_true +from common.utils import random_string from orgs.mixins.api import RootOrgViewMixin -from orgs.utils import tmp_to_root_org from perms.models import ActionChoices from terminal.models import EndpointRule from ..models import ConnectionToken @@ -249,10 +249,6 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView serializer = self.get_serializer(instance=token) return Response(serializer.data, status=status.HTTP_200_OK) - def dispatch(self, request, *args, **kwargs): - with tmp_to_root_org(): - return super().dispatch(request, *args, **kwargs) - def get_queryset(self): return ConnectionToken.objects.filter(user=self.request.user) @@ -269,16 +265,17 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView data = serializer.validated_data user = self.get_user(serializer) asset = data.get('asset') - login = data.get('login') + account_name = data.get('account_name') data['org_id'] = asset.org_id data['user'] = user + data['value'] = random_string(16) util = PermAccountUtil() - permed_account = util.validate_permission(user, asset, login) + permed_account = util.validate_permission(user, asset, account_name) if not permed_account or not permed_account.actions: msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( - user, asset, login + user, asset, account_name ) raise PermissionDenied(msg) @@ -286,9 +283,9 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView raise PermissionDenied('Expired') if permed_account.has_secret: - data['secret'] = '' + data['input_secret'] = '' if permed_account.username != '@INPUT': - data['username'] = '' + data['input_username'] = '' return permed_account diff --git a/apps/authentication/api/perm_token.py b/apps/authentication/api/perm_token.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/authentication/api/temp_token.py b/apps/authentication/api/temp_token.py index 6e640edd6..2fa5791e3 100644 --- a/apps/authentication/api/temp_token.py +++ b/apps/authentication/api/temp_token.py @@ -2,10 +2,10 @@ from django.utils import timezone from rest_framework.response import Response from rest_framework.decorators import action +from rbac.permissions import RBACPermission from common.drf.api import JMSModelViewSet from ..models import TempToken from ..serializers import TempTokenSerializer -from rbac.permissions import RBACPermission class TempTokenViewSet(JMSModelViewSet): diff --git a/apps/authentication/migrations/0015_auto_20221125_2240.py b/apps/authentication/migrations/0015_auto_20221125_2240.py new file mode 100644 index 000000000..7b1c073e8 --- /dev/null +++ b/apps/authentication/migrations/0015_auto_20221125_2240.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.14 on 2022-11-25 14:40 + +import common.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0014_auto_20221122_2152'), + ] + + operations = [ + migrations.RenameField( + model_name='connectiontoken', + old_name='login', + new_name='account_name' + ), + migrations.RenameField( + model_name='connectiontoken', + old_name='secret', + new_name='value', + ), + migrations.RenameField( + model_name='connectiontoken', + old_name='username', + new_name='input_username', + ), + migrations.AddField( + model_name='connectiontoken', + name='input_secret', + field=common.db.fields.EncryptCharField(default='', max_length=128, verbose_name='Input Secret'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='account_name', + field=models.CharField(max_length=128, verbose_name='Account name'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='input_username', + field=models.CharField(default='', max_length=128, verbose_name='Input Username'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='value', + field=models.CharField(default='', max_length=64, verbose_name='Value'), + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 058d07581..d0a1d8478 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -19,6 +19,7 @@ def date_expired_default(): class ConnectionToken(OrgModelMixin, JMSBaseModel): + value = models.CharField(max_length=64, default='', verbose_name=_("Value")) user = models.ForeignKey( 'users.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('User') @@ -27,9 +28,9 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): 'assets.Asset', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('Asset'), ) - login = models.CharField(max_length=128, verbose_name=_("Login account")) - username = models.CharField(max_length=128, default='', verbose_name=_("Username")) - secret = EncryptCharField(max_length=64, default='', verbose_name=_("Secret")) + account_name = models.CharField(max_length=128, verbose_name=_("Account name")) # 登录账号Name + input_username = models.CharField(max_length=128, default='', verbose_name=_("Input Username")) + input_secret = EncryptCharField(max_length=64, default='', verbose_name=_("Input Secret")) protocol = models.CharField( choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 77981cd4a..db6b35963 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -15,15 +15,14 @@ __all__ = [ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): - username = serializers.CharField(max_length=128, label=_("Input username"), - allow_null=True, allow_blank=True) expire_time = serializers.IntegerField(read_only=True, label=_('Expired time')) class Meta: model = ConnectionToken - fields_mini = ['id'] + fields_mini = ['id', 'value'] fields_small = fields_mini + [ - 'protocol', 'login', 'secret', 'username', + 'protocol', 'account_name', + 'input_username', 'input_secret', 'actions', 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', @@ -37,6 +36,9 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): 'user_display', 'asset_display', ] fields = fields_small + fields_fk + read_only_fields + extra_kwargs = { + 'value': {'read_only': True}, + } def get_request_user(self): request = self.context.get('request') diff --git a/apps/perms/api/perm_token.py b/apps/perms/api/perm_token.py index 63cf08062..e69de29bb 100644 --- a/apps/perms/api/perm_token.py +++ b/apps/perms/api/perm_token.py @@ -1,5 +0,0 @@ -from rest_framework.viewsets import ModelViewSet - - -class PermTokenViewSet(ModelViewSet): - pass diff --git a/apps/static/img/logo_text_white.png b/apps/static/img/logo_text_white.png index 39dea6778055a6d3bae745d3c749989dad7240a5..f791baa71a2599846091805fc5c7027fe43857bf 100644 GIT binary patch literal 5027 zcmeHL_cI&-6D4{%mm@k!kf!96(>c*OoYPzM9?{F`1gE#?ov7iIBx;0cmymEeIqlTw z(V~m;`TmaY+nKj-c6NVyGjC^hW3@C@0F*401Ox;Cn5v@AEg#-Oh@9v)cjU9P-4daf zj*2|t^iRIf+k(Pf)!2)GfQs(FASB4mXCfdVBGA&%Q@U;5{6GG$!2hZOWKb&dTeA>& z>uBl{P&^L~)uOpWDkv!~A`R1JWaCj*iFgbHMb4tGwqO~ z#bxAWsd*V#BT4L zQ`Xs6E@bQbuzl;3OVw0wTJiG5ctUM!LuK?tOkG^kkDc$7AJ_ev28Y0ZX2SkXzW=E| ze6*VCgK2YXF%BHC=6&1|k!h(EUQmdHdYdhEjl3Hxjh%HWN!Li(?vI)as?M(YDwAKI z{KeL@@G}7cPynVVr{|ZylN%^KF@4kbqAUlb3ueY(vzY`rk!I2Tj}@4#<0u4IQtw7- zp){U|z>RqD8R~gzF2RP}#+y893#x6~^IyGwykyp?!`r!;rv*K21gETYWDJ*n_i;a4?n89(25)5s@mZzo>pew-CSqA~ z{b+OkR^>r1#yGWN(;9tjD}^Zm@1=|pMG21`gSV6u>o-Vsk6=;Cx{2_Pv#|+{>A9Y< zxJzTiYaWb=_Gs^1(998YGxCH6!hW3NRFS)Kpjos>;2^$@9cx5-dR+A+lC17p3C)hj zLmlc`fYBFKtYT`p>r%5o%u&&UyFZk2jN9KW=Fm|q5e&?l#~RM}ye686hrKVe|AOkSdX%L`12zNCY^8{%rTJ1d!^V zSDh>LkM`Au+gO)nH&sS9bX13IzHmV##7r-h_toXNr#|JQk}h``?26NrQYPu1JjV*E zq%sh`ul};mCy7bZAs31({h{&h{j9R5h0vai7^i)7*8P{g?lLvyCqei+aTX}f$p`ul z>DvJ4Tzu3M8!KgC>TGFtar#P8I2X$~9!f$HU6a;bI*_2n-&gdUs32#`$O?@CabGy> zxDrf)C6<5qD&Li>ImuL5qBOr9%Q2^^@kkpQN=|l@bY*S7-iXO&H-Ez{cQ0vI-IL6R zRM;%GR9E7##5cmXtG;D`?Ljz(Q;*C4iBZ7|RF3TT(UF}529oGBQi~_na_Pd)TS9;;5Yb<7s4vO3s^!?a}@Skc?{|57aa}{fr2xkp;Z}5S^7lko$b$O#A3;djZ465J2H3Rw4a@u18XHqRWDIO( zXuDC%0gX+sQOI-#^$LBv6;rMedbR?A@)jYSpRb9D$uh~qdS!?=(lCpl7@KFRtu2WA z{@27Fe|%zWU7CKv>f_@h#Hy@W9@1etG}74{w6LkDLbiwRB^%S_W7Ikrp!O zK^=s6Ge^JUzh>{KNbQ%+hJ4LXDTF2X02p5rkCl~Q(qpPygxpnpBDQv7_%LWEF7c^T zf(u2VaL^NQ1RxUI?<)#F1Ze|snfX3M6T7~p&A+~=i`vFIY3Wr}p* z@OYORa?VvZ;rs2OMag-B=%ztNSy)&Ie61o`~Z zZ2(Q@E;4h-YuMY7(Yjb!p!ICdnv(NCJJ$5RTWY#-%&u7&)c2Gyes>zGW6zPydoVut zmE@k{0JBSQ8Ae-oy%=kdj3=u<*m_?M%ZZfMGI};wV>v~fHnjJbh@$u{Z`GV$eD5Eh zGzMDFEagCefERvLCs1n*N!D`s*lUuUt)OH63KlV-wAwtm0@y^&!De0PyjX7d?ZrXO z$d5Kn#Y02q1jOmw)Khx+mdh#Ih#L z*ZyeVH*jv{vDg`SOW(FK5@;D6s?^8egc%L#m+#NlzT%t{31+@sn&-PJ3uZb!DcsgR z0Ud)BX@CVyox49QtGI!Tu>53@$=Y7A+*hl-OV%eE2FH}`ZX z$C?-=bk}S*185yxM_@KI^d4WFsqW8IjJ(Yhy#L1EEeqv8dcq)=o5LJ|R+1rnsxh6c z#xXu4TY2*u;+zX;Yz-kah)=x(S6YMR;BIuZxJ0S6qp|vuRJl7^^;;QBuc<#-6SdQ8 z=Y&m)xVQ}|zx=pooXod=m4mo=&tWtwm%T%-dT$Oz%r8%tT`M~m?#T^#`y)2AcwO5- z@$npxguN&Fp{A5omK~$dk!ud{GGYG{8YOWqp)x}mx2GdO--@frKq4bhOs+qoYFsu_;U5CS?e*(-)tusWVF=obBRr?Ko!4Wrb{gToF~MyMERTQ$@-+2#;Nx&G2TyYDE{L!y}{8kvOCWe zKlKR#G!L+;MN0-V1w1kk3vES74BYvaq+~UpBB6&P&UfpW zHr92k;cbs}xH`KNHbRI_UY#tQMbI=X&;Sj`F5UqfAe$_2$M>;a+L=bpYe*R?91U05r$W05{e=?GJpghLs9tsqWeuoK;pv zs|F4o8$-Op^!5)%JkM8y6c>T)XTCCx8}W-}?d9^^F%B-b6uhKjXw*!&W%* zu4>j2_Crlx9D*YB!P-nn8zig?xkCy;z0=#W&l)*l9f@~3GT@L`pa3BxNu%-c9WSe@ zs^$$0maVcksfzgl;o#5Bz>s1~AN8>9Rnehi(ny`>I_M}H*K=mP=+tZzyv4C#W5$_G z48QAeeKD{0xPr5vNOQa1f_K7Ph;^4Oh`+nq?r!H$+s7_^V0B6OLbSguV<#D$zLNl2 zXasoVzj?ws!KA7`Ks^#;+2L)ToVWKT4|KM=t@f3#sq3fL{d3P zJ;#1|@|!(C$hBJ|?Aq@fXkvTnO+NFkl01j@lg&CdI9z?05)ZATjJK>VoXG?fGLd8? zvqmw?UE;3*G&P}AVR=Eib%>mN_JA)KmJYn?PO zKhNU{Z=~k;rZcqaaKI{1!A9O(**X&tgbc|}3?SUU`V9^;T&wSoSI5uI(Q2ASL^CY1 zVwP4Z9=7m3lhoAF50s}xs6B7oyJHT92nh-K4QtsCI_y@e;Z&3O$-Px(%Y>>#tmjMb-dYN-J-nni~KS8-leMs$u*HNsk~={+#5`E>)+p1&%9QlCvTAvw2Whk ztM{RuR-Vv9#+z*UoUmW3zO`n2(OVVD9PV>vQ&UDHYa;`Y1m~wbjK^UsqPc$a`Yy2I z0e@!*G4I20owaj?BrftevmnId?Lzx#wj=#@K8eUX4XBJ&ix1kpvB5oHrjZoTxI30M z@@)*s`s0crK;(3$@PGs}Ew3F9~OEoEh6yq*Us?EwBJF7saMsc{v#?X}F_I#*o1eNodz-!$`Or%ci{mA1~TI3+kU{Pved08`Rb JtdqA4{|^CbEnWZs literal 8320 zcmbVybyO5@{O;2AMM_#iS{kHNLJ*{+8)T(f5Rh&V=};t>Zdd_ zaDVsQzwf!{%-J)$XXl-FKKVS)XQH$;lnL=^@IfFDp~@RYZ4d~}3ON6Oiw%6XxJitI zK#U+2MLAvX%>Ar@PbTYGo9*9zP`*vEo=tI5_QX}e{mdd(otjt^5MmuM`}v?_8~!xy zeMz_ohP7(*%Mw;6$!5vLA-qo#k|pnFG+q>Cjq!|R4-KRjf0|FHx10+BfyH~De_>(i ztZ`B?sG7RntqqcDiu$o2`sW)sVtF&uFLTSM)xe4dv&Gjb+tM=brkj60H-`L1$c zvE4|FDfa_gUzgZV;a(?C?c_VXMA*1ftF@D5;bt^_YMvo4tK0h6vKnSUo*+z2-%SHk zj+hck{!TqJ5^q=#Spc$}qP-u+Rz3_n_h4!~%etRfx!hw*638BIZR}s49leMm0d9ak z#~G=RzIOkL?L2MqgCH*jepTEB(f&I}cTSSP8q@bVBfGid+n<&f_qHB%=h@y6V^-ta zK^ZTw1C9e74G@Vc;e9wt3vu;1Pf_Cm6ub3yywK&5ebk$T#?#IHG?a^IA8|dhn-?_Y)-fs(8lCyC1?Vi8sJ#VZzTRtk5=a}&hKhaG6WmNY4G$wpu zm@TwT3%02W+>^#`Hri14g)S8sJL`bmB(qY|p}+sfN*XBzTfLYWv?;cj6{6W2jt{cXbbHHPZ_CmT`0g%iD(;^tTW!y1kbCMM zl-oF96-?K!_Nv61;gs>8plZ!Y;qtB{bLU2g$$Le}^{nEVR30D- z^R)RX(W~b%d8?lSd@4=nyrpRT3zn_w@Mqyc3(`_!dxnqBPx^v+{CIJj_I<25PS$q~ zQ6P|VG}+s$$Log1%JGzH><5Zh`quWM@3y-YqWmvlS#(MY<%a(D%FD>O3K}RQt`+MD zOXSOO6K4!P$fFD^E#pmFBt6=^{ZmvZV)#p4Fd_gSFc2UkDB}| zE|{C3*#U7l?miLW zR$zjDX=LG3o2gY@rp9M37iERvsp$&Yp>E~~4fZbb;;%2>%P^?%9T@DqG~skcA##UH z>W1gqHnoo<3U8!H2$JhEHNzs31t>=O>->zRcsA%=m}#(anSFz z`nh&tz#i*6hX2u(^ez5k2I?_UYpBKiNy8kXZ^K*B;dKr#1%YoXZ&4lJigf=nqS{51 z_Lgmm*q83CR5y(rNkQhIPXOjksOaK}AX}drL4T7~4O;Bl!veU#i*qFwHTAga2 zp~;zzhLaOdBVq7(Yswtq%Gq+{kf*J`f2ms1|8wa;DmSIsIjrEav!c~`RZZxqcJ_nX zq%gnU@7H=b?2!bGRewBRgBo`%Sg|wqavHh~nIaeUZ^?B2g&Wj~EY0m67`k}TA7KZ$ z7R6PK-sefDTfJX?_gj9DDn4*z?>}HR)pGbZ9gry?Q?~o2>(j3 z#$}c2PBgvy=CD`Ol!t~gI-f&zC>2nrMI>(y&w-7wW(jrF`h22^YX&km@8a}j42j4G zcPBPc+kCwHtX^MDv>ALP9|%Qb`WqXhSRSN_c7%S#+CVlO`0*b~9OM!z`GxqQ4 z^|bN$r?Vu@tMrY*$Dk40YUu*qw&+GQlrl92F%B`7SHff$HVE3$dLxg73GhJf!_}R<4gtc`Rstz~ zWoa3ZY}xe#Rq!IHvT|q=_f=`U2WCeMO)ol#=khkTx9xkWkf8-pCHgEj(hal=`V_KT zkR%pl#tu@m-y6rRln{9HeGtRj>^*Ns_~u6)4-Hk0bCZ7aj|f-RA^G{n1GIKZ(NI~t zo`6!!V8*MkbB?Qk{A3=ai=sA);Ofu_pZV%A%c6PptEE8tuvwMO=7p!w*`7RS-mFZT zK<~*qDHL}o>)YWUe(v8!&i*3R|b-rn57s0jR>{tR#UPMk4SlqLFHsb^x%soUU z`HSjQu-Duw)~t;GAk|^ch8ir>JL4Ezy-5w$eA#=DV&|UFg~E>&0M`w@r5V~}pqBNj zK9r8F&aty&KN*g#CVDeaeSyn4P?sR!$*{WlqZhaqs@IDS-Xoy7i8kmn$1~A+3Etz1 zT_!X3Nw=>x^5QuA2P^a90`C#8UUvU+NUyp%Or-+P!_MLUmso_SG*jN$+LvDY$Bf`T z!nlBHheZsU4V>PYY4V#!n6WLe)@f`tgTTF?%&#GfW>G3IPAcw-Q(411`w{J;J-goO zmBpSdQrpB&0`2Zh{Io9coEw=0Wr`*_XYU z_;>TOW8trORC;H~CQo%0T9lQAha6#DnG*-^zT$$__{IY<#xFjmfR%ndYiF6yki`uu zeE;l_l+KkU^?v40gI0M}cL;ISy&XvTYfv_M`UO>jDpUN?#ktr|?SiG%oSi{u#FFOtvkhbJQ(>Jc77}vg z{omlp9b2!k{GG62v`Z)~Y)!1ZI%jaQNi(cn36Vh*+`hugKK4%?N&7`VT*B?rf-+b$ zET@E~b7%QQCgQ($@*2Wy`K%mc?uq(`|Ahtd8S_{aHsGU7>xE|T13)sxc}vi@nAN}R zuFuJ(d0RPzfF#e-u8^xk2?pZQ4_kQf=mvM}#s^p=CS!Z>&4u>CIoODMZL&qs-D41YzO+sD6yk@};%R1AD2m+(alz2cPjs0%m=vsH{ql6q%U) zh^0h}i$7DPSlw>v>b*0M^;{se1lG53?OrAURFDUtHaGWd-!xD)@+6vnP<_JGU7k+| zQx#tTIqPvexTv2sqM35Y;TMLXxA@%4{?*MGpKjvFg3TqgSjMRo`?6znVC6?!(CsN?TsTe-26M^G%-RhhU- zGw5;n(R3{H6u2-q9NjvCN?>=wE2eOZA}JgsUnkvb8Jt&cu49%tgJW2M36TS6-mi<| zXcuafqL;_`Hy)C&QKvL>GFX9&_qf3nT(Dt>(s6Dj_3Z-~SAPNn`G$*`ZEcVS=g%7( z<%bfG|6kgZ92#Kjbq-&EU|f)Z-np+Hyq~o^csbhf9y;3MvLNkWMqnyF#~K-g)mGz& z)zOS(Ged;Tca)bYXrNuuU%}3yli~;1)6Zc=m2;rBIs4BX4fb=p$c>NIA755saPBMV z{SBf-N!{mmUQd8AEnYmxGATfO~a=h`v?MTi2vD7H{%XIQh@UF%k zs0Or$EKWJ!JT?0$T}FBs0l!|k6&_giBh8u3JBU4f$1UvR{|qJbfD05vt$wy-K_EaQ~vxfK=yD4uYp`-o<1WgqxK^k%zMhQ-A6OmFJ8dX z>2;K>HMf|`%yD{+=*%)*+X|^y&teEY%zhvV{?l8`_U8$QWNN8(qK{7A-e=W$r7*%) z^m~!hO{9y8@#%gK2ql{=A^O%|1lKZERkCrd*lgO z(W-T^th{n&{bUc<`r9pYeOHxdSL;wea9 zIhKC&%_lgq51vqiWQa51tXa@5hqqYP>drevNf}}W#RWVwoo*)T2$DZ#)1D56anPr7 z=$Me)DTCg>iFl#aj~XqLsbl44AKhL?zZ%A0cm6mGH-28kN*0}IZm{394?1GxZE+_5 zy$gGtArN9Vp=}@aaj$kJTb!QI-(qmRA!Vm`2GR2TfwJhES5w6Wd-=P?ctc5v%8I5} zIR#oTRx9jEEd@TI?lh()X1V)o|IBoH%Ut#i{s;yLHMrUIX;^bOx4}`>nMv&2-9o7) z`jzt?3Fv{&v?M^f;U1-+WidE)V*Q4|N_Yv!U7+og74aHtkp z9y8y$D!V|y%MItpy*9*3#aD$|c7#)qI>ghfUMxF)BfzYdE76Q4j6EA*11AWrNaGxh zB%Ao*xQKHO*eawK7N*AnnEulvDK5BE@B_Gk*Y8;jj1FDze{bpyGj;oEv^C2OAHPm= zWmU!##xLQ}+P@|4XThi>YQdgUUlxHwL|_<3df;T^zBrlE-3UI2T;>aM&**p}VCNC9 z4;x?kRzJT*0u`MC;QSR)Bbimy%F(q^TY11E*~5djnv`0PTI4L`tA16EW>_8peWofi z)iNe8@rxo)I|1Zk{K8YSFh2RB8zuZ*R1L?~To(44C$A@gw z_6V;vjpB+oZIg~|S_)};KoHq-8#?B-hVQ1C^C86p{E(G~FA{@J8I+wKRfEqe+8*GH zw(E>czPd|=+J`Ta-FVyzW_PFRKm2;@9L$?^E$kSfB5Jq0Btu3>4pCa2o}SMlqWyH! zoi<-9o;`dZT~BpaJvGw}GBEgWuF1uRxg`-n6Q^lFC!4|#jivj~R{Ju6Fjj0~s+R(% z%nI$0g{dljsz^bCge+T_<_wY0C@lfn8lmkX^jLT{{R*lzL1WuMVGQ9~)#1>oWwP6> z^Kd9-wl&M|z6ANB3I3teZJ?U%&%r)o$6ue;eJJlq)fyLt9pg8QSm;edq8KZ6)9SQd z{3;a9b#V6Vpgmh`@7Z3Owiaw(03V=ygkw(f{EJZOO8OO(arJ%}+dY2h>AG#&FE-D8 z!y2c@?9E-gcBba*7OLJJV+TaRZm^vJh9-ePk6#<}y7?AWva^`U6)Rl(atSv;YdSpY zO*%%|_Nodompe|O!Q0DMW;oE~t3CarD`L&uYw|S?c#l*f{#upIN@|!V;&`&-X@f!f z!ubrg^ZVSjKbP~s*y}7i>iQa@F@GRu zg0PWX+`*p>wNC3EP*f<=Q0Now!&VxCbSE422(WUiIUO=Az{l%awwyU*LnAPS!sf{@ zLToD=22Rt!s;YTP5D!3N`_dQS$G$;G_XBzyx#gDaxN&XZIq?)(9A`~DaeCC-^ zajPh-2)$-Yv2CZuN|-lE$WZ7EahSI)4;n;5pBaE5_Z;(`dUjGf`cz{?HOsmWPr{U! zZiKG>UeO(uIvj>dCbGDi*I8l>FQ5WT`^JgW!{Cg8Mn#J@ARIIlUn#6fQB#-!UR2~j z8ARYCw?J^-!06y>6Kv5>{GHA_@IR!?kvEa5SSL7;Ti(RDq}bjr327?RlzUiUa89^j zxzA5W9@Y~f_~-6)drfpl552;QX^X7>f^fkU_T{m8V5Cogx%M_%7Eb-h$23>HgyDC_ z=Wl|mt}^f6?xnBo?z%@g(z-$`q$< zPAC-SoT2VHhriK*W&AQbQuTUy=b9YE%2k#S+^nw|wt2-BX3;#iyMY~q>lFf1gm)`B zXRwAER!!T_DbmnVxhmFd|4BMNg?`A#Sp-lV0Q}i^rMEKHy@j;g(j?oZWcW`?uZ+_5 zo&j(Pyn68OpOWZhN)-JLt*nE;`o?Jz;h9`@vPqxCUw?Wu%3fnZ9c~=fs;2f}f22E! z=mZa;Vv65keW*);xEwc>+B~#M!H5{cw8J5wmd@UCuh@_#!LY<)g7gdrnVzhC8&N4q zPx$mvpIq65(%t1W<@Z@XvU8e0OYbxpTYncbhfr1>&VZYg^UbU<@ztywG?qjlrUBlllsv3hr#q>5kISJuHPl?(^RJ% zS^2mY?oum38GZw}zh}hNmq}<^E3g*kha>rjGys{1!+;m^RCXC?_&?JJpfvkcKxLpk zXfjuBe$|TIII+xEm`Ntg!Ub%Jg3=Xca)Np!GliG$m zEe2rkuQoN1N6s-8j*=Yz+Ci{lOQF!WU4DN>lz@L)u5`YX%lGvK#l>SI?Lo>tDr_zz zloo(yGeX)~kpgHVo=j{~q8lfli~#Y|91L0LjX$M=v&y5W;C&o+pOb$h zT?I&9>Y<+g4Qbx&5Or|IIFi&_7EdLpXf!pC@XT6$cJ*a+mLiVlQlj!~-l6VSqQ2ud zn_UdlzdG6-BYAi}g;Su<=a(-w0B-&ki|gXcMsXcxbd{}Y+VF7E3en`Hmmsf{I7wO@ zUm;1e0jIR+LPH)QFM1@4NxJ!Ej+Jiql=kwEu708TIe!@!N!zNt(*VQ0Q z$0tgl9?>Zf8;kR8O1?#+H#ND=N?Yyk!Toprdl^9VoU2Dw@nVN8s|VVNt%`cl;z zSOAcAYA`}SftCtvAP7N2Iws%2>f0F}a_CszOx`2j!xb1zFULErFi)bX! zsja}84_VqF0oviH^Nz8}hTi}>h#YsCHH-6EI7-XMPz`v@Af10$LG3*b>Q7JjCd*}n z+(d(`kn)=`qz%s-LH8236b+p6JfJ4h0%SG5NZ4)>A<{iiGYDF-*VVIOdC$(8sj)lT zPy0L1KmP(rjnHps3wc2?kI=p2>ug#$9jimDww8(LOP4U{7uv}DFWW))UlK79g1Dj3 zdG!#SB-tnei_>%DW&8qE=f4w?y+JooIArd{Vnp1@`y<_VCLLM+naJmOg_R~$QsfiI zJI^%a=Z=-sgHUV}gKN3qSVu-+hLc#*KY9pg$*UO5lnZu%tf}x?KQQ;5W2AghEcjoC zB-lBjyv7g6Gup&`G99y!>@;l5VDa2gZ#4 z4be}Mvf5R~k(;%Cnxa%FWfnc|j?~!H=w!E|+Jb`BngjDz{iH@;K)rl#@-UKp6DLOU zn6uOCTNfpicRmV^#FuDnVm@N zfiY^+z32QQd;WXqFqtDZX?HhkOgn=+`!F-L_>uTh z36Kp8o6=Hs(?M_bxZK=Xr?^=nqux^J$#Dw;B6q}(3rL6>XOLc%pGm6(<0Ik*MnAK5 z#>_l+vqw7fPApSW|NH-gQ+Um$*>utryBqk1;@?%W%LiA`KvPe~M*&?J0f6`syGlvc zmQYz937FB{~pJXV(8txqIs^Hv?T*}(sYgjZ3U zST1GvJxh6ePYrlsh;8-3QZzlb%&M9iYH0<95(Dg{3IEtK2D3-*Sc1&z~HoGn~ZUa)SScil2uOahfxZ7x!b`0 z>;j}dc02Fo1Up?tMG{a+eX!cCIO>7m1jOCU!4FF;?4O6ro=d2OS^tbs!EG>{r?$V*6MP+1%|0_<5FO3g;WewuDc#hw}3NG2doMO z0X$a8b6G-0)JjLDDs9sb7!Ng()<6FG(aV?kTs2IUMItrOeuvwfkcpKX4lD{L+|Y&z zBUd2gfFW?xU`pGkYQseQIu&5({Xa)b@|`vCN`U0!y8!?9$N%>eQ&m)u3SGq*O%$gZ Qunb7$wT5D)yk+SB0^iX+pa1{> From 2c8f51940af73fe614bbdd3614348d1210d88a16 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Sat, 26 Nov 2022 03:13:06 +0800 Subject: [PATCH 388/488] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9ops=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/job.py | 4 +++- apps/ops/models/job.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index 86a52f373..e668bcee0 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -16,7 +16,9 @@ class JobViewSet(OrgBulkModelViewSet): def get_queryset(self): query_set = super().get_queryset() - return query_set.filter(instant=False) + if self.action != 'retrieve': + return query_set.filter(instant=False) + return query_set def perform_create(self, serializer): instance = serializer.save() diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index f2e7eaa4b..3795a0455 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -91,6 +91,9 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): def create_execution(self): return self.executions.create() + class Meta: + ordering = ['date_created'] + class JobExecution(JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) @@ -198,3 +201,6 @@ class JobExecution(JMSOrgBaseModel): except Exception as e: logging.error(e, exc_info=True) self.set_error(e) + + class Meta: + ordering = ['-date_created'] From 8a3bc51faa66edf49836e7103b316d2c21954851 Mon Sep 17 00:00:00 2001 From: Bai Date: Sat, 26 Nov 2022 09:52:04 +0800 Subject: [PATCH 389/488] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20authentication=20?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=96=87=E4=BB=B6=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{0015_auto_20221125_2240.py => 0016_auto_20221125_2240.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/authentication/migrations/{0015_auto_20221125_2240.py => 0016_auto_20221125_2240.py} (95%) diff --git a/apps/authentication/migrations/0015_auto_20221125_2240.py b/apps/authentication/migrations/0016_auto_20221125_2240.py similarity index 95% rename from apps/authentication/migrations/0015_auto_20221125_2240.py rename to apps/authentication/migrations/0016_auto_20221125_2240.py index 7b1c073e8..92478a523 100644 --- a/apps/authentication/migrations/0015_auto_20221125_2240.py +++ b/apps/authentication/migrations/0016_auto_20221125_2240.py @@ -7,7 +7,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('authentication', '0014_auto_20221122_2152'), + ('authentication', '0015_alter_connectiontoken_login'), ] operations = [ From 0b802b1782468a3c390b2345714169a760e8bc18 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Sat, 26 Nov 2022 19:07:12 +0800 Subject: [PATCH 390/488] perf: navigation page --- apps/common/utils/timezone.py | 20 ++++-- apps/jumpserver/api.py | 100 ++++++++++++++++++----------- apps/orgs/caches.py | 76 +++++++++++++++++----- apps/orgs/signal_handlers/cache.py | 14 ++-- 4 files changed, 146 insertions(+), 64 deletions(-) diff --git a/apps/common/utils/timezone.py b/apps/common/utils/timezone.py index 4b60008af..17e44b7bf 100644 --- a/apps/common/utils/timezone.py +++ b/apps/common/utils/timezone.py @@ -1,22 +1,21 @@ -import datetime - import pytz +from datetime import datetime, timedelta, timezone from django.utils import timezone as dj_timezone from rest_framework.fields import DateTimeField -max = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc) +max = datetime.max.replace(tzinfo=timezone.utc) -def astimezone(dt: datetime.datetime, tzinfo: pytz.tzinfo.DstTzInfo): +def astimezone(dt: datetime, tzinfo: pytz.tzinfo.DstTzInfo): assert dj_timezone.is_aware(dt) return tzinfo.normalize(dt.astimezone(tzinfo)) -def as_china_cst(dt: datetime.datetime): +def as_china_cst(dt: datetime): return astimezone(dt, pytz.timezone('Asia/Shanghai')) -def as_current_tz(dt: datetime.datetime): +def as_current_tz(dt: datetime): return astimezone(dt, dj_timezone.get_current_timezone()) @@ -36,6 +35,15 @@ def local_now_date_display(fmt='%Y-%m-%d'): return local_now().strftime(fmt) +def local_zero_hour(fmt='%Y-%m-%d'): + return datetime.strptime(local_now().strftime(fmt), fmt) + + +def local_monday(): + zero_hour_time = local_zero_hour() + return zero_hour_time - timedelta(zero_hour_time.weekday()) + + _rest_dt_field = DateTimeField() dt_parser = _rest_dt_field.to_internal_value dt_formatter = _rest_dt_field.to_representation diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 59580d178..bfa50da4d 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -3,45 +3,51 @@ import time from django.core.cache import cache from django.utils import timezone from django.utils.timesince import timesince -from django.db.models import Count, Max +from django.db.models import Count, Max, F from django.http.response import JsonResponse, HttpResponse from rest_framework.views import APIView from rest_framework.permissions import AllowAny -from collections import Counter +from rest_framework.request import Request from rest_framework.response import Response from users.models import User from assets.models import Asset +from assets.const import AllTypes from terminal.models import Session from terminal.utils import ComponentsPrometheusMetricsUtil from orgs.utils import current_org from common.utils import lazyproperty +from common.utils.timezone import local_now, local_zero_hour from orgs.caches import OrgResourceStatisticsCache - __all__ = ['IndexApi'] class DatesLoginMetricMixin: + request: Request + @lazyproperty def days(self): query_params = self.request.query_params - if query_params.get('monthly'): - return 30 - return 7 + # monthly + count = query_params.get('days') + return count if count else 0 @lazyproperty def sessions_queryset(self): - days = timezone.now() - timezone.timedelta(days=self.days) - sessions_queryset = Session.objects.filter(date_start__gt=days) + days = self.days + if days == 0: + t = local_zero_hour() + else: + t = local_now() - timezone.timedelta(days=days) + sessions_queryset = Session.objects.filter(date_start__gte=t) return sessions_queryset @lazyproperty def session_dates_list(self): - now = timezone.now() + now = local_now() dates = [(now - timezone.timedelta(days=i)).date() for i in range(self.days)] dates.reverse() - # dates = self.sessions_queryset.dates('date_start', 'day') return dates def get_dates_metrics_date(self): @@ -63,7 +69,7 @@ class DatesLoginMetricMixin: def __set_data_to_cache(self, date, tp, count): cache_key = self.get_cache_key(date, tp) - cache.set(cache_key, count, 3600*24*7) + cache.set(cache_key, count, 3600 * 24 * 7) @staticmethod def get_date_start_2_end(d): @@ -162,40 +168,45 @@ class DatesLoginMetricMixin: def dates_total_count_disabled_assets(self): return Asset.objects.filter(is_active=False).count() - # 以下是从week中而来 - def get_dates_login_times_top5_users(self): - users = self.sessions_queryset.values_list('user_id', flat=True) - users = [ - {'user': user, 'total': total} - for user, total in Counter(users).most_common(5) - ] - return users - def get_dates_total_count_login_users(self): return len(set(self.sessions_queryset.values_list('user_id', flat=True))) def get_dates_total_count_login_times(self): return self.sessions_queryset.count() - def get_dates_login_times_top10_assets(self): + @lazyproperty + def get_type_to_assets(self): + result = Asset.objects.annotate(type=F('platform__type')). \ + values('type').order_by('type').annotate(total=Count(1)) + all_types_dict = dict(AllTypes.choices()) + result = list(result) + for i in result: + tp = i['type'] + i['label'] = all_types_dict[tp] + return result + + def get_dates_login_times_assets(self): assets = self.sessions_queryset.values("asset") \ - .annotate(total=Count("asset")) \ - .annotate(last=Max("date_start")).order_by("-total")[:10] + .annotate(total=Count("asset")) \ + .annotate(last=Max("date_start")).order_by("-total") + assets = assets[:10] for asset in assets: asset['last'] = str(asset['last']) return list(assets) - def get_dates_login_times_top10_users(self): + def get_dates_login_times_users(self): users = self.sessions_queryset.values("user_id") \ - .annotate(total=Count("user_id")) \ - .annotate(user=Max('user')) \ - .annotate(last=Max("date_start")).order_by("-total")[:10] + .annotate(total=Count("user_id")) \ + .annotate(user=Max('user')) \ + .annotate(last=Max("date_start")).order_by("-total") + users = users[:10] for user in users: user['last'] = str(user['last']) return list(users) - def get_dates_login_record_top10_sessions(self): - sessions = self.sessions_queryset.order_by('-date_start')[:10] + def get_dates_login_record_sessions(self): + sessions = self.sessions_queryset.order_by('-date_start') + sessions = sessions[:10] for session in sessions: session.avatar_url = User.get_avatar_url("") sessions = [ @@ -229,11 +240,13 @@ class IndexApi(DatesLoginMetricMixin, APIView): if _all or query_params.get('total_count') or query_params.get('total_count_users'): data.update({ 'total_count_users': caches.users_amount, + 'total_count_users_this_week': caches.new_users_amount_this_week, }) if _all or query_params.get('total_count') or query_params.get('total_count_assets'): data.update({ 'total_count_assets': caches.assets_amount, + 'total_count_assets_this_week': caches.new_assets_amount_this_week, }) if _all or query_params.get('total_count') or query_params.get('total_count_online_users'): @@ -246,6 +259,23 @@ class IndexApi(DatesLoginMetricMixin, APIView): 'total_count_online_sessions': caches.total_count_online_sessions, }) + if _all or query_params.get('total_count') or query_params.get('total_count_today_failed_sessions'): + data.update({ + 'total_count_today_failed_sessions': caches.total_count_today_failed_sessions, + }) + if _all or query_params.get('total_count') or query_params.get('total_count_today_login_users'): + data.update({ + 'total_count_today_login_users': caches.total_count_today_login_users, + }) + if _all or query_params.get('total_count') or query_params.get('total_count_today_active_assets'): + data.update({ + 'total_count_today_active_assets': caches.total_count_today_active_assets, + }) + if _all or query_params.get('total_count') or query_params.get('total_count_type_to_assets_amount'): + data.update({ + 'total_count_type_to_assets_amount': self.get_type_to_assets, + }) + if _all or query_params.get('dates_metrics'): data.update({ 'dates_metrics_date': self.get_dates_metrics_date(), @@ -274,24 +304,19 @@ class IndexApi(DatesLoginMetricMixin, APIView): 'dates_total_count_login_times': self.get_dates_total_count_login_times(), }) - if _all or query_params.get('dates_login_times_top5_users'): - data.update({ - 'dates_login_times_top5_users': self.get_dates_login_times_top5_users(), - }) - if _all or query_params.get('dates_login_times_top10_assets'): data.update({ - 'dates_login_times_top10_assets': self.get_dates_login_times_top10_assets(), + 'dates_login_times_top10_assets': self.get_dates_login_times_assets(), }) if _all or query_params.get('dates_login_times_top10_users'): data.update({ - 'dates_login_times_top10_users': self.get_dates_login_times_top10_users(), + 'dates_login_times_top10_users': self.get_dates_login_times_users(), }) if _all or query_params.get('dates_login_record_top10_sessions'): data.update({ - 'dates_login_record_top10_sessions': self.get_dates_login_record_top10_sessions() + 'dates_login_record_top10_sessions': self.get_dates_login_record_sessions() }) return JsonResponse(data, status=200) @@ -353,4 +378,3 @@ class PrometheusMetricsApi(HealthApiMixin): util = ComponentsPrometheusMetricsUtil() metrics_text = util.get_prometheus_metrics_text() return HttpResponse(metrics_text, content_type='text/plain; version=0.0.4; charset=utf-8') - diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index a17cd832b..6b73c43b8 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -1,11 +1,14 @@ from django.db.transaction import on_commit + from orgs.models import Organization from orgs.tasks import refresh_org_cache_task from orgs.utils import current_org, tmp_to_org - from common.cache import Cache, IntegerField from common.utils import get_logger +from common.utils.timezone import local_zero_hour, local_monday from users.models import UserGroup, User +from audits.models import UserLoginLog +from audits.const import LoginStatusChoices from assets.models import Node, Domain, Asset, Account from terminal.models import Session from perms.models import AssetPermission @@ -35,30 +38,35 @@ class OrgRelatedCache(Cache): """ 在事务提交之后再发送信号,防止因事务的隔离性导致未获得最新的数据 """ + def func(): logger.debug(f'CACHE: Send refresh task {self}.{fields}') refresh_org_cache_task.delay(self, *fields) + on_commit(func) def expire(self, *fields): def func(): super(OrgRelatedCache, self).expire(*fields) + on_commit(func) class OrgResourceStatisticsCache(OrgRelatedCache): users_amount = IntegerField() - groups_amount = IntegerField(queryset=UserGroup.objects) - assets_amount = IntegerField() + new_users_amount_this_week = IntegerField() + new_assets_amount_this_week = IntegerField() nodes_amount = IntegerField(queryset=Node.objects) - accounts_amount = IntegerField(queryset=Account.objects) domains_amount = IntegerField(queryset=Domain.objects) - # gateways_amount = IntegerField(queryset=Gateway.objects) + groups_amount = IntegerField(queryset=UserGroup.objects) + accounts_amount = IntegerField(queryset=Account.objects) asset_perms_amount = IntegerField(queryset=AssetPermission.objects) - total_count_online_users = IntegerField() total_count_online_sessions = IntegerField() + total_count_today_login_users = IntegerField() + total_count_today_active_assets = IntegerField() + total_count_today_failed_sessions = IntegerField() def __init__(self, org): super().__init__() @@ -70,18 +78,56 @@ class OrgResourceStatisticsCache(OrgRelatedCache): def get_current_org(self): return self.org + def get_users(self): + return User.get_org_users(self.org) + + @staticmethod + def get_assets(): + return Asset.objects.all() + def compute_users_amount(self): - amount = User.get_org_users(self.org).count() - return amount + users = self.get_users() + return users.count() + + def compute_new_users_amount_this_week(self): + monday_time = local_monday() + users = self.get_users().filter(date_joined__gte=monday_time) + return users.count() def compute_assets_amount(self): - if self.org.is_root(): - return Asset.objects.all().count() - node = Node.org_root() - return node.assets_amount + assets = self.get_assets() + return assets.count() - def compute_total_count_online_users(self): - return Session.objects.filter(is_finished=False).values_list('user_id').distinct().count() + def compute_new_assets_amount_this_week(self): + monday_time = local_monday() + assets = self.get_assets().filter(date_created__gte=monday_time) + return assets.count() - def compute_total_count_online_sessions(self): + @staticmethod + def compute_total_count_online_users(): + return Session.objects.filter( + is_finished=False + ).values_list('user_id').distinct().count() + + @staticmethod + def compute_total_count_online_sessions(): return Session.objects.filter(is_finished=False).count() + + @staticmethod + def compute_total_count_today_login_users(): + t = local_zero_hour() + return UserLoginLog.objects.filter( + datetime__gte=t, status=LoginStatusChoices.success + ).values('username').distinct().count() + + @staticmethod + def compute_total_count_today_active_assets(): + t = local_zero_hour() + return Session.objects.filter( + date_start__gte=t, is_success=False + ).values('asset_id').distinct().count() + + @staticmethod + def compute_total_count_today_failed_sessions(): + t = local_zero_hour() + return Session.objects.filter(date_start__gte=t, is_success=False).count() diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index b3e06362d..9d3ce5c85 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -2,8 +2,9 @@ from django.db.models.signals import post_save, pre_delete, pre_save, post_delet from django.dispatch import receiver from orgs.models import Organization -from assets.models import Node +from assets.models import Node, Account from perms.models import AssetPermission +from audits.models import UserLoginLog from users.models import UserGroup, User from users.signals import pre_user_leave_org from terminal.models import Session @@ -74,12 +75,15 @@ def on_user_delete_refresh_cache(sender, instance, **kwargs): class OrgResourceStatisticsRefreshUtil: model_cache_field_mapper = { - AssetPermission: ['asset_perms_amount'], - Domain: ['domains_amount'], Node: ['nodes_amount'], - Asset: ['assets_amount'], + Domain: ['domains_amount'], UserGroup: ['groups_amount'], - RoleBinding: ['users_amount'] + Account: ['accounts_amount'], + UserLoginLog: ['total_count_today_login_users'], + RoleBinding: ['users_amount', 'new_users_amount_this_week'], + Asset: ['assets_amount', 'new_assets_amount_this_week'], + AssetPermission: ['asset_perms_amount'], + } @classmethod From 392ae18d85f0e5f56b0fd4ad34d7f635053bf032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Sun, 27 Nov 2022 05:56:53 +0800 Subject: [PATCH 391/488] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20uvicorn=20?= =?UTF-8?q?=E5=8F=96=E4=BB=A3=20daphne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/services/command.py | 8 +----- .../commands/services/services/__init__.py | 1 - .../commands/services/services/daphne.py | 25 ------------------- .../commands/services/services/gunicorn.py | 4 +-- requirements/requirements.txt | 4 +-- 5 files changed, 5 insertions(+), 37 deletions(-) delete mode 100644 apps/common/management/commands/services/services/daphne.py diff --git a/apps/common/management/commands/services/command.py b/apps/common/management/commands/services/command.py index dd2cd9cdb..1fb28fd3a 100644 --- a/apps/common/management/commands/services/command.py +++ b/apps/common/management/commands/services/command.py @@ -6,7 +6,6 @@ from .hands import * class Services(TextChoices): gunicorn = 'gunicorn', 'gunicorn' - daphne = 'daphne', 'daphne' celery_ansible = 'celery_ansible', 'celery_ansible' celery_default = 'celery_default', 'celery_default' beat = 'beat', 'beat' @@ -22,7 +21,6 @@ class Services(TextChoices): from . import services services_map = { cls.gunicorn.value: services.GunicornService, - cls.daphne: services.DaphneService, cls.flower: services.FlowerService, cls.celery_default: services.CeleryDefaultService, cls.celery_ansible: services.CeleryAnsibleService, @@ -30,13 +28,9 @@ class Services(TextChoices): } return services_map.get(name) - @classmethod - def ws_services(cls): - return [cls.daphne] - @classmethod def web_services(cls): - return [cls.gunicorn, cls.daphne, cls.flower] + return [cls.gunicorn, cls.flower] @classmethod def celery_services(cls): diff --git a/apps/common/management/commands/services/services/__init__.py b/apps/common/management/commands/services/services/__init__.py index cceb9627c..35329a7d4 100644 --- a/apps/common/management/commands/services/services/__init__.py +++ b/apps/common/management/commands/services/services/__init__.py @@ -1,6 +1,5 @@ from .beat import * from .celery_ansible import * from .celery_default import * -from .daphne import * from .flower import * from .gunicorn import * diff --git a/apps/common/management/commands/services/services/daphne.py b/apps/common/management/commands/services/services/daphne.py deleted file mode 100644 index 09dd337a6..000000000 --- a/apps/common/management/commands/services/services/daphne.py +++ /dev/null @@ -1,25 +0,0 @@ -from ..hands import * -from .base import BaseService - -__all__ = ['DaphneService'] - - -class DaphneService(BaseService): - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - @property - def cmd(self): - print("\n- Start Daphne ASGI WS Server") - - cmd = [ - 'daphne', 'jumpserver.asgi:application', - '-b', HTTP_HOST, - '-p', str(WS_PORT), - ] - return cmd - - @property - def cwd(self): - return APPS_DIR diff --git a/apps/common/management/commands/services/services/gunicorn.py b/apps/common/management/commands/services/services/gunicorn.py index bfaeea8c4..5cc67b45c 100644 --- a/apps/common/management/commands/services/services/gunicorn.py +++ b/apps/common/management/commands/services/services/gunicorn.py @@ -17,9 +17,9 @@ class GunicornService(BaseService): log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s ' bind = f'{HTTP_HOST}:{HTTP_PORT}' cmd = [ - 'gunicorn', 'jumpserver.wsgi', + 'gunicorn', 'jumpserver.asgi:application', '-b', bind, - '-k', 'gthread', + '-k', 'uvicorn.workers.UvicornWorker', '--threads', '10', '-w', str(self.worker), '--max-requests', '4096', diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8af2fcb0f..0ff982b81 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -86,8 +86,6 @@ pytz==2022.1 # Runtime django-proxy==1.2.1 channels-redis==3.4.0 -channels==3.0.4 -daphne==3.0.2 python-daemon==2.3.0 eventlet==0.33.1 greenlet==1.1.2 @@ -96,6 +94,8 @@ celery==5.2.7 flower==1.0.0 django-celery-beat==2.3.0 kombu==5.2.4 +uvicorn==0.20.0 +websockets==10.4 # Auth python-ldap==3.4.0 ldap3==2.9.1 From eec463774a70e426b26ed8ad920a7d3567a2fc34 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Sun, 27 Nov 2022 12:53:38 +0800 Subject: [PATCH 392/488] perf: user login logs --- apps/orgs/caches.py | 11 +++++++---- apps/orgs/signal_handlers/cache.py | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index 6b73c43b8..0a665c130 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -113,12 +113,15 @@ class OrgResourceStatisticsCache(OrgRelatedCache): def compute_total_count_online_sessions(): return Session.objects.filter(is_finished=False).count() - @staticmethod - def compute_total_count_today_login_users(): + def compute_total_count_today_login_users(self): t = local_zero_hour() - return UserLoginLog.objects.filter( + user_login_logs = UserLoginLog.objects.filter( datetime__gte=t, status=LoginStatusChoices.success - ).values('username').distinct().count() + ) + if not self.org.is_root(): + usernames = self.org.get_members().values('username') + user_login_logs = user_login_logs.filter(username__in=usernames) + return user_login_logs.values('username').distinct().count() @staticmethod def compute_total_count_today_active_assets(): diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 9d3ce5c85..27947c2d2 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -79,7 +79,6 @@ class OrgResourceStatisticsRefreshUtil: Domain: ['domains_amount'], UserGroup: ['groups_amount'], Account: ['accounts_amount'], - UserLoginLog: ['total_count_today_login_users'], RoleBinding: ['users_amount', 'new_users_amount_this_week'], Asset: ['assets_amount', 'new_assets_amount_this_week'], AssetPermission: ['asset_perms_amount'], From 61c96baeaee6f18269d59b00aab21594dc6a012a Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 27 Nov 2022 14:36:16 +0800 Subject: [PATCH 393/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96UserLoginLog=E5=AF=B9=E8=B1=A1org=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/signal_handlers/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 27947c2d2..505422377 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -91,7 +91,7 @@ class OrgResourceStatisticsRefreshUtil: if not cache_field_name: return OrgResourceStatisticsCache(Organization.root()).expire(*cache_field_name) - if instance.org: + if getattr(instance, 'org', None): OrgResourceStatisticsCache(instance.org).expire(*cache_field_name) From 23f3f903f55f478fbfd651f4e960c75ee1cb0a9e Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 27 Nov 2022 18:31:28 +0800 Subject: [PATCH 394/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 1 - apps/authentication/models/connection_token.py | 12 ++++++------ apps/authentication/serializers/connection_token.py | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 59b0b7593..a6e818cce 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -1,7 +1,6 @@ import base64 import json import os -import time import urllib.parse from django.http import HttpResponse diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index d0a1d8478..4bfac68d2 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -77,7 +77,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def permed_account(self): from perms.utils import PermAccountUtil permed_account = PermAccountUtil().validate_permission( - self.user, self.asset, self.login + self.user, self.asset, self.account_name ) return permed_account @@ -100,13 +100,13 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('No asset or inactive asset') return is_valid, error - if not self.login: + if not self.account_name: error = _('No account') raise PermissionDenied(error) if not self.permed_account or not self.permed_account.actions: msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( - self.user, self.asset, self.login + self.user, self.asset, self.account_name ) raise PermissionDenied(msg) @@ -123,10 +123,10 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): if not self.asset: return None - account = self.asset.accounts.filter(name=self.login).first() - if self.login == '@INPUT' or not account: + account = self.asset.accounts.filter(name=self.account_name).first() + if self.account_name == '@INPUT' or not account: return { - 'name': self.login, + 'name': self.account_name, 'username': self.username, 'secret_type': 'password', 'secret': self.secret diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index db6b35963..117ec2a04 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -154,7 +154,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'secret', 'user', 'asset', 'account', + 'id', 'value', 'user', 'asset', 'account', 'protocol', 'domain', 'gateway', 'actions', 'expire_at', 'platform', ] From b2bb46a51e0cf203b4537188dc1db1d62c1adc4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Sun, 27 Nov 2022 20:20:26 +0800 Subject: [PATCH 395/488] =?UTF-8?q?perf:=20=E5=8E=BB=E6=8E=89=E4=B8=8D?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=208070=20=E7=AB=AF=E5=8F=A3?= =?UTF-8?q?=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile.loong64 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 28dedbba9..e20279604 100644 --- a/Dockerfile +++ b/Dockerfile @@ -100,6 +100,6 @@ VOLUME /opt/jumpserver/logs ENV LANG=zh_CN.UTF-8 -EXPOSE 8070 EXPOSE 8080 + ENTRYPOINT ["./entrypoint.sh"] diff --git a/Dockerfile.loong64 b/Dockerfile.loong64 index 580792776..c2fa521b6 100644 --- a/Dockerfile.loong64 +++ b/Dockerfile.loong64 @@ -91,6 +91,6 @@ VOLUME /opt/jumpserver/logs ENV LANG=zh_CN.UTF-8 -EXPOSE 8070 EXPOSE 8080 + ENTRYPOINT ["./entrypoint.sh"] From 7f2267cf1338ac26e5bd6b4461644d59eae1e4ca Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 28 Nov 2022 11:42:03 +0800 Subject: [PATCH 396/488] perf: navigation page --- apps/jumpserver/api.py | 61 ------------------------------------------ 1 file changed, 61 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index bfa50da4d..b70983c39 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -133,47 +133,6 @@ class DatesLoginMetricMixin: data.append(count) return data - @lazyproperty - def dates_total_count_active_users(self): - count = len(set(self.sessions_queryset.values_list('user_id', flat=True))) - return count - - @lazyproperty - def dates_total_count_inactive_users(self): - total = current_org.get_members().count() - active = self.dates_total_count_active_users - count = total - active - if count < 0: - count = 0 - return count - - @lazyproperty - def dates_total_count_disabled_users(self): - return current_org.get_members().filter(is_active=False).count() - - @lazyproperty - def dates_total_count_active_assets(self): - return len(set(self.sessions_queryset.values_list('asset', flat=True))) - - @lazyproperty - def dates_total_count_inactive_assets(self): - total = Asset.objects.all().count() - active = self.dates_total_count_active_assets - count = total - active - if count < 0: - count = 0 - return count - - @lazyproperty - def dates_total_count_disabled_assets(self): - return Asset.objects.filter(is_active=False).count() - - def get_dates_total_count_login_users(self): - return len(set(self.sessions_queryset.values_list('user_id', flat=True))) - - def get_dates_total_count_login_times(self): - return self.sessions_queryset.count() - @lazyproperty def get_type_to_assets(self): result = Asset.objects.annotate(type=F('platform__type')). \ @@ -284,26 +243,6 @@ class IndexApi(DatesLoginMetricMixin, APIView): 'dates_metrics_total_count_active_assets': self.get_dates_metrics_total_count_active_assets(), }) - if _all or query_params.get('dates_total_count_users'): - data.update({ - 'dates_total_count_active_users': self.dates_total_count_active_users, - 'dates_total_count_inactive_users': self.dates_total_count_inactive_users, - 'dates_total_count_disabled_users': self.dates_total_count_disabled_users, - }) - - if _all or query_params.get('dates_total_count_assets'): - data.update({ - 'dates_total_count_active_assets': self.dates_total_count_active_assets, - 'dates_total_count_inactive_assets': self.dates_total_count_inactive_assets, - 'dates_total_count_disabled_assets': self.dates_total_count_disabled_assets, - }) - - if _all or query_params.get('dates_total_count'): - data.update({ - 'dates_total_count_login_users': self.get_dates_total_count_login_users(), - 'dates_total_count_login_times': self.get_dates_total_count_login_times(), - }) - if _all or query_params.get('dates_login_times_top10_assets'): data.update({ 'dates_login_times_top10_assets': self.get_dates_login_times_assets(), From 072c44974e231d6bb8c9803189ef0a8c118f0f26 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:47:40 +0800 Subject: [PATCH 397/488] perf: navigation page (#9125) Co-authored-by: feng <1304903146@qq.com> --- apps/jumpserver/api.py | 61 ------------------------------------------ 1 file changed, 61 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index bfa50da4d..b70983c39 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -133,47 +133,6 @@ class DatesLoginMetricMixin: data.append(count) return data - @lazyproperty - def dates_total_count_active_users(self): - count = len(set(self.sessions_queryset.values_list('user_id', flat=True))) - return count - - @lazyproperty - def dates_total_count_inactive_users(self): - total = current_org.get_members().count() - active = self.dates_total_count_active_users - count = total - active - if count < 0: - count = 0 - return count - - @lazyproperty - def dates_total_count_disabled_users(self): - return current_org.get_members().filter(is_active=False).count() - - @lazyproperty - def dates_total_count_active_assets(self): - return len(set(self.sessions_queryset.values_list('asset', flat=True))) - - @lazyproperty - def dates_total_count_inactive_assets(self): - total = Asset.objects.all().count() - active = self.dates_total_count_active_assets - count = total - active - if count < 0: - count = 0 - return count - - @lazyproperty - def dates_total_count_disabled_assets(self): - return Asset.objects.filter(is_active=False).count() - - def get_dates_total_count_login_users(self): - return len(set(self.sessions_queryset.values_list('user_id', flat=True))) - - def get_dates_total_count_login_times(self): - return self.sessions_queryset.count() - @lazyproperty def get_type_to_assets(self): result = Asset.objects.annotate(type=F('platform__type')). \ @@ -284,26 +243,6 @@ class IndexApi(DatesLoginMetricMixin, APIView): 'dates_metrics_total_count_active_assets': self.get_dates_metrics_total_count_active_assets(), }) - if _all or query_params.get('dates_total_count_users'): - data.update({ - 'dates_total_count_active_users': self.dates_total_count_active_users, - 'dates_total_count_inactive_users': self.dates_total_count_inactive_users, - 'dates_total_count_disabled_users': self.dates_total_count_disabled_users, - }) - - if _all or query_params.get('dates_total_count_assets'): - data.update({ - 'dates_total_count_active_assets': self.dates_total_count_active_assets, - 'dates_total_count_inactive_assets': self.dates_total_count_inactive_assets, - 'dates_total_count_disabled_assets': self.dates_total_count_disabled_assets, - }) - - if _all or query_params.get('dates_total_count'): - data.update({ - 'dates_total_count_login_users': self.get_dates_total_count_login_users(), - 'dates_total_count_login_times': self.get_dates_total_count_login_times(), - }) - if _all or query_params.get('dates_login_times_top10_assets'): data.update({ 'dates_login_times_top10_assets': self.get_dates_login_times_assets(), From 0212e32ab22092a29010aa6a597a68f39e6b8c89 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 28 Nov 2022 14:52:42 +0800 Subject: [PATCH 398/488] perf: navigation api --- apps/jumpserver/api.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index b70983c39..c7dbb6cd7 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -199,15 +199,33 @@ class IndexApi(DatesLoginMetricMixin, APIView): if _all or query_params.get('total_count') or query_params.get('total_count_users'): data.update({ 'total_count_users': caches.users_amount, - 'total_count_users_this_week': caches.new_users_amount_this_week, }) if _all or query_params.get('total_count') or query_params.get('total_count_assets'): data.update({ 'total_count_assets': caches.assets_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_users_this_week'): + data.update({ + 'total_count_users_this_week': caches.new_users_amount_this_week, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_assets_this_week'): + data.update({ 'total_count_assets_this_week': caches.new_assets_amount_this_week, }) + if _all or query_params.get('total_count') or query_params.get('total_count_today_login_users'): + data.update({ + 'total_count_today_login_users': caches.total_count_today_login_users, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_today_active_assets'): + data.update({ + 'total_count_today_active_assets': caches.total_count_today_active_assets, + }) + if _all or query_params.get('total_count') or query_params.get('total_count_online_users'): data.update({ 'total_count_online_users': caches.total_count_online_users, @@ -222,14 +240,7 @@ class IndexApi(DatesLoginMetricMixin, APIView): data.update({ 'total_count_today_failed_sessions': caches.total_count_today_failed_sessions, }) - if _all or query_params.get('total_count') or query_params.get('total_count_today_login_users'): - data.update({ - 'total_count_today_login_users': caches.total_count_today_login_users, - }) - if _all or query_params.get('total_count') or query_params.get('total_count_today_active_assets'): - data.update({ - 'total_count_today_active_assets': caches.total_count_today_active_assets, - }) + if _all or query_params.get('total_count') or query_params.get('total_count_type_to_assets_amount'): data.update({ 'total_count_type_to_assets_amount': self.get_type_to_assets, From a1d72a17461fbe456e5fb69de7c66a9f5598b0a8 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 28 Nov 2022 15:01:16 +0800 Subject: [PATCH 399/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token=20=E4=B8=80=E4=BA=9B=20Url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 22 ++++++------------- .../authentication/models/connection_token.py | 20 ++++++++--------- apps/locale/ja/LC_MESSAGES/django.mo | 4 ++-- apps/locale/ja/LC_MESSAGES/django.po | 4 ---- apps/locale/zh/LC_MESSAGES/django.mo | 4 ++-- apps/locale/zh/LC_MESSAGES/django.po | 6 +---- 6 files changed, 22 insertions(+), 38 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index a6e818cce..e08ba5291 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -159,7 +159,7 @@ class RDPFileClientProtocolURLMixin: 'ip': endpoint.host, 'port': str(endpoint.ssh_port), 'username': 'JMS-{}'.format(str(token.id)), - 'password': token.secret + 'password': token.value } token = json.dumps(data) return filename, token @@ -176,9 +176,9 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): get_serializer: callable perform_create: callable - @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') - def get_rdp_file(self, request, *args, **kwargs): - token = self.create_connection_token() + @action(methods=['POST', 'GET'], detail=True, url_path='rdp-file') + def get_rdp_file(self, *args, **kwargs): + token = self.get_object() token.is_valid() filename, content = self.get_rdp_file_info(token) filename = '{}.rdp'.format(filename) @@ -186,9 +186,9 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'%s' % filename return response - @action(methods=['POST', 'GET'], detail=False, url_path='client-url') - def get_client_protocol_url(self, request, *args, **kwargs): - token = self.create_connection_token() + @action(methods=['POST', 'GET'], detail=True, url_path='client-url') + def get_client_protocol_url(self, *args, **kwargs): + token = self.get_object() token.is_valid() try: protocol_data = self.get_client_protocol_data(token) @@ -207,14 +207,6 @@ class ExtraActionApiMixin(RDPFileClientProtocolURLMixin): instance.expire() return Response(status=status.HTTP_204_NO_CONTENT) - def create_connection_token(self): - data = self.request.query_params if self.request.method == 'GET' else self.request.data - serializer = self.get_serializer(data=data) - serializer.is_valid(raise_exception=True) - self.perform_create(serializer) - token: ConnectionToken = serializer.instance - return token - class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelViewSet): filterset_fields = ( diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 4bfac68d2..2298eb0f3 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -1,17 +1,17 @@ -import time from datetime import timedelta + +from django.conf import settings +from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.db import models -from django.conf import settings from rest_framework.exceptions import PermissionDenied -from orgs.mixins.models import OrgModelMixin +from assets.const import Protocol +from common.db.fields import EncryptCharField +from common.db.models import JMSBaseModel from common.utils import lazyproperty, pretty_string from common.utils.timezone import as_current_tz -from common.db.models import JMSBaseModel -from common.db.fields import EncryptCharField -from assets.const import Protocol +from orgs.mixins.models import OrgModelMixin def date_expired_default(): @@ -21,7 +21,7 @@ def date_expired_default(): class ConnectionToken(OrgModelMixin, JMSBaseModel): value = models.CharField(max_length=64, default='', verbose_name=_("Value")) user = models.ForeignKey( - 'users.User', on_delete=models.SET_NULL, null=True, blank=True, + 'users.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('User') ) asset = models.ForeignKey( @@ -29,8 +29,8 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): related_name='connection_tokens', verbose_name=_('Asset'), ) account_name = models.CharField(max_length=128, verbose_name=_("Account name")) # 登录账号Name - input_username = models.CharField(max_length=128, default='', verbose_name=_("Input Username")) - input_secret = EncryptCharField(max_length=64, default='', verbose_name=_("Input Secret")) + input_username = models.CharField(max_length=128, default='', blank=True, verbose_name=_("Input Username")) + input_secret = EncryptCharField(max_length=64, default='', blank=True, verbose_name=_("Input Secret")) protocol = models.CharField( choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 3e19617aa..f74980a81 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:adfa9c01178d5f6490e616f62d41c71974d42f9e3bd078fcf1b3c7124384df0b -size 117024 +oid sha256:4a5338177d87680e0030c77f187a06664136d5dea63c8dffc43fa686091f2da4 +size 117102 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index dddb5b792..e9f747826 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -603,14 +603,10 @@ msgid "All" msgstr "すべて" #: assets/models/account.py:46 -#, fuzzy -#| msgid "Manually input" msgid "Manual input" msgstr "手動入力" #: assets/models/account.py:47 -#, fuzzy -#| msgid "Dynamic code" msgid "Dynamic user" msgstr "動的コード" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4451b285c..e3f454768 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:eeaa813f4ea052a1cd85b8ae5addfde6b088fd21a0261f8724d62823835512a2 -size 104043 +oid sha256:30ae571e06eb7d2f0fee70013a812ea3bdb8e14715e1a1f4eb5e2c92311034f8 +size 104086 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 686c70a31..56b0a8f28 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -578,16 +578,12 @@ msgid "All" msgstr "全部" #: assets/models/account.py:46 -#, fuzzy -#| msgid "Manually input" msgid "Manual input" msgstr "手动输入" #: assets/models/account.py:47 -#, fuzzy -#| msgid "Dynamic code" msgid "Dynamic user" -msgstr "动态码" +msgstr "动态用户" #: assets/models/account.py:55 msgid "Su from" From 3c5b459ab79350c3d0ed3dcd6e14f850160bc395 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 28 Nov 2022 15:31:00 +0800 Subject: [PATCH 400/488] fix: connect token serializer --- apps/authentication/models/connection_token.py | 2 +- apps/authentication/serializers/connection_token.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 2298eb0f3..82ad3589f 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -136,7 +136,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): 'name': account.name, 'username': account.username, 'secret_type': account.secret_type, - 'secret': account.secret_type or self.secret + 'secret': account.secret or self.secret } @lazyproperty diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 117ec2a04..767106aba 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,8 +1,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from assets.serializers import PlatformSerializer from assets.models import Asset, Domain, CommandFilterRule, Account, Platform +from assets.serializers import PlatformSerializer, AssetProtocolsSerializer from authentication.models import ConnectionToken from orgs.mixins.serializers import OrgResourceModelSerializerMixin from perms.serializers.permission import ActionChoicesField @@ -87,6 +87,7 @@ class ConnectionTokenUserSerializer(serializers.ModelSerializer): class ConnectionTokenAssetSerializer(serializers.ModelSerializer): """ Asset """ + protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) class Meta: model = Asset @@ -99,7 +100,7 @@ class ConnectionTokenAccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = [ - 'name', 'username', 'secret_type', 'secret', + 'name', 'username', 'secret_type', 'secret', ] From 4f718f9b1fa16c5221168a7237bf0c2254155989 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:54:16 +0800 Subject: [PATCH 401/488] perf: account template secret api (#9127) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/account/template.py | 21 +++++++++++++++++++-- apps/assets/serializers/account/template.py | 8 ++++++++ apps/assets/urls/api_urls.py | 1 + 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/account/template.py b/apps/assets/api/account/template.py index c04fd8ab6..dd9ee1d00 100644 --- a/apps/assets/api/account/template.py +++ b/apps/assets/api/account/template.py @@ -1,6 +1,10 @@ -from orgs.mixins.api import OrgBulkModelViewSet -from assets.models import AccountTemplate from assets import serializers +from assets.models import AccountTemplate +from rbac.permissions import RBACPermission +from authentication.const import ConfirmType +from common.mixins import RecordViewLogMixin +from common.permissions import UserConfirmation +from orgs.mixins.api import OrgBulkModelViewSet class AccountTemplateViewSet(OrgBulkModelViewSet): @@ -10,3 +14,16 @@ class AccountTemplateViewSet(OrgBulkModelViewSet): serializer_classes = { 'default': serializers.AccountTemplateSerializer } + + +class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet): + serializer_classes = { + 'default': serializers.AccountTemplateSecretSerializer, + } + http_method_names = ['get', 'options'] + # Todo: 记得打开 + # permission_classes = [RBACPermission, UserConfirmation.require(ConfirmType.MFA)] + rbac_perms = { + 'list': 'assets.view_accounttemplatesecret', + 'retrieve': 'assets.view_accounttemplatesecret', + } diff --git a/apps/assets/serializers/account/template.py b/apps/assets/serializers/account/template.py index 4599ca0e7..7a7de7f11 100644 --- a/apps/assets/serializers/account/template.py +++ b/apps/assets/serializers/account/template.py @@ -1,3 +1,4 @@ +from common.drf.serializers import SecretReadableMixin from assets.models import AccountTemplate from .base import BaseAccountSerializer @@ -17,3 +18,10 @@ class AccountTemplateSerializer(BaseAccountSerializer): # if not required_field_dict: # return # raise serializers.ValidationError(required_field_dict) + + +class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer): + class Meta(AccountTemplateSerializer.Meta): + extra_kwargs = { + 'secret': {'write_only': False}, + } diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index a7077aa39..773f5c348 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -16,6 +16,7 @@ router.register(r'webs', api.WebViewSet, 'web') router.register(r'clouds', api.CloudViewSet, 'cloud') router.register(r'accounts', api.AccountViewSet, 'account') router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template') +router.register(r'account-template-secrets', api.AccountTemplateSecretsViewSet, 'account-template-secret') router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret') router.register(r'platforms', api.AssetPlatformViewSet, 'platform') router.register(r'labels', api.LabelViewSet, 'label') From d0b9dd457f282ffabaaaf619869cf108e3b04dff Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 28 Nov 2022 16:12:06 +0800 Subject: [PATCH 402/488] perf: navigation date metrics --- apps/jumpserver/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index c7dbb6cd7..48b767299 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -31,7 +31,8 @@ class DatesLoginMetricMixin: query_params = self.request.query_params # monthly count = query_params.get('days') - return count if count else 0 + count = int(count) if count else 0 + return count @lazyproperty def sessions_queryset(self): From 742cac1e9001a9a363fd15661ac0dc963a8a2eef Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 28 Nov 2022 17:57:33 +0800 Subject: [PATCH 403/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/const.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 3f653aaf7..21d79fd0a 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -56,11 +56,7 @@ class NativeClient(TextChoices): xshell = 'xshell', 'Xshell' # Magnus - mysql = 'mysql', 'mysql' - psql = 'psql', 'psql' - sqlplus = 'sqlplus', 'sqlplus' - redis = 'redis-cli', 'redis-cli' - mongodb = 'mongo', 'mongo' + db_client = 'db_client', _('DB Client') # Razor mstsc = 'mstsc', 'Remote Desktop' @@ -73,11 +69,11 @@ class NativeClient(TextChoices): 'windows': [cls.putty], }, Protocol.rdp: [cls.mstsc], - Protocol.mysql: [cls.mysql], - Protocol.oracle: [cls.sqlplus], - Protocol.postgresql: [cls.psql], - Protocol.redis: [cls.redis], - Protocol.mongodb: [cls.mongodb], + Protocol.mysql: [cls.db_client], + Protocol.oracle: [cls.db_client], + Protocol.postgresql: [cls.db_client], + Protocol.redis: [cls.db_client], + Protocol.mongodb: [cls.db_client], } return clients @@ -183,8 +179,8 @@ class TerminalType(TextChoices): cls.magnus: { 'listen': [], 'support': [ - Protocol.mysql, Protocol.postgresql, Protocol.oracle, - Protocol.mariadb + Protocol.mysql, Protocol.postgresql, + Protocol.oracle, Protocol.mariadb ], 'match': 'map' }, From 11636dafd8024a2684f7c905bff2f5a4f42c9a98 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 28 Nov 2022 18:43:58 +0800 Subject: [PATCH 404/488] perf: history account secret perm (#9128) Co-authored-by: feng <1304903146@qq.com> --- .../0113_alter_accounttemplate_options.py | 17 +++++++++++++ apps/assets/models/account.py | 4 ++++ .../migrations/0017_auto_20221128_1839.py | 24 +++++++++++++++++++ .../ops/migrations/0036_auto_20221128_1839.py | 21 ++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 apps/assets/migrations/0113_alter_accounttemplate_options.py create mode 100644 apps/authentication/migrations/0017_auto_20221128_1839.py create mode 100644 apps/ops/migrations/0036_auto_20221128_1839.py diff --git a/apps/assets/migrations/0113_alter_accounttemplate_options.py b/apps/assets/migrations/0113_alter_accounttemplate_options.py new file mode 100644 index 000000000..e635426c1 --- /dev/null +++ b/apps/assets/migrations/0113_alter_accounttemplate_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.14 on 2022-11-28 10:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0112_gateway_to_asset'), + ] + + operations = [ + migrations.AlterModelOptions( + name='accounttemplate', + options={'permissions': [('view_accounttemplatesecret', 'Can view asset account template secret'), ('change_accounttemplatesecret', 'Can change asset account template secret')], 'verbose_name': 'Account template'}, + ), + ] diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index cad5f9ded..930fd7882 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -94,6 +94,10 @@ class AccountTemplate(BaseAccount): unique_together = ( ('name', 'org_id'), ) + permissions = [ + ('view_accounttemplatesecret', _('Can view asset account template secret')), + ('change_accounttemplatesecret', _('Can change asset account template secret')), + ] def __str__(self): return self.username diff --git a/apps/authentication/migrations/0017_auto_20221128_1839.py b/apps/authentication/migrations/0017_auto_20221128_1839.py new file mode 100644 index 000000000..31a49267a --- /dev/null +++ b/apps/authentication/migrations/0017_auto_20221128_1839.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-11-28 10:39 + +import common.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0016_auto_20221125_2240'), + ] + + operations = [ + migrations.AlterField( + model_name='connectiontoken', + name='input_secret', + field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, verbose_name='Input Secret'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='input_username', + field=models.CharField(blank=True, default='', max_length=128, verbose_name='Input Username'), + ), + ] diff --git a/apps/ops/migrations/0036_auto_20221128_1839.py b/apps/ops/migrations/0036_auto_20221128_1839.py new file mode 100644 index 000000000..22bc435e2 --- /dev/null +++ b/apps/ops/migrations/0036_auto_20221128_1839.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.14 on 2022-11-28 10:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0035_jobexecution_org_id'), + ] + + operations = [ + migrations.AlterModelOptions( + name='job', + options={'ordering': ['date_created']}, + ), + migrations.AlterModelOptions( + name='jobexecution', + options={'ordering': ['-date_created']}, + ), + ] From 3052aa759c98e3a180bb9a022323d8b087817724 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 28 Nov 2022 21:54:20 +0800 Subject: [PATCH 405/488] perf: ticket login asset acl --- apps/acls/api/login_asset_check.py | 4 ++-- apps/acls/models/login_asset_acl.py | 14 +++++++------- apps/acls/serializers/login_asset_check.py | 19 ++++++++++++------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index bedf78d41..331c42768 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -26,7 +26,7 @@ class LoginAssetCheckAPI(CreateAPIView): def check_if_need_confirm(self): queries = { 'user': self.serializer.user, 'asset': self.serializer.asset, - 'account': self.serializer.account, + 'account_username': self.serializer.username, 'action': LoginAssetACL.ActionChoices.login_confirm } with tmp_to_org(self.serializer.org): @@ -45,7 +45,7 @@ class LoginAssetCheckAPI(CreateAPIView): ticket = LoginAssetACL.create_login_asset_confirm_ticket( user=self.serializer.user, asset=self.serializer.asset, - account=self.serializer.account, + account_username=self.serializer.username, assignees=acl.reviewers.all(), org_id=self.serializer.org.id, ) diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 2ad9363e5..b01e4aed1 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -43,11 +43,11 @@ class LoginAssetACL(BaseACL, OrgModelMixin): return self.name @classmethod - def filter(cls, user, asset, account, action): + def filter(cls, user, asset, account_username, action): queryset = cls.objects.filter(action=action) queryset = cls.filter_user(user, queryset) queryset = cls.filter_asset(asset, queryset) - queryset = cls.filter_account(account, queryset) + queryset = cls.filter_account(account_username, queryset) return queryset @classmethod @@ -69,18 +69,18 @@ class LoginAssetACL(BaseACL, OrgModelMixin): return queryset @classmethod - def filter_account(cls, account, queryset): + def filter_account(cls, account_username, queryset): queryset = queryset.filter( - Q(accounts__name_group__contains=account.name) | + Q(accounts__name_group__contains=account_username) | Q(accounts__name_group__contains='*') ).filter( - Q(accounts__username_group__contains=account.username) | + Q(accounts__username_group__contains=account_username) | Q(accounts__username_group__contains='*') ) return queryset @classmethod - def create_login_asset_confirm_ticket(cls, user, asset, account, assignees, org_id): + def create_login_asset_confirm_ticket(cls, user, asset, account_username, assignees, org_id): from tickets.const import TicketType from tickets.models import ApplyLoginAssetTicket title = _('Login asset confirm') + ' ({})'.format(user) @@ -90,7 +90,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): 'applicant': user, 'apply_login_user': user, 'apply_login_asset': asset, - 'apply_login_account': str(account), + 'apply_login_account': account_username, 'type': TicketType.login_asset_confirm, } ticket = ApplyLoginAssetTicket.objects.create(**data) diff --git a/apps/acls/serializers/login_asset_check.py b/apps/acls/serializers/login_asset_check.py index 2240cb8d6..279feb3b6 100644 --- a/apps/acls/serializers/login_asset_check.py +++ b/apps/acls/serializers/login_asset_check.py @@ -10,15 +10,13 @@ __all__ = ['LoginAssetCheckSerializer'] class LoginAssetCheckSerializer(serializers.Serializer): user_id = serializers.UUIDField(required=True, allow_null=False) asset_id = serializers.UUIDField(required=True, allow_null=False) - account_id = serializers.UUIDField(required=True, allow_null=False) account_username = serializers.CharField(max_length=128, default='') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.user = None self.asset = None - self.account = None - self._account_username = None + self.username = None def validate_user_id(self, user_id): self.user = self.validate_object_exist(User, user_id) @@ -28,10 +26,6 @@ class LoginAssetCheckSerializer(serializers.Serializer): self.asset = self.validate_object_exist(Asset, asset_id) return asset_id - def validate_account_id(self, account_id): - self.account = self.validate_object_exist(Account, account_id) - return account_id - @staticmethod def validate_object_exist(model, field_id): with tmp_to_root_org(): @@ -41,6 +35,17 @@ class LoginAssetCheckSerializer(serializers.Serializer): raise serializers.ValidationError(error) return obj + def validate_account_username(self, account_username): + asset_id = self.initial_data.get('asset_id') + account = Account.objects.filter( + username=account_username, asset_id=asset_id + ).first() + if not account: + error = 'Account username does not exist' + raise serializers.ValidationError(error) + self.username = account_username + return account_username + @lazyproperty def org(self): return self.asset.org From f6bdc7f81cfe4fc12e706570b33849922529eb28 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 28 Nov 2022 22:58:43 +0800 Subject: [PATCH 406/488] =?UTF-8?q?pref:=20=E6=9A=82=E5=AD=98=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E8=BF=9E=E6=8E=A5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 32 +++++++++++++------ .../migrations/0016_auto_20221125_2240.py | 12 +++---- .../migrations/0017_auto_20221128_2148.py | 20 ++++++++++++ .../authentication/models/connection_token.py | 1 + .../serializers/connection_token.py | 24 +++++++------- .../ops/migrations/0036_auto_20221128_2148.py | 21 ++++++++++++ apps/terminal/const.py | 24 ++++++++------ 7 files changed, 96 insertions(+), 38 deletions(-) create mode 100644 apps/authentication/migrations/0017_auto_20221128_2148.py create mode 100644 apps/ops/migrations/0036_auto_20221128_2148.py diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index e08ba5291..777c02e6a 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -11,6 +11,7 @@ from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework.request import Request from rest_framework.response import Response +from rest_framework.serializers import ValidationError from common.drf.api import JMSModelViewSet from common.http import is_true @@ -18,6 +19,7 @@ from common.utils import random_string from orgs.mixins.api import RootOrgViewMixin from perms.models import ActionChoices from terminal.models import EndpointRule +from terminal.const import NativeClient from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, @@ -128,19 +130,20 @@ class RDPFileClientProtocolURLMixin: return true_value if is_true(os.getenv(env_key, env_default)) else false_value def get_client_protocol_data(self, token: ConnectionToken): - protocol = token.protocol username = token.user.username rdp_config = ssh_token = '' - if protocol == 'rdp': - filename, rdp_config = self.get_rdp_file_info(token) - elif protocol == 'ssh': + connect_method = token.connect_method + + if connect_method == NativeClient.ssh: filename, ssh_token = self.get_ssh_token(token) + elif connect_method == NativeClient.mstsc: + filename, rdp_config = self.get_rdp_file_info(token) else: - raise ValueError('Protocol not support: {}'.format(protocol)) + raise ValueError('Protocol not support: {}'.format(connect_method)) return { "filename": filename, - "protocol": protocol, + "protocol": token.protocol, "username": username, "token": ssh_token, "config": rdp_config @@ -234,14 +237,25 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView rbac_perm = 'authentication.view_connectiontokensecret' if not request.user.has_perm(rbac_perm): raise PermissionDenied('Not allow to view secret') - token_id = request.data.get('token') or '' + + token_id = request.data.get('id') or '' token = get_object_or_404(ConnectionToken, pk=token_id) + if token.is_expired: + raise ValidationError({'id': 'Token is expired'}) + token.is_valid() serializer = self.get_serializer(instance=token) + expire_now = request.data.get('expire_now', True) + if expire_now: + token.expire() + return Response(serializer.data, status=status.HTTP_200_OK) def get_queryset(self): - return ConnectionToken.objects.filter(user=self.request.user) + queryset = ConnectionToken.objects\ + .filter(user=self.request.user)\ + .filter(date_expired__lt=timezone.now()) + return queryset def get_user(self, serializer): return self.request.user @@ -299,7 +313,7 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet): def renewal(self, request, *args, **kwargs): from common.utils.timezone import as_current_tz - token_id = request.data.get('token') or '' + token_id = request.data.get('id') or '' token = get_object_or_404(ConnectionToken, pk=token_id) date_expired = as_current_tz(token.date_expired) if token.is_expired: diff --git a/apps/authentication/migrations/0016_auto_20221125_2240.py b/apps/authentication/migrations/0016_auto_20221125_2240.py index 92478a523..ac1294f86 100644 --- a/apps/authentication/migrations/0016_auto_20221125_2240.py +++ b/apps/authentication/migrations/0016_auto_20221125_2240.py @@ -26,20 +26,20 @@ class Migration(migrations.Migration): old_name='username', new_name='input_username', ), - migrations.AddField( - model_name='connectiontoken', - name='input_secret', - field=common.db.fields.EncryptCharField(default='', max_length=128, verbose_name='Input Secret'), - ), migrations.AlterField( model_name='connectiontoken', name='account_name', field=models.CharField(max_length=128, verbose_name='Account name'), ), + migrations.AlterField( + model_name='connectiontoken', + name='input_secret', + field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, verbose_name='Input Secret'), + ), migrations.AlterField( model_name='connectiontoken', name='input_username', - field=models.CharField(default='', max_length=128, verbose_name='Input Username'), + field=models.CharField(blank=True, default='', max_length=128, verbose_name='Input Username'), ), migrations.AlterField( model_name='connectiontoken', diff --git a/apps/authentication/migrations/0017_auto_20221128_2148.py b/apps/authentication/migrations/0017_auto_20221128_2148.py new file mode 100644 index 000000000..396089bbc --- /dev/null +++ b/apps/authentication/migrations/0017_auto_20221128_2148.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.14 on 2022-11-28 13:48 + +import common.db.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0016_auto_20221125_2240'), + ] + + operations = [ + migrations.AddField( + model_name='connectiontoken', + name='connect_method', + field=models.CharField(default='web_ui', max_length=32, verbose_name='Connect method'), + preserve_default=False, + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 82ad3589f..5505f81a3 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -34,6 +34,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): protocol = models.CharField( choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) + connect_method = models.CharField(max_length=32, verbose_name=_("Connect method")) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) date_expired = models.DateTimeField( diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 767106aba..0a223306a 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -109,16 +109,10 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): class Meta: model = Asset - fields = ['id', 'address', 'port', 'username', 'password', 'private_key'] - - -class ConnectionTokenDomainSerializer(serializers.ModelSerializer): - """ Domain """ - gateways = ConnectionTokenGatewaySerializer(many=True, read_only=True) - - class Meta: - model = Domain - fields = ['id', 'name', 'gateways'] + fields = [ + 'id', 'address', 'port', 'username', + 'password', 'private_key' + ] class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): @@ -143,6 +137,7 @@ class ConnectionTokenPlatform(PlatformSerializer): class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): + expire_now = serializers.BooleanField(label=_('Expired now'), default=True) user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) platform = ConnectionTokenPlatform(read_only=True) @@ -155,7 +150,10 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'value', 'user', 'asset', 'account', - 'protocol', 'domain', 'gateway', - 'actions', 'expire_at', 'platform', + 'id', 'value', 'user', 'asset', 'platform', 'account', + 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', ] + extra_kwargs = { + 'value': {'read_only': True}, + 'expire_now': {'write_only': True}, + } diff --git a/apps/ops/migrations/0036_auto_20221128_2148.py b/apps/ops/migrations/0036_auto_20221128_2148.py new file mode 100644 index 000000000..b2da1a35a --- /dev/null +++ b/apps/ops/migrations/0036_auto_20221128_2148.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.14 on 2022-11-28 13:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0035_jobexecution_org_id'), + ] + + operations = [ + migrations.AlterModelOptions( + name='job', + options={'ordering': ['date_created']}, + ), + migrations.AlterModelOptions( + name='jobexecution', + options={'ordering': ['-date_created']}, + ), + ] diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 21d79fd0a..177d50e20 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -96,17 +96,21 @@ class NativeClient(TextChoices): @classmethod def get_launch_command(cls, name, os='windows'): commands = { - 'ssh': 'ssh {username}@{hostname} -p {port}', - 'putty': 'putty -ssh {username}@{hostname} -P {port}', - 'xshell': '-url ssh://root:passwd@192.168.10.100', - 'mysql': 'mysql -h {hostname} -P {port} -u {username} -p', - 'psql': { - 'default': 'psql -h {hostname} -p {port} -U {username} -W', - 'windows': 'psql /h {hostname} /p {port} /U {username} -W', + cls.ssh: 'ssh {token.id}@{endpoint.ip} -p {endpoint.port}', + cls.putty: 'putty-ssh {token.id}@{endpoint.ip} -P {endpoint.port}', + cls.xshell: 'xshell -url ssh://{token.id}:{token.value}@{endpoint.ip}:{endpoint.port}', + # 'mysql': 'mysql -h {hostname} -P {port} -u {username} -p', + # 'psql': { + # 'default': 'psql -h {hostname} -p {port} -U {username} -W', + # 'windows': 'psql /h {hostname} /p {port} /U {username} -W', + # }, + # 'sqlplus': 'sqlplus {username}/{password}@{hostname}:{port}', + # 'redis': 'redis-cli -h {hostname} -p {port} -a {password}', + cls.mstsc: { + 'command': "$open_file$", + 'file': { + } }, - 'sqlplus': 'sqlplus {username}/{password}@{hostname}:{port}', - 'redis': 'redis-cli -h {hostname} -p {port} -a {password}', - 'mstsc': 'mstsc /v:{hostname}:{port}', } command = commands.get(name) if isinstance(command, dict): From e4edf3be02a729b6e8b5c7ca394d3a80e817c678 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 29 Nov 2022 09:34:43 +0800 Subject: [PATCH 407/488] perf: migrate --- .../migrations/0016_auto_20221125_2240.py | 10 --------- .../migrations/0017_auto_20221128_1839.py | 11 ++++++++-- .../migrations/0017_auto_20221128_2148.py | 20 ------------------ .../ops/migrations/0036_auto_20221128_2148.py | 21 ------------------- 4 files changed, 9 insertions(+), 53 deletions(-) delete mode 100644 apps/authentication/migrations/0017_auto_20221128_2148.py delete mode 100644 apps/ops/migrations/0036_auto_20221128_2148.py diff --git a/apps/authentication/migrations/0016_auto_20221125_2240.py b/apps/authentication/migrations/0016_auto_20221125_2240.py index ac1294f86..745fcef2b 100644 --- a/apps/authentication/migrations/0016_auto_20221125_2240.py +++ b/apps/authentication/migrations/0016_auto_20221125_2240.py @@ -31,16 +31,6 @@ class Migration(migrations.Migration): name='account_name', field=models.CharField(max_length=128, verbose_name='Account name'), ), - migrations.AlterField( - model_name='connectiontoken', - name='input_secret', - field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, verbose_name='Input Secret'), - ), - migrations.AlterField( - model_name='connectiontoken', - name='input_username', - field=models.CharField(blank=True, default='', max_length=128, verbose_name='Input Username'), - ), migrations.AlterField( model_name='connectiontoken', name='value', diff --git a/apps/authentication/migrations/0017_auto_20221128_1839.py b/apps/authentication/migrations/0017_auto_20221128_1839.py index 31a49267a..8c392cb92 100644 --- a/apps/authentication/migrations/0017_auto_20221128_1839.py +++ b/apps/authentication/migrations/0017_auto_20221128_1839.py @@ -11,10 +11,17 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( + migrations.AddField( + model_name='connectiontoken', + name='connect_method', + field=models.CharField(default='web_ui', max_length=32, verbose_name='Connect method'), + preserve_default=False, + ), + migrations.AddField( model_name='connectiontoken', name='input_secret', - field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, verbose_name='Input Secret'), + field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, + verbose_name='Input Secret'), ), migrations.AlterField( model_name='connectiontoken', diff --git a/apps/authentication/migrations/0017_auto_20221128_2148.py b/apps/authentication/migrations/0017_auto_20221128_2148.py deleted file mode 100644 index 396089bbc..000000000 --- a/apps/authentication/migrations/0017_auto_20221128_2148.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-28 13:48 - -import common.db.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0016_auto_20221125_2240'), - ] - - operations = [ - migrations.AddField( - model_name='connectiontoken', - name='connect_method', - field=models.CharField(default='web_ui', max_length=32, verbose_name='Connect method'), - preserve_default=False, - ), - ] diff --git a/apps/ops/migrations/0036_auto_20221128_2148.py b/apps/ops/migrations/0036_auto_20221128_2148.py deleted file mode 100644 index b2da1a35a..000000000 --- a/apps/ops/migrations/0036_auto_20221128_2148.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-28 13:48 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0035_jobexecution_org_id'), - ] - - operations = [ - migrations.AlterModelOptions( - name='job', - options={'ordering': ['date_created']}, - ), - migrations.AlterModelOptions( - name='jobexecution', - options={'ordering': ['-date_created']}, - ), - ] From 0981cd1ed1d80aa79c8afa4f36b94a1a53143303 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 29 Nov 2022 14:42:04 +0800 Subject: [PATCH 408/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20Connect=20?= =?UTF-8?q?token=20=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 64 ++++++++----------- .../migrations/0016_auto_20221125_2240.py | 15 ++++- .../migrations/0017_auto_20221128_1839.py | 13 ---- .../0018_connectiontoken_endpoint_protocol.py | 19 ++++++ .../authentication/models/connection_token.py | 3 + .../serializers/connection_token.py | 10 ++- apps/common/utils/django.py | 19 +++++- apps/common/utils/http.py | 7 +- apps/terminal/api/component/terminal.py | 9 +-- apps/terminal/const.py | 55 ++++++++++------ apps/terminal/serializers/terminal.py | 4 +- 11 files changed, 129 insertions(+), 89 deletions(-) create mode 100644 apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 777c02e6a..b8f461d52 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -16,10 +16,11 @@ from rest_framework.serializers import ValidationError from common.drf.api import JMSModelViewSet from common.http import is_true from common.utils import random_string +from common.utils.django import get_request_os from orgs.mixins.api import RootOrgViewMixin from perms.models import ActionChoices -from terminal.models import EndpointRule from terminal.const import NativeClient +from terminal.models import EndpointRule from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, @@ -130,42 +131,32 @@ class RDPFileClientProtocolURLMixin: return true_value if is_true(os.getenv(env_key, env_default)) else false_value def get_client_protocol_data(self, token: ConnectionToken): - username = token.user.username - rdp_config = ssh_token = '' - connect_method = token.connect_method + _os = get_request_os(self.request) - if connect_method == NativeClient.ssh: - filename, ssh_token = self.get_ssh_token(token) - elif connect_method == NativeClient.mstsc: - filename, rdp_config = self.get_rdp_file_info(token) - else: - raise ValueError('Protocol not support: {}'.format(connect_method)) + connect_method = getattr(NativeClient, token.connect_method, None) + if connect_method is None: + raise ValueError('Connect method not support: {}'.format(token.connect_method)) - return { - "filename": filename, - "protocol": token.protocol, - "username": username, - "token": ssh_token, - "config": rdp_config - } - - def get_ssh_token(self, token: ConnectionToken): - if token.asset: - name = token.asset.name - else: - name = '*' - prefix_name = f'{token.user.username}-{name}' - filename = self.get_connect_filename(prefix_name) - - endpoint = self.get_smart_endpoint(protocol='ssh', asset=token.asset) data = { - 'ip': endpoint.host, - 'port': str(endpoint.ssh_port), - 'username': 'JMS-{}'.format(str(token.id)), - 'password': token.value + 'id': str(token.id), + 'value': token.value, + 'cmd': '', + 'file': {} } - token = json.dumps(data) - return filename, token + + if connect_method == NativeClient.mstsc: + filename, content = self.get_rdp_file_info(token) + data.update({ + 'file': { + 'filename': filename, + 'content': content, + } + }) + else: + endpoint = self.get_smart_endpoint(protocol=token.endpoint_protocol, asset=token.asset) + cmd = NativeClient.get_launch_command(connect_method, token, endpoint) + data.update({'cmd': cmd}) + return data def get_smart_endpoint(self, protocol, asset=None): target_ip = asset.get_target_ip() if asset else '' @@ -223,6 +214,7 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView 'get_secret_detail': ConnectionTokenSecretSerializer, } rbac_perms = { + 'list': 'authentication.view_connectiontoken', 'retrieve': 'authentication.view_connectiontoken', 'create': 'authentication.add_connectiontoken', 'expire': 'authentication.add_connectiontoken', @@ -252,9 +244,9 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView return Response(serializer.data, status=status.HTTP_200_OK) def get_queryset(self): - queryset = ConnectionToken.objects\ - .filter(user=self.request.user)\ - .filter(date_expired__lt=timezone.now()) + queryset = ConnectionToken.objects \ + .filter(user=self.request.user) \ + .filter(date_expired__gt=timezone.now()) return queryset def get_user(self, serializer): diff --git a/apps/authentication/migrations/0016_auto_20221125_2240.py b/apps/authentication/migrations/0016_auto_20221125_2240.py index 745fcef2b..041a29fc6 100644 --- a/apps/authentication/migrations/0016_auto_20221125_2240.py +++ b/apps/authentication/migrations/0016_auto_20221125_2240.py @@ -1,11 +1,11 @@ # Generated by Django 3.2.14 on 2022-11-25 14:40 -import common.db.fields from django.db import migrations, models +import common.db.fields + class Migration(migrations.Migration): - dependencies = [ ('authentication', '0015_alter_connectiontoken_login'), ] @@ -36,4 +36,15 @@ class Migration(migrations.Migration): name='value', field=models.CharField(default='', max_length=64, verbose_name='Value'), ), + migrations.AddField( + model_name='connectiontoken', + name='input_secret', + field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, + verbose_name='Input Secret'), + ), + migrations.AlterField( + model_name='connectiontoken', + name='input_username', + field=models.CharField(blank=True, default='', max_length=128, verbose_name='Input Username'), + ), ] diff --git a/apps/authentication/migrations/0017_auto_20221128_1839.py b/apps/authentication/migrations/0017_auto_20221128_1839.py index 8c392cb92..bcdb71020 100644 --- a/apps/authentication/migrations/0017_auto_20221128_1839.py +++ b/apps/authentication/migrations/0017_auto_20221128_1839.py @@ -1,11 +1,9 @@ # Generated by Django 3.2.14 on 2022-11-28 10:39 -import common.db.fields from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('authentication', '0016_auto_20221125_2240'), ] @@ -17,15 +15,4 @@ class Migration(migrations.Migration): field=models.CharField(default='web_ui', max_length=32, verbose_name='Connect method'), preserve_default=False, ), - migrations.AddField( - model_name='connectiontoken', - name='input_secret', - field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, - verbose_name='Input Secret'), - ), - migrations.AlterField( - model_name='connectiontoken', - name='input_username', - field=models.CharField(blank=True, default='', max_length=128, verbose_name='Input Username'), - ), ] diff --git a/apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py b/apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py new file mode 100644 index 000000000..f267a62fd --- /dev/null +++ b/apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.14 on 2022-11-29 04:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0017_auto_20221128_1839'), + ] + + operations = [ + migrations.AddField( + model_name='connectiontoken', + name='endpoint_protocol', + field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S'), ('http', 'HTTP'), ('None', ' Settings')], default='', max_length=16, verbose_name='Endpoint protocol'), + preserve_default=False, + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 5505f81a3..7f4e7f42b 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -35,6 +35,9 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) connect_method = models.CharField(max_length=32, verbose_name=_("Connect method")) + endpoint_protocol = models.CharField( + choices=Protocol.choices, max_length=16, verbose_name=_("Endpoint protocol") + ) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) date_expired = models.DateTimeField( diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 0a223306a..5915f542b 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from assets.models import Asset, Domain, CommandFilterRule, Account, Platform +from assets.models import Asset, CommandFilterRule, Account, Platform from assets.serializers import PlatformSerializer, AssetProtocolsSerializer from authentication.models import ConnectionToken from orgs.mixins.serializers import OrgResourceModelSerializerMixin @@ -21,21 +21,19 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields_mini = ['id', 'value'] fields_small = fields_mini + [ - 'protocol', 'account_name', + 'user', 'asset', 'account_name', 'input_username', 'input_secret', + 'connect_method', 'endpoint_protocol', 'protocol', 'actions', 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', ] - fields_fk = [ - 'user', 'asset', - ] read_only_fields = [ # 普通 Token 不支持指定 user 'user', 'expire_time', 'user_display', 'asset_display', ] - fields = fields_small + fields_fk + read_only_fields + fields = fields_small + read_only_fields extra_kwargs = { 'value': {'read_only': True}, } diff --git a/apps/common/utils/django.py b/apps/common/utils/django.py index 1f3f83282..2c4808d16 100644 --- a/apps/common/utils/django.py +++ b/apps/common/utils/django.py @@ -2,11 +2,11 @@ # import re -from django.shortcuts import reverse as dj_reverse from django.conf import settings -from django.utils import timezone from django.db import models from django.db.models.signals import post_save, pre_save +from django.shortcuts import reverse as dj_reverse +from django.utils import timezone UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}') @@ -80,3 +80,18 @@ def bulk_create_with_signal(cls: models.Model, items, **kwargs): for i in items: post_save.send(sender=cls, instance=i, created=True) return result + + +def get_request_os(request): + """获取请求的操作系统""" + agent = request.META.get('HTTP_USER_AGENT', '').lower() + + if agent is None: + return 'unknown' + if 'windows' in agent.lower(): + return 'windows' + if 'mac' in agent.lower(): + return 'mac' + if 'linux' in agent.lower(): + return 'linux' + return 'unknown' diff --git a/apps/common/utils/http.py b/apps/common/utils/http.py index 185397881..ab39c4fa2 100644 --- a/apps/common/utils/http.py +++ b/apps/common/utils/http.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -import time -from email.utils import formatdate import calendar import threading +import time +from email.utils import formatdate _STRPTIME_LOCK = threading.Lock() @@ -35,3 +35,6 @@ def http_to_unixtime(time_string): def iso8601_to_unixtime(time_string): """把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。""" return to_unixtime(time_string, _ISO8601_FORMAT) + + + diff --git a/apps/terminal/api/component/terminal.py b/apps/terminal/api/component/terminal.py index e3aa4afb3..df14296f5 100644 --- a/apps/terminal/api/component/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -12,6 +12,7 @@ from common.drf.api import JMSBulkModelViewSet from common.exceptions import JMSException from common.permissions import IsValidUser from common.permissions import WithBootstrapToken +from common.utils import get_request_os from terminal import serializers from terminal.const import TerminalType from terminal.models import Terminal @@ -77,13 +78,7 @@ class ConnectMethodListApi(generics.ListAPIView): permission_classes = [IsValidUser] def get_queryset(self): - user_agent = self.request.META['HTTP_USER_AGENT'].lower() - if 'macintosh' in user_agent: - os = 'macos' - elif 'windows' in user_agent: - os = 'windows' - else: - os = 'linux' + os = get_request_os(self.request) return TerminalType.get_protocols_connect_methods(os) def list(self, request, *args, **kwargs): diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 177d50e20..40c89ff24 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -56,7 +56,11 @@ class NativeClient(TextChoices): xshell = 'xshell', 'Xshell' # Magnus - db_client = 'db_client', _('DB Client') + mysql = 'db_client_mysql', _('DB Client') + psql = 'db_client_psql', _('DB Client') + sqlplus = 'db_client_sqlplus', _('DB Client') + redis = 'db_client_redis', _('DB Client') + mongodb = 'db_client_mongodb', _('DB Client') # Razor mstsc = 'mstsc', 'Remote Desktop' @@ -69,14 +73,23 @@ class NativeClient(TextChoices): 'windows': [cls.putty], }, Protocol.rdp: [cls.mstsc], - Protocol.mysql: [cls.db_client], - Protocol.oracle: [cls.db_client], - Protocol.postgresql: [cls.db_client], - Protocol.redis: [cls.db_client], - Protocol.mongodb: [cls.db_client], + Protocol.mysql: [cls.mysql], + Protocol.oracle: [cls.sqlplus], + Protocol.postgresql: [cls.psql], + Protocol.redis: [cls.redis], + Protocol.mongodb: [cls.mongodb], } return clients + @classmethod + def get_target_protocol(cls, name, os): + for protocol, clients in cls.get_native_clients().items(): + if isinstance(clients, dict): + clients = clients.get(os) or clients.get('default') + if name in clients: + return protocol + return None + @classmethod def get_methods(cls, os='windows'): clients_map = cls.get_native_clients() @@ -94,23 +107,18 @@ class NativeClient(TextChoices): return methods @classmethod - def get_launch_command(cls, name, os='windows'): + def get_launch_command(cls, name, token, endpoint, os='windows'): commands = { - cls.ssh: 'ssh {token.id}@{endpoint.ip} -p {endpoint.port}', - cls.putty: 'putty-ssh {token.id}@{endpoint.ip} -P {endpoint.port}', - cls.xshell: 'xshell -url ssh://{token.id}:{token.value}@{endpoint.ip}:{endpoint.port}', - # 'mysql': 'mysql -h {hostname} -P {port} -u {username} -p', - # 'psql': { + cls.ssh: f'ssh {token.id}@{endpoint.host} -p {endpoint.ssh_port}', + cls.putty: f'putty -ssh {token.id}@{endpoint.host} -P {endpoint.ssh_port}', + cls.xshell: f'xshell -url ssh://{token.id}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', + # cls.mysql: 'mysql -h {hostname} -P {port} -u {username} -p', + # cls.psql: { # 'default': 'psql -h {hostname} -p {port} -U {username} -W', # 'windows': 'psql /h {hostname} /p {port} /U {username} -W', # }, - # 'sqlplus': 'sqlplus {username}/{password}@{hostname}:{port}', - # 'redis': 'redis-cli -h {hostname} -p {port} -a {password}', - cls.mstsc: { - 'command': "$open_file$", - 'file': { - } - }, + # cls.sqlplus: 'sqlplus {username}/{password}@{hostname}:{port}', + # cls.redis: 'redis-cli -h {hostname} -p {port} -a {password}', } command = commands.get(name) if isinstance(command, dict): @@ -217,19 +225,26 @@ class TerminalType(TextChoices): methods[protocol.value].append({ 'value': web_protocol.value, 'label': web_protocol.label, + 'endpoint_protocol': 'http', 'type': 'web', 'component': component.value, }) # Native method methods[protocol.value].extend([ - {'component': component.value, 'type': 'native', **method} + { + 'component': component.value, + 'type': 'native', + 'endpoint_protocol': listen_protocol, + **method + } for method in native_methods[listen_protocol] ]) for protocol, applet_methods in applet_methods.items(): for method in applet_methods: method['type'] = 'applet' + method['listen'] = 'rdp' method['component'] = cls.tinker.value methods[protocol].extend(applet_methods) return methods diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index df32d89c2..f7f935f50 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -138,4 +138,6 @@ class TerminalRegistrationSerializer(serializers.ModelSerializer): class ConnectMethodSerializer(serializers.Serializer): value = serializers.CharField(max_length=128) label = serializers.CharField(max_length=128) - group = serializers.CharField(max_length=128) + type = serializers.CharField(max_length=128) + listen = serializers.CharField(max_length=128) + component = serializers.CharField(max_length=128) From 9412c5d42d481a4fe5e5bc8be209df2728152fba Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 29 Nov 2022 14:45:29 +0800 Subject: [PATCH 409/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token=20=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index b8f461d52..3639206d3 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -140,7 +140,7 @@ class RDPFileClientProtocolURLMixin: data = { 'id': str(token.id), 'value': token.value, - 'cmd': '', + 'command': '', 'file': {} } @@ -148,14 +148,14 @@ class RDPFileClientProtocolURLMixin: filename, content = self.get_rdp_file_info(token) data.update({ 'file': { - 'filename': filename, + 'name': filename, 'content': content, } }) else: endpoint = self.get_smart_endpoint(protocol=token.endpoint_protocol, asset=token.asset) cmd = NativeClient.get_launch_command(connect_method, token, endpoint) - data.update({'cmd': cmd}) + data.update({'command': cmd}) return data def get_smart_endpoint(self, protocol, asset=None): From 3ac952f7355c1e2320a04cc90d3af66adabc3e56 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 29 Nov 2022 15:47:35 +0800 Subject: [PATCH 410/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token=20=E6=8B=89=E8=B5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 3639206d3..387081cc6 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -24,8 +24,7 @@ from terminal.models import EndpointRule from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, - SuperConnectionTokenSerializer, ConnectionTokenDisplaySerializer, -) + SuperConnectionTokenSerializer, ) __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] @@ -209,8 +208,6 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView search_fields = filterset_fields serializer_classes = { 'default': ConnectionTokenSerializer, - 'list': ConnectionTokenDisplaySerializer, - 'retrieve': ConnectionTokenDisplaySerializer, 'get_secret_detail': ConnectionTokenSecretSerializer, } rbac_perms = { From d849fd52bd7102aebc87bebce656c5fc7212d86a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 29 Nov 2022 17:01:03 +0800 Subject: [PATCH 411/488] perf: domian add node (#9130) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/migrations/0114_node_domain.py | 21 +++++++++++++++++++++ apps/assets/models/asset/common.py | 8 ++++++++ apps/assets/models/node.py | 9 +++++++-- apps/assets/serializers/domain.py | 17 ++++++++++++----- apps/ops/ansible/inventory.py | 7 +++++-- 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 apps/assets/migrations/0114_node_domain.py diff --git a/apps/assets/migrations/0114_node_domain.py b/apps/assets/migrations/0114_node_domain.py new file mode 100644 index 000000000..896a611d5 --- /dev/null +++ b/apps/assets/migrations/0114_node_domain.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.14 on 2022-11-29 05:14 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0113_alter_accounttemplate_options'), + ] + + operations = [ + migrations.AddField( + model_name='node', + name='domain', + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, + related_name='nodes', to='assets.domain', verbose_name='Domain' + ), + ), + ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 81693252d..7f01c222d 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -105,6 +105,14 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): def __str__(self): return '{0.name}({0.address})'.format(self) + def get_domains(self): + from ..domain import Domain + node_ids = self.get_all_nodes(flat=True) + domains = Domain.objects.filter( + Q(nodes__id__in=node_ids) | Q(id=self.domain_id) + ).distinct() + return domains + @property def specific(self): if not hasattr(self, self.category): diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 0e98bce14..bf394e982 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -554,9 +554,14 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin): full_value = models.CharField(max_length=4096, verbose_name=_('Full value'), default='') child_mark = models.IntegerField(default=0) date_create = models.DateTimeField(auto_now_add=True) - parent_key = models.CharField(max_length=64, verbose_name=_("Parent key"), - db_index=True, default='') + parent_key = models.CharField( + max_length=64, verbose_name=_("Parent key"), db_index=True, default='' + ) assets_amount = models.IntegerField(default=0) + domain = models.ForeignKey( + "assets.Domain", null=True, blank=True, related_name='nodes', + verbose_name=_("Domain"), on_delete=models.SET_NULL + ) objects = OrgManager.from_queryset(NodeQuerySet)() is_node = True diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 9e495d104..617aa34bc 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -4,19 +4,22 @@ from rest_framework import serializers from rest_framework.generics import get_object_or_404 from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.serializers import BulkOrgResourceModelSerializer, OrgResourceSerializerMixin +from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin, WritableNestedModelSerializer from common.drf.fields import ObjectRelatedField, EncryptedField -from assets.models import Platform, Node from assets.const import SecretType, GATEWAY_NAME from ..serializers import AssetProtocolsSerializer -from ..models import Domain, Asset, Account, Host +from ..models import Platform, Domain, Node, Asset, Account, Host from .utils import validate_password_for_ansible, validate_ssh_key class DomainSerializer(BulkOrgResourceModelSerializer): + node_count = serializers.SerializerMethodField(label=_('Nodes amount')) asset_count = serializers.SerializerMethodField(label=_('Assets amount')) gateway_count = serializers.SerializerMethodField(label=_('Gateways count')) + nodes = ObjectRelatedField( + many=True, required=False, queryset=Node.objects, label=_('Node') + ) assets = ObjectRelatedField( many=True, required=False, queryset=Asset.objects, label=_('Asset') ) @@ -25,14 +28,18 @@ class DomainSerializer(BulkOrgResourceModelSerializer): model = Domain fields_mini = ['id', 'name'] fields_small = fields_mini + ['comment'] - fields_m2m = ['assets'] - read_only_fields = ['asset_count', 'gateway_count', 'date_created'] + fields_m2m = ['nodes', 'assets'] + read_only_fields = ['node_count', 'asset_count', 'gateway_count', 'date_created'] fields = fields_small + fields_m2m + read_only_fields extra_kwargs = { 'assets': {'required': False, 'label': _('Assets')}, } + @staticmethod + def get_node_count(obj): + return obj.nodes.count() + @staticmethod def get_asset_count(obj): return obj.assets.count() diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 1e006ae77..aa7f8186d 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ import json import os +import random from collections import defaultdict from django.utils.translation import gettext as _ @@ -117,8 +118,10 @@ class JMSInventory: host.update(ansible_config) gateway = None - if asset.domain: - gateway = asset.domain.select_gateway() + domains = asset.get_domains() + if domains: + gateways = [i.select_gateway() for i in domains if i.select_gateway()] + gateway = random.choice(gateways) if gateways else None if ansible_connection == 'local': if gateway: From 426900145e12af820637080ab764032653f0080c Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 29 Nov 2022 17:08:15 +0800 Subject: [PATCH 412/488] perf: connect token asset add specific --- apps/authentication/serializers/connection_token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 5915f542b..e851ceadf 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -89,7 +89,8 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer): class Meta: model = Asset - fields = ['id', 'name', 'address', 'protocols', 'org_id'] + fields = ['id', 'name', 'address', 'protocols', + 'org_id', 'specific'] class ConnectionTokenAccountSerializer(serializers.ModelSerializer): From 65936485548b93d21ca7c971d9040c4e1b0f17bd Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 29 Nov 2022 18:36:42 +0800 Subject: [PATCH 413/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 49 +++++++++++++-------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 387081cc6..3e1a61a86 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -20,7 +20,7 @@ from common.utils.django import get_request_os from orgs.mixins.api import RootOrgViewMixin from perms.models import ActionChoices from terminal.const import NativeClient -from terminal.models import EndpointRule +from terminal.models import EndpointRule, Applet from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, @@ -33,13 +33,34 @@ class RDPFileClientProtocolURLMixin: request: Request get_serializer: callable + @staticmethod + def set_applet_info(token, rdp_options): + # remote-app + applet = Applet.objects.filter(name=token.connect_method).first() + if not applet: + return rdp_options + + cmdline = { + 'app_name': applet.name, + 'user_id': str(token.user.id), + 'asset_id': str(token.asset.id), + 'token_id': token.id + } + + app = '||tinker' + rdp_options['remoteapplicationmode:i'] = '1' + rdp_options['alternate shell:s'] = app + rdp_options['remoteapplicationprogram:s'] = app + rdp_options['remoteapplicationname:s'] = app + + cmdline_b64 = base64.b64encode(json.dumps(cmdline).encode()).decode() + rdp_options['remoteapplicationcmdline:s'] = cmdline_b64 + return rdp_options + def get_rdp_file_info(self, token: ConnectionToken): rdp_options = { 'full address:s': '', 'username:s': '', - # 'screen mode id:i': '1', - # 'desktopwidth:i': '1280', - # 'desktopheight:i': '800', 'use multimon:i': '0', 'session bpp:i': '32', 'audiomode:i': '0', @@ -60,11 +81,6 @@ class RDPFileClientProtocolURLMixin: 'bookmarktype:i': '3', 'use redirection server name:i': '0', 'smart sizing:i': '1', - # 'drivestoredirect:s': '*', - # 'domain:s': '' - # 'alternate shell:s:': '||MySQLWorkbench', - # 'remoteapplicationname:s': 'Firefox', - # 'remoteapplicationcmdline:s': '', } # 设置磁盘挂载 @@ -97,16 +113,11 @@ class RDPFileClientProtocolURLMixin: rdp_options['session bpp:i'] = os.getenv('JUMPSERVER_COLOR_DEPTH', '32') rdp_options['audiomode:i'] = self.parse_env_bool('JUMPSERVER_DISABLE_AUDIO', 'false', '2', '0') - if token.asset: - name = token.asset.name - # remote-app - # app = '||jmservisor' - # rdp_options['remoteapplicationmode:i'] = '1' - # rdp_options['alternate shell:s'] = app - # rdp_options['remoteapplicationprogram:s'] = app - # rdp_options['remoteapplicationname:s'] = name - else: - name = '*' + # 设置远程应用 + self.set_applet_info(token, rdp_options) + + # 文件名 + name = token.asset.name prefix_name = f'{token.user.username}-{name}' filename = self.get_connect_filename(prefix_name) From 52541d1dad94b40eda791a472b7eab1794b9e13a Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 29 Nov 2022 19:05:45 +0800 Subject: [PATCH 414/488] perf: push dynamic user --- apps/assets/tasks/push_account.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/assets/tasks/push_account.py b/apps/assets/tasks/push_account.py index c2c7156e8..7c596c8f2 100644 --- a/apps/assets/tasks/push_account.py +++ b/apps/assets/tasks/push_account.py @@ -12,11 +12,14 @@ __all__ = [ @org_aware_func("assets") -def push_accounts_to_assets_util(accounts, assets): +def push_accounts_to_assets_util(accounts, assets, username=None): from assets.models import PushAccountAutomation task_name = gettext_noop("Push accounts to assets") task_name = PushAccountAutomation.generate_unique_name(task_name) - account_usernames = list(accounts.values_list('username', flat=True)) + if username is None: + account_usernames = list(accounts.values_list('username', flat=True)) + else: + account_usernames = [username] data = { 'name': task_name, @@ -29,10 +32,10 @@ def push_accounts_to_assets_util(accounts, assets): @shared_task(queue="ansible", verbose_name=_('Push accounts to assets')) -def push_accounts_to_assets(account_ids, asset_ids): +def push_accounts_to_assets(account_ids, asset_ids, username=None): from assets.models import Asset, Account with tmp_to_root_org(): assets = Asset.objects.filter(id__in=asset_ids) accounts = Account.objects.filter(id__in=account_ids) - return push_accounts_to_assets_util(accounts, assets) + return push_accounts_to_assets_util(accounts, assets, username) From dd207016b2837e20651bdce6a8b7582a9879d577 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 29 Nov 2022 19:14:12 +0800 Subject: [PATCH 415/488] perf: del domain node --- apps/assets/migrations/0114_node_domain.py | 9 +-------- apps/assets/models/asset/common.py | 8 -------- apps/assets/models/node.py | 4 ---- apps/assets/serializers/domain.py | 10 +--------- apps/ops/ansible/inventory.py | 7 ++----- 5 files changed, 4 insertions(+), 34 deletions(-) diff --git a/apps/assets/migrations/0114_node_domain.py b/apps/assets/migrations/0114_node_domain.py index 896a611d5..0149a5d11 100644 --- a/apps/assets/migrations/0114_node_domain.py +++ b/apps/assets/migrations/0114_node_domain.py @@ -4,18 +4,11 @@ from django.db import migrations, models import django.db.models.deletion +# TODO 最后去掉这个迁移 class Migration(migrations.Migration): dependencies = [ ('assets', '0113_alter_accounttemplate_options'), ] operations = [ - migrations.AddField( - model_name='node', - name='domain', - field=models.ForeignKey( - blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='nodes', to='assets.domain', verbose_name='Domain' - ), - ), ] diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 7f01c222d..81693252d 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -105,14 +105,6 @@ class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): def __str__(self): return '{0.name}({0.address})'.format(self) - def get_domains(self): - from ..domain import Domain - node_ids = self.get_all_nodes(flat=True) - domains = Domain.objects.filter( - Q(nodes__id__in=node_ids) | Q(id=self.domain_id) - ).distinct() - return domains - @property def specific(self): if not hasattr(self, self.category): diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index bf394e982..54bdecf61 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -558,10 +558,6 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin): max_length=64, verbose_name=_("Parent key"), db_index=True, default='' ) assets_amount = models.IntegerField(default=0) - domain = models.ForeignKey( - "assets.Domain", null=True, blank=True, related_name='nodes', - verbose_name=_("Domain"), on_delete=models.SET_NULL - ) objects = OrgManager.from_queryset(NodeQuerySet)() is_node = True diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 617aa34bc..f60cb9516 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -14,12 +14,8 @@ from .utils import validate_password_for_ansible, validate_ssh_key class DomainSerializer(BulkOrgResourceModelSerializer): - node_count = serializers.SerializerMethodField(label=_('Nodes amount')) asset_count = serializers.SerializerMethodField(label=_('Assets amount')) gateway_count = serializers.SerializerMethodField(label=_('Gateways count')) - nodes = ObjectRelatedField( - many=True, required=False, queryset=Node.objects, label=_('Node') - ) assets = ObjectRelatedField( many=True, required=False, queryset=Asset.objects, label=_('Asset') ) @@ -29,17 +25,13 @@ class DomainSerializer(BulkOrgResourceModelSerializer): fields_mini = ['id', 'name'] fields_small = fields_mini + ['comment'] fields_m2m = ['nodes', 'assets'] - read_only_fields = ['node_count', 'asset_count', 'gateway_count', 'date_created'] + read_only_fields = ['asset_count', 'gateway_count', 'date_created'] fields = fields_small + fields_m2m + read_only_fields extra_kwargs = { 'assets': {'required': False, 'label': _('Assets')}, } - @staticmethod - def get_node_count(obj): - return obj.nodes.count() - @staticmethod def get_asset_count(obj): return obj.assets.count() diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index aa7f8186d..1e006ae77 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -1,7 +1,6 @@ # ~*~ coding: utf-8 ~*~ import json import os -import random from collections import defaultdict from django.utils.translation import gettext as _ @@ -118,10 +117,8 @@ class JMSInventory: host.update(ansible_config) gateway = None - domains = asset.get_domains() - if domains: - gateways = [i.select_gateway() for i in domains if i.select_gateway()] - gateway = random.choice(gateways) if gateways else None + if asset.domain: + gateway = asset.domain.select_gateway() if ansible_connection == 'local': if gateway: From cc5b37350c9fdde531d13076ef0471c5ca0df803 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 29 Nov 2022 19:37:11 +0800 Subject: [PATCH 416/488] perf: domain del nodes --- apps/assets/serializers/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index f60cb9516..3e2b4889f 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -24,7 +24,7 @@ class DomainSerializer(BulkOrgResourceModelSerializer): model = Domain fields_mini = ['id', 'name'] fields_small = fields_mini + ['comment'] - fields_m2m = ['nodes', 'assets'] + fields_m2m = ['assets'] read_only_fields = ['asset_count', 'gateway_count', 'date_created'] fields = fields_small + fields_m2m + read_only_fields From e191a197c6b71ed0be7a62b10cdc1af658401448 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 29 Nov 2022 19:44:12 +0800 Subject: [PATCH 417/488] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=89=A9?= =?UTF-8?q?=E4=BD=99=E6=B5=81=E7=A8=8B,=20=E4=BF=AE=E6=94=B9=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/adhoc.py | 7 +- apps/ops/api/job.py | 18 +++-- apps/ops/api/playbook.py | 8 ++- .../ops/migrations/0036_auto_20221129_1529.py | 26 ++++++++ .../ops/migrations/0037_auto_20221129_1926.py | 26 ++++++++ apps/ops/migrations/0038_playbook_org_id.py | 18 +++++ .../ops/migrations/0039_auto_20221129_1932.py | 25 +++++++ apps/ops/models/adhoc.py | 36 ++-------- apps/ops/models/job.py | 9 ++- apps/ops/models/playbook.py | 6 +- apps/ops/serializers/adhoc.py | 65 ++----------------- apps/ops/serializers/job.py | 3 +- apps/ops/serializers/playbook.py | 5 +- apps/ops/tasks.py | 11 +--- 14 files changed, 144 insertions(+), 119 deletions(-) create mode 100644 apps/ops/migrations/0036_auto_20221129_1529.py create mode 100644 apps/ops/migrations/0037_auto_20221129_1926.py create mode 100644 apps/ops/migrations/0038_playbook_org_id.py create mode 100644 apps/ops/migrations/0039_auto_20221129_1932.py diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 9889349df..aca7047f1 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -2,6 +2,8 @@ # from rest_framework import viewsets + +from orgs.mixins.api import OrgBulkModelViewSet from ..models import AdHoc from ..serializers import ( AdHocSerializer @@ -12,6 +14,7 @@ __all__ = [ ] -class AdHocViewSet(viewsets.ModelViewSet): - queryset = AdHoc.objects.all() +class AdHocViewSet(OrgBulkModelViewSet): serializer_class = AdHocSerializer + permission_classes = () + model = AdHoc diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index e668bcee0..65d741b52 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -5,10 +5,16 @@ from ops.serializers.job import JobSerializer, JobExecutionSerializer __all__ = ['JobViewSet', 'JobExecutionViewSet'] -from ops.tasks import run_ops_job, run_ops_job_executions +from ops.tasks import run_ops_job_execution from orgs.mixins.api import OrgBulkModelViewSet +def set_task_to_serializer_data(serializer, task): + data = getattr(serializer, "_data", {}) + data["task_id"] = task.id + setattr(serializer, "_data", data) + + class JobViewSet(OrgBulkModelViewSet): serializer_class = JobSerializer model = Job @@ -23,23 +29,25 @@ class JobViewSet(OrgBulkModelViewSet): def perform_create(self, serializer): instance = serializer.save() if instance.instant: - run_ops_job.delay(instance.id) + execution = instance.create_execution() + task = run_ops_job_execution.delay(execution.id) + set_task_to_serializer_data(serializer, task) class JobExecutionViewSet(OrgBulkModelViewSet): serializer_class = JobExecutionSerializer http_method_names = ('get', 'post', 'head', 'options',) - # filter_fields = ('type',) permission_classes = () model = JobExecution def perform_create(self, serializer): instance = serializer.save() - run_ops_job_executions.delay(instance.id) + task = run_ops_job_execution.delay(instance.id) + set_task_to_serializer_data(serializer, task) def get_queryset(self): query_set = super().get_queryset() job_id = self.request.query_params.get('job_id') if job_id: - self.queryset = query_set.filter(job_id=job_id) + query_set = query_set.filter(job_id=job_id) return query_set diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index 2cb846c0e..bda7dfe33 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -2,7 +2,8 @@ import os import zipfile from django.conf import settings -from rest_framework import viewsets + +from orgs.mixins.api import OrgBulkModelViewSet from ..models import Playbook from ..serializers.playbook import PlaybookSerializer @@ -15,9 +16,10 @@ def unzip_playbook(src, dist): fz.extract(file, dist) -class PlaybookViewSet(viewsets.ModelViewSet): - queryset = Playbook.objects.all() +class PlaybookViewSet(OrgBulkModelViewSet): serializer_class = PlaybookSerializer + permission_classes = () + model = Playbook def perform_create(self, serializer): instance = serializer.save() diff --git a/apps/ops/migrations/0036_auto_20221129_1529.py b/apps/ops/migrations/0036_auto_20221129_1529.py new file mode 100644 index 000000000..a766ee72d --- /dev/null +++ b/apps/ops/migrations/0036_auto_20221129_1529.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.14 on 2022-11-29 07:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0035_jobexecution_org_id'), + ] + + operations = [ + migrations.AlterModelOptions( + name='job', + options={'ordering': ['date_created']}, + ), + migrations.AlterModelOptions( + name='jobexecution', + options={'ordering': ['-date_created']}, + ), + migrations.AddField( + model_name='job', + name='use_parameter_define', + field=models.BooleanField(default=False, verbose_name='Use Parameter Define'), + ), + ] diff --git a/apps/ops/migrations/0037_auto_20221129_1926.py b/apps/ops/migrations/0037_auto_20221129_1926.py new file mode 100644 index 000000000..086a93cd7 --- /dev/null +++ b/apps/ops/migrations/0037_auto_20221129_1926.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.14 on 2022-11-29 11:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0036_auto_20221129_1529'), + ] + + operations = [ + migrations.RenameField( + model_name='adhoc', + old_name='owner', + new_name='creator', + ), + migrations.AddField( + model_name='adhoc', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + migrations.DeleteModel( + name='AdHocExecution', + ), + ] diff --git a/apps/ops/migrations/0038_playbook_org_id.py b/apps/ops/migrations/0038_playbook_org_id.py new file mode 100644 index 000000000..0de95334c --- /dev/null +++ b/apps/ops/migrations/0038_playbook_org_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-11-29 11:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0037_auto_20221129_1926'), + ] + + operations = [ + migrations.AddField( + model_name='playbook', + name='org_id', + field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), + ), + ] diff --git a/apps/ops/migrations/0039_auto_20221129_1932.py b/apps/ops/migrations/0039_auto_20221129_1932.py new file mode 100644 index 000000000..f54ed1cbe --- /dev/null +++ b/apps/ops/migrations/0039_auto_20221129_1932.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.14 on 2022-11-29 11:32 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ops', '0038_playbook_org_id'), + ] + + operations = [ + migrations.RemoveField( + model_name='playbook', + name='owner', + ), + migrations.AddField( + model_name='playbook', + name='creator', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator'), + ), + ] diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index e94223fb9..2c0cf9940 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -1,21 +1,18 @@ # ~*~ coding: utf-8 ~*~ -import os.path import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.db.models import BaseCreateUpdateModel from common.utils import get_logger -from .base import BaseAnsibleJob, BaseAnsibleExecution -from ..ansible import AdHocRunner +from orgs.mixins.models import JMSOrgBaseModel -__all__ = ["AdHoc", "AdHocExecution"] +__all__ = ["AdHoc"] logger = get_logger(__file__) -class AdHoc(BaseCreateUpdateModel): +class AdHoc(JMSOrgBaseModel): class Modules(models.TextChoices): shell = 'shell', _('Shell') winshell = 'win_shell', _('Powershell') @@ -26,7 +23,7 @@ class AdHoc(BaseCreateUpdateModel): module = models.CharField(max_length=128, choices=Modules.choices, default=Modules.shell, verbose_name=_('Module')) args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) - owner = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) @property def row_count(self): @@ -41,28 +38,3 @@ class AdHoc(BaseCreateUpdateModel): def __str__(self): return "{}: {}".format(self.module, self.args) - - -class AdHocExecution(BaseAnsibleExecution): - """ - AdHoc running history. - """ - task = models.ForeignKey('AdHoc', verbose_name=_("Adhoc"), related_name='executions', on_delete=models.CASCADE) - - def get_runner(self): - inv = self.task.inventory - inv.write_to_file(self.inventory_path) - - runner = AdHocRunner( - self.inventory_path, self.task.module, module_args=self.task.args, - pattern=self.task.pattern, project_dir=self.private_dir - ) - return runner - - def task_display(self): - return str(self.task) - - class Meta: - db_table = "ops_adhoc_execution" - get_latest_by = 'date_start' - verbose_name = _("AdHoc execution") diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 3795a0455..e74258b55 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -45,6 +45,7 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas')) runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip, verbose_name=_('Runas policy')) + use_parameter_define = models.BooleanField(default=False, verbose_name=(_('Use Parameter Define'))) parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define')) comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) @@ -77,9 +78,9 @@ class Job(JMSOrgBaseModel, PeriodTaskModelMixin): return total_cost / finished_count if finished_count else 0 def get_register_task(self): - from ..tasks import run_ops_job + from ..tasks import run_ops_job_execution name = "run_ops_job_period_{}".format(str(self.id)[:8]) - task = run_ops_job.name + task = run_ops_job_execution.name args = (str(self.id),) kwargs = {} return name, task, args, kwargs @@ -108,6 +109,10 @@ class JobExecution(JMSOrgBaseModel): date_start = models.DateTimeField(null=True, verbose_name=_('Date start'), db_index=True) date_finished = models.DateTimeField(null=True, verbose_name=_("Date finished")) + @property + def job_type(self): + return self.job.type + def get_runner(self): inv = self.job.inventory inv.write_to_file(self.inventory_path) diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index 10be7bd06..9af396124 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -5,14 +5,14 @@ from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ -from common.db.models import BaseCreateUpdateModel +from orgs.mixins.models import JMSOrgBaseModel -class Playbook(BaseCreateUpdateModel): +class Playbook(JMSOrgBaseModel): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name'), null=True) path = models.FileField(upload_to='playbooks/') - owner = models.ForeignKey('users.User', verbose_name=_("Owner"), on_delete=models.SET_NULL, null=True) + creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) @property def work_path(self): diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index f5d8d4780..fdbb9a397 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -6,70 +6,15 @@ import datetime from rest_framework import serializers from common.drf.fields import ReadableHiddenField -from ..models import AdHoc, AdHocExecution +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from ..models import AdHoc -class AdHocSerializer(serializers.ModelSerializer): - owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) +class AdHocSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): + creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) row_count = serializers.IntegerField(read_only=True) size = serializers.IntegerField(read_only=True) class Meta: model = AdHoc - fields = ["id", "name", "module", "row_count", "size", "args", "owner", "date_created", "date_updated"] - - -class AdHocExecutionSerializer(serializers.ModelSerializer): - stat = serializers.SerializerMethodField() - last_success = serializers.ListField(source='success_hosts') - last_failure = serializers.DictField(source='failed_hosts') - - class Meta: - model = AdHocExecution - fields_mini = ['id'] - fields_small = fields_mini + [ - 'timedelta', 'result', 'summary', 'short_id', - 'is_finished', 'is_success', - 'date_start', 'date_finished', - ] - fields_fk = ['task', 'task_display'] - fields_custom = ['stat', 'last_success', 'last_failure'] - fields = fields_small + fields_fk + fields_custom - - @staticmethod - def get_task(obj): - return obj.task.id - - @staticmethod - def get_stat(obj): - count_failed_hosts = len(obj.failed_hosts) - count_success_hosts = len(obj.success_hosts) - count_total = count_success_hosts + count_failed_hosts - return { - "total": count_total, - "success": count_success_hosts, - "failed": count_failed_hosts - } - - -class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer): - class Meta: - model = AdHocExecution - fields = [ - 'id', 'task', 'task_display', 'hosts_amount', 'adhoc', 'date_start', 'stat', - 'date_finished', 'timedelta', 'is_finished', 'is_success', - 'short_id', 'adhoc_short_id', 'last_success', 'last_failure' - ] - - -class AdHocExecutionNestSerializer(serializers.ModelSerializer): - last_success = serializers.ListField(source='success_hosts') - last_failure = serializers.DictField(source='failed_hosts') - last_run = serializers.CharField(source='short_id') - - class Meta: - model = AdHocExecution - fields = ( - 'last_success', 'last_failure', 'last_run', 'timedelta', - 'is_finished', 'is_success' - ) + fields = ["id", "name", "module", "row_count", "size", "args", "creator", "date_created", "date_updated"] diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index e5d76f85b..5775c7094 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -15,6 +15,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost"] fields = read_only_fields + [ "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", + "use_parameter_define", "parameters_define", "timeout", "chdir", @@ -28,7 +29,7 @@ class JobExecutionSerializer(serializers.ModelSerializer): class Meta: model = JobExecution read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created', - 'is_success', 'task_id', 'short_id'] + 'is_success', 'task_id', 'short_id', 'job_type'] fields = read_only_fields + [ "job", "parameters" ] diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py index 7ca165501..0dc1c458e 100644 --- a/apps/ops/serializers/playbook.py +++ b/apps/ops/serializers/playbook.py @@ -4,6 +4,7 @@ from rest_framework import serializers from common.drf.fields import ReadableHiddenField from ops.models import Playbook +from orgs.mixins.serializers import BulkOrgResourceModelSerializer def parse_playbook_name(path): @@ -11,8 +12,8 @@ def parse_playbook_name(path): return file_name.split(".")[-2] -class PlaybookSerializer(serializers.ModelSerializer): - owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) +class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): + creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) def create(self, validated_data): name = validated_data.get('name') diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 51541dd32..bd4d5d448 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -30,18 +30,11 @@ def run_ops_job(job_id): job = get_object_or_none(Job, id=job_id) with tmp_to_org(job.org): execution = job.create_execution() - try: - execution.start() - except SoftTimeLimitExceeded: - execution.set_error('Run timeout') - logger.error("Run adhoc timeout") - except Exception as e: - execution.set_error(e) - logger.error("Start adhoc execution error: {}".format(e)) + run_ops_job_execution(execution) @shared_task(soft_time_limit=60, queue="ansible", verbose_name=_("Run ansible task execution")) -def run_ops_job_executions(execution_id, **kwargs): +def run_ops_job_execution(execution_id, **kwargs): execution = get_object_or_none(JobExecution, id=execution_id) with tmp_to_org(execution.org): try: From d741f1434266fa62c089db912c26e5d200921651 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 29 Nov 2022 19:47:45 +0800 Subject: [PATCH 418/488] feat: merge migrations --- ...6_auto_20221128_1839_0039_auto_20221129_1932.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py diff --git a/apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py b/apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py new file mode 100644 index 000000000..c0bbcb9a1 --- /dev/null +++ b/apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.14 on 2022-11-29 11:47 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0036_auto_20221128_1839'), + ('ops', '0039_auto_20221129_1932'), + ] + + operations = [ + ] From 82a8118ca0f14e5e77f766213a99939514f952d4 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Tue, 29 Nov 2022 19:52:48 +0800 Subject: [PATCH 419/488] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0comment=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ops/migrations/0041_auto_20221129_1952.py | 23 +++++++++++++++++++ apps/ops/models/adhoc.py | 2 ++ apps/ops/models/playbook.py | 1 + apps/ops/serializers/adhoc.py | 3 ++- apps/ops/serializers/playbook.py | 2 +- 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 apps/ops/migrations/0041_auto_20221129_1952.py diff --git a/apps/ops/migrations/0041_auto_20221129_1952.py b/apps/ops/migrations/0041_auto_20221129_1952.py new file mode 100644 index 000000000..fb3411a4d --- /dev/null +++ b/apps/ops/migrations/0041_auto_20221129_1952.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-11-29 11:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932'), + ] + + operations = [ + migrations.AddField( + model_name='adhoc', + name='comment', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), + ), + migrations.AddField( + model_name='playbook', + name='comment', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), + ), + ] diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 2c0cf9940..890a47b91 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -24,6 +24,8 @@ class AdHoc(JMSOrgBaseModel): verbose_name=_('Module')) args = models.CharField(max_length=1024, default='', verbose_name=_('Args')) creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) + @property def row_count(self): diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index 9af396124..eb767649a 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -13,6 +13,7 @@ class Playbook(JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_('Name'), null=True) path = models.FileField(upload_to='playbooks/') creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True) + comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) @property def work_path(self): diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index fdbb9a397..48ddf6567 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -17,4 +17,5 @@ class AdHocSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerialize class Meta: model = AdHoc - fields = ["id", "name", "module", "row_count", "size", "args", "creator", "date_created", "date_updated"] + fields = ["id", "name", "module", "row_count", "size", "args", "creator", "comment", "date_created", + "date_updated"] diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py index 0dc1c458e..57c7f2fe5 100644 --- a/apps/ops/serializers/playbook.py +++ b/apps/ops/serializers/playbook.py @@ -25,5 +25,5 @@ class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerial class Meta: model = Playbook fields = [ - "id", "name", "path", "date_created", "owner", "date_updated" + "id", "name", "path", "comment", "date_created", "creator", "date_updated" ] From 44ee80b05a78bb4559eb5b9e5877a620f3b4491a Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 29 Nov 2022 21:41:33 +0800 Subject: [PATCH 420/488] =?UTF-8?q?perf:=20=E5=8E=BB=E6=8E=89=20connect=20?= =?UTF-8?q?token=20endpoint=20protocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 23 ++++++++++++------- ...emove_connectiontoken_endpoint_protocol.py | 17 ++++++++++++++ .../authentication/models/connection_token.py | 3 --- .../serializers/connection_token.py | 2 +- apps/terminal/const.py | 9 ++++++++ 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 3e1a61a86..67c732519 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -19,12 +19,13 @@ from common.utils import random_string from common.utils.django import get_request_os from orgs.mixins.api import RootOrgViewMixin from perms.models import ActionChoices -from terminal.const import NativeClient +from terminal.const import NativeClient, TerminalType from terminal.models import EndpointRule, Applet from ..models import ConnectionToken from ..serializers import ( ConnectionTokenSerializer, ConnectionTokenSecretSerializer, - SuperConnectionTokenSerializer, ) + SuperConnectionTokenSerializer, +) __all__ = ['ConnectionTokenViewSet', 'SuperConnectionTokenViewSet'] @@ -143,9 +144,12 @@ class RDPFileClientProtocolURLMixin: def get_client_protocol_data(self, token: ConnectionToken): _os = get_request_os(self.request) - connect_method = getattr(NativeClient, token.connect_method, None) - if connect_method is None: - raise ValueError('Connect method not support: {}'.format(token.connect_method)) + connect_method_name = token.connect_method + connect_method_dict = TerminalType.get_connect_method( + token.connect_method, token.protocol, _os + ) + if connect_method_dict is None: + raise ValueError('Connect method not support: {}'.format(connect_method_name)) data = { 'id': str(token.id), @@ -154,7 +158,7 @@ class RDPFileClientProtocolURLMixin: 'file': {} } - if connect_method == NativeClient.mstsc: + if connect_method_name == NativeClient.mstsc: filename, content = self.get_rdp_file_info(token) data.update({ 'file': { @@ -163,8 +167,11 @@ class RDPFileClientProtocolURLMixin: } }) else: - endpoint = self.get_smart_endpoint(protocol=token.endpoint_protocol, asset=token.asset) - cmd = NativeClient.get_launch_command(connect_method, token, endpoint) + endpoint = self.get_smart_endpoint( + protocol=connect_method_dict['endpoint_protocol'], + asset=token.asset + ) + cmd = NativeClient.get_launch_command(connect_method_name, token, endpoint) data.update({'command': cmd}) return data diff --git a/apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py b/apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py new file mode 100644 index 000000000..ef7f401bd --- /dev/null +++ b/apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.14 on 2022-11-29 13:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0018_connectiontoken_endpoint_protocol'), + ] + + operations = [ + migrations.RemoveField( + model_name='connectiontoken', + name='endpoint_protocol', + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 7f4e7f42b..5505f81a3 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -35,9 +35,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") ) connect_method = models.CharField(max_length=32, verbose_name=_("Connect method")) - endpoint_protocol = models.CharField( - choices=Protocol.choices, max_length=16, verbose_name=_("Endpoint protocol") - ) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) date_expired = models.DateTimeField( diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index e851ceadf..16ef7dc1b 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -23,7 +23,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): fields_small = fields_mini + [ 'user', 'asset', 'account_name', 'input_username', 'input_secret', - 'connect_method', 'endpoint_protocol', 'protocol', + 'connect_method', 'protocol', 'actions', 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 40c89ff24..dbfcc0dec 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -204,6 +204,15 @@ class TerminalType(TextChoices): } return protocols + @classmethod + def get_connect_method(cls, name, protocol, os): + methods = cls.get_protocols_connect_methods(os) + protocol_methods = methods.get(protocol, []) + for method in protocol_methods: + if method['value'] == name: + return method + return None + @classmethod def get_protocols_connect_methods(cls, os): methods = defaultdict(list) From 4b61790a923185d1d95e5883b77fb72a5cf3f3e9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 29 Nov 2022 21:50:48 +0800 Subject: [PATCH 421/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20connect=20t?= =?UTF-8?q?oken=20remote=20app=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 67c732519..6293c4627 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -45,7 +45,7 @@ class RDPFileClientProtocolURLMixin: 'app_name': applet.name, 'user_id': str(token.user.id), 'asset_id': str(token.asset.id), - 'token_id': token.id + 'token_id': str(token.id) } app = '||tinker' From 0f1e19ba41490d6dbab1da4353ea76ba9435c46b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 30 Nov 2022 11:28:51 +0800 Subject: [PATCH 422/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=B7=BB=E5=8A=A0=20ssl=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/mixin.py | 19 ++-------- .../migrations/0115_auto_20221130_1118.py | 38 +++++++++++++++++++ apps/assets/models/asset/database.py | 10 +++++ 3 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 apps/assets/migrations/0115_auto_20221130_1118.py diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index f7f788e72..59be2b5f5 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -1,10 +1,10 @@ from typing import List + from rest_framework.request import Request -from common.utils import lazyproperty, timeit -from assets.models import Node, Asset -from assets.pagination import NodeAssetTreePagination +from assets.models import Node from assets.utils import get_node_from_request, is_query_node_all_assets +from common.utils import lazyproperty, timeit class SerializeToTreeNodeMixin: @@ -38,14 +38,6 @@ class SerializeToTreeNodeMixin: ] return data - def get_platform(self, asset: Asset): - default = 'file' - icon = {'windows', 'linux'} - platform = asset.platform.type.lower() - if platform in icon: - return platform - return default - @timeit def serialize_assets(self, assets, node_key=None): if node_key is None: @@ -61,13 +53,11 @@ class SerializeToTreeNodeMixin: 'pId': get_pid(asset), 'isParent': False, 'open': False, - 'iconSkin': self.get_platform(asset), + 'iconSkin': asset.type, 'chkDisabled': not asset.is_active, 'meta': { 'type': 'asset', 'data': { - 'id': asset.id, - 'name': asset.name, 'org_name': asset.org_name }, } @@ -78,7 +68,6 @@ class SerializeToTreeNodeMixin: class NodeFilterMixin: - # pagination_class = NodeAssetTreePagination request: Request @lazyproperty diff --git a/apps/assets/migrations/0115_auto_20221130_1118.py b/apps/assets/migrations/0115_auto_20221130_1118.py new file mode 100644 index 000000000..3d5fc9b20 --- /dev/null +++ b/apps/assets/migrations/0115_auto_20221130_1118.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.14 on 2022-11-30 03:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0114_node_domain'), + ] + + operations = [ + migrations.AddField( + model_name='database', + name='allow_invalid_cert', + field=models.BooleanField(default=False, verbose_name='Allow invalid cert'), + ), + migrations.AddField( + model_name='database', + name='ca_cert', + field=models.TextField(blank=True, verbose_name='CA cert'), + ), + migrations.AddField( + model_name='database', + name='client_cert', + field=models.TextField(blank=True, verbose_name='Client cert'), + ), + migrations.AddField( + model_name='database', + name='client_key', + field=models.TextField(blank=True, verbose_name='Client key'), + ), + migrations.AddField( + model_name='database', + name='use_ssl', + field=models.BooleanField(default=False, verbose_name='Use SSL'), + ), + ] diff --git a/apps/assets/models/asset/database.py b/apps/assets/models/asset/database.py index 6aef15a8f..4772a6b08 100644 --- a/apps/assets/models/asset/database.py +++ b/apps/assets/models/asset/database.py @@ -6,6 +6,11 @@ from .common import Asset class Database(Asset): db_name = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True) + use_ssl = models.BooleanField(default=False, verbose_name=_("Use SSL")) + ca_cert = models.TextField(verbose_name=_("CA cert"), blank=True) + client_cert = models.TextField(verbose_name=_("Client cert"), blank=True) + client_key = models.TextField(verbose_name=_("Client key"), blank=True) + allow_invalid_cert = models.BooleanField(default=False, verbose_name=_('Allow invalid cert')) def __str__(self): return '{}({}://{}/{})'.format(self.name, self.type, self.address, self.db_name) @@ -18,6 +23,11 @@ class Database(Asset): def specific(self): return { 'db_name': self.db_name, + 'use_ssl': self.use_ssl, + 'ca_cert': self.ca_cert, + 'client_cert': self.client_cert, + 'client_key': self.client_key, + 'allow_invalid_cert': self.allow_invalid_cert, } class Meta: From 99e126f5157b5187393ad08ae5affbeed0bb194b Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 30 Nov 2022 15:08:55 +0800 Subject: [PATCH 423/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20Acl=20acco?= =?UTF-8?q?unts=20serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/migrations/0004_auto_20220831_1658.py | 2 +- apps/acls/models/login_asset_acl.py | 2 +- apps/acls/serializers/login_asset_acl.py | 17 +++++------------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/apps/acls/migrations/0004_auto_20220831_1658.py b/apps/acls/migrations/0004_auto_20220831_1658.py index e4392992b..6fd1ef86b 100644 --- a/apps/acls/migrations/0004_auto_20220831_1658.py +++ b/apps/acls/migrations/0004_auto_20220831_1658.py @@ -22,7 +22,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='loginassetacl', name='accounts', - field=models.JSONField(default=dict, verbose_name='Account'), + field=models.JSONField(verbose_name='Account'), ), migrations.RunPython(migrate_system_users_to_accounts), migrations.RemoveField( diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index b01e4aed1..1c7455fb2 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -18,7 +18,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): # 条件 users = models.JSONField(verbose_name=_('User')) - accounts = models.JSONField(verbose_name=_('Account'), default=dict) + accounts = models.JSONField(verbose_name=_('Account')) assets = models.JSONField(verbose_name=_('Asset')) # 动作 action = models.CharField( diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index 84bab6cc3..2a04b6c97 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -28,30 +28,25 @@ class LoginAssetACLAssestsSerializer(serializers.Serializer): ip_group_help_text = _( "Format for comma-delimited string, with * indicating a match all. " "Such as: " - "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 " - "(Domain name support)" + "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" + " (Domain name support)" ) ip_group = serializers.ListField( default=["*"], child=serializers.CharField(max_length=1024), - label=_("IP"), + label=_("IP/Host"), help_text=ip_group_help_text, ) hostname_group = serializers.ListField( default=["*"], child=serializers.CharField(max_length=128), - label=_("Hostname"), + label=_("Name"), help_text=common_help_text, ) class LoginAssetACLAccountsSerializer(serializers.Serializer): - protocol_group_help_text = _( - "Format for comma-delimited string, with * indicating a match all. " - "Protocol options: {}" - ) - name_group = serializers.ListField( default=["*"], child=serializers.CharField(max_length=128), @@ -70,9 +65,7 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): users = LoginAssetACLUsersSerializer() assets = LoginAssetACLAssestsSerializer() accounts = LoginAssetACLAccountsSerializer() - reviewers_amount = serializers.IntegerField( - read_only=True, source="reviewers.count" - ) + reviewers_amount = serializers.IntegerField(read_only=True, source="reviewers.count") action = LabeledChoiceField( choices=models.LoginAssetACL.ActionChoices.choices, label=_("Action") ) From d46f321f1a8789af175adcbe3db30fac5413439d Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 30 Nov 2022 15:24:32 +0800 Subject: [PATCH 424/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/mixin.py | 8 ++++-- apps/terminal/const.py | 56 ++++++++++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index 59be2b5f5..2abe967b0 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -2,7 +2,7 @@ from typing import List from rest_framework.request import Request -from assets.models import Node +from assets.models import Node, PlatformProtocol from assets.utils import get_node_from_request, is_query_node_all_assets from common.utils import lazyproperty, timeit @@ -40,6 +40,9 @@ class SerializeToTreeNodeMixin: @timeit def serialize_assets(self, assets, node_key=None): + sftp_enabled_platform = PlatformProtocol.objects \ + .filter(name='ssh', setting__sftp_enabled=True) \ + .values_list('platform', flat=True).distinct() if node_key is None: get_pid = lambda asset: getattr(asset, 'parent_key', '') else: @@ -58,7 +61,8 @@ class SerializeToTreeNodeMixin: 'meta': { 'type': 'asset', 'data': { - 'org_name': asset.org_name + 'org_name': asset.org_name, + 'sftp': asset.platform_id in sftp_enabled_platform, }, } } diff --git a/apps/terminal/const.py b/apps/terminal/const.py index dbfcc0dec..5177f1372 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -44,9 +44,30 @@ class ComponentLoad(TextChoices): return set(dict(cls.choices).keys()) -class HttpMethod(TextChoices): +class WebMethod(TextChoices): web_gui = 'web_gui', 'Web GUI' web_cli = 'web_cli', 'Web CLI' + web_sftp = 'web_sftp', 'Web SFTP' + + @classmethod + def get_methods(cls): + return { + Protocol.ssh: [cls.web_cli, cls.web_sftp], + Protocol.telnet: [cls.web_cli], + Protocol.rdp: [cls.web_gui], + Protocol.vnc: [cls.web_gui], + + Protocol.mysql: [cls.web_cli, cls.web_gui], + Protocol.mariadb: [cls.web_cli, cls.web_gui], + Protocol.oracle: [cls.web_cli, cls.web_gui], + Protocol.postgresql: [cls.web_cli, cls.web_gui], + Protocol.sqlserver: [cls.web_cli, cls.web_gui], + Protocol.redis: [cls.web_cli], + Protocol.mongodb: [cls.web_cli], + + Protocol.k8s: [cls.web_gui], + Protocol.http: [] + } class NativeClient(TextChoices): @@ -67,6 +88,8 @@ class NativeClient(TextChoices): @classmethod def get_native_clients(cls): + # native client 关注的是 endpoint 的 protocol, + # 比如 telnet mysql, koko 都支持,到那时暴露的是 ssh 协议 clients = { Protocol.ssh: { 'default': [cls.ssh], @@ -162,7 +185,7 @@ class TerminalType(TextChoices): def protocols(cls): protocols = { cls.koko: { - 'web_method': HttpMethod.web_cli, + 'web_methods': [WebMethod.web_cli, WebMethod.web_sftp], 'listen': [Protocol.ssh, Protocol.http], 'support': [ Protocol.ssh, Protocol.telnet, @@ -174,7 +197,7 @@ class TerminalType(TextChoices): 'match': 'm2m' }, cls.omnidb: { - 'web_method': HttpMethod.web_gui, + 'web_methods': [WebMethod.web_gui], 'listen': [Protocol.http], 'support': [ Protocol.mysql, Protocol.postgresql, Protocol.oracle, @@ -183,7 +206,7 @@ class TerminalType(TextChoices): 'match': 'm2m' }, cls.lion: { - 'web_method': HttpMethod.web_gui, + 'web_methods': [WebMethod.web_gui], 'listen': [Protocol.http], 'support': [Protocol.rdp, Protocol.vnc], 'match': 'm2m' @@ -216,6 +239,7 @@ class TerminalType(TextChoices): @classmethod def get_protocols_connect_methods(cls, os): methods = defaultdict(list) + web_methods = WebMethod.get_methods() native_methods = NativeClient.get_methods(os) applet_methods = AppletMethod.get_methods() @@ -229,16 +253,6 @@ class TerminalType(TextChoices): listen = component_protocol['listen'] for listen_protocol in listen: - if listen_protocol == Protocol.http: - web_protocol = component_protocol['web_method'] - methods[protocol.value].append({ - 'value': web_protocol.value, - 'label': web_protocol.label, - 'endpoint_protocol': 'http', - 'type': 'web', - 'component': component.value, - }) - # Native method methods[protocol.value].extend([ { @@ -250,6 +264,20 @@ class TerminalType(TextChoices): for method in native_methods[listen_protocol] ]) + protocol_web_methods = set(web_methods.get(protocol, [])) \ + & set(component_protocol.get('web_methods', [])) + print("protocol_web_methods", protocol, protocol_web_methods) + methods[protocol.value].extend([ + { + 'component': component.value, + 'type': 'web', + 'endpoint_protocol': 'http', + 'value': method.value, + 'label': method.label, + } + for method in protocol_web_methods + ]) + for protocol, applet_methods in applet_methods.items(): for method in applet_methods: method['type'] = 'applet' From 307cf97ccb59b8d7b69be4811d976a617a13c299 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 30 Nov 2022 15:39:27 +0800 Subject: [PATCH 425/488] =?UTF-8?q?perf:=20=E6=8E=88=E6=9D=83=E7=9A=84?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E6=94=AF=E6=8C=81=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 3 ++- apps/perms/api/user_permission/assets/mixin.py | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index f6cc509b3..04da13061 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -6,8 +6,8 @@ from rest_framework.decorators import action from rest_framework.response import Response from assets import serializers -from assets.models import Asset from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend +from assets.models import Asset from assets.tasks import ( push_accounts_to_assets, test_assets_connectivity_manual, update_assets_hardware_info_manual, verify_accounts_connectivity, @@ -24,6 +24,7 @@ __all__ = [ "AssetViewSet", "AssetTaskCreateApi", "AssetsTaskCreateApi", + 'AssetFilterSet' ] diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index e7a584ef4..e95ba7aa1 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -1,13 +1,14 @@ -from rest_framework.response import Response from rest_framework.request import Request +from rest_framework.response import Response -from common.utils import get_logger -from users.models import User +from assets.api.asset.asset import AssetFilterSet from assets.api.mixin import SerializeToTreeNodeMixin from assets.models import Asset, Node -from perms.pagination import NodeGrantedAssetPagination, AllGrantedAssetPagination +from common.utils import get_logger from perms import serializers +from perms.pagination import NodeGrantedAssetPagination, AllGrantedAssetPagination from perms.utils.user_permission import UserGrantedAssetsQueryUtils +from users.models import User logger = get_logger(__name__) @@ -32,7 +33,8 @@ class UserAllGrantedAssetsQuerysetMixin: only_fields = serializers.AssetGrantedSerializer.Meta.only_fields pagination_class = AllGrantedAssetPagination ordering_fields = ("name", "address") - ordering = ('name', ) + filterset_class = AssetFilterSet + ordering = ('name',) user: User From 48d29494046c9f8fa698e418d61bfa5c802aad6c Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 30 Nov 2022 16:21:27 +0800 Subject: [PATCH 426/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20acl=20asse?= =?UTF-8?q?ts=20name/address?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/login_asset_acl.py | 11 +++++---- apps/acls/serializers/login_asset_acl.py | 26 +++++++++------------- apps/acls/serializers/login_asset_check.py | 4 +--- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 1c7455fb2..a27a730d0 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -61,19 +61,18 @@ class LoginAssetACL(BaseACL, OrgModelMixin): @classmethod def filter_asset(cls, asset, queryset): queryset = queryset.filter( - Q(assets__hostname_group__contains=asset.name) | - Q(assets__hostname_group__contains='*') + Q(assets__name_group__contains=asset.name) | + Q(assets__name_group__contains='*') ) - ids = [q.id for q in queryset if contains_ip(asset.address, q.assets.get('ip_group', []))] + ids = [ + q.id for q in queryset if contains_ip(asset.address, q.assets.get('address_group', [])) + ] queryset = cls.objects.filter(id__in=ids) return queryset @classmethod def filter_account(cls, account_username, queryset): queryset = queryset.filter( - Q(accounts__name_group__contains=account_username) | - Q(accounts__name_group__contains='*') - ).filter( Q(accounts__username_group__contains=account_username) | Q(accounts__username_group__contains='*') ) diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index 2a04b6c97..053771fb4 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -25,34 +25,28 @@ class LoginAssetACLUsersSerializer(serializers.Serializer): class LoginAssetACLAssestsSerializer(serializers.Serializer): - ip_group_help_text = _( + address_group_help_text = _( "Format for comma-delimited string, with * indicating a match all. " "Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" " (Domain name support)" ) - ip_group = serializers.ListField( - default=["*"], - child=serializers.CharField(max_length=1024), - label=_("IP/Host"), - help_text=ip_group_help_text, - ) - hostname_group = serializers.ListField( - default=["*"], - child=serializers.CharField(max_length=128), - label=_("Name"), - help_text=common_help_text, - ) - - -class LoginAssetACLAccountsSerializer(serializers.Serializer): name_group = serializers.ListField( default=["*"], child=serializers.CharField(max_length=128), label=_("Name"), help_text=common_help_text, ) + address_group = serializers.ListField( + default=["*"], + child=serializers.CharField(max_length=1024), + label=_("IP/Host"), + help_text=address_group_help_text, + ) + + +class LoginAssetACLAccountsSerializer(serializers.Serializer): username_group = serializers.ListField( default=["*"], child=serializers.CharField(max_length=128), diff --git a/apps/acls/serializers/login_asset_check.py b/apps/acls/serializers/login_asset_check.py index 279feb3b6..49afda63a 100644 --- a/apps/acls/serializers/login_asset_check.py +++ b/apps/acls/serializers/login_asset_check.py @@ -37,9 +37,7 @@ class LoginAssetCheckSerializer(serializers.Serializer): def validate_account_username(self, account_username): asset_id = self.initial_data.get('asset_id') - account = Account.objects.filter( - username=account_username, asset_id=asset_id - ).first() + account = Account.objects.filter(username=account_username, asset_id=asset_id).first() if not account: error = 'Account username does not exist' raise serializers.ValidationError(error) From dcbdb0af4d11c6f0ed597c6e2b05205bc9af6a7e Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Wed, 30 Nov 2022 16:24:17 +0800 Subject: [PATCH 427/488] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=9B=AE=E5=BD=95=E5=88=87=E6=8D=A2,=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=AD=97=E6=AE=B5=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/job.py | 10 +++++++++- apps/ops/serializers/adhoc.py | 6 ++---- apps/ops/serializers/playbook.py | 5 +++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index e74258b55..5ce5f38c8 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -113,6 +113,13 @@ class JobExecution(JMSOrgBaseModel): def job_type(self): return self.job.type + def compile_shell(self): + if self.job.type != 'adhoc': + return + result = "{}{}{} ".format('\'', self.job.args, '\'') + result += "chdir={}".format(self.job.chdir) + return result + def get_runner(self): inv = self.job.inventory inv.write_to_file(self.inventory_path) @@ -122,8 +129,9 @@ class JobExecution(JMSOrgBaseModel): extra_vars = {} if self.job.type == 'adhoc': + args = self.compile_shell() runner = AdHocRunner( - self.inventory_path, self.job.module, module_args=self.job.args, + self.inventory_path, self.job.module, module_args=args, pattern="all", project_dir=self.private_dir, extra_vars=extra_vars, ) elif self.job.type == 'playbook': diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 48ddf6567..08d583be1 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -1,8 +1,6 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -import datetime - from rest_framework import serializers from common.drf.fields import ReadableHiddenField @@ -17,5 +15,5 @@ class AdHocSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerialize class Meta: model = AdHoc - fields = ["id", "name", "module", "row_count", "size", "args", "creator", "comment", "date_created", - "date_updated"] + read_only_field = ["id", "row_count", "size", "creator", "date_created", "date_updated"] + fields = read_only_field + ["id", "name", "module", "args", "comment"] diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py index 57c7f2fe5..4ff43abb4 100644 --- a/apps/ops/serializers/playbook.py +++ b/apps/ops/serializers/playbook.py @@ -24,6 +24,7 @@ class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerial class Meta: model = Playbook - fields = [ - "id", "name", "path", "comment", "date_created", "creator", "date_updated" + read_only_fields = ["id", "date_created", "date_updated"] + fields = read_only_fields + [ + "id", "name", "comment", "creator", ] From ed77d05bd8eb5ba55cfdf9141924feb6d78d9ee0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 30 Nov 2022 16:33:22 +0800 Subject: [PATCH 428/488] =?UTF-8?q?pref:=20conneect=20token=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20su=20from?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/models/connection_token.py | 8 +++++--- apps/authentication/serializers/connection_token.py | 13 +++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 5505f81a3..850a6c589 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -128,16 +128,18 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): if self.account_name == '@INPUT' or not account: return { 'name': self.account_name, - 'username': self.username, + 'username': self.input_username, 'secret_type': 'password', - 'secret': self.secret + 'secret': self.input_secret, + 'su_from': None } else: return { 'name': account.name, 'username': account.username, 'secret_type': account.secret_type, - 'secret': account.secret or self.secret + 'secret': account.secret or self.input_secret, + 'su_from': account.su_from, } @lazyproperty diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 16ef7dc1b..dec6676eb 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -93,13 +93,22 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer): 'org_id', 'specific'] -class ConnectionTokenAccountSerializer(serializers.ModelSerializer): +class SimpleAccountSerializer(serializers.ModelSerializer): """ Account """ + class Meta: + model = Account + fields = ['name', 'username', 'secret_type', 'secret'] + + +class ConnectionTokenAccountSerializer(serializers.ModelSerializer): + """ Account """ + su_from = SimpleAccountSerializer(required=False, label=_('Su from')) + class Meta: model = Account fields = [ - 'name', 'username', 'secret_type', 'secret', + 'name', 'username', 'secret_type', 'secret', 'su_from', ] From 0ffea3855b604f71dcda30c6ae40ec157286fcef Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 30 Nov 2022 16:39:27 +0800 Subject: [PATCH 429/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/serializers/connection_token.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index dec6676eb..225b8c8db 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -148,9 +148,9 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): expire_now = serializers.BooleanField(label=_('Expired now'), default=True) user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) - platform = ConnectionTokenPlatform(read_only=True) account = ConnectionTokenAccountSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) + platform = ConnectionTokenPlatform(read_only=True) # cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() @@ -158,7 +158,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): class Meta: model = ConnectionToken fields = [ - 'id', 'value', 'user', 'asset', 'platform', 'account', + 'id', 'value', 'user', 'asset', 'account', 'platform', 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', ] extra_kwargs = { From 6bb706efcf59e85e667c3ff79246c187b8eaffdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Tue, 29 Nov 2022 11:32:26 +0800 Subject: [PATCH 430/488] =?UTF-8?q?perf:=20=E6=B7=BB=E5=8A=A0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=9E=84=E5=BB=BA=E6=B5=8B=E8=AF=95=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/jms-build-test.yml | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/jms-build-test.yml diff --git a/.github/workflows/jms-build-test.yml b/.github/workflows/jms-build-test.yml new file mode 100644 index 000000000..0f5309cac --- /dev/null +++ b/.github/workflows/jms-build-test.yml @@ -0,0 +1,32 @@ +name: "Run Build Test" +on: + push: + branches: + - pr@* + - repr@* + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: docker/setup-qemu-action@v2 + + - uses: docker/setup-buildx-action@v2 + + - uses: docker/build-push-action@v3 + with: + context: . + push: false + tags: jumpserver/core:test + file: Dockerfile + cache-from: type=gha + cache-to: type=gha,mode=max + + - uses: LouisBrunner/checks-action@v1.5.0 + if: always() + with: + token: ${{ secrets.GITHUB_TOKEN }} + name: Check Build + conclusion: ${{ job.status }} From 2dea891b1524ce91add4786b8756cf1be733090a Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 30 Nov 2022 17:11:36 +0800 Subject: [PATCH 431/488] =?UTF-8?q?perf:=20=E5=A4=84=E7=90=86=20acl=20?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=20check=20=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/login_asset_check.py | 46 +++++++++++----------- apps/acls/serializers/login_asset_check.py | 30 ++++---------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index 331c42768..662befafd 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -20,34 +20,41 @@ class LoginAssetCheckAPI(CreateAPIView): return LoginAssetACL.objects.all() def create(self, request, *args, **kwargs): - is_need_confirm, response_data = self.check_if_need_confirm() - return Response(data=response_data, status=200) + data = self.check_confirm() + return Response(data=data, status=200) - def check_if_need_confirm(self): + @lazyproperty + def serializer(self): + serializer = self.get_serializer(data=self.request.data) + serializer.is_valid(raise_exception=True) + return serializer + + def check_confirm(self): queries = { - 'user': self.serializer.user, 'asset': self.serializer.asset, - 'account_username': self.serializer.username, + 'user': self.serializer.user, + 'asset': self.serializer.asset, + 'account_username': self.serializer.account_username, 'action': LoginAssetACL.ActionChoices.login_confirm } - with tmp_to_org(self.serializer.org): + with tmp_to_org(self.serializer.asset.org): acl = LoginAssetACL.filter(**queries).valid().first() - if not acl: - is_need_confirm = False - response_data = {} - else: - is_need_confirm = True + if acl: + need_confirm = True response_data = self._get_response_data_of_need_confirm(acl) - response_data['need_confirm'] = is_need_confirm - return is_need_confirm, response_data + else: + need_confirm = False + response_data = {} + response_data['need_confirm'] = need_confirm + return response_data - def _get_response_data_of_need_confirm(self, acl): + def _get_response_data_of_need_confirm(self, acl) -> dict: ticket = LoginAssetACL.create_login_asset_confirm_ticket( user=self.serializer.user, asset=self.serializer.asset, - account_username=self.serializer.username, + account_username=self.serializer.account_username, assignees=acl.reviewers.all(), - org_id=self.serializer.org.id, + org_id=self.serializer.asset.org.id, ) confirm_status_url = reverse( view_name='api-tickets:super-ticket-status', @@ -68,10 +75,3 @@ class LoginAssetCheckAPI(CreateAPIView): 'ticket_id': str(ticket.id) } return data - - @lazyproperty - def serializer(self): - serializer = self.get_serializer(data=self.request.data) - serializer.is_valid(raise_exception=True) - return serializer - diff --git a/apps/acls/serializers/login_asset_check.py b/apps/acls/serializers/login_asset_check.py index 49afda63a..eac7481d5 100644 --- a/apps/acls/serializers/login_asset_check.py +++ b/apps/acls/serializers/login_asset_check.py @@ -16,34 +16,20 @@ class LoginAssetCheckSerializer(serializers.Serializer): super().__init__(*args, **kwargs) self.user = None self.asset = None - self.username = None def validate_user_id(self, user_id): - self.user = self.validate_object_exist(User, user_id) + self.user = self.get_object(User, user_id) return user_id def validate_asset_id(self, asset_id): - self.asset = self.validate_object_exist(Asset, asset_id) + self.asset = self.get_object(Asset, asset_id) return asset_id @staticmethod - def validate_object_exist(model, field_id): + def get_object(model, pk): with tmp_to_root_org(): - obj = get_object_or_none(model, pk=field_id) - if not obj: - error = '{} Model object does not exist'.format(model.__name__) - raise serializers.ValidationError(error) - return obj - - def validate_account_username(self, account_username): - asset_id = self.initial_data.get('asset_id') - account = Account.objects.filter(username=account_username, asset_id=asset_id).first() - if not account: - error = 'Account username does not exist' - raise serializers.ValidationError(error) - self.username = account_username - return account_username - - @lazyproperty - def org(self): - return self.asset.org + obj = get_object_or_none(model, pk=pk) + if obj: + return obj + error = '{} Model object does not exist'.format(model.__name__) + raise serializers.ValidationError(error) From 4083df07cc12379963161e7f71a313d7dfa44546 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 30 Nov 2022 18:13:15 +0800 Subject: [PATCH 432/488] perf: audit navigation (#9133) Co-authored-by: feng <1304903146@qq.com> --- apps/jumpserver/api.py | 203 +++++++++++++++++++++++++++++++++++++---- apps/orgs/caches.py | 13 --- 2 files changed, 185 insertions(+), 31 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 48b767299..aa1c85c04 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -13,48 +13,121 @@ from rest_framework.response import Response from users.models import User from assets.models import Asset from assets.const import AllTypes -from terminal.models import Session +from terminal.models import Session, Command from terminal.utils import ComponentsPrometheusMetricsUtil from orgs.utils import current_org from common.utils import lazyproperty +from audits.models import UserLoginLog, PasswordChangeLog, OperateLog +from audits.const import LoginStatusChoices from common.utils.timezone import local_now, local_zero_hour from orgs.caches import OrgResourceStatisticsCache __all__ = ['IndexApi'] -class DatesLoginMetricMixin: +class DateTimeMixin: request: Request + @property + def org(self): + return current_org + @lazyproperty def days(self): query_params = self.request.query_params - # monthly count = query_params.get('days') count = int(count) if count else 0 return count - @lazyproperty - def sessions_queryset(self): + @property + def days_to_datetime(self): days = self.days if days == 0: t = local_zero_hour() else: t = local_now() - timezone.timedelta(days=days) - sessions_queryset = Session.objects.filter(date_start__gte=t) - return sessions_queryset + return t @lazyproperty - def session_dates_list(self): + def dates_list(self): now = local_now() dates = [(now - timezone.timedelta(days=i)).date() for i in range(self.days)] dates.reverse() return dates def get_dates_metrics_date(self): - dates_metrics_date = [d.strftime('%m-%d') for d in self.session_dates_list] or ['0'] + dates_metrics_date = [d.strftime('%m-%d') for d in self.dates_list] or ['0'] return dates_metrics_date + @lazyproperty + def users(self): + return self.org.get_members() + + @lazyproperty + def sessions_queryset(self): + t = self.days_to_datetime + sessions_queryset = Session.objects.filter(date_start__gte=t) + return sessions_queryset + + def get_logs_queryset(self, queryset, query_params): + query = {} + if not self.org.is_root(): + if query_params == 'username': + query = { + f'{query_params}__in': self.users.values_list('username', flat=True) + } + else: + query = { + f'{query_params}__in': [str(user) for user in self.users] + } + queryset = queryset.filter(**query) + return queryset + + @lazyproperty + def login_logs_queryset(self): + t = self.days_to_datetime + queryset = UserLoginLog.objects.filter(datetime__gte=t) + queryset = self.get_logs_queryset(queryset, 'username') + return queryset + + @lazyproperty + def password_change_logs_queryset(self): + t = self.days_to_datetime + queryset = PasswordChangeLog.objects.filter(datetime__gte=t) + queryset = self.get_logs_queryset(queryset, 'user') + return queryset + + @lazyproperty + def operate_logs_queryset(self): + t = self.days_to_datetime + queryset = OperateLog.objects.filter(datetime__gte=t) + queryset = self.get_logs_queryset(queryset, 'user') + return queryset + + @lazyproperty + def ftp_logs_queryset(self): + t = self.days_to_datetime + queryset = OperateLog.objects.filter(datetime__gte=t) + queryset = self.get_logs_queryset(queryset, 'user') + return queryset + + @lazyproperty + def command_queryset(self): + t = self.days_to_datetime + t = t.timestamp() + queryset = Command.objects.filter(timestamp__gte=t) + return queryset + + +class DatesLoginMetricMixin: + dates_list: list + command_queryset: Command.objects + sessions_queryset: Session.objects + ftp_logs_queryset: OperateLog.objects + login_logs_queryset: UserLoginLog.objects + operate_logs_queryset: OperateLog.objects + password_change_logs_queryset: PasswordChangeLog.objects + @staticmethod def get_cache_key(date, tp): date_str = date.strftime("%Y%m%d") @@ -93,7 +166,7 @@ class DatesLoginMetricMixin: def get_dates_metrics_total_count_login(self): data = [] - for d in self.session_dates_list: + for d in self.dates_list: count = self.get_date_login_count(d) data.append(count) if len(data) == 0: @@ -112,7 +185,7 @@ class DatesLoginMetricMixin: def get_dates_metrics_total_count_active_users(self): data = [] - for d in self.session_dates_list: + for d in self.dates_list: count = self.get_date_user_count(d) data.append(count) return data @@ -129,11 +202,28 @@ class DatesLoginMetricMixin: def get_dates_metrics_total_count_active_assets(self): data = [] - for d in self.session_dates_list: + for d in self.dates_list: count = self.get_date_asset_count(d) data.append(count) return data + def get_date_session_count(self, date): + tp = "SESSION" + count = self.__get_data_from_cache(date, tp) + if count is not None: + return count + ds, de = self.get_date_start_2_end(date) + count = Session.objects.filter(date_start__range=(ds, de)).count() + self.__set_data_to_cache(date, tp, count) + return count + + def get_dates_metrics_total_count_sessions(self): + data = [] + for d in self.dates_list: + count = self.get_date_session_count(d) + data.append(count) + return data + @lazyproperty def get_type_to_assets(self): result = Asset.objects.annotate(type=F('platform__type')). \ @@ -181,8 +271,44 @@ class DatesLoginMetricMixin: ] return sessions + @lazyproperty + def user_login_logs_amount(self): + return self.login_logs_queryset.count() -class IndexApi(DatesLoginMetricMixin, APIView): + @lazyproperty + def user_login_success_logs_amount(self): + return self.login_logs_queryset.filter(status=LoginStatusChoices.success).count() + + @lazyproperty + def user_login_amount(self): + return self.login_logs_queryset.values('username').distinct().count() + + @lazyproperty + def operate_logs_amount(self): + return self.operate_logs_queryset.count() + + @lazyproperty + def change_password_logs_amount(self): + return self.password_change_logs_queryset.count() + + @lazyproperty + def commands_amount(self): + return self.command_queryset.count() + + @lazyproperty + def commands_danger_amount(self): + return self.command_queryset.filter(risk_level=Command.RISK_LEVEL_DANGEROUS).count() + + @lazyproperty + def sessions_amount(self): + return self.sessions_queryset.count() + + @lazyproperty + def ftp_logs_amount(self): + return self.ftp_logs_queryset.count() + + +class IndexApi(DateTimeMixin, DatesLoginMetricMixin, APIView): http_method_names = ['get'] def check_permissions(self, request): @@ -193,7 +319,7 @@ class IndexApi(DatesLoginMetricMixin, APIView): query_params = self.request.query_params - caches = OrgResourceStatisticsCache(current_org) + caches = OrgResourceStatisticsCache(self.org) _all = query_params.get('all') @@ -217,9 +343,9 @@ class IndexApi(DatesLoginMetricMixin, APIView): 'total_count_assets_this_week': caches.new_assets_amount_this_week, }) - if _all or query_params.get('total_count') or query_params.get('total_count_today_login_users'): + if _all or query_params.get('total_count') or query_params.get('total_count_login_users'): data.update({ - 'total_count_today_login_users': caches.total_count_today_login_users, + 'total_count_login_users': self.user_login_amount }) if _all or query_params.get('total_count') or query_params.get('total_count_today_active_assets'): @@ -242,9 +368,50 @@ class IndexApi(DatesLoginMetricMixin, APIView): 'total_count_today_failed_sessions': caches.total_count_today_failed_sessions, }) - if _all or query_params.get('total_count') or query_params.get('total_count_type_to_assets_amount'): + if _all or query_params.get('total_count') or query_params.get('total_count_user_login_logs'): data.update({ - 'total_count_type_to_assets_amount': self.get_type_to_assets, + 'total_count_user_login_logs': self.user_login_logs_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_user_login_success_logs'): + data.update({ + 'total_count_user_login_success_logs': self.user_login_success_logs_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_operate_logs'): + data.update({ + 'total_count_operate_logs': self.operate_logs_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_change_password_logs'): + data.update({ + 'total_count_change_password_logs': self.change_password_logs_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_commands'): + data.update({ + 'total_count_commands': self.commands_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_commands_danger'): + data.update({ + 'total_count_commands_danger': self.commands_danger_amount, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_history_sessions'): + data.update({ + 'total_count_history_sessions': self.sessions_amount - caches.total_count_online_sessions, + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_ftp_logs'): + data.update({ + 'total_count_ftp_logs': self.ftp_logs_amount, + }) + + if _all or query_params.get('session_dates_metrics'): + data.update({ + 'dates_metrics_date': self.get_dates_metrics_date(), + 'dates_metrics_total_count_session': self.get_dates_metrics_total_count_sessions(), }) if _all or query_params.get('dates_metrics'): diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index 0a665c130..cd67984a3 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -7,8 +7,6 @@ from common.cache import Cache, IntegerField from common.utils import get_logger from common.utils.timezone import local_zero_hour, local_monday from users.models import UserGroup, User -from audits.models import UserLoginLog -from audits.const import LoginStatusChoices from assets.models import Node, Domain, Asset, Account from terminal.models import Session from perms.models import AssetPermission @@ -64,7 +62,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache): asset_perms_amount = IntegerField(queryset=AssetPermission.objects) total_count_online_users = IntegerField() total_count_online_sessions = IntegerField() - total_count_today_login_users = IntegerField() total_count_today_active_assets = IntegerField() total_count_today_failed_sessions = IntegerField() @@ -113,16 +110,6 @@ class OrgResourceStatisticsCache(OrgRelatedCache): def compute_total_count_online_sessions(): return Session.objects.filter(is_finished=False).count() - def compute_total_count_today_login_users(self): - t = local_zero_hour() - user_login_logs = UserLoginLog.objects.filter( - datetime__gte=t, status=LoginStatusChoices.success - ) - if not self.org.is_root(): - usernames = self.org.get_members().values('username') - user_login_logs = user_login_logs.filter(username__in=usernames) - return user_login_logs.values('username').distinct().count() - @staticmethod def compute_total_count_today_active_assets(): t = local_zero_hour() From 142348b05527b97dbe45233bff8166bba621f925 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 30 Nov 2022 17:52:00 +0800 Subject: [PATCH 433/488] =?UTF-8?q?perf:=20=E5=A2=9E=E5=8A=A0=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/assets/mixin.py | 3 +++ apps/perms/serializers/user_permission.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index e95ba7aa1..09abdf686 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -88,6 +88,9 @@ class AssetsSerializerFormatMixin: serializer_class = serializers.AssetGrantedSerializer filterset_fields = ['name', 'address', 'id', 'comment'] search_fields = ['name', 'address', 'comment'] + filterset_class = AssetFilterSet + ordering_fields = ("name", "address") + ordering = ('name',) class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 3b60d25bb..9dcb04ae7 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -5,8 +5,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from assets.const import Category, AllTypes -from assets.serializers.asset.common import AssetProtocolsSerializer from assets.models import Node, Asset, Platform, Account +from assets.serializers.asset.common import AssetProtocolsSerializer from common.drf.fields import ObjectRelatedField, LabeledChoiceField from perms.serializers.permission import ActionChoicesField @@ -48,5 +48,6 @@ class AccountsPermedSerializer(serializers.ModelSerializer): class Meta: model = Account - fields = ['id', 'name', 'has_username', 'username', 'has_secret', 'secret_type', 'actions'] + fields = ['id', 'name', 'has_username', 'username', + 'has_secret', 'secret_type', 'actions'] read_only_fields = fields From edae6942ac8e382c864fee244e057f502eb60fe2 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 30 Nov 2022 20:02:13 +0800 Subject: [PATCH 434/488] perf: connection token client add protocol (#9134) Co-authored-by: feng <1304903146@qq.com> --- apps/authentication/api/connection_token.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 6293c4627..e9df9d33b 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -154,6 +154,7 @@ class RDPFileClientProtocolURLMixin: data = { 'id': str(token.id), 'value': token.value, + 'protocol': token.protocol, 'command': '', 'file': {} } From 314b63cec8ef76b20f7caf80aa2a17b4d98d7d88 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 30 Nov 2022 20:21:23 +0800 Subject: [PATCH 435/488] perf: connection token launch command --- apps/terminal/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 5177f1372..5ae73ca0f 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -133,8 +133,8 @@ class NativeClient(TextChoices): def get_launch_command(cls, name, token, endpoint, os='windows'): commands = { cls.ssh: f'ssh {token.id}@{endpoint.host} -p {endpoint.ssh_port}', - cls.putty: f'putty -ssh {token.id}@{endpoint.host} -P {endpoint.ssh_port}', - cls.xshell: f'xshell -url ssh://{token.id}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', + cls.putty: f'putty.exe -ssh {token.id}@{endpoint.host} -P {endpoint.ssh_port}', + cls.xshell: f'xshell.exe -url ssh://{token.id}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', # cls.mysql: 'mysql -h {hostname} -P {port} -u {username} -p', # cls.psql: { # 'default': 'psql -h {hostname} -p {port} -U {username} -W', From a0df39ad28afa9cb69da8f702c79caff925f71bb Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 30 Nov 2022 20:38:22 +0800 Subject: [PATCH 436/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9connection=20t?= =?UTF-8?q?oken=20=E5=AF=B9=E6=8E=A5client=E7=9A=84username?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/const.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 5ae73ca0f..6d7a14f9d 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -131,10 +131,11 @@ class NativeClient(TextChoices): @classmethod def get_launch_command(cls, name, token, endpoint, os='windows'): + username = f'JMS-{token.id}' commands = { - cls.ssh: f'ssh {token.id}@{endpoint.host} -p {endpoint.ssh_port}', - cls.putty: f'putty.exe -ssh {token.id}@{endpoint.host} -P {endpoint.ssh_port}', - cls.xshell: f'xshell.exe -url ssh://{token.id}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', + cls.ssh: f'ssh {username}@{endpoint.host} -p {endpoint.ssh_port}', + cls.putty: f'putty.exe -ssh {username}@{endpoint.host} -P {endpoint.ssh_port}', + cls.xshell: f'xshell.exe -url ssh://{username}:{token.value}@{endpoint.host}:{endpoint.ssh_port}', # cls.mysql: 'mysql -h {hostname} -P {port} -u {username} -p', # cls.psql: { # 'default': 'psql -h {hostname} -p {port} -U {username} -W', From 2aa1d664a6d8df659361bb5034aa72ba4c4f2153 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 30 Nov 2022 21:13:50 +0800 Subject: [PATCH 437/488] =?UTF-8?q?perf:=20=E5=A4=84=E7=90=86=20acl=20filt?= =?UTF-8?q?er=20=E9=80=BB=E8=BE=91=E6=94=BE=E5=88=B0=20queryset=20?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/login_asset_check.py | 15 ++++--- apps/acls/models/login_asset_acl.py | 64 ++++++++++++----------------- apps/common/utils/ip/utils.py | 2 +- 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index 662befafd..df7288c4c 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -30,15 +30,14 @@ class LoginAssetCheckAPI(CreateAPIView): return serializer def check_confirm(self): - queries = { - 'user': self.serializer.user, - 'asset': self.serializer.asset, - 'account_username': self.serializer.account_username, - 'action': LoginAssetACL.ActionChoices.login_confirm - } with tmp_to_org(self.serializer.asset.org): - acl = LoginAssetACL.filter(**queries).valid().first() - + acl = LoginAssetACL.objects\ + .filter(action=LoginAssetACL.ActionChoices.login_confirm)\ + .filter_user(self.serializer.user)\ + .filter_asset(self.serializer.asset)\ + .filter_account(self.serializer.account_username)\ + .valid()\ + .first() if acl: need_confirm = True response_data = self._get_response_data_of_need_confirm(acl) diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index a27a730d0..842d41432 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -6,6 +6,32 @@ from .base import BaseACL, BaseACLQuerySet from common.utils.ip import contains_ip +class ACLQuerySet(BaseACLQuerySet): + def filter_user(self, user): + return self.filter( + Q(users__username_group__contains=user.username) | + Q(users__username_group__contains='*') + ) + + def filter_asset(self, asset): + queryset = self.filter( + Q(assets__name_group__contains=asset.name) | + Q(assets__name_group__contains='*') + ) + ids = [ + q.id for q in queryset + if contains_ip(asset.address, q.assets.get('address_group', [])) + ] + queryset = LoginAssetACL.objects.filter(id__in=ids) + return queryset + + def filter_account(self, account_username): + return self.filter( + Q(accounts__username_group__contains=account_username) | + Q(accounts__username_group__contains='*') + ) + + class ACLManager(OrgManager): def valid(self): @@ -32,7 +58,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): verbose_name=_("Reviewers") ) - objects = ACLManager.from_queryset(BaseACLQuerySet)() + objects = ACLManager.from_queryset(ACLQuerySet)() class Meta: unique_together = ('name', 'org_id') @@ -42,42 +68,6 @@ class LoginAssetACL(BaseACL, OrgModelMixin): def __str__(self): return self.name - @classmethod - def filter(cls, user, asset, account_username, action): - queryset = cls.objects.filter(action=action) - queryset = cls.filter_user(user, queryset) - queryset = cls.filter_asset(asset, queryset) - queryset = cls.filter_account(account_username, queryset) - return queryset - - @classmethod - def filter_user(cls, user, queryset): - queryset = queryset.filter( - Q(users__username_group__contains=user.username) | - Q(users__username_group__contains='*') - ) - return queryset - - @classmethod - def filter_asset(cls, asset, queryset): - queryset = queryset.filter( - Q(assets__name_group__contains=asset.name) | - Q(assets__name_group__contains='*') - ) - ids = [ - q.id for q in queryset if contains_ip(asset.address, q.assets.get('address_group', [])) - ] - queryset = cls.objects.filter(id__in=ids) - return queryset - - @classmethod - def filter_account(cls, account_username, queryset): - queryset = queryset.filter( - Q(accounts__username_group__contains=account_username) | - Q(accounts__username_group__contains='*') - ) - return queryset - @classmethod def create_login_asset_confirm_ticket(cls, user, asset, account_username, assignees, org_id): from tickets.const import TicketType diff --git a/apps/common/utils/ip/utils.py b/apps/common/utils/ip/utils.py index 46b4a0e46..6a6fce26b 100644 --- a/apps/common/utils/ip/utils.py +++ b/apps/common/utils/ip/utils.py @@ -66,7 +66,7 @@ def contains_ip(ip, ip_group): if in_ip_segment(ip, _ip): return True else: - # is domain name + # address / host if ip == _ip: return True From a430b0f1a9ec6ee95ba20badded4657c1003416d Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 30 Nov 2022 23:05:20 +0800 Subject: [PATCH 438/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20Login=20As?= =?UTF-8?q?set=20ACL=20Serializer=20reviewers=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/serializers/login_asset_acl.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index 053771fb4..6e3e6bc50 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -1,9 +1,11 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from common.drf.fields import LabeledChoiceField +from common.drf.fields import ObjectRelatedField from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.models import Organization -from common.drf.fields import LabeledChoiceField +from users.models import User from acls import models @@ -59,6 +61,9 @@ class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): users = LoginAssetACLUsersSerializer() assets = LoginAssetACLAssestsSerializer() accounts = LoginAssetACLAccountsSerializer() + reviewers = ObjectRelatedField( + queryset=User.objects, many=True, required=False, label=_('Reviewers') + ) reviewers_amount = serializers.IntegerField(read_only=True, source="reviewers.count") action = LabeledChoiceField( choices=models.LoginAssetACL.ActionChoices.choices, label=_("Action") From d198dfcba9d5b358b6a66b1dfe7acf00e2c88a81 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 1 Dec 2022 00:36:02 +0800 Subject: [PATCH 439/488] perf: granted asset add specific field --- apps/perms/serializers/user_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 9dcb04ae7..a85770a50 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -30,7 +30,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer): 'domain', 'platform', "comment", "org_id", "is_active", ] - fields = only_fields + ['protocols', 'category', 'type'] + ['org_name'] + fields = only_fields + ['protocols', 'category', 'type', 'specific'] + ['org_name'] read_only_fields = fields From 26efc42e8b0874e1a1a0fb6fb77441a05dca0d80 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 1 Dec 2022 10:23:04 +0800 Subject: [PATCH 440/488] perf: type to assets --- apps/jumpserver/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index aa1c85c04..b0b31f4c9 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -408,6 +408,11 @@ class IndexApi(DateTimeMixin, DatesLoginMetricMixin, APIView): 'total_count_ftp_logs': self.ftp_logs_amount, }) + if _all or query_params.get('total_count') or query_params.get('total_count_type_to_assets_amount'): + data.update({ + 'total_count_type_to_assets_amount': self.get_type_to_assets, + }) + if _all or query_params.get('session_dates_metrics'): data.update({ 'dates_metrics_date': self.get_dates_metrics_date(), From 592d79c0f8aab18e944d50374bafea3eba81661f Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 1 Dec 2022 12:02:20 +0800 Subject: [PATCH 441/488] =?UTF-8?q?perf:=20=E5=90=88=E5=B9=B6=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E8=A7=84=E5=88=99=E7=94=A8=E6=88=B7=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=20API=20URL=EF=BC=8C=E7=BB=9F=E4=B8=80=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20//=20=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission/assets/api.py | 79 ++++++++--------- .../perms/api/user_permission/assets/mixin.py | 15 +--- apps/perms/api/user_permission/mixin.py | 23 +---- apps/perms/api/user_permission/nodes.py | 73 ++++++++-------- .../api/user_permission/nodes_with_assets.py | 32 +++---- apps/perms/urls/user_permission.py | 84 ++++++++----------- 6 files changed, 131 insertions(+), 175 deletions(-) diff --git a/apps/perms/api/user_permission/assets/api.py b/apps/perms/api/user_permission/assets/api.py index 0fc3047fd..a08644449 100644 --- a/apps/perms/api/user_permission/assets/api.py +++ b/apps/perms/api/user_permission/assets/api.py @@ -3,58 +3,58 @@ from rest_framework.generics import ListAPIView from common.utils import get_logger from .mixin import ( - UserAllGrantedAssetsQuerysetMixin, UserDirectGrantedAssetsQuerysetMixin, UserFavoriteGrantedAssetsMixin, - UserGrantedNodeAssetsMixin, AssetsSerializerFormatMixin, AssetsTreeFormatMixin, + AssetsTreeFormatMixin, + UserGrantedNodeAssetsMixin, + AssetSerializerFormatMixin, + UserFavoriteGrantedAssetsMixin, + UserAllGrantedAssetsQuerysetMixin, + UserDirectGrantedAssetsQuerysetMixin, ) -from ..mixin import AssetRoleAdminMixin, AssetRoleUserMixin +from ..mixin import SelfOrPKUserMixin, RebuildTreeMixin __all__ = [ - 'UserDirectGrantedAssetsApi', 'MyDirectGrantedAssetsApi', + 'UserDirectGrantedAssetsApi', 'UserFavoriteGrantedAssetsApi', - 'MyFavoriteGrantedAssetsApi', 'UserDirectGrantedAssetsAsTreeApi', - 'MyUngroupAssetsAsTreeApi', - 'UserAllGrantedAssetsApi', 'MyAllGrantedAssetsApi', 'MyAllAssetsAsTreeApi', + 'UserDirectGrantedAssetsAsTreeApi', + 'UserUngroupAssetsAsTreeApi', + 'UserAllGrantedAssetsApi', 'UserGrantedNodeAssetsApi', - 'MyGrantedNodeAssetsApi', ] logger = get_logger(__name__) class UserDirectGrantedAssetsApi( - AssetRoleAdminMixin, UserDirectGrantedAssetsQuerysetMixin, - AssetsSerializerFormatMixin, ListAPIView + SelfOrPKUserMixin, + UserDirectGrantedAssetsQuerysetMixin, + AssetSerializerFormatMixin, + ListAPIView ): """ 直接授权给用户的资产 """ pass -class MyDirectGrantedAssetsApi(AssetRoleUserMixin, UserDirectGrantedAssetsApi): - """ 直接授权给我的资产 """ - pass - - class UserFavoriteGrantedAssetsApi( - AssetRoleAdminMixin, UserFavoriteGrantedAssetsMixin, - AssetsSerializerFormatMixin, ListAPIView + SelfOrPKUserMixin, + UserFavoriteGrantedAssetsMixin, + AssetSerializerFormatMixin, + ListAPIView ): """ 用户收藏的授权资产 """ pass -class MyFavoriteGrantedAssetsApi(AssetRoleUserMixin, UserFavoriteGrantedAssetsApi): - """ 我收藏的授权资产 """ - pass - - -class UserDirectGrantedAssetsAsTreeApi(AssetsTreeFormatMixin, UserDirectGrantedAssetsApi): +class UserDirectGrantedAssetsAsTreeApi( + RebuildTreeMixin, + AssetsTreeFormatMixin, + UserDirectGrantedAssetsApi +): """ 用户直接授权的资产作为树 """ pass -class MyUngroupAssetsAsTreeApi(AssetRoleUserMixin, UserDirectGrantedAssetsAsTreeApi): - """ 我的未分组节点下的资产作为树 """ - +class UserUngroupAssetsAsTreeApi(UserDirectGrantedAssetsAsTreeApi): + """ 用户未分组节点下的资产作为树 """ def get_queryset(self): queryset = super().get_queryset() if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: @@ -63,31 +63,20 @@ class MyUngroupAssetsAsTreeApi(AssetRoleUserMixin, UserDirectGrantedAssetsAsTree class UserAllGrantedAssetsApi( - AssetRoleAdminMixin, UserAllGrantedAssetsQuerysetMixin, - AssetsSerializerFormatMixin, ListAPIView + SelfOrPKUserMixin, + UserAllGrantedAssetsQuerysetMixin, + AssetSerializerFormatMixin, + ListAPIView ): """ 授权给用户的所有资产 """ pass -class MyAllGrantedAssetsApi(AssetRoleUserMixin, UserAllGrantedAssetsApi): - """ 授权给我的所有资产 """ - pass - - -class MyAllAssetsAsTreeApi(AssetsTreeFormatMixin, MyAllGrantedAssetsApi): - """ 授权给我的所有资产作为树 """ - pass - - class UserGrantedNodeAssetsApi( - AssetRoleAdminMixin, UserGrantedNodeAssetsMixin, - AssetsSerializerFormatMixin, ListAPIView + SelfOrPKUserMixin, + UserGrantedNodeAssetsMixin, + AssetSerializerFormatMixin, + ListAPIView ): """ 授权给用户的节点资产 """ pass - - -class MyGrantedNodeAssetsApi(AssetRoleUserMixin, UserGrantedNodeAssetsApi): - """ 授权给我的节点资产 """ - pass diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets/mixin.py index 09abdf686..58832cdee 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets/mixin.py @@ -1,20 +1,18 @@ from rest_framework.request import Request from rest_framework.response import Response +from common.utils import get_logger +from users.models import User from assets.api.asset.asset import AssetFilterSet from assets.api.mixin import SerializeToTreeNodeMixin from assets.models import Asset, Node -from common.utils import get_logger from perms import serializers from perms.pagination import NodeGrantedAssetPagination, AllGrantedAssetPagination from perms.utils.user_permission import UserGrantedAssetsQueryUtils -from users.models import User logger = get_logger(__name__) -# 获取数据的 ------------------------------------------------------------ - class UserDirectGrantedAssetsQuerysetMixin: only_fields = serializers.AssetGrantedSerializer.Meta.only_fields user: User @@ -73,18 +71,13 @@ class UserGrantedNodeAssetsMixin: return Asset.objects.none() node_id = self.kwargs.get("node_id") - node, assets = UserGrantedAssetsQueryUtils(self.user).get_node_all_assets( - node_id - ) + node, assets = UserGrantedAssetsQueryUtils(self.user).get_node_all_assets(node_id) assets = assets.prefetch_related('platform').only(*self.only_fields) self.pagination_node = node return assets -# 控制格式的 ---------------------------------------------------- - - -class AssetsSerializerFormatMixin: +class AssetSerializerFormatMixin: serializer_class = serializers.AssetGrantedSerializer filterset_fields = ['name', 'address', 'id', 'comment'] search_fields = ['name', 'address', 'comment'] diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 9ff8ed0f1..2c145221a 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -7,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _ from common.http import is_true from common.utils import is_uuid from common.exceptions import JMSObjectDoesNotExist -from common.mixins.api import RoleAdminMixin, RoleUserMixin from perms.utils.user_permission import UserGrantedTreeRefreshController from rbac.permissions import RBACPermission from users.models import User @@ -23,24 +22,6 @@ class RebuildTreeMixin: return super().get(request, *args, **kwargs) -class AssetRoleAdminMixin(RebuildTreeMixin, RoleAdminMixin): - rbac_perms = ( - ('list', 'perms.view_userassets'), - ('retrieve', 'perms.view_userassets'), - ('get_tree', 'perms.view_userassets'), - ('GET', 'perms.view_userassets'), - ) - - -class AssetRoleUserMixin(RebuildTreeMixin, RoleUserMixin): - rbac_perms = ( - ('list', 'perms.view_myassets'), - ('retrieve', 'perms.view_myassets'), - ('get_tree', 'perms.view_myassets'), - ('GET', 'perms.view_myassets'), - ) - - class SelfOrPKUserMixin: kwargs: dict request: Request @@ -59,6 +40,7 @@ class SelfOrPKUserMixin: ('retrieve', 'perms.view_myassets'), ('get_tree', 'perms.view_myassets'), ('GET', 'perms.view_myassets'), + ('OPTIONS', 'perms.view_myassets'), ) @property @@ -68,6 +50,7 @@ class SelfOrPKUserMixin: ('retrieve', 'perms.view_userassets'), ('get_tree', 'perms.view_userassets'), ('GET', 'perms.view_userassets'), + ('OPTIONS', 'perms.view_userassets'), ) @property @@ -76,6 +59,8 @@ class SelfOrPKUserMixin: user = self.request.user elif is_uuid(self.kwargs.get('user')): user = get_object_or_404(User, pk=self.kwargs.get('user')) + elif hasattr(self, 'swagger_fake_view'): + user = self.request.user else: raise JMSObjectDoesNotExist(object_name=_('User')) return user diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index 74f0b6728..eb0d321d5 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -1,31 +1,25 @@ # -*- coding: utf-8 -*- # import abc -from rest_framework.generics import ( - ListAPIView -) -from rest_framework.response import Response from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.generics import ListAPIView -from assets.api.mixin import SerializeToTreeNodeMixin from common.utils import get_logger -from .mixin import AssetRoleAdminMixin, AssetRoleUserMixin -from perms.hands import User +from assets.api.mixin import SerializeToTreeNodeMixin from perms import serializers - +from perms.hands import User from perms.utils.user_permission import UserGrantedNodesQueryUtils +from .mixin import SelfOrPKUserMixin, RebuildTreeMixin logger = get_logger(__name__) __all__ = [ 'UserGrantedNodesApi', - 'MyGrantedNodesApi', - 'MyGrantedNodesAsTreeApi', - 'UserGrantedNodeChildrenForAdminApi', - 'MyGrantedNodeChildrenApi', - 'UserGrantedNodeChildrenAsTreeForAdminApi', - 'MyGrantedNodeChildrenAsTreeApi', + 'UserGrantedNodesAsTreeApi', + 'UserGrantedNodeChildrenApi', + 'UserGrantedNodeChildrenAsTreeApi', 'BaseGrantedNodeAsTreeApi', 'UserGrantedNodesMixin', ] @@ -98,35 +92,42 @@ class UserGrantedNodesMixin: return nodes -# ------------------------------------------ -# 最终的 api -class UserGrantedNodeChildrenForAdminApi(AssetRoleAdminMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenApi): +# API + + +class UserGrantedNodeChildrenApi( + SelfOrPKUserMixin, + UserGrantedNodeChildrenMixin, + BaseNodeChildrenApi +): + """ 用户授权的节点下的子节点""" pass -class MyGrantedNodeChildrenApi(AssetRoleUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenApi): +class UserGrantedNodeChildrenAsTreeApi( + SelfOrPKUserMixin, + RebuildTreeMixin, + UserGrantedNodeChildrenMixin, + BaseNodeChildrenAsTreeApi +): + """ 用户授权的节点下的子节点树""" pass -class UserGrantedNodeChildrenAsTreeForAdminApi(AssetRoleAdminMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenAsTreeApi): +class UserGrantedNodesApi( + SelfOrPKUserMixin, + UserGrantedNodesMixin, + BaseGrantedNodeApi +): + """ 用户授权的节点 """ pass -class MyGrantedNodeChildrenAsTreeApi(AssetRoleUserMixin, UserGrantedNodeChildrenMixin, BaseNodeChildrenAsTreeApi): - def get_permissions(self): - permissions = super().get_permissions() - return permissions - - -class UserGrantedNodesApi(AssetRoleAdminMixin, UserGrantedNodesMixin, BaseGrantedNodeApi): +class UserGrantedNodesAsTreeApi( + SelfOrPKUserMixin, + RebuildTreeMixin, + UserGrantedNodesMixin, + BaseGrantedNodeAsTreeApi +): + """ 用户授权的节点树 """ pass - - -class MyGrantedNodesApi(AssetRoleUserMixin, UserGrantedNodesApi): - pass - - -class MyGrantedNodesAsTreeApi(AssetRoleUserMixin, UserGrantedNodesMixin, BaseGrantedNodeAsTreeApi): - pass - -# ------------------------------------------ diff --git a/apps/perms/api/user_permission/nodes_with_assets.py b/apps/perms/api/user_permission/nodes_with_assets.py index 6c848878f..20cda7e00 100644 --- a/apps/perms/api/user_permission/nodes_with_assets.py +++ b/apps/perms/api/user_permission/nodes_with_assets.py @@ -1,24 +1,25 @@ # -*- coding: utf-8 -*- # +from django.conf import settings +from django.db.models import F, Value, CharField from rest_framework.generics import ListAPIView from rest_framework.request import Request from rest_framework.response import Response -from django.db.models import F, Value, CharField -from django.conf import settings -from common.utils.common import timeit -from orgs.utils import tmp_to_root_org -from common.permissions import IsValidUser from common.utils import get_logger, get_object_or_none -from .mixin import AssetRoleUserMixin, AssetRoleAdminMixin +from common.utils.common import timeit +from common.permissions import IsValidUser + +from assets.models import Asset +from assets.api import SerializeToTreeNodeMixin +from perms.hands import Node +from perms.models import AssetPermission, PermNode from perms.utils.user_permission import ( UserGrantedTreeBuildUtils, get_user_all_asset_perm_ids, UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, ) -from perms.models import AssetPermission, PermNode -from assets.models import Asset -from assets.api import SerializeToTreeNodeMixin -from perms.hands import Node + +from .mixin import SelfOrPKUserMixin, RebuildTreeMixin logger = get_logger(__name__) @@ -148,9 +149,10 @@ class GrantedNodeChildrenWithAssetsAsTreeApiMixin(SerializeToTreeNodeMixin, return Response(data=all_tree_nodes) -class UserGrantedNodeChildrenWithAssetsAsTreeApi(AssetRoleAdminMixin, GrantedNodeChildrenWithAssetsAsTreeApiMixin): - pass - - -class MyGrantedNodeChildrenWithAssetsAsTreeApi(AssetRoleUserMixin, GrantedNodeChildrenWithAssetsAsTreeApiMixin): +class UserGrantedNodeChildrenWithAssetsAsTreeApi( + SelfOrPKUserMixin, + RebuildTreeMixin, + GrantedNodeChildrenWithAssetsAsTreeApiMixin +): + """ 用户授权的节点的子节点与资产树 """ pass diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index c7413dbbc..973374b7a 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -3,68 +3,54 @@ from django.urls import path, include from .. import api user_permission_urlpatterns = [ - # 以 serializer 格式返回 - path('/assets/', api.UserAllGrantedAssetsApi.as_view(), name='user-assets'), - path('assets/', api.MyAllGrantedAssetsApi.as_view(), name='my-assets'), - # Tree Node 的数据格式返回 - path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'), - path('assets/tree/', api.MyAllAssetsAsTreeApi.as_view(), name='my-assets-as-tree'), - path('ungroup/assets/tree/', api.MyUngroupAssetsAsTreeApi.as_view(), name='my-ungroup-assets-as-tree'), + # such as: my | self | user.id - # 获取用户所有`直接授权的节点`与`直接授权资产`关联的节点 - # 以 serializer 格式返回 - path('/nodes/', api.UserGrantedNodesApi.as_view(), name='user-nodes'), - path('nodes/', api.MyGrantedNodesApi.as_view(), name='my-nodes'), - # 以 Tree Node 的数据格式返回 - path('/nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='user-nodes-as-tree'), - path('nodes/tree/', api.MyGrantedNodesAsTreeApi.as_view(), name='my-nodes-as-tree'), + # assets + path('/assets/', api.UserAllGrantedAssetsApi.as_view(), + name='user-assets'), + path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), + name='user-assets-as-tree'), + path('/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(), + name='user-ungroup-assets-as-tree'), - # 一层一层的获取用户授权的节点, - # 以 Serializer 的数据格式返回 - path('/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'), - path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'), - # 以 Tree Node 的数据格式返回 - path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), + # nodes + path('/nodes/', api.UserGrantedNodesApi.as_view(), + name='user-nodes'), + path('/nodes/tree/', api.UserGrantedNodesAsTreeApi.as_view(), + name='user-nodes-as-tree'), + path('/nodes/children/', api.UserGrantedNodeChildrenApi.as_view(), + name='user-nodes-children'), + path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeApi.as_view(), name='user-nodes-children-as-tree'), - # 部分调用位置 - # - 普通用户 -> 我的资产 -> 展开节点 时调用 - path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'), - # 此接口会返回整棵树 - # 普通用户 -> 命令执行 -> 左侧树 + # node-assets + path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), + name='user-node-assets'), + path('/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), + name='user-ungrouped-assets'), + path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), + name='user-ungrouped-assets'), + + path('/nodes/children-with-assets/tree/', + api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='user-nodes-children-with-assets-as-tree'), + path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'), - # 主要用于 luna 页面,带资产的节点树 - path('/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), - name='user-nodes-children-with-assets-as-tree'), - path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), - name='my-nodes-children-with-assets-as-tree'), - - # 查询授权树上某个节点的所有资产 - path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), - path('nodes//assets/', api.MyGrantedNodeAssetsApi.as_view(), name='my-node-assets'), - - # 未分组的资产 - path('/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), - path('nodes/ungrouped/assets/', api.MyDirectGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), - - # 收藏的资产 - path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), - path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), - name='my-ungrouped-assets'), - - # 获取授权给用户某个资产的所有账号 - # user params: ['my', 'self'] or user.id + # accounts path('/assets//accounts/', api.UserPermedAssetAccountsApi.as_view(), name='user-permed-asset-accounts'), ] user_group_permission_urlpatterns = [ # 查询某个用户组授权的资产和资产组 - path('/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), - path('/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), - path('/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'), + path('/assets/', api.UserGroupGrantedAssetsApi.as_view(), + name='user-group-assets'), + path('/nodes/', api.UserGroupGrantedNodesApi.as_view(), + name='user-group-nodes'), + path('/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), + name='user-group-nodes-children'), path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'), path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), From b55b755e8e9c2951854b467e7f07b89634d764ef Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 1 Dec 2022 12:03:01 +0800 Subject: [PATCH 442/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20LoginAssetA?= =?UTF-8?q?CL=20Check=20API=20=E8=8E=B7=E5=8F=96=20account=5Fusername=20?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/login_asset_check.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index df7288c4c..04042473f 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -35,7 +35,7 @@ class LoginAssetCheckAPI(CreateAPIView): .filter(action=LoginAssetACL.ActionChoices.login_confirm)\ .filter_user(self.serializer.user)\ .filter_asset(self.serializer.asset)\ - .filter_account(self.serializer.account_username)\ + .filter_account(self.serializer.validated_data.get('account_username'))\ .valid()\ .first() if acl: @@ -51,7 +51,7 @@ class LoginAssetCheckAPI(CreateAPIView): ticket = LoginAssetACL.create_login_asset_confirm_ticket( user=self.serializer.user, asset=self.serializer.asset, - account_username=self.serializer.account_username, + account_username=self.serializer.validated_data.get('account_username'), assignees=acl.reviewers.all(), org_id=self.serializer.asset.org.id, ) From d1461b33c55130d0a9ebd691cdcd2a0c634dfcae Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:12:31 +0800 Subject: [PATCH 443/488] perf: gather account mysql (#9136) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/automations/gather_accounts/database/mysql/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/automations/gather_accounts/database/mysql/main.yml b/apps/assets/automations/gather_accounts/database/mysql/main.yml index 4b166322a..4ba9ec221 100644 --- a/apps/assets/automations/gather_accounts/database/mysql/main.yml +++ b/apps/assets/automations/gather_accounts/database/mysql/main.yml @@ -1,7 +1,7 @@ - hosts: mysql gather_facts: no vars: - ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + ansible_python_interpreter: /usr/local/bin/python tasks: - name: Get info From 7a475fc029609f84049e514c75274631a20b80a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 1 Dec 2022 14:54:57 +0800 Subject: [PATCH 444/488] =?UTF-8?q?perf:=20=E6=8E=A7=E5=88=B6=20gunicorn?= =?UTF-8?q?=20=E5=90=AF=E5=8A=A8=E8=BF=9B=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/services/services/gunicorn.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/common/management/commands/services/services/gunicorn.py b/apps/common/management/commands/services/services/gunicorn.py index 5cc67b45c..97db306a7 100644 --- a/apps/common/management/commands/services/services/gunicorn.py +++ b/apps/common/management/commands/services/services/gunicorn.py @@ -1,3 +1,4 @@ +import multiprocessing from ..hands import * from .base import BaseService @@ -16,11 +17,15 @@ class GunicornService(BaseService): log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s ' bind = f'{HTTP_HOST}:{HTTP_PORT}' + cores = 10 + if (multiprocessing.cpu_count() * 2 + 1) < cores: + cores = multiprocessing.cpu_count() * 2 + 1 + cmd = [ 'gunicorn', 'jumpserver.asgi:application', '-b', bind, '-k', 'uvicorn.workers.UvicornWorker', - '--threads', '10', + '--threads', str(cores), '-w', str(self.worker), '--max-requests', '4096', '--access-logformat', log_format, From 8162a1b17e9f2ae3ec00654e3f6329c1d09cde04 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Dec 2022 15:21:53 +0800 Subject: [PATCH 445/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/domain.py | 10 +- apps/assets/migrations/0116_delete_gateway.py | 16 +++ apps/assets/migrations/0117_gateway.py | 24 ++++ apps/assets/models/asset/host.py | 8 +- apps/assets/models/domain.py | 129 +++--------------- 5 files changed, 64 insertions(+), 123 deletions(-) create mode 100644 apps/assets/migrations/0116_delete_gateway.py create mode 100644 apps/assets/migrations/0117_gateway.py diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index b98cd7273..948bb6a7b 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -35,7 +35,7 @@ class GatewayViewSet(OrgBulkModelViewSet): serializer_class = serializers.GatewaySerializer def get_queryset(self): - queryset = Host.get_gateway_queryset() + queryset = Domain.get_gateway_queryset() return queryset @@ -45,17 +45,17 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView): } def get_queryset(self): - queryset = Host.get_gateway_queryset() + queryset = Domain.get_gateway_queryset() return queryset def post(self, request, *args, **kwargs): - self.object = self.get_object() - local_port = self.request.data.get('port') or self.object.port + gateway = self.get_object() + local_port = self.request.data.get('port') or gateway.port try: local_port = int(local_port) except ValueError: raise ValidationError({'port': _('Number required')}) - ok, e = self.object.test_connective(local_port=local_port) + ok, e = gateway.test_connective(local_port=local_port) if ok: return Response("ok") else: diff --git a/apps/assets/migrations/0116_delete_gateway.py b/apps/assets/migrations/0116_delete_gateway.py new file mode 100644 index 000000000..9ccb1c1b7 --- /dev/null +++ b/apps/assets/migrations/0116_delete_gateway.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.14 on 2022-12-01 07:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0115_auto_20221130_1118'), + ] + + operations = [ + migrations.DeleteModel( + name='Gateway', + ), + ] diff --git a/apps/assets/migrations/0117_gateway.py b/apps/assets/migrations/0117_gateway.py new file mode 100644 index 000000000..6bf8ce138 --- /dev/null +++ b/apps/assets/migrations/0117_gateway.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.14 on 2022-12-01 07:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0116_delete_gateway'), + ] + + operations = [ + migrations.CreateModel( + name='Gateway', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('assets.host',), + ), + ] diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index a1dbb6de3..6ca93b89f 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -3,10 +3,4 @@ from .common import Asset class Host(Asset): - - @classmethod - def get_gateway_queryset(cls): - queryset = cls.objects.filter( - platform__name=GATEWAY_NAME - ) - return queryset + pass diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 248e02f20..daa08a343 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -1,25 +1,20 @@ # -*- coding: utf-8 -*- # import uuid -import socket import random -import paramiko +import paramiko from django.db import models -from django.core.cache import cache -from django.db.models.query import QuerySet from django.utils.translation import ugettext_lazy as _ -from common.db import fields from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin from assets.models import Host -from .base import BaseAccount -from ..const import SecretType +from assets.const import GATEWAY_NAME logger = get_logger(__file__) -__all__ = ['Domain', 'GatewayMixin'] +__all__ = ['Domain'] class Domain(OrgModelMixin): @@ -36,9 +31,16 @@ class Domain(OrgModelMixin): def __str__(self): return self.name + @classmethod + def get_gateway_queryset(cls): + queryset = Host.objects.filter( + platform__name=GATEWAY_NAME + ) + return queryset + @lazyproperty def gateways(self): - return Host.get_gateway_queryset().filter(domain=self, is_active=True) + return self.get_gateway_queryset().filter(domain=self, is_active=True) def select_gateway(self): return self.random_gateway() @@ -53,50 +55,11 @@ class Domain(OrgModelMixin): return random.choice(self.gateways) -class GatewayMixin: - id: uuid.UUID - port: int - address: str - accounts: QuerySet - private_key_path: str - private_key_obj: paramiko.RSAKey - UNCONNECTED_KEY_TMPL = 'asset_unconnective_gateway_{}' - UNCONNECTED_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}' - UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5 - - def set_unconnected(self): - unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id) - unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id) - unconnected_silence_period = cache.get( - unconnected_silence_period_key, self.UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE - ) - cache.set(unconnected_silence_period_key, unconnected_silence_period * 2) - cache.set(unconnected_key, unconnected_silence_period, unconnected_silence_period) - - def set_connective(self): - unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id) - unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id) - - cache.delete(unconnected_key) - cache.delete(unconnected_silence_period_key) - - def get_is_unconnected(self): - unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id) - return cache.get(unconnected_key, False) - - @property - def is_connective(self): - return not self.get_is_unconnected() - - @is_connective.setter - def is_connective(self, value): - if value: - self.set_connective() - else: - self.set_unconnected() +class Gateway(Host): + class Meta: + proxy = True def test_connective(self, local_port=None): - # TODO 走ansible runner if local_port is None: local_port = self.port @@ -106,7 +69,7 @@ class GatewayMixin: proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: - proxy.connect(self.address, port=self.port, + proxy.connect(self.ip, port=self.port, username=self.username, password=self.password, pkey=self.private_key_obj) @@ -118,8 +81,8 @@ class GatewayMixin: socket.gaierror) as e: err = str(e) if err.startswith('[Errno None] Unable to connect to port'): - err = _('Unable to connect to port {port} on {address}') - err = err.format(port=self.port, ip=self.address) + err = _('Unable to connect to port {port} on {ip}') + err = err.format(port=self.port, ip=self.ip) elif err == 'Authentication failed.': err = _('Authentication failed') elif err == 'Connect failed': @@ -134,7 +97,7 @@ class GatewayMixin: client.connect("127.0.0.1", port=local_port, username=self.username, password=self.password, - key_filename=self.private_key_path, + key_filename=self.private_key_file, sock=sock, timeout=5) except (paramiko.SSHException, @@ -152,59 +115,3 @@ class GatewayMixin: client.close() self.is_connective = True return True, None - - @lazyproperty - def username(self): - account = self.accounts.all().first() - if account: - return account.username - logger.error(f'Gateway {self} has no account') - return '' - - def get_secret(self, secret_type): - account = self.accounts.filter(secret_type=secret_type).first() - if account: - return account.secret - logger.error(f'Gateway {self} has no {secret_type} account') - - @lazyproperty - def password(self): - secret_type = SecretType.PASSWORD - return self.get_secret(secret_type) - - @lazyproperty - def private_key(self): - secret_type = SecretType.SSH_KEY - return self.get_secret(secret_type) - - -class Gateway(BaseAccount): - class Protocol(models.TextChoices): - ssh = 'ssh', 'SSH' - - name = models.CharField(max_length=128, verbose_name='Name') - ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) - port = models.IntegerField(default=22, verbose_name=_('Port')) - protocol = models.CharField( - choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") - ) - domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain")) - comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) - is_active = models.BooleanField(default=True, verbose_name=_("Is active")) - password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) - private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key')) - public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key')) - - secret = None - secret_type = None - privileged = None - - def __str__(self): - return self.name - - class Meta: - unique_together = [('name', 'org_id')] - verbose_name = _("Gateway") - permissions = [ - ('test_gateway', _('Test gateway')) - ] From 2bc47c87d1077ff70721f9a5d4edbf9202c35341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 1 Dec 2022 16:12:10 +0800 Subject: [PATCH 446/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=AD=A3=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/management/commands/services/command.py | 7 ++++++- .../management/commands/services/services/gunicorn.py | 6 +----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/common/management/commands/services/command.py b/apps/common/management/commands/services/command.py index 1fb28fd3a..2ee11385d 100644 --- a/apps/common/management/commands/services/command.py +++ b/apps/common/management/commands/services/command.py @@ -1,3 +1,4 @@ +import multiprocessing from django.core.management.base import BaseCommand, CommandError from django.db.models import TextChoices from .utils import ServicesUtil @@ -91,11 +92,15 @@ class BaseActionCommand(BaseCommand): super().__init__(*args, **kwargs) def add_arguments(self, parser): + cores = 10 + if (multiprocessing.cpu_count() + 1) < cores: + cores = multiprocessing.cpu_count() + 1 + parser.add_argument( 'services', nargs='+', choices=Services.export_services_values(), help='Service', ) parser.add_argument('-d', '--daemon', nargs="?", const=True) - parser.add_argument('-w', '--worker', type=int, nargs="?", default=4) + parser.add_argument('-w', '--worker', type=int, nargs="?", default=cores) parser.add_argument('-f', '--force', nargs="?", const=True) def initial_util(self, *args, **options): diff --git a/apps/common/management/commands/services/services/gunicorn.py b/apps/common/management/commands/services/services/gunicorn.py index 97db306a7..495ace6c7 100644 --- a/apps/common/management/commands/services/services/gunicorn.py +++ b/apps/common/management/commands/services/services/gunicorn.py @@ -1,4 +1,3 @@ -import multiprocessing from ..hands import * from .base import BaseService @@ -17,15 +16,12 @@ class GunicornService(BaseService): log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s ' bind = f'{HTTP_HOST}:{HTTP_PORT}' - cores = 10 - if (multiprocessing.cpu_count() * 2 + 1) < cores: - cores = multiprocessing.cpu_count() * 2 + 1 cmd = [ 'gunicorn', 'jumpserver.asgi:application', '-b', bind, '-k', 'uvicorn.workers.UvicornWorker', - '--threads', str(cores), + '--threads', str(self.worker * 2), '-w', str(self.worker), '--max-requests', '4096', '--access-logformat', log_format, From ce3ec851477d6de6d054b27a4b9c422afa40b46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Thu, 1 Dec 2022 16:22:26 +0800 Subject: [PATCH 447/488] =?UTF-8?q?fix:=20=E5=8E=BB=E6=8E=89=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jms b/jms index 5d5b5c433..57410c933 100755 --- a/jms +++ b/jms @@ -177,7 +177,7 @@ if __name__ == '__main__': help="The service to start", ) parser.add_argument('-d', '--daemon', nargs="?", const=True) - parser.add_argument('-w', '--worker', type=int, nargs="?", default=4) + parser.add_argument('-w', '--worker', type=int, nargs="?") parser.add_argument('-f', '--force', nargs="?", const=True) args = parser.parse_args() From fa0382fc5ea919fdb3063eeb0f4526b260ae8b97 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Thu, 1 Dec 2022 18:22:41 +0800 Subject: [PATCH 448/488] perf: gateway manager --- apps/assets/models/domain.py | 87 +++++++++---------------------- apps/assets/serializers/domain.py | 10 ---- 2 files changed, 26 insertions(+), 71 deletions(-) diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index daa08a343..9580b17ba 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -9,12 +9,13 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin -from assets.models import Host +from assets.models import Host, Platform from assets.const import GATEWAY_NAME +from orgs.mixins.models import OrgManager logger = get_logger(__file__) -__all__ = ['Domain'] +__all__ = ['Domain', 'Gateway'] class Domain(OrgModelMixin): @@ -33,10 +34,7 @@ class Domain(OrgModelMixin): @classmethod def get_gateway_queryset(cls): - queryset = Host.objects.filter( - platform__name=GATEWAY_NAME - ) - return queryset + return Gateway.objects.all() @lazyproperty def gateways(self): @@ -55,63 +53,30 @@ class Domain(OrgModelMixin): return random.choice(self.gateways) +class GatewayManager(OrgManager): + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.filter(platform__name=GATEWAY_NAME) + return queryset + + def bulk_create(self, objs, batch_size=None, ignore_conflicts=False): + platform = Gateway().default_platform + for obj in objs: + obj.platform_id = platform.id + return super().bulk_create(objs, batch_size, ignore_conflicts) + + class Gateway(Host): + objects = GatewayManager() + class Meta: proxy = True - def test_connective(self, local_port=None): - if local_port is None: - local_port = self.port + @lazyproperty + def default_platform(self): + return Platform.objects.get(name=GATEWAY_NAME, internal=True) - client = paramiko.SSHClient() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - proxy = paramiko.SSHClient() - proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - try: - proxy.connect(self.ip, port=self.port, - username=self.username, - password=self.password, - pkey=self.private_key_obj) - except(paramiko.AuthenticationException, - paramiko.BadAuthenticationType, - paramiko.SSHException, - paramiko.ChannelException, - paramiko.ssh_exception.NoValidConnectionsError, - socket.gaierror) as e: - err = str(e) - if err.startswith('[Errno None] Unable to connect to port'): - err = _('Unable to connect to port {port} on {ip}') - err = err.format(port=self.port, ip=self.ip) - elif err == 'Authentication failed.': - err = _('Authentication failed') - elif err == 'Connect failed': - err = _('Connect failed') - self.is_connective = False - return False, err - - try: - sock = proxy.get_transport().open_channel( - 'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0) - ) - client.connect("127.0.0.1", port=local_port, - username=self.username, - password=self.password, - key_filename=self.private_key_file, - sock=sock, - timeout=5) - except (paramiko.SSHException, - paramiko.ssh_exception.SSHException, - paramiko.ChannelException, - paramiko.AuthenticationException, - TimeoutError) as e: - - err = getattr(e, 'text', str(e)) - if err == 'Connect failed': - err = _('Connect failed') - self.is_connective = False - return False, err - finally: - client.close() - self.is_connective = True - return True, None + def save(self, *args, **kwargs): + platform = self.default_platform + self.platform_id = platform.id + return super().save(*args, **kwargs) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 3e2b4889f..f17aa19ee 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -93,15 +93,6 @@ class GatewaySerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeria validated_data.pop('passphrase', None) return username, password, private_key - @staticmethod - def generate_default_data(): - platform = Platform.objects.get(name=GATEWAY_NAME, internal=True) - # node = Node.objects.all().order_by('date_created').first() - data = { - 'platform': platform, - } - return data - @staticmethod def create_accounts(instance, username, password, private_key): account_name = f'{instance.name}-{_("Gateway")}' @@ -135,7 +126,6 @@ class GatewaySerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeria def create(self, validated_data): auth_fields = self.clean_auth_fields(validated_data) - validated_data.update(self.generate_default_data()) instance = super().create(validated_data) self.create_accounts(instance, *auth_fields) return instance From cb3877bbdae8347bfef98c9364ce9396606ff9ba Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Dec 2022 19:41:18 +0800 Subject: [PATCH 449/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20acl=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=91=BD=E4=BB=A4=E8=BF=87=E6=BB=A4=20acl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/command_acl.py | 12 ++ .../migrations/0005_auto_20221201_1846.py | 35 ++++ .../0006_commandfilteracl_commandgroup.py | 61 +++++++ apps/acls/models/__init__.py | 1 + apps/acls/models/base.py | 21 ++- apps/acls/models/command_acl.py | 162 ++++++++++++++++++ apps/acls/models/login_acl.py | 24 +-- apps/acls/models/login_asset_acl.py | 22 +-- apps/acls/serializers/command_filter.py | 0 9 files changed, 294 insertions(+), 44 deletions(-) create mode 100644 apps/acls/api/command_acl.py create mode 100644 apps/acls/migrations/0005_auto_20221201_1846.py create mode 100644 apps/acls/migrations/0006_commandfilteracl_commandgroup.py create mode 100644 apps/acls/serializers/command_filter.py diff --git a/apps/acls/api/command_acl.py b/apps/acls/api/command_acl.py new file mode 100644 index 000000000..563e0c1a2 --- /dev/null +++ b/apps/acls/api/command_acl.py @@ -0,0 +1,12 @@ +from orgs.mixins.api import OrgBulkModelViewSet +from .. import models, serializers + + +__all__ = ['CommandFilterACLViewSet'] + + +class CommandFilterACLViewSet(OrgBulkModelViewSet): + model = models.CommandFilterACL + filterset_fields = ('name', ) + search_fields = filterset_fields + serializer_class = serializers.LoginAssetACLSerializer diff --git a/apps/acls/migrations/0005_auto_20221201_1846.py b/apps/acls/migrations/0005_auto_20221201_1846.py new file mode 100644 index 000000000..b69216896 --- /dev/null +++ b/apps/acls/migrations/0005_auto_20221201_1846.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.14 on 2022-12-01 10:46 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('acls', '0004_auto_20220831_1658'), + ] + + operations = [ + migrations.AlterField( + model_name='loginacl', + name='action', + field=models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow'), ('confirm', 'Confirm')], default='reject', max_length=64, verbose_name='Action'), + ), + migrations.AlterField( + model_name='loginacl', + name='reviewers', + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers'), + ), + migrations.AlterField( + model_name='loginassetacl', + name='action', + field=models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow'), ('confirm', 'Confirm')], default='reject', max_length=64, verbose_name='Action'), + ), + migrations.AlterField( + model_name='loginassetacl', + name='reviewers', + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers'), + ), + ] diff --git a/apps/acls/migrations/0006_commandfilteracl_commandgroup.py b/apps/acls/migrations/0006_commandfilteracl_commandgroup.py new file mode 100644 index 000000000..05122b733 --- /dev/null +++ b/apps/acls/migrations/0006_commandfilteracl_commandgroup.py @@ -0,0 +1,61 @@ +# Generated by Django 3.2.14 on 2022-12-01 11:39 + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('acls', '0005_auto_20221201_1846'), + ] + + operations = [ + migrations.CreateModel( + name='CommandGroup', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command', max_length=16, verbose_name='Type')), + ('content', models.TextField(help_text='One line one command', verbose_name='Content')), + ('ignore_case', models.BooleanField(default=True, verbose_name='Ignore case')), + ], + options={ + 'verbose_name': 'Command filter rule', + 'unique_together': {('org_id', 'name')}, + }, + ), + migrations.CreateModel( + name='CommandFilterACL', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('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')), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')), + ('action', models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow'), ('confirm', 'Confirm')], default='reject', max_length=64, verbose_name='Action')), + ('is_active', models.BooleanField(default=True, verbose_name='Active')), + ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), + ('users', models.JSONField(verbose_name='User')), + ('accounts', models.JSONField(verbose_name='Account')), + ('assets', models.JSONField(verbose_name='Asset')), + ('commands', models.ManyToManyField(to='acls.CommandGroup', verbose_name='Commands')), + ('reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), + ], + options={ + 'verbose_name': 'Command acl', + 'ordering': ('priority', '-date_updated', 'name'), + 'unique_together': {('name', 'org_id')}, + }, + ), + ] diff --git a/apps/acls/models/__init__.py b/apps/acls/models/__init__.py index 45d49c378..3c5416992 100644 --- a/apps/acls/models/__init__.py +++ b/apps/acls/models/__init__.py @@ -1,2 +1,3 @@ from .login_acl import * from .login_asset_acl import * +from .command_acl import * diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 73ab5c59c..6bda02df8 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -4,7 +4,13 @@ from django.core.validators import MinValueValidator, MaxValueValidator from common.mixins import CommonModelMixin -__all__ = ['BaseACL', 'BaseACLQuerySet'] +__all__ = ['BaseACL', 'BaseACLQuerySet', 'ACLManager'] + + +class ActionChoices(models.TextChoices): + reject = 'reject', _('Reject') + allow = 'allow', _('Allow') + confirm = 'confirm', _('Confirm') class BaseACLQuerySet(models.QuerySet): @@ -21,6 +27,11 @@ class BaseACLQuerySet(models.QuerySet): return self.inactive() +class ACLManager(models.Manager): + def valid(self): + return self.get_queryset().valid() + + class BaseACL(CommonModelMixin): name = models.CharField(max_length=128, verbose_name=_('Name')) priority = models.IntegerField( @@ -28,8 +39,16 @@ class BaseACL(CommonModelMixin): help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)] ) + action = models.CharField( + max_length=64, verbose_name=_('Action'), + choices=ActionChoices.choices, default=ActionChoices.reject + ) + reviewers = models.ManyToManyField('users.User', blank=True, verbose_name=_("Reviewers")) is_active = models.BooleanField(default=True, verbose_name=_("Active")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) + objects = ACLManager.from_queryset(BaseACLQuerySet)() + ActionChoices = ActionChoices + class Meta: abstract = True diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index e69de29bb..4fba67a3d 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# +import re + +from django.db import models +from django.db.models import Q +from django.utils.translation import ugettext_lazy as _ + +from users.models import User, UserGroup +from orgs.mixins.models import JMSOrgBaseModel +from common.utils import lazyproperty, get_logger, get_object_or_none +from orgs.mixins.models import OrgModelMixin +from .base import BaseACL + +logger = get_logger(__file__) + + +class CommandGroup(JMSOrgBaseModel): + class Type(models.TextChoices): + command = 'command', _('Command') + regex = 'regex', _('Regex') + + name = models.CharField(max_length=128, verbose_name=_("Name")) + type = models.CharField(max_length=16, default=Type.command, choices=Type.choices, verbose_name=_("Type")) + content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) + ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case')) + + class Meta: + unique_together = [('org_id', 'name')] + verbose_name = _("Command filter rule") + + @lazyproperty + def pattern(self): + if self.type == 'command': + s = self.construct_command_regex(content=self.content) + else: + s = r'{0}'.format(self.content) + return s + + @classmethod + def construct_command_regex(cls, content): + regex = [] + content = content.replace('\r\n', '\n') + for _cmd in content.split('\n'): + cmd = re.sub(r'\s+', ' ', _cmd) + cmd = re.escape(cmd) + cmd = cmd.replace('\\ ', '\s+') + + # 有空格就不能 铆钉单词了 + if ' ' in _cmd: + regex.append(cmd) + continue + + if not cmd: + continue + + # 如果是单个字符 + if cmd[-1].isalpha(): + regex.append(r'\b{0}\b'.format(cmd)) + else: + regex.append(r'\b{0}'.format(cmd)) + s = r'{}'.format('|'.join(regex)) + return s + + @staticmethod + def compile_regex(regex, ignore_case): + args = [] + if ignore_case: + args.append(re.IGNORECASE) + try: + pattern = re.compile(regex, *args) + except Exception as e: + error = _('The generated regular expression is incorrect: {}').format(str(e)) + logger.error(error) + return False, error, None + return True, '', pattern + + def match(self, data): + succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case) + if not succeed: + return False, '' + + found = pattern.search(data) + if not found: + return False, '' + else: + return True, found.group() + + def __str__(self): + return '{} % {}'.format(self.type, self.content) + + def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): + from tickets.const import TicketType + from tickets.models import ApplyCommandTicket + data = { + 'title': _('Command confirm') + ' ({})'.format(session.user), + 'type': TicketType.command_confirm, + 'applicant': session.user_obj, + 'apply_run_user_id': session.user_id, + 'apply_run_asset': str(session.asset), + 'apply_run_account': str(session.account), + 'apply_run_command': run_command[:4090], + 'apply_from_session_id': str(session.id), + 'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id), + 'apply_from_cmd_filter_id': str(cmd_filter_rule.filter.id), + 'org_id': org_id, + } + ticket = ApplyCommandTicket.objects.create(**data) + assignees = self.reviewers.all() + ticket.open_by_system(assignees) + return ticket + + @classmethod + def get_queryset( + cls, user_id=None, user_group_id=None, account=None, + asset_id=None, org_id=None + ): + from assets.models import Account + user_groups = [] + user = get_object_or_none(User, pk=user_id) + if user: + user_groups.extend(list(user.groups.all())) + user_group = get_object_or_none(UserGroup, pk=user_group_id) + if user_group: + org_id = user_group.org_id + user_groups.append(user_group) + + asset = get_object_or_none(Asset, pk=asset_id) + q = Q() + if user: + q |= Q(users=user) + if user_groups: + q |= Q(user_groups__in=set(user_groups)) + if account: + org_id = account.org_id + q |= Q(accounts__contains=account.username) | \ + Q(accounts__contains=Account.AliasAccount.ALL) + if asset: + org_id = asset.org_id + q |= Q(assets=asset) + if q: + cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True) + if org_id: + cmd_filters = cmd_filters.filter(org_id=org_id) + rule_ids = cmd_filters.values_list('rules', flat=True) + rules = cls.objects.filter(id__in=rule_ids) + else: + rules = cls.objects.none() + return rules + + +class CommandFilterACL(OrgModelMixin, BaseACL): + # 条件 + users = models.JSONField(verbose_name=_('User')) + accounts = models.JSONField(verbose_name=_('Account')) + assets = models.JSONField(verbose_name=_('Asset')) + commands = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) + + class Meta: + unique_together = ('name', 'org_id') + ordering = ('priority', '-date_updated', 'name') + verbose_name = _('Command acl') diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index 71f202b15..aedb8aa9c 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -1,24 +1,14 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from .base import BaseACL, BaseACLQuerySet + from common.utils import get_request_ip, get_ip_city from common.utils.ip import contains_ip from common.utils.time_period import contains_time_period from common.utils.timezone import local_now_display - - -class ACLManager(models.Manager): - - def valid(self): - return self.get_queryset().valid() +from .base import BaseACL class LoginACL(BaseACL): - class ActionChoices(models.TextChoices): - reject = 'reject', _('Reject') - allow = 'allow', _('Allow') - confirm = 'confirm', _('Login confirm') - # 用户 user = models.ForeignKey( 'users.User', on_delete=models.CASCADE, verbose_name=_('User'), @@ -26,16 +16,6 @@ class LoginACL(BaseACL): ) # 规则 rules = models.JSONField(default=dict, verbose_name=_('Rule')) - # 动作 - action = models.CharField( - max_length=64, verbose_name=_('Action'), - choices=ActionChoices.choices, default=ActionChoices.reject - ) - reviewers = models.ManyToManyField( - 'users.User', verbose_name=_("Reviewers"), - related_name="login_confirm_acls", blank=True - ) - objects = ACLManager.from_queryset(BaseACLQuerySet)() class Meta: ordering = ('priority', '-date_updated', 'name') diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 842d41432..de9897c7b 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -2,7 +2,7 @@ from django.db import models from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from orgs.mixins.models import OrgModelMixin, OrgManager -from .base import BaseACL, BaseACLQuerySet +from .base import BaseACL, BaseACLQuerySet, ACLManager from common.utils.ip import contains_ip @@ -32,31 +32,11 @@ class ACLQuerySet(BaseACLQuerySet): ) -class ACLManager(OrgManager): - - def valid(self): - return self.get_queryset().valid() - - class LoginAssetACL(BaseACL, OrgModelMixin): - class ActionChoices(models.TextChoices): - login_confirm = 'login_confirm', _('Login confirm') - # 条件 users = models.JSONField(verbose_name=_('User')) accounts = models.JSONField(verbose_name=_('Account')) assets = models.JSONField(verbose_name=_('Asset')) - # 动作 - action = models.CharField( - max_length=64, choices=ActionChoices.choices, default=ActionChoices.login_confirm, - verbose_name=_('Action') - ) - # 动作: 附加字段 - # - login_confirm - reviewers = models.ManyToManyField( - 'users.User', related_name='review_login_asset_acls', blank=True, - verbose_name=_("Reviewers") - ) objects = ACLManager.from_queryset(ACLQuerySet)() diff --git a/apps/acls/serializers/command_filter.py b/apps/acls/serializers/command_filter.py new file mode 100644 index 000000000..e69de29bb From 10e3100d3c66f962549f1367036de10fb38df112 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 1 Dec 2022 22:09:16 +0800 Subject: [PATCH 450/488] fix: LoginAssetACL confirm action --- apps/acls/api/login_asset_check.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index 04042473f..6ebee2579 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -1,10 +1,10 @@ -from rest_framework.response import Response from rest_framework.generics import CreateAPIView +from rest_framework.response import Response from common.utils import reverse, lazyproperty from orgs.utils import tmp_to_org -from ..models import LoginAssetACL from .. import serializers +from ..models import LoginAssetACL __all__ = ['LoginAssetCheckAPI'] @@ -31,12 +31,12 @@ class LoginAssetCheckAPI(CreateAPIView): def check_confirm(self): with tmp_to_org(self.serializer.asset.org): - acl = LoginAssetACL.objects\ - .filter(action=LoginAssetACL.ActionChoices.login_confirm)\ - .filter_user(self.serializer.user)\ - .filter_asset(self.serializer.asset)\ - .filter_account(self.serializer.validated_data.get('account_username'))\ - .valid()\ + acl = LoginAssetACL.objects \ + .filter(action=LoginAssetACL.ActionChoices.confirm) \ + .filter_user(self.serializer.user) \ + .filter_asset(self.serializer.asset) \ + .filter_account(self.serializer.validated_data.get('account_username')) \ + .valid() \ .first() if acl: need_confirm = True From 709b6e5b0dc25745a972f8843a729b882426cfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com> Date: Fri, 2 Dec 2022 10:45:12 +0800 Subject: [PATCH 451/488] =?UTF-8?q?perf:=20=E5=8E=BB=E6=8E=89=20gunicorn?= =?UTF-8?q?=20threads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/management/commands/services/command.py | 4 ++-- apps/common/management/commands/services/services/gunicorn.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/common/management/commands/services/command.py b/apps/common/management/commands/services/command.py index 2ee11385d..fcaa8f1cd 100644 --- a/apps/common/management/commands/services/command.py +++ b/apps/common/management/commands/services/command.py @@ -93,8 +93,8 @@ class BaseActionCommand(BaseCommand): def add_arguments(self, parser): cores = 10 - if (multiprocessing.cpu_count() + 1) < cores: - cores = multiprocessing.cpu_count() + 1 + if (multiprocessing.cpu_count() * 2 + 1) < cores: + cores = multiprocessing.cpu_count() * 2 + 1 parser.add_argument( 'services', nargs='+', choices=Services.export_services_values(), help='Service', diff --git a/apps/common/management/commands/services/services/gunicorn.py b/apps/common/management/commands/services/services/gunicorn.py index 495ace6c7..5eab30ec3 100644 --- a/apps/common/management/commands/services/services/gunicorn.py +++ b/apps/common/management/commands/services/services/gunicorn.py @@ -21,7 +21,6 @@ class GunicornService(BaseService): 'gunicorn', 'jumpserver.asgi:application', '-b', bind, '-k', 'uvicorn.workers.UvicornWorker', - '--threads', str(self.worker * 2), '-w', str(self.worker), '--max-requests', '4096', '--access-logformat', log_format, From a18f544cf86f74e92797eaa955b6610a4409d7a3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 11:12:14 +0800 Subject: [PATCH 452/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20=20acl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0005_auto_20221201_1846.py | 5 +- .../0006_commandfilteracl_commandgroup.py | 27 ++-- .../migrations/0007_auto_20221202_1048.py | 21 +++ apps/acls/models/base.py | 45 ++++-- apps/acls/models/command_acl.py | 33 +++-- apps/acls/models/login_asset_acl.py | 34 +---- apps/acls/serializers/base.py | 94 +++++++++++++ apps/acls/serializers/command_filter.py | 16 +++ apps/acls/serializers/login_asset_acl.py | 108 +-------------- apps/assets/models/cmd_filter.py | 130 +----------------- apps/authentication/mixins.py | 12 +- 11 files changed, 217 insertions(+), 308 deletions(-) create mode 100644 apps/acls/migrations/0007_auto_20221202_1048.py create mode 100644 apps/acls/serializers/base.py diff --git a/apps/acls/migrations/0005_auto_20221201_1846.py b/apps/acls/migrations/0005_auto_20221201_1846.py index b69216896..4885ea97e 100644 --- a/apps/acls/migrations/0005_auto_20221201_1846.py +++ b/apps/acls/migrations/0005_auto_20221201_1846.py @@ -5,7 +5,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('acls', '0004_auto_20220831_1658'), @@ -15,7 +14,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='loginacl', name='action', - field=models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow'), ('confirm', 'Confirm')], default='reject', max_length=64, verbose_name='Action'), + field=models.CharField(default='reject', max_length=64, verbose_name='Action'), ), migrations.AlterField( model_name='loginacl', @@ -25,7 +24,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='loginassetacl', name='action', - field=models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow'), ('confirm', 'Confirm')], default='reject', max_length=64, verbose_name='Action'), + field=models.CharField(default='reject', max_length=64, verbose_name='Action'), ), migrations.AlterField( model_name='loginassetacl', diff --git a/apps/acls/migrations/0006_commandfilteracl_commandgroup.py b/apps/acls/migrations/0006_commandfilteracl_commandgroup.py index 05122b733..95b12e9f0 100644 --- a/apps/acls/migrations/0006_commandfilteracl_commandgroup.py +++ b/apps/acls/migrations/0006_commandfilteracl_commandgroup.py @@ -1,13 +1,13 @@ # Generated by Django 3.2.14 on 2022-12-01 11:39 -from django.conf import settings -import django.core.validators -from django.db import migrations, models import uuid +import django.core.validators +from django.conf import settings +from django.db import migrations, models + class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('acls', '0005_auto_20221201_1846'), @@ -22,9 +22,11 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('name', models.CharField(max_length=128, verbose_name='Name')), - ('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command', max_length=16, verbose_name='Type')), + ('type', models.CharField(choices=[('command', 'Command'), ('regex', 'Regex')], default='command', + max_length=16, verbose_name='Type')), ('content', models.TextField(help_text='One line one command', verbose_name='Content')), ('ignore_case', models.BooleanField(default=True, verbose_name='Ignore case')), ], @@ -36,21 +38,26 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CommandFilterACL', fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('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')), ('name', models.CharField(max_length=128, verbose_name='Name')), - ('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')), - ('action', models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow'), ('confirm', 'Confirm')], default='reject', max_length=64, verbose_name='Action')), + ('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', + validators=[django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(100)], + verbose_name='Priority')), + ('action', models.CharField(default='reject', max_length=64, verbose_name='Action')), ('is_active', models.BooleanField(default=True, verbose_name='Active')), ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), ('users', models.JSONField(verbose_name='User')), ('accounts', models.JSONField(verbose_name='Account')), ('assets', models.JSONField(verbose_name='Asset')), ('commands', models.ManyToManyField(to='acls.CommandGroup', verbose_name='Commands')), - ('reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), + ( + 'reviewers', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Reviewers')), ], options={ 'verbose_name': 'Command acl', diff --git a/apps/acls/migrations/0007_auto_20221202_1048.py b/apps/acls/migrations/0007_auto_20221202_1048.py new file mode 100644 index 000000000..1a61a4ff4 --- /dev/null +++ b/apps/acls/migrations/0007_auto_20221202_1048.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.14 on 2022-12-02 02:48 + +from django.db import migrations + + +def migrate_login_type(apps, schema_editor): + login_asset_model = apps.get_model('acls', 'LoginAssetACL') + login_asset_model.objects.filter(action='login_confirm').update(action='review') + + login_system_model = apps.get_model('acls', 'LoginACL') + login_system_model.objects.filter(action='confirm').update(action='review') + + +class Migration(migrations.Migration): + dependencies = [ + ('acls', '0006_commandfilteracl_commandgroup'), + ] + + operations = [ + migrations.RunPython(migrate_login_type), + ] diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 6bda02df8..33e1fbc2a 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -1,16 +1,18 @@ -from django.db import models -from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator +from django.db import models +from django.db.models import Q +from django.utils.translation import ugettext_lazy as _ + from common.mixins import CommonModelMixin +from common.utils import contains_ip - -__all__ = ['BaseACL', 'BaseACLQuerySet', 'ACLManager'] +__all__ = ['BaseACL', 'BaseACLQuerySet', 'ACLManager', 'AssetAccountUserACLQuerySet'] class ActionChoices(models.TextChoices): reject = 'reject', _('Reject') - allow = 'allow', _('Allow') - confirm = 'confirm', _('Confirm') + accept = 'allow', _('Allow') + review = 'review', _('Review') class BaseACLQuerySet(models.QuerySet): @@ -27,6 +29,32 @@ class BaseACLQuerySet(models.QuerySet): return self.inactive() +class AssetAccountUserACLQuerySet(BaseACLQuerySet): + def filter_user(self, user): + return self.filter( + Q(users__username_group__contains=user.username) | + Q(users__username_group__contains='*') + ) + + def filter_asset(self, asset): + queryset = self.filter( + Q(assets__name_group__contains=asset.name) | + Q(assets__name_group__contains='*') + ) + ids = [ + q.id for q in queryset + if contains_ip(asset.address, q.assets.get('address_group', [])) + ] + queryset = self.filter(id__in=ids) + return queryset + + def filter_account(self, account_username): + return self.filter( + Q(accounts__username_group__contains=account_username) | + Q(accounts__username_group__contains='*') + ) + + class ACLManager(models.Manager): def valid(self): return self.get_queryset().valid() @@ -39,10 +67,7 @@ class BaseACL(CommonModelMixin): help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)] ) - action = models.CharField( - max_length=64, verbose_name=_('Action'), - choices=ActionChoices.choices, default=ActionChoices.reject - ) + action = models.CharField(max_length=64, default=ActionChoices.reject, verbose_name=_('Action')) reviewers = models.ManyToManyField('users.User', blank=True, verbose_name=_("Reviewers")) is_active = models.BooleanField(default=True, verbose_name=_("Active")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 4fba67a3d..056d9b4f6 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -6,11 +6,11 @@ from django.db import models from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from users.models import User, UserGroup -from orgs.mixins.models import JMSOrgBaseModel from common.utils import lazyproperty, get_logger, get_object_or_none +from orgs.mixins.models import JMSOrgBaseModel from orgs.mixins.models import OrgModelMixin -from .base import BaseACL +from users.models import User, UserGroup +from .base import BaseACL, AssetAccountUserACLQuerySet, ACLManager logger = get_logger(__file__) @@ -50,7 +50,6 @@ class CommandGroup(JMSOrgBaseModel): if ' ' in _cmd: regex.append(cmd) continue - if not cmd: continue @@ -89,6 +88,19 @@ class CommandGroup(JMSOrgBaseModel): def __str__(self): return '{} % {}'.format(self.type, self.content) + +class CommandFilterACL(OrgModelMixin, BaseACL): + users = models.JSONField(verbose_name=_('User')) + assets = models.JSONField(verbose_name=_('Asset')) + accounts = models.JSONField(verbose_name=_('Account')) + commands = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) + objects = ACLManager.from_queryset(AssetAccountUserACLQuerySet)() + + class Meta: + unique_together = ('name', 'org_id') + ordering = ('priority', '-date_updated', 'name') + verbose_name = _('Command acl') + def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): from tickets.const import TicketType from tickets.models import ApplyCommandTicket @@ -147,16 +159,3 @@ class CommandGroup(JMSOrgBaseModel): else: rules = cls.objects.none() return rules - - -class CommandFilterACL(OrgModelMixin, BaseACL): - # 条件 - users = models.JSONField(verbose_name=_('User')) - accounts = models.JSONField(verbose_name=_('Account')) - assets = models.JSONField(verbose_name=_('Asset')) - commands = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) - - class Meta: - unique_together = ('name', 'org_id') - ordering = ('priority', '-date_updated', 'name') - verbose_name = _('Command acl') diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index de9897c7b..6af48faab 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -1,35 +1,8 @@ from django.db import models -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgModelMixin, OrgManager -from .base import BaseACL, BaseACLQuerySet, ACLManager -from common.utils.ip import contains_ip - -class ACLQuerySet(BaseACLQuerySet): - def filter_user(self, user): - return self.filter( - Q(users__username_group__contains=user.username) | - Q(users__username_group__contains='*') - ) - - def filter_asset(self, asset): - queryset = self.filter( - Q(assets__name_group__contains=asset.name) | - Q(assets__name_group__contains='*') - ) - ids = [ - q.id for q in queryset - if contains_ip(asset.address, q.assets.get('address_group', [])) - ] - queryset = LoginAssetACL.objects.filter(id__in=ids) - return queryset - - def filter_account(self, account_username): - return self.filter( - Q(accounts__username_group__contains=account_username) | - Q(accounts__username_group__contains='*') - ) +from orgs.mixins.models import OrgModelMixin +from .base import BaseACL, ACLManager, AssetAccountUserACLQuerySet class LoginAssetACL(BaseACL, OrgModelMixin): @@ -38,7 +11,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin): accounts = models.JSONField(verbose_name=_('Account')) assets = models.JSONField(verbose_name=_('Asset')) - objects = ACLManager.from_queryset(ACLQuerySet)() + objects = ACLManager.from_queryset(AssetAccountUserACLQuerySet)() class Meta: unique_together = ('name', 'org_id') @@ -65,4 +38,3 @@ class LoginAssetACL(BaseACL, OrgModelMixin): ticket = ApplyLoginAssetTicket.objects.create(**data) ticket.open_by_system(assignees) return ticket - diff --git a/apps/acls/serializers/base.py b/apps/acls/serializers/base.py new file mode 100644 index 000000000..9f511cd06 --- /dev/null +++ b/apps/acls/serializers/base.py @@ -0,0 +1,94 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from acls.models.base import ActionChoices +from common.drf.fields import LabeledChoiceField, ObjectRelatedField +from orgs.models import Organization +from users.models import User + +common_help_text = _( + "Format for comma-delimited string, with * indicating a match all. " +) + + +class ACLUsersSerializer(serializers.Serializer): + username_group = serializers.ListField( + default=["*"], + child=serializers.CharField(max_length=128), + label=_("Username"), + help_text=common_help_text, + ) + + +class ACLAssestsSerializer(serializers.Serializer): + address_group_help_text = _( + "Format for comma-delimited string, with * indicating a match all. " + "Such as: " + "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" + " (Domain name support)" + ) + + name_group = serializers.ListField( + default=["*"], + child=serializers.CharField(max_length=128), + label=_("Name"), + help_text=common_help_text, + ) + address_group = serializers.ListField( + default=["*"], + child=serializers.CharField(max_length=1024), + label=_("IP/Host"), + help_text=address_group_help_text, + ) + + +class ACLAccountsSerializer(serializers.Serializer): + username_group = serializers.ListField( + default=["*"], + child=serializers.CharField(max_length=128), + label=_("Username"), + help_text=common_help_text, + ) + + +class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer): + users = ACLUsersSerializer() + assets = ACLAssestsSerializer() + accounts = ACLAccountsSerializer() + reviewers = ObjectRelatedField( + queryset=User.objects, many=True, required=False, label=_('Reviewers') + ) + reviewers_amount = serializers.IntegerField(read_only=True, source="reviewers.count") + action = LabeledChoiceField( + choices=ActionChoices.choices, label=_("Action") + ) + + class Meta: + fields_mini = ["id", "name"] + fields_small = fields_mini + [ + "users", "accounts", "assets", "is_active", + "date_created", "date_updated", "priority", + "action", "comment", "created_by", "org_id", + ] + fields_m2m = ["reviewers", "reviewers_amount"] + fields = fields_small + fields_m2m + extra_kwargs = { + "reviewers": {"allow_null": False, "required": True}, + "priority": {"default": 50}, + "is_active": {"default": True}, + } + + def validate_reviewers(self, reviewers): + org_id = self.fields["org_id"].default() + org = Organization.get_instance(org_id) + if not org: + error = _("The organization `{}` does not exist".format(org_id)) + raise serializers.ValidationError(error) + users = org.get_members() + valid_reviewers = list(set(reviewers) & set(users)) + if not valid_reviewers: + error = _( + "None of the reviewers belong to Organization `{}`".format(org.name) + ) + raise serializers.ValidationError(error) + return valid_reviewers diff --git a/apps/acls/serializers/command_filter.py b/apps/acls/serializers/command_filter.py index e69de29bb..a6b090c42 100644 --- a/apps/acls/serializers/command_filter.py +++ b/apps/acls/serializers/command_filter.py @@ -0,0 +1,16 @@ +from django.utils.translation import ugettext_lazy as _ + +from acls.models import CommandGroup, CommandFilterACL +from common.drf.fields import ObjectRelatedField +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from .base import BaseUserAssetAccountACLSerializerMixin + +__all__ = ["CommandFilterACLSerializer"] + + +class CommandFilterACLSerializer(BaseUserAssetAccountACLSerializerMixin, BulkOrgResourceModelSerializer): + commands = ObjectRelatedField(queryset=CommandGroup.objects, many=True, required=False, label=_('Commands')) + + class Meta(BaseUserAssetAccountACLSerializerMixin.Meta): + model = CommandFilterACL + fields = BaseUserAssetAccountACLSerializerMixin.Meta.fields + ['commands'] diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index 6e3e6bc50..b360bb55f 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -1,109 +1,11 @@ -from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ - -from common.drf.fields import LabeledChoiceField -from common.drf.fields import ObjectRelatedField from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from orgs.models import Organization -from users.models import User -from acls import models +from .base import BaseUserAssetAccountACLSerializerMixin +from ..models import LoginAssetACL __all__ = ["LoginAssetACLSerializer"] -common_help_text = _( - "Format for comma-delimited string, with * indicating a match all. " -) - - -class LoginAssetACLUsersSerializer(serializers.Serializer): - username_group = serializers.ListField( - default=["*"], - child=serializers.CharField(max_length=128), - label=_("Username"), - help_text=common_help_text, - ) - - -class LoginAssetACLAssestsSerializer(serializers.Serializer): - address_group_help_text = _( - "Format for comma-delimited string, with * indicating a match all. " - "Such as: " - "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" - " (Domain name support)" - ) - - name_group = serializers.ListField( - default=["*"], - child=serializers.CharField(max_length=128), - label=_("Name"), - help_text=common_help_text, - ) - address_group = serializers.ListField( - default=["*"], - child=serializers.CharField(max_length=1024), - label=_("IP/Host"), - help_text=address_group_help_text, - ) - - -class LoginAssetACLAccountsSerializer(serializers.Serializer): - username_group = serializers.ListField( - default=["*"], - child=serializers.CharField(max_length=128), - label=_("Username"), - help_text=common_help_text, - ) - - -class LoginAssetACLSerializer(BulkOrgResourceModelSerializer): - users = LoginAssetACLUsersSerializer() - assets = LoginAssetACLAssestsSerializer() - accounts = LoginAssetACLAccountsSerializer() - reviewers = ObjectRelatedField( - queryset=User.objects, many=True, required=False, label=_('Reviewers') - ) - reviewers_amount = serializers.IntegerField(read_only=True, source="reviewers.count") - action = LabeledChoiceField( - choices=models.LoginAssetACL.ActionChoices.choices, label=_("Action") - ) - - class Meta: - model = models.LoginAssetACL - fields_mini = ["id", "name"] - fields_small = fields_mini + [ - "users", - "accounts", - "assets", - "is_active", - "date_created", - "date_updated", - "priority", - "action", - "comment", - "created_by", - "org_id", - ] - fields_m2m = ["reviewers", "reviewers_amount"] - fields = fields_small + fields_m2m - extra_kwargs = { - "reviewers": {"allow_null": False, "required": True}, - "priority": {"default": 50}, - "is_active": {"default": True}, - } - - def validate_reviewers(self, reviewers): - org_id = self.fields["org_id"].default() - org = Organization.get_instance(org_id) - if not org: - error = _("The organization `{}` does not exist".format(org_id)) - raise serializers.ValidationError(error) - users = org.get_members() - valid_reviewers = list(set(reviewers) & set(users)) - if not valid_reviewers: - error = _( - "None of the reviewers belong to Organization `{}`".format(org.name) - ) - raise serializers.ValidationError(error) - return valid_reviewers +class LoginAssetACLSerializer(BaseUserAssetAccountACLSerializerMixin, BulkOrgResourceModelSerializer): + class Meta(BaseUserAssetAccountACLSerializerMixin.Meta): + model = LoginAssetACL diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 5b9b1ad85..5f4adebcf 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -1,17 +1,13 @@ # -*- coding: utf-8 -*- # -import re import uuid -from django.db import models -from django.db.models import Q from django.core.validators import MinValueValidator, MaxValueValidator +from django.db import models from django.utils.translation import ugettext_lazy as _ -from users.models import User, UserGroup +from common.utils import get_logger from orgs.mixins.models import OrgModelMixin -from common.utils import lazyproperty, get_logger, get_object_or_none -from ..models import Asset, Account logger = get_logger(__file__) @@ -93,125 +89,3 @@ class CommandFilterRule(OrgModelMixin): class Meta: ordering = ('priority', 'action') verbose_name = _("Command filter rule") - - @lazyproperty - def pattern(self): - if self.type == 'command': - s = self.construct_command_regex(content=self.content) - else: - s = r'{0}'.format(self.content) - return s - - @classmethod - def construct_command_regex(cls, content): - regex = [] - content = content.replace('\r\n', '\n') - for _cmd in content.split('\n'): - cmd = re.sub(r'\s+', ' ', _cmd) - cmd = re.escape(cmd) - cmd = cmd.replace('\\ ', '\s+') - - # 有空格就不能 铆钉单词了 - if ' ' in _cmd: - regex.append(cmd) - continue - - if not cmd: - continue - - # 如果是单个字符 - if cmd[-1].isalpha(): - regex.append(r'\b{0}\b'.format(cmd)) - else: - regex.append(r'\b{0}'.format(cmd)) - s = r'{}'.format('|'.join(regex)) - return s - - @staticmethod - def compile_regex(regex, ignore_case): - try: - if ignore_case: - pattern = re.compile(regex, re.IGNORECASE) - else: - pattern = re.compile(regex) - except Exception as e: - error = _('The generated regular expression is incorrect: {}').format(str(e)) - logger.error(error) - return False, error, None - return True, '', pattern - - def match(self, data): - succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case) - if not succeed: - return self.ACTION_UNKNOWN, '' - - found = pattern.search(data) - if not found: - return self.ACTION_UNKNOWN, '' - - if self.action == self.ActionChoices.allow: - return self.ActionChoices.allow, found.group() - else: - return self.ActionChoices.deny, found.group() - - def __str__(self): - return '{} % {}'.format(self.type, self.content) - - def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): - from tickets.const import TicketType - from tickets.models import ApplyCommandTicket - data = { - 'title': _('Command confirm') + ' ({})'.format(session.user), - 'type': TicketType.command_confirm, - 'applicant': session.user_obj, - 'apply_run_user_id': session.user_id, - 'apply_run_asset': str(session.asset), - 'apply_run_account': str(session.account), - 'apply_run_command': run_command[:4090], - 'apply_from_session_id': str(session.id), - 'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id), - 'apply_from_cmd_filter_id': str(cmd_filter_rule.filter.id), - 'org_id': org_id, - } - ticket = ApplyCommandTicket.objects.create(**data) - assignees = self.reviewers.all() - ticket.open_by_system(assignees) - return ticket - - @classmethod - def get_queryset( - cls, user_id=None, user_group_id=None, account=None, - asset_id=None, org_id=None - ): - from assets.models import Account - user_groups = [] - user = get_object_or_none(User, pk=user_id) - if user: - user_groups.extend(list(user.groups.all())) - user_group = get_object_or_none(UserGroup, pk=user_group_id) - if user_group: - org_id = user_group.org_id - user_groups.append(user_group) - - asset = get_object_or_none(Asset, pk=asset_id) - q = Q() - if user: - q |= Q(users=user) - if user_groups: - q |= Q(user_groups__in=set(user_groups)) - if account: - org_id = account.org_id - q |= Q(accounts__contains=account.username) | \ - Q(accounts__contains=Account.AliasAccount.ALL) - if asset: - org_id = asset.org_id - q |= Q(assets=asset) - if q: - cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True) - if org_id: - cmd_filters = cmd_filters.filter(org_id=org_id) - rule_ids = cmd_filters.values_list('rules', flat=True) - rules = cls.objects.filter(id__in=rule_ids) - else: - rules = cls.objects.none() - return rules diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 7341b4bd1..ec6d2e98d 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -1,25 +1,25 @@ # -*- coding: utf-8 -*- # import inspect -from functools import partial import time +from functools import partial from typing import Callable -from django.utils.http import urlencode -from django.core.cache import cache from django.conf import settings from django.contrib import auth -from django.utils.translation import ugettext as _ -from rest_framework.request import Request from django.contrib.auth import ( BACKEND_SESSION_KEY, load_backend, PermissionDenied, user_login_failed, _clean_credentials, ) +from django.core.cache import cache from django.core.exceptions import ImproperlyConfigured from django.shortcuts import reverse, redirect, get_object_or_404 +from django.utils.http import urlencode +from django.utils.translation import ugettext as _ +from rest_framework.request import Request -from common.utils import get_request_ip, get_logger, bulk_get, FlashMessageUtil from acls.models import LoginACL +from common.utils import get_request_ip, get_logger, bulk_get, FlashMessageUtil from users.models import User from users.utils import LoginBlockUtil, MFABlockUtils, LoginIpBlockUtil from . import errors From 19c3f98e8fbc4d333180551051c25d171f628d2b Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 11:14:29 +0800 Subject: [PATCH 453/488] =?UTF-8?q?pref:=20=E4=BF=AE=E6=94=B9=20migrations?= =?UTF-8?q?=20=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/migrations/0016_alter_userloginlog_type.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/audits/migrations/0016_alter_userloginlog_type.py b/apps/audits/migrations/0016_alter_userloginlog_type.py index 86145721d..1047a4a30 100644 --- a/apps/audits/migrations/0016_alter_userloginlog_type.py +++ b/apps/audits/migrations/0016_alter_userloginlog_type.py @@ -4,15 +4,17 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('audits', '0015_auto_20221011_1745'), + ('audits', '0015_auto_20221111_1919'), ] operations = [ migrations.AlterField( model_name='userloginlog', name='type', - field=models.CharField(choices=[('web_cli', 'Web Client'), ('web_gui', 'Web GUI'), ('db_cli', 'DB Client'), ('db_gui', 'DB GUI'), ('rdp_cli', 'RDP Client'), ('rdp_file', 'RDP File'), ('ssh_cli', 'SSH Client'), ('web_sftp', 'Web SFTP')], max_length=128, verbose_name='Login type'), + field=models.CharField(choices=[('web_cli', 'Web Client'), ('web_gui', 'Web GUI'), ('db_cli', 'DB Client'), + ('db_gui', 'DB GUI'), ('rdp_cli', 'RDP Client'), ('rdp_file', 'RDP File'), + ('ssh_cli', 'SSH Client'), ('web_sftp', 'Web SFTP')], max_length=128, + verbose_name='Login type'), ), ] From 541358978d1e66449b1577996fae6dc468181fdd Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:45:05 +0800 Subject: [PATCH 454/488] fix: gateway (#9145) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/domain.py | 5 +- apps/assets/serializers/domain.py | 108 ++---------------------------- 2 files changed, 9 insertions(+), 104 deletions(-) diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 948bb6a7b..954f4842c 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -1,5 +1,4 @@ # ~*~ coding: utf-8 ~*~ -from django.db.models import F from django.views.generic.detail import SingleObjectMixin from django.utils.translation import ugettext as _ from rest_framework.views import APIView, Response @@ -7,7 +6,7 @@ from rest_framework.serializers import ValidationError from common.utils import get_logger from orgs.mixins.api import OrgBulkModelViewSet -from ..models import Domain, Host +from ..models import Domain, Gateway from .. import serializers logger = get_logger(__file__) @@ -29,7 +28,7 @@ class DomainViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet): - perm_model = Host + perm_model = Gateway filterset_fields = ("domain__name", "name", "domain") search_fields = ("domain__name",) serializer_class = serializers.GatewaySerializer diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index f17aa19ee..8ee651e88 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers -from rest_framework.generics import get_object_or_404 from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from common.drf.serializers import SecretReadableMixin, WritableNestedModelSerializer -from common.drf.fields import ObjectRelatedField, EncryptedField -from assets.const import SecretType, GATEWAY_NAME -from ..serializers import AssetProtocolsSerializer -from ..models import Platform, Domain, Node, Asset, Account, Host -from .utils import validate_password_for_ansible, validate_ssh_key +from common.drf.serializers import SecretReadableMixin +from common.drf.fields import ObjectRelatedField +from ..serializers import HostSerializer +from ..models import Domain, Gateway, Asset class DomainSerializer(BulkOrgResourceModelSerializer): @@ -41,100 +38,9 @@ class DomainSerializer(BulkOrgResourceModelSerializer): return obj.gateways.count() -class GatewaySerializer(BulkOrgResourceModelSerializer, WritableNestedModelSerializer): - password = EncryptedField( - label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, - validators=[validate_password_for_ansible], write_only=True - ) - private_key = EncryptedField( - label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, - max_length=16384, write_only=True - ) - passphrase = serializers.CharField( - label=_('Key password'), allow_blank=True, allow_null=True, required=False, write_only=True, - max_length=512, - ) - username = serializers.CharField( - label=_('Username'), allow_blank=True, max_length=128, required=True, write_only=True - ) - username_display = serializers.SerializerMethodField(label=_('Username')) - protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) - - class Meta: - model = Host - fields_mini = ['id', 'name', 'address'] - fields_small = fields_mini + ['is_active', 'comment'] - fields = fields_small + ['domain', 'protocols'] + [ - 'username', 'password', 'private_key', 'passphrase', 'username_display' - ] - extra_kwargs = { - 'name': {'label': _("Name")}, - 'address': {'label': _('Address')}, - } - - @staticmethod - def get_username_display(obj): - account = obj.accounts.order_by('-privileged').first() - return account.username if account else '' - - def validate_private_key(self, secret): - if not secret: - return - passphrase = self.initial_data.get('passphrase') - passphrase = passphrase if passphrase else None - validate_ssh_key(secret, passphrase) - return secret - - @staticmethod - def clean_auth_fields(validated_data): - username = validated_data.pop('username', None) - password = validated_data.pop('password', None) - private_key = validated_data.pop('private_key', None) - validated_data.pop('passphrase', None) - return username, password, private_key - - @staticmethod - def create_accounts(instance, username, password, private_key): - account_name = f'{instance.name}-{_("Gateway")}' - account_data = { - 'privileged': True, - 'name': account_name, - 'username': username, - 'asset_id': instance.id, - 'created_by': instance.created_by - } - if password: - Account.objects.create( - **account_data, secret=password, secret_type=SecretType.PASSWORD - ) - if private_key: - Account.objects.create( - **account_data, secret=private_key, secret_type=SecretType.SSH_KEY - ) - - @staticmethod - def update_accounts(instance, username, password, private_key): - accounts = instance.accounts.filter(username=username) - if password: - account = get_object_or_404(accounts, SecretType.PASSWORD) - account.secret = password - account.save() - if private_key: - account = get_object_or_404(accounts, SecretType.SSH_KEY) - account.secret = private_key - account.save() - - def create(self, validated_data): - auth_fields = self.clean_auth_fields(validated_data) - instance = super().create(validated_data) - self.create_accounts(instance, *auth_fields) - return instance - - def update(self, instance, validated_data): - auth_fields = self.clean_auth_fields(validated_data) - instance = super().update(instance, validated_data) - self.update_accounts(instance, *auth_fields) - return instance +class GatewaySerializer(HostSerializer): + class Meta(HostSerializer.Meta): + model = Gateway class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): From a6aafaec05189a1354a198acdf4fa1ed07e2163d Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 11:53:07 +0800 Subject: [PATCH 455/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20=20command?= =?UTF-8?q?=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/__init__.py | 1 + apps/acls/api/command_acl.py | 10 +++++++-- apps/acls/serializers/__init__.py | 1 + apps/acls/serializers/command_filter.py | 16 +++++++++----- apps/acls/serializers/login_acl.py | 28 ++++++++---------------- apps/acls/serializers/login_asset_acl.py | 6 ++--- apps/acls/urls/api_urls.py | 4 ++-- 7 files changed, 35 insertions(+), 31 deletions(-) diff --git a/apps/acls/api/__init__.py b/apps/acls/api/__init__.py index ff52a1ce9..2f720effe 100644 --- a/apps/acls/api/__init__.py +++ b/apps/acls/api/__init__.py @@ -1,3 +1,4 @@ +from .command_acl import * from .login_acl import * from .login_asset_acl import * from .login_asset_check import * diff --git a/apps/acls/api/command_acl.py b/apps/acls/api/command_acl.py index 563e0c1a2..bb9a5482e 100644 --- a/apps/acls/api/command_acl.py +++ b/apps/acls/api/command_acl.py @@ -1,12 +1,18 @@ from orgs.mixins.api import OrgBulkModelViewSet from .. import models, serializers - __all__ = ['CommandFilterACLViewSet'] +class CommandGroupViewSet(OrgBulkModelViewSet): + model = models.CommandGroup + filterset_fields = ('name',) + search_fields = filterset_fields + serializer_class = serializers.CommandGroupSerializer + + class CommandFilterACLViewSet(OrgBulkModelViewSet): model = models.CommandFilterACL - filterset_fields = ('name', ) + filterset_fields = ('name',) search_fields = filterset_fields serializer_class = serializers.LoginAssetACLSerializer diff --git a/apps/acls/serializers/__init__.py b/apps/acls/serializers/__init__.py index ff52a1ce9..465474c4f 100644 --- a/apps/acls/serializers/__init__.py +++ b/apps/acls/serializers/__init__.py @@ -1,3 +1,4 @@ +from .command_filter import * from .login_acl import * from .login_asset_acl import * from .login_asset_check import * diff --git a/apps/acls/serializers/command_filter.py b/apps/acls/serializers/command_filter.py index a6b090c42..934d38198 100644 --- a/apps/acls/serializers/command_filter.py +++ b/apps/acls/serializers/command_filter.py @@ -3,14 +3,20 @@ from django.utils.translation import ugettext_lazy as _ from acls.models import CommandGroup, CommandFilterACL from common.drf.fields import ObjectRelatedField from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from .base import BaseUserAssetAccountACLSerializerMixin +from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer -__all__ = ["CommandFilterACLSerializer"] +__all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer"] -class CommandFilterACLSerializer(BaseUserAssetAccountACLSerializerMixin, BulkOrgResourceModelSerializer): +class CommandGroupSerializer(BulkOrgResourceModelSerializer): + class Meta: + model = CommandGroup + fields = ['id', 'name', 'type', 'content', 'comment'] + + +class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer): commands = ObjectRelatedField(queryset=CommandGroup.objects, many=True, required=False, label=_('Commands')) - class Meta(BaseUserAssetAccountACLSerializerMixin.Meta): + class Meta(BaseSerializer.Meta): model = CommandFilterACL - fields = BaseUserAssetAccountACLSerializerMixin.Meta.fields + ['commands'] + fields = BaseSerializer.Meta.fields + ['commands'] diff --git a/apps/acls/serializers/login_acl.py b/apps/acls/serializers/login_acl.py index f759da435..db89445c8 100644 --- a/apps/acls/serializers/login_acl.py +++ b/apps/acls/serializers/login_acl.py @@ -1,12 +1,12 @@ from django.utils.translation import ugettext as _ from rest_framework import serializers -from common.drf.serializers import BulkModelSerializer -from common.drf.serializers import MethodSerializer -from common.drf.fields import ObjectRelatedField + +from common.drf.fields import ObjectRelatedField, LabeledChoiceField +from common.drf.serializers import BulkModelSerializer, MethodSerializer from jumpserver.utils import has_valid_xpack_license from users.models import User -from ..models import LoginACL from .rules import RuleSerializer +from ..models import LoginACL __all__ = [ "LoginACLSerializer", @@ -22,9 +22,7 @@ class LoginACLSerializer(BulkModelSerializer): reviewers = ObjectRelatedField( queryset=User.objects, label=_("Reviewers"), many=True, required=False ) - action_display = serializers.ReadOnlyField( - source="get_action_display", label=_("Action") - ) + action = LabeledChoiceField(choices=LoginACL.ActionChoices.choices) reviewers_amount = serializers.IntegerField( read_only=True, source="reviewers.count" ) @@ -34,17 +32,9 @@ class LoginACLSerializer(BulkModelSerializer): model = LoginACL fields_mini = ["id", "name"] fields_small = fields_mini + [ - "priority", - "rules", - "action", - "action_display", - "is_active", - "user", - "date_created", - "date_updated", - "reviewers_amount", - "comment", - "created_by", + "priority", "user", "rules", "action", + "is_active", "date_created", "date_updated", + "reviewers_amount", "comment", "created_by", ] fields_fk = ["user"] fields_m2m = ["reviewers"] @@ -65,7 +55,7 @@ class LoginACLSerializer(BulkModelSerializer): return choices = action._choices if not has_valid_xpack_license(): - choices.pop(LoginACL.ActionChoices.confirm, None) + choices.pop(LoginACL.ActionChoices.review, None) action._choices = choices def get_rules_serializer(self): diff --git a/apps/acls/serializers/login_asset_acl.py b/apps/acls/serializers/login_asset_acl.py index b360bb55f..de160d124 100644 --- a/apps/acls/serializers/login_asset_acl.py +++ b/apps/acls/serializers/login_asset_acl.py @@ -1,11 +1,11 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from .base import BaseUserAssetAccountACLSerializerMixin +from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer from ..models import LoginAssetACL __all__ = ["LoginAssetACLSerializer"] -class LoginAssetACLSerializer(BaseUserAssetAccountACLSerializerMixin, BulkOrgResourceModelSerializer): - class Meta(BaseUserAssetAccountACLSerializerMixin.Meta): +class LoginAssetACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer): + class Meta(BaseSerializer.Meta): model = LoginAssetACL diff --git a/apps/acls/urls/api_urls.py b/apps/acls/urls/api_urls.py index c4040ff45..742082022 100644 --- a/apps/acls/urls/api_urls.py +++ b/apps/acls/urls/api_urls.py @@ -1,14 +1,14 @@ from django.urls import path from rest_framework_bulk.routes import BulkRouter + from .. import api - app_name = 'acls' - router = BulkRouter() router.register(r'login-acls', api.LoginACLViewSet, 'login-acl') router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl') +router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl') urlpatterns = [ path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'), From 61e6ab20a2e8f46981c4f5e11cdb84ae30b2c4f5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 12:27:26 +0800 Subject: [PATCH 456/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20Connect=20?= =?UTF-8?q?acl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/command_acl.py | 2 +- apps/acls/migrations/0004_connectacl.py | 40 ------------------- .../migrations/0008_commandgroup_comment.py | 18 +++++++++ apps/acls/models/command_acl.py | 1 + apps/acls/urls/api_urls.py | 1 + .../0016_alter_userloginlog_type.py | 20 ---------- 6 files changed, 21 insertions(+), 61 deletions(-) delete mode 100644 apps/acls/migrations/0004_connectacl.py create mode 100644 apps/acls/migrations/0008_commandgroup_comment.py delete mode 100644 apps/audits/migrations/0016_alter_userloginlog_type.py diff --git a/apps/acls/api/command_acl.py b/apps/acls/api/command_acl.py index bb9a5482e..b717a464f 100644 --- a/apps/acls/api/command_acl.py +++ b/apps/acls/api/command_acl.py @@ -1,7 +1,7 @@ from orgs.mixins.api import OrgBulkModelViewSet from .. import models, serializers -__all__ = ['CommandFilterACLViewSet'] +__all__ = ['CommandFilterACLViewSet', 'CommandGroupViewSet'] class CommandGroupViewSet(OrgBulkModelViewSet): diff --git a/apps/acls/migrations/0004_connectacl.py b/apps/acls/migrations/0004_connectacl.py deleted file mode 100644 index 3b640d1f3..000000000 --- a/apps/acls/migrations/0004_connectacl.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-30 02:46 - -from django.conf import settings -import django.core.validators -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0040_alter_user_source'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('acls', '0003_auto_20211130_1037'), - ] - - operations = [ - migrations.CreateModel( - name='ConnectACL', - fields=[ - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('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')), - ('name', models.CharField(max_length=128, verbose_name='Name')), - ('priority', models.IntegerField(default=50, help_text='1-100, the lower the value will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')), - ('is_active', models.BooleanField(default=True, verbose_name='Active')), - ('comment', models.TextField(blank=True, default='', verbose_name='Comment')), - ('rules', models.JSONField(default=list, verbose_name='Rule')), - ('action', models.CharField(choices=[('reject', 'Reject'), ('allow', 'Allow')], default='reject', max_length=64, verbose_name='Action')), - ('user_groups', models.ManyToManyField(blank=True, related_name='connect_acls', to='users.UserGroup', verbose_name='User group')), - ('users', models.ManyToManyField(blank=True, related_name='connect_acls', to=settings.AUTH_USER_MODEL, verbose_name='User')), - ], - options={ - 'verbose_name': 'Connect acl', - 'ordering': ('priority', '-date_updated', 'name'), - }, - ), - ] diff --git a/apps/acls/migrations/0008_commandgroup_comment.py b/apps/acls/migrations/0008_commandgroup_comment.py new file mode 100644 index 000000000..631ff8eb7 --- /dev/null +++ b/apps/acls/migrations/0008_commandgroup_comment.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2022-12-02 04:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0007_auto_20221202_1048'), + ] + + operations = [ + migrations.AddField( + model_name='commandgroup', + name='comment', + field=models.TextField(blank=True, verbose_name='Comment'), + ), + ] diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 056d9b4f6..bec473250 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -24,6 +24,7 @@ class CommandGroup(JMSOrgBaseModel): type = models.CharField(max_length=16, default=Type.command, choices=Type.choices, verbose_name=_("Type")) content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case')) + comment = models.TextField(blank=True, verbose_name=_("Comment")) class Meta: unique_together = [('org_id', 'name')] diff --git a/apps/acls/urls/api_urls.py b/apps/acls/urls/api_urls.py index 742082022..0185278d9 100644 --- a/apps/acls/urls/api_urls.py +++ b/apps/acls/urls/api_urls.py @@ -8,6 +8,7 @@ app_name = 'acls' router = BulkRouter() router.register(r'login-acls', api.LoginACLViewSet, 'login-acl') router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl') +router.register(r'command-groups', api.CommandGroupViewSet, 'command-group') router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl') urlpatterns = [ diff --git a/apps/audits/migrations/0016_alter_userloginlog_type.py b/apps/audits/migrations/0016_alter_userloginlog_type.py deleted file mode 100644 index 1047a4a30..000000000 --- a/apps/audits/migrations/0016_alter_userloginlog_type.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 3.2.16 on 2022-11-30 07:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('audits', '0015_auto_20221111_1919'), - ] - - operations = [ - migrations.AlterField( - model_name='userloginlog', - name='type', - field=models.CharField(choices=[('web_cli', 'Web Client'), ('web_gui', 'Web GUI'), ('db_cli', 'DB Client'), - ('db_gui', 'DB GUI'), ('rdp_cli', 'RDP Client'), ('rdp_file', 'RDP File'), - ('ssh_cli', 'SSH Client'), ('web_sftp', 'Web SFTP')], max_length=128, - verbose_name='Login type'), - ), - ] From 158d49b230a1a86006ea3bc5c77054d8b76ba433 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 2 Dec 2022 12:40:29 +0800 Subject: [PATCH 457/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=E7=9A=84=E8=B5=84=E4=BA=A7=20API=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20id=20=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 04da13061..ed8f9e9f8 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -37,7 +37,7 @@ class AssetFilterSet(BaseFilterSet): class Meta: model = Asset - fields = ["name", "address", "is_active", "type", "category", "hostname"] + fields = ["id", "name", "address", "is_active", "type", "category", "hostname"] class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): From faf1dedfe24ddf9ab6468adeb2aa556776fa37d7 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Fri, 2 Dec 2022 13:00:48 +0800 Subject: [PATCH 458/488] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E6=8E=89?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=96=B9=E5=BC=8F=E6=8E=A7=E5=88=B6=E5=8D=8A?= =?UTF-8?q?=E6=88=90=E5=93=81=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/connect_acl.py | 52 ----------- apps/acls/models/connect_acl.py | 120 -------------------------- apps/acls/serializers/connnect_acl.py | 36 -------- 3 files changed, 208 deletions(-) delete mode 100644 apps/acls/api/connect_acl.py delete mode 100644 apps/acls/models/connect_acl.py delete mode 100644 apps/acls/serializers/connnect_acl.py diff --git a/apps/acls/api/connect_acl.py b/apps/acls/api/connect_acl.py deleted file mode 100644 index aeff03ea2..000000000 --- a/apps/acls/api/connect_acl.py +++ /dev/null @@ -1,52 +0,0 @@ -from rest_framework.views import APIView -from rest_framework import status -from django.http.response import JsonResponse -from django.utils.translation import ugettext_lazy as _ - -from common.drf.api import JMSBulkModelViewSet -from common.const.choices import ConnectMethodChoices -from ..models import ConnectACL -from .. import serializers - -__all__ = ['ConnectACLViewSet', 'ConnectMethodsAPI', 'ConnectMethodPermissionsAPI'] - - -class ConnectACLViewSet(JMSBulkModelViewSet): - queryset = ConnectACL.objects.all() - filterset_fields = ('name', ) - search_fields = ('name',) - serializer_class = serializers.ConnectACLSerializer - - -class ConnectMethodsAPI(APIView): - rbac_perms = { - 'GET': 'acls.view_connnectacl', - } - - @staticmethod - def get(request, *args, **kwargs): - data = [] - for m in ConnectMethodChoices.choices: - data.append({'label': m[1], 'value': m[0]}) - return JsonResponse(data, safe=False) - - -class ConnectMethodPermissionsAPI(APIView): - rbac_perms = { - 'GET': 'acls.view_connnectacl', - } - - @staticmethod - def get(request, *args, **kwargs): - login_type = request.query_params.get('login_type') - if not login_type: - rules = ConnectACL().all_rules(request.user) - return JsonResponse({'rules': rules}) - - acl = ConnectACL.match(request.user, login_type) - if acl: - err = _('The current user is not allowed to login in this way') - return JsonResponse({'error': err}) - else: - return JsonResponse({'msg': 'ok'}) - diff --git a/apps/acls/models/connect_acl.py b/apps/acls/models/connect_acl.py deleted file mode 100644 index a936f3cb4..000000000 --- a/apps/acls/models/connect_acl.py +++ /dev/null @@ -1,120 +0,0 @@ -from django.db import models -from django.core.cache import cache -from django.utils.translation import ugettext_lazy as _ - -from common.utils.connection import get_redis_client -from common.const.choices import ConnectMethodChoices -from orgs.mixins.models import OrgManager, OrgModelMixin -from .base import BaseACL, BaseACLQuerySet - - -class ACLManager(OrgManager): - - def valid(self): - return self.get_queryset().valid() - - -class ConnectACL(BaseACL, OrgModelMixin): - ConnectACLUserCacheKey = 'CONNECT_ACL_USER_{}' - ConnectACLUserCacheTTL = 600 - - class ActionChoices(models.TextChoices): - reject = 'reject', _('Reject') - - # 用户 - users = models.ManyToManyField( - 'users.User', related_name='connect_acls', blank=True, - verbose_name=_("User") - ) - user_groups = models.ManyToManyField( - 'users.UserGroup', related_name='connect_acls', blank=True, - verbose_name=_("User group"), - ) - rules = models.JSONField(default=list, verbose_name=_('Rule')) - # 动作 - action = models.CharField( - max_length=64, verbose_name=_('Action'), - choices=ActionChoices.choices, default=ActionChoices.reject - ) - - objects = ACLManager.from_queryset(BaseACLQuerySet)() - - class Meta: - ordering = ('priority', '-date_updated', 'name') - verbose_name = _('Connect acl') - - def __str__(self): - return self.name - - @property - def rules_display(self): - return ', '.join( - [ConnectMethodChoices.get_label(i) for i in self.rules] - ) - - def is_action(self, action): - return self.action == action - - @staticmethod - def match(user, connect_type): - if not user: - return - - user_acls = user.connect_acls.all().valid().distinct() - for acl in user_acls: - if connect_type in acl.rules: - return acl - - for user_group in user.groups.all(): - acls = user_group.connect_acls.all().valid().distinct() - for acl in acls: - if connect_type in acl.rules: - return acl - - def _get_all_rules_from_cache(self, user): - find = False - cache_key = self.ConnectACLUserCacheKey.format(user.id) - rules = cache.get(cache_key) - if rules is not None: - find = True - return rules, find - - @staticmethod - def _get_all_rules_from_db(user): - connect_rules = set() - user_acls = user.connect_acls.all().valid() - user_acl_rules = user_acls.values_list('id', 'rules') - for r_id, rule in user_acl_rules: - connect_rules.update(rule) - - for ug in user.groups.all(): - user_group_acls = ug.connect_acls.all().valid() - user_group_rules = user_group_acls.values_list('id', 'rules') - for r_id, rule in user_group_rules: - connect_rules.update(rule) - return list(connect_rules) - - def set_all_rules_to_cache(self, key, rules): - cache.set(key, rules, self.ConnectACLUserCacheTTL) - - def all_rules(self, user): - rules, find = self._get_all_rules_from_cache(user) - if not find: - rules = self._get_all_rules_from_db(user) - self.set_all_rules_to_cache( - self.ConnectACLUserCacheKey.format(user.id), rules - ) - return rules - - def clear_rules_cache(self): - cache.delete_pattern( - self.ConnectACLUserCacheKey.format('*') - ) - - def save(self, *args, **kwargs): - self.clear_rules_cache() - return super().save(*args, **kwargs) - - def delete(self, using=None, keep_parents=False): - self.clear_rules_cache() - return super().delete(using=using, keep_parents=keep_parents) diff --git a/apps/acls/serializers/connnect_acl.py b/apps/acls/serializers/connnect_acl.py deleted file mode 100644 index c4377491f..000000000 --- a/apps/acls/serializers/connnect_acl.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.utils.translation import ugettext as _ -from rest_framework import serializers - -from common.drf.serializers import BulkModelSerializer -from common.const.choices import ConnectMethodChoices -from ..models import ConnectACL - - -__all__ = ['ConnectACLSerializer', ] - - -class ConnectACLSerializer(BulkModelSerializer): - action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) - - class Meta: - model = ConnectACL - fields_mini = ['id', 'name'] - fields_small = fields_mini + [ - 'priority', 'rules', 'rules_display', 'action', 'action_display', 'is_active', - 'date_created', 'date_updated', 'comment', 'created_by' - ] - fields_m2m = ['users', 'user_groups'] - fields = fields_small + fields_m2m - extra_kwargs = { - 'priority': {'default': 50}, - 'is_active': {'default': True} - } - - @staticmethod - def validate_rules(rules): - for r in rules: - label = ConnectMethodChoices.get_label(r) - if not label: - error = _('Invalid connection method: {}').format(r) - raise serializers.ValidationError(error) - return rules From 2d771eedc1859b676d3c1254cb86050ce2714c31 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 13:15:03 +0800 Subject: [PATCH 459/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=8E=88=E6=9D=83=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 5 +- .../{assets/mixin.py => assets.py} | 127 +++++++++--------- .../api/user_permission/assets/__init__.py | 1 - apps/perms/api/user_permission/assets/api.py | 82 ----------- apps/perms/api/user_permission/mixin.py | 38 +++++- apps/perms/urls/user_permission.py | 10 +- 6 files changed, 109 insertions(+), 154 deletions(-) rename apps/perms/api/user_permission/{assets/mixin.py => assets.py} (53%) delete mode 100644 apps/perms/api/user_permission/assets/__init__.py delete mode 100644 apps/perms/api/user_permission/assets/api.py diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 04da13061..72264048a 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -37,7 +37,10 @@ class AssetFilterSet(BaseFilterSet): class Meta: model = Asset - fields = ["name", "address", "is_active", "type", "category", "hostname"] + fields = [ + "id", "name", "address", "is_active", + "type", "category", "hostname" + ] class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): diff --git a/apps/perms/api/user_permission/assets/mixin.py b/apps/perms/api/user_permission/assets.py similarity index 53% rename from apps/perms/api/user_permission/assets/mixin.py rename to apps/perms/api/user_permission/assets.py index 58832cdee..d3ee274aa 100644 --- a/apps/perms/api/user_permission/assets/mixin.py +++ b/apps/perms/api/user_permission/assets.py @@ -1,25 +1,37 @@ -from rest_framework.request import Request -from rest_framework.response import Response +from django.conf import settings +from rest_framework.generics import ListAPIView -from common.utils import get_logger -from users.models import User -from assets.api.asset.asset import AssetFilterSet -from assets.api.mixin import SerializeToTreeNodeMixin from assets.models import Asset, Node +from common.utils import get_logger from perms import serializers -from perms.pagination import NodeGrantedAssetPagination, AllGrantedAssetPagination +from perms.pagination import AllGrantedAssetPagination +from perms.pagination import NodeGrantedAssetPagination from perms.utils.user_permission import UserGrantedAssetsQueryUtils +from .mixin import ( + SelfOrPKUserMixin, RebuildTreeMixin, + PermedAssetSerializerMixin, AssetsTreeFormatMixin +) + +__all__ = [ + 'UserDirectPermedAssetsApi', + 'UserFavoriteAssetsApi', + 'UserDirectPermedAssetsAsTreeApi', + 'UserUngroupAssetsAsTreeApi', + 'UserAllPermedAssetsApi', + 'UserPermedNodeAssetsApi', +] logger = get_logger(__name__) -class UserDirectGrantedAssetsQuerysetMixin: +class UserDirectPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): + """ 直接授权给用户的资产 """ only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - user: User def get_queryset(self): if getattr(self, 'swagger_fake_view', False): return Asset.objects.none() + assets = UserGrantedAssetsQueryUtils(self.user) \ .get_direct_granted_assets() \ .prefetch_related('platform') \ @@ -27,14 +39,49 @@ class UserDirectGrantedAssetsQuerysetMixin: return assets -class UserAllGrantedAssetsQuerysetMixin: +class UserFavoriteAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): + only_fields = serializers.AssetGrantedSerializer.Meta.only_fields + """ 用户收藏的授权资产 """ + + def get_queryset(self): + if getattr(self, 'swagger_fake_view', False): + return Asset.objects.none() + + user = self.user + utils = UserGrantedAssetsQueryUtils(user) + assets = utils.get_favorite_assets() + assets = assets.prefetch_related('platform').only(*self.only_fields) + return assets + + +class UserDirectPermedAssetsAsTreeApi(RebuildTreeMixin, AssetsTreeFormatMixin, UserDirectPermedAssetsApi): + """ 用户直接授权的资产作为树 """ + only_fields = serializers.AssetGrantedSerializer.Meta.only_fields + + def get_queryset(self): + if getattr(self, 'swagger_fake_view', False): + return Asset.objects.none() + + assets = UserGrantedAssetsQueryUtils(self.user) \ + .get_direct_granted_assets() \ + .prefetch_related('platform') \ + .only(*self.only_fields) + return assets + + +class UserUngroupAssetsAsTreeApi(UserDirectPermedAssetsAsTreeApi): + """ 用户未分组节点下的资产作为树 """ + + def get_queryset(self): + queryset = super().get_queryset() + if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + queryset = queryset.none() + return queryset + + +class UserAllPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): only_fields = serializers.AssetGrantedSerializer.Meta.only_fields pagination_class = AllGrantedAssetPagination - ordering_fields = ("name", "address") - filterset_class = AssetFilterSet - ordering = ('name',) - - user: User def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -45,26 +92,11 @@ class UserAllGrantedAssetsQuerysetMixin: return queryset -class UserFavoriteGrantedAssetsMixin: - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - user: User - - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() - user = self.user - utils = UserGrantedAssetsQueryUtils(user) - assets = utils.get_favorite_assets() - assets = assets.prefetch_related('platform').only(*self.only_fields) - return assets - - -class UserGrantedNodeAssetsMixin: +class UserPermedNodeAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): only_fields = serializers.AssetGrantedSerializer.Meta.only_fields pagination_class = NodeGrantedAssetPagination - pagination_node: Node - user: User kwargs: dict + pagination_node: Node def get_queryset(self): if getattr(self, 'swagger_fake_view', False): @@ -75,34 +107,3 @@ class UserGrantedNodeAssetsMixin: assets = assets.prefetch_related('platform').only(*self.only_fields) self.pagination_node = node return assets - - -class AssetSerializerFormatMixin: - serializer_class = serializers.AssetGrantedSerializer - filterset_fields = ['name', 'address', 'id', 'comment'] - search_fields = ['name', 'address', 'comment'] - filterset_class = AssetFilterSet - ordering_fields = ("name", "address") - ordering = ('name',) - - -class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): - """ - 将 资产 序列化成树的结构返回 - """ - filter_queryset: callable - get_queryset: callable - - filterset_fields = ['name', 'address', 'id', 'comment'] - search_fields = ['name', 'address', 'comment'] - - def list(self, request: Request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - - if request.query_params.get('search'): - # 如果用户搜索的条件不精准,会导致返回大量的无意义数据。 - # 这里限制一下返回数据的最大条数 - queryset = queryset[:999] - queryset = sorted(queryset, key=lambda asset: asset.name) - data = self.serialize_assets(queryset, None) - return Response(data=data) diff --git a/apps/perms/api/user_permission/assets/__init__.py b/apps/perms/api/user_permission/assets/__init__.py deleted file mode 100644 index 0a0e47b0b..000000000 --- a/apps/perms/api/user_permission/assets/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .api import * diff --git a/apps/perms/api/user_permission/assets/api.py b/apps/perms/api/user_permission/assets/api.py deleted file mode 100644 index a08644449..000000000 --- a/apps/perms/api/user_permission/assets/api.py +++ /dev/null @@ -1,82 +0,0 @@ -from django.conf import settings -from rest_framework.generics import ListAPIView - -from common.utils import get_logger -from .mixin import ( - AssetsTreeFormatMixin, - UserGrantedNodeAssetsMixin, - AssetSerializerFormatMixin, - UserFavoriteGrantedAssetsMixin, - UserAllGrantedAssetsQuerysetMixin, - UserDirectGrantedAssetsQuerysetMixin, -) -from ..mixin import SelfOrPKUserMixin, RebuildTreeMixin - -__all__ = [ - 'UserDirectGrantedAssetsApi', - 'UserFavoriteGrantedAssetsApi', - 'UserDirectGrantedAssetsAsTreeApi', - 'UserUngroupAssetsAsTreeApi', - 'UserAllGrantedAssetsApi', - 'UserGrantedNodeAssetsApi', -] - -logger = get_logger(__name__) - - -class UserDirectGrantedAssetsApi( - SelfOrPKUserMixin, - UserDirectGrantedAssetsQuerysetMixin, - AssetSerializerFormatMixin, - ListAPIView -): - """ 直接授权给用户的资产 """ - pass - - -class UserFavoriteGrantedAssetsApi( - SelfOrPKUserMixin, - UserFavoriteGrantedAssetsMixin, - AssetSerializerFormatMixin, - ListAPIView -): - """ 用户收藏的授权资产 """ - pass - - -class UserDirectGrantedAssetsAsTreeApi( - RebuildTreeMixin, - AssetsTreeFormatMixin, - UserDirectGrantedAssetsApi -): - """ 用户直接授权的资产作为树 """ - pass - - -class UserUngroupAssetsAsTreeApi(UserDirectGrantedAssetsAsTreeApi): - """ 用户未分组节点下的资产作为树 """ - def get_queryset(self): - queryset = super().get_queryset() - if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - queryset = queryset.none() - return queryset - - -class UserAllGrantedAssetsApi( - SelfOrPKUserMixin, - UserAllGrantedAssetsQuerysetMixin, - AssetSerializerFormatMixin, - ListAPIView -): - """ 授权给用户的所有资产 """ - pass - - -class UserGrantedNodeAssetsApi( - SelfOrPKUserMixin, - UserGrantedNodeAssetsMixin, - AssetSerializerFormatMixin, - ListAPIView -): - """ 授权给用户的节点资产 """ - pass diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 2c145221a..7be96caed 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -1,12 +1,16 @@ # -*- coding: utf-8 -*- # from django.shortcuts import get_object_or_404 -from rest_framework.request import Request from django.utils.translation import ugettext_lazy as _ +from rest_framework.request import Request +from rest_framework.response import Response +from assets.api.asset.asset import AssetFilterSet +from assets.api.mixin import SerializeToTreeNodeMixin +from common.exceptions import JMSObjectDoesNotExist from common.http import is_true from common.utils import is_uuid -from common.exceptions import JMSObjectDoesNotExist +from perms import serializers from perms.utils.user_permission import UserGrantedTreeRefreshController from rbac.permissions import RBACPermission from users.models import User @@ -67,3 +71,33 @@ class SelfOrPKUserMixin: def request_user_is_self(self): return self.kwargs.get('user') in ['my', 'self'] + + +class PermedAssetSerializerMixin: + serializer_class = serializers.AssetGrantedSerializer + filterset_class = AssetFilterSet + search_fields = ['name', 'address', 'comment'] + ordering_fields = ("name", "address") + ordering = ('name',) + + +class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): + """ + 将 资产 序列化成树的结构返回 + """ + filter_queryset: callable + get_queryset: callable + + filterset_fields = ['name', 'address', 'id', 'comment'] + search_fields = ['name', 'address', 'comment'] + + def list(self, request: Request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + + if request.query_params.get('search'): + # 如果用户搜索的条件不精准,会导致返回大量的无意义数据。 + # 这里限制一下返回数据的最大条数 + queryset = queryset[:999] + queryset = sorted(queryset, key=lambda asset: asset.name) + data = self.serialize_assets(queryset, None) + return Response(data=data) diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index 973374b7a..c3a555375 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -6,9 +6,9 @@ user_permission_urlpatterns = [ # such as: my | self | user.id # assets - path('/assets/', api.UserAllGrantedAssetsApi.as_view(), + path('/assets/', api.UserAllPermedAssetsApi.as_view(), name='user-assets'), - path('/assets/tree/', api.UserDirectGrantedAssetsAsTreeApi.as_view(), + path('/assets/tree/', api.UserDirectPermedAssetsAsTreeApi.as_view(), name='user-assets-as-tree'), path('/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(), name='user-ungroup-assets-as-tree'), @@ -24,11 +24,11 @@ user_permission_urlpatterns = [ name='user-nodes-children-as-tree'), # node-assets - path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), + path('/nodes//assets/', api.UserPermedNodeAssetsApi.as_view(), name='user-node-assets'), - path('/nodes/ungrouped/assets/', api.UserDirectGrantedAssetsApi.as_view(), + path('/nodes/ungrouped/assets/', api.UserDirectPermedAssetsApi.as_view(), name='user-ungrouped-assets'), - path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), + path('/nodes/favorite/assets/', api.UserFavoriteAssetsApi.as_view(), name='user-ungrouped-assets'), path('/nodes/children-with-assets/tree/', From 6d0545f04f64bfa30c997a42e81c5d5714f02a19 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 2 Dec 2022 12:21:56 +0800 Subject: [PATCH 460/488] =?UTF-8?q?perf:=20=E6=8C=81=E7=BB=AD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BD=9C=E4=B8=9A=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0004_connectacl_0007_auto_20221202_1048.py | 14 + apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 693 ++++++++++-------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 680 +++++++++-------- apps/ops/api/job.py | 19 +- apps/ops/api/playbook.py | 9 +- apps/ops/exception.py | 6 + apps/ops/models/job.py | 2 +- apps/ops/models/playbook.py | 10 +- apps/ops/serializers/job.py | 6 +- apps/ops/serializers/playbook.py | 3 +- 12 files changed, 820 insertions(+), 630 deletions(-) create mode 100644 apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py create mode 100644 apps/ops/exception.py diff --git a/apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py b/apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py new file mode 100644 index 000000000..96ba9d4a4 --- /dev/null +++ b/apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.14 on 2022-12-02 03:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0004_connectacl'), + ('acls', '0007_auto_20221202_1048'), + ] + + operations = [ + ] diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index f74980a81..4468fe146 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a5338177d87680e0030c77f187a06664136d5dea63c8dffc43fa686091f2da4 -size 117102 +oid sha256:a2d20ebe29a2ae521e5026f493313abbee6a7a6b103901164766e5d1ae4ab564 +size 116377 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index e9f747826..47d2340d3 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-16 20:11+0800\n" +"POT-Creation-Date: 2022-12-01 18:42+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,21 +22,22 @@ msgstr "" msgid "Acls" msgstr "Acls" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:58 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:38 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:50 assets/models/cmd_filter.py:25 -#: assets/models/domain.py:24 assets/models/group.py:20 +#: assets/models/base.py:49 assets/models/cmd_filter.py:25 +#: assets/models/domain.py:27 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:21 #: assets/models/platform.py:72 assets/serializers/asset/common.py:86 -#: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 -#: ops/models/celery.py:15 ops/models/job.py:34 ops/models/playbook.py:13 -#: orgs/models.py:70 perms/models/asset_permission.py:51 rbac/models/role.py:29 +#: assets/serializers/domain.py:71 assets/serializers/platform.py:138 +#: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 +#: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 +#: perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 #: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 -#: terminal/models/component/terminal.py:82 users/forms/profile.py:33 +#: terminal/models/component/terminal.py:79 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 #: xpack/plugins/cloud/models.py:30 msgid "Name" @@ -54,33 +55,34 @@ msgstr "1-100、低い値は最初に一致します" #: acls/models/base.py:31 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:67 terminal/models/session/sharing.py:28 -#: tickets/const.py:38 +#: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 +#: tickets/const.py:37 msgid "Active" msgstr "アクティブ" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 #: assets/models/asset/common.py:100 assets/models/automations/base.py:22 -#: assets/models/backup.py:29 assets/models/base.py:58 +#: assets/models/backup.py:29 assets/models/base.py:57 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 -#: assets/models/domain.py:25 assets/models/domain.py:69 +#: assets/models/domain.py:28 assets/models/domain.py:192 #: assets/models/group.py:23 assets/models/label.py:22 -#: assets/models/platform.py:77 orgs/models.py:74 -#: perms/models/asset_permission.py:77 rbac/models/role.py:37 +#: assets/models/platform.py:77 ops/models/adhoc.py:27 ops/models/job.py:50 +#: ops/models/playbook.py:17 orgs/models.py:74 +#: perms/models/asset_permission.py:71 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 #: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 -#: terminal/models/component/terminal.py:93 tickets/models/comment.py:32 -#: tickets/models/ticket/general.py:288 users/models/group.py:16 +#: terminal/models/component/terminal.py:91 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:296 users/models/group.py:16 #: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "コメント" -#: acls/models/login_acl.py:18 tickets/const.py:46 +#: acls/models/login_acl.py:18 tickets/const.py:45 #: tickets/templates/tickets/approve_check_password.html:49 msgid "Reject" msgstr "拒否" @@ -89,15 +91,15 @@ msgstr "拒否" msgid "Allow" msgstr "許可" -#: acls/models/login_acl.py:20 acls/models/login_acl.py:75 -#: acls/models/login_asset_acl.py:17 tickets/const.py:9 +#: acls/models/login_acl.py:20 acls/models/login_acl.py:76 +#: acls/models/login_asset_acl.py:17 tickets/const.py:10 msgid "Login confirm" msgstr "ログイン確認" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 #: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 -#: audits/models.py:79 authentication/models/connection_token.py:22 +#: audits/models.py:79 authentication/models/connection_token.py:25 #: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:80 #: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 @@ -115,7 +117,7 @@ msgid "Rule" msgstr "ルール" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:77 +#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:64 #: assets/models/cmd_filter.py:81 audits/models.py:50 audits/serializers.py:69 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" @@ -131,22 +133,22 @@ msgid "Login acl" msgstr "ログインacl" #: acls/models/login_asset_acl.py:21 assets/models/account.py:61 -#: assets/serializers/automations/change_secret.py:88 -#: assets/serializers/automations/change_secret.py:110 -#: authentication/models/connection_token.py:33 ops/models/base.py:18 +#: assets/serializers/automations/change_secret.py:101 +#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 #: perms/models/perm_token.py:14 terminal/models/session/session.py:34 #: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "アカウント" #: acls/models/login_asset_acl.py:22 assets/models/account.py:51 -#: assets/models/asset/common.py:83 assets/models/asset/common.py:227 +#: assets/models/asset/common.py:83 assets/models/asset/common.py:212 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:59 -#: assets/serializers/automations/change_secret.py:87 -#: assets/serializers/automations/change_secret.py:109 -#: assets/serializers/gathered_user.py:11 assets/serializers/label.py:30 -#: audits/models.py:33 authentication/models/connection_token.py:26 +#: assets/serializers/automations/change_secret.py:100 +#: assets/serializers/automations/change_secret.py:122 +#: assets/serializers/domain.py:20 assets/serializers/gathered_user.py:11 +#: assets/serializers/label.py:30 audits/models.py:33 +#: authentication/models/connection_token.py:29 #: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 #: terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 @@ -161,7 +163,7 @@ msgstr "資産" msgid "Login asset acl" msgstr "ログインasset acl" -#: acls/models/login_asset_acl.py:86 tickets/const.py:11 +#: acls/models/login_asset_acl.py:85 tickets/const.py:12 msgid "Login asset confirm" msgstr "ログイン資産の確認" @@ -170,8 +172,9 @@ msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" #: acls/serializers/login_asset_acl.py:22 -#: acls/serializers/login_asset_acl.py:64 assets/models/_user.py:34 -#: assets/models/base.py:51 assets/models/gathered_user.py:15 +#: acls/serializers/login_asset_acl.py:53 assets/models/_user.py:34 +#: assets/models/base.py:50 assets/models/gathered_user.py:15 +#: assets/serializers/domain.py:58 assets/serializers/domain.py:60 #: audits/models.py:95 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -194,34 +197,18 @@ msgstr "" "192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" "db8:1a:1110:::/64 (ドメイン名サポート)" -#: acls/serializers/login_asset_acl.py:38 acls/serializers/rules/rules.py:33 -#: assets/models/asset/common.py:92 assets/models/domain.py:65 -#: authentication/templates/authentication/_msg_oauth_bind.html:12 -#: authentication/templates/authentication/_msg_rest_password_success.html:8 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 -msgid "IP" -msgstr "IP" +#: acls/serializers/login_asset_acl.py:44 assets/serializers/asset/host.py:40 +#, fuzzy +#| msgid "Host" +msgid "IP/Host" +msgstr "ホスト" -#: acls/serializers/login_asset_acl.py:44 -#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 -msgid "Hostname" -msgstr "ホスト名" - -#: acls/serializers/login_asset_acl.py:51 -msgid "" -"Format for comma-delimited string, with * indicating a match all. Protocol " -"options: {}" -msgstr "" -"コンマ区切り文字列の形式。* はすべて一致することを示します。プロトコルオプ" -"ション: {}" - -#: acls/serializers/login_asset_acl.py:108 -#: tickets/serializers/ticket/ticket.py:67 +#: acls/serializers/login_asset_acl.py:95 +#: tickets/serializers/ticket/ticket.py:66 msgid "The organization `{}` does not exist" msgstr "組織 '{}'は存在しません" -#: acls/serializers/login_asset_acl.py:114 +#: acls/serializers/login_asset_acl.py:101 msgid "None of the reviewers belong to Organization `{}`" msgstr "いずれのレビューアも組織 '{}' に属していません" @@ -240,6 +227,15 @@ msgstr "" "192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" "db8:1a:1110::/64" +#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 +#: assets/models/domain.py:186 +#: authentication/templates/authentication/_msg_oauth_bind.html:12 +#: authentication/templates/authentication/_msg_rest_password_success.html:8 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 +msgid "IP" +msgstr "IP" + #: acls/serializers/rules/rules.py:35 msgid "Time Period" msgstr "期間" @@ -252,7 +248,7 @@ msgstr "アプリケーション" #: assets/models/platform.py:73 assets/serializers/asset/common.py:62 #: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 #: assets/serializers/platform.py:139 perms/serializers/user_permission.py:23 -#: tickets/models/ticket/apply_application.py:14 +#: tickets/models/ticket/apply_application.py:13 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" msgstr "カテゴリ" @@ -266,8 +262,8 @@ msgstr "カテゴリ" #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:17 -#: tickets/models/ticket/general.py:273 tickets/serializers/flow.py:53 +#: tickets/models/ticket/apply_application.py:16 +#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 #: tickets/serializers/ticket/ticket.py:18 #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 @@ -292,7 +288,7 @@ msgstr "アプリケーションを一致させることができます" msgid "The parameter 'action' must be [{}]" msgstr "パラメータ 'action' は [{}] でなければなりません。" -#: assets/api/domain.py:52 +#: assets/api/domain.py:57 msgid "Number required" msgstr "必要な数" @@ -329,8 +325,8 @@ msgid "Ok" msgstr "OK" #: assets/const/account.py:8 -#: assets/serializers/automations/change_secret.py:105 -#: assets/serializers/automations/change_secret.py:133 audits/const.py:74 +#: assets/serializers/automations/change_secret.py:118 +#: assets/serializers/automations/change_secret.py:146 audits/const.py:74 #: common/const/choices.py:19 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 @@ -338,8 +334,9 @@ msgid "Failed" msgstr "失敗しました" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/domain.py:71 audits/signal_handlers.py:46 -#: authentication/confirm/password.py:9 authentication/forms.py:32 +#: assets/models/domain.py:194 assets/serializers/domain.py:46 +#: audits/signal_handlers.py:46 authentication/confirm/password.py:9 +#: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 #: users/forms/profile.py:22 users/serializers/user.py:105 @@ -444,7 +441,7 @@ msgid "Device" msgstr "" #: assets/const/category.py:13 assets/models/asset/database.py:8 -#: assets/models/asset/database.py:24 +#: assets/models/asset/database.py:34 #: xpack/plugins/change_auth_plan/models/app.py:31 msgid "Database" msgstr "データベース" @@ -501,14 +498,15 @@ msgstr "共通ユーザー" msgid "Admin user" msgstr "管理ユーザー" -#: assets/models/_user.py:36 assets/models/domain.py:72 +#: assets/models/_user.py:36 assets/models/domain.py:195 +#: assets/serializers/domain.py:50 #: xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" msgstr "SSH秘密鍵" -#: assets/models/_user.py:37 assets/models/domain.py:73 +#: assets/models/_user.py:37 assets/models/domain.py:196 #: xpack/plugins/change_auth_plan/models/asset.py:57 #: xpack/plugins/change_auth_plan/models/asset.py:127 #: xpack/plugins/change_auth_plan/models/asset.py:203 @@ -516,10 +514,10 @@ msgid "SSH public key" msgstr "SSHパブリックキー" #: assets/models/_user.py:41 assets/models/automations/base.py:92 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 +#: assets/models/domain.py:29 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:54 ops/models/job.py:69 orgs/models.py:73 -#: perms/models/asset_permission.py:75 users/models/group.py:18 +#: ops/models/base.py:54 ops/models/job.py:108 orgs/models.py:73 +#: perms/models/asset_permission.py:74 users/models/group.py:18 #: users/models/user.py:927 msgid "Date created" msgstr "作成された日付" @@ -529,10 +527,10 @@ msgstr "作成された日付" msgid "Date updated" msgstr "更新日" -#: assets/models/_user.py:43 assets/models/base.py:59 +#: assets/models/_user.py:43 assets/models/base.py:58 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 -#: orgs/models.py:71 perms/models/asset_permission.py:74 +#: orgs/models.py:71 perms/models/asset_permission.py:75 #: users/models/user.py:710 users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 msgid "Created by" @@ -542,8 +540,8 @@ msgstr "によって作成された" msgid "Username same with user" msgstr "ユーザーと同じユーザー名" -#: assets/models/_user.py:48 assets/models/domain.py:67 -#: authentication/models/connection_token.py:29 perms/models/perm_token.py:16 +#: assets/models/_user.py:48 assets/models/domain.py:189 +#: authentication/models/connection_token.py:35 perms/models/perm_token.py:16 #: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 #: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" @@ -557,7 +555,7 @@ msgstr "オートプッシュ" msgid "Sudo" msgstr "すど" -#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:30 +#: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 msgid "Shell" msgstr "シェル" @@ -643,7 +641,19 @@ msgstr "資産履歴アカウントパスワードを表示できます" msgid "Account template" msgstr "アカウント名" -#: assets/models/asset/common.py:82 assets/models/domain.py:66 +#: assets/models/account.py:98 +#, fuzzy +#| msgid "Can view asset account secret" +msgid "Can view asset account template secret" +msgstr "資産アカウントの秘密を表示できます" + +#: assets/models/account.py:99 +#, fuzzy +#| msgid "Can change asset account secret" +msgid "Can change asset account template secret" +msgstr "資産口座の秘密を変更できます" + +#: assets/models/asset/common.py:82 assets/models/domain.py:187 #: assets/models/platform.py:22 settings/serializers/auth/radius.py:15 #: settings/serializers/auth/sms.py:57 #: xpack/plugins/cloud/serializers/account_attrs.py:73 @@ -657,8 +667,8 @@ msgstr "ポート" msgid "Platform" msgstr "プラットフォーム" -#: assets/models/asset/common.py:95 assets/models/domain.py:29 -#: assets/models/domain.py:68 assets/serializers/asset/common.py:64 +#: assets/models/asset/common.py:95 assets/models/domain.py:32 +#: assets/models/domain.py:191 assets/serializers/asset/common.py:64 msgid "Domain" msgstr "ドメイン" @@ -672,8 +682,8 @@ msgid "Nodes" msgstr "ノード" #: assets/models/asset/common.py:98 assets/models/automations/base.py:21 -#: assets/models/base.py:57 assets/models/cmd_filter.py:39 -#: assets/models/domain.py:70 assets/models/label.py:21 +#: assets/models/base.py:56 assets/models/cmd_filter.py:39 +#: assets/models/domain.py:193 assets/models/label.py:21 #: terminal/models/applet/applet.py:25 users/serializers/user.py:202 msgid "Is active" msgstr "アクティブです。" @@ -682,32 +692,60 @@ msgstr "アクティブです。" msgid "Labels" msgstr "ラベル" -#: assets/models/asset/common.py:230 +#: assets/models/asset/common.py:215 msgid "Can refresh asset hardware info" msgstr "資産ハードウェア情報を更新できます" -#: assets/models/asset/common.py:231 +#: assets/models/asset/common.py:216 msgid "Can test asset connectivity" msgstr "資産接続をテストできます" -#: assets/models/asset/common.py:232 +#: assets/models/asset/common.py:217 #, fuzzy #| msgid "Can push system user to asset" msgid "Can push account to asset" msgstr "システムユーザーを資産にプッシュできます" -#: assets/models/asset/common.py:233 +#: assets/models/asset/common.py:218 msgid "Can match asset" msgstr "アセットを一致させることができます" -#: assets/models/asset/common.py:234 +#: assets/models/asset/common.py:219 msgid "Add asset to node" msgstr "ノードにアセットを追加する" -#: assets/models/asset/common.py:235 +#: assets/models/asset/common.py:220 msgid "Move asset to node" msgstr "アセットをノードに移動する" +#: assets/models/asset/database.py:9 settings/serializers/email.py:36 +msgid "Use SSL" +msgstr "SSLの使用" + +#: assets/models/asset/database.py:10 +#, fuzzy +#| msgid "SP cert" +msgid "CA cert" +msgstr "SP 証明書" + +#: assets/models/asset/database.py:11 +#, fuzzy +#| msgid "Client Secret" +msgid "Client cert" +msgstr "クライアント秘密" + +#: assets/models/asset/database.py:12 +#, fuzzy +#| msgid "Client" +msgid "Client key" +msgstr "クライアント" + +#: assets/models/asset/database.py:13 +#, fuzzy +#| msgid "Host invalid" +msgid "Allow invalid cert" +msgstr "ホスト無効" + #: assets/models/asset/web.py:9 audits/const.py:67 #: terminal/serializers/applet_host.py:25 msgid "Disabled" @@ -750,7 +788,7 @@ msgid "Accounts" msgstr "アカウント" #: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 +#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:32 #: ops/models/base.py:17 ops/models/job.py:44 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 @@ -764,19 +802,19 @@ msgid "Automation task" msgstr "自動管理" #: assets/models/automations/base.py:91 audits/models.py:115 -#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:64 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 #: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 #: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:281 tickets/serializers/ticket/ticket.py:19 +#: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:19 #: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:223 msgid "Status" msgstr "ステータス" #: assets/models/automations/base.py:93 assets/models/backup.py:76 #: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 -#: ops/models/job.py:70 perms/models/asset_permission.py:69 +#: ops/models/job.py:109 perms/models/asset_permission.py:67 #: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 -#: tickets/models/ticket/apply_application.py:28 +#: tickets/models/ticket/apply_application.py:30 #: tickets/models/ticket/apply_asset.py:19 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:199 @@ -786,7 +824,7 @@ msgstr "開始日" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/celery.py:60 ops/models/job.py:71 +#: ops/models/celery.py:60 ops/models/job.py:110 #: terminal/models/applet/host.py:106 msgid "Date finished" msgstr "終了日" @@ -807,13 +845,13 @@ msgid "Trigger mode" msgstr "トリガーモード" #: assets/models/automations/base.py:104 -#: assets/serializers/automations/change_secret.py:90 +#: assets/serializers/automations/change_secret.py:103 #, fuzzy #| msgid "Command execution" msgid "Automation task execution" msgstr "コマンド実行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:53 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:52 #: assets/serializers/account/account.py:95 assets/serializers/base.py:13 #, fuzzy #| msgid "Secret key" @@ -828,9 +866,8 @@ msgid "Secret strategy" msgstr "SSHキー戦略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:55 -#: assets/serializers/base.py:16 authentication/models/connection_token.py:34 -#: authentication/models/temp_token.py:10 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:54 +#: assets/serializers/base.py:16 authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 msgid "Secret" @@ -949,8 +986,8 @@ msgid "Reason" msgstr "理由" #: assets/models/backup.py:92 -#: assets/serializers/automations/change_secret.py:86 -#: assets/serializers/automations/change_secret.py:111 +#: assets/serializers/automations/change_secret.py:99 +#: assets/serializers/automations/change_secret.py:124 #: terminal/serializers/session.py:36 #: xpack/plugins/change_auth_plan/models/base.py:198 #: xpack/plugins/change_auth_plan/serializers/asset.py:173 @@ -961,15 +998,15 @@ msgstr "成功は" msgid "Account backup execution" msgstr "アカウントバックアップの実行" -#: assets/models/base.py:28 assets/serializers/domain.py:42 +#: assets/models/base.py:27 msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:30 authentication/models/temp_token.py:12 +#: assets/models/base.py:29 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:56 +#: assets/models/base.py:55 msgid "Privileged" msgstr "" @@ -1026,33 +1063,33 @@ msgstr "コマンドフィルタルール" msgid "The generated regular expression is incorrect: {}" msgstr "生成された正規表現が正しくありません: {}" -#: assets/models/cmd_filter.py:164 tickets/const.py:12 +#: assets/models/cmd_filter.py:164 tickets/const.py:11 msgid "Command confirm" msgstr "コマンドの確認" -#: assets/models/domain.py:84 -msgid "Gateway" -msgstr "ゲートウェイ" - -#: assets/models/domain.py:86 -msgid "Test gateway" -msgstr "テストゲートウェイ" - -#: assets/models/domain.py:142 +#: assets/models/domain.py:121 #, fuzzy, python-brace-format #| msgid "Unable to connect to port {port} on {ip}" msgid "Unable to connect to port {port} on {address}" msgstr "{ip} でポート {port} に接続できません" -#: assets/models/domain.py:145 authentication/middleware.py:76 +#: assets/models/domain.py:124 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "認証に失敗しました" -#: assets/models/domain.py:147 assets/models/domain.py:169 +#: assets/models/domain.py:126 assets/models/domain.py:148 msgid "Connect failed" msgstr "接続に失敗しました" +#: assets/models/domain.py:207 +msgid "Gateway" +msgstr "ゲートウェイ" + +#: assets/models/domain.py:209 +msgid "Test gateway" +msgstr "テストゲートウェイ" + #: assets/models/gathered_user.py:16 msgid "Present" msgstr "プレゼント" @@ -1088,6 +1125,7 @@ msgstr "システム" #: assets/models/label.py:18 assets/models/node.py:553 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 +#: authentication/models/connection_token.py:22 #: common/drf/serializers/common.py:82 settings/models.py:34 msgid "Value" msgstr "値" @@ -1114,16 +1152,16 @@ msgstr "キー" msgid "Full value" msgstr "フルバリュー" -#: assets/models/node.py:557 perms/models/perm_node.py:22 +#: assets/models/node.py:558 perms/models/perm_node.py:22 msgid "Parent key" msgstr "親キー" -#: assets/models/node.py:566 xpack/plugins/cloud/models.py:98 +#: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 #: xpack/plugins/cloud/serializers/task.py:68 msgid "Node" msgstr "ノード" -#: assets/models/node.py:569 +#: assets/models/node.py:570 msgid "Can match node" msgstr "ノードを一致させることができます" @@ -1204,7 +1242,7 @@ msgstr "サービスアカウントキー" msgid "Verify account method" msgstr "パスワード/キーの確認" -#: assets/models/platform.py:75 tickets/models/ticket/general.py:298 +#: assets/models/platform.py:75 tickets/models/ticket/general.py:299 msgid "Meta" msgstr "メタ" @@ -1319,12 +1357,14 @@ msgstr "定期的なパフォーマンス" msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 +#: assets/serializers/asset/common.py:68 assets/serializers/domain.py:61 +#: assets/serializers/platform.py:101 +#: authentication/serializers/connection_token.py:88 #: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "プロトコル" -#: assets/serializers/asset/common.py:87 +#: assets/serializers/asset/common.py:87 assets/serializers/domain.py:72 msgid "Address" msgstr "アドレス" @@ -1348,7 +1388,7 @@ msgstr "ベンダー" msgid "Model" msgstr "モデル" -#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:296 +#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 msgid "Serial number" msgstr "シリアル番号" @@ -1400,30 +1440,24 @@ msgstr "ホスト名生" msgid "Asset number" msgstr "資産番号" -#: assets/serializers/asset/host.py:40 -#, fuzzy -#| msgid "Host" -msgid "IP/Host" -msgstr "ホスト" - #: assets/serializers/automations/change_secret.py:28 #: xpack/plugins/change_auth_plan/models/asset.py:50 #: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "SSH Key strategy" msgstr "SSHキー戦略" -#: assets/serializers/automations/change_secret.py:57 +#: assets/serializers/automations/change_secret.py:70 #: xpack/plugins/change_auth_plan/serializers/base.py:58 msgid "* Please enter the correct password length" msgstr "* 正しいパスワードの長さを入力してください" -#: assets/serializers/automations/change_secret.py:60 +#: assets/serializers/automations/change_secret.py:73 #: xpack/plugins/change_auth_plan/serializers/base.py:61 msgid "* Password length range 6-30 bits" msgstr "* パスワードの長さの範囲6-30ビット" -#: assets/serializers/automations/change_secret.py:104 -#: assets/serializers/automations/change_secret.py:132 audits/const.py:73 +#: assets/serializers/automations/change_secret.py:117 +#: assets/serializers/automations/change_secret.py:145 audits/const.py:73 #: audits/models.py:39 common/const/choices.py:18 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 @@ -1436,7 +1470,7 @@ msgstr "成功" msgid "Executed amount" msgstr "実行時間" -#: assets/serializers/base.py:21 +#: assets/serializers/base.py:21 assets/serializers/domain.py:54 msgid "Key password" msgstr "キーパスワード" @@ -1450,14 +1484,18 @@ msgstr "" msgid "Types" msgstr "タイプ" -#: assets/serializers/domain.py:14 assets/serializers/label.py:12 +#: assets/serializers/domain.py:17 assets/serializers/label.py:12 msgid "Assets amount" msgstr "資産額" -#: assets/serializers/domain.py:15 +#: assets/serializers/domain.py:18 msgid "Gateways count" msgstr "ゲートウェイ数" +#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 +msgid "Hostname" +msgstr "ホスト名" + #: assets/serializers/label.py:13 msgid "Category display" msgstr "カテゴリ表示" @@ -1496,19 +1534,19 @@ msgstr "自動" msgid "Primary" msgstr "" -#: assets/serializers/utils.py:15 +#: assets/serializers/utils.py:13 msgid "Password can not contains `{{` " msgstr "パスワードには '{{' を含まない" -#: assets/serializers/utils.py:18 +#: assets/serializers/utils.py:16 msgid "Password can not contains `'` " msgstr "パスワードには `'` を含まない" -#: assets/serializers/utils.py:20 +#: assets/serializers/utils.py:18 msgid "Password can not contains `\"` " msgstr "パスワードには `\"` を含まない" -#: assets/serializers/utils.py:26 +#: assets/serializers/utils.py:24 msgid "private key invalid or passphrase error" msgstr "秘密鍵が無効またはpassphraseエラー" @@ -1589,7 +1627,7 @@ msgstr "" msgid "Test if the assets under the node are connectable " msgstr "ノードの下のアセットが接続可能かどうかをテストします。" -#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:31 +#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 #, fuzzy #| msgid "Create account successfully" msgid "Push accounts to assets" @@ -1641,7 +1679,7 @@ msgstr "Rmdir" msgid "Delete" msgstr "削除" -#: audits/const.py:47 perms/const.py:14 +#: audits/const.py:47 perms/const.py:13 msgid "Upload" msgstr "アップロード" @@ -1653,7 +1691,7 @@ msgstr "名前の変更" msgid "Symlink" msgstr "Symlink" -#: audits/const.py:50 perms/const.py:15 +#: audits/const.py:50 perms/const.py:14 msgid "Download" msgstr "ダウンロード" @@ -1673,7 +1711,7 @@ msgid "Create" msgstr "作成" #: audits/const.py:62 terminal/models/applet/host.py:24 -#: terminal/models/component/terminal.py:159 +#: terminal/models/component/terminal.py:157 msgid "Terminal" msgstr "ターミナル" @@ -1854,7 +1892,7 @@ msgstr "{AssetPermission} 追加 {UserGroup}" msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 削除 {UserGroup}" -#: audits/signal_handlers.py:84 perms/models/asset_permission.py:83 +#: audits/signal_handlers.py:84 perms/models/asset_permission.py:81 msgid "Asset permission" msgstr "資産権限" @@ -2211,56 +2249,73 @@ msgid "Please change your password" msgstr "パスワードを変更してください" #: authentication/models/connection_token.py:31 +#: terminal/serializers/storage.py:111 +msgid "Account name" +msgstr "アカウント名" + +#: authentication/models/connection_token.py:32 +#, fuzzy +#| msgid "Custom Username" +msgid "Input Username" +msgstr "カスタムユーザー名" + +#: authentication/models/connection_token.py:33 +#, fuzzy +#| msgid "Client Secret" +msgid "Input Secret" +msgstr "クライアント秘密" + +#: authentication/models/connection_token.py:37 perms/models/perm_token.py:17 +#, fuzzy +#| msgid "Connect timeout" +msgid "Connect method" +msgstr "接続タイムアウト" + +#: authentication/models/connection_token.py:38 #: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "ユーザー表示" -#: authentication/models/connection_token.py:32 +#: authentication/models/connection_token.py:39 msgid "Asset display" msgstr "アセット名" -#: authentication/models/connection_token.py:36 -#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:72 -#: tickets/models/ticket/apply_application.py:29 +#: authentication/models/connection_token.py:41 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 +#: tickets/models/ticket/apply_application.py:31 #: tickets/models/ticket/apply_asset.py:20 users/models/user.py:707 msgid "Date expired" msgstr "期限切れの日付" -#: authentication/models/connection_token.py:41 +#: authentication/models/connection_token.py:46 msgid "Connection token" msgstr "接続トークン" -#: authentication/models/connection_token.py:43 +#: authentication/models/connection_token.py:48 msgid "Can view connection token secret" msgstr "接続トークンの秘密を表示できます" -#: authentication/models/connection_token.py:82 +#: authentication/models/connection_token.py:95 msgid "Connection token expired at: {}" msgstr "接続トークンの有効期限: {}" -#: authentication/models/connection_token.py:86 +#: authentication/models/connection_token.py:98 msgid "No user or invalid user" msgstr "" -#: authentication/models/connection_token.py:90 +#: authentication/models/connection_token.py:102 #, fuzzy #| msgid "Asset inactive" msgid "No asset or inactive asset" msgstr "アセットがアクティブ化されていません" -#: authentication/models/connection_token.py:94 +#: authentication/models/connection_token.py:105 #, fuzzy #| msgid "Login acl" msgid "No account" msgstr "ログインacl" -#: authentication/models/connection_token.py:101 -msgid "User has no permission to access asset or permission expired" -msgstr "" -"ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れて" -"います" - -#: authentication/models/connection_token.py:144 +#: authentication/models/connection_token.py:172 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2288,15 +2343,16 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" -#: authentication/serializers/connection_token.py:19 -#: xpack/plugins/cloud/models.py:36 -msgid "Validity" -msgstr "有効性" - -#: authentication/serializers/connection_token.py:20 +#: authentication/serializers/connection_token.py:18 msgid "Expired time" msgstr "期限切れ時間" +#: authentication/serializers/connection_token.py:139 +#, fuzzy +#| msgid "Expired" +msgid "Expired now" +msgstr "期限切れ" + #: authentication/serializers/token.py:79 perms/serializers/permission.py:30 #: perms/serializers/permission.py:61 users/serializers/user.py:203 msgid "Is valid" @@ -2385,7 +2441,7 @@ msgstr "コードエラー" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 +#: jumpserver/conf.py:389 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2477,7 +2533,7 @@ msgstr "" "能性があります" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:19 +#: templates/flash_message_standalone.html:28 tickets/const.py:17 msgid "Cancel" msgstr "キャンセル" @@ -2690,7 +2746,7 @@ msgstr "タイミングトリガー" msgid "Ready" msgstr "の準備を" -#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:37 +#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 msgid "Pending" msgstr "未定" @@ -2934,11 +2990,11 @@ msgstr "特殊文字を含むべきではない" msgid "The mobile phone number format is incorrect" msgstr "携帯電話番号の形式が正しくありません" -#: jumpserver/conf.py:389 +#: jumpserver/conf.py:388 msgid "Create account successfully" msgstr "アカウントを正常に作成" -#: jumpserver/conf.py:391 +#: jumpserver/conf.py:390 msgid "Your account has been created successfully" msgstr "アカウントが正常に作成されました" @@ -3043,6 +3099,10 @@ msgstr "パスワードの変更" msgid "Custom password" msgstr "カスタムパスワード" +#: ops/exception.py:6 +msgid "no valid program entry found." +msgstr "" + #: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "サイクル実行" @@ -3068,39 +3128,31 @@ msgstr "{} から {} までの範囲" msgid "Require periodic or regularly perform setting" msgstr "定期的または定期的に設定を行う必要があります" -#: ops/models/adhoc.py:21 ops/models/job.py:31 +#: ops/models/adhoc.py:18 ops/models/job.py:31 #, fuzzy #| msgid "PowerShell" msgid "Powershell" msgstr "PowerShell" -#: ops/models/adhoc.py:25 +#: ops/models/adhoc.py:22 msgid "Pattern" msgstr "パターン" -#: ops/models/adhoc.py:27 ops/models/job.py:38 +#: ops/models/adhoc.py:24 ops/models/job.py:38 msgid "Module" msgstr "" -#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:36 +#: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 #: terminal/models/component/task.py:17 msgid "Args" msgstr "アルグ" -#: ops/models/adhoc.py:29 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:43 ops/models/job.py:68 +#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 #: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "作成者" -#: ops/models/adhoc.py:50 ops/models/job.py:21 -msgid "Adhoc" -msgstr "" - -#: ops/models/adhoc.py:68 -msgid "AdHoc execution" -msgstr "アドホックエキューション" - #: ops/models/base.py:19 #, fuzzy #| msgid "Account key" @@ -3119,11 +3171,12 @@ msgstr "コマンド実行" msgid "Date last run" msgstr "最終同期日" -#: ops/models/base.py:51 ops/models/job.py:66 xpack/plugins/cloud/models.py:169 +#: ops/models/base.py:51 ops/models/job.py:105 +#: xpack/plugins/cloud/models.py:169 msgid "Result" msgstr "結果" -#: ops/models/base.py:52 ops/models/job.py:67 +#: ops/models/base.py:52 ops/models/job.py:106 msgid "Summary" msgstr "" @@ -3132,7 +3185,7 @@ msgid "Kwargs" msgstr "クワーグ" #: ops/models/celery.py:56 tickets/models/comment.py:13 -#: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 +#: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 #: tickets/serializers/ticket/ticket.py:20 msgid "State" msgstr "状態" @@ -3148,6 +3201,10 @@ msgstr "終了" msgid "Date published" msgstr "終了日" +#: ops/models/job.py:21 +msgid "Adhoc" +msgstr "" + #: ops/models/job.py:22 ops/models/job.py:41 msgid "Playbook" msgstr "" @@ -3183,13 +3240,15 @@ msgid "Runas policy" msgstr "アカウントキー" #: ops/models/job.py:48 -#, fuzzy -#| msgid "Disable" -msgid "Variables" -msgstr "無効化" +msgid "Use Parameter Define" +msgstr "" -#: ops/models/playbook.py:15 -msgid "Owner" +#: ops/models/job.py:49 +msgid "Parameters define" +msgstr "" + +#: ops/models/job.py:104 +msgid "Parameters" msgstr "" #: ops/notifications.py:17 @@ -3220,43 +3279,49 @@ msgstr "{max_threshold}%: => {value} を超える使用メモリ" msgid "CPU load more than {max_threshold}: => {value}" msgstr "{max_threshold} を超えるCPUロード: => {value}" -#: ops/signal_handlers.py:63 terminal/models/applet/host.py:108 +#: ops/serializers/job.py:11 +#, fuzzy +#| msgid "Run system user" +msgid "Run after save" +msgstr "システムユーザーの実行" + +#: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 #: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "タスク" -#: ops/tasks.py:27 +#: ops/tasks.py:28 #, fuzzy #| msgid "Run asset" msgid "Run ansible task" msgstr "アセットの実行" -#: ops/tasks.py:41 +#: ops/tasks.py:36 #, fuzzy #| msgid "Run asset" msgid "Run ansible task execution" msgstr "アセットの実行" -#: ops/tasks.py:54 +#: ops/tasks.py:50 msgid "Periodic clear celery tasks" msgstr "" -#: ops/tasks.py:56 +#: ops/tasks.py:52 msgid "Clean celery log period" msgstr "きれいなセロリログ期間" -#: ops/tasks.py:73 +#: ops/tasks.py:69 #, fuzzy #| msgid "Clean celery log period" msgid "Clear celery periodic tasks" msgstr "きれいなセロリログ期間" -#: ops/tasks.py:96 +#: ops/tasks.py:92 msgid "Create or update periodic tasks" msgstr "" -#: ops/tasks.py:104 +#: ops/tasks.py:100 #, fuzzy #| msgid "Periodic perform" msgid "Periodic check service performance" @@ -3292,7 +3357,7 @@ msgstr "アプリ組織" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:61 +#: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:60 msgid "Organization" msgstr "組織" @@ -3336,25 +3401,25 @@ msgstr "グローバル組織名" msgid "App permissions" msgstr "アプリの権限" -#: perms/const.py:13 +#: perms/const.py:12 msgid "Connect" msgstr "接続" -#: perms/const.py:16 +#: perms/const.py:15 #, fuzzy #| msgid "Copy link" msgid "Copy" msgstr "リンクのコピー" -#: perms/const.py:17 +#: perms/const.py:16 msgid "Paste" msgstr "" -#: perms/const.py:27 +#: perms/const.py:26 msgid "Transfer" msgstr "" -#: perms/const.py:28 +#: perms/const.py:27 #, fuzzy #| msgid "Clipboard copy" msgid "Clipboard" @@ -3362,12 +3427,12 @@ msgstr "クリップボードのコピー" #: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 #: perms/serializers/permission.py:29 perms/serializers/permission.py:59 -#: tickets/models/ticket/apply_application.py:26 +#: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:18 msgid "Actions" msgstr "アクション" -#: perms/models/asset_permission.py:76 +#: perms/models/asset_permission.py:73 msgid "From ticket" msgstr "チケットから" @@ -3401,12 +3466,6 @@ msgstr "ユーザーグループの資産を表示できます" msgid "Permed account" msgstr "アカウントを集める" -#: perms/models/perm_token.py:17 -#, fuzzy -#| msgid "Connect timeout" -msgid "Connect method" -msgstr "接続タイムアウト" - #: perms/notifications.py:12 perms/notifications.py:44 msgid "today" msgstr "今" @@ -3657,7 +3716,7 @@ msgstr "マイアプリ" msgid "Ticket comment" msgstr "チケットコメント" -#: rbac/tree.py:115 tickets/models/ticket/general.py:305 +#: rbac/tree.py:115 tickets/models/ticket/general.py:306 msgid "Ticket" msgstr "チケット" @@ -4247,10 +4306,6 @@ msgstr "テスト受信者" msgid "Tips: Used only as a test mail recipient" msgstr "ヒント: テストメールの受信者としてのみ使用" -#: settings/serializers/email.py:36 -msgid "Use SSL" -msgstr "SSLの使用" - #: settings/serializers/email.py:37 msgid "If SMTP port is 465, may be select" msgstr "SMTPポートが465の場合は、" @@ -4857,8 +4912,8 @@ msgstr "期限切れです。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" @@ -4879,34 +4934,34 @@ msgid "" " " msgstr "" "\n" -" クリックしてください リンク パスワードの更新\n" +" クリックしてください リンク パスワードの更新\n" " " #: templates/_message.html:43 #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" -" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" +" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" " " #: templates/_message.html:56 #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" +" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -4970,7 +5025,7 @@ msgstr "" msgid "Offline video player" msgstr "オフラインビデオプレーヤー" -#: terminal/api/component/endpoint.py:33 +#: terminal/api/component/endpoint.py:31 msgid "Not found protocol query params" msgstr "プロトコルクエリパラメータが見つかりません" @@ -5002,7 +5057,7 @@ msgstr "テスト成功" msgid "Test failure: Account invalid" msgstr "テスト失敗: アカウントが無効" -#: terminal/api/component/terminal.py:39 +#: terminal/api/component/terminal.py:38 msgid "Have online sessions" msgstr "オンラインセッションを持つ" @@ -5080,26 +5135,33 @@ msgid "Timestamp" msgstr "タイムスタンプ" #: terminal/backends/command/serializers.py:41 -#: terminal/models/component/terminal.py:87 +#: terminal/models/component/terminal.py:84 msgid "Remote Address" msgstr "リモートアドレス" -#: terminal/const.py:33 +#: terminal/const.py:37 msgid "Critical" msgstr "クリティカル" -#: terminal/const.py:34 +#: terminal/const.py:38 msgid "High" msgstr "高い" -#: terminal/const.py:35 users/templates/users/reset_password.html:50 +#: terminal/const.py:39 users/templates/users/reset_password.html:50 msgid "Normal" msgstr "正常" -#: terminal/const.py:36 +#: terminal/const.py:40 msgid "Offline" msgstr "オフライン" +#: terminal/const.py:80 terminal/const.py:81 terminal/const.py:82 +#: terminal/const.py:83 terminal/const.py:84 +#, fuzzy +#| msgid "Client" +msgid "DB Client" +msgstr "クライアント" + #: terminal/exceptions.py:8 msgid "Bulk create not support" msgstr "一括作成非サポート" @@ -5249,20 +5311,20 @@ msgid "Default storage" msgstr "デフォルトのストレージ" #: terminal/models/component/storage.py:136 -#: terminal/models/component/terminal.py:88 +#: terminal/models/component/terminal.py:85 msgid "Command storage" msgstr "コマンドストレージ" #: terminal/models/component/storage.py:196 -#: terminal/models/component/terminal.py:89 +#: terminal/models/component/terminal.py:86 msgid "Replay storage" msgstr "再生ストレージ" -#: terminal/models/component/terminal.py:85 +#: terminal/models/component/terminal.py:82 msgid "type" msgstr "タイプ" -#: terminal/models/component/terminal.py:161 +#: terminal/models/component/terminal.py:159 msgid "Can view terminal config" msgstr "ターミナル構成を表示できます" @@ -5517,10 +5579,6 @@ msgstr "リージョン" msgid "Container name" msgstr "コンテナー名" -#: terminal/serializers/storage.py:111 -msgid "Account name" -msgstr "アカウント名" - #: terminal/serializers/storage.py:112 msgid "Account key" msgstr "アカウントキー" @@ -5573,7 +5631,7 @@ msgstr "表示" msgid "Tickets" msgstr "チケット" -#: tickets/const.py:10 +#: tickets/const.py:9 msgid "Apply for asset" msgstr "資産の申請" @@ -5581,23 +5639,23 @@ msgstr "資産の申請" msgid "Open" msgstr "オープン" -#: tickets/const.py:17 tickets/const.py:30 -msgid "Approved" -msgstr "承認済み" - #: tickets/const.py:18 tickets/const.py:31 -msgid "Rejected" -msgstr "拒否" - -#: tickets/const.py:20 tickets/const.py:33 msgid "Reopen" msgstr "" -#: tickets/const.py:32 tickets/const.py:39 +#: tickets/const.py:19 tickets/const.py:32 +msgid "Approved" +msgstr "承認済み" + +#: tickets/const.py:20 tickets/const.py:33 +msgid "Rejected" +msgstr "拒否" + +#: tickets/const.py:30 tickets/const.py:38 msgid "Closed" msgstr "クローズ" -#: tickets/const.py:45 +#: tickets/const.py:46 msgid "Approve" msgstr "承認" @@ -5610,21 +5668,21 @@ msgid "Two level" msgstr "2つのレベル" #: tickets/const.py:55 -msgid "Super admin" -msgstr "スーパー管理者" - -#: tickets/const.py:56 msgid "Org admin" msgstr "Org admin" -#: tickets/const.py:57 -msgid "Super admin and org admin" -msgstr "スーパーadminとorg admin" - -#: tickets/const.py:58 +#: tickets/const.py:56 msgid "Custom user" msgstr "カスタムユーザー" +#: tickets/const.py:57 +msgid "Super admin" +msgstr "スーパー管理者" + +#: tickets/const.py:58 +msgid "Super admin and org admin" +msgstr "スーパーadminとorg admin" + #: tickets/errors.py:9 msgid "Ticket already closed" msgstr "チケットはすでに閉じています" @@ -5653,18 +5711,6 @@ msgstr "変更後" msgid "{} {} the ticket" msgstr "{} {} チケット" -#: tickets/handlers/login_confirm.py:18 -msgid "Applied login IP" -msgstr "応用ログインIP" - -#: tickets/handlers/login_confirm.py:19 -msgid "Applied login city" -msgstr "応用ログイン都市" - -#: tickets/handlers/login_confirm.py:20 -msgid "Applied login datetime" -msgstr "適用されたログインの日付時間" - #: tickets/models/comment.py:14 msgid "common" msgstr "" @@ -5678,15 +5724,15 @@ msgid "Body" msgstr "ボディ" #: tickets/models/flow.py:20 tickets/models/flow.py:62 -#: tickets/models/ticket/general.py:37 +#: tickets/models/ticket/general.py:39 msgid "Approve level" msgstr "レベルを承認する" -#: tickets/models/flow.py:25 tickets/serializers/flow.py:17 +#: tickets/models/flow.py:25 tickets/serializers/flow.py:18 msgid "Approve strategy" msgstr "戦略を承認する" -#: tickets/models/flow.py:30 tickets/serializers/flow.py:19 +#: tickets/models/flow.py:30 tickets/serializers/flow.py:20 msgid "Assignees" msgstr "アシニーズ" @@ -5702,16 +5748,16 @@ msgstr "チケットの流れ" msgid "Ticket session relation" msgstr "チケットセッションの関係" -#: tickets/models/ticket/apply_application.py:11 +#: tickets/models/ticket/apply_application.py:10 #: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" msgstr "認可ルール名" -#: tickets/models/ticket/apply_application.py:20 +#: tickets/models/ticket/apply_application.py:19 msgid "Apply applications" msgstr "アプリケーションの適用" -#: tickets/models/ticket/apply_application.py:23 +#: tickets/models/ticket/apply_application.py:22 msgid "Apply system users" msgstr "システムユーザーの適用" @@ -5745,15 +5791,15 @@ msgid "Run asset" msgstr "アセットの実行" #: tickets/models/ticket/command_confirm.py:13 +msgid "Run command" +msgstr "実行コマンド" + +#: tickets/models/ticket/command_confirm.py:14 #, fuzzy #| msgid "account" msgid "Run account" msgstr "アカウント" -#: tickets/models/ticket/command_confirm.py:14 -msgid "Run command" -msgstr "実行コマンド" - #: tickets/models/ticket/command_confirm.py:21 msgid "From cmd filter" msgstr "コマンドフィルタ規則から" @@ -5762,19 +5808,19 @@ msgstr "コマンドフィルタ規則から" msgid "From cmd filter rule" msgstr "コマンドフィルタ規則から" -#: tickets/models/ticket/general.py:72 +#: tickets/models/ticket/general.py:74 msgid "Ticket step" msgstr "チケットステップ" -#: tickets/models/ticket/general.py:90 +#: tickets/models/ticket/general.py:92 msgid "Ticket assignee" msgstr "割り当てられたチケット" -#: tickets/models/ticket/general.py:270 +#: tickets/models/ticket/general.py:271 msgid "Title" msgstr "タイトル" -#: tickets/models/ticket/general.py:286 +#: tickets/models/ticket/general.py:287 msgid "Applicant" msgstr "応募者" @@ -5790,23 +5836,23 @@ msgstr "承認ステップ" msgid "Relation snapshot" msgstr "製造オーダスナップショット" -#: tickets/models/ticket/general.py:390 +#: tickets/models/ticket/general.py:391 msgid "Please try again" msgstr "もう一度お試しください" -#: tickets/models/ticket/general.py:421 +#: tickets/models/ticket/general.py:424 msgid "Super ticket" msgstr "スーパーチケット" -#: tickets/models/ticket/login_asset_confirm.py:12 +#: tickets/models/ticket/login_asset_confirm.py:11 msgid "Login user" msgstr "ログインユーザー" -#: tickets/models/ticket/login_asset_confirm.py:16 +#: tickets/models/ticket/login_asset_confirm.py:14 msgid "Login asset" msgstr "ログイン資産" -#: tickets/models/ticket/login_asset_confirm.py:19 +#: tickets/models/ticket/login_asset_confirm.py:17 #, fuzzy #| msgid "Login acl" msgid "Login account" @@ -5840,15 +5886,15 @@ msgstr "チケットが処理されました。プロセッサー- {}" msgid "Ticket has processed - {} ({})" msgstr "チケットが処理済み- {} ({})" -#: tickets/serializers/flow.py:20 +#: tickets/serializers/flow.py:21 msgid "Assignees display" msgstr "受付者名" -#: tickets/serializers/flow.py:46 +#: tickets/serializers/flow.py:47 msgid "Please select the Assignees" msgstr "受付をお選びください" -#: tickets/serializers/flow.py:74 +#: tickets/serializers/flow.py:75 msgid "The current organization type already exists" msgstr "現在の組織タイプは既に存在します。" @@ -5871,11 +5917,11 @@ msgstr "チケットで作成 ({}-{})" msgid "The expiration date should be greater than the start date" msgstr "有効期限は開始日より大きくする必要があります" -#: tickets/serializers/ticket/common.py:83 +#: tickets/serializers/ticket/common.py:84 msgid "Permission named `{}` already exists" msgstr "'{}'という名前の権限は既に存在します" -#: tickets/serializers/ticket/ticket.py:85 +#: tickets/serializers/ticket/ticket.py:83 msgid "The ticket flow `{}` does not exist" msgstr "チケットフロー '{}'が存在しない" @@ -6742,6 +6788,10 @@ msgstr "クラウドセンター" msgid "Provider" msgstr "プロバイダー" +#: xpack/plugins/cloud/models.py:36 +msgid "Validity" +msgstr "有効性" + #: xpack/plugins/cloud/models.py:41 msgid "Cloud account" msgstr "クラウドアカウント" @@ -7197,6 +7247,35 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#~ msgid "" +#~ "Format for comma-delimited string, with * indicating a match all. " +#~ "Protocol options: {}" +#~ msgstr "" +#~ "コンマ区切り文字列の形式。* はすべて一致することを示します。プロトコルオプ" +#~ "ション: {}" + +#~ msgid "User has no permission to access asset or permission expired" +#~ msgstr "" +#~ "ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れ" +#~ "ています" + +#~ msgid "AdHoc execution" +#~ msgstr "アドホックエキューション" + +#, fuzzy +#~| msgid "Disable" +#~ msgid "Variables" +#~ msgstr "無効化" + +#~ msgid "Applied login IP" +#~ msgstr "応用ログインIP" + +#~ msgid "Applied login city" +#~ msgstr "応用ログイン都市" + +#~ msgid "Applied login datetime" +#~ msgstr "適用されたログインの日付時間" + #~ msgid "Type display" #~ msgstr "タイプ表示" @@ -7403,9 +7482,6 @@ msgstr "コミュニティ版" #~ msgid "Target url" #~ msgstr "ターゲットURL" -#~ msgid "Custom Username" -#~ msgstr "カスタムユーザー名" - #~ msgid "Mysql workbench username" #~ msgstr "Mysql workbench のユーザー名" @@ -7743,8 +7819,5 @@ msgstr "コミュニティ版" #~ "チケットによって作成されたチケットタイトル: {}、チケット申請者: {}、チケッ" #~ "ト処理者: {}、チケットID: {}" -#~ msgid "Run system user" -#~ msgstr "システムユーザーの実行" - #~ msgid "Login system user" #~ msgstr "ログインシステムユーザー" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e3f454768..cd5e43d04 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:30ae571e06eb7d2f0fee70013a812ea3bdb8e14715e1a1f4eb5e2c92311034f8 -size 104086 +oid sha256:eb680a5e6725fcd4459a8e712b0eda8df3e9990915e7f3b9602b16307ff36221 +size 103614 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 56b0a8f28..49b6c682a 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-16 20:11+0800\n" +"POT-Creation-Date: 2022-12-01 18:42+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -21,21 +21,22 @@ msgstr "" msgid "Acls" msgstr "访问控制" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:58 +#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:38 #: applications/models.py:10 assets/models/_user.py:33 #: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:50 assets/models/cmd_filter.py:25 -#: assets/models/domain.py:24 assets/models/group.py:20 +#: assets/models/base.py:49 assets/models/cmd_filter.py:25 +#: assets/models/domain.py:27 assets/models/group.py:20 #: assets/models/label.py:17 assets/models/platform.py:21 #: assets/models/platform.py:72 assets/serializers/asset/common.py:86 -#: assets/serializers/platform.py:138 ops/mixin.py:20 ops/models/adhoc.py:24 -#: ops/models/celery.py:15 ops/models/job.py:34 ops/models/playbook.py:13 -#: orgs/models.py:70 perms/models/asset_permission.py:51 rbac/models/role.py:29 +#: assets/serializers/domain.py:71 assets/serializers/platform.py:138 +#: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 +#: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 +#: perms/models/asset_permission.py:51 rbac/models/role.py:29 #: settings/models.py:33 settings/serializers/sms.py:6 #: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 #: terminal/models/component/endpoint.py:87 #: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 -#: terminal/models/component/terminal.py:82 users/forms/profile.py:33 +#: terminal/models/component/terminal.py:79 users/forms/profile.py:33 #: users/models/group.py:15 users/models/user.py:665 #: xpack/plugins/cloud/models.py:30 msgid "Name" @@ -53,33 +54,34 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)" #: acls/models/base.py:31 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:67 terminal/models/session/sharing.py:28 -#: tickets/const.py:38 +#: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 +#: tickets/const.py:37 msgid "Active" msgstr "激活中" #: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 #: assets/models/asset/common.py:100 assets/models/automations/base.py:22 -#: assets/models/backup.py:29 assets/models/base.py:58 +#: assets/models/backup.py:29 assets/models/base.py:57 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 -#: assets/models/domain.py:25 assets/models/domain.py:69 +#: assets/models/domain.py:28 assets/models/domain.py:192 #: assets/models/group.py:23 assets/models/label.py:22 -#: assets/models/platform.py:77 orgs/models.py:74 -#: perms/models/asset_permission.py:77 rbac/models/role.py:37 +#: assets/models/platform.py:77 ops/models/adhoc.py:27 ops/models/job.py:50 +#: ops/models/playbook.py:17 orgs/models.py:74 +#: perms/models/asset_permission.py:71 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 #: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 #: terminal/models/component/endpoint.py:24 #: terminal/models/component/endpoint.py:97 #: terminal/models/component/storage.py:28 -#: terminal/models/component/terminal.py:93 tickets/models/comment.py:32 -#: tickets/models/ticket/general.py:288 users/models/group.py:16 +#: terminal/models/component/terminal.py:91 tickets/models/comment.py:32 +#: tickets/models/ticket/general.py:296 users/models/group.py:16 #: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 #: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" -#: acls/models/login_acl.py:18 tickets/const.py:46 +#: acls/models/login_acl.py:18 tickets/const.py:45 #: tickets/templates/tickets/approve_check_password.html:49 msgid "Reject" msgstr "拒绝" @@ -88,15 +90,15 @@ msgstr "拒绝" msgid "Allow" msgstr "允许" -#: acls/models/login_acl.py:20 acls/models/login_acl.py:75 -#: acls/models/login_asset_acl.py:17 tickets/const.py:9 +#: acls/models/login_acl.py:20 acls/models/login_acl.py:76 +#: acls/models/login_asset_acl.py:17 tickets/const.py:10 msgid "Login confirm" msgstr "登录复核" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 #: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 #: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 -#: audits/models.py:79 authentication/models/connection_token.py:22 +#: audits/models.py:79 authentication/models/connection_token.py:25 #: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:80 #: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 @@ -114,7 +116,7 @@ msgid "Rule" msgstr "规则" #: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:77 +#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:64 #: assets/models/cmd_filter.py:81 audits/models.py:50 audits/serializers.py:69 #: authentication/templates/authentication/_access_key_modal.html:34 msgid "Action" @@ -130,22 +132,22 @@ msgid "Login acl" msgstr "登录访问控制" #: acls/models/login_asset_acl.py:21 assets/models/account.py:61 -#: assets/serializers/automations/change_secret.py:88 -#: assets/serializers/automations/change_secret.py:110 -#: authentication/models/connection_token.py:33 ops/models/base.py:18 +#: assets/serializers/automations/change_secret.py:101 +#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 #: perms/models/perm_token.py:14 terminal/models/session/session.py:34 #: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 msgid "Account" msgstr "账号" #: acls/models/login_asset_acl.py:22 assets/models/account.py:51 -#: assets/models/asset/common.py:83 assets/models/asset/common.py:227 +#: assets/models/asset/common.py:83 assets/models/asset/common.py:212 #: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 #: assets/serializers/account/account.py:59 -#: assets/serializers/automations/change_secret.py:87 -#: assets/serializers/automations/change_secret.py:109 -#: assets/serializers/gathered_user.py:11 assets/serializers/label.py:30 -#: audits/models.py:33 authentication/models/connection_token.py:26 +#: assets/serializers/automations/change_secret.py:100 +#: assets/serializers/automations/change_secret.py:122 +#: assets/serializers/domain.py:20 assets/serializers/gathered_user.py:11 +#: assets/serializers/label.py:30 audits/models.py:33 +#: authentication/models/connection_token.py:29 #: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 #: terminal/backends/command/models.py:21 #: terminal/backends/command/serializers.py:14 @@ -160,7 +162,7 @@ msgstr "资产" msgid "Login asset acl" msgstr "登录资产访问控制" -#: acls/models/login_asset_acl.py:86 tickets/const.py:11 +#: acls/models/login_asset_acl.py:85 tickets/const.py:12 msgid "Login asset confirm" msgstr "登录资产复核" @@ -169,8 +171,9 @@ msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_asset_acl.py:22 -#: acls/serializers/login_asset_acl.py:64 assets/models/_user.py:34 -#: assets/models/base.py:51 assets/models/gathered_user.py:15 +#: acls/serializers/login_asset_acl.py:53 assets/models/_user.py:34 +#: assets/models/base.py:50 assets/models/gathered_user.py:15 +#: assets/serializers/domain.py:58 assets/serializers/domain.py:60 #: audits/models.py:95 authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 @@ -192,32 +195,16 @@ msgstr "" "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" -#: acls/serializers/login_asset_acl.py:38 acls/serializers/rules/rules.py:33 -#: assets/models/asset/common.py:92 assets/models/domain.py:65 -#: authentication/templates/authentication/_msg_oauth_bind.html:12 -#: authentication/templates/authentication/_msg_rest_password_success.html:8 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 -msgid "IP" -msgstr "IP" +#: acls/serializers/login_asset_acl.py:44 assets/serializers/asset/host.py:40 +msgid "IP/Host" +msgstr "IP/主机名" -#: acls/serializers/login_asset_acl.py:44 -#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 -msgid "Hostname" -msgstr "主机名" - -#: acls/serializers/login_asset_acl.py:51 -msgid "" -"Format for comma-delimited string, with * indicating a match all. Protocol " -"options: {}" -msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" - -#: acls/serializers/login_asset_acl.py:108 -#: tickets/serializers/ticket/ticket.py:67 +#: acls/serializers/login_asset_acl.py:95 +#: tickets/serializers/ticket/ticket.py:66 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" -#: acls/serializers/login_asset_acl.py:114 +#: acls/serializers/login_asset_acl.py:101 msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" @@ -235,6 +222,15 @@ msgstr "" "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" +#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 +#: assets/models/domain.py:186 +#: authentication/templates/authentication/_msg_oauth_bind.html:12 +#: authentication/templates/authentication/_msg_rest_password_success.html:8 +#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 +#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 +msgid "IP" +msgstr "IP" + #: acls/serializers/rules/rules.py:35 msgid "Time Period" msgstr "时段" @@ -247,7 +243,7 @@ msgstr "应用管理" #: assets/models/platform.py:73 assets/serializers/asset/common.py:62 #: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 #: assets/serializers/platform.py:139 perms/serializers/user_permission.py:23 -#: tickets/models/ticket/apply_application.py:14 +#: tickets/models/ticket/apply_application.py:13 #: xpack/plugins/change_auth_plan/models/app.py:24 msgid "Category" msgstr "类别" @@ -261,8 +257,8 @@ msgstr "类别" #: terminal/models/component/storage.py:57 #: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 #: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:17 -#: tickets/models/ticket/general.py:273 tickets/serializers/flow.py:53 +#: tickets/models/ticket/apply_application.py:16 +#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 #: tickets/serializers/ticket/ticket.py:18 #: xpack/plugins/change_auth_plan/models/app.py:27 #: xpack/plugins/change_auth_plan/models/app.py:152 @@ -287,7 +283,7 @@ msgstr "匹配应用" msgid "The parameter 'action' must be [{}]" msgstr "参数 'action' 必须是 [{}]" -#: assets/api/domain.py:52 +#: assets/api/domain.py:57 msgid "Number required" msgstr "需要为数字" @@ -322,8 +318,8 @@ msgid "Ok" msgstr "成功" #: assets/const/account.py:8 -#: assets/serializers/automations/change_secret.py:105 -#: assets/serializers/automations/change_secret.py:133 audits/const.py:74 +#: assets/serializers/automations/change_secret.py:118 +#: assets/serializers/automations/change_secret.py:146 audits/const.py:74 #: common/const/choices.py:19 #: xpack/plugins/change_auth_plan/serializers/asset.py:190 #: xpack/plugins/cloud/const.py:33 @@ -331,8 +327,9 @@ msgid "Failed" msgstr "失败" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/domain.py:71 audits/signal_handlers.py:46 -#: authentication/confirm/password.py:9 authentication/forms.py:32 +#: assets/models/domain.py:194 assets/serializers/domain.py:46 +#: audits/signal_handlers.py:46 authentication/confirm/password.py:9 +#: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 #: users/forms/profile.py:22 users/serializers/user.py:105 @@ -425,7 +422,7 @@ msgid "Device" msgstr "网络设备" #: assets/const/category.py:13 assets/models/asset/database.py:8 -#: assets/models/asset/database.py:24 +#: assets/models/asset/database.py:34 #: xpack/plugins/change_auth_plan/models/app.py:31 msgid "Database" msgstr "数据库" @@ -476,14 +473,15 @@ msgstr "普通用户" msgid "Admin user" msgstr "特权用户" -#: assets/models/_user.py:36 assets/models/domain.py:72 +#: assets/models/_user.py:36 assets/models/domain.py:195 +#: assets/serializers/domain.py:50 #: xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" msgstr "SSH 密钥" -#: assets/models/_user.py:37 assets/models/domain.py:73 +#: assets/models/_user.py:37 assets/models/domain.py:196 #: xpack/plugins/change_auth_plan/models/asset.py:57 #: xpack/plugins/change_auth_plan/models/asset.py:127 #: xpack/plugins/change_auth_plan/models/asset.py:203 @@ -491,10 +489,10 @@ msgid "SSH public key" msgstr "SSH 公钥" #: assets/models/_user.py:41 assets/models/automations/base.py:92 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 +#: assets/models/domain.py:29 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:54 ops/models/job.py:69 orgs/models.py:73 -#: perms/models/asset_permission.py:75 users/models/group.py:18 +#: ops/models/base.py:54 ops/models/job.py:108 orgs/models.py:73 +#: perms/models/asset_permission.py:74 users/models/group.py:18 #: users/models/user.py:927 msgid "Date created" msgstr "创建日期" @@ -504,10 +502,10 @@ msgstr "创建日期" msgid "Date updated" msgstr "更新日期" -#: assets/models/_user.py:43 assets/models/base.py:59 +#: assets/models/_user.py:43 assets/models/base.py:58 #: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 -#: orgs/models.py:71 perms/models/asset_permission.py:74 +#: orgs/models.py:71 perms/models/asset_permission.py:75 #: users/models/user.py:710 users/serializers/group.py:33 #: xpack/plugins/change_auth_plan/models/base.py:48 msgid "Created by" @@ -517,8 +515,8 @@ msgstr "创建者" msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/_user.py:48 assets/models/domain.py:67 -#: authentication/models/connection_token.py:29 perms/models/perm_token.py:16 +#: assets/models/_user.py:48 assets/models/domain.py:189 +#: authentication/models/connection_token.py:35 perms/models/perm_token.py:16 #: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 #: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 msgid "Protocol" @@ -532,7 +530,7 @@ msgstr "自动推送" msgid "Sudo" msgstr "Sudo" -#: assets/models/_user.py:51 ops/models/adhoc.py:20 ops/models/job.py:30 +#: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 msgid "Shell" msgstr "Shell" @@ -614,7 +612,19 @@ msgstr "可以查看资产历史账号密码" msgid "Account template" msgstr "账号模版" -#: assets/models/asset/common.py:82 assets/models/domain.py:66 +#: assets/models/account.py:98 +#, fuzzy +#| msgid "Can view asset account secret" +msgid "Can view asset account template secret" +msgstr "可以查看资产账号密码" + +#: assets/models/account.py:99 +#, fuzzy +#| msgid "Can change asset account secret" +msgid "Can change asset account template secret" +msgstr "可以更改资产账号密码" + +#: assets/models/asset/common.py:82 assets/models/domain.py:187 #: assets/models/platform.py:22 settings/serializers/auth/radius.py:15 #: settings/serializers/auth/sms.py:57 #: xpack/plugins/cloud/serializers/account_attrs.py:73 @@ -628,8 +638,8 @@ msgstr "端口" msgid "Platform" msgstr "资产平台" -#: assets/models/asset/common.py:95 assets/models/domain.py:29 -#: assets/models/domain.py:68 assets/serializers/asset/common.py:64 +#: assets/models/asset/common.py:95 assets/models/domain.py:32 +#: assets/models/domain.py:191 assets/serializers/asset/common.py:64 msgid "Domain" msgstr "网域" @@ -643,8 +653,8 @@ msgid "Nodes" msgstr "节点" #: assets/models/asset/common.py:98 assets/models/automations/base.py:21 -#: assets/models/base.py:57 assets/models/cmd_filter.py:39 -#: assets/models/domain.py:70 assets/models/label.py:21 +#: assets/models/base.py:56 assets/models/cmd_filter.py:39 +#: assets/models/domain.py:193 assets/models/label.py:21 #: terminal/models/applet/applet.py:25 users/serializers/user.py:202 msgid "Is active" msgstr "激活" @@ -653,32 +663,60 @@ msgstr "激活" msgid "Labels" msgstr "标签管理" -#: assets/models/asset/common.py:230 +#: assets/models/asset/common.py:215 msgid "Can refresh asset hardware info" msgstr "可以更新资产硬件信息" -#: assets/models/asset/common.py:231 +#: assets/models/asset/common.py:216 msgid "Can test asset connectivity" msgstr "可以测试资产连接性" -#: assets/models/asset/common.py:232 +#: assets/models/asset/common.py:217 #, fuzzy #| msgid "Can push system user to asset" msgid "Can push account to asset" msgstr "可以推送系统用户到资产" -#: assets/models/asset/common.py:233 +#: assets/models/asset/common.py:218 msgid "Can match asset" msgstr "可以匹配资产" -#: assets/models/asset/common.py:234 +#: assets/models/asset/common.py:219 msgid "Add asset to node" msgstr "添加资产到节点" -#: assets/models/asset/common.py:235 +#: assets/models/asset/common.py:220 msgid "Move asset to node" msgstr "移动资产到节点" +#: assets/models/asset/database.py:9 settings/serializers/email.py:36 +msgid "Use SSL" +msgstr "使用 SSL" + +#: assets/models/asset/database.py:10 +#, fuzzy +#| msgid "SP cert" +msgid "CA cert" +msgstr "SP 证书" + +#: assets/models/asset/database.py:11 +#, fuzzy +#| msgid "Client Secret" +msgid "Client cert" +msgstr "客户端密钥" + +#: assets/models/asset/database.py:12 +#, fuzzy +#| msgid "Client" +msgid "Client key" +msgstr "客户端" + +#: assets/models/asset/database.py:13 +#, fuzzy +#| msgid "Host invalid" +msgid "Allow invalid cert" +msgstr "主机无效" + #: assets/models/asset/web.py:9 audits/const.py:67 #: terminal/serializers/applet_host.py:25 msgid "Disabled" @@ -715,7 +753,7 @@ msgid "Accounts" msgstr "账号管理" #: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 +#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:32 #: ops/models/base.py:17 ops/models/job.py:44 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 @@ -727,19 +765,19 @@ msgid "Automation task" msgstr "自动化任务" #: assets/models/automations/base.py:91 audits/models.py:115 -#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:64 +#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 #: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 #: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:281 tickets/serializers/ticket/ticket.py:19 +#: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:19 #: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:223 msgid "Status" msgstr "状态" #: assets/models/automations/base.py:93 assets/models/backup.py:76 #: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 -#: ops/models/job.py:70 perms/models/asset_permission.py:69 +#: ops/models/job.py:109 perms/models/asset_permission.py:67 #: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 -#: tickets/models/ticket/apply_application.py:28 +#: tickets/models/ticket/apply_application.py:30 #: tickets/models/ticket/apply_asset.py:19 #: xpack/plugins/change_auth_plan/models/base.py:108 #: xpack/plugins/change_auth_plan/models/base.py:199 @@ -749,7 +787,7 @@ msgstr "开始日期" #: assets/models/automations/base.py:94 #: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/celery.py:60 ops/models/job.py:71 +#: ops/models/celery.py:60 ops/models/job.py:110 #: terminal/models/applet/host.py:106 msgid "Date finished" msgstr "结束日期" @@ -768,11 +806,11 @@ msgid "Trigger mode" msgstr "触发模式" #: assets/models/automations/base.py:104 -#: assets/serializers/automations/change_secret.py:90 +#: assets/serializers/automations/change_secret.py:103 msgid "Automation task execution" msgstr "自动化任务执行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:53 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:52 #: assets/serializers/account/account.py:95 assets/serializers/base.py:13 msgid "Secret type" msgstr "密文类型" @@ -783,9 +821,8 @@ msgid "Secret strategy" msgstr "密钥策略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:55 -#: assets/serializers/base.py:16 authentication/models/connection_token.py:34 -#: authentication/models/temp_token.py:10 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:54 +#: assets/serializers/base.py:16 authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 msgid "Secret" @@ -886,8 +923,8 @@ msgid "Reason" msgstr "原因" #: assets/models/backup.py:92 -#: assets/serializers/automations/change_secret.py:86 -#: assets/serializers/automations/change_secret.py:111 +#: assets/serializers/automations/change_secret.py:99 +#: assets/serializers/automations/change_secret.py:124 #: terminal/serializers/session.py:36 #: xpack/plugins/change_auth_plan/models/base.py:198 #: xpack/plugins/change_auth_plan/serializers/asset.py:173 @@ -898,15 +935,15 @@ msgstr "是否成功" msgid "Account backup execution" msgstr "账号备份执行" -#: assets/models/base.py:28 assets/serializers/domain.py:42 +#: assets/models/base.py:27 msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:30 authentication/models/temp_token.py:12 +#: assets/models/base.py:29 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:56 +#: assets/models/base.py:55 msgid "Privileged" msgstr "特权账号" @@ -963,32 +1000,32 @@ msgstr "命令过滤规则" msgid "The generated regular expression is incorrect: {}" msgstr "生成的正则表达式有误" -#: assets/models/cmd_filter.py:164 tickets/const.py:12 +#: assets/models/cmd_filter.py:164 tickets/const.py:11 msgid "Command confirm" msgstr "命令复核" -#: assets/models/domain.py:84 -msgid "Gateway" -msgstr "网关" - -#: assets/models/domain.py:86 -msgid "Test gateway" -msgstr "测试网关" - -#: assets/models/domain.py:142 +#: assets/models/domain.py:121 #, python-brace-format msgid "Unable to connect to port {port} on {address}" msgstr "无法连接到 {address} 上的端口 {port}" -#: assets/models/domain.py:145 authentication/middleware.py:76 +#: assets/models/domain.py:124 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "认证失败" -#: assets/models/domain.py:147 assets/models/domain.py:169 +#: assets/models/domain.py:126 assets/models/domain.py:148 msgid "Connect failed" msgstr "连接失败" +#: assets/models/domain.py:207 +msgid "Gateway" +msgstr "网关" + +#: assets/models/domain.py:209 +msgid "Test gateway" +msgstr "测试网关" + #: assets/models/gathered_user.py:16 msgid "Present" msgstr "存在" @@ -1024,6 +1061,7 @@ msgstr "系统" #: assets/models/label.py:18 assets/models/node.py:553 #: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 +#: authentication/models/connection_token.py:22 #: common/drf/serializers/common.py:82 settings/models.py:34 msgid "Value" msgstr "值" @@ -1050,16 +1088,16 @@ msgstr "键" msgid "Full value" msgstr "全称" -#: assets/models/node.py:557 perms/models/perm_node.py:22 +#: assets/models/node.py:558 perms/models/perm_node.py:22 msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:566 xpack/plugins/cloud/models.py:98 +#: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 #: xpack/plugins/cloud/serializers/task.py:68 msgid "Node" msgstr "节点" -#: assets/models/node.py:569 +#: assets/models/node.py:570 msgid "Can match node" msgstr "可以匹配节点" @@ -1120,7 +1158,7 @@ msgstr "校验账号" msgid "Verify account method" msgstr "验证z" -#: assets/models/platform.py:75 tickets/models/ticket/general.py:298 +#: assets/models/platform.py:75 tickets/models/ticket/general.py:299 msgid "Meta" msgstr "元数据" @@ -1222,12 +1260,14 @@ msgstr "定时执行" msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 +#: assets/serializers/asset/common.py:68 assets/serializers/domain.py:61 +#: assets/serializers/platform.py:101 +#: authentication/serializers/connection_token.py:88 #: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "协议组" -#: assets/serializers/asset/common.py:87 +#: assets/serializers/asset/common.py:87 assets/serializers/domain.py:72 msgid "Address" msgstr "地址" @@ -1247,7 +1287,7 @@ msgstr "制造商" msgid "Model" msgstr "型号" -#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:296 +#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 msgid "Serial number" msgstr "序列号" @@ -1299,28 +1339,24 @@ msgstr "主机名原始" msgid "Asset number" msgstr "资产编号" -#: assets/serializers/asset/host.py:40 -msgid "IP/Host" -msgstr "IP/主机名" - #: assets/serializers/automations/change_secret.py:28 #: xpack/plugins/change_auth_plan/models/asset.py:50 #: xpack/plugins/change_auth_plan/serializers/asset.py:33 msgid "SSH Key strategy" msgstr "SSH 密钥策略" -#: assets/serializers/automations/change_secret.py:57 +#: assets/serializers/automations/change_secret.py:70 #: xpack/plugins/change_auth_plan/serializers/base.py:58 msgid "* Please enter the correct password length" msgstr "* 请输入正确的密码长度" -#: assets/serializers/automations/change_secret.py:60 +#: assets/serializers/automations/change_secret.py:73 #: xpack/plugins/change_auth_plan/serializers/base.py:61 msgid "* Password length range 6-30 bits" msgstr "* 密码长度范围 6-30 位" -#: assets/serializers/automations/change_secret.py:104 -#: assets/serializers/automations/change_secret.py:132 audits/const.py:73 +#: assets/serializers/automations/change_secret.py:117 +#: assets/serializers/automations/change_secret.py:145 audits/const.py:73 #: audits/models.py:39 common/const/choices.py:18 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 @@ -1333,7 +1369,7 @@ msgstr "成功" msgid "Executed amount" msgstr "执行次数" -#: assets/serializers/base.py:21 +#: assets/serializers/base.py:21 assets/serializers/domain.py:54 msgid "Key password" msgstr "密钥密码" @@ -1345,14 +1381,18 @@ msgstr "约束" msgid "Types" msgstr "类型" -#: assets/serializers/domain.py:14 assets/serializers/label.py:12 +#: assets/serializers/domain.py:17 assets/serializers/label.py:12 msgid "Assets amount" msgstr "资产数量" -#: assets/serializers/domain.py:15 +#: assets/serializers/domain.py:18 msgid "Gateways count" msgstr "网关数量" +#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 +msgid "Hostname" +msgstr "主机名" + #: assets/serializers/label.py:13 msgid "Category display" msgstr "类别名称" @@ -1385,19 +1425,19 @@ msgstr "自动填充" msgid "Primary" msgstr "主要的" -#: assets/serializers/utils.py:15 +#: assets/serializers/utils.py:13 msgid "Password can not contains `{{` " msgstr "密码不能包含 `{{` 字符" -#: assets/serializers/utils.py:18 +#: assets/serializers/utils.py:16 msgid "Password can not contains `'` " msgstr "密码不能包含 `'` 字符" -#: assets/serializers/utils.py:20 +#: assets/serializers/utils.py:18 msgid "Password can not contains `\"` " msgstr "密码不能包含 `\"` 字符" -#: assets/serializers/utils.py:26 +#: assets/serializers/utils.py:24 msgid "private key invalid or passphrase error" msgstr "密钥不合法或密钥密码错误" @@ -1464,7 +1504,7 @@ msgstr "手动测试节点下的资产可连接性" msgid "Test if the assets under the node are connectable " msgstr "测试节点下资产是否可连接: " -#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:31 +#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 msgid "Push accounts to assets" msgstr "推送账号到资产" @@ -1510,7 +1550,7 @@ msgstr "删除目录" msgid "Delete" msgstr "删除" -#: audits/const.py:47 perms/const.py:14 +#: audits/const.py:47 perms/const.py:13 msgid "Upload" msgstr "上传文件" @@ -1522,7 +1562,7 @@ msgstr "重命名" msgid "Symlink" msgstr "建立软链接" -#: audits/const.py:50 perms/const.py:15 +#: audits/const.py:50 perms/const.py:14 msgid "Download" msgstr "下载文件" @@ -1542,7 +1582,7 @@ msgid "Create" msgstr "创建" #: audits/const.py:62 terminal/models/applet/host.py:24 -#: terminal/models/component/terminal.py:159 +#: terminal/models/component/terminal.py:157 msgid "Terminal" msgstr "终端" @@ -1723,7 +1763,7 @@ msgstr "{AssetPermission} 添加 {UserGroup}" msgid "{AssetPermission} REMOVE {UserGroup}" msgstr "{AssetPermission} 移除 {UserGroup}" -#: audits/signal_handlers.py:84 perms/models/asset_permission.py:83 +#: audits/signal_handlers.py:84 perms/models/asset_permission.py:81 msgid "Asset permission" msgstr "资产授权" @@ -2067,54 +2107,73 @@ msgid "Please change your password" msgstr "请修改密码" #: authentication/models/connection_token.py:31 +#: terminal/serializers/storage.py:111 +msgid "Account name" +msgstr "账号名称" + +#: authentication/models/connection_token.py:32 +#, fuzzy +#| msgid "Custom Username" +msgid "Input Username" +msgstr "自定义用户名" + +#: authentication/models/connection_token.py:33 +#, fuzzy +#| msgid "Client Secret" +msgid "Input Secret" +msgstr "客户端密钥" + +#: authentication/models/connection_token.py:37 perms/models/perm_token.py:17 +#, fuzzy +#| msgid "Connect timeout" +msgid "Connect method" +msgstr "连接超时时间" + +#: authentication/models/connection_token.py:38 #: rbac/serializers/rolebinding.py:21 msgid "User display" msgstr "用户名称" -#: authentication/models/connection_token.py:32 +#: authentication/models/connection_token.py:39 msgid "Asset display" msgstr "资产名称" -#: authentication/models/connection_token.py:36 -#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:72 -#: tickets/models/ticket/apply_application.py:29 +#: authentication/models/connection_token.py:41 +#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 +#: tickets/models/ticket/apply_application.py:31 #: tickets/models/ticket/apply_asset.py:20 users/models/user.py:707 msgid "Date expired" msgstr "失效日期" -#: authentication/models/connection_token.py:41 +#: authentication/models/connection_token.py:46 msgid "Connection token" msgstr "连接令牌" -#: authentication/models/connection_token.py:43 +#: authentication/models/connection_token.py:48 msgid "Can view connection token secret" msgstr "可以查看连接令牌密文" -#: authentication/models/connection_token.py:82 +#: authentication/models/connection_token.py:95 msgid "Connection token expired at: {}" msgstr "连接令牌过期: {}" -#: authentication/models/connection_token.py:86 +#: authentication/models/connection_token.py:98 msgid "No user or invalid user" msgstr "" -#: authentication/models/connection_token.py:90 +#: authentication/models/connection_token.py:102 #, fuzzy #| msgid "Asset inactive" msgid "No asset or inactive asset" msgstr "资产未激活" -#: authentication/models/connection_token.py:94 +#: authentication/models/connection_token.py:105 #, fuzzy #| msgid "Login account" msgid "No account" msgstr "登录账号" -#: authentication/models/connection_token.py:101 -msgid "User has no permission to access asset or permission expired" -msgstr "用户没有权限访问资产或权限已过期" - -#: authentication/models/connection_token.py:144 +#: authentication/models/connection_token.py:172 msgid "Super connection token" msgstr "超级连接令牌" @@ -2142,15 +2201,16 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" -#: authentication/serializers/connection_token.py:19 -#: xpack/plugins/cloud/models.py:36 -msgid "Validity" -msgstr "有效" - -#: authentication/serializers/connection_token.py:20 +#: authentication/serializers/connection_token.py:18 msgid "Expired time" msgstr "过期时间" +#: authentication/serializers/connection_token.py:139 +#, fuzzy +#| msgid "Expired" +msgid "Expired now" +msgstr "过期时间" + #: authentication/serializers/token.py:79 perms/serializers/permission.py:30 #: perms/serializers/permission.py:61 users/serializers/user.py:203 msgid "Is valid" @@ -2239,7 +2299,7 @@ msgstr "代码错误" #: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:390 +#: jumpserver/conf.py:389 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: tickets/templates/tickets/approve_check_password.html:33 @@ -2323,7 +2383,7 @@ msgid "" msgstr "如果这次公钥更新不是由你发起的,那么你的账号可能存在安全问题" #: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:19 +#: templates/flash_message_standalone.html:28 tickets/const.py:17 msgid "Cancel" msgstr "取消" @@ -2535,7 +2595,7 @@ msgstr "定时触发" msgid "Ready" msgstr "准备" -#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:37 +#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 msgid "Pending" msgstr "待定的" @@ -2774,11 +2834,11 @@ msgstr "不能包含特殊字符" msgid "The mobile phone number format is incorrect" msgstr "手机号格式不正确" -#: jumpserver/conf.py:389 +#: jumpserver/conf.py:388 msgid "Create account successfully" msgstr "创建账号成功" -#: jumpserver/conf.py:391 +#: jumpserver/conf.py:390 msgid "Your account has been created successfully" msgstr "你的账号已创建成功" @@ -2870,6 +2930,10 @@ msgstr "更改密码" msgid "Custom password" msgstr "自定义密码" +#: ops/exception.py:6 +msgid "no valid program entry found." +msgstr "" + #: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:72 msgid "Cycle perform" msgstr "周期执行" @@ -2895,39 +2959,31 @@ msgstr "输入在 {} - {} 范围之间" msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" -#: ops/models/adhoc.py:21 ops/models/job.py:31 +#: ops/models/adhoc.py:18 ops/models/job.py:31 #, fuzzy #| msgid "PowerShell" msgid "Powershell" msgstr "PowerShell" -#: ops/models/adhoc.py:25 +#: ops/models/adhoc.py:22 msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:27 ops/models/job.py:38 +#: ops/models/adhoc.py:24 ops/models/job.py:38 msgid "Module" msgstr "" -#: ops/models/adhoc.py:28 ops/models/celery.py:54 ops/models/job.py:36 +#: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 #: terminal/models/component/task.py:17 msgid "Args" msgstr "参数" -#: ops/models/adhoc.py:29 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:43 ops/models/job.py:68 +#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 +#: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 #: terminal/models/session/sharing.py:24 msgid "Creator" msgstr "创建者" -#: ops/models/adhoc.py:50 ops/models/job.py:21 -msgid "Adhoc" -msgstr "" - -#: ops/models/adhoc.py:68 -msgid "AdHoc execution" -msgstr "任务执行" - #: ops/models/base.py:19 msgid "Account policy" msgstr "账号策略" @@ -2940,11 +2996,12 @@ msgstr "最后执行" msgid "Date last run" msgstr "最后执行日期" -#: ops/models/base.py:51 ops/models/job.py:66 xpack/plugins/cloud/models.py:169 +#: ops/models/base.py:51 ops/models/job.py:105 +#: xpack/plugins/cloud/models.py:169 msgid "Result" msgstr "结果" -#: ops/models/base.py:52 ops/models/job.py:67 +#: ops/models/base.py:52 ops/models/job.py:106 msgid "Summary" msgstr "汇总" @@ -2953,7 +3010,7 @@ msgid "Kwargs" msgstr "其它参数" #: ops/models/celery.py:56 tickets/models/comment.py:13 -#: tickets/models/ticket/general.py:41 tickets/models/ticket/general.py:277 +#: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 #: tickets/serializers/ticket/ticket.py:20 msgid "State" msgstr "状态" @@ -2967,6 +3024,10 @@ msgstr "结束" msgid "Date published" msgstr "发布日期" +#: ops/models/job.py:21 +msgid "Adhoc" +msgstr "" + #: ops/models/job.py:22 ops/models/job.py:41 msgid "Playbook" msgstr "Playbook" @@ -3006,14 +3067,16 @@ msgid "Runas policy" msgstr "账号策略" #: ops/models/job.py:48 -#, fuzzy -#| msgid "Disable" -msgid "Variables" -msgstr "禁用" +msgid "Use Parameter Define" +msgstr "定义参数" -#: ops/models/playbook.py:15 -msgid "Owner" -msgstr "Owner" +#: ops/models/job.py:49 +msgid "Parameters define" +msgstr "" + +#: ops/models/job.py:104 +msgid "Parameters" +msgstr "" #: ops/notifications.py:17 msgid "Server performance" @@ -3043,37 +3106,41 @@ msgstr "内存使用率超过 {max_threshold}%: => {value}" msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" -#: ops/signal_handlers.py:63 terminal/models/applet/host.py:108 +#: ops/serializers/job.py:11 +msgid "Run after save" +msgstr "保存后运行" + +#: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 #: terminal/models/component/task.py:26 #: xpack/plugins/gathered_user/models.py:68 msgid "Task" msgstr "任务" -#: ops/tasks.py:27 +#: ops/tasks.py:28 msgid "Run ansible task" msgstr "运行 ansible 任务" -#: ops/tasks.py:41 +#: ops/tasks.py:36 msgid "Run ansible task execution" msgstr "运行 ansible 任务" -#: ops/tasks.py:54 +#: ops/tasks.py:50 msgid "Periodic clear celery tasks" msgstr "定时清理 Celery 任务" -#: ops/tasks.py:56 +#: ops/tasks.py:52 msgid "Clean celery log period" msgstr "定期清理 Celery 日志" -#: ops/tasks.py:73 +#: ops/tasks.py:69 msgid "Clear celery periodic tasks" msgstr "清理 Celery 定时任务" -#: ops/tasks.py:96 +#: ops/tasks.py:92 msgid "Create or update periodic tasks" msgstr "创建或更新定时任务" -#: ops/tasks.py:104 +#: ops/tasks.py:100 msgid "Periodic check service performance" msgstr "定时检查服务性能" @@ -3106,7 +3173,7 @@ msgstr "组织管理" #: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 #: rbac/const.py:7 rbac/models/rolebinding.py:48 #: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:300 tickets/serializers/ticket/ticket.py:61 +#: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:60 msgid "Organization" msgstr "组织" @@ -3148,25 +3215,25 @@ msgstr "刷新组织缓存" msgid "App permissions" msgstr "授权管理" -#: perms/const.py:13 +#: perms/const.py:12 msgid "Connect" msgstr "连接" -#: perms/const.py:16 +#: perms/const.py:15 #, fuzzy #| msgid "Copy link" msgid "Copy" msgstr "复制链接" -#: perms/const.py:17 +#: perms/const.py:16 msgid "Paste" msgstr "" -#: perms/const.py:27 +#: perms/const.py:26 msgid "Transfer" msgstr "" -#: perms/const.py:28 +#: perms/const.py:27 #, fuzzy #| msgid "Clipboard copy" msgid "Clipboard" @@ -3174,12 +3241,12 @@ msgstr "剪贴板复制" #: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 #: perms/serializers/permission.py:29 perms/serializers/permission.py:59 -#: tickets/models/ticket/apply_application.py:26 +#: tickets/models/ticket/apply_application.py:28 #: tickets/models/ticket/apply_asset.py:18 msgid "Actions" msgstr "动作" -#: perms/models/asset_permission.py:76 +#: perms/models/asset_permission.py:73 msgid "From ticket" msgstr "来自工单" @@ -3213,12 +3280,6 @@ msgstr "可以查看用户组授权的资产" msgid "Permed account" msgstr "收集账号" -#: perms/models/perm_token.py:17 -#, fuzzy -#| msgid "Connect timeout" -msgid "Connect method" -msgstr "连接超时时间" - #: perms/notifications.py:12 perms/notifications.py:44 msgid "today" msgstr "今" @@ -3468,7 +3529,7 @@ msgstr "我的应用" msgid "Ticket comment" msgstr "工单评论" -#: rbac/tree.py:115 tickets/models/ticket/general.py:305 +#: rbac/tree.py:115 tickets/models/ticket/general.py:306 msgid "Ticket" msgstr "工单管理" @@ -4054,10 +4115,6 @@ msgstr "测试收件人" msgid "Tips: Used only as a test mail recipient" msgstr "提示:仅用来作为测试邮件收件人" -#: settings/serializers/email.py:36 -msgid "Use SSL" -msgstr "使用 SSL" - #: settings/serializers/email.py:37 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用 SSL" @@ -4639,13 +4696,13 @@ msgstr "过期。" #, python-format msgid "" "\n" -" Your password has expired, please click this link update password.\n" +" Your password has expired, please click this link update password.\n" " " msgstr "" "\n" -" 您的密码已经过期,请点击 链接 更新密码\n" +" 您的密码已经过期,请点击 链接 更新密码\n" " " #: templates/_message.html:30 @@ -4669,8 +4726,8 @@ msgstr "" #, python-format msgid "" "\n" -" Your information was incomplete. Please click this link to complete your information.\n" +" Your information was incomplete. Please click this link to complete your information.\n" " " msgstr "" "\n" @@ -4682,13 +4739,13 @@ msgstr "" #, python-format msgid "" "\n" -" Your ssh public key not set or expired. Please click this link to update\n" +" Your ssh public key not set or expired. Please click this link to update\n" " " msgstr "" "\n" -" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" +" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" " " #: templates/_mfa_login_field.html:28 @@ -4747,7 +4804,7 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远 msgid "Offline video player" msgstr "离线录像播放器" -#: terminal/api/component/endpoint.py:33 +#: terminal/api/component/endpoint.py:31 msgid "Not found protocol query params" msgstr "" @@ -4779,7 +4836,7 @@ msgstr "测试成功" msgid "Test failure: Account invalid" msgstr "测试失败: 账号无效" -#: terminal/api/component/terminal.py:39 +#: terminal/api/component/terminal.py:38 msgid "Have online sessions" msgstr "有在线会话" @@ -4857,26 +4914,33 @@ msgid "Timestamp" msgstr "时间戳" #: terminal/backends/command/serializers.py:41 -#: terminal/models/component/terminal.py:87 +#: terminal/models/component/terminal.py:84 msgid "Remote Address" msgstr "远端地址" -#: terminal/const.py:33 +#: terminal/const.py:37 msgid "Critical" msgstr "严重" -#: terminal/const.py:34 +#: terminal/const.py:38 msgid "High" msgstr "较高" -#: terminal/const.py:35 users/templates/users/reset_password.html:50 +#: terminal/const.py:39 users/templates/users/reset_password.html:50 msgid "Normal" msgstr "正常" -#: terminal/const.py:36 +#: terminal/const.py:40 msgid "Offline" msgstr "离线" +#: terminal/const.py:80 terminal/const.py:81 terminal/const.py:82 +#: terminal/const.py:83 terminal/const.py:84 +#, fuzzy +#| msgid "Client" +msgid "DB Client" +msgstr "客户端" + #: terminal/exceptions.py:8 msgid "Bulk create not support" msgstr "不支持批量创建" @@ -5018,20 +5082,20 @@ msgid "Default storage" msgstr "默认存储" #: terminal/models/component/storage.py:136 -#: terminal/models/component/terminal.py:88 +#: terminal/models/component/terminal.py:85 msgid "Command storage" msgstr "命令存储" #: terminal/models/component/storage.py:196 -#: terminal/models/component/terminal.py:89 +#: terminal/models/component/terminal.py:86 msgid "Replay storage" msgstr "录像存储" -#: terminal/models/component/terminal.py:85 +#: terminal/models/component/terminal.py:82 msgid "type" msgstr "类型" -#: terminal/models/component/terminal.py:161 +#: terminal/models/component/terminal.py:159 msgid "Can view terminal config" msgstr "可以查看终端配置" @@ -5274,10 +5338,6 @@ msgstr "地域" msgid "Container name" msgstr "容器名称" -#: terminal/serializers/storage.py:111 -msgid "Account name" -msgstr "账号名称" - #: terminal/serializers/storage.py:112 msgid "Account key" msgstr "账号密钥" @@ -5330,7 +5390,7 @@ msgstr "查看" msgid "Tickets" msgstr "工单管理" -#: tickets/const.py:10 +#: tickets/const.py:9 msgid "Apply for asset" msgstr "申请资产" @@ -5338,23 +5398,23 @@ msgstr "申请资产" msgid "Open" msgstr "打开" -#: tickets/const.py:17 tickets/const.py:30 -msgid "Approved" -msgstr "已同意" - #: tickets/const.py:18 tickets/const.py:31 -msgid "Rejected" -msgstr "已拒绝" - -#: tickets/const.py:20 tickets/const.py:33 msgid "Reopen" msgstr "" -#: tickets/const.py:32 tickets/const.py:39 +#: tickets/const.py:19 tickets/const.py:32 +msgid "Approved" +msgstr "已同意" + +#: tickets/const.py:20 tickets/const.py:33 +msgid "Rejected" +msgstr "已拒绝" + +#: tickets/const.py:30 tickets/const.py:38 msgid "Closed" msgstr "关闭的" -#: tickets/const.py:45 +#: tickets/const.py:46 msgid "Approve" msgstr "同意" @@ -5367,21 +5427,21 @@ msgid "Two level" msgstr "2 级" #: tickets/const.py:55 -msgid "Super admin" -msgstr "超级管理员" - -#: tickets/const.py:56 msgid "Org admin" msgstr "组织管理员" -#: tickets/const.py:57 -msgid "Super admin and org admin" -msgstr "组织管理员或超级管理员" - -#: tickets/const.py:58 +#: tickets/const.py:56 msgid "Custom user" msgstr "自定义用户" +#: tickets/const.py:57 +msgid "Super admin" +msgstr "超级管理员" + +#: tickets/const.py:58 +msgid "Super admin and org admin" +msgstr "组织管理员或超级管理员" + #: tickets/errors.py:9 msgid "Ticket already closed" msgstr "工单已经关闭" @@ -5409,18 +5469,6 @@ msgstr "变更后" msgid "{} {} the ticket" msgstr "{} {} 工单" -#: tickets/handlers/login_confirm.py:18 -msgid "Applied login IP" -msgstr "申请登录的IP" - -#: tickets/handlers/login_confirm.py:19 -msgid "Applied login city" -msgstr "申请登录的城市" - -#: tickets/handlers/login_confirm.py:20 -msgid "Applied login datetime" -msgstr "申请登录的日期" - #: tickets/models/comment.py:14 msgid "common" msgstr "" @@ -5434,15 +5482,15 @@ msgid "Body" msgstr "内容" #: tickets/models/flow.py:20 tickets/models/flow.py:62 -#: tickets/models/ticket/general.py:37 +#: tickets/models/ticket/general.py:39 msgid "Approve level" msgstr "审批级别" -#: tickets/models/flow.py:25 tickets/serializers/flow.py:17 +#: tickets/models/flow.py:25 tickets/serializers/flow.py:18 msgid "Approve strategy" msgstr "审批策略" -#: tickets/models/flow.py:30 tickets/serializers/flow.py:19 +#: tickets/models/flow.py:30 tickets/serializers/flow.py:20 msgid "Assignees" msgstr "受理人" @@ -5458,16 +5506,16 @@ msgstr "工单流程" msgid "Ticket session relation" msgstr "工单会话" -#: tickets/models/ticket/apply_application.py:11 +#: tickets/models/ticket/apply_application.py:10 #: tickets/models/ticket/apply_asset.py:13 msgid "Permission name" msgstr "授权规则名称" -#: tickets/models/ticket/apply_application.py:20 +#: tickets/models/ticket/apply_application.py:19 msgid "Apply applications" msgstr "申请应用" -#: tickets/models/ticket/apply_application.py:23 +#: tickets/models/ticket/apply_application.py:22 msgid "Apply system users" msgstr "申请的系统用户" @@ -5499,13 +5547,13 @@ msgid "Run asset" msgstr "运行的资产" #: tickets/models/ticket/command_confirm.py:13 -msgid "Run account" -msgstr "账号" - -#: tickets/models/ticket/command_confirm.py:14 msgid "Run command" msgstr "运行的命令" +#: tickets/models/ticket/command_confirm.py:14 +msgid "Run account" +msgstr "账号" + #: tickets/models/ticket/command_confirm.py:21 msgid "From cmd filter" msgstr "来自命令过滤规则" @@ -5514,19 +5562,19 @@ msgstr "来自命令过滤规则" msgid "From cmd filter rule" msgstr "来自命令过滤规则" -#: tickets/models/ticket/general.py:72 +#: tickets/models/ticket/general.py:74 msgid "Ticket step" msgstr "工单步骤" -#: tickets/models/ticket/general.py:90 +#: tickets/models/ticket/general.py:92 msgid "Ticket assignee" msgstr "工单受理人" -#: tickets/models/ticket/general.py:270 +#: tickets/models/ticket/general.py:271 msgid "Title" msgstr "标题" -#: tickets/models/ticket/general.py:286 +#: tickets/models/ticket/general.py:287 msgid "Applicant" msgstr "申请人" @@ -5542,23 +5590,23 @@ msgstr "审批步骤" msgid "Relation snapshot" msgstr "工单快照" -#: tickets/models/ticket/general.py:390 +#: tickets/models/ticket/general.py:391 msgid "Please try again" msgstr "请再次尝试" -#: tickets/models/ticket/general.py:421 +#: tickets/models/ticket/general.py:424 msgid "Super ticket" msgstr "超级工单" -#: tickets/models/ticket/login_asset_confirm.py:12 +#: tickets/models/ticket/login_asset_confirm.py:11 msgid "Login user" msgstr "登录用户" -#: tickets/models/ticket/login_asset_confirm.py:16 +#: tickets/models/ticket/login_asset_confirm.py:14 msgid "Login asset" msgstr "登录资产" -#: tickets/models/ticket/login_asset_confirm.py:19 +#: tickets/models/ticket/login_asset_confirm.py:17 msgid "Login account" msgstr "登录账号" @@ -5590,15 +5638,15 @@ msgstr "你的工单已被处理, 处理人 - {}" msgid "Ticket has processed - {} ({})" msgstr "你的工单已被处理, 处理人 - {} ({})" -#: tickets/serializers/flow.py:20 +#: tickets/serializers/flow.py:21 msgid "Assignees display" msgstr "受理人名称" -#: tickets/serializers/flow.py:46 +#: tickets/serializers/flow.py:47 msgid "Please select the Assignees" msgstr "请选择受理人" -#: tickets/serializers/flow.py:74 +#: tickets/serializers/flow.py:75 msgid "The current organization type already exists" msgstr "当前组织已存在该类型" @@ -5621,11 +5669,11 @@ msgstr "通过工单创建 ({}-{})" msgid "The expiration date should be greater than the start date" msgstr "过期时间要大于开始时间" -#: tickets/serializers/ticket/common.py:83 +#: tickets/serializers/ticket/common.py:84 msgid "Permission named `{}` already exists" msgstr "授权名称 `{}` 已存在" -#: tickets/serializers/ticket/ticket.py:85 +#: tickets/serializers/ticket/ticket.py:83 msgid "The ticket flow `{}` does not exist" msgstr "工单流程 `{}` 不存在" @@ -6473,6 +6521,10 @@ msgstr "云管中心" msgid "Provider" msgstr "云服务商" +#: xpack/plugins/cloud/models.py:36 +msgid "Validity" +msgstr "有效" + #: xpack/plugins/cloud/models.py:41 msgid "Cloud account" msgstr "云账号" @@ -6925,6 +6977,34 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "" +#~ "Format for comma-delimited string, with * indicating a match all. " +#~ "Protocol options: {}" +#~ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" + +#~ msgid "User has no permission to access asset or permission expired" +#~ msgstr "用户没有权限访问资产或权限已过期" + +#~ msgid "AdHoc execution" +#~ msgstr "任务执行" + +#, fuzzy +#~| msgid "Disable" +#~ msgid "Variables" +#~ msgstr "禁用" + +#~ msgid "Owner" +#~ msgstr "Owner" + +#~ msgid "Applied login IP" +#~ msgstr "申请登录的IP" + +#~ msgid "Applied login city" +#~ msgstr "申请登录的城市" + +#~ msgid "Applied login datetime" +#~ msgstr "申请登录的日期" + #~ msgid "Type display" #~ msgstr "类型名称" @@ -7116,9 +7196,6 @@ msgstr "社区版" #~ msgid "Target url" #~ msgstr "目标URL" -#~ msgid "Custom Username" -#~ msgstr "自定义用户名" - #~ msgid "Mysql workbench username" #~ msgstr "Mysql 工作台 用户名" @@ -7453,8 +7530,5 @@ msgstr "社区版" #~ msgstr "" #~ "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" -#~ msgid "Run system user" -#~ msgstr "运行的系统用户" - #~ msgid "Login system user" #~ msgstr "登录系统用户" diff --git a/apps/ops/api/job.py b/apps/ops/api/job.py index 65d741b52..c5dfbc1e1 100644 --- a/apps/ops/api/job.py +++ b/apps/ops/api/job.py @@ -28,10 +28,21 @@ class JobViewSet(OrgBulkModelViewSet): def perform_create(self, serializer): instance = serializer.save() - if instance.instant: - execution = instance.create_execution() - task = run_ops_job_execution.delay(execution.id) - set_task_to_serializer_data(serializer, task) + run_after_save = serializer.validated_data.get('run_after_save', False) + if instance.instant or run_after_save: + self.run_job(instance, serializer) + + def perform_update(self, serializer): + instance = serializer.save() + run_after_save = serializer.validated_data.get('run_after_save', False) + if run_after_save: + self.run_job(instance, serializer) + + @staticmethod + def run_job(job, serializer): + execution = job.create_execution() + task = run_ops_job_execution.delay(execution.id) + set_task_to_serializer_data(serializer, task) class JobExecutionViewSet(OrgBulkModelViewSet): diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index bda7dfe33..e8cf1ff16 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -4,6 +4,7 @@ import zipfile from django.conf import settings from orgs.mixins.api import OrgBulkModelViewSet +from ..exception import PlaybookNoValidEntry from ..models import Playbook from ..serializers.playbook import PlaybookSerializer @@ -25,6 +26,10 @@ class PlaybookViewSet(OrgBulkModelViewSet): instance = serializer.save() src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name) dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__()) - if os.path.exists(dest_path): - os.makedirs(dest_path) unzip_playbook(src_path, dest_path) + valid_entry = ('main.yml', 'main.yaml', 'main') + for f in os.listdir(dest_path): + if f in valid_entry: + return + os.remove(dest_path) + raise PlaybookNoValidEntry diff --git a/apps/ops/exception.py b/apps/ops/exception.py new file mode 100644 index 000000000..9cd98a94d --- /dev/null +++ b/apps/ops/exception.py @@ -0,0 +1,6 @@ +from common.exceptions import JMSException +from django.utils.translation import gettext_lazy as _ + + +class PlaybookNoValidEntry(JMSException): + default_detail = _('no valid program entry found.') diff --git a/apps/ops/models/job.py b/apps/ops/models/job.py index 5ce5f38c8..ff858a4fb 100644 --- a/apps/ops/models/job.py +++ b/apps/ops/models/job.py @@ -136,7 +136,7 @@ class JobExecution(JMSOrgBaseModel): ) elif self.job.type == 'playbook': runner = PlaybookRunner( - self.inventory_path, self.job.playbook.work_path + self.inventory_path, self.job.playbook.entry ) else: raise Exception("unsupported job type") diff --git a/apps/ops/models/playbook.py b/apps/ops/models/playbook.py index eb767649a..f92968762 100644 --- a/apps/ops/models/playbook.py +++ b/apps/ops/models/playbook.py @@ -5,6 +5,7 @@ from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ +from ops.exception import PlaybookNoValidEntry from orgs.mixins.models import JMSOrgBaseModel @@ -16,5 +17,10 @@ class Playbook(JMSOrgBaseModel): comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True) @property - def work_path(self): - return os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__(), "main.yaml") + def entry(self): + work_dir = os.path.join(settings.DATA_DIR, "ops", "playbook", self.id.__str__()) + valid_entry = ('main.yml', 'main.yaml', 'main') + for f in os.listdir(work_dir): + if f in valid_entry: + return os.path.join(work_dir, f) + raise PlaybookNoValidEntry diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 5775c7094..19e63f98b 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -1,14 +1,14 @@ +from django.utils.translation import ugettext as _ from rest_framework import serializers from common.drf.fields import ReadableHiddenField from ops.mixin import PeriodTaskSerializerMixin from ops.models import Job, JobExecution from orgs.mixins.serializers import BulkOrgResourceModelSerializer -_all_ = [] - class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) + run_after_save = serializers.BooleanField(label=_("Run after save"), default=False, required=False) class Meta: model = Job @@ -21,7 +21,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): "chdir", "comment", "summary", - "is_periodic", "interval", "crontab" + "is_periodic", "interval", "crontab", "run_after_save" ] diff --git a/apps/ops/serializers/playbook.py b/apps/ops/serializers/playbook.py index 4ff43abb4..1633688be 100644 --- a/apps/ops/serializers/playbook.py +++ b/apps/ops/serializers/playbook.py @@ -14,6 +14,7 @@ def parse_playbook_name(path): class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerializer): creator = ReadableHiddenField(default=serializers.CurrentUserDefault()) + path = serializers.FileField(required=False) def create(self, validated_data): name = validated_data.get('name') @@ -26,5 +27,5 @@ class PlaybookSerializer(BulkOrgResourceModelSerializer, serializers.ModelSerial model = Playbook read_only_fields = ["id", "date_created", "date_updated"] fields = read_only_fields + [ - "id", "name", "comment", "creator", + "id", 'path', "name", "comment", "creator", ] From 2f5e133558a180440c5494e6f1c8b018abfb483e Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Fri, 2 Dec 2022 17:18:11 +0800 Subject: [PATCH 461/488] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96celery?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/models/celery.py | 4 ++++ apps/ops/serializers/celery.py | 5 ++++- apps/ops/serializers/job.py | 7 ++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/ops/models/celery.py b/apps/ops/models/celery.py index 6e53f868d..7b98dc3ab 100644 --- a/apps/ops/models/celery.py +++ b/apps/ops/models/celery.py @@ -71,5 +71,9 @@ class CeleryTaskExecution(models.Model): return self.date_finished - self.date_start return None + @property + def is_success(self): + return self.state == 'SUCCESS' + def __str__(self): return "{}: {}".format(self.name, self.id) diff --git a/apps/ops/serializers/celery.py b/apps/ops/serializers/celery.py index 8d75d227f..776b1fd5b 100644 --- a/apps/ops/serializers/celery.py +++ b/apps/ops/serializers/celery.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals from rest_framework import serializers +from django.utils.translation import gettext_lazy as _ from django_celery_beat.models import PeriodicTask @@ -35,10 +36,12 @@ class CeleryTaskSerializer(serializers.ModelSerializer): class CeleryTaskExecutionSerializer(serializers.ModelSerializer): + is_success = serializers.BooleanField(required=False, read_only=True, label=_('Success')) + class Meta: model = CeleryTaskExecution fields = [ - "id", "name", "args", "kwargs", "time_cost", "timedelta", "state", "is_finished", "date_published", + "id", "name", "args", "kwargs", "time_cost", "timedelta", "is_success", "is_finished", "date_published", "date_start", "date_finished" ] diff --git a/apps/ops/serializers/job.py b/apps/ops/serializers/job.py index 19e63f98b..1851cdfd8 100644 --- a/apps/ops/serializers/job.py +++ b/apps/ops/serializers/job.py @@ -8,11 +8,12 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): owner = ReadableHiddenField(default=serializers.CurrentUserDefault()) - run_after_save = serializers.BooleanField(label=_("Run after save"), default=False, required=False) + run_after_save = serializers.BooleanField(label=_("Run after save"), read_only=True, default=False, required=False) class Meta: model = Job - read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost"] + read_only_fields = ["id", "date_last_run", "date_created", "date_updated", "average_time_cost", + "run_after_save"] fields = read_only_fields + [ "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner", "use_parameter_define", @@ -21,7 +22,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin): "chdir", "comment", "summary", - "is_periodic", "interval", "crontab", "run_after_save" + "is_periodic", "interval", "crontab" ] From 0e0a9f4654783fbf711157a4d2b581e586044a33 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 2 Dec 2022 17:36:55 +0800 Subject: [PATCH 462/488] perf: gateway account (#9150) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/api/asset/asset.py | 8 +++---- apps/assets/models/base.py | 5 ++-- apps/assets/models/domain.py | 39 ++++++++++++++++++++++++++++++- apps/assets/serializers/domain.py | 23 +++++++++++------- apps/ops/ansible/inventory.py | 4 ++-- 5 files changed, 60 insertions(+), 19 deletions(-) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index 72264048a..df139a8c9 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -7,7 +7,7 @@ from rest_framework.response import Response from assets import serializers from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBackend -from assets.models import Asset +from assets.models import Asset, Gateway from assets.tasks import ( push_accounts_to_assets, test_assets_connectivity_manual, update_assets_hardware_info_manual, verify_accounts_connectivity, @@ -57,7 +57,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): ("default", serializers.AssetSerializer), ("suggestion", serializers.MiniAssetSerializer), ("platform", serializers.PlatformSerializer), - ("gateways", serializers.GatewayWithAuthSerializer), + ("gateways", serializers.GatewaySerializer), ) rbac_perms = ( ("match", "assets.match_asset"), @@ -76,9 +76,9 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): def gateways(self, *args, **kwargs): asset = self.get_object() if not asset.domain: - gateways = Asset.objects.none() + gateways = Gateway.objects.none() else: - gateways = asset.domain.gateways.filter(protocol="ssh") + gateways = asset.domain.gateways return self.get_paginated_response_from_queryset(gateways) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 59c135847..30eafa6b7 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -6,7 +6,6 @@ from hashlib import md5 import sshpubkeys from django.conf import settings from django.db import models -from django.db.models import QuerySet from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -35,7 +34,7 @@ class AbsConnectivity(models.Model): @classmethod def bulk_set_connectivity(cls, queryset_or_id, connectivity): - if not isinstance(queryset_or_id, QuerySet): + if not isinstance(queryset_or_id, models.QuerySet): queryset = cls.objects.filter(id__in=queryset_or_id) else: queryset = queryset_or_id @@ -87,7 +86,7 @@ class BaseAccount(JMSOrgBaseModel): @lazyproperty def public_key(self): if self.secret_type == SecretType.SSH_KEY and self.private_key: - return parse_ssh_public_key_str(private_key=self.private_key) + return parse_ssh_public_key_str(self.private_key) return None @property diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 9580b17ba..3825ae742 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin from assets.models import Host, Platform -from assets.const import GATEWAY_NAME +from assets.const import GATEWAY_NAME, SecretType from orgs.mixins.models import OrgManager logger = get_logger(__file__) @@ -80,3 +80,40 @@ class Gateway(Host): platform = self.default_platform self.platform_id = platform.id return super().save(*args, **kwargs) + + @lazyproperty + def select_accounts(self) -> dict: + account_dict = {} + accounts = self.accounts.filter(is_active=True).order_by('-privileged', '-date_updated') + password_account = accounts.filter(secret_type=SecretType.PASSWORD).first() + if password_account: + account_dict[SecretType.PASSWORD] = password_account + + ssh_key_account = accounts.filter(secret_type=SecretType.SSH_KEY).first() + if ssh_key_account: + account_dict[SecretType.SSH_KEY] = ssh_key_account + return account_dict + + @property + def password(self): + account = self.select_accounts.get(SecretType.PASSWORD) + return account.secret if account else None + + @property + def private_key(self): + account = self.select_accounts.get(SecretType.SSH_KEY) + return account.secret if account else None + + def private_key_path(self): + account = self.select_accounts.get(SecretType.SSH_KEY) + return account.private_key_path if account else None + + @lazyproperty + def username(self): + accounts = self.select_accounts.values() + if len(accounts) == 0: + return None + accounts = sorted( + accounts, key=lambda x: x['privileged'], reverse=True + ) + return accounts[0].username diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 8ee651e88..b06a1afc8 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -39,21 +39,26 @@ class DomainSerializer(BulkOrgResourceModelSerializer): class GatewaySerializer(HostSerializer): + effective_accounts = serializers.SerializerMethodField() + class Meta(HostSerializer.Meta): model = Gateway + fields = HostSerializer.Meta.fields + ['effective_accounts'] - -class GatewayWithAuthSerializer(SecretReadableMixin, GatewaySerializer): - class Meta(GatewaySerializer.Meta): - extra_kwargs = { - 'password': {'write_only': False}, - 'private_key': {"write_only": False}, - 'public_key': {"write_only": False}, - } + @staticmethod + def get_effective_accounts(obj): + accounts = obj.select_accounts.values() + return [ + { + 'id': account.id, + 'username': account.username, + 'secret_type': account.secret_type, + } for account in accounts + ] class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer): - gateways = GatewayWithAuthSerializer(many=True, read_only=True) + gateways = GatewaySerializer(many=True, read_only=True) class Meta: model = Domain diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index f71801b76..1258742e0 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -50,7 +50,7 @@ class JMSInventory: 0, "sshpass -p '{}'".format(gateway.password) ) if gateway.private_key: - proxy_command_list.append("-i {}".format(gateway.private_key_file)) + proxy_command_list.append("-i {}".format(gateway.private_key_path)) proxy_command = "'-o ProxyCommand={}'".format( " ".join(proxy_command_list) @@ -67,7 +67,7 @@ class JMSInventory: if account.secret_type == 'password': var['ansible_password'] = account.secret elif account.secret_type == 'ssh_key': - var['ansible_ssh_private_key_file'] = account.private_key_file + var['ansible_ssh_private_key_file'] = account.private_key_path return var def make_ssh_account_vars(self, host, asset, account, automation, protocols, platform, gateway): From aa0dabfd108c66a1308f5595d8089529b45d06cd Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Fri, 2 Dec 2022 17:27:42 +0800 Subject: [PATCH 463/488] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E7=94=A8migrations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...erge_0004_connectacl_0007_auto_20221202_1048.py | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py diff --git a/apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py b/apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py deleted file mode 100644 index 96ba9d4a4..000000000 --- a/apps/acls/migrations/0008_merge_0004_connectacl_0007_auto_20221202_1048.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 3.2.14 on 2022-12-02 03:15 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('acls', '0004_connectacl'), - ('acls', '0007_auto_20221202_1048'), - ] - - operations = [ - ] From c8c5aca355b4e8bb4000c9aeb3ae8fd6b832b824 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 2 Dec 2022 17:48:44 +0800 Subject: [PATCH 464/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20ConnectionT?= =?UTF-8?q?oken=20Serializer=20=E5=91=BD=E4=BB=A4=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/command_acl.py | 21 ++++++++++--------- .../authentication/models/connection_token.py | 8 +++---- .../serializers/connection_token.py | 20 ++++++++++-------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index bec473250..37ce641f9 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -124,11 +124,9 @@ class CommandFilterACL(OrgModelMixin, BaseACL): return ticket @classmethod - def get_queryset( - cls, user_id=None, user_group_id=None, account=None, - asset_id=None, org_id=None - ): - from assets.models import Account + def get_command_groups(cls, user_id=None, user_group_id=None, account=None, asset_id=None, org_id=None): + + from assets.models import Account, Asset user_groups = [] user = get_object_or_none(User, pk=user_id) if user: @@ -152,11 +150,14 @@ class CommandFilterACL(OrgModelMixin, BaseACL): org_id = asset.org_id q |= Q(assets=asset) if q: - cmd_filters = CommandFilter.objects.filter(q).filter(is_active=True) + cmd_filters = cls.objects.filter(q).filter(is_active=True) if org_id: cmd_filters = cmd_filters.filter(org_id=org_id) - rule_ids = cmd_filters.values_list('rules', flat=True) - rules = cls.objects.filter(id__in=rule_ids) + filter_ids = cmd_filters.values_list('id', flat=True) + command_group_ids = cls.commands.through.objects\ + .filter(commandfilteracl_id__in=filter_ids)\ + .values_list('commandgroup_id', flat=True) + cmd_groups = CommandGroup.objects.filter(id__in=command_group_ids) else: - rules = cls.objects.none() - return rules + cmd_groups = CommandGroup.objects.none() + return cmd_groups diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 850a6c589..b3f5ba981 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -156,16 +156,16 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): return self.domain.random_gateway() @lazyproperty - def cmd_filter_rules(self): - from assets.models import CommandFilterRule + def acl_command_groups(self): + from acls.models import CommandFilterACL kwargs = { 'user_id': self.user.id, 'account': self.account, } if self.asset: kwargs['asset_id'] = self.asset.id - rules = CommandFilterRule.get_queryset(**kwargs) - return rules + cmd_groups = CommandFilterACL.get_command_groups(**kwargs) + return cmd_groups class SuperConnectionToken(ConnectionToken): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 225b8c8db..30ad46975 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -2,6 +2,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from assets.models import Asset, CommandFilterRule, Account, Platform +from acls.models import CommandGroup from assets.serializers import PlatformSerializer, AssetProtocolsSerializer from authentication.models import ConnectionToken from orgs.mixins.serializers import OrgResourceModelSerializerMixin @@ -89,8 +90,9 @@ class ConnectionTokenAssetSerializer(serializers.ModelSerializer): class Meta: model = Asset - fields = ['id', 'name', 'address', 'protocols', - 'org_id', 'specific'] + fields = [ + 'id', 'name', 'address', 'protocols', 'category', 'type', 'org_id', 'specific' + ] class SimpleAccountSerializer(serializers.ModelSerializer): @@ -123,14 +125,14 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): ] -class ConnectionTokenCmdFilterRuleSerializer(serializers.ModelSerializer): - """ Command filter rule """ +class ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): + """ ACL command group""" class Meta: - model = CommandFilterRule + model = CommandGroup fields = [ 'id', 'type', 'content', 'ignore_case', 'pattern', - 'priority', 'action', 'date_created', + 'action', 'date_created', ] @@ -145,23 +147,23 @@ class ConnectionTokenPlatform(PlatformSerializer): class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): - expire_now = serializers.BooleanField(label=_('Expired now'), default=True) user = ConnectionTokenUserSerializer(read_only=True) asset = ConnectionTokenAssetSerializer(read_only=True) account = ConnectionTokenAccountSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) platform = ConnectionTokenPlatform(read_only=True) - # cmd_filter_rules = ConnectionTokenCmdFilterRuleSerializer(many=True) + acl_command_groups = ConnectionTokenACLCmdGroupSerializer(read_only=True, many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() + expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) class Meta: model = ConnectionToken fields = [ 'id', 'value', 'user', 'asset', 'account', 'platform', + 'acl_command_groups', 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', ] extra_kwargs = { 'value': {'read_only': True}, - 'expire_now': {'write_only': True}, } From 52e80824706b995cf1fb158abb8221eb61b7f3f5 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 2 Dec 2022 17:52:51 +0800 Subject: [PATCH 465/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20LoginAssetA?= =?UTF-8?q?CL=20=E8=BF=87=E6=BB=A4=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/login_asset_check.py | 2 +- apps/acls/models/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index 6ebee2579..73c200f1e 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -32,7 +32,7 @@ class LoginAssetCheckAPI(CreateAPIView): def check_confirm(self): with tmp_to_org(self.serializer.asset.org): acl = LoginAssetACL.objects \ - .filter(action=LoginAssetACL.ActionChoices.confirm) \ + .filter(action=LoginAssetACL.ActionChoices.review) \ .filter_user(self.serializer.user) \ .filter_asset(self.serializer.asset) \ .filter_account(self.serializer.validated_data.get('account_username')) \ diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 33e1fbc2a..83e2a5cde 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -11,7 +11,7 @@ __all__ = ['BaseACL', 'BaseACLQuerySet', 'ACLManager', 'AssetAccountUserACLQuery class ActionChoices(models.TextChoices): reject = 'reject', _('Reject') - accept = 'allow', _('Allow') + accept = 'accept', _('Accept') review = 'review', _('Review') From a70f85e3469c6c4380c5e20ece5ec5a4a240a8af Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 2 Dec 2022 18:09:07 +0800 Subject: [PATCH 466/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20ConnectionT?= =?UTF-8?q?oken=20Serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/command_acl.py | 2 ++ apps/authentication/models/connection_token.py | 11 ++++++++--- apps/authentication/serializers/connection_token.py | 7 +++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 37ce641f9..30c19a4b3 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -125,6 +125,8 @@ class CommandFilterACL(OrgModelMixin, BaseACL): @classmethod def get_command_groups(cls, user_id=None, user_group_id=None, account=None, asset_id=None, org_id=None): + # Todo: Do + return CommandGroup.objects.all() from assets.models import Account, Asset user_groups = [] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index b3f5ba981..e987fc48c 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -121,26 +121,31 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): @lazyproperty def account(self): + from assets.models import Account if not self.asset: return None account = self.asset.accounts.filter(name=self.account_name).first() if self.account_name == '@INPUT' or not account: - return { + data = { 'name': self.account_name, 'username': self.input_username, 'secret_type': 'password', 'secret': self.input_secret, - 'su_from': None + 'su_from': None, + 'org_id': self.asset.org_id } + Account(**data) else: - return { + data = { 'name': account.name, 'username': account.username, 'secret_type': account.secret_type, 'secret': account.secret or self.input_secret, 'su_from': account.su_from, + 'org_id': account.org_id } + return Account(**data) @lazyproperty def domain(self): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 30ad46975..3fefbb5af 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -120,8 +120,8 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): class Meta: model = Asset fields = [ - 'id', 'address', 'port', 'username', - 'password', 'private_key' + 'id', 'address', 'port', + # 'username', 'password', 'private_key' ] @@ -131,8 +131,7 @@ class ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): class Meta: model = CommandGroup fields = [ - 'id', 'type', 'content', 'ignore_case', 'pattern', - 'action', 'date_created', + 'id', 'type', 'content', 'ignore_case', 'pattern' ] From fbea1f3480bc8f790d9202b4ce40d774bcdf36b9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 19:56:13 +0800 Subject: [PATCH 467/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E6=8B=BC=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/encode.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index db5aee795..b06d96a3d 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -7,6 +7,7 @@ import os import re import time from io import StringIO +import logging import paramiko import sshpubkeys @@ -74,13 +75,13 @@ _supported_paramiko_ssh_key_types = (paramiko.RSAKey, paramiko.DSSKey, paramiko. def ssh_key_string_to_obj(text, password=None): key = None for ssh_key_type in _supported_paramiko_ssh_key_types: - if not isinstance(ssh_key_type, paramiko.PKey): - continue try: key = ssh_key_type.from_private_key(StringIO(text), password=password) return key except paramiko.SSHException: pass + if key is None: + raise ValueError('Invalid private key') return key @@ -152,9 +153,11 @@ def parse_ssh_private_key_str(text: bytes, password=None) -> str: private_key = _parse_ssh_private_key(text, password=password) if private_key is None: return "" - private_key_bytes = private_key.private_bytes(serialization.Encoding.PEM, - serialization.PrivateFormat.OpenSSH, - serialization.NoEncryption()) + private_key_bytes = private_key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.OpenSSH, + serialization.NoEncryption() + ) return private_key_bytes.decode('utf-8') @@ -193,6 +196,7 @@ def _parse_ssh_private_key(text, password=None): private_key = serialization.load_ssh_private_key(text, password=password) return private_key except (ValueError, TypeError): + logging.error("Invalid private key") pass return None From 052a4afef601ce3b814927e4a9b7d578e5dce55c Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 20:06:56 +0800 Subject: [PATCH 468/488] =?UTF-8?q?pref:=20=E6=9A=82=E6=97=B6=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20key=20fingerprint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/base.py | 2 ++ apps/common/utils/encode.py | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 30eafa6b7..3ecfd4deb 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -91,6 +91,8 @@ class BaseAccount(JMSOrgBaseModel): @property def ssh_key_fingerprint(self): + # Todo: 等待修复 + return '等待修复' if self.public_key: public_key = self.public_key elif self.private_key: diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index b06d96a3d..81856b931 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -165,8 +165,10 @@ def parse_ssh_public_key_str(text: bytes = "", password=None) -> str: private_key = _parse_ssh_private_key(text, password=password) if private_key is None: return "" - public_key_bytes = private_key.public_key().public_bytes(serialization.Encoding.OpenSSH, - serialization.PublicFormat.OpenSSH) + public_key_bytes = private_key.public_key().public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.OpenSSH, + ) return public_key_bytes.decode('utf-8') @@ -185,12 +187,11 @@ def _parse_ssh_private_key(text, password=None): text = text.encode("utf-8") except UnicodeDecodeError: return None - if password is not None: - if isinstance(password, str): - try: - password = password.encode("utf-8") - except UnicodeDecodeError: - return None + if isinstance(password, str): + try: + password = password.encode("utf-8") + except UnicodeDecodeError: + return None try: private_key = serialization.load_ssh_private_key(text, password=password) From 156a6c9dc54d08271fc9290bf7200cc480f2d1b5 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Fri, 2 Dec 2022 20:28:49 +0800 Subject: [PATCH 469/488] perf: gateway test connective (#9152) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/models/base.py | 6 +++ apps/assets/models/domain.py | 78 ++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 3ecfd4deb..e16671691 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -32,6 +32,12 @@ class AbsConnectivity(models.Model): self.date_verified = timezone.now() self.save(update_fields=['connectivity', 'date_verified']) + @property + def is_connective(self): + if self.connectivity == Connectivity.OK: + return True + return False + @classmethod def bulk_set_connectivity(cls, queryset_or_id, connectivity): if not isinstance(queryset_or_id, models.QuerySet): diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 3825ae742..e9894f72c 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -2,15 +2,16 @@ # import uuid import random - +import socket import paramiko + from django.db import models from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin from assets.models import Host, Platform -from assets.const import GATEWAY_NAME, SecretType +from assets.const import GATEWAY_NAME, SecretType, Connectivity from orgs.mixins.models import OrgManager logger = get_logger(__file__) @@ -102,8 +103,14 @@ class Gateway(Host): @property def private_key(self): account = self.select_accounts.get(SecretType.SSH_KEY) - return account.secret if account else None + return account.private_key if account else None + @property + def private_key_obj(self): + account = self.select_accounts.get(SecretType.SSH_KEY) + return account.private_key_obj if account else None + + @property def private_key_path(self): account = self.select_accounts.get(SecretType.SSH_KEY) return account.private_key_path if account else None @@ -117,3 +124,68 @@ class Gateway(Host): accounts, key=lambda x: x['privileged'], reverse=True ) return accounts[0].username + + def test_connective(self, local_port=None): + local_port = self.port if local_port is None else local_port + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + proxy = paramiko.SSHClient() + proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + try: + proxy.connect( + self.address, + port=self.port, + username=self.username, + password=self.password, + pkey=self.private_key_obj + ) + except( + paramiko.AuthenticationException, + paramiko.BadAuthenticationType, + paramiko.SSHException, + paramiko.ChannelException, + paramiko.ssh_exception.NoValidConnectionsError, + socket.gaierror + ) as e: + err = str(e) + if err.startswith('[Errno None] Unable to connect to port'): + err = _('Unable to connect to port {port} on {address}') + err = err.format(port=self.port, address=self.address) + elif err == 'Authentication failed.': + err = _('Authentication failed') + elif err == 'Connect failed': + err = _('Connect failed') + self.set_connectivity(Connectivity.FAILED) + return False, err + + try: + sock = proxy.get_transport().open_channel( + 'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0) + ) + client.connect( + '127.0.0.1', + sock=sock, + timeout=5, + port=local_port, + username=self.username, + password=self.password, + key_filename=self.private_key_path, + ) + except ( + paramiko.SSHException, + paramiko.ssh_exception.SSHException, + paramiko.ChannelException, + paramiko.AuthenticationException, + TimeoutError + ) as e: + + err = getattr(e, 'text', str(e)) + if err == 'Connect failed': + err = _('Connect failed') + self.set_connectivity(Connectivity.FAILED) + return False, err + finally: + client.close() + self.set_connectivity(Connectivity.OK) + return True, None From 2cf3a21d27bf4337f3d6b325303e5b1efcdd8b69 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 2 Dec 2022 20:33:32 +0800 Subject: [PATCH 470/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/account.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/perms/utils/account.py b/apps/perms/utils/account.py index b394bfbc4..7c0caf988 100644 --- a/apps/perms/utils/account.py +++ b/apps/perms/utils/account.py @@ -9,15 +9,15 @@ __all__ = ['PermAccountUtil'] class PermAccountUtil(AssetPermissionUtil): """ 资产授权账号相关的工具 """ - def validate_permission(self, user, asset, account_username): + def validate_permission(self, user, asset, account_name): """ 校验用户有某个资产下某个账号名的权限 :param user: User :param asset: Asset - :param account_username: 可能是 @USER @INPUT 字符串 + :param account_name: 可能是 @USER @INPUT 字符串 """ permed_accounts = self.get_permed_accounts_for_user(user, asset) - accounts_mapper = {account.username: account for account in permed_accounts} - account = accounts_mapper.get(account_username) + accounts_mapper = {account.name: account for account in permed_accounts} + account = accounts_mapper.get(account_name) return account def get_permed_accounts_for_user(self, user, asset): From 6480b916d61ac2023d1403c301e6d32a0a26e232 Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 3 Dec 2022 15:25:04 +0800 Subject: [PATCH 471/488] perf: parse ssh private key --- apps/assets/models/base.py | 2 -- apps/common/utils/encode.py | 34 +++++++++++++++------------------- apps/ops/ws.py | 2 +- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index e16671691..0885f5b88 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -97,8 +97,6 @@ class BaseAccount(JMSOrgBaseModel): @property def ssh_key_fingerprint(self): - # Todo: 等待修复 - return '等待修复' if self.public_key: public_key = self.public_key elif self.private_key: diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index 81856b931..9eefde044 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -7,7 +7,6 @@ import os import re import time from io import StringIO -import logging import paramiko import sshpubkeys @@ -69,7 +68,11 @@ class Signer(metaclass=Singleton): return None -_supported_paramiko_ssh_key_types = (paramiko.RSAKey, paramiko.DSSKey, paramiko.Ed25519Key) +_supported_paramiko_ssh_key_types = ( + paramiko.RSAKey, + paramiko.DSSKey, + paramiko.Ed25519Key, + paramiko.ECDSAKey,) def ssh_key_string_to_obj(text, password=None): @@ -134,17 +137,6 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h def validate_ssh_private_key(text, password=None): - if isinstance(text, str): - try: - text = text.encode("utf-8") - except UnicodeDecodeError: - return False - if isinstance(password, str): - try: - password = password.encode("utf-8") - except UnicodeDecodeError: - return False - key = parse_ssh_private_key_str(text, password=password) return bool(key) @@ -153,6 +145,7 @@ def parse_ssh_private_key_str(text: bytes, password=None) -> str: private_key = _parse_ssh_private_key(text, password=password) if private_key is None: return "" + # 解析之后,转换成 openssh 格式的私钥 private_key_bytes = private_key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.OpenSSH, @@ -194,12 +187,15 @@ def _parse_ssh_private_key(text, password=None): return None try: - private_key = serialization.load_ssh_private_key(text, password=password) - return private_key - except (ValueError, TypeError): - logging.error("Invalid private key") - pass - return None + if is_openssh_format_key(text): + return serialization.load_ssh_private_key(text, password=password) + return serialization.load_pem_private_key(text, password=password) + except (ValueError, TypeError) as e: + raise e + + +def is_openssh_format_key(text: bytes): + return text.startswith(b"-----BEGIN OPENSSH PRIVATE KEY-----") def validate_ssh_public_key(text): diff --git a/apps/ops/ws.py b/apps/ops/ws.py index 473093ba2..24023a33e 100644 --- a/apps/ops/ws.py +++ b/apps/ops/ws.py @@ -71,8 +71,8 @@ class TaskLogWebsocket(AsyncJsonWebsocketConsumer): await asyncio.sleep(0.2) except OSError as e: logger.warn('Task log path open failed: {}'.format(e)) + await self.close() async def disconnect(self, close_code): self.disconnected = True - await self.close() close_old_connections() From 2b5bd558f3dd9293a9e58e60c4248469d3c20d40 Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 4 Dec 2022 00:04:39 +0800 Subject: [PATCH 472/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=BF=87=E6=BB=A4=E7=9B=B8=E5=85=B3=E7=9A=84Model,=20?= =?UTF-8?q?CommandFilterACL,=20CommandGroup;=20=E4=BF=AE=E6=94=B9Model=20Q?= =?UTF-8?q?uerySet=20=E7=9B=B8=E5=85=B3=E7=9A=84=E6=96=B9=E6=B3=95;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/login_asset_check.py | 14 +-- .../migrations/0009_auto_20221204_0001.py | 22 ++++ apps/acls/models/base.py | 91 +++++++++++---- apps/acls/models/command_acl.py | 110 +++++++----------- apps/acls/models/login_acl.py | 6 +- apps/acls/models/login_asset_acl.py | 13 +-- .../authentication/models/connection_token.py | 10 +- 7 files changed, 147 insertions(+), 119 deletions(-) create mode 100644 apps/acls/migrations/0009_auto_20221204_0001.py diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index 73c200f1e..62babfafb 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -31,13 +31,13 @@ class LoginAssetCheckAPI(CreateAPIView): def check_confirm(self): with tmp_to_org(self.serializer.asset.org): - acl = LoginAssetACL.objects \ - .filter(action=LoginAssetACL.ActionChoices.review) \ - .filter_user(self.serializer.user) \ - .filter_asset(self.serializer.asset) \ - .filter_account(self.serializer.validated_data.get('account_username')) \ - .valid() \ - .first() + kwargs = { + 'user': self.serializer.user, + 'asset': self.serializer.asset, + 'account_username': self.serializer.validated_data.get('account_username'), + 'action': LoginAssetACL.ActionChoices.review + } + acl = LoginAssetACL.filter_queryset(**kwargs).valid().first() if acl: need_confirm = True response_data = self._get_response_data_of_need_confirm(acl) diff --git a/apps/acls/migrations/0009_auto_20221204_0001.py b/apps/acls/migrations/0009_auto_20221204_0001.py new file mode 100644 index 000000000..b5286160f --- /dev/null +++ b/apps/acls/migrations/0009_auto_20221204_0001.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.14 on 2022-12-03 16:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0008_commandgroup_comment'), + ] + + operations = [ + migrations.AlterModelOptions( + name='commandgroup', + options={'verbose_name': 'Command group'}, + ), + migrations.RenameField( + model_name='commandfilteracl', + old_name='commands', + new_name='command_groups', + ), + ] diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 83e2a5cde..98036ec92 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -5,8 +5,15 @@ from django.utils.translation import ugettext_lazy as _ from common.mixins import CommonModelMixin from common.utils import contains_ip +from orgs.mixins.models import OrgModelMixin -__all__ = ['BaseACL', 'BaseACLQuerySet', 'ACLManager', 'AssetAccountUserACLQuerySet'] +__all__ = [ + 'ACLManager', + 'BaseACL', + 'BaseACLQuerySet', + 'UserAssetAccountBaseACL', + 'UserAssetAccountACLQuerySet' +] class ActionChoices(models.TextChoices): @@ -29,30 +36,35 @@ class BaseACLQuerySet(models.QuerySet): return self.inactive() -class AssetAccountUserACLQuerySet(BaseACLQuerySet): - def filter_user(self, user): - return self.filter( - Q(users__username_group__contains=user.username) | +class UserAssetAccountACLQuerySet(BaseACLQuerySet): + def filter_user(self, username): + q = Q(users__username_group__contains=username) | \ Q(users__username_group__contains='*') - ) + return self.filter(q) - def filter_asset(self, asset): - queryset = self.filter( - Q(assets__name_group__contains=asset.name) | - Q(assets__name_group__contains='*') - ) - ids = [ - q.id for q in queryset - if contains_ip(asset.address, q.assets.get('address_group', [])) - ] - queryset = self.filter(id__in=ids) + def filter_asset(self, name=None, address=None): + queryset = self.filter() + if name: + q = Q(assets__name_group__contains=name) | \ + Q(assets__name_group__contains='*') + queryset = queryset.filter(q) + if address: + ids = [ + q.id for q in queryset + if contains_ip(address, q.assets.get('address_group', [])) + ] + queryset = queryset.filter(id__in=ids) return queryset - def filter_account(self, account_username): - return self.filter( - Q(accounts__username_group__contains=account_username) | - Q(accounts__username_group__contains='*') - ) + def filter_account(self, name=None, username=None): + q = Q() + if name: + q &= Q(accounts__name_group__contains=name) | \ + Q(accounts__name_group__contains='*') + if username: + q &= Q(accounts__username_group__contains=username) | \ + Q(accounts__username_group__contains='*') + return self.filter(q) class ACLManager(models.Manager): @@ -72,8 +84,43 @@ class BaseACL(CommonModelMixin): is_active = models.BooleanField(default=True, verbose_name=_("Active")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) - objects = ACLManager.from_queryset(BaseACLQuerySet)() ActionChoices = ActionChoices + objects = ACLManager.from_queryset(BaseACLQuerySet)() class Meta: abstract = True + + +class UserAssetAccountBaseACL(BaseACL, OrgModelMixin): + # username_group + users = models.JSONField(verbose_name=_('User')) + # name_group, address_group + assets = models.JSONField(verbose_name=_('Asset')) + # name_group, username_group + accounts = models.JSONField(verbose_name=_('Account')) + + objects = ACLManager.from_queryset(UserAssetAccountACLQuerySet)() + + class Meta: + abstract = True + + @classmethod + def filter_queryset(cls, user=None, asset=None, account=None, account_username=None, **kwargs): + queryset = cls.objects.all() + org_id = None + if user: + queryset = queryset.filter_user(user.username) + if asset: + org_id = asset.org_id + queryset = queryset.filter_asset(asset.name, asset.address) + if account: + org_id = account.org_id + queryset = queryset.filter_account(account.name, account.username) + if account_username: + queryset = queryset.filter_account(username=account_username) + if org_id: + kwargs['org_id'] = org_id + if kwargs: + queryset = queryset.filter(**kwargs) + return queryset + diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 30c19a4b3..8c920c5e7 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -3,37 +3,41 @@ import re from django.db import models -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ -from common.utils import lazyproperty, get_logger, get_object_or_none +from common.utils import lazyproperty, get_logger from orgs.mixins.models import JMSOrgBaseModel -from orgs.mixins.models import OrgModelMixin -from users.models import User, UserGroup -from .base import BaseACL, AssetAccountUserACLQuerySet, ACLManager + +from .base import UserAssetAccountBaseACL, UserAssetAccountACLQuerySet, ACLManager logger = get_logger(__file__) -class CommandGroup(JMSOrgBaseModel): - class Type(models.TextChoices): - command = 'command', _('Command') - regex = 'regex', _('Regex') +class TypeChoices(models.TextChoices): + command = 'command', _('Command') + regex = 'regex', _('Regex') + +class CommandGroup(JMSOrgBaseModel): name = models.CharField(max_length=128, verbose_name=_("Name")) - type = models.CharField(max_length=16, default=Type.command, choices=Type.choices, verbose_name=_("Type")) + type = models.CharField( + max_length=16, default=TypeChoices.command, choices=TypeChoices.choices, + verbose_name=_("Type") + ) content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) ignore_case = models.BooleanField(default=True, verbose_name=_('Ignore case')) comment = models.TextField(blank=True, verbose_name=_("Comment")) + TypeChoices = TypeChoices + class Meta: unique_together = [('org_id', 'name')] - verbose_name = _("Command filter rule") + verbose_name = _("Command group") @lazyproperty def pattern(self): if self.type == 'command': - s = self.construct_command_regex(content=self.content) + s = self.construct_command_regex(self.content) else: s = r'{0}'.format(self.content) return s @@ -62,6 +66,17 @@ class CommandGroup(JMSOrgBaseModel): s = r'{}'.format('|'.join(regex)) return s + def match(self, data): + succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case) + if not succeed: + return False, '' + + found = pattern.search(data) + if not found: + return False, '' + else: + return True, found.group() + @staticmethod def compile_regex(regex, ignore_case): args = [] @@ -75,27 +90,23 @@ class CommandGroup(JMSOrgBaseModel): return False, error, None return True, '', pattern - def match(self, data): - succeed, error, pattern = self.compile_regex(self.pattern, self.ignore_case) - if not succeed: - return False, '' - - found = pattern.search(data) - if not found: - return False, '' - else: - return True, found.group() - def __str__(self): return '{} % {}'.format(self.type, self.content) -class CommandFilterACL(OrgModelMixin, BaseACL): - users = models.JSONField(verbose_name=_('User')) - assets = models.JSONField(verbose_name=_('Asset')) - accounts = models.JSONField(verbose_name=_('Account')) - commands = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) - objects = ACLManager.from_queryset(AssetAccountUserACLQuerySet)() +class CommandFilterACLQuerySet(UserAssetAccountACLQuerySet): + def get_command_groups(self): + ids = self.values_list('id', flat=True) + queryset = CommandFilterACL.command_groups.through.objects.filter(commandfilteracl_id__in=ids) + cmd_group_ids = queryset.values_list('commandgroup_id', flat=True) + command_groups = CommandGroup.objects.filter(id__in=cmd_group_ids) + return command_groups + + +class CommandFilterACL(UserAssetAccountBaseACL): + command_groups = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) + + objects = ACLManager.from_queryset(CommandFilterACLQuerySet)() class Meta: unique_together = ('name', 'org_id') @@ -122,44 +133,3 @@ class CommandFilterACL(OrgModelMixin, BaseACL): assignees = self.reviewers.all() ticket.open_by_system(assignees) return ticket - - @classmethod - def get_command_groups(cls, user_id=None, user_group_id=None, account=None, asset_id=None, org_id=None): - # Todo: Do - return CommandGroup.objects.all() - - from assets.models import Account, Asset - user_groups = [] - user = get_object_or_none(User, pk=user_id) - if user: - user_groups.extend(list(user.groups.all())) - user_group = get_object_or_none(UserGroup, pk=user_group_id) - if user_group: - org_id = user_group.org_id - user_groups.append(user_group) - - asset = get_object_or_none(Asset, pk=asset_id) - q = Q() - if user: - q |= Q(users=user) - if user_groups: - q |= Q(user_groups__in=set(user_groups)) - if account: - org_id = account.org_id - q |= Q(accounts__contains=account.username) | \ - Q(accounts__contains=Account.AliasAccount.ALL) - if asset: - org_id = asset.org_id - q |= Q(assets=asset) - if q: - cmd_filters = cls.objects.filter(q).filter(is_active=True) - if org_id: - cmd_filters = cmd_filters.filter(org_id=org_id) - filter_ids = cmd_filters.values_list('id', flat=True) - command_group_ids = cls.commands.through.objects\ - .filter(commandfilteracl_id__in=filter_ids)\ - .values_list('commandgroup_id', flat=True) - cmd_groups = CommandGroup.objects.filter(id__in=command_group_ids) - else: - cmd_groups = CommandGroup.objects.none() - return cmd_groups diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index aedb8aa9c..1bc4a05a1 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -9,12 +9,10 @@ from .base import BaseACL class LoginACL(BaseACL): - # 用户 user = models.ForeignKey( - 'users.User', on_delete=models.CASCADE, verbose_name=_('User'), - related_name='login_acls' + 'users.User', on_delete=models.CASCADE, related_name='login_acls', verbose_name=_('User') ) - # 规则 + # 规则, ip_group, time_period rules = models.JSONField(default=dict, verbose_name=_('Rule')) class Meta: diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 6af48faab..695b83e05 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -1,17 +1,10 @@ -from django.db import models from django.utils.translation import ugettext_lazy as _ -from orgs.mixins.models import OrgModelMixin -from .base import BaseACL, ACLManager, AssetAccountUserACLQuerySet + +from .base import UserAssetAccountBaseACL -class LoginAssetACL(BaseACL, OrgModelMixin): - # 条件 - users = models.JSONField(verbose_name=_('User')) - accounts = models.JSONField(verbose_name=_('Account')) - assets = models.JSONField(verbose_name=_('Asset')) - - objects = ACLManager.from_queryset(AssetAccountUserACLQuerySet)() +class LoginAssetACL(UserAssetAccountBaseACL): class Meta: unique_together = ('name', 'org_id') diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index e987fc48c..ef4fad0ac 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -135,7 +135,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): 'su_from': None, 'org_id': self.asset.org_id } - Account(**data) else: data = { 'name': account.name, @@ -164,13 +163,12 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def acl_command_groups(self): from acls.models import CommandFilterACL kwargs = { - 'user_id': self.user.id, + 'user': self.user, + 'asset': self.asset, 'account': self.account, } - if self.asset: - kwargs['asset_id'] = self.asset.id - cmd_groups = CommandFilterACL.get_command_groups(**kwargs) - return cmd_groups + command_groups = CommandFilterACL.filter_queryset(**kwargs).get_command_groups() + return command_groups class SuperConnectionToken(ConnectionToken): From a69b762f131547a91f78ec78f8c7961b77d713f9 Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 4 Dec 2022 12:08:44 +0800 Subject: [PATCH 473/488] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20ACL=20Actio?= =?UTF-8?q?nChoices=20review,=20accept,=20reject=20=20=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/migrations/0002_auto_20210926_1047.py | 5 +++-- apps/acls/models/login_acl.py | 2 +- apps/authentication/mixins.py | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/acls/migrations/0002_auto_20210926_1047.py b/apps/acls/migrations/0002_auto_20210926_1047.py index 53ab54711..2429b7cea 100644 --- a/apps/acls/migrations/0002_auto_20210926_1047.py +++ b/apps/acls/migrations/0002_auto_20210926_1047.py @@ -30,10 +30,11 @@ def migrate_login_confirm(apps, schema_editor): if reviewers.count() == 0: continue data = { + 'user': user, 'name': f'{user.name}-{login_confirm} ({date_created})', 'created_by': instance.created_by, - 'action': LoginACL.ActionChoices.confirm, + 'action': 'confirm', 'rules': {'ip_group': ['*'], 'time_period': DEFAULT_TIME_PERIODS} } instance = login_acl_model.objects.create(**data) @@ -44,7 +45,7 @@ def migrate_ip_group(apps, schema_editor): login_acl_model = apps.get_model("acls", "LoginACL") updates = list() with transaction.atomic(): - for instance in login_acl_model.objects.exclude(action=LoginACL.ActionChoices.confirm): + for instance in login_acl_model.objects.exclude(action='confirm'): instance.rules = {'ip_group': instance.ip_group, 'time_period': DEFAULT_TIME_PERIODS} updates.append(instance) login_acl_model.objects.bulk_update(updates, ['rules', ]) diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index 1bc4a05a1..de6a73d1b 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -36,7 +36,7 @@ class LoginACL(BaseACL): return for acl in acl_qs: - if acl.is_action(LoginACL.ActionChoices.confirm) and \ + if acl.is_action(LoginACL.ActionChoices.review) and \ not acl.reviewers.exists(): continue ip_group = acl.rules.get('ip_group') diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index ec6d2e98d..f381edecf 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -333,13 +333,13 @@ class AuthACLMixin: return acl: LoginACL - if acl.is_action(acl.ActionChoices.allow): + if acl.is_action(acl.ActionChoices.accept): return if acl.is_action(acl.ActionChoices.reject): raise errors.LoginACLIPAndTimePeriodNotAllowed(user.username, request=self.request) - if acl.is_action(acl.ActionChoices.confirm): + if acl.is_action(acl.ActionChoices.review): self.request.session['auth_confirm_required'] = '1' self.request.session['auth_acl_id'] = str(acl.id) return @@ -354,7 +354,7 @@ class AuthACLMixin: acl = LoginACL.filter_acl(user).filter(id=acl_id).first() if not acl: return - if not acl.is_action(acl.ActionChoices.confirm): + if not acl.is_action(acl.ActionChoices.review): return self.get_ticket_or_create(acl) self.check_user_login_confirm() From 5568c4c5df3bd6e36414e5c7705c14d88f7092d2 Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 4 Dec 2022 14:44:30 +0800 Subject: [PATCH 474/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20CommandFil?= =?UTF-8?q?terACL=20Account=20=E5=8F=AA=E5=8C=B9=E9=85=8D=20username=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/base.py | 15 +++++---------- apps/acls/serializers/command_filter.py | 8 +++++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 98036ec92..43577d44c 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -56,14 +56,9 @@ class UserAssetAccountACLQuerySet(BaseACLQuerySet): queryset = queryset.filter(id__in=ids) return queryset - def filter_account(self, name=None, username=None): - q = Q() - if name: - q &= Q(accounts__name_group__contains=name) | \ - Q(accounts__name_group__contains='*') - if username: - q &= Q(accounts__username_group__contains=username) | \ - Q(accounts__username_group__contains='*') + def filter_account(self, username): + q = Q(accounts__username_group__contains=username) | \ + Q(accounts__username_group__contains='*') return self.filter(q) @@ -96,7 +91,7 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin): users = models.JSONField(verbose_name=_('User')) # name_group, address_group assets = models.JSONField(verbose_name=_('Asset')) - # name_group, username_group + # username_group accounts = models.JSONField(verbose_name=_('Account')) objects = ACLManager.from_queryset(UserAssetAccountACLQuerySet)() @@ -115,7 +110,7 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin): queryset = queryset.filter_asset(asset.name, asset.address) if account: org_id = account.org_id - queryset = queryset.filter_account(account.name, account.username) + queryset = queryset.filter_account(account.username) if account_username: queryset = queryset.filter_account(username=account_username) if org_id: diff --git a/apps/acls/serializers/command_filter.py b/apps/acls/serializers/command_filter.py index 934d38198..a8e62da41 100644 --- a/apps/acls/serializers/command_filter.py +++ b/apps/acls/serializers/command_filter.py @@ -11,12 +11,14 @@ __all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer"] class CommandGroupSerializer(BulkOrgResourceModelSerializer): class Meta: model = CommandGroup - fields = ['id', 'name', 'type', 'content', 'comment'] + fields = ['id', 'name', 'type', 'content', 'ignore_case', 'comment'] class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer): - commands = ObjectRelatedField(queryset=CommandGroup.objects, many=True, required=False, label=_('Commands')) + command_groups = ObjectRelatedField( + queryset=CommandGroup.objects, many=True, required=False, label=_('Commands') + ) class Meta(BaseSerializer.Meta): model = CommandFilterACL - fields = BaseSerializer.Meta.fields + ['commands'] + fields = BaseSerializer.Meta.fields + ['command_groups'] From 048be1782d428647805527110fa7d37895378890 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 4 Dec 2022 17:39:48 +0800 Subject: [PATCH 475/488] fix: replay file data --- apps/terminal/api/session/session.py | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/terminal/api/session/session.py b/apps/terminal/api/session/session.py index fe742024d..25475cdc4 100644 --- a/apps/terminal/api/session/session.py +++ b/apps/terminal/api/session/session.py @@ -3,33 +3,33 @@ import os import tarfile -from django.db.models import F -from django.shortcuts import get_object_or_404, reverse -from django.utils.translation import ugettext as _ -from django.utils.encoding import escape_uri_path -from django.http import FileResponse from django.core.files.storage import default_storage +from django.db.models import F +from django.http import FileResponse +from django.shortcuts import get_object_or_404, reverse +from django.utils.encoding import escape_uri_path +from django.utils.translation import ugettext as _ +from rest_framework import generics from rest_framework import viewsets, views -from rest_framework.response import Response from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated -from rest_framework import generics +from rest_framework.response import Response -from common.utils import data_to_json from common.const.http import GET -from common.utils import get_logger, get_object_or_none -from common.mixins.api import AsyncApiMixin from common.drf.filters import DatetimeRangeFilter from common.drf.renders import PassthroughRenderer +from common.mixins.api import AsyncApiMixin +from common.utils import data_to_json +from common.utils import get_logger, get_object_or_none from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import tmp_to_root_org, tmp_to_org -from users.models import User +from terminal import serializers +from terminal.models import Session from terminal.utils import ( find_session_replay_local, download_session_replay, is_session_approver, get_session_replay_url ) -from terminal.models import Session -from terminal import serializers +from users.models import User __all__ = [ 'SessionViewSet', 'SessionReplayViewSet', @@ -40,7 +40,7 @@ logger = get_logger(__name__) class MySessionAPIView(generics.ListAPIView): - permission_classes = (IsAuthenticated, ) + permission_classes = (IsAuthenticated,) serializer_class = serializers.SessionSerializer def get_queryset(self): @@ -109,7 +109,7 @@ class SessionViewSet(OrgBulkModelViewSet): return response def get_queryset(self): - queryset = super().get_queryset().prefetch_related('terminal')\ + queryset = super().get_queryset().prefetch_related('terminal') \ .annotate(terminal_display=F('terminal__name')) return queryset @@ -169,7 +169,7 @@ class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet): data = { 'type': tp, 'src': url, 'user': session.user, 'asset': session.asset, - 'system_user': session.system_user, + 'system_user': session.account, 'date_start': session.date_start, 'date_end': session.date_end, 'download_url': download_url, From 229d36abb4f6eff2364589f0ed273668da8daec5 Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 4 Dec 2022 18:01:35 +0800 Subject: [PATCH 476/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=BF=87=E6=BB=A4=20ACL=20=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/command_acl.py | 2 +- apps/acls/serializers/base.py | 8 +- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 412 ++++++++++++++------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 412 ++++++++++++++------------- 6 files changed, 439 insertions(+), 403 deletions(-) diff --git a/apps/acls/api/command_acl.py b/apps/acls/api/command_acl.py index b717a464f..717e36930 100644 --- a/apps/acls/api/command_acl.py +++ b/apps/acls/api/command_acl.py @@ -15,4 +15,4 @@ class CommandFilterACLViewSet(OrgBulkModelViewSet): model = models.CommandFilterACL filterset_fields = ('name',) search_fields = filterset_fields - serializer_class = serializers.LoginAssetACLSerializer + serializer_class = serializers.CommandFilterACLSerializer diff --git a/apps/acls/serializers/base.py b/apps/acls/serializers/base.py index 9f511cd06..862f38827 100644 --- a/apps/acls/serializers/base.py +++ b/apps/acls/serializers/base.py @@ -52,15 +52,15 @@ class ACLAccountsSerializer(serializers.Serializer): class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer): - users = ACLUsersSerializer() - assets = ACLAssestsSerializer() - accounts = ACLAccountsSerializer() + users = ACLUsersSerializer(label=_('User')) + assets = ACLAssestsSerializer(label=_('Asset')) + accounts = ACLAccountsSerializer(label=_('Account')) reviewers = ObjectRelatedField( queryset=User.objects, many=True, required=False, label=_('Reviewers') ) reviewers_amount = serializers.IntegerField(read_only=True, source="reviewers.count") action = LabeledChoiceField( - choices=ActionChoices.choices, label=_("Action") + choices=ActionChoices.choices, default=ActionChoices.reject, label=_("Action") ) class Meta: diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 4468fe146..75ff2b13c 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2d20ebe29a2ae521e5026f493313abbee6a7a6b103901164766e5d1ae4ab564 -size 116377 +oid sha256:822927ab8fef4d3d70848fe1ecb109e1505a1346cc56f5ed79f5355e5c2d7e90 +size 116337 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 47d2340d3..6acaf7e80 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-12-01 18:42+0800\n" +"POT-Creation-Date: 2022-12-04 17:56+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,14 +22,27 @@ msgstr "" msgid "Acls" msgstr "Acls" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:38 -#: applications/models.py:10 assets/models/_user.py:33 -#: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:49 assets/models/cmd_filter.py:25 -#: assets/models/domain.py:27 assets/models/group.py:20 -#: assets/models/label.py:17 assets/models/platform.py:21 -#: assets/models/platform.py:72 assets/serializers/asset/common.py:86 -#: assets/serializers/domain.py:71 assets/serializers/platform.py:138 +#: acls/models/base.py:20 tickets/const.py:45 +#: tickets/templates/tickets/approve_check_password.html:49 +msgid "Reject" +msgstr "拒否" + +#: acls/models/base.py:21 +msgid "Accept" +msgstr "同意" + +#: acls/models/base.py:22 +msgid "Review" +msgstr "レビュー" + +#: acls/models/base.py:71 acls/models/command_acl.py:22 +#: acls/serializers/base.py:34 applications/models.py:10 +#: assets/models/_user.py:33 assets/models/asset/common.py:81 +#: assets/models/asset/common.py:91 assets/models/base.py:54 +#: assets/models/cmd_filter.py:21 assets/models/domain.py:24 +#: assets/models/group.py:20 assets/models/label.py:17 +#: assets/models/platform.py:21 assets/models/platform.py:72 +#: assets/serializers/asset/common.py:86 assets/serializers/platform.py:138 #: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 #: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 #: perms/models/asset_permission.py:51 rbac/models/role.py:29 @@ -43,32 +56,43 @@ msgstr "Acls" msgid "Name" msgstr "名前" -#: acls/models/base.py:27 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:90 +#: acls/models/base.py:73 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:90 msgid "Priority" msgstr "優先順位" -#: acls/models/base.py:28 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:91 +#: acls/models/base.py:74 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:91 msgid "1-100, the lower the value will be match first" msgstr "1-100、低い値は最初に一致します" -#: acls/models/base.py:31 authentication/models/access_key.py:15 +#: acls/models/base.py:77 acls/serializers/base.py:63 +#: assets/models/cmd_filter.py:77 audits/models.py:50 audits/serializers.py:69 +#: authentication/templates/authentication/_access_key_modal.html:34 +msgid "Action" +msgstr "アクション" + +#: acls/models/base.py:78 acls/serializers/base.py:59 +#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:82 +msgid "Reviewers" +msgstr "レビュアー" + +#: acls/models/base.py:79 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 #: tickets/const.py:37 msgid "Active" msgstr "アクティブ" -#: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 +#: acls/models/base.py:80 acls/models/command_acl.py:29 +#: applications/models.py:19 assets/models/_user.py:40 #: assets/models/asset/common.py:100 assets/models/automations/base.py:22 -#: assets/models/backup.py:29 assets/models/base.py:57 -#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 -#: assets/models/domain.py:28 assets/models/domain.py:192 -#: assets/models/group.py:23 assets/models/label.py:22 -#: assets/models/platform.py:77 ops/models/adhoc.py:27 ops/models/job.py:50 -#: ops/models/playbook.py:17 orgs/models.py:74 -#: perms/models/asset_permission.py:71 rbac/models/role.py:37 +#: assets/models/backup.py:29 assets/models/base.py:62 +#: assets/models/cmd_filter.py:36 assets/models/cmd_filter.py:84 +#: assets/models/domain.py:25 assets/models/group.py:23 +#: assets/models/label.py:22 assets/models/platform.py:77 +#: ops/models/adhoc.py:27 ops/models/job.py:50 ops/models/playbook.py:17 +#: orgs/models.py:74 perms/models/asset_permission.py:71 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 #: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 #: terminal/models/component/endpoint.py:24 @@ -82,25 +106,12 @@ msgstr "アクティブ" msgid "Comment" msgstr "コメント" -#: acls/models/login_acl.py:18 tickets/const.py:45 -#: tickets/templates/tickets/approve_check_password.html:49 -msgid "Reject" -msgstr "拒否" - -#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:67 -msgid "Allow" -msgstr "許可" - -#: acls/models/login_acl.py:20 acls/models/login_acl.py:76 -#: acls/models/login_asset_acl.py:17 tickets/const.py:10 -msgid "Login confirm" -msgstr "ログイン確認" - -#: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 -#: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 -#: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 -#: audits/models.py:79 authentication/models/connection_token.py:25 -#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:80 +#: acls/models/base.py:91 acls/models/login_acl.py:13 +#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 +#: assets/models/cmd_filter.py:24 assets/models/label.py:15 audits/models.py:29 +#: audits/models.py:48 audits/models.py:79 +#: authentication/models/connection_token.py:25 +#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:69 #: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 @@ -112,41 +123,13 @@ msgstr "ログイン確認" msgid "User" msgstr "ユーザー" -#: acls/models/login_acl.py:28 -msgid "Rule" -msgstr "ルール" - -#: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:64 -#: assets/models/cmd_filter.py:81 audits/models.py:50 audits/serializers.py:69 -#: authentication/templates/authentication/_access_key_modal.html:34 -msgid "Action" -msgstr "アクション" - -#: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32 -#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86 -msgid "Reviewers" -msgstr "レビュー担当者" - -#: acls/models/login_acl.py:42 -msgid "Login acl" -msgstr "ログインacl" - -#: acls/models/login_asset_acl.py:21 assets/models/account.py:61 -#: assets/serializers/automations/change_secret.py:101 -#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 -#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 -msgid "Account" -msgstr "アカウント" - -#: acls/models/login_asset_acl.py:22 assets/models/account.py:51 -#: assets/models/asset/common.py:83 assets/models/asset/common.py:212 -#: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 -#: assets/serializers/account/account.py:59 +#: acls/models/base.py:93 acls/serializers/base.py:56 +#: assets/models/account.py:51 assets/models/asset/common.py:83 +#: assets/models/asset/common.py:212 assets/models/cmd_filter.py:32 +#: assets/models/gathered_user.py:14 assets/serializers/account/account.py:59 #: assets/serializers/automations/change_secret.py:100 #: assets/serializers/automations/change_secret.py:122 -#: assets/serializers/domain.py:20 assets/serializers/gathered_user.py:11 +#: assets/serializers/domain.py:17 assets/serializers/gathered_user.py:11 #: assets/serializers/label.py:30 audits/models.py:33 #: authentication/models/connection_token.py:29 #: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 @@ -159,23 +142,112 @@ msgstr "アカウント" msgid "Asset" msgstr "資産" -#: acls/models/login_asset_acl.py:40 +#: acls/models/base.py:95 acls/serializers/base.py:57 +#: assets/models/account.py:61 +#: assets/serializers/automations/change_secret.py:101 +#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 +#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 +msgid "Account" +msgstr "アカウント" + +#: acls/models/command_acl.py:17 assets/models/cmd_filter.py:56 +#: terminal/backends/command/serializers.py:15 +#: terminal/models/session/session.py:41 +#: terminal/templates/terminal/_msg_command_alert.html:12 +#: terminal/templates/terminal/_msg_command_execute_alert.html:10 +msgid "Command" +msgstr "コマンド" + +#: acls/models/command_acl.py:18 assets/models/cmd_filter.py:55 +msgid "Regex" +msgstr "正規情報" + +#: acls/models/command_acl.py:25 applications/models.py:15 +#: assets/models/_user.py:46 assets/models/automations/base.py:20 +#: assets/models/cmd_filter.py:70 assets/models/platform.py:74 +#: assets/serializers/asset/common.py:63 +#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 +#: audits/serializers.py:40 ops/models/job.py:42 +#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 +#: terminal/models/component/storage.py:57 +#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 +#: tickets/models/comment.py:26 tickets/models/flow.py:57 +#: tickets/models/ticket/apply_application.py:16 +#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 +#: tickets/serializers/ticket/ticket.py:18 +#: xpack/plugins/change_auth_plan/models/app.py:27 +#: xpack/plugins/change_auth_plan/models/app.py:152 +msgid "Type" +msgstr "タイプ" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 +msgid "Content" +msgstr "コンテンツ" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 +msgid "One line one command" +msgstr "1行1コマンド" + +#: acls/models/command_acl.py:28 assets/models/cmd_filter.py:76 +msgid "Ignore case" +msgstr "家を無視する" + +#: acls/models/command_acl.py:35 +#, fuzzy +#| msgid "Command record" +msgid "Command group" +msgstr "コマンドレコード" + +#: acls/models/command_acl.py:88 +msgid "The generated regular expression is incorrect: {}" +msgstr "生成された正規表現が正しくありません: {}" + +#: acls/models/command_acl.py:107 acls/serializers/command_filter.py:19 +#, fuzzy +#| msgid "Command" +msgid "Commands" +msgstr "コマンド" + +#: acls/models/command_acl.py:114 +#, fuzzy +#| msgid "Command" +msgid "Command acl" +msgstr "コマンド" + +#: acls/models/command_acl.py:120 tickets/const.py:11 +msgid "Command confirm" +msgstr "コマンドの確認" + +#: acls/models/login_acl.py:16 +msgid "Rule" +msgstr "ルール" + +#: acls/models/login_acl.py:20 +msgid "Login acl" +msgstr "ログインacl" + +#: acls/models/login_acl.py:54 tickets/const.py:10 +msgid "Login confirm" +msgstr "ログイン確認" + +#: acls/models/login_asset_acl.py:12 msgid "Login asset acl" msgstr "ログインasset acl" -#: acls/models/login_asset_acl.py:85 tickets/const.py:12 +#: acls/models/login_asset_acl.py:21 tickets/const.py:12 msgid "Login asset confirm" msgstr "ログイン資産の確認" -#: acls/serializers/login_acl.py:16 acls/serializers/login_asset_acl.py:14 +#: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" -#: acls/serializers/login_asset_acl.py:22 -#: acls/serializers/login_asset_acl.py:53 assets/models/_user.py:34 -#: assets/models/base.py:50 assets/models/gathered_user.py:15 -#: assets/serializers/domain.py:58 assets/serializers/domain.py:60 -#: audits/models.py:95 authentication/forms.py:25 authentication/forms.py:27 +#: acls/serializers/base.py:18 acls/serializers/base.py:49 +#: assets/models/_user.py:34 assets/models/base.py:55 +#: assets/models/gathered_user.py:15 audits/models.py:95 +#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 @@ -187,7 +259,7 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること msgid "Username" msgstr "ユーザー名" -#: acls/serializers/login_asset_acl.py:29 +#: acls/serializers/base.py:25 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -197,18 +269,17 @@ msgstr "" "192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" "db8:1a:1110:::/64 (ドメイン名サポート)" -#: acls/serializers/login_asset_acl.py:44 assets/serializers/asset/host.py:40 +#: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 #, fuzzy #| msgid "Host" msgid "IP/Host" msgstr "ホスト" -#: acls/serializers/login_asset_acl.py:95 -#: tickets/serializers/ticket/ticket.py:66 +#: acls/serializers/base.py:85 tickets/serializers/ticket/ticket.py:66 msgid "The organization `{}` does not exist" msgstr "組織 '{}'は存在しません" -#: acls/serializers/login_asset_acl.py:101 +#: acls/serializers/base.py:91 msgid "None of the reviewers belong to Organization `{}`" msgstr "いずれのレビューアも組織 '{}' に属していません" @@ -228,7 +299,6 @@ msgstr "" "db8:1a:1110::/64" #: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 -#: assets/models/domain.py:186 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -253,23 +323,6 @@ msgstr "アプリケーション" msgid "Category" msgstr "カテゴリ" -#: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:20 assets/models/cmd_filter.py:74 -#: assets/models/platform.py:74 assets/serializers/asset/common.py:63 -#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 -#: audits/serializers.py:40 ops/models/job.py:42 -#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 -#: terminal/models/component/storage.py:57 -#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 -#: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:16 -#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 -#: tickets/serializers/ticket/ticket.py:18 -#: xpack/plugins/change_auth_plan/models/app.py:27 -#: xpack/plugins/change_auth_plan/models/app.py:152 -msgid "Type" -msgstr "タイプ" - #: applications/models.py:17 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/serializers/account.py:61 msgid "Attrs" @@ -288,7 +341,7 @@ msgstr "アプリケーションを一致させることができます" msgid "The parameter 'action' must be [{}]" msgstr "パラメータ 'action' は [{}] でなければなりません。" -#: assets/api/domain.py:57 +#: assets/api/domain.py:56 msgid "Number required" msgstr "必要な数" @@ -334,7 +387,6 @@ msgid "Failed" msgstr "失敗しました" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/domain.py:194 assets/serializers/domain.py:46 #: audits/signal_handlers.py:46 authentication/confirm/password.py:9 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 @@ -498,23 +550,20 @@ msgstr "共通ユーザー" msgid "Admin user" msgstr "管理ユーザー" -#: assets/models/_user.py:36 assets/models/domain.py:195 -#: assets/serializers/domain.py:50 -#: xpack/plugins/change_auth_plan/models/asset.py:54 +#: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" msgstr "SSH秘密鍵" -#: assets/models/_user.py:37 assets/models/domain.py:196 -#: xpack/plugins/change_auth_plan/models/asset.py:57 +#: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 #: xpack/plugins/change_auth_plan/models/asset.py:127 #: xpack/plugins/change_auth_plan/models/asset.py:203 msgid "SSH public key" msgstr "SSHパブリックキー" #: assets/models/_user.py:41 assets/models/automations/base.py:92 -#: assets/models/domain.py:29 assets/models/gathered_user.py:19 +#: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 #: ops/models/base.py:54 ops/models/job.py:108 orgs/models.py:73 #: perms/models/asset_permission.py:74 users/models/group.py:18 @@ -527,8 +576,8 @@ msgstr "作成された日付" msgid "Date updated" msgstr "更新日" -#: assets/models/_user.py:43 assets/models/base.py:58 -#: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 +#: assets/models/_user.py:43 assets/models/base.py:63 +#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:87 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 #: orgs/models.py:71 perms/models/asset_permission.py:75 #: users/models/user.py:710 users/serializers/group.py:33 @@ -540,10 +589,10 @@ msgstr "によって作成された" msgid "Username same with user" msgstr "ユーザーと同じユーザー名" -#: assets/models/_user.py:48 assets/models/domain.py:189 -#: authentication/models/connection_token.py:35 perms/models/perm_token.py:16 -#: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 -#: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 +#: assets/models/_user.py:48 authentication/models/connection_token.py:35 +#: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 +#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 +#: terminal/serializers/storage.py:68 msgid "Protocol" msgstr "プロトコル" @@ -609,6 +658,7 @@ msgid "Dynamic user" msgstr "動的コード" #: assets/models/account.py:55 +#: authentication/serializers/connection_token.py:108 #, fuzzy #| msgid "Switch from" msgid "Su from" @@ -653,9 +703,8 @@ msgstr "資産アカウントの秘密を表示できます" msgid "Can change asset account template secret" msgstr "資産口座の秘密を変更できます" -#: assets/models/asset/common.py:82 assets/models/domain.py:187 -#: assets/models/platform.py:22 settings/serializers/auth/radius.py:15 -#: settings/serializers/auth/sms.py:57 +#: assets/models/asset/common.py:82 assets/models/platform.py:22 +#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:57 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "ポート" @@ -667,8 +716,8 @@ msgstr "ポート" msgid "Platform" msgstr "プラットフォーム" -#: assets/models/asset/common.py:95 assets/models/domain.py:32 -#: assets/models/domain.py:191 assets/serializers/asset/common.py:64 +#: assets/models/asset/common.py:95 assets/models/domain.py:29 +#: assets/serializers/asset/common.py:64 msgid "Domain" msgstr "ドメイン" @@ -682,9 +731,9 @@ msgid "Nodes" msgstr "ノード" #: assets/models/asset/common.py:98 assets/models/automations/base.py:21 -#: assets/models/base.py:56 assets/models/cmd_filter.py:39 -#: assets/models/domain.py:193 assets/models/label.py:21 -#: terminal/models/applet/applet.py:25 users/serializers/user.py:202 +#: assets/models/base.py:61 assets/models/cmd_filter.py:35 +#: assets/models/label.py:21 terminal/models/applet/applet.py:25 +#: users/serializers/user.py:202 msgid "Is active" msgstr "アクティブです。" @@ -781,14 +830,14 @@ msgstr "パスワードルール" msgid "Submit selector" msgstr "" -#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 +#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:34 #: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 #: perms/serializers/permission.py:32 rbac/tree.py:37 msgid "Accounts" msgstr "アカウント" #: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:32 +#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 #: ops/models/base.py:17 ops/models/job.py:44 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 @@ -851,8 +900,8 @@ msgstr "トリガーモード" msgid "Automation task execution" msgstr "コマンド実行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:52 -#: assets/serializers/account/account.py:95 assets/serializers/base.py:13 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:57 +#: assets/serializers/account/account.py:97 assets/serializers/base.py:13 #, fuzzy #| msgid "Secret key" msgid "Secret type" @@ -866,7 +915,7 @@ msgid "Secret strategy" msgstr "SSHキー戦略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:54 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:59 #: assets/serializers/base.py:16 authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 @@ -998,98 +1047,62 @@ msgstr "成功は" msgid "Account backup execution" msgstr "アカウントバックアップの実行" -#: assets/models/base.py:27 +#: assets/models/base.py:26 msgid "Connectivity" msgstr "接続性" -#: assets/models/base.py:29 authentication/models/temp_token.py:12 +#: assets/models/base.py:28 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "確認済みの日付" -#: assets/models/base.py:55 +#: assets/models/base.py:60 msgid "Privileged" msgstr "" -#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:56 +#: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:56 #: users/models/group.py:31 users/models/user.py:671 msgid "User group" msgstr "ユーザーグループ" -#: assets/models/cmd_filter.py:52 +#: assets/models/cmd_filter.py:48 msgid "Command filter" msgstr "コマンドフィルター" -#: assets/models/cmd_filter.py:59 -msgid "Regex" -msgstr "正規情報" - -#: assets/models/cmd_filter.py:60 terminal/backends/command/serializers.py:15 -#: terminal/models/session/session.py:41 -#: terminal/templates/terminal/_msg_command_alert.html:12 -#: terminal/templates/terminal/_msg_command_execute_alert.html:10 -msgid "Command" -msgstr "コマンド" - -#: assets/models/cmd_filter.py:66 +#: assets/models/cmd_filter.py:62 msgid "Deny" msgstr "拒否" -#: assets/models/cmd_filter.py:68 +#: assets/models/cmd_filter.py:63 +msgid "Allow" +msgstr "許可" + +#: assets/models/cmd_filter.py:64 msgid "Reconfirm" msgstr "再確認" -#: assets/models/cmd_filter.py:72 +#: assets/models/cmd_filter.py:68 msgid "Filter" msgstr "フィルター" -#: assets/models/cmd_filter.py:79 settings/serializers/basic.py:10 -#: xpack/plugins/license/models.py:29 -msgid "Content" -msgstr "コンテンツ" - -#: assets/models/cmd_filter.py:79 -msgid "One line one command" -msgstr "1行1コマンド" - -#: assets/models/cmd_filter.py:80 -msgid "Ignore case" -msgstr "家を無視する" - -#: assets/models/cmd_filter.py:95 +#: assets/models/cmd_filter.py:91 msgid "Command filter rule" msgstr "コマンドフィルタルール" -#: assets/models/cmd_filter.py:138 -msgid "The generated regular expression is incorrect: {}" -msgstr "生成された正規表現が正しくありません: {}" - -#: assets/models/cmd_filter.py:164 tickets/const.py:11 -msgid "Command confirm" -msgstr "コマンドの確認" - -#: assets/models/domain.py:121 +#: assets/models/domain.py:153 #, fuzzy, python-brace-format #| msgid "Unable to connect to port {port} on {ip}" msgid "Unable to connect to port {port} on {address}" msgstr "{ip} でポート {port} に接続できません" -#: assets/models/domain.py:124 authentication/middleware.py:76 +#: assets/models/domain.py:156 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "認証に失敗しました" -#: assets/models/domain.py:126 assets/models/domain.py:148 +#: assets/models/domain.py:158 assets/models/domain.py:185 msgid "Connect failed" msgstr "接続に失敗しました" -#: assets/models/domain.py:207 -msgid "Gateway" -msgstr "ゲートウェイ" - -#: assets/models/domain.py:209 -msgid "Test gateway" -msgstr "テストゲートウェイ" - #: assets/models/gathered_user.py:16 msgid "Present" msgstr "プレゼント" @@ -1357,14 +1370,13 @@ msgstr "定期的なパフォーマンス" msgid "Currently only mail sending is supported" msgstr "現在、メール送信のみがサポートされています" -#: assets/serializers/asset/common.py:68 assets/serializers/domain.py:61 -#: assets/serializers/platform.py:101 -#: authentication/serializers/connection_token.py:88 +#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 +#: authentication/serializers/connection_token.py:89 #: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "プロトコル" -#: assets/serializers/asset/common.py:87 assets/serializers/domain.py:72 +#: assets/serializers/asset/common.py:87 msgid "Address" msgstr "アドレス" @@ -1458,7 +1470,7 @@ msgstr "* パスワードの長さの範囲6-30ビット" #: assets/serializers/automations/change_secret.py:117 #: assets/serializers/automations/change_secret.py:145 audits/const.py:73 -#: audits/models.py:39 common/const/choices.py:18 +#: audits/models.py:39 common/const/choices.py:18 ops/serializers/celery.py:39 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" @@ -1470,7 +1482,7 @@ msgstr "成功" msgid "Executed amount" msgstr "実行時間" -#: assets/serializers/base.py:21 assets/serializers/domain.py:54 +#: assets/serializers/base.py:21 msgid "Key password" msgstr "キーパスワード" @@ -1484,11 +1496,11 @@ msgstr "" msgid "Types" msgstr "タイプ" -#: assets/serializers/domain.py:17 assets/serializers/label.py:12 +#: assets/serializers/domain.py:14 assets/serializers/label.py:12 msgid "Assets amount" msgstr "資産額" -#: assets/serializers/domain.py:18 +#: assets/serializers/domain.py:15 msgid "Gateways count" msgstr "ゲートウェイ数" @@ -2315,7 +2327,7 @@ msgstr "アセットがアクティブ化されていません" msgid "No account" msgstr "ログインacl" -#: authentication/models/connection_token.py:172 +#: authentication/models/connection_token.py:177 msgid "Super connection token" msgstr "スーパー接続トークン" @@ -2343,11 +2355,11 @@ msgstr "異なる都市ログインのリマインダー" msgid "binding reminder" msgstr "バインディングリマインダー" -#: authentication/serializers/connection_token.py:18 +#: authentication/serializers/connection_token.py:19 msgid "Expired time" msgstr "期限切れ時間" -#: authentication/serializers/connection_token.py:139 +#: authentication/serializers/connection_token.py:157 #, fuzzy #| msgid "Expired" msgid "Expired now" @@ -3057,13 +3069,13 @@ msgstr "" msgid "No account available" msgstr "利用できないアカウント" -#: ops/ansible/inventory.py:175 +#: ops/ansible/inventory.py:178 #, fuzzy #| msgid "User disabled." msgid "Ansible disabled" msgstr "ユーザーが無効になりました。" -#: ops/ansible/inventory.py:191 +#: ops/ansible/inventory.py:194 msgid "Skip hosts below:" msgstr "" @@ -7247,6 +7259,12 @@ msgstr "究極のエディション" msgid "Community edition" msgstr "コミュニティ版" +#~ msgid "Gateway" +#~ msgstr "ゲートウェイ" + +#~ msgid "Test gateway" +#~ msgstr "テストゲートウェイ" + #~ msgid "" #~ "Format for comma-delimited string, with * indicating a match all. " #~ "Protocol options: {}" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index cd5e43d04..3f1c8bffe 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:eb680a5e6725fcd4459a8e712b0eda8df3e9990915e7f3b9602b16307ff36221 -size 103614 +oid sha256:3bd4186e087ac63f435e771d9238bcc249cf01332a7e169475ff80f7ecb7fdf9 +size 103601 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 49b6c682a..1f91ddca8 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-12-01 18:42+0800\n" +"POT-Creation-Date: 2022-12-04 17:56+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -21,14 +21,27 @@ msgstr "" msgid "Acls" msgstr "访问控制" -#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:38 -#: applications/models.py:10 assets/models/_user.py:33 -#: assets/models/asset/common.py:81 assets/models/asset/common.py:91 -#: assets/models/base.py:49 assets/models/cmd_filter.py:25 -#: assets/models/domain.py:27 assets/models/group.py:20 -#: assets/models/label.py:17 assets/models/platform.py:21 -#: assets/models/platform.py:72 assets/serializers/asset/common.py:86 -#: assets/serializers/domain.py:71 assets/serializers/platform.py:138 +#: acls/models/base.py:20 tickets/const.py:45 +#: tickets/templates/tickets/approve_check_password.html:49 +msgid "Reject" +msgstr "拒绝" + +#: acls/models/base.py:21 +msgid "Accept" +msgstr "同意" + +#: acls/models/base.py:22 +msgid "Review" +msgstr "复核" + +#: acls/models/base.py:71 acls/models/command_acl.py:22 +#: acls/serializers/base.py:34 applications/models.py:10 +#: assets/models/_user.py:33 assets/models/asset/common.py:81 +#: assets/models/asset/common.py:91 assets/models/base.py:54 +#: assets/models/cmd_filter.py:21 assets/models/domain.py:24 +#: assets/models/group.py:20 assets/models/label.py:17 +#: assets/models/platform.py:21 assets/models/platform.py:72 +#: assets/serializers/asset/common.py:86 assets/serializers/platform.py:138 #: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 #: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 #: perms/models/asset_permission.py:51 rbac/models/role.py:29 @@ -42,32 +55,43 @@ msgstr "访问控制" msgid "Name" msgstr "名称" -#: acls/models/base.py:27 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:90 +#: acls/models/base.py:73 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:90 msgid "Priority" msgstr "优先级" -#: acls/models/base.py:28 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:76 terminal/models/component/endpoint.py:91 +#: acls/models/base.py:74 assets/models/_user.py:47 +#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:91 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" -#: acls/models/base.py:31 authentication/models/access_key.py:15 +#: acls/models/base.py:77 acls/serializers/base.py:63 +#: assets/models/cmd_filter.py:77 audits/models.py:50 audits/serializers.py:69 +#: authentication/templates/authentication/_access_key_modal.html:34 +msgid "Action" +msgstr "动作" + +#: acls/models/base.py:78 acls/serializers/base.py:59 +#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:82 +msgid "Reviewers" +msgstr "复核人" + +#: acls/models/base.py:79 authentication/models/access_key.py:15 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 #: tickets/const.py:37 msgid "Active" msgstr "激活中" -#: acls/models/base.py:32 applications/models.py:19 assets/models/_user.py:40 +#: acls/models/base.py:80 acls/models/command_acl.py:29 +#: applications/models.py:19 assets/models/_user.py:40 #: assets/models/asset/common.py:100 assets/models/automations/base.py:22 -#: assets/models/backup.py:29 assets/models/base.py:57 -#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 -#: assets/models/domain.py:28 assets/models/domain.py:192 -#: assets/models/group.py:23 assets/models/label.py:22 -#: assets/models/platform.py:77 ops/models/adhoc.py:27 ops/models/job.py:50 -#: ops/models/playbook.py:17 orgs/models.py:74 -#: perms/models/asset_permission.py:71 rbac/models/role.py:37 +#: assets/models/backup.py:29 assets/models/base.py:62 +#: assets/models/cmd_filter.py:36 assets/models/cmd_filter.py:84 +#: assets/models/domain.py:25 assets/models/group.py:23 +#: assets/models/label.py:22 assets/models/platform.py:77 +#: ops/models/adhoc.py:27 ops/models/job.py:50 ops/models/playbook.py:17 +#: orgs/models.py:74 perms/models/asset_permission.py:71 rbac/models/role.py:37 #: settings/models.py:38 terminal/models/applet/applet.py:28 #: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 #: terminal/models/component/endpoint.py:24 @@ -81,25 +105,12 @@ msgstr "激活中" msgid "Comment" msgstr "备注" -#: acls/models/login_acl.py:18 tickets/const.py:45 -#: tickets/templates/tickets/approve_check_password.html:49 -msgid "Reject" -msgstr "拒绝" - -#: acls/models/login_acl.py:19 assets/models/cmd_filter.py:67 -msgid "Allow" -msgstr "允许" - -#: acls/models/login_acl.py:20 acls/models/login_acl.py:76 -#: acls/models/login_asset_acl.py:17 tickets/const.py:10 -msgid "Login confirm" -msgstr "登录复核" - -#: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:20 -#: acls/serializers/login_acl.py:21 assets/models/cmd_filter.py:28 -#: assets/models/label.py:15 audits/models.py:29 audits/models.py:48 -#: audits/models.py:79 authentication/models/connection_token.py:25 -#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:80 +#: acls/models/base.py:91 acls/models/login_acl.py:13 +#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 +#: assets/models/cmd_filter.py:24 assets/models/label.py:15 audits/models.py:29 +#: audits/models.py:48 audits/models.py:79 +#: authentication/models/connection_token.py:25 +#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:69 #: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 #: rbac/builtin.py:120 rbac/models/rolebinding.py:41 #: terminal/backends/command/models.py:20 @@ -111,41 +122,13 @@ msgstr "登录复核" msgid "User" msgstr "用户" -#: acls/models/login_acl.py:28 -msgid "Rule" -msgstr "规则" - -#: acls/models/login_acl.py:31 acls/models/login_asset_acl.py:26 -#: acls/serializers/login_acl.py:26 acls/serializers/login_asset_acl.py:64 -#: assets/models/cmd_filter.py:81 audits/models.py:50 audits/serializers.py:69 -#: authentication/templates/authentication/_access_key_modal.html:34 -msgid "Action" -msgstr "动作" - -#: acls/models/login_acl.py:35 acls/models/login_asset_acl.py:32 -#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:86 -msgid "Reviewers" -msgstr "审批人" - -#: acls/models/login_acl.py:42 -msgid "Login acl" -msgstr "登录访问控制" - -#: acls/models/login_asset_acl.py:21 assets/models/account.py:61 -#: assets/serializers/automations/change_secret.py:101 -#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 -#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 -msgid "Account" -msgstr "账号" - -#: acls/models/login_asset_acl.py:22 assets/models/account.py:51 -#: assets/models/asset/common.py:83 assets/models/asset/common.py:212 -#: assets/models/cmd_filter.py:36 assets/models/gathered_user.py:14 -#: assets/serializers/account/account.py:59 +#: acls/models/base.py:93 acls/serializers/base.py:56 +#: assets/models/account.py:51 assets/models/asset/common.py:83 +#: assets/models/asset/common.py:212 assets/models/cmd_filter.py:32 +#: assets/models/gathered_user.py:14 assets/serializers/account/account.py:59 #: assets/serializers/automations/change_secret.py:100 #: assets/serializers/automations/change_secret.py:122 -#: assets/serializers/domain.py:20 assets/serializers/gathered_user.py:11 +#: assets/serializers/domain.py:17 assets/serializers/gathered_user.py:11 #: assets/serializers/label.py:30 audits/models.py:33 #: authentication/models/connection_token.py:29 #: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 @@ -158,23 +141,112 @@ msgstr "账号" msgid "Asset" msgstr "资产" -#: acls/models/login_asset_acl.py:40 +#: acls/models/base.py:95 acls/serializers/base.py:57 +#: assets/models/account.py:61 +#: assets/serializers/automations/change_secret.py:101 +#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 +#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 +#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 +msgid "Account" +msgstr "账号" + +#: acls/models/command_acl.py:17 assets/models/cmd_filter.py:56 +#: terminal/backends/command/serializers.py:15 +#: terminal/models/session/session.py:41 +#: terminal/templates/terminal/_msg_command_alert.html:12 +#: terminal/templates/terminal/_msg_command_execute_alert.html:10 +msgid "Command" +msgstr "命令" + +#: acls/models/command_acl.py:18 assets/models/cmd_filter.py:55 +msgid "Regex" +msgstr "正则表达式" + +#: acls/models/command_acl.py:25 applications/models.py:15 +#: assets/models/_user.py:46 assets/models/automations/base.py:20 +#: assets/models/cmd_filter.py:70 assets/models/platform.py:74 +#: assets/serializers/asset/common.py:63 +#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 +#: audits/serializers.py:40 ops/models/job.py:42 +#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 +#: terminal/models/component/storage.py:57 +#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 +#: tickets/models/comment.py:26 tickets/models/flow.py:57 +#: tickets/models/ticket/apply_application.py:16 +#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 +#: tickets/serializers/ticket/ticket.py:18 +#: xpack/plugins/change_auth_plan/models/app.py:27 +#: xpack/plugins/change_auth_plan/models/app.py:152 +msgid "Type" +msgstr "类型" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 +#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 +msgid "Content" +msgstr "内容" + +#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 +msgid "One line one command" +msgstr "每行一个命令" + +#: acls/models/command_acl.py:28 assets/models/cmd_filter.py:76 +msgid "Ignore case" +msgstr "忽略大小写" + +#: acls/models/command_acl.py:35 +#, fuzzy +#| msgid "Command record" +msgid "Command group" +msgstr "命令记录" + +#: acls/models/command_acl.py:88 +msgid "The generated regular expression is incorrect: {}" +msgstr "生成的正则表达式有误" + +#: acls/models/command_acl.py:107 acls/serializers/command_filter.py:19 +#, fuzzy +#| msgid "Command" +msgid "Commands" +msgstr "命令" + +#: acls/models/command_acl.py:114 +#, fuzzy +#| msgid "Command" +msgid "Command acl" +msgstr "命令" + +#: acls/models/command_acl.py:120 tickets/const.py:11 +msgid "Command confirm" +msgstr "命令复核" + +#: acls/models/login_acl.py:16 +msgid "Rule" +msgstr "规则" + +#: acls/models/login_acl.py:20 +msgid "Login acl" +msgstr "登录访问控制" + +#: acls/models/login_acl.py:54 tickets/const.py:10 +msgid "Login confirm" +msgstr "登录复核" + +#: acls/models/login_asset_acl.py:12 msgid "Login asset acl" msgstr "登录资产访问控制" -#: acls/models/login_asset_acl.py:85 tickets/const.py:12 +#: acls/models/login_asset_acl.py:21 tickets/const.py:12 msgid "Login asset confirm" msgstr "登录资产复核" -#: acls/serializers/login_acl.py:16 acls/serializers/login_asset_acl.py:14 +#: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " -#: acls/serializers/login_asset_acl.py:22 -#: acls/serializers/login_asset_acl.py:53 assets/models/_user.py:34 -#: assets/models/base.py:50 assets/models/gathered_user.py:15 -#: assets/serializers/domain.py:58 assets/serializers/domain.py:60 -#: audits/models.py:95 authentication/forms.py:25 authentication/forms.py:27 +#: acls/serializers/base.py:18 acls/serializers/base.py:49 +#: assets/models/_user.py:34 assets/models/base.py:55 +#: assets/models/gathered_user.py:15 audits/models.py:95 +#: authentication/forms.py:25 authentication/forms.py:27 #: authentication/models/temp_token.py:9 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 @@ -186,7 +258,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " msgid "Username" msgstr "用户名" -#: acls/serializers/login_asset_acl.py:29 +#: acls/serializers/base.py:25 msgid "" "Format for comma-delimited string, with * indicating a match all. Such as: " "192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" @@ -195,16 +267,15 @@ msgstr "" "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" -#: acls/serializers/login_asset_acl.py:44 assets/serializers/asset/host.py:40 +#: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 msgid "IP/Host" msgstr "IP/主机名" -#: acls/serializers/login_asset_acl.py:95 -#: tickets/serializers/ticket/ticket.py:66 +#: acls/serializers/base.py:85 tickets/serializers/ticket/ticket.py:66 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" -#: acls/serializers/login_asset_acl.py:101 +#: acls/serializers/base.py:91 msgid "None of the reviewers belong to Organization `{}`" msgstr "所有复核人都不属于组织 `{}`" @@ -223,7 +294,6 @@ msgstr "" "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" #: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 -#: assets/models/domain.py:186 #: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_rest_password_success.html:8 #: authentication/templates/authentication/_msg_rest_public_key_success.html:8 @@ -248,23 +318,6 @@ msgstr "应用管理" msgid "Category" msgstr "类别" -#: applications/models.py:15 assets/models/_user.py:46 -#: assets/models/automations/base.py:20 assets/models/cmd_filter.py:74 -#: assets/models/platform.py:74 assets/serializers/asset/common.py:63 -#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 -#: audits/serializers.py:40 ops/models/job.py:42 -#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 -#: terminal/models/component/storage.py:57 -#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 -#: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:16 -#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 -#: tickets/serializers/ticket/ticket.py:18 -#: xpack/plugins/change_auth_plan/models/app.py:27 -#: xpack/plugins/change_auth_plan/models/app.py:152 -msgid "Type" -msgstr "类型" - #: applications/models.py:17 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/serializers/account.py:61 msgid "Attrs" @@ -283,7 +336,7 @@ msgstr "匹配应用" msgid "The parameter 'action' must be [{}]" msgstr "参数 'action' 必须是 [{}]" -#: assets/api/domain.py:57 +#: assets/api/domain.py:56 msgid "Number required" msgstr "需要为数字" @@ -327,7 +380,6 @@ msgid "Failed" msgstr "失败" #: assets/const/account.py:12 assets/models/_user.py:35 -#: assets/models/domain.py:194 assets/serializers/domain.py:46 #: audits/signal_handlers.py:46 authentication/confirm/password.py:9 #: authentication/forms.py:32 #: authentication/templates/authentication/login.html:228 @@ -473,23 +525,20 @@ msgstr "普通用户" msgid "Admin user" msgstr "特权用户" -#: assets/models/_user.py:36 assets/models/domain.py:195 -#: assets/serializers/domain.py:50 -#: xpack/plugins/change_auth_plan/models/asset.py:54 +#: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 #: xpack/plugins/change_auth_plan/models/asset.py:131 #: xpack/plugins/change_auth_plan/models/asset.py:207 msgid "SSH private key" msgstr "SSH 密钥" -#: assets/models/_user.py:37 assets/models/domain.py:196 -#: xpack/plugins/change_auth_plan/models/asset.py:57 +#: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 #: xpack/plugins/change_auth_plan/models/asset.py:127 #: xpack/plugins/change_auth_plan/models/asset.py:203 msgid "SSH public key" msgstr "SSH 公钥" #: assets/models/_user.py:41 assets/models/automations/base.py:92 -#: assets/models/domain.py:29 assets/models/gathered_user.py:19 +#: assets/models/domain.py:26 assets/models/gathered_user.py:19 #: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 #: ops/models/base.py:54 ops/models/job.py:108 orgs/models.py:73 #: perms/models/asset_permission.py:74 users/models/group.py:18 @@ -502,8 +551,8 @@ msgstr "创建日期" msgid "Date updated" msgstr "更新日期" -#: assets/models/_user.py:43 assets/models/base.py:58 -#: assets/models/cmd_filter.py:44 assets/models/cmd_filter.py:91 +#: assets/models/_user.py:43 assets/models/base.py:63 +#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:87 #: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 #: orgs/models.py:71 perms/models/asset_permission.py:75 #: users/models/user.py:710 users/serializers/group.py:33 @@ -515,10 +564,10 @@ msgstr "创建者" msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/_user.py:48 assets/models/domain.py:189 -#: authentication/models/connection_token.py:35 perms/models/perm_token.py:16 -#: terminal/models/applet/applet.py:26 terminal/serializers/session.py:18 -#: terminal/serializers/session.py:32 terminal/serializers/storage.py:68 +#: assets/models/_user.py:48 authentication/models/connection_token.py:35 +#: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 +#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 +#: terminal/serializers/storage.py:68 msgid "Protocol" msgstr "协议" @@ -584,6 +633,7 @@ msgid "Dynamic user" msgstr "动态用户" #: assets/models/account.py:55 +#: authentication/serializers/connection_token.py:108 msgid "Su from" msgstr "切换自" @@ -624,9 +674,8 @@ msgstr "可以查看资产账号密码" msgid "Can change asset account template secret" msgstr "可以更改资产账号密码" -#: assets/models/asset/common.py:82 assets/models/domain.py:187 -#: assets/models/platform.py:22 settings/serializers/auth/radius.py:15 -#: settings/serializers/auth/sms.py:57 +#: assets/models/asset/common.py:82 assets/models/platform.py:22 +#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:57 #: xpack/plugins/cloud/serializers/account_attrs.py:73 msgid "Port" msgstr "端口" @@ -638,8 +687,8 @@ msgstr "端口" msgid "Platform" msgstr "资产平台" -#: assets/models/asset/common.py:95 assets/models/domain.py:32 -#: assets/models/domain.py:191 assets/serializers/asset/common.py:64 +#: assets/models/asset/common.py:95 assets/models/domain.py:29 +#: assets/serializers/asset/common.py:64 msgid "Domain" msgstr "网域" @@ -653,9 +702,9 @@ msgid "Nodes" msgstr "节点" #: assets/models/asset/common.py:98 assets/models/automations/base.py:21 -#: assets/models/base.py:56 assets/models/cmd_filter.py:39 -#: assets/models/domain.py:193 assets/models/label.py:21 -#: terminal/models/applet/applet.py:25 users/serializers/user.py:202 +#: assets/models/base.py:61 assets/models/cmd_filter.py:35 +#: assets/models/label.py:21 terminal/models/applet/applet.py:25 +#: users/serializers/user.py:202 msgid "Is active" msgstr "激活" @@ -746,14 +795,14 @@ msgstr "密码选择器" msgid "Submit selector" msgstr "提交按钮选择器" -#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:38 +#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:34 #: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 #: perms/serializers/permission.py:32 rbac/tree.py:37 msgid "Accounts" msgstr "账号管理" #: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:32 +#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 #: ops/models/base.py:17 ops/models/job.py:44 #: terminal/templates/terminal/_msg_command_execute_alert.html:16 #: xpack/plugins/change_auth_plan/models/asset.py:40 @@ -810,8 +859,8 @@ msgstr "触发模式" msgid "Automation task execution" msgstr "自动化任务执行" -#: assets/models/automations/change_secret.py:15 assets/models/base.py:52 -#: assets/serializers/account/account.py:95 assets/serializers/base.py:13 +#: assets/models/automations/change_secret.py:15 assets/models/base.py:57 +#: assets/serializers/account/account.py:97 assets/serializers/base.py:13 msgid "Secret type" msgstr "密文类型" @@ -821,7 +870,7 @@ msgid "Secret strategy" msgstr "密钥策略" #: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:54 +#: assets/models/automations/change_secret.py:57 assets/models/base.py:59 #: assets/serializers/base.py:16 authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 @@ -935,97 +984,61 @@ msgstr "是否成功" msgid "Account backup execution" msgstr "账号备份执行" -#: assets/models/base.py:27 +#: assets/models/base.py:26 msgid "Connectivity" msgstr "可连接性" -#: assets/models/base.py:29 authentication/models/temp_token.py:12 +#: assets/models/base.py:28 authentication/models/temp_token.py:12 msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:55 +#: assets/models/base.py:60 msgid "Privileged" msgstr "特权账号" -#: assets/models/cmd_filter.py:32 perms/models/asset_permission.py:56 +#: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:56 #: users/models/group.py:31 users/models/user.py:671 msgid "User group" msgstr "用户组" -#: assets/models/cmd_filter.py:52 +#: assets/models/cmd_filter.py:48 msgid "Command filter" msgstr "命令过滤器" -#: assets/models/cmd_filter.py:59 -msgid "Regex" -msgstr "正则表达式" - -#: assets/models/cmd_filter.py:60 terminal/backends/command/serializers.py:15 -#: terminal/models/session/session.py:41 -#: terminal/templates/terminal/_msg_command_alert.html:12 -#: terminal/templates/terminal/_msg_command_execute_alert.html:10 -msgid "Command" -msgstr "命令" - -#: assets/models/cmd_filter.py:66 +#: assets/models/cmd_filter.py:62 msgid "Deny" msgstr "拒绝" -#: assets/models/cmd_filter.py:68 +#: assets/models/cmd_filter.py:63 +msgid "Allow" +msgstr "允许" + +#: assets/models/cmd_filter.py:64 msgid "Reconfirm" msgstr "复核" -#: assets/models/cmd_filter.py:72 +#: assets/models/cmd_filter.py:68 msgid "Filter" msgstr "过滤器" -#: assets/models/cmd_filter.py:79 settings/serializers/basic.py:10 -#: xpack/plugins/license/models.py:29 -msgid "Content" -msgstr "内容" - -#: assets/models/cmd_filter.py:79 -msgid "One line one command" -msgstr "每行一个命令" - -#: assets/models/cmd_filter.py:80 -msgid "Ignore case" -msgstr "忽略大小写" - -#: assets/models/cmd_filter.py:95 +#: assets/models/cmd_filter.py:91 msgid "Command filter rule" msgstr "命令过滤规则" -#: assets/models/cmd_filter.py:138 -msgid "The generated regular expression is incorrect: {}" -msgstr "生成的正则表达式有误" - -#: assets/models/cmd_filter.py:164 tickets/const.py:11 -msgid "Command confirm" -msgstr "命令复核" - -#: assets/models/domain.py:121 +#: assets/models/domain.py:153 #, python-brace-format msgid "Unable to connect to port {port} on {address}" msgstr "无法连接到 {address} 上的端口 {port}" -#: assets/models/domain.py:124 authentication/middleware.py:76 +#: assets/models/domain.py:156 authentication/middleware.py:76 #: xpack/plugins/cloud/providers/fc.py:48 msgid "Authentication failed" msgstr "认证失败" -#: assets/models/domain.py:126 assets/models/domain.py:148 +#: assets/models/domain.py:158 assets/models/domain.py:185 msgid "Connect failed" msgstr "连接失败" -#: assets/models/domain.py:207 -msgid "Gateway" -msgstr "网关" - -#: assets/models/domain.py:209 -msgid "Test gateway" -msgstr "测试网关" - #: assets/models/gathered_user.py:16 msgid "Present" msgstr "存在" @@ -1260,14 +1273,13 @@ msgstr "定时执行" msgid "Currently only mail sending is supported" msgstr "当前只支持邮件发送" -#: assets/serializers/asset/common.py:68 assets/serializers/domain.py:61 -#: assets/serializers/platform.py:101 -#: authentication/serializers/connection_token.py:88 +#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 +#: authentication/serializers/connection_token.py:89 #: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 msgid "Protocols" msgstr "协议组" -#: assets/serializers/asset/common.py:87 assets/serializers/domain.py:72 +#: assets/serializers/asset/common.py:87 msgid "Address" msgstr "地址" @@ -1357,7 +1369,7 @@ msgstr "* 密码长度范围 6-30 位" #: assets/serializers/automations/change_secret.py:117 #: assets/serializers/automations/change_secret.py:145 audits/const.py:73 -#: audits/models.py:39 common/const/choices.py:18 +#: audits/models.py:39 common/const/choices.py:18 ops/serializers/celery.py:39 #: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 #: xpack/plugins/change_auth_plan/serializers/asset.py:189 msgid "Success" @@ -1369,7 +1381,7 @@ msgstr "成功" msgid "Executed amount" msgstr "执行次数" -#: assets/serializers/base.py:21 assets/serializers/domain.py:54 +#: assets/serializers/base.py:21 msgid "Key password" msgstr "密钥密码" @@ -1381,11 +1393,11 @@ msgstr "约束" msgid "Types" msgstr "类型" -#: assets/serializers/domain.py:17 assets/serializers/label.py:12 +#: assets/serializers/domain.py:14 assets/serializers/label.py:12 msgid "Assets amount" msgstr "资产数量" -#: assets/serializers/domain.py:18 +#: assets/serializers/domain.py:15 msgid "Gateways count" msgstr "网关数量" @@ -2173,7 +2185,7 @@ msgstr "资产未激活" msgid "No account" msgstr "登录账号" -#: authentication/models/connection_token.py:172 +#: authentication/models/connection_token.py:177 msgid "Super connection token" msgstr "超级连接令牌" @@ -2201,11 +2213,11 @@ msgstr "异地登录提醒" msgid "binding reminder" msgstr "绑定提醒" -#: authentication/serializers/connection_token.py:18 +#: authentication/serializers/connection_token.py:19 msgid "Expired time" msgstr "过期时间" -#: authentication/serializers/connection_token.py:139 +#: authentication/serializers/connection_token.py:157 #, fuzzy #| msgid "Expired" msgid "Expired now" @@ -2894,11 +2906,11 @@ msgstr "发布站内信" msgid "No account available" msgstr "没有账号可以使用" -#: ops/ansible/inventory.py:175 +#: ops/ansible/inventory.py:178 msgid "Ansible disabled" msgstr "Ansible 已禁用" -#: ops/ansible/inventory.py:191 +#: ops/ansible/inventory.py:194 msgid "Skip hosts below:" msgstr "跳过一下主机:" @@ -6977,6 +6989,12 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "Gateway" +#~ msgstr "网关" + +#~ msgid "Test gateway" +#~ msgstr "测试网关" + #~ msgid "" #~ "Format for comma-delimited string, with * indicating a match all. " #~ "Protocol options: {}" From 70fb00c4eef195afa7267bcc8e90e04a0b70d87d Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 4 Dec 2022 18:48:48 +0800 Subject: [PATCH 477/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=BF=87=E6=BB=A4ACL=E5=BA=8F=E5=88=97=E7=B1=BB?= =?UTF-8?q?=E5=92=8C=E9=83=A8=E5=88=86=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/serializers/__init__.py | 2 +- apps/acls/serializers/base.py | 5 + .../{command_filter.py => command_acl.py} | 8 +- apps/locale/ja/LC_MESSAGES/django.mo | 4 +- apps/locale/ja/LC_MESSAGES/django.po | 7841 ----------------- apps/locale/zh/LC_MESSAGES/django.mo | 4 +- apps/locale/zh/LC_MESSAGES/django.po | 7552 ---------------- 7 files changed, 17 insertions(+), 15399 deletions(-) rename apps/acls/serializers/{command_filter.py => command_acl.py} (79%) delete mode 100644 apps/locale/ja/LC_MESSAGES/django.po delete mode 100644 apps/locale/zh/LC_MESSAGES/django.po diff --git a/apps/acls/serializers/__init__.py b/apps/acls/serializers/__init__.py index 465474c4f..88814b6c4 100644 --- a/apps/acls/serializers/__init__.py +++ b/apps/acls/serializers/__init__.py @@ -1,4 +1,4 @@ -from .command_filter import * from .login_acl import * from .login_asset_acl import * from .login_asset_check import * +from .command_acl import * diff --git a/apps/acls/serializers/base.py b/apps/acls/serializers/base.py index 862f38827..fdf6f8a23 100644 --- a/apps/acls/serializers/base.py +++ b/apps/acls/serializers/base.py @@ -79,6 +79,11 @@ class BaseUserAssetAccountACLSerializerMixin(serializers.Serializer): } def validate_reviewers(self, reviewers): + action = self.initial_data.get('action') + if not action and self.instance: + action = self.instance.action + if action != ActionChoices.review: + return reviewers org_id = self.fields["org_id"].default() org = Organization.get_instance(org_id) if not org: diff --git a/apps/acls/serializers/command_filter.py b/apps/acls/serializers/command_acl.py similarity index 79% rename from apps/acls/serializers/command_filter.py rename to apps/acls/serializers/command_acl.py index a8e62da41..4e0d3edaa 100644 --- a/apps/acls/serializers/command_filter.py +++ b/apps/acls/serializers/command_acl.py @@ -1,4 +1,5 @@ from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers from acls.models import CommandGroup, CommandFilterACL from common.drf.fields import ObjectRelatedField @@ -9,6 +10,11 @@ __all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer"] class CommandGroupSerializer(BulkOrgResourceModelSerializer): + type = serializers.ChoiceField( + choices=CommandGroup.TypeChoices.choices, default=CommandGroup.TypeChoices.command, + label=_('Type') + ) + class Meta: model = CommandGroup fields = ['id', 'name', 'type', 'content', 'ignore_case', 'comment'] @@ -16,7 +22,7 @@ class CommandGroupSerializer(BulkOrgResourceModelSerializer): class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer): command_groups = ObjectRelatedField( - queryset=CommandGroup.objects, many=True, required=False, label=_('Commands') + queryset=CommandGroup.objects, many=True, required=False, label=_('Command group') ) class Meta(BaseSerializer.Meta): diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 75ff2b13c..54a75e2ed 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:822927ab8fef4d3d70848fe1ecb109e1505a1346cc56f5ed79f5355e5c2d7e90 -size 116337 +oid sha256:0818af791dad7cd50e19c41de0bc8967f9d08f949f48d5c2020786153a743349 +size 116392 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po deleted file mode 100644 index 6acaf7e80..000000000 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ /dev/null @@ -1,7841 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-12-04 17:56+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#: acls/apps.py:7 -msgid "Acls" -msgstr "Acls" - -#: acls/models/base.py:20 tickets/const.py:45 -#: tickets/templates/tickets/approve_check_password.html:49 -msgid "Reject" -msgstr "拒否" - -#: acls/models/base.py:21 -msgid "Accept" -msgstr "同意" - -#: acls/models/base.py:22 -msgid "Review" -msgstr "レビュー" - -#: acls/models/base.py:71 acls/models/command_acl.py:22 -#: acls/serializers/base.py:34 applications/models.py:10 -#: assets/models/_user.py:33 assets/models/asset/common.py:81 -#: assets/models/asset/common.py:91 assets/models/base.py:54 -#: assets/models/cmd_filter.py:21 assets/models/domain.py:24 -#: assets/models/group.py:20 assets/models/label.py:17 -#: assets/models/platform.py:21 assets/models/platform.py:72 -#: assets/serializers/asset/common.py:86 assets/serializers/platform.py:138 -#: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 -#: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 -#: perms/models/asset_permission.py:51 rbac/models/role.py:29 -#: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 -#: terminal/models/component/endpoint.py:87 -#: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 -#: terminal/models/component/terminal.py:79 users/forms/profile.py:33 -#: users/models/group.py:15 users/models/user.py:665 -#: xpack/plugins/cloud/models.py:30 -msgid "Name" -msgstr "名前" - -#: acls/models/base.py:73 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:90 -msgid "Priority" -msgstr "優先順位" - -#: acls/models/base.py:74 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:91 -msgid "1-100, the lower the value will be match first" -msgstr "1-100、低い値は最初に一致します" - -#: acls/models/base.py:77 acls/serializers/base.py:63 -#: assets/models/cmd_filter.py:77 audits/models.py:50 audits/serializers.py:69 -#: authentication/templates/authentication/_access_key_modal.html:34 -msgid "Action" -msgstr "アクション" - -#: acls/models/base.py:78 acls/serializers/base.py:59 -#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:82 -msgid "Reviewers" -msgstr "レビュアー" - -#: acls/models/base.py:79 authentication/models/access_key.py:15 -#: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 -#: tickets/const.py:37 -msgid "Active" -msgstr "アクティブ" - -#: acls/models/base.py:80 acls/models/command_acl.py:29 -#: applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:100 assets/models/automations/base.py:22 -#: assets/models/backup.py:29 assets/models/base.py:62 -#: assets/models/cmd_filter.py:36 assets/models/cmd_filter.py:84 -#: assets/models/domain.py:25 assets/models/group.py:23 -#: assets/models/label.py:22 assets/models/platform.py:77 -#: ops/models/adhoc.py:27 ops/models/job.py:50 ops/models/playbook.py:17 -#: orgs/models.py:74 perms/models/asset_permission.py:71 rbac/models/role.py:37 -#: settings/models.py:38 terminal/models/applet/applet.py:28 -#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 -#: terminal/models/component/endpoint.py:24 -#: terminal/models/component/endpoint.py:97 -#: terminal/models/component/storage.py:28 -#: terminal/models/component/terminal.py:91 tickets/models/comment.py:32 -#: tickets/models/ticket/general.py:296 users/models/group.py:16 -#: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 -#: xpack/plugins/gathered_user/models.py:26 -msgid "Comment" -msgstr "コメント" - -#: acls/models/base.py:91 acls/models/login_acl.py:13 -#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 -#: assets/models/cmd_filter.py:24 assets/models/label.py:15 audits/models.py:29 -#: audits/models.py:48 audits/models.py:79 -#: authentication/models/connection_token.py:25 -#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:69 -#: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 -#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 -#: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 -#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 -#: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:895 -#: users/models/user.py:926 users/serializers/group.py:19 -msgid "User" -msgstr "ユーザー" - -#: acls/models/base.py:93 acls/serializers/base.py:56 -#: assets/models/account.py:51 assets/models/asset/common.py:83 -#: assets/models/asset/common.py:212 assets/models/cmd_filter.py:32 -#: assets/models/gathered_user.py:14 assets/serializers/account/account.py:59 -#: assets/serializers/automations/change_secret.py:100 -#: assets/serializers/automations/change_secret.py:122 -#: assets/serializers/domain.py:17 assets/serializers/gathered_user.py:11 -#: assets/serializers/label.py:30 audits/models.py:33 -#: authentication/models/connection_token.py:29 -#: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 -#: terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 -#: terminal/models/session/session.py:32 terminal/notifications.py:90 -#: xpack/plugins/change_auth_plan/models/asset.py:200 -#: xpack/plugins/change_auth_plan/serializers/asset.py:172 -#: xpack/plugins/cloud/models.py:219 -msgid "Asset" -msgstr "資産" - -#: acls/models/base.py:95 acls/serializers/base.py:57 -#: assets/models/account.py:61 -#: assets/serializers/automations/change_secret.py:101 -#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 -#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 -msgid "Account" -msgstr "アカウント" - -#: acls/models/command_acl.py:17 assets/models/cmd_filter.py:56 -#: terminal/backends/command/serializers.py:15 -#: terminal/models/session/session.py:41 -#: terminal/templates/terminal/_msg_command_alert.html:12 -#: terminal/templates/terminal/_msg_command_execute_alert.html:10 -msgid "Command" -msgstr "コマンド" - -#: acls/models/command_acl.py:18 assets/models/cmd_filter.py:55 -msgid "Regex" -msgstr "正規情報" - -#: acls/models/command_acl.py:25 applications/models.py:15 -#: assets/models/_user.py:46 assets/models/automations/base.py:20 -#: assets/models/cmd_filter.py:70 assets/models/platform.py:74 -#: assets/serializers/asset/common.py:63 -#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 -#: audits/serializers.py:40 ops/models/job.py:42 -#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 -#: terminal/models/component/storage.py:57 -#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 -#: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:16 -#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 -#: tickets/serializers/ticket/ticket.py:18 -#: xpack/plugins/change_auth_plan/models/app.py:27 -#: xpack/plugins/change_auth_plan/models/app.py:152 -msgid "Type" -msgstr "タイプ" - -#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 -msgid "Content" -msgstr "コンテンツ" - -#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 -msgid "One line one command" -msgstr "1行1コマンド" - -#: acls/models/command_acl.py:28 assets/models/cmd_filter.py:76 -msgid "Ignore case" -msgstr "家を無視する" - -#: acls/models/command_acl.py:35 -#, fuzzy -#| msgid "Command record" -msgid "Command group" -msgstr "コマンドレコード" - -#: acls/models/command_acl.py:88 -msgid "The generated regular expression is incorrect: {}" -msgstr "生成された正規表現が正しくありません: {}" - -#: acls/models/command_acl.py:107 acls/serializers/command_filter.py:19 -#, fuzzy -#| msgid "Command" -msgid "Commands" -msgstr "コマンド" - -#: acls/models/command_acl.py:114 -#, fuzzy -#| msgid "Command" -msgid "Command acl" -msgstr "コマンド" - -#: acls/models/command_acl.py:120 tickets/const.py:11 -msgid "Command confirm" -msgstr "コマンドの確認" - -#: acls/models/login_acl.py:16 -msgid "Rule" -msgstr "ルール" - -#: acls/models/login_acl.py:20 -msgid "Login acl" -msgstr "ログインacl" - -#: acls/models/login_acl.py:54 tickets/const.py:10 -msgid "Login confirm" -msgstr "ログイン確認" - -#: acls/models/login_asset_acl.py:12 -msgid "Login asset acl" -msgstr "ログインasset acl" - -#: acls/models/login_asset_acl.py:21 tickets/const.py:12 -msgid "Login asset confirm" -msgstr "ログイン資産の確認" - -#: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 -msgid "Format for comma-delimited string, with * indicating a match all. " -msgstr "コンマ区切り文字列の形式。* はすべて一致することを示します。" - -#: acls/serializers/base.py:18 acls/serializers/base.py:49 -#: assets/models/_user.py:34 assets/models/base.py:55 -#: assets/models/gathered_user.py:15 audits/models.py:95 -#: authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models/temp_token.py:9 -#: authentication/templates/authentication/_msg_different_city.html:9 -#: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: users/forms/profile.py:32 users/models/user.py:663 -#: users/templates/users/_msg_user_created.html:12 -#: xpack/plugins/change_auth_plan/models/asset.py:35 -#: xpack/plugins/change_auth_plan/models/asset.py:196 -#: xpack/plugins/cloud/serializers/account_attrs.py:26 -msgid "Username" -msgstr "ユーザー名" - -#: acls/serializers/base.py:25 -msgid "" -"Format for comma-delimited string, with * indicating a match all. Such as: " -"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" -"db8:1a:1110::/64 (Domain name support)" -msgstr "" -"コンマ区切り文字列の形式。* はすべて一致することを示します。例: " -"192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" -"db8:1a:1110:::/64 (ドメイン名サポート)" - -#: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 -#, fuzzy -#| msgid "Host" -msgid "IP/Host" -msgstr "ホスト" - -#: acls/serializers/base.py:85 tickets/serializers/ticket/ticket.py:66 -msgid "The organization `{}` does not exist" -msgstr "組織 '{}'は存在しません" - -#: acls/serializers/base.py:91 -msgid "None of the reviewers belong to Organization `{}`" -msgstr "いずれのレビューアも組織 '{}' に属していません" - -#: acls/serializers/rules/rules.py:20 -#: xpack/plugins/cloud/serializers/task.py:22 -msgid "IP address invalid: `{}`" -msgstr "IPアドレスが無効: '{}'" - -#: acls/serializers/rules/rules.py:25 -msgid "" -"Format for comma-delimited string, with * indicating a match all. Such as: " -"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" -"db8:1a:1110::/64 " -msgstr "" -"コンマ区切り文字列の形式。* はすべて一致することを示します。例: " -"192.168.10.1、192.168.1.0/24、10.1.1.1-10.1.1.20、2001:db8:2de::e13、2001:" -"db8:1a:1110::/64" - -#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 -#: authentication/templates/authentication/_msg_oauth_bind.html:12 -#: authentication/templates/authentication/_msg_rest_password_success.html:8 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 -msgid "IP" -msgstr "IP" - -#: acls/serializers/rules/rules.py:35 -msgid "Time Period" -msgstr "期間" - -#: applications/apps.py:9 -msgid "Applications" -msgstr "アプリケーション" - -#: applications/models.py:12 assets/models/label.py:20 -#: assets/models/platform.py:73 assets/serializers/asset/common.py:62 -#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 -#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:23 -#: tickets/models/ticket/apply_application.py:13 -#: xpack/plugins/change_auth_plan/models/app.py:24 -msgid "Category" -msgstr "カテゴリ" - -#: applications/models.py:17 xpack/plugins/cloud/models.py:35 -#: xpack/plugins/cloud/serializers/account.py:61 -msgid "Attrs" -msgstr "ツールバーの" - -#: applications/models.py:23 -msgid "Application" -msgstr "アプリケーション" - -#: applications/models.py:27 -msgid "Can match application" -msgstr "アプリケーションを一致させることができます" - -#: assets/api/automations/base.py:76 -#: xpack/plugins/change_auth_plan/api/asset.py:94 -msgid "The parameter 'action' must be [{}]" -msgstr "パラメータ 'action' は [{}] でなければなりません。" - -#: assets/api/domain.py:56 -msgid "Number required" -msgstr "必要な数" - -#: assets/api/node.py:62 -msgid "You can't update the root node name" -msgstr "ルートノード名を更新できません" - -#: assets/api/node.py:69 -msgid "You can't delete the root node ({})" -msgstr "ルートノード ({}) を削除できません。" - -#: assets/api/node.py:72 -msgid "Deletion failed and the node contains assets" -msgstr "削除に失敗し、ノードにアセットが含まれています。" - -#: assets/apps.py:9 -msgid "App assets" -msgstr "アプリ資産" - -#: assets/automations/base/manager.py:123 -#, fuzzy -#| msgid "Disabled" -msgid "{} disabled" -msgstr "無効" - -#: assets/const/account.py:6 audits/const.py:6 audits/const.py:63 -#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 -#: common/utils/ip/utils.py:84 -msgid "Unknown" -msgstr "不明" - -#: assets/const/account.py:7 -msgid "Ok" -msgstr "OK" - -#: assets/const/account.py:8 -#: assets/serializers/automations/change_secret.py:118 -#: assets/serializers/automations/change_secret.py:146 audits/const.py:74 -#: common/const/choices.py:19 -#: xpack/plugins/change_auth_plan/serializers/asset.py:190 -#: xpack/plugins/cloud/const.py:33 -msgid "Failed" -msgstr "失敗しました" - -#: assets/const/account.py:12 assets/models/_user.py:35 -#: audits/signal_handlers.py:46 authentication/confirm/password.py:9 -#: authentication/forms.py:32 -#: authentication/templates/authentication/login.html:228 -#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:105 -#: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models/base.py:42 -#: xpack/plugins/change_auth_plan/models/base.py:117 -#: xpack/plugins/change_auth_plan/models/base.py:192 -#: xpack/plugins/change_auth_plan/serializers/base.py:21 -#: xpack/plugins/change_auth_plan/serializers/base.py:73 -#: xpack/plugins/cloud/serializers/account_attrs.py:28 -msgid "Password" -msgstr "パスワード" - -#: assets/const/account.py:13 -#, fuzzy -#| msgid "SSH Key" -msgid "SSH key" -msgstr "SSHキー" - -#: assets/const/account.py:14 authentication/models/access_key.py:31 -msgid "Access key" -msgstr "アクセスキー" - -#: assets/const/account.py:15 assets/models/_user.py:38 -#: authentication/models/sso_token.py:13 -msgid "Token" -msgstr "トークン" - -#: assets/const/automation.py:13 -msgid "Ping" -msgstr "" - -#: assets/const/automation.py:14 -#, fuzzy -#| msgid "Gather account" -msgid "Gather facts" -msgstr "アカウントを集める" - -#: assets/const/automation.py:15 -#, fuzzy -#| msgid "Gather account" -msgid "Create account" -msgstr "アカウントを集める" - -#: assets/const/automation.py:16 -#, fuzzy -#| msgid "Change auth" -msgid "Change secret" -msgstr "秘密を改める" - -#: assets/const/automation.py:17 -#, fuzzy -#| msgid "Verify auth" -msgid "Verify account" -msgstr "パスワード/キーの確認" - -#: assets/const/automation.py:18 -#, fuzzy -#| msgid "Gather account" -msgid "Gather accounts" -msgstr "アカウントを集める" - -#: assets/const/automation.py:38 assets/serializers/account/base.py:26 -msgid "Specific" -msgstr "" - -#: assets/const/automation.py:39 ops/const.py:20 -#: xpack/plugins/change_auth_plan/models/base.py:28 -msgid "All assets use the same random password" -msgstr "すべての資産は同じランダムパスワードを使用します" - -#: assets/const/automation.py:40 ops/const.py:21 -#: xpack/plugins/change_auth_plan/models/base.py:29 -msgid "All assets use different random password" -msgstr "すべての資産は異なるランダムパスワードを使用します" - -#: assets/const/automation.py:44 ops/const.py:13 -#: xpack/plugins/change_auth_plan/models/asset.py:30 -msgid "Append SSH KEY" -msgstr "追加" - -#: assets/const/automation.py:45 ops/const.py:14 -#: xpack/plugins/change_auth_plan/models/asset.py:31 -msgid "Empty and append SSH KEY" -msgstr "すべてクリアして追加" - -#: assets/const/automation.py:46 ops/const.py:15 -#: xpack/plugins/change_auth_plan/models/asset.py:32 -msgid "Replace (The key generated by JumpServer) " -msgstr "置換(JumpServerによって生成された鍵)" - -#: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:59 -#: terminal/models/component/endpoint.py:12 -#: xpack/plugins/cloud/serializers/account_attrs.py:72 -msgid "Host" -msgstr "ホスト" - -#: assets/const/category.py:12 -msgid "Device" -msgstr "" - -#: assets/const/category.py:13 assets/models/asset/database.py:8 -#: assets/models/asset/database.py:34 -#: xpack/plugins/change_auth_plan/models/app.py:31 -msgid "Database" -msgstr "データベース" - -#: assets/const/category.py:14 -#, fuzzy -#| msgid "Cloud center" -msgid "Cloud service" -msgstr "クラウドセンター" - -#: assets/const/category.py:15 audits/const.py:61 -#: terminal/models/applet/applet.py:18 -msgid "Web" -msgstr "" - -#: assets/const/device.py:7 terminal/models/applet/applet.py:17 -#: tickets/const.py:8 -msgid "General" -msgstr "一般" - -#: assets/const/device.py:8 -#, fuzzy -#| msgid "Switch from" -msgid "Switch" -msgstr "から切り替え" - -#: assets/const/device.py:9 -msgid "Router" -msgstr "" - -#: assets/const/device.py:10 -msgid "Firewall" -msgstr "" - -#: assets/const/web.py:7 -#, fuzzy -#| msgid "Website icon" -msgid "Website" -msgstr "ウェブサイトのアイコン" - -#: assets/models/_user.py:24 -msgid "Automatic managed" -msgstr "自動管理" - -#: assets/models/_user.py:25 -msgid "Manually input" -msgstr "手動入力" - -#: assets/models/_user.py:29 -msgid "Common user" -msgstr "共通ユーザー" - -#: assets/models/_user.py:30 -msgid "Admin user" -msgstr "管理ユーザー" - -#: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 -#: xpack/plugins/change_auth_plan/models/asset.py:131 -#: xpack/plugins/change_auth_plan/models/asset.py:207 -msgid "SSH private key" -msgstr "SSH秘密鍵" - -#: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 -#: xpack/plugins/change_auth_plan/models/asset.py:127 -#: xpack/plugins/change_auth_plan/models/asset.py:203 -msgid "SSH public key" -msgstr "SSHパブリックキー" - -#: assets/models/_user.py:41 assets/models/automations/base.py:92 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 -#: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:54 ops/models/job.py:108 orgs/models.py:73 -#: perms/models/asset_permission.py:74 users/models/group.py:18 -#: users/models/user.py:927 -msgid "Date created" -msgstr "作成された日付" - -#: assets/models/_user.py:42 assets/models/gathered_user.py:20 -#: common/db/models.py:77 common/mixins/models.py:51 -msgid "Date updated" -msgstr "更新日" - -#: assets/models/_user.py:43 assets/models/base.py:63 -#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:87 -#: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 -#: orgs/models.py:71 perms/models/asset_permission.py:75 -#: users/models/user.py:710 users/serializers/group.py:33 -#: xpack/plugins/change_auth_plan/models/base.py:48 -msgid "Created by" -msgstr "によって作成された" - -#: assets/models/_user.py:45 -msgid "Username same with user" -msgstr "ユーザーと同じユーザー名" - -#: assets/models/_user.py:48 authentication/models/connection_token.py:35 -#: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 -#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 -#: terminal/serializers/storage.py:68 -msgid "Protocol" -msgstr "プロトコル" - -#: assets/models/_user.py:49 -msgid "Auto push" -msgstr "オートプッシュ" - -#: assets/models/_user.py:50 -msgid "Sudo" -msgstr "すど" - -#: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 -msgid "Shell" -msgstr "シェル" - -#: assets/models/_user.py:52 -msgid "Login mode" -msgstr "ログインモード" - -#: assets/models/_user.py:53 -msgid "SFTP Root" -msgstr "SFTPルート" - -#: assets/models/_user.py:54 -msgid "Home" -msgstr "ホーム" - -#: assets/models/_user.py:55 -msgid "System groups" -msgstr "システムグループ" - -#: assets/models/_user.py:58 -msgid "User switch" -msgstr "ユーザースイッチ" - -#: assets/models/_user.py:59 -msgid "Switch from" -msgstr "から切り替え" - -#: assets/models/_user.py:65 audits/models.py:34 -#: terminal/backends/command/models.py:22 -#: terminal/backends/command/serializers.py:36 -#: xpack/plugins/change_auth_plan/models/app.py:35 -#: xpack/plugins/change_auth_plan/models/app.py:146 -msgid "System user" -msgstr "システムユーザー" - -#: assets/models/_user.py:67 -msgid "Can match system user" -msgstr "システムユーザーに一致できます" - -#: assets/models/account.py:45 common/db/fields.py:222 -#: settings/serializers/terminal.py:12 -msgid "All" -msgstr "すべて" - -#: assets/models/account.py:46 -msgid "Manual input" -msgstr "手動入力" - -#: assets/models/account.py:47 -msgid "Dynamic user" -msgstr "動的コード" - -#: assets/models/account.py:55 -#: authentication/serializers/connection_token.py:108 -#, fuzzy -#| msgid "Switch from" -msgid "Su from" -msgstr "から切り替え" - -#: assets/models/account.py:57 settings/serializers/auth/cas.py:18 -#: terminal/models/applet/applet.py:22 -msgid "Version" -msgstr "バージョン" - -#: assets/models/account.py:67 -msgid "Can view asset account secret" -msgstr "資産アカウントの秘密を表示できます" - -#: assets/models/account.py:68 -msgid "Can change asset account secret" -msgstr "資産口座の秘密を変更できます" - -#: assets/models/account.py:69 -msgid "Can view asset history account" -msgstr "資産履歴アカウントを表示できます" - -#: assets/models/account.py:70 -msgid "Can view asset history account secret" -msgstr "資産履歴アカウントパスワードを表示できます" - -#: assets/models/account.py:93 assets/serializers/account/account.py:15 -#, fuzzy -#| msgid "Account name" -msgid "Account template" -msgstr "アカウント名" - -#: assets/models/account.py:98 -#, fuzzy -#| msgid "Can view asset account secret" -msgid "Can view asset account template secret" -msgstr "資産アカウントの秘密を表示できます" - -#: assets/models/account.py:99 -#, fuzzy -#| msgid "Can change asset account secret" -msgid "Can change asset account template secret" -msgstr "資産口座の秘密を変更できます" - -#: assets/models/asset/common.py:82 assets/models/platform.py:22 -#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:57 -#: xpack/plugins/cloud/serializers/account_attrs.py:73 -msgid "Port" -msgstr "ポート" - -#: assets/models/asset/common.py:93 assets/models/platform.py:110 -#: assets/serializers/asset/common.py:65 -#: perms/serializers/user_permission.py:21 -#: xpack/plugins/cloud/serializers/account_attrs.py:172 -msgid "Platform" -msgstr "プラットフォーム" - -#: assets/models/asset/common.py:95 assets/models/domain.py:29 -#: assets/serializers/asset/common.py:64 -msgid "Domain" -msgstr "ドメイン" - -#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 -#: assets/serializers/asset/common.py:66 -#: assets/serializers/automations/base.py:21 -#: perms/models/asset_permission.py:62 -#: xpack/plugins/change_auth_plan/models/asset.py:44 -#: xpack/plugins/gathered_user/models.py:24 -msgid "Nodes" -msgstr "ノード" - -#: assets/models/asset/common.py:98 assets/models/automations/base.py:21 -#: assets/models/base.py:61 assets/models/cmd_filter.py:35 -#: assets/models/label.py:21 terminal/models/applet/applet.py:25 -#: users/serializers/user.py:202 -msgid "Is active" -msgstr "アクティブです。" - -#: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 -msgid "Labels" -msgstr "ラベル" - -#: assets/models/asset/common.py:215 -msgid "Can refresh asset hardware info" -msgstr "資産ハードウェア情報を更新できます" - -#: assets/models/asset/common.py:216 -msgid "Can test asset connectivity" -msgstr "資産接続をテストできます" - -#: assets/models/asset/common.py:217 -#, fuzzy -#| msgid "Can push system user to asset" -msgid "Can push account to asset" -msgstr "システムユーザーを資産にプッシュできます" - -#: assets/models/asset/common.py:218 -msgid "Can match asset" -msgstr "アセットを一致させることができます" - -#: assets/models/asset/common.py:219 -msgid "Add asset to node" -msgstr "ノードにアセットを追加する" - -#: assets/models/asset/common.py:220 -msgid "Move asset to node" -msgstr "アセットをノードに移動する" - -#: assets/models/asset/database.py:9 settings/serializers/email.py:36 -msgid "Use SSL" -msgstr "SSLの使用" - -#: assets/models/asset/database.py:10 -#, fuzzy -#| msgid "SP cert" -msgid "CA cert" -msgstr "SP 証明書" - -#: assets/models/asset/database.py:11 -#, fuzzy -#| msgid "Client Secret" -msgid "Client cert" -msgstr "クライアント秘密" - -#: assets/models/asset/database.py:12 -#, fuzzy -#| msgid "Client" -msgid "Client key" -msgstr "クライアント" - -#: assets/models/asset/database.py:13 -#, fuzzy -#| msgid "Host invalid" -msgid "Allow invalid cert" -msgstr "ホスト無効" - -#: assets/models/asset/web.py:9 audits/const.py:67 -#: terminal/serializers/applet_host.py:25 -msgid "Disabled" -msgstr "無効" - -#: assets/models/asset/web.py:10 -msgid "Basic" -msgstr "" - -#: assets/models/asset/web.py:11 assets/models/asset/web.py:17 -msgid "Script" -msgstr "" - -#: assets/models/asset/web.py:13 -#, fuzzy -#| msgid "Auto" -msgid "Autofill" -msgstr "自動" - -#: assets/models/asset/web.py:14 assets/serializers/platform.py:30 -#, fuzzy -#| msgid "Username attr" -msgid "Username selector" -msgstr "ユーザー名のプロパティ" - -#: assets/models/asset/web.py:15 assets/serializers/platform.py:33 -#, fuzzy -#| msgid "Password rules" -msgid "Password selector" -msgstr "パスワードルール" - -#: assets/models/asset/web.py:16 assets/serializers/platform.py:36 -msgid "Submit selector" -msgstr "" - -#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:34 -#: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 -#: perms/serializers/permission.py:32 rbac/tree.py:37 -msgid "Accounts" -msgstr "アカウント" - -#: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 -#: ops/models/base.py:17 ops/models/job.py:44 -#: terminal/templates/terminal/_msg_command_execute_alert.html:16 -#: xpack/plugins/change_auth_plan/models/asset.py:40 -msgid "Assets" -msgstr "資産" - -#: assets/models/automations/base.py:82 assets/models/automations/base.py:89 -#, fuzzy -#| msgid "Automatic managed" -msgid "Automation task" -msgstr "自動管理" - -#: assets/models/automations/base.py:91 audits/models.py:115 -#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 -#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 -#: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:19 -#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:223 -msgid "Status" -msgstr "ステータス" - -#: assets/models/automations/base.py:93 assets/models/backup.py:76 -#: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 -#: ops/models/job.py:109 perms/models/asset_permission.py:67 -#: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 -#: tickets/models/ticket/apply_application.py:30 -#: tickets/models/ticket/apply_asset.py:19 -#: xpack/plugins/change_auth_plan/models/base.py:108 -#: xpack/plugins/change_auth_plan/models/base.py:199 -#: xpack/plugins/gathered_user/models.py:71 -msgid "Date start" -msgstr "開始日" - -#: assets/models/automations/base.py:94 -#: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/celery.py:60 ops/models/job.py:110 -#: terminal/models/applet/host.py:106 -msgid "Date finished" -msgstr "終了日" - -#: assets/models/automations/base.py:96 -#: assets/serializers/automations/base.py:39 -#, fuzzy -#| msgid "Relation snapshot" -msgid "Automation snapshot" -msgstr "製造オーダスナップショット" - -#: assets/models/automations/base.py:100 assets/models/backup.py:87 -#: assets/serializers/account/backup.py:37 -#: assets/serializers/automations/base.py:41 -#: xpack/plugins/change_auth_plan/models/base.py:121 -#: xpack/plugins/change_auth_plan/serializers/base.py:78 -msgid "Trigger mode" -msgstr "トリガーモード" - -#: assets/models/automations/base.py:104 -#: assets/serializers/automations/change_secret.py:103 -#, fuzzy -#| msgid "Command execution" -msgid "Automation task execution" -msgstr "コマンド実行" - -#: assets/models/automations/change_secret.py:15 assets/models/base.py:57 -#: assets/serializers/account/account.py:97 assets/serializers/base.py:13 -#, fuzzy -#| msgid "Secret key" -msgid "Secret type" -msgstr "秘密キー" - -#: assets/models/automations/change_secret.py:19 -#: assets/serializers/automations/change_secret.py:25 -#, fuzzy -#| msgid "SSH Key strategy" -msgid "Secret strategy" -msgstr "SSHキー戦略" - -#: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:59 -#: assets/serializers/base.py:16 authentication/models/temp_token.py:10 -#: authentication/templates/authentication/_access_key_modal.html:31 -#: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 -msgid "Secret" -msgstr "ひみつ" - -#: assets/models/automations/change_secret.py:22 -#: xpack/plugins/change_auth_plan/models/base.py:39 -msgid "Password rules" -msgstr "パスワードルール" - -#: assets/models/automations/change_secret.py:25 -#, fuzzy -#| msgid "SSH Key strategy" -msgid "SSH key change strategy" -msgstr "SSHキー戦略" - -#: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 -#: assets/serializers/account/backup.py:30 -#: assets/serializers/automations/change_secret.py:40 -#: xpack/plugins/change_auth_plan/models/app.py:40 -#: xpack/plugins/change_auth_plan/models/asset.py:63 -#: xpack/plugins/change_auth_plan/serializers/base.py:45 -msgid "Recipient" -msgstr "受信者" - -#: assets/models/automations/change_secret.py:34 -#, fuzzy -#| msgid "Change auth" -msgid "Change secret automation" -msgstr "秘密を改める" - -#: assets/models/automations/change_secret.py:56 -#, fuzzy -#| msgid "Secret" -msgid "Old secret" -msgstr "ひみつ" - -#: assets/models/automations/change_secret.py:58 -#, fuzzy -#| msgid "Date start" -msgid "Date started" -msgstr "開始日" - -#: assets/models/automations/change_secret.py:61 common/const/choices.py:20 -#, fuzzy -#| msgid "WeCom Error" -msgid "Error" -msgstr "企業微信エラー" - -#: assets/models/automations/change_secret.py:64 -#, fuzzy -#| msgid "Change auth" -msgid "Change secret record" -msgstr "秘密を改める" - -#: assets/models/automations/discovery_account.py:8 -#, fuzzy -#| msgid "Verify auth" -msgid "Discovery account automation" -msgstr "パスワード/キーの確認" - -#: assets/models/automations/gather_accounts.py:15 -#: assets/tasks/gather_accounts.py:28 -#, fuzzy -#| msgid "Gather assets users" -msgid "Gather asset accounts" -msgstr "資産ユーザーの収集" - -#: assets/models/automations/gather_facts.py:15 -#, fuzzy -#| msgid "Gather assets users" -msgid "Gather asset facts" -msgstr "資産ユーザーの収集" - -#: assets/models/automations/ping.py:15 -#, fuzzy -#| msgid "Login asset" -msgid "Ping asset" -msgstr "ログイン資産" - -#: assets/models/automations/push_account.py:16 -#, fuzzy -#| msgid "Is service account" -msgid "Push asset account" -msgstr "サービスアカウントです" - -#: assets/models/automations/verify_account.py:15 -#, fuzzy -#| msgid "Verify auth" -msgid "Verify asset account" -msgstr "パスワード/キーの確認" - -#: assets/models/backup.py:37 assets/models/backup.py:95 -msgid "Account backup plan" -msgstr "アカウントバックアップ計画" - -#: assets/models/backup.py:79 -#: authentication/templates/authentication/_msg_oauth_bind.html:11 -#: notifications/notifications.py:186 -#: xpack/plugins/change_auth_plan/models/base.py:111 -#: xpack/plugins/change_auth_plan/models/base.py:200 -#: xpack/plugins/gathered_user/models.py:74 -msgid "Time" -msgstr "時間" - -#: assets/models/backup.py:83 -msgid "Account backup snapshot" -msgstr "アカウントのバックアップスナップショット" - -#: assets/models/backup.py:90 audits/models.py:110 -#: terminal/models/session/sharing.py:108 -#: xpack/plugins/change_auth_plan/models/base.py:197 -#: xpack/plugins/change_auth_plan/serializers/asset.py:171 -#: xpack/plugins/cloud/models.py:175 -msgid "Reason" -msgstr "理由" - -#: assets/models/backup.py:92 -#: assets/serializers/automations/change_secret.py:99 -#: assets/serializers/automations/change_secret.py:124 -#: terminal/serializers/session.py:36 -#: xpack/plugins/change_auth_plan/models/base.py:198 -#: xpack/plugins/change_auth_plan/serializers/asset.py:173 -msgid "Is success" -msgstr "成功は" - -#: assets/models/backup.py:99 -msgid "Account backup execution" -msgstr "アカウントバックアップの実行" - -#: assets/models/base.py:26 -msgid "Connectivity" -msgstr "接続性" - -#: assets/models/base.py:28 authentication/models/temp_token.py:12 -msgid "Date verified" -msgstr "確認済みの日付" - -#: assets/models/base.py:60 -msgid "Privileged" -msgstr "" - -#: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:56 -#: users/models/group.py:31 users/models/user.py:671 -msgid "User group" -msgstr "ユーザーグループ" - -#: assets/models/cmd_filter.py:48 -msgid "Command filter" -msgstr "コマンドフィルター" - -#: assets/models/cmd_filter.py:62 -msgid "Deny" -msgstr "拒否" - -#: assets/models/cmd_filter.py:63 -msgid "Allow" -msgstr "許可" - -#: assets/models/cmd_filter.py:64 -msgid "Reconfirm" -msgstr "再確認" - -#: assets/models/cmd_filter.py:68 -msgid "Filter" -msgstr "フィルター" - -#: assets/models/cmd_filter.py:91 -msgid "Command filter rule" -msgstr "コマンドフィルタルール" - -#: assets/models/domain.py:153 -#, fuzzy, python-brace-format -#| msgid "Unable to connect to port {port} on {ip}" -msgid "Unable to connect to port {port} on {address}" -msgstr "{ip} でポート {port} に接続できません" - -#: assets/models/domain.py:156 authentication/middleware.py:76 -#: xpack/plugins/cloud/providers/fc.py:48 -msgid "Authentication failed" -msgstr "認証に失敗しました" - -#: assets/models/domain.py:158 assets/models/domain.py:185 -msgid "Connect failed" -msgstr "接続に失敗しました" - -#: assets/models/gathered_user.py:16 -msgid "Present" -msgstr "プレゼント" - -#: assets/models/gathered_user.py:17 -msgid "Date last login" -msgstr "最終ログイン日" - -#: assets/models/gathered_user.py:18 -msgid "IP last login" -msgstr "IP最終ログイン" - -#: assets/models/gathered_user.py:31 -msgid "GatherUser" -msgstr "収集ユーザー" - -#: assets/models/group.py:30 -msgid "Asset group" -msgstr "資産グループ" - -#: assets/models/group.py:34 assets/models/platform.py:19 -#: xpack/plugins/cloud/providers/nutanix.py:30 -msgid "Default" -msgstr "デフォルト" - -#: assets/models/group.py:34 -msgid "Default asset group" -msgstr "デフォルトアセットグループ" - -#: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:912 -msgid "System" -msgstr "システム" - -#: assets/models/label.py:18 assets/models/node.py:553 -#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 -#: authentication/models/connection_token.py:22 -#: common/drf/serializers/common.py:82 settings/models.py:34 -msgid "Value" -msgstr "値" - -#: assets/models/label.py:36 assets/serializers/cagegory.py:6 -#: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 -#: settings/serializers/sms.py:7 -msgid "Label" -msgstr "ラベル" - -#: assets/models/node.py:158 -msgid "New node" -msgstr "新しいノード" - -#: assets/models/node.py:481 -msgid "empty" -msgstr "空" - -#: assets/models/node.py:552 perms/models/perm_node.py:21 -msgid "Key" -msgstr "キー" - -#: assets/models/node.py:554 assets/serializers/node.py:20 -msgid "Full value" -msgstr "フルバリュー" - -#: assets/models/node.py:558 perms/models/perm_node.py:22 -msgid "Parent key" -msgstr "親キー" - -#: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 -#: xpack/plugins/cloud/serializers/task.py:68 -msgid "Node" -msgstr "ノード" - -#: assets/models/node.py:570 -msgid "Can match node" -msgstr "ノードを一致させることができます" - -#: assets/models/platform.py:20 -#, fuzzy -#| msgid "MFA required" -msgid "Required" -msgstr "MFAが必要" - -#: assets/models/platform.py:23 users/templates/users/reset_password.html:29 -msgid "Setting" -msgstr "設定" - -#: assets/models/platform.py:42 audits/const.py:68 settings/models.py:37 -#: terminal/serializers/applet_host.py:26 -msgid "Enabled" -msgstr "有効化" - -#: assets/models/platform.py:43 -msgid "Ansible config" -msgstr "" - -#: assets/models/platform.py:44 -#, fuzzy -#| msgid "MFA enabled" -msgid "Ping enabled" -msgstr "MFA有効化" - -#: assets/models/platform.py:45 -msgid "Ping method" -msgstr "" - -#: assets/models/platform.py:46 assets/models/platform.py:56 -#, fuzzy -#| msgid "Gather assets users" -msgid "Gather facts enabled" -msgstr "資産ユーザーの収集" - -#: assets/models/platform.py:47 assets/models/platform.py:58 -#, fuzzy -#| msgid "Gather assets users" -msgid "Gather facts method" -msgstr "資産ユーザーの収集" - -#: assets/models/platform.py:48 -#, fuzzy -#| msgid "Create account successfully" -msgid "Push account enabled" -msgstr "アカウントを正常に作成" - -#: assets/models/platform.py:49 -#, fuzzy -#| msgid "Create account successfully" -msgid "Push account method" -msgstr "アカウントを正常に作成" - -#: assets/models/platform.py:50 -#, fuzzy -#| msgid "Change Password" -msgid "Change password enabled" -msgstr "パスワードの変更" - -#: assets/models/platform.py:52 -#, fuzzy -#| msgid "Change Password" -msgid "Change password method" -msgstr "パスワードの変更" - -#: assets/models/platform.py:53 -#, fuzzy -#| msgid "Service account key" -msgid "Verify account enabled" -msgstr "サービスアカウントキー" - -#: assets/models/platform.py:55 -#, fuzzy -#| msgid "Verify auth" -msgid "Verify account method" -msgstr "パスワード/キーの確認" - -#: assets/models/platform.py:75 tickets/models/ticket/general.py:299 -msgid "Meta" -msgstr "メタ" - -#: assets/models/platform.py:76 -msgid "Internal" -msgstr "内部" - -#: assets/models/platform.py:80 assets/serializers/platform.py:96 -msgid "Charset" -msgstr "シャーセット" - -#: assets/models/platform.py:82 -#, fuzzy -#| msgid "Domain name" -msgid "Domain enabled" -msgstr "ドメイン名" - -#: assets/models/platform.py:83 -#, fuzzy -#| msgid "Protocols" -msgid "Protocols enabled" -msgstr "プロトコル" - -#: assets/models/platform.py:85 -#, fuzzy -#| msgid "MFA enabled" -msgid "Su enabled" -msgstr "MFA有効化" - -#: assets/models/platform.py:86 -msgid "SU method" -msgstr "" - -#: assets/models/platform.py:88 assets/serializers/platform.py:103 -#, fuzzy -#| msgid "Automatic managed" -msgid "Automation" -msgstr "自動管理" - -#: assets/models/utils.py:19 -#, python-format -msgid "%(value)s is not an even number" -msgstr "%(value)s は偶数ではありません" - -#: assets/notifications.py:8 -msgid "Notification of account backup route task results" -msgstr "アカウントバックアップルートタスクの結果の通知" - -#: assets/notifications.py:18 -msgid "" -"{} - The account backup passage task has been completed. See the attachment " -"for details" -msgstr "" -"{} -アカウントバックアップの通過タスクが完了しました。詳細は添付ファイルをご" -"覧ください" - -#: assets/notifications.py:20 -msgid "" -"{} - The account backup passage task has been completed: the encryption " -"password has not been set - please go to personal information -> file " -"encryption password to set the encryption password" -msgstr "" -"{} -アカウントのバックアップ通過タスクが完了しました: 暗号化パスワードが設定" -"されていません-個人情報にアクセスしてください-> ファイル暗号化パスワードを設" -"定してください暗号化パスワード" - -#: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 -msgid "Notification of implementation result of encryption change plan" -msgstr "暗号化変更プランの実装結果の通知" - -#: assets/notifications.py:41 -#: xpack/plugins/change_auth_plan/notifications.py:18 -msgid "" -"{} - The encryption change task has been completed. See the attachment for " -"details" -msgstr "{} -暗号化変更タスクが完了しました。詳細は添付ファイルをご覧ください" - -#: assets/notifications.py:42 -#: xpack/plugins/change_auth_plan/notifications.py:19 -msgid "" -"{} - The encryption change task has been completed: the encryption password " -"has not been set - please go to personal information -> file encryption " -"password to set the encryption password" -msgstr "" -"{} -暗号化変更タスクが完了しました: 暗号化パスワードが設定されていません-個人" -"情報にアクセスしてください-> ファイル暗号化パスワードを設定してください" - -#: assets/serializers/account/account.py:18 -msgid "Push now" -msgstr "" - -#: assets/serializers/account/account.py:20 -#, fuzzy -#| msgid "Secret" -msgid "Has secret" -msgstr "ひみつ" - -#: assets/serializers/account/account.py:27 -msgid "Account template not found" -msgstr "" - -#: assets/serializers/account/backup.py:29 -#: assets/serializers/automations/base.py:34 ops/mixin.py:102 -#: settings/serializers/auth/ldap.py:65 -#: xpack/plugins/change_auth_plan/serializers/base.py:43 -msgid "Periodic perform" -msgstr "定期的なパフォーマンス" - -#: assets/serializers/account/backup.py:31 -#: assets/serializers/automations/change_secret.py:41 -#: xpack/plugins/change_auth_plan/serializers/base.py:46 -msgid "Currently only mail sending is supported" -msgstr "現在、メール送信のみがサポートされています" - -#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 -#: authentication/serializers/connection_token.py:89 -#: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 -msgid "Protocols" -msgstr "プロトコル" - -#: assets/serializers/asset/common.py:87 -msgid "Address" -msgstr "アドレス" - -#: assets/serializers/asset/common.py:138 -#, fuzzy -#| msgid "Application not exists" -msgid "Platform not exist" -msgstr "アプリが存在しません" - -#: assets/serializers/asset/common.py:154 -#, fuzzy -#| msgid "Protocol duplicate: {}" -msgid "Protocol is required: {}" -msgstr "プロトコル重複: {}" - -#: assets/serializers/asset/host.py:12 -msgid "Vendor" -msgstr "ベンダー" - -#: assets/serializers/asset/host.py:13 -msgid "Model" -msgstr "モデル" - -#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 -msgid "Serial number" -msgstr "シリアル番号" - -#: assets/serializers/asset/host.py:16 -msgid "CPU model" -msgstr "CPU モデル" - -#: assets/serializers/asset/host.py:17 -msgid "CPU count" -msgstr "CPU カウント" - -#: assets/serializers/asset/host.py:18 -msgid "CPU cores" -msgstr "CPU カラー" - -#: assets/serializers/asset/host.py:19 -msgid "CPU vcpus" -msgstr "CPU 合計" - -#: assets/serializers/asset/host.py:20 -msgid "Memory" -msgstr "メモリ" - -#: assets/serializers/asset/host.py:21 -msgid "Disk total" -msgstr "ディスクの合計" - -#: assets/serializers/asset/host.py:22 -msgid "Disk info" -msgstr "ディスク情報" - -#: assets/serializers/asset/host.py:24 -msgid "OS" -msgstr "OS" - -#: assets/serializers/asset/host.py:25 -msgid "OS version" -msgstr "システムバージョン" - -#: assets/serializers/asset/host.py:26 -msgid "OS arch" -msgstr "システムアーキテクチャ" - -#: assets/serializers/asset/host.py:27 -msgid "Hostname raw" -msgstr "ホスト名生" - -#: assets/serializers/asset/host.py:28 -msgid "Asset number" -msgstr "資産番号" - -#: assets/serializers/automations/change_secret.py:28 -#: xpack/plugins/change_auth_plan/models/asset.py:50 -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 -msgid "SSH Key strategy" -msgstr "SSHキー戦略" - -#: assets/serializers/automations/change_secret.py:70 -#: xpack/plugins/change_auth_plan/serializers/base.py:58 -msgid "* Please enter the correct password length" -msgstr "* 正しいパスワードの長さを入力してください" - -#: assets/serializers/automations/change_secret.py:73 -#: xpack/plugins/change_auth_plan/serializers/base.py:61 -msgid "* Password length range 6-30 bits" -msgstr "* パスワードの長さの範囲6-30ビット" - -#: assets/serializers/automations/change_secret.py:117 -#: assets/serializers/automations/change_secret.py:145 audits/const.py:73 -#: audits/models.py:39 common/const/choices.py:18 ops/serializers/celery.py:39 -#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 -#: xpack/plugins/change_auth_plan/serializers/asset.py:189 -msgid "Success" -msgstr "成功" - -#: assets/serializers/automations/gather_accounts.py:23 -#, fuzzy -#| msgid "Executed times" -msgid "Executed amount" -msgstr "実行時間" - -#: assets/serializers/base.py:21 -msgid "Key password" -msgstr "キーパスワード" - -#: assets/serializers/cagegory.py:9 -msgid "Constraints" -msgstr "" - -#: assets/serializers/cagegory.py:15 -#, fuzzy -#| msgid "Type" -msgid "Types" -msgstr "タイプ" - -#: assets/serializers/domain.py:14 assets/serializers/label.py:12 -msgid "Assets amount" -msgstr "資産額" - -#: assets/serializers/domain.py:15 -msgid "Gateways count" -msgstr "ゲートウェイ数" - -#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 -msgid "Hostname" -msgstr "ホスト名" - -#: assets/serializers/label.py:13 -msgid "Category display" -msgstr "カテゴリ表示" - -#: assets/serializers/node.py:17 -msgid "value" -msgstr "値" - -#: assets/serializers/node.py:31 -msgid "Can't contains: /" -msgstr "含まれない:/" - -#: assets/serializers/node.py:41 -msgid "The same level node name cannot be the same" -msgstr "同じレベルのノード名を同じにすることはできません。" - -#: assets/serializers/platform.py:24 -#, fuzzy -#| msgid "MFA enabled" -msgid "SFTP enabled" -msgstr "MFA有効化" - -#: assets/serializers/platform.py:25 -#, fuzzy -#| msgid "SFTP Root" -msgid "SFTP home" -msgstr "SFTPルート" - -#: assets/serializers/platform.py:28 -#, fuzzy -#| msgid "Auto" -msgid "Auto fill" -msgstr "自動" - -#: assets/serializers/platform.py:78 -msgid "Primary" -msgstr "" - -#: assets/serializers/utils.py:13 -msgid "Password can not contains `{{` " -msgstr "パスワードには '{{' を含まない" - -#: assets/serializers/utils.py:16 -msgid "Password can not contains `'` " -msgstr "パスワードには `'` を含まない" - -#: assets/serializers/utils.py:18 -msgid "Password can not contains `\"` " -msgstr "パスワードには `\"` を含まない" - -#: assets/serializers/utils.py:24 -msgid "private key invalid or passphrase error" -msgstr "秘密鍵が無効またはpassphraseエラー" - -#: assets/tasks/automation.py:11 -#, fuzzy -#| msgid "Verify auth" -msgid "Execute automation" -msgstr "パスワード/キーの確認" - -#: assets/tasks/backup.py:13 -#, fuzzy -#| msgid "Account backup plan" -msgid "Execute account backup plan" -msgstr "アカウントバックアップ計画" - -#: assets/tasks/gather_accounts.py:31 -#, fuzzy -#| msgid "Gather assets users" -msgid "Gather assets accounts" -msgstr "資産ユーザーの収集" - -#: assets/tasks/gather_facts.py:26 -msgid "Update some assets hardware info. " -msgstr "一部の資産ハードウェア情報を更新します。" - -#: assets/tasks/gather_facts.py:44 -#, fuzzy -#| msgid "Update node asset hardware information: " -msgid "Manually update the hardware information of assets" -msgstr "ノード資産のハードウェア情報を更新します。" - -#: assets/tasks/gather_facts.py:49 -msgid "Update assets hardware info: " -msgstr "資産のハードウェア情報を更新する:" - -#: assets/tasks/gather_facts.py:53 -msgid "Manually update the hardware information of assets under a node" -msgstr "" - -#: assets/tasks/gather_facts.py:59 -msgid "Update node asset hardware information: " -msgstr "ノード資産のハードウェア情報を更新します。" - -#: assets/tasks/nodes_amount.py:16 -msgid "Check the amount of assets under the node" -msgstr "" - -#: assets/tasks/nodes_amount.py:28 -msgid "" -"The task of self-checking is already running and cannot be started repeatedly" -msgstr "" -"セルフチェックのタスクはすでに実行されており、繰り返し開始することはできませ" -"ん" - -#: assets/tasks/nodes_amount.py:34 -msgid "Periodic check the amount of assets under the node" -msgstr "" - -#: assets/tasks/ping.py:21 assets/tasks/ping.py:39 -#, fuzzy -#| msgid "Test assets connectivity. " -msgid "Test assets connectivity " -msgstr "資産の接続性をテストします。" - -#: assets/tasks/ping.py:33 -#, fuzzy -#| msgid "Can test asset connectivity" -msgid "Manually test the connectivity of a asset" -msgstr "資産接続をテストできます" - -#: assets/tasks/ping.py:43 -msgid "Manually test the connectivity of assets under a node" -msgstr "" - -#: assets/tasks/ping.py:49 -#, fuzzy -#| msgid "Test if the assets under the node are connectable: " -msgid "Test if the assets under the node are connectable " -msgstr "ノードの下のアセットが接続可能かどうかをテストします。" - -#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 -#, fuzzy -#| msgid "Create account successfully" -msgid "Push accounts to assets" -msgstr "アカウントを正常に作成" - -#: assets/tasks/utils.py:17 -msgid "Asset has been disabled, skipped: {}" -msgstr "資産が無効化されました。スキップ: {}" - -#: assets/tasks/utils.py:21 -msgid "Asset may not be support ansible, skipped: {}" -msgstr "資産はサポートできない場合があります。スキップ: {}" - -#: assets/tasks/utils.py:39 -msgid "For security, do not push user {}" -msgstr "セキュリティのために、ユーザー {} をプッシュしないでください" - -#: assets/tasks/utils.py:55 -msgid "No assets matched, stop task" -msgstr "一致する資産がない、タスクを停止" - -#: assets/tasks/verify_account.py:30 -#, fuzzy -#| msgid "Verify auth" -msgid "Verify asset account availability" -msgstr "パスワード/キーの確認" - -#: assets/tasks/verify_account.py:37 -#, fuzzy -#| msgid "Test account connectivity: " -msgid "Verify accounts connectivity" -msgstr "テストアカウント接続:" - -#: audits/apps.py:9 -msgid "Audits" -msgstr "監査" - -#: audits/const.py:44 -msgid "Mkdir" -msgstr "Mkdir" - -#: audits/const.py:45 -msgid "Rmdir" -msgstr "Rmdir" - -#: audits/const.py:46 audits/const.py:56 -#: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:226 -msgid "Delete" -msgstr "削除" - -#: audits/const.py:47 perms/const.py:13 -msgid "Upload" -msgstr "アップロード" - -#: audits/const.py:48 -msgid "Rename" -msgstr "名前の変更" - -#: audits/const.py:49 -msgid "Symlink" -msgstr "Symlink" - -#: audits/const.py:50 perms/const.py:14 -msgid "Download" -msgstr "ダウンロード" - -#: audits/const.py:54 rbac/tree.py:224 -msgid "View" -msgstr "表示" - -#: audits/const.py:55 rbac/tree.py:225 templates/_csv_import_export.html:18 -#: templates/_csv_update_modal.html:6 -msgid "Update" -msgstr "更新" - -#: audits/const.py:57 -#: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:223 -msgid "Create" -msgstr "作成" - -#: audits/const.py:62 terminal/models/applet/host.py:24 -#: terminal/models/component/terminal.py:157 -msgid "Terminal" -msgstr "ターミナル" - -#: audits/const.py:69 -msgid "-" -msgstr "-" - -#: audits/models.py:31 audits/models.py:55 audits/models.py:82 -#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 -msgid "Remote addr" -msgstr "リモートaddr" - -#: audits/models.py:36 audits/serializers.py:19 -msgid "Operate" -msgstr "操作" - -#: audits/models.py:38 -msgid "Filename" -msgstr "ファイル名" - -#: audits/models.py:43 -msgid "File transfer log" -msgstr "ファイル転送ログ" - -#: audits/models.py:52 audits/serializers.py:85 -msgid "Resource Type" -msgstr "リソースタイプ" - -#: audits/models.py:53 -msgid "Resource" -msgstr "リソース" - -#: audits/models.py:58 audits/models.py:84 -#: terminal/backends/command/serializers.py:40 -msgid "Datetime" -msgstr "時間" - -#: audits/models.py:74 -msgid "Operate log" -msgstr "ログの操作" - -#: audits/models.py:80 -msgid "Change by" -msgstr "による変更" - -#: audits/models.py:90 -msgid "Password change log" -msgstr "パスワード変更ログ" - -#: audits/models.py:97 -msgid "Login type" -msgstr "ログインタイプ" - -#: audits/models.py:99 tickets/models/ticket/login_confirm.py:10 -msgid "Login ip" -msgstr "ログインIP" - -#: audits/models.py:101 -#: authentication/templates/authentication/_msg_different_city.html:11 -#: tickets/models/ticket/login_confirm.py:11 -msgid "Login city" -msgstr "ログイン都市" - -#: audits/models.py:104 audits/serializers.py:62 -msgid "User agent" -msgstr "ユーザーエージェント" - -#: audits/models.py:107 audits/serializers.py:39 -#: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:65 users/models/user.py:688 -#: users/serializers/profile.py:126 -msgid "MFA" -msgstr "MFA" - -#: audits/models.py:117 -msgid "Date login" -msgstr "日付ログイン" - -#: audits/models.py:119 audits/serializers.py:64 -msgid "Authentication backend" -msgstr "認証バックエンド" - -#: audits/models.py:160 -msgid "User login log" -msgstr "ユーザーログインログ" - -#: audits/serializers.py:63 -msgid "Reason display" -msgstr "理由表示" - -#: audits/signal_handlers.py:45 -msgid "SSH Key" -msgstr "SSHキー" - -#: audits/signal_handlers.py:47 -msgid "SSO" -msgstr "SSO" - -#: audits/signal_handlers.py:48 -msgid "Auth Token" -msgstr "認証トークン" - -#: audits/signal_handlers.py:49 authentication/notifications.py:73 -#: authentication/views/login.py:73 authentication/views/wecom.py:178 -#: notifications/backends/__init__.py:11 users/models/user.py:724 -msgid "WeCom" -msgstr "企業微信" - -#: audits/signal_handlers.py:50 authentication/views/feishu.py:145 -#: authentication/views/login.py:85 notifications/backends/__init__.py:14 -#: users/models/user.py:726 -msgid "FeiShu" -msgstr "本を飛ばす" - -#: audits/signal_handlers.py:51 authentication/views/dingtalk.py:180 -#: authentication/views/login.py:79 notifications/backends/__init__.py:12 -#: users/models/user.py:725 -msgid "DingTalk" -msgstr "DingTalk" - -#: audits/signal_handlers.py:52 authentication/models/temp_token.py:16 -msgid "Temporary token" -msgstr "仮パスワード" - -#: audits/signal_handlers.py:64 -msgid "User and Group" -msgstr "ユーザーとグループ" - -#: audits/signal_handlers.py:65 -#, python-brace-format -msgid "{User} JOINED {UserGroup}" -msgstr "{User} に参加 {UserGroup}" - -#: audits/signal_handlers.py:66 -#, python-brace-format -msgid "{User} LEFT {UserGroup}" -msgstr "{User} のそばを通る {UserGroup}" - -#: audits/signal_handlers.py:69 -msgid "Node and Asset" -msgstr "ノードと資産" - -#: audits/signal_handlers.py:70 -#, python-brace-format -msgid "{Node} ADD {Asset}" -msgstr "{Node} 追加 {Asset}" - -#: audits/signal_handlers.py:71 -#, python-brace-format -msgid "{Node} REMOVE {Asset}" -msgstr "{Node} 削除 {Asset}" - -#: audits/signal_handlers.py:74 -msgid "User asset permissions" -msgstr "ユーザー資産の権限" - -#: audits/signal_handlers.py:75 -#, python-brace-format -msgid "{AssetPermission} ADD {User}" -msgstr "{AssetPermission} 追加 {User}" - -#: audits/signal_handlers.py:76 -#, python-brace-format -msgid "{AssetPermission} REMOVE {User}" -msgstr "{AssetPermission} 削除 {User}" - -#: audits/signal_handlers.py:79 -msgid "User group asset permissions" -msgstr "ユーザーグループの資産権限" - -#: audits/signal_handlers.py:80 -#, python-brace-format -msgid "{AssetPermission} ADD {UserGroup}" -msgstr "{AssetPermission} 追加 {UserGroup}" - -#: audits/signal_handlers.py:81 -#, python-brace-format -msgid "{AssetPermission} REMOVE {UserGroup}" -msgstr "{AssetPermission} 削除 {UserGroup}" - -#: audits/signal_handlers.py:84 perms/models/asset_permission.py:81 -msgid "Asset permission" -msgstr "資産権限" - -#: audits/signal_handlers.py:85 -#, python-brace-format -msgid "{AssetPermission} ADD {Asset}" -msgstr "{AssetPermission} 追加 {Asset}" - -#: audits/signal_handlers.py:86 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Asset}" -msgstr "{AssetPermission} 削除 {Asset}" - -#: audits/signal_handlers.py:89 -msgid "Node permission" -msgstr "ノード権限" - -#: audits/signal_handlers.py:90 -#, python-brace-format -msgid "{AssetPermission} ADD {Node}" -msgstr "{AssetPermission} 追加 {Node}" - -#: audits/signal_handlers.py:91 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Node}" -msgstr "{AssetPermission} 削除 {Node}" - -#: authentication/api/confirm.py:40 -msgid "This action require verify your MFA" -msgstr "この操作には、MFAを検証する必要があります" - -#: authentication/api/mfa.py:59 -msgid "Current user not support mfa type: {}" -msgstr "現在のユーザーはmfaタイプをサポートしていません: {}" - -#: authentication/apps.py:7 -msgid "Authentication" -msgstr "認証" - -#: authentication/backends/drf.py:56 -msgid "Invalid signature header. No credentials provided." -msgstr "署名ヘッダーが無効です。資格情報は提供されていません。" - -#: authentication/backends/drf.py:59 -msgid "Invalid signature header. Signature string should not contain spaces." -msgstr "署名ヘッダーが無効です。署名文字列にはスペースを含まないでください。" - -#: authentication/backends/drf.py:66 -msgid "Invalid signature header. Format like AccessKeyId:Signature" -msgstr "署名ヘッダーが無効です。AccessKeyIdのような形式: Signature" - -#: authentication/backends/drf.py:70 -msgid "" -"Invalid signature header. Signature string should not contain invalid " -"characters." -msgstr "" -"署名ヘッダーが無効です。署名文字列に無効な文字を含めることはできません。" - -#: authentication/backends/drf.py:90 authentication/backends/drf.py:106 -msgid "Invalid signature." -msgstr "署名が無効です。" - -#: authentication/backends/drf.py:97 -msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" -msgstr "HTTP header: Date not provide or not" - -#: authentication/backends/drf.py:102 -msgid "Expired, more than 15 minutes" -msgstr "期限切れ、15分以上" - -#: authentication/backends/drf.py:109 -msgid "User disabled." -msgstr "ユーザーが無効になりました。" - -#: authentication/backends/drf.py:127 -msgid "Invalid token header. No credentials provided." -msgstr "無効なトークンヘッダー。資格情報は提供されていません。" - -#: authentication/backends/drf.py:130 -msgid "Invalid token header. Sign string should not contain spaces." -msgstr "無効なトークンヘッダー。記号文字列にはスペースを含めないでください。" - -#: authentication/backends/drf.py:137 -msgid "" -"Invalid token header. Sign string should not contain invalid characters." -msgstr "" -"無効なトークンヘッダー。署名文字列に無効な文字を含めることはできません。" - -#: authentication/backends/drf.py:148 -msgid "Invalid token or cache refreshed." -msgstr "無効なトークンまたはキャッシュの更新。" - -#: authentication/backends/oauth2/backends.py:155 -msgid "User invalid, disabled or expired" -msgstr "ユーザーが無効、無効、または期限切れです" - -#: authentication/confirm/password.py:16 -msgid "Authentication failed password incorrect" -msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません)" - -#: authentication/confirm/relogin.py:10 -msgid "Login time has exceeded {} minutes, please login again" -msgstr "ログイン時間が {} 分を超えました。もう一度ログインしてください" - -#: authentication/errors/const.py:18 -msgid "Username/password check failed" -msgstr "ユーザー名/パスワードのチェックに失敗しました" - -#: authentication/errors/const.py:19 -msgid "Password decrypt failed" -msgstr "パスワードの復号化に失敗しました" - -#: authentication/errors/const.py:20 -msgid "MFA failed" -msgstr "MFAに失敗しました" - -#: authentication/errors/const.py:21 -msgid "MFA unset" -msgstr "MFAセットなし" - -#: authentication/errors/const.py:22 -msgid "Username does not exist" -msgstr "ユーザー名が存在しません" - -#: authentication/errors/const.py:23 -msgid "Password expired" -msgstr "パスワード期限切れ" - -#: authentication/errors/const.py:24 -msgid "Disabled or expired" -msgstr "無効または期限切れ" - -#: authentication/errors/const.py:25 -msgid "This account is inactive." -msgstr "このアカウントは非アクティブです。" - -#: authentication/errors/const.py:26 -msgid "This account is expired" -msgstr "このアカウントは期限切れです" - -#: authentication/errors/const.py:27 -msgid "Auth backend not match" -msgstr "Authバックエンドが一致しない" - -#: authentication/errors/const.py:28 -msgid "ACL is not allowed" -msgstr "ログイン アクセス制御は許可されません" - -#: authentication/errors/const.py:29 -msgid "Only local users are allowed" -msgstr "ローカルユーザーのみが許可されています" - -#: authentication/errors/const.py:39 -msgid "No session found, check your cookie" -msgstr "セッションが見つかりませんでした。クッキーを確認してください" - -#: authentication/errors/const.py:41 -#, python-brace-format -msgid "" -"The username or password you entered is incorrect, please enter it again. " -"You can also try {times_try} times (The account will be temporarily locked " -"for {block_time} minutes)" -msgstr "" -"入力したユーザー名またはパスワードが正しくありません。再度入力してください。 " -"{times_try} 回試すこともできます (アカウントは {block_time} 分の間一時的に" -"ロックされます)" - -#: authentication/errors/const.py:47 authentication/errors/const.py:55 -msgid "" -"The account has been locked (please contact admin to unlock it or try again " -"after {} minutes)" -msgstr "" -"アカウントがロックされています (管理者に連絡してロックを解除するか、 {} 分後" -"にもう一度お試しください)" - -#: authentication/errors/const.py:51 -#, fuzzy -#| msgid "" -#| "The ip has been locked (please contact admin to unlock it or try again " -#| "after {} minutes)" -msgid "" -"The address has been locked (please contact admin to unlock it or try again " -"after {} minutes)" -msgstr "" -"IPがロックされています (管理者に連絡してロックを解除するか、 {} 分後にもう一" -"度お試しください)" - -#: authentication/errors/const.py:59 -#, python-brace-format -msgid "" -"{error}, You can also try {times_try} times (The account will be temporarily " -"locked for {block_time} minutes)" -msgstr "" -"{error},{times_try} 回も試すことができます (アカウントは {block_time} 分の間" -"一時的にロックされます)" - -#: authentication/errors/const.py:63 -msgid "MFA required" -msgstr "MFAが必要" - -#: authentication/errors/const.py:64 -msgid "MFA not set, please set it first" -msgstr "MFAをセットしない、最初にセットしてください" - -#: authentication/errors/const.py:65 -msgid "Login confirm required" -msgstr "ログイン確認が必要" - -#: authentication/errors/const.py:66 -msgid "Wait login confirm ticket for accept" -msgstr "受け入れのためのログイン確認チケットを待つ" - -#: authentication/errors/const.py:67 -msgid "Login confirm ticket was {}" -msgstr "ログイン確認チケットは {} でした" - -#: authentication/errors/failed.py:146 -msgid "Current IP and Time period is not allowed" -msgstr "現在の IP と期間はログインを許可されていません" - -#: authentication/errors/failed.py:151 -msgid "Please enter MFA code" -msgstr "MFAコードを入力してください" - -#: authentication/errors/failed.py:156 -msgid "Please enter SMS code" -msgstr "SMSコードを入力してください" - -#: authentication/errors/failed.py:161 users/exceptions.py:15 -msgid "Phone not set" -msgstr "電話が設定されていない" - -#: authentication/errors/mfa.py:8 -msgid "SSO auth closed" -msgstr "SSO authは閉鎖されました" - -#: authentication/errors/mfa.py:18 authentication/views/wecom.py:80 -msgid "WeCom is already bound" -msgstr "企業の微信はすでにバインドされています" - -#: authentication/errors/mfa.py:23 authentication/views/wecom.py:237 -#: authentication/views/wecom.py:291 -msgid "WeCom is not bound" -msgstr "企業の微信をバインドしていません" - -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 -#: authentication/views/dingtalk.py:297 -msgid "DingTalk is not bound" -msgstr "DingTalkはバインドされていません" - -#: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 -msgid "FeiShu is not bound" -msgstr "本を飛ばすは拘束されていません" - -#: authentication/errors/mfa.py:38 -msgid "Your password is invalid" -msgstr "パスワードが無効です" - -#: authentication/errors/redirect.py:85 authentication/mixins.py:306 -msgid "Your password is too simple, please change it for security" -msgstr "パスワードがシンプルすぎるので、セキュリティのために変更してください" - -#: authentication/errors/redirect.py:93 authentication/mixins.py:313 -msgid "You should to change your password before login" -msgstr "ログインする前にパスワードを変更する必要があります" - -#: authentication/errors/redirect.py:101 authentication/mixins.py:320 -msgid "Your password has expired, please reset before logging in" -msgstr "" -"パスワードの有効期限が切れました。ログインする前にリセットしてください。" - -#: authentication/forms.py:45 -msgid "{} days auto login" -msgstr "{} 日自動ログイン" - -#: authentication/forms.py:56 -msgid "MFA Code" -msgstr "MFAコード" - -#: authentication/forms.py:57 -msgid "MFA type" -msgstr "MFAタイプ" - -#: authentication/forms.py:70 users/forms/profile.py:28 -msgid "MFA code" -msgstr "MFAコード" - -#: authentication/forms.py:72 -msgid "Dynamic code" -msgstr "動的コード" - -#: authentication/mfa/base.py:7 -msgid "Please input security code" -msgstr "セキュリティコードを入力してください" - -#: authentication/mfa/otp.py:7 -msgid "OTP code invalid, or server time error" -msgstr "OTPコードが無効、またはサーバー時間エラー" - -#: authentication/mfa/otp.py:12 -msgid "OTP" -msgstr "OTP" - -#: authentication/mfa/otp.py:13 -msgid "OTP verification code" -msgstr "OTP検証コード" - -#: authentication/mfa/otp.py:48 -msgid "Virtual OTP based MFA" -msgstr "仮想OTPベースのMFA" - -#: authentication/mfa/radius.py:7 -msgid "Radius verify code invalid" -msgstr "Radius verifyコードが無効" - -#: authentication/mfa/radius.py:13 -msgid "Radius verification code" -msgstr "半径確認コード" - -#: authentication/mfa/radius.py:44 -msgid "Radius global enabled, cannot disable" -msgstr "Radius globalが有効になり、無効にできません" - -#: authentication/mfa/sms.py:7 -msgid "SMS verify code invalid" -msgstr "SMS検証コードが無効" - -#: authentication/mfa/sms.py:12 -msgid "SMS" -msgstr "SMS" - -#: authentication/mfa/sms.py:13 -msgid "SMS verification code" -msgstr "SMS確認コード" - -#: authentication/mfa/sms.py:57 -msgid "Set phone number to enable" -msgstr "電話番号を設定して有効にする" - -#: authentication/mfa/sms.py:61 -msgid "Clear phone number to disable" -msgstr "無効にする電話番号をクリアする" - -#: authentication/middleware.py:77 settings/utils/ldap.py:652 -msgid "Authentication failed (before login check failed): {}" -msgstr "認証に失敗しました (ログインチェックが失敗する前): {}" - -#: authentication/mixins.py:256 -msgid "The MFA type ({}) is not enabled" -msgstr "MFAタイプ ({}) が有効になっていない" - -#: authentication/mixins.py:296 -msgid "Please change your password" -msgstr "パスワードを変更してください" - -#: authentication/models/connection_token.py:31 -#: terminal/serializers/storage.py:111 -msgid "Account name" -msgstr "アカウント名" - -#: authentication/models/connection_token.py:32 -#, fuzzy -#| msgid "Custom Username" -msgid "Input Username" -msgstr "カスタムユーザー名" - -#: authentication/models/connection_token.py:33 -#, fuzzy -#| msgid "Client Secret" -msgid "Input Secret" -msgstr "クライアント秘密" - -#: authentication/models/connection_token.py:37 perms/models/perm_token.py:17 -#, fuzzy -#| msgid "Connect timeout" -msgid "Connect method" -msgstr "接続タイムアウト" - -#: authentication/models/connection_token.py:38 -#: rbac/serializers/rolebinding.py:21 -msgid "User display" -msgstr "ユーザー表示" - -#: authentication/models/connection_token.py:39 -msgid "Asset display" -msgstr "アセット名" - -#: authentication/models/connection_token.py:41 -#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 -#: tickets/models/ticket/apply_application.py:31 -#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:707 -msgid "Date expired" -msgstr "期限切れの日付" - -#: authentication/models/connection_token.py:46 -msgid "Connection token" -msgstr "接続トークン" - -#: authentication/models/connection_token.py:48 -msgid "Can view connection token secret" -msgstr "接続トークンの秘密を表示できます" - -#: authentication/models/connection_token.py:95 -msgid "Connection token expired at: {}" -msgstr "接続トークンの有効期限: {}" - -#: authentication/models/connection_token.py:98 -msgid "No user or invalid user" -msgstr "" - -#: authentication/models/connection_token.py:102 -#, fuzzy -#| msgid "Asset inactive" -msgid "No asset or inactive asset" -msgstr "アセットがアクティブ化されていません" - -#: authentication/models/connection_token.py:105 -#, fuzzy -#| msgid "Login acl" -msgid "No account" -msgstr "ログインacl" - -#: authentication/models/connection_token.py:177 -msgid "Super connection token" -msgstr "スーパー接続トークン" - -#: authentication/models/private_token.py:9 -msgid "Private Token" -msgstr "プライベートトークン" - -#: authentication/models/sso_token.py:14 -msgid "Expired" -msgstr "期限切れ" - -#: authentication/models/sso_token.py:18 -msgid "SSO token" -msgstr "SSO token" - -#: authentication/models/temp_token.py:11 -msgid "Verified" -msgstr "確認済み" - -#: authentication/notifications.py:19 -msgid "Different city login reminder" -msgstr "異なる都市ログインのリマインダー" - -#: authentication/notifications.py:52 -msgid "binding reminder" -msgstr "バインディングリマインダー" - -#: authentication/serializers/connection_token.py:19 -msgid "Expired time" -msgstr "期限切れ時間" - -#: authentication/serializers/connection_token.py:157 -#, fuzzy -#| msgid "Expired" -msgid "Expired now" -msgstr "期限切れ" - -#: authentication/serializers/token.py:79 perms/serializers/permission.py:30 -#: perms/serializers/permission.py:61 users/serializers/user.py:203 -msgid "Is valid" -msgstr "有効です" - -#: authentication/templates/authentication/_access_key_modal.html:6 -msgid "API key list" -msgstr "APIキーリスト" - -#: authentication/templates/authentication/_access_key_modal.html:18 -msgid "Using api key sign api header, every requests header difference" -msgstr "APIキー記号APIヘッダーを使用すると、すべてのリクエストヘッダーの違い" - -#: authentication/templates/authentication/_access_key_modal.html:19 -msgid "docs" -msgstr "ドキュメント" - -#: authentication/templates/authentication/_access_key_modal.html:30 -#: users/serializers/group.py:35 -msgid "ID" -msgstr "ID" - -#: authentication/templates/authentication/_access_key_modal.html:33 -#: terminal/notifications.py:93 terminal/notifications.py:141 -msgid "Date" -msgstr "日付" - -#: authentication/templates/authentication/_access_key_modal.html:48 -msgid "Show" -msgstr "表示" - -#: authentication/templates/authentication/_access_key_modal.html:66 -#: settings/serializers/security.py:39 users/models/user.py:556 -#: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 -#: users/templates/users/user_verify_mfa.html:36 -msgid "Disable" -msgstr "無効化" - -#: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:557 users/serializers/profile.py:117 -#: users/templates/users/mfa_setting.html:26 -#: users/templates/users/mfa_setting.html:68 -msgid "Enable" -msgstr "有効化" - -#: authentication/templates/authentication/_access_key_modal.html:147 -msgid "Delete success" -msgstr "削除成功" - -#: authentication/templates/authentication/_access_key_modal.html:155 -#: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:44 -msgid "Close" -msgstr "閉じる" - -#: authentication/templates/authentication/_captcha_field.html:8 -msgid "Play CAPTCHA as audio file" -msgstr "CAPTCHAをオーディオファイルとして再生する" - -#: authentication/templates/authentication/_captcha_field.html:15 -#: users/forms/profile.py:103 -msgid "Captcha" -msgstr "キャプチャ" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:5 -msgid "MFA confirm" -msgstr "MFA確認" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:17 -msgid "Need MFA for view auth" -msgstr "ビューオートのためにMFAが必要" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:20 -#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:37 -#: templates/_modal.html:23 templates/flash_message_standalone.html:37 -#: users/templates/users/user_password_verify.html:20 -msgid "Confirm" -msgstr "確認" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:25 -msgid "Code error" -msgstr "コードエラー" - -#: authentication/templates/authentication/_msg_different_city.html:3 -#: authentication/templates/authentication/_msg_oauth_bind.html:3 -#: authentication/templates/authentication/_msg_reset_password.html:3 -#: authentication/templates/authentication/_msg_rest_password_success.html:2 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:389 -#: perms/templates/perms/_msg_item_permissions_expire.html:3 -#: perms/templates/perms/_msg_permed_items_expire.html:3 -#: tickets/templates/tickets/approve_check_password.html:33 -#: users/templates/users/_msg_account_expire_reminder.html:4 -#: users/templates/users/_msg_password_expire_reminder.html:4 -#: users/templates/users/_msg_reset_mfa.html:4 -#: users/templates/users/_msg_reset_ssh_key.html:4 -msgid "Hello" -msgstr "こんにちは" - -#: authentication/templates/authentication/_msg_different_city.html:6 -msgid "Your account has remote login behavior, please pay attention" -msgstr "アカウントにリモートログイン動作があります。注意してください" - -#: authentication/templates/authentication/_msg_different_city.html:10 -msgid "Login time" -msgstr "ログイン時間" - -#: authentication/templates/authentication/_msg_different_city.html:16 -msgid "" -"If you suspect that the login behavior is abnormal, please modify the " -"account password in time." -msgstr "" -"ログイン動作が異常であると疑われる場合は、時間内にアカウントのパスワードを変" -"更してください。" - -#: authentication/templates/authentication/_msg_oauth_bind.html:6 -msgid "Your account has just been bound to" -msgstr "アカウントはにバインドされています" - -#: authentication/templates/authentication/_msg_oauth_bind.html:17 -msgid "If the operation is not your own, unbind and change the password." -msgstr "操作が独自のものでない場合は、パスワードをバインド解除して変更します。" - -#: authentication/templates/authentication/_msg_reset_password.html:6 -msgid "" -"Please click the link below to reset your password, if not your request, " -"concern your account security" -msgstr "" -"下のリンクをクリックしてパスワードをリセットしてください。リクエストがない場" -"合は、アカウントのセキュリティに関係します。" - -#: authentication/templates/authentication/_msg_reset_password.html:10 -msgid "Click here reset password" -msgstr "ここをクリックしてパスワードをリセット" - -#: authentication/templates/authentication/_msg_reset_password.html:16 -#: users/templates/users/_msg_user_created.html:22 -msgid "This link is valid for 1 hour. After it expires" -msgstr "このリンクは1時間有効です。有効期限が切れた後" - -#: authentication/templates/authentication/_msg_reset_password.html:17 -#: users/templates/users/_msg_user_created.html:23 -msgid "request new one" -msgstr "新しいものを要求する" - -#: authentication/templates/authentication/_msg_rest_password_success.html:5 -msgid "Your password has just been successfully updated" -msgstr "パスワードが正常に更新されました" - -#: authentication/templates/authentication/_msg_rest_password_success.html:9 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:9 -msgid "Browser" -msgstr "ブラウザ" - -#: authentication/templates/authentication/_msg_rest_password_success.html:13 -msgid "" -"If the password update was not initiated by you, your account may have " -"security issues" -msgstr "" -"パスワードの更新が開始されなかった場合、アカウントにセキュリティ上の問題があ" -"る可能性があります" - -#: authentication/templates/authentication/_msg_rest_password_success.html:14 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:14 -msgid "If you have any questions, you can contact the administrator" -msgstr "質問があれば、管理者に連絡できます" - -#: authentication/templates/authentication/_msg_rest_public_key_success.html:5 -msgid "Your public key has just been successfully updated" -msgstr "公開鍵が正常に更新されました" - -#: authentication/templates/authentication/_msg_rest_public_key_success.html:13 -msgid "" -"If the public key update was not initiated by you, your account may have " -"security issues" -msgstr "" -"公開鍵の更新が開始されなかった場合、アカウントにセキュリティ上の問題がある可" -"能性があります" - -#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:17 -msgid "Cancel" -msgstr "キャンセル" - -#: authentication/templates/authentication/login.html:221 -msgid "Welcome back, please enter username and password to login" -msgstr "" -"おかえりなさい、ログインするためにユーザー名とパスワードを入力してください" - -#: authentication/templates/authentication/login.html:256 -#: users/templates/users/forgot_password.html:16 -#: users/templates/users/forgot_password.html:17 -msgid "Forgot password" -msgstr "パスワードを忘れた" - -#: authentication/templates/authentication/login.html:264 -#: templates/_header_bar.html:89 -msgid "Login" -msgstr "ログイン" - -#: authentication/templates/authentication/login.html:271 -msgid "More login options" -msgstr "その他のログインオプション" - -#: authentication/templates/authentication/login_mfa.html:6 -msgid "MFA Auth" -msgstr "MFA マルチファクタ認証" - -#: authentication/templates/authentication/login_mfa.html:19 -#: users/templates/users/user_otp_check_password.html:12 -#: users/templates/users/user_otp_enable_bind.html:24 -#: users/templates/users/user_otp_enable_install_app.html:29 -#: users/templates/users/user_verify_mfa.html:30 -msgid "Next" -msgstr "次へ" - -#: authentication/templates/authentication/login_mfa.html:22 -msgid "Can't provide security? Please contact the administrator!" -msgstr "セキュリティを提供できませんか? 管理者に連絡してください!" - -#: authentication/templates/authentication/login_wait_confirm.html:41 -msgid "Refresh" -msgstr "リフレッシュ" - -#: authentication/templates/authentication/login_wait_confirm.html:46 -msgid "Copy link" -msgstr "リンクのコピー" - -#: authentication/templates/authentication/login_wait_confirm.html:51 -msgid "Return" -msgstr "返品" - -#: authentication/templates/authentication/login_wait_confirm.html:116 -msgid "Copy success" -msgstr "コピー成功" - -#: authentication/utils.py:28 common/utils/ip/geoip/utils.py:24 -#: xpack/plugins/cloud/const.py:24 -msgid "LAN" -msgstr "ローカルエリアネットワーク" - -#: authentication/views/dingtalk.py:42 -msgid "DingTalk Error, Please contact your system administrator" -msgstr "DingTalkエラー、システム管理者に連絡してください" - -#: authentication/views/dingtalk.py:45 -msgid "DingTalk Error" -msgstr "DingTalkエラー" - -#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 -#: authentication/views/wecom.py:56 -msgid "" -"The system configuration is incorrect. Please contact your administrator" -msgstr "システム設定が正しくありません。管理者に連絡してください" - -#: authentication/views/dingtalk.py:81 -msgid "DingTalk is already bound" -msgstr "DingTalkはすでにバインドされています" - -#: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 -msgid "Invalid user_id" -msgstr "無効なuser_id" - -#: authentication/views/dingtalk.py:165 -msgid "DingTalk query user failed" -msgstr "DingTalkクエリユーザーが失敗しました" - -#: authentication/views/dingtalk.py:174 -msgid "The DingTalk is already bound to another user" -msgstr "DingTalkはすでに別のユーザーにバインドされています" - -#: authentication/views/dingtalk.py:181 -msgid "Binding DingTalk successfully" -msgstr "DingTalkのバインドに成功" - -#: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 -msgid "Failed to get user from DingTalk" -msgstr "DingTalkからユーザーを取得できませんでした" - -#: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 -msgid "Please login with a password and then bind the DingTalk" -msgstr "パスワードでログインし、DingTalkをバインドしてください" - -#: authentication/views/feishu.py:40 -msgid "FeiShu Error" -msgstr "FeiShuエラー" - -#: authentication/views/feishu.py:88 -msgid "FeiShu is already bound" -msgstr "FeiShuはすでにバインドされています" - -#: authentication/views/feishu.py:130 -msgid "FeiShu query user failed" -msgstr "FeiShuクエリユーザーが失敗しました" - -#: authentication/views/feishu.py:139 -msgid "The FeiShu is already bound to another user" -msgstr "FeiShuはすでに別のユーザーにバインドされています" - -#: authentication/views/feishu.py:146 -msgid "Binding FeiShu successfully" -msgstr "本を飛ばすのバインドに成功" - -#: authentication/views/feishu.py:198 -msgid "Failed to get user from FeiShu" -msgstr "本を飛ばすからユーザーを取得できませんでした" - -#: authentication/views/feishu.py:205 -msgid "Please login with a password and then bind the FeiShu" -msgstr "パスワードでログインしてから本を飛ばすをバインドしてください" - -#: authentication/views/login.py:181 -msgid "Redirecting" -msgstr "リダイレクト" - -#: authentication/views/login.py:182 -msgid "Redirecting to {} authentication" -msgstr "{} 認証へのリダイレクト" - -#: authentication/views/login.py:205 -msgid "Please enable cookies and try again." -msgstr "クッキーを有効にして、もう一度お試しください。" - -#: authentication/views/login.py:307 -msgid "" -"Wait for {} confirm, You also can copy link to her/him
\n" -" Don't close this page" -msgstr "" -"{} 確認を待ちます。彼女/彼へのリンクをコピーすることもできます
\n" -" このページを閉じないでください" - -#: authentication/views/login.py:312 -msgid "No ticket found" -msgstr "チケットが見つかりません" - -#: authentication/views/login.py:346 -msgid "Logout success" -msgstr "ログアウト成功" - -#: authentication/views/login.py:347 -msgid "Logout success, return login page" -msgstr "ログアウト成功、ログインページを返す" - -#: authentication/views/wecom.py:41 -msgid "WeCom Error, Please contact your system administrator" -msgstr "企業微信エラー、システム管理者に連絡してください" - -#: authentication/views/wecom.py:44 -msgid "WeCom Error" -msgstr "企業微信エラー" - -#: authentication/views/wecom.py:163 -msgid "WeCom query user failed" -msgstr "企業微信ユーザーの問合せに失敗しました" - -#: authentication/views/wecom.py:172 -msgid "The WeCom is already bound to another user" -msgstr "この企業の微信はすでに他のユーザーをバインドしている。" - -#: authentication/views/wecom.py:179 -msgid "Binding WeCom successfully" -msgstr "企業の微信のバインドに成功" - -#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 -msgid "Failed to get user from WeCom" -msgstr "企業の微信からユーザーを取得できませんでした" - -#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 -msgid "Please login with a password and then bind the WeCom" -msgstr "パスワードでログインしてからWeComをバインドしてください" - -#: common/const/__init__.py:6 -#, python-format -msgid "%(name)s was created successfully" -msgstr "%(name)s が正常に作成されました" - -#: common/const/__init__.py:7 -#, python-format -msgid "%(name)s was updated successfully" -msgstr "%(name)s は正常に更新されました" - -#: common/const/choices.py:10 -msgid "Manual trigger" -msgstr "手動トリガー" - -#: common/const/choices.py:11 -msgid "Timing trigger" -msgstr "タイミングトリガー" - -#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 -msgid "Ready" -msgstr "の準備を" - -#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 -msgid "Pending" -msgstr "未定" - -#: common/const/choices.py:17 -msgid "Running" -msgstr "" - -#: common/const/choices.py:21 -#, fuzzy -#| msgid "Cancel" -msgid "Canceled" -msgstr "キャンセル" - -#: common/db/encoder.py:11 -msgid "ugettext_lazy" -msgstr "ugettext_lazy" - -#: common/db/fields.py:93 -msgid "Marshal dict data to char field" -msgstr "チャーフィールドへのマーシャルディクトデータ" - -#: common/db/fields.py:97 -msgid "Marshal dict data to text field" -msgstr "テキストフィールドへのマーシャルディクトデータ" - -#: common/db/fields.py:109 -msgid "Marshal list data to char field" -msgstr "元帥リストデータをチャーフィールドに" - -#: common/db/fields.py:113 -msgid "Marshal list data to text field" -msgstr "マーシャルリストデータをテキストフィールドに" - -#: common/db/fields.py:117 -msgid "Marshal data to char field" -msgstr "チャーフィールドへのマーシャルデータ" - -#: common/db/fields.py:121 -msgid "Marshal data to text field" -msgstr "テキストフィールドへのマーシャルデータ" - -#: common/db/fields.py:163 -msgid "Encrypt field using Secret Key" -msgstr "Secret Keyを使用したフィールドの暗号化" - -#: common/db/models.py:75 -msgid "Updated by" -msgstr "によって更新" - -#: common/drf/exc_handlers.py:25 -msgid "Object" -msgstr "オブジェクト" - -#: common/drf/fields.py:74 tickets/serializers/ticket/common.py:58 -#: xpack/plugins/change_auth_plan/serializers/asset.py:64 -#: xpack/plugins/change_auth_plan/serializers/asset.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:70 -#: xpack/plugins/change_auth_plan/serializers/asset.py:101 -#: xpack/plugins/cloud/serializers/account_attrs.py:56 -msgid "This field is required." -msgstr "このフィールドは必須です。" - -#: common/drf/fields.py:75 -#, fuzzy, python-brace-format -#| msgid "%s object does not exist." -msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "%s オブジェクトは存在しません。" - -#: common/drf/fields.py:76 -#, python-brace-format -msgid "Incorrect type. Expected pk value, received {data_type}." -msgstr "" - -#: common/drf/fields.py:138 -msgid "Invalid data type, should be list" -msgstr "" - -#: common/drf/fields.py:153 -#, fuzzy -#| msgid "Invalid ip" -msgid "Invalid choice: {}" -msgstr "無効なIP" - -#: common/drf/parsers/base.py:17 -msgid "The file content overflowed (The maximum length `{}` bytes)" -msgstr "ファイルの内容がオーバーフローしました (最大長 '{}' バイト)" - -#: common/drf/parsers/base.py:159 -msgid "Parse file error: {}" -msgstr "解析ファイルエラー: {}" - -#: common/drf/serializers/common.py:86 -msgid "Children" -msgstr "" - -#: common/drf/serializers/common.py:94 -#, fuzzy -#| msgid "Filename" -msgid "File" -msgstr "ファイル名" - -#: common/exceptions.py:15 -#, python-format -msgid "%s object does not exist." -msgstr "%s オブジェクトは存在しません。" - -#: common/exceptions.py:25 -msgid "Someone else is doing this. Please wait for complete" -msgstr "他の誰かがこれをやっています。完了をお待ちください" - -#: common/exceptions.py:30 -msgid "Your request timeout" -msgstr "リクエストのタイムアウト" - -#: common/exceptions.py:35 -msgid "M2M reverse not allowed" -msgstr "M2Mリバースは許可されません" - -#: common/exceptions.py:41 -msgid "Is referenced by other objects and cannot be deleted" -msgstr "他のオブジェクトによって参照され、削除できません。" - -#: common/exceptions.py:48 -msgid "This action require confirm current user" -msgstr "このアクションでは、MFAの確認が必要です。" - -#: common/exceptions.py:56 -msgid "Unexpect error occur" -msgstr "予期しないエラーが発生します" - -#: common/mixins/api/action.py:52 -msgid "Request file format may be wrong" -msgstr "リクエストファイルの形式が間違っている可能性があります" - -#: common/mixins/models.py:33 -msgid "is discard" -msgstr "は破棄されます" - -#: common/mixins/models.py:34 -msgid "discard time" -msgstr "時間を捨てる" - -#: common/mixins/views.py:58 -msgid "Export all" -msgstr "すべてエクスポート" - -#: common/mixins/views.py:60 -msgid "Export only selected items" -msgstr "選択項目のみエクスポート" - -#: common/mixins/views.py:65 -#, python-format -msgid "Export filtered: %s" -msgstr "検索のエクスポート: %s" - -#: common/sdk/im/exceptions.py:23 -msgid "Network error, please contact system administrator" -msgstr "ネットワークエラー、システム管理者に連絡してください" - -#: common/sdk/im/wecom/__init__.py:15 -msgid "WeCom error, please contact system administrator" -msgstr "企業微信エラー、システム管理者に連絡してください" - -#: common/sdk/sms/alibaba.py:56 -msgid "Signature does not match" -msgstr "署名が一致しない" - -#: common/sdk/sms/cmpp2.py:46 -msgid "sp_id is 6 bits" -msgstr "SP idは6ビット" - -#: common/sdk/sms/cmpp2.py:216 -msgid "Failed to connect to the CMPP gateway server, err: {}" -msgstr "接続ゲートウェイサーバエラー, 非: {}" - -#: common/sdk/sms/endpoint.py:16 -msgid "Alibaba cloud" -msgstr "アリ雲" - -#: common/sdk/sms/endpoint.py:17 -msgid "Tencent cloud" -msgstr "テンセント雲" - -#: common/sdk/sms/endpoint.py:18 -msgid "CMPP v2.0" -msgstr "CMPP v2.0" - -#: common/sdk/sms/endpoint.py:29 -msgid "SMS provider not support: {}" -msgstr "SMSプロバイダーはサポートしていません: {}" - -#: common/sdk/sms/endpoint.py:50 -msgid "SMS verification code signature or template invalid" -msgstr "SMS検証コードの署名またはテンプレートが無効" - -#: common/sdk/sms/utils.py:15 -msgid "The verification code has expired. Please resend it" -msgstr "確認コードの有効期限が切れています。再送信してください" - -#: common/sdk/sms/utils.py:20 -msgid "The verification code is incorrect" -msgstr "確認コードが正しくありません" - -#: common/sdk/sms/utils.py:25 -msgid "Please wait {} seconds before sending" -msgstr "{} 秒待ってから送信してください" - -#: common/tasks.py:13 -#, fuzzy -#| msgid "Send user" -msgid "Send email" -msgstr "ユーザーを送信" - -#: common/tasks.py:40 -msgid "Send email attachment" -msgstr "" - -#: common/utils/ip/geoip/utils.py:26 -msgid "Invalid ip" -msgstr "無効なIP" - -#: common/utils/ip/utils.py:78 -#, fuzzy -#| msgid "Invalid signature." -msgid "Invalid address" -msgstr "署名が無効です。" - -#: common/validators.py:14 -msgid "Special char not allowed" -msgstr "特別なcharは許可されていません" - -#: common/validators.py:32 -msgid "This field must be unique." -msgstr "このフィールドは一意である必要があります。" - -#: common/validators.py:40 -msgid "Should not contains special characters" -msgstr "特殊文字を含むべきではない" - -#: common/validators.py:46 -msgid "The mobile phone number format is incorrect" -msgstr "携帯電話番号の形式が正しくありません" - -#: jumpserver/conf.py:388 -msgid "Create account successfully" -msgstr "アカウントを正常に作成" - -#: jumpserver/conf.py:390 -msgid "Your account has been created successfully" -msgstr "アカウントが正常に作成されました" - -#: jumpserver/context_processor.py:12 -msgid "JumpServer Open Source Bastion Host" -msgstr "JumpServer オープンソースの要塞ホスト" - -#: jumpserver/views/celery_flower.py:23 -msgid "

Flower service unavailable, check it

" -msgstr "

フラワーサービス利用不可、チェック

" - -#: jumpserver/views/other.py:26 -msgid "" -"
Luna is a separately deployed program, you need to deploy Luna, koko, " -"configure nginx for url distribution,
If you see this page, " -"prove that you are not accessing the nginx listening port. Good luck." -msgstr "" -"
Lunaは個別にデプロイされたプログラムです。Luna、kokoをデプロイする必要" -"があります。urlディストリビューションにnginxを設定します。
この" -"ページが表示されている場合は、nginxリスニングポートにアクセスしていないことを" -"証明してください。頑張ってください。" - -#: jumpserver/views/other.py:70 -msgid "Websocket server run on port: {}, you should proxy it on nginx" -msgstr "" -"Websocket サーバーはport: {}で実行されます。nginxでプロキシする必要がありま" -"す。" - -#: jumpserver/views/other.py:84 -msgid "" -"
Koko is a separately deployed program, you need to deploy Koko, " -"configure nginx for url distribution,
If you see this page, " -"prove that you are not accessing the nginx listening port. Good luck." -msgstr "" -"
Kokoは個別にデプロイされているプログラムです。Kokoをデプロイする必要が" -"あります。URL配布用にnginxを設定します。
このページが表示されて" -"いる場合は、nginxリスニングポートにアクセスしていないことを証明してください。" -"頑張ってください。" - -#: notifications/apps.py:7 -msgid "Notifications" -msgstr "通知" - -#: notifications/backends/__init__.py:10 users/forms/profile.py:102 -#: users/models/user.py:667 -msgid "Email" -msgstr "メール" - -#: notifications/backends/__init__.py:13 -msgid "Site message" -msgstr "サイトメッセージ" - -#: notifications/notifications.py:46 -msgid "Publish the station message" -msgstr "" - -#: ops/ansible/inventory.py:75 -#, fuzzy -#| msgid "Account unavailable" -msgid "No account available" -msgstr "利用できないアカウント" - -#: ops/ansible/inventory.py:178 -#, fuzzy -#| msgid "User disabled." -msgid "Ansible disabled" -msgstr "ユーザーが無効になりました。" - -#: ops/ansible/inventory.py:194 -msgid "Skip hosts below:" -msgstr "" - -#: ops/api/celery.py:63 ops/api/celery.py:78 -msgid "Waiting task start" -msgstr "タスク開始待ち" - -#: ops/apps.py:9 ops/notifications.py:16 -msgid "App ops" -msgstr "アプリ操作" - -#: ops/const.py:6 -msgid "Push" -msgstr "" - -#: ops/const.py:7 -#, fuzzy -#| msgid "Verified" -msgid "Verify" -msgstr "確認済み" - -#: ops/const.py:8 -msgid "Collect" -msgstr "" - -#: ops/const.py:9 -#, fuzzy -#| msgid "Change Password" -msgid "Change password" -msgstr "パスワードの変更" - -#: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 -msgid "Custom password" -msgstr "カスタムパスワード" - -#: ops/exception.py:6 -msgid "no valid program entry found." -msgstr "" - -#: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:72 -msgid "Cycle perform" -msgstr "サイクル実行" - -#: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 -#: settings/serializers/auth/ldap.py:69 -msgid "Regularly perform" -msgstr "定期的に実行する" - -#: ops/mixin.py:108 -msgid "Interval" -msgstr "間隔" - -#: ops/mixin.py:118 -msgid "* Please enter a valid crontab expression" -msgstr "* 有効なcrontab式を入力してください" - -#: ops/mixin.py:125 -msgid "Range {} to {}" -msgstr "{} から {} までの範囲" - -#: ops/mixin.py:136 -msgid "Require periodic or regularly perform setting" -msgstr "定期的または定期的に設定を行う必要があります" - -#: ops/models/adhoc.py:18 ops/models/job.py:31 -#, fuzzy -#| msgid "PowerShell" -msgid "Powershell" -msgstr "PowerShell" - -#: ops/models/adhoc.py:22 -msgid "Pattern" -msgstr "パターン" - -#: ops/models/adhoc.py:24 ops/models/job.py:38 -msgid "Module" -msgstr "" - -#: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 -#: terminal/models/component/task.py:17 -msgid "Args" -msgstr "アルグ" - -#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 -#: terminal/models/session/sharing.py:24 -msgid "Creator" -msgstr "作成者" - -#: ops/models/base.py:19 -#, fuzzy -#| msgid "Account key" -msgid "Account policy" -msgstr "アカウントキー" - -#: ops/models/base.py:20 -#, fuzzy -#| msgid "Command execution" -msgid "Last execution" -msgstr "コマンド実行" - -#: ops/models/base.py:22 -#, fuzzy -#| msgid "Date last sync" -msgid "Date last run" -msgstr "最終同期日" - -#: ops/models/base.py:51 ops/models/job.py:105 -#: xpack/plugins/cloud/models.py:169 -msgid "Result" -msgstr "結果" - -#: ops/models/base.py:52 ops/models/job.py:106 -msgid "Summary" -msgstr "" - -#: ops/models/celery.py:55 terminal/models/component/task.py:18 -msgid "Kwargs" -msgstr "クワーグ" - -#: ops/models/celery.py:56 tickets/models/comment.py:13 -#: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 -#: tickets/serializers/ticket/ticket.py:20 -msgid "State" -msgstr "状態" - -#: ops/models/celery.py:57 terminal/models/session/sharing.py:111 -#: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 -msgid "Finished" -msgstr "終了" - -#: ops/models/celery.py:58 -#, fuzzy -#| msgid "Date finished" -msgid "Date published" -msgstr "終了日" - -#: ops/models/job.py:21 -msgid "Adhoc" -msgstr "" - -#: ops/models/job.py:22 ops/models/job.py:41 -msgid "Playbook" -msgstr "" - -#: ops/models/job.py:25 -msgid "Privileged Only" -msgstr "" - -#: ops/models/job.py:26 -msgid "Privileged First" -msgstr "" - -#: ops/models/job.py:27 -msgid "Skip" -msgstr "" - -#: ops/models/job.py:39 -msgid "Chdir" -msgstr "" - -#: ops/models/job.py:40 -msgid "Timeout (Seconds)" -msgstr "" - -#: ops/models/job.py:45 -msgid "Runas" -msgstr "" - -#: ops/models/job.py:47 -#, fuzzy -#| msgid "Account key" -msgid "Runas policy" -msgstr "アカウントキー" - -#: ops/models/job.py:48 -msgid "Use Parameter Define" -msgstr "" - -#: ops/models/job.py:49 -msgid "Parameters define" -msgstr "" - -#: ops/models/job.py:104 -msgid "Parameters" -msgstr "" - -#: ops/notifications.py:17 -msgid "Server performance" -msgstr "サーバーのパフォーマンス" - -#: ops/notifications.py:23 -msgid "Terminal health check warning" -msgstr "ターミナルヘルスチェックの警告" - -#: ops/notifications.py:68 -#, python-brace-format -msgid "The terminal is offline: {name}" -msgstr "ターミナルはオフラインです: {name}" - -#: ops/notifications.py:73 -#, python-brace-format -msgid "Disk used more than {max_threshold}%: => {value}" -msgstr "{max_threshold}%: => {value} を超えるディスクを使用" - -#: ops/notifications.py:78 -#, python-brace-format -msgid "Memory used more than {max_threshold}%: => {value}" -msgstr "{max_threshold}%: => {value} を超える使用メモリ" - -#: ops/notifications.py:83 -#, python-brace-format -msgid "CPU load more than {max_threshold}: => {value}" -msgstr "{max_threshold} を超えるCPUロード: => {value}" - -#: ops/serializers/job.py:11 -#, fuzzy -#| msgid "Run system user" -msgid "Run after save" -msgstr "システムユーザーの実行" - -#: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 -#: terminal/models/component/task.py:26 -#: xpack/plugins/gathered_user/models.py:68 -msgid "Task" -msgstr "タスク" - -#: ops/tasks.py:28 -#, fuzzy -#| msgid "Run asset" -msgid "Run ansible task" -msgstr "アセットの実行" - -#: ops/tasks.py:36 -#, fuzzy -#| msgid "Run asset" -msgid "Run ansible task execution" -msgstr "アセットの実行" - -#: ops/tasks.py:50 -msgid "Periodic clear celery tasks" -msgstr "" - -#: ops/tasks.py:52 -msgid "Clean celery log period" -msgstr "きれいなセロリログ期間" - -#: ops/tasks.py:69 -#, fuzzy -#| msgid "Clean celery log period" -msgid "Clear celery periodic tasks" -msgstr "きれいなセロリログ期間" - -#: ops/tasks.py:92 -msgid "Create or update periodic tasks" -msgstr "" - -#: ops/tasks.py:100 -#, fuzzy -#| msgid "Periodic perform" -msgid "Periodic check service performance" -msgstr "定期的なパフォーマンス" - -#: ops/templates/ops/celery_task_log.html:4 -msgid "Task log" -msgstr "タスクログ" - -#: ops/utils.py:64 -msgid "Update task content: {}" -msgstr "タスク内容の更新: {}" - -#: orgs/api.py:67 -msgid "The current organization ({}) cannot be deleted" -msgstr "現在の組織 ({}) は削除できません" - -#: orgs/api.py:72 -msgid "" -"LDAP synchronization is set to the current organization. Please switch to " -"another organization before deleting" -msgstr "" -"LDAP 同期は現在の組織に設定されます。削除する前に別の組織に切り替えてください" - -#: orgs/api.py:81 -msgid "The organization have resource ({}) cannot be deleted" -msgstr "組織のリソース ({}) は削除できません" - -#: orgs/apps.py:7 rbac/tree.py:113 -msgid "App organizations" -msgstr "アプリ組織" - -#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 -#: rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:60 -msgid "Organization" -msgstr "組織" - -#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 -msgid "Org name" -msgstr "組織名" - -#: orgs/models.py:72 -#, fuzzy -#| msgid "Built-in" -msgid "Builtin" -msgstr "内蔵" - -#: orgs/models.py:80 -msgid "GLOBAL" -msgstr "グローバル組織" - -#: orgs/models.py:82 -msgid "DEFAULT" -msgstr "" - -#: orgs/models.py:84 -msgid "SYSTEM" -msgstr "" - -#: orgs/models.py:90 -msgid "Can view root org" -msgstr "グローバル組織を表示できます" - -#: orgs/models.py:91 -msgid "Can view all joined org" -msgstr "参加しているすべての組織を表示できます" - -#: orgs/tasks.py:9 -#, fuzzy -#| msgid "Global organization name" -msgid "Refresh organization cache" -msgstr "グローバル組織名" - -#: perms/apps.py:9 -msgid "App permissions" -msgstr "アプリの権限" - -#: perms/const.py:12 -msgid "Connect" -msgstr "接続" - -#: perms/const.py:15 -#, fuzzy -#| msgid "Copy link" -msgid "Copy" -msgstr "リンクのコピー" - -#: perms/const.py:16 -msgid "Paste" -msgstr "" - -#: perms/const.py:26 -msgid "Transfer" -msgstr "" - -#: perms/const.py:27 -#, fuzzy -#| msgid "Clipboard copy" -msgid "Clipboard" -msgstr "クリップボードのコピー" - -#: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 -#: perms/serializers/permission.py:29 perms/serializers/permission.py:59 -#: tickets/models/ticket/apply_application.py:28 -#: tickets/models/ticket/apply_asset.py:18 -msgid "Actions" -msgstr "アクション" - -#: perms/models/asset_permission.py:73 -msgid "From ticket" -msgstr "チケットから" - -#: perms/models/perm_node.py:55 -msgid "Ungrouped" -msgstr "グループ化されていません" - -#: perms/models/perm_node.py:57 -msgid "Favorite" -msgstr "お気に入り" - -#: perms/models/perm_node.py:104 -msgid "Permed asset" -msgstr "許可された資産" - -#: perms/models/perm_node.py:106 -msgid "Can view my assets" -msgstr "私の資産を見ることができます" - -#: perms/models/perm_node.py:107 -msgid "Can view user assets" -msgstr "ユーザー資産を表示できます" - -#: perms/models/perm_node.py:108 -msgid "Can view usergroup assets" -msgstr "ユーザーグループの資産を表示できます" - -#: perms/models/perm_node.py:119 -#, fuzzy -#| msgid "Gather account" -msgid "Permed account" -msgstr "アカウントを集める" - -#: perms/notifications.py:12 perms/notifications.py:44 -msgid "today" -msgstr "今" - -#: perms/notifications.py:15 -msgid "You permed assets is about to expire" -msgstr "パーマ資産の有効期限が近づいています" - -#: perms/notifications.py:20 -msgid "permed assets" -msgstr "パーマ資産" - -#: perms/notifications.py:59 -msgid "Asset permissions is about to expire" -msgstr "資産権限の有効期限が近づいています" - -#: perms/notifications.py:64 -msgid "asset permissions of organization {}" -msgstr "組織 {} の資産権限" - -#: perms/serializers/permission.py:31 perms/serializers/permission.py:60 -#: users/serializers/user.py:100 users/serializers/user.py:205 -msgid "Is expired" -msgstr "期限切れです" - -#: perms/templates/perms/_msg_item_permissions_expire.html:7 -#: perms/templates/perms/_msg_permed_items_expire.html:7 -#, python-format -msgid "" -"\n" -" The following %(item_type)s will expire in %(count)s days\n" -" " -msgstr "" -"\n" -" 次の %(item_type)s は %(count)s 日以内に期限切れになります\n" -" " - -#: perms/templates/perms/_msg_permed_items_expire.html:21 -msgid "If you have any question, please contact the administrator" -msgstr "質問があったら、管理者に連絡して下さい" - -#: perms/utils/user_permission.py:627 rbac/tree.py:57 -msgid "My assets" -msgstr "私の資産" - -#: rbac/api/role.py:34 -msgid "Internal role, can't be destroy" -msgstr "内部の役割は、破壊することはできません" - -#: rbac/api/role.py:38 -msgid "The role has been bound to users, can't be destroy" -msgstr "ロールはユーザーにバインドされており、破壊することはできません" - -#: rbac/api/role.py:60 -msgid "Internal role, can't be update" -msgstr "内部ロール、更新できません" - -#: rbac/api/rolebinding.py:52 -msgid "{} at least one system role" -msgstr "{} 少なくとも1つのシステムロール" - -#: rbac/apps.py:7 -msgid "RBAC" -msgstr "RBAC" - -#: rbac/builtin.py:111 -msgid "SystemAdmin" -msgstr "システム管理者" - -#: rbac/builtin.py:114 -msgid "SystemAuditor" -msgstr "システム監査人" - -#: rbac/builtin.py:117 -msgid "SystemComponent" -msgstr "システムコンポーネント" - -#: rbac/builtin.py:123 -msgid "OrgAdmin" -msgstr "組織管理者" - -#: rbac/builtin.py:126 -msgid "OrgAuditor" -msgstr "監査員を組織する" - -#: rbac/builtin.py:129 -msgid "OrgUser" -msgstr "組織ユーザー" - -#: rbac/models/menu.py:13 -msgid "Menu permission" -msgstr "メニュー権限" - -#: rbac/models/menu.py:15 -msgid "Can view console view" -msgstr "コンソールビューを表示できます" - -#: rbac/models/menu.py:16 -msgid "Can view audit view" -msgstr "監査ビューを表示できます" - -#: rbac/models/menu.py:17 -msgid "Can view workbench view" -msgstr "ワークスペースビューを表示できます" - -#: rbac/models/menu.py:18 -msgid "Can view web terminal" -msgstr "Webターミナルを表示できます" - -#: rbac/models/menu.py:19 -msgid "Can view file manager" -msgstr "ファイルマネージャを表示できます" - -#: rbac/models/permission.py:26 -msgid "Permission" -msgstr "権限" - -#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 -#: settings/serializers/auth/oauth2.py:35 -msgid "Scope" -msgstr "スコープ" - -#: rbac/models/role.py:34 -msgid "Permissions" -msgstr "権限" - -#: rbac/models/role.py:36 -msgid "Built-in" -msgstr "内蔵" - -#: rbac/models/role.py:46 rbac/models/rolebinding.py:44 -#: users/models/user.py:675 -msgid "Role" -msgstr "ロール" - -#: rbac/models/role.py:144 -msgid "System role" -msgstr "システムの役割" - -#: rbac/models/role.py:152 -msgid "Organization role" -msgstr "組織の役割" - -#: rbac/models/rolebinding.py:53 -msgid "Role binding" -msgstr "ロールバインディング" - -#: rbac/models/rolebinding.py:137 -msgid "All organizations" -msgstr "全ての組織" - -#: rbac/models/rolebinding.py:166 -msgid "" -"User last role in org, can not be delete, you can remove user from org " -"instead" -msgstr "" -"ユーザーの最後のロールは削除できません。ユーザーを組織から削除できます。" - -#: rbac/models/rolebinding.py:173 -msgid "Organization role binding" -msgstr "組織の役割バインディング" - -#: rbac/models/rolebinding.py:188 -msgid "System role binding" -msgstr "システムロールバインディング" - -#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 -msgid "Perms" -msgstr "パーマ" - -#: rbac/serializers/role.py:11 -msgid "Scope display" -msgstr "スコープ表示" - -#: rbac/serializers/role.py:26 users/serializers/group.py:34 -msgid "Users amount" -msgstr "ユーザー数" - -#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 -msgid "Display name" -msgstr "表示名" - -#: rbac/serializers/rolebinding.py:22 -msgid "Role display" -msgstr "ロール表示" - -#: rbac/serializers/rolebinding.py:56 -msgid "Has bound this role" -msgstr "この役割をバインドしました" - -#: rbac/tree.py:18 rbac/tree.py:19 -msgid "All permissions" -msgstr "すべての権限" - -#: rbac/tree.py:25 -msgid "Console view" -msgstr "コンソールビュー" - -#: rbac/tree.py:26 -msgid "Workbench view" -msgstr "ワークスペースビュー" - -#: rbac/tree.py:27 -msgid "Audit view" -msgstr "監査ビュー" - -#: rbac/tree.py:28 settings/models.py:156 -msgid "System setting" -msgstr "システム設定" - -#: rbac/tree.py:29 -msgid "Other" -msgstr "その他" - -#: rbac/tree.py:41 -msgid "Session audits" -msgstr "セッション監査" - -#: rbac/tree.py:51 -msgid "Cloud import" -msgstr "クラウドインポート" - -#: rbac/tree.py:52 -msgid "Backup account" -msgstr "バックアップアカウント" - -#: rbac/tree.py:53 -msgid "Gather account" -msgstr "アカウントを集める" - -#: rbac/tree.py:54 -msgid "App change auth" -msgstr "応用改密" - -#: rbac/tree.py:55 -msgid "Asset change auth" -msgstr "資産の改ざん" - -#: rbac/tree.py:56 -msgid "Terminal setting" -msgstr "ターミナル設定" - -#: rbac/tree.py:58 -msgid "My apps" -msgstr "マイアプリ" - -#: rbac/tree.py:114 -msgid "Ticket comment" -msgstr "チケットコメント" - -#: rbac/tree.py:115 tickets/models/ticket/general.py:306 -msgid "Ticket" -msgstr "チケット" - -#: rbac/tree.py:116 -msgid "Common setting" -msgstr "共通設定" - -#: rbac/tree.py:117 -msgid "View permission tree" -msgstr "権限ツリーの表示" - -#: rbac/tree.py:118 -msgid "Execute batch command" -msgstr "バッチ実行コマンド" - -#: settings/api/dingtalk.py:31 settings/api/feishu.py:36 -#: settings/api/sms.py:131 settings/api/wecom.py:37 -msgid "Test success" -msgstr "テストの成功" - -#: settings/api/email.py:20 -msgid "Test mail sent to {}, please check" -msgstr "{}に送信されたテストメールを確認してください" - -#: settings/api/ldap.py:166 -msgid "Synchronization start, please wait." -msgstr "同期開始、お待ちください。" - -#: settings/api/ldap.py:170 -msgid "Synchronization is running, please wait." -msgstr "同期が実行中です。しばらくお待ちください。" - -#: settings/api/ldap.py:175 -msgid "Synchronization error: {}" -msgstr "同期エラー: {}" - -#: settings/api/ldap.py:213 -msgid "Get ldap users is None" -msgstr "Ldapユーザーを取得するにはNone" - -#: settings/api/ldap.py:222 -msgid "Imported {} users successfully (Organization: {})" -msgstr "{} 人のユーザーを正常にインポートしました (組織: {})" - -#: settings/api/sms.py:113 -msgid "Invalid SMS platform" -msgstr "無効なショートメッセージプラットフォーム" - -#: settings/api/sms.py:119 -msgid "test_phone is required" -msgstr "携帯番号をテストこのフィールドは必須です" - -#: settings/apps.py:7 -msgid "Settings" -msgstr "設定" - -#: settings/models.py:158 -msgid "Can change email setting" -msgstr "メール設定を変更できます" - -#: settings/models.py:159 -msgid "Can change auth setting" -msgstr "資格認定の設定" - -#: settings/models.py:160 -msgid "Can change system msg sub setting" -msgstr "システムmsgサブ设定を変更できます" - -#: settings/models.py:161 -msgid "Can change sms setting" -msgstr "Smsの設定を変えることができます" - -#: settings/models.py:162 -msgid "Can change security setting" -msgstr "セキュリティ設定を変更できます" - -#: settings/models.py:163 -msgid "Can change clean setting" -msgstr "きれいな設定を変えることができます" - -#: settings/models.py:164 -msgid "Can change interface setting" -msgstr "インターフェイスの設定を変えることができます" - -#: settings/models.py:165 -msgid "Can change license setting" -msgstr "ライセンス設定を変更できます" - -#: settings/models.py:166 -msgid "Can change terminal setting" -msgstr "ターミナルの設定を変えることができます" - -#: settings/models.py:167 -msgid "Can change other setting" -msgstr "他の設定を変えることができます" - -#: settings/serializers/auth/base.py:10 -msgid "CAS Auth" -msgstr "CAS 認証" - -#: settings/serializers/auth/base.py:11 -msgid "OPENID Auth" -msgstr "OPENID 認証" - -#: settings/serializers/auth/base.py:12 -msgid "RADIUS Auth" -msgstr "RADIUS 認証" - -#: settings/serializers/auth/base.py:13 -msgid "DingTalk Auth" -msgstr "くぎ 認証" - -#: settings/serializers/auth/base.py:14 -msgid "FeiShu Auth" -msgstr "飛本 認証" - -#: settings/serializers/auth/base.py:15 -msgid "WeCom Auth" -msgstr "企業微信 認証" - -#: settings/serializers/auth/base.py:16 -msgid "SSO Auth" -msgstr "SSO Token 認証" - -#: settings/serializers/auth/base.py:17 -msgid "SAML2 Auth" -msgstr "SAML2 認証" - -#: settings/serializers/auth/base.py:20 settings/serializers/basic.py:36 -msgid "Forgot password url" -msgstr "パスワードのURLを忘れた" - -#: settings/serializers/auth/base.py:26 -msgid "Enable login redirect msg" -msgstr "ログインリダイレクトの有効化msg" - -#: settings/serializers/auth/cas.py:10 -msgid "Enable CAS Auth" -msgstr "CAS 認証の有効化" - -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:48 -msgid "Server url" -msgstr "サービス側アドレス" - -#: settings/serializers/auth/cas.py:14 -msgid "Proxy server url" -msgstr "コールバックアドレス" - -#: settings/serializers/auth/cas.py:16 settings/serializers/auth/saml2.py:32 -msgid "Logout completely" -msgstr "同期ログアウト" - -#: settings/serializers/auth/cas.py:21 -msgid "Username attr" -msgstr "ユーザー名のプロパティ" - -#: settings/serializers/auth/cas.py:24 -msgid "Enable attributes map" -msgstr "属性マップの有効化" - -#: settings/serializers/auth/cas.py:26 settings/serializers/auth/saml2.py:31 -msgid "Rename attr" -msgstr "マッピングのプロパティ" - -#: settings/serializers/auth/cas.py:27 -msgid "Create user if not" -msgstr "そうでない場合はユーザーを作成" - -#: settings/serializers/auth/dingtalk.py:13 -msgid "Enable DingTalk Auth" -msgstr "ピン認証の有効化" - -#: settings/serializers/auth/feishu.py:12 -msgid "Enable FeiShu Auth" -msgstr "飛本認証の有効化" - -#: settings/serializers/auth/ldap.py:41 -msgid "LDAP server" -msgstr "LDAPサーバー" - -#: settings/serializers/auth/ldap.py:42 -msgid "eg: ldap://localhost:389" -msgstr "例: ldap://localhost:389" - -#: settings/serializers/auth/ldap.py:44 -msgid "Bind DN" -msgstr "DN のバインド" - -#: settings/serializers/auth/ldap.py:49 -msgid "User OU" -msgstr "ユーザー OU" - -#: settings/serializers/auth/ldap.py:50 -msgid "Use | split multi OUs" -msgstr "使用 | splitマルチ OU" - -#: settings/serializers/auth/ldap.py:53 -msgid "User search filter" -msgstr "ユーザー検索フィルター" - -#: settings/serializers/auth/ldap.py:54 -#, python-format -msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" -msgstr "選択は (cnまたはuidまたはsAMAccountName)=%(user)s)" - -#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oauth2.py:51 -#: settings/serializers/auth/oidc.py:36 -msgid "User attr map" -msgstr "ユーザー属性マッピング" - -#: settings/serializers/auth/ldap.py:58 -msgid "" -"User attr map present how to map LDAP user attr to jumpserver, username,name," -"email is jumpserver attr" -msgstr "" -"ユーザー属性マッピングは、LDAPのユーザー属性をjumpserverユーザーにマッピング" -"する方法、username, name,emailはjumpserverのユーザーが必要とする属性です" - -#: settings/serializers/auth/ldap.py:76 -msgid "Connect timeout" -msgstr "接続タイムアウト" - -#: settings/serializers/auth/ldap.py:78 -msgid "Search paged size" -msgstr "ページサイズを検索" - -#: settings/serializers/auth/ldap.py:80 -msgid "Enable LDAP auth" -msgstr "LDAP認証の有効化" - -#: settings/serializers/auth/oauth2.py:20 -msgid "Enable OAuth2 Auth" -msgstr "OAuth2認証の有効化" - -#: settings/serializers/auth/oauth2.py:23 -msgid "Logo" -msgstr "アイコン" - -#: settings/serializers/auth/oauth2.py:26 -msgid "Service provider" -msgstr "サービスプロバイダー" - -#: settings/serializers/auth/oauth2.py:29 settings/serializers/auth/oidc.py:18 -msgid "Client Id" -msgstr "クライアントID" - -#: settings/serializers/auth/oauth2.py:32 settings/serializers/auth/oidc.py:21 -#: xpack/plugins/cloud/serializers/account_attrs.py:38 -msgid "Client Secret" -msgstr "クライアント秘密" - -#: settings/serializers/auth/oauth2.py:38 settings/serializers/auth/oidc.py:62 -msgid "Provider auth endpoint" -msgstr "認証エンドポイントアドレス" - -#: settings/serializers/auth/oauth2.py:41 settings/serializers/auth/oidc.py:65 -msgid "Provider token endpoint" -msgstr "プロバイダートークンエンドポイント" - -#: settings/serializers/auth/oauth2.py:44 settings/serializers/auth/oidc.py:29 -msgid "Client authentication method" -msgstr "クライアント認証方式" - -#: settings/serializers/auth/oauth2.py:48 settings/serializers/auth/oidc.py:71 -msgid "Provider userinfo endpoint" -msgstr "プロバイダーuserinfoエンドポイント" - -#: settings/serializers/auth/oauth2.py:54 settings/serializers/auth/oidc.py:92 -#: settings/serializers/auth/saml2.py:33 -msgid "Always update user" -msgstr "常にユーザーを更新" - -#: settings/serializers/auth/oidc.py:15 -msgid "Base site url" -msgstr "ベースサイトのアドレス" - -#: settings/serializers/auth/oidc.py:31 -msgid "Share session" -msgstr "セッションの共有" - -#: settings/serializers/auth/oidc.py:33 -msgid "Ignore ssl verification" -msgstr "Ssl検証を無視する" - -#: settings/serializers/auth/oidc.py:37 -msgid "" -"User attr map present how to map OpenID user attr to jumpserver, username," -"name,email is jumpserver attr" -msgstr "" -"ユーザー属性マッピングは、OpenIDのユーザー属性をjumpserverユーザーにマッピン" -"グする方法、username, name,emailはjumpserverのユーザーが必要とする属性です" - -#: settings/serializers/auth/oidc.py:45 -msgid "Use Keycloak" -msgstr "Keycloakを使用する" - -#: settings/serializers/auth/oidc.py:51 -msgid "Realm name" -msgstr "レルム名" - -#: settings/serializers/auth/oidc.py:57 -msgid "Enable OPENID Auth" -msgstr "OIDC認証の有効化" - -#: settings/serializers/auth/oidc.py:59 -msgid "Provider endpoint" -msgstr "プロバイダーエンドポイント" - -#: settings/serializers/auth/oidc.py:68 -msgid "Provider jwks endpoint" -msgstr "プロバイダーjwksエンドポイント" - -#: settings/serializers/auth/oidc.py:74 -msgid "Provider end session endpoint" -msgstr "プロバイダーのセッション終了エンドポイント" - -#: settings/serializers/auth/oidc.py:77 -msgid "Provider sign alg" -msgstr "プロビダーサインalg" - -#: settings/serializers/auth/oidc.py:80 -msgid "Provider sign key" -msgstr "プロバイダ署名キー" - -#: settings/serializers/auth/oidc.py:82 -msgid "Scopes" -msgstr "スコープ" - -#: settings/serializers/auth/oidc.py:84 -msgid "Id token max age" -msgstr "IDトークンの最大年齢" - -#: settings/serializers/auth/oidc.py:87 -msgid "Id token include claims" -msgstr "IDトークンにはクレームが含まれます" - -#: settings/serializers/auth/oidc.py:89 -msgid "Use state" -msgstr "使用状態" - -#: settings/serializers/auth/oidc.py:90 -msgid "Use nonce" -msgstr "Nonceを使用" - -#: settings/serializers/auth/radius.py:13 -msgid "Enable Radius Auth" -msgstr "Radius認証の有効化" - -#: settings/serializers/auth/radius.py:19 -msgid "OTP in Radius" -msgstr "Radius のOTP" - -#: settings/serializers/auth/saml2.py:12 -msgid "Enable SAML2 Auth" -msgstr "SAML2認証の有効化" - -#: settings/serializers/auth/saml2.py:15 -msgid "IDP metadata URL" -msgstr "IDP metadata アドレス" - -#: settings/serializers/auth/saml2.py:18 -msgid "IDP metadata XML" -msgstr "IDP metadata XML" - -#: settings/serializers/auth/saml2.py:21 -msgid "SP advanced settings" -msgstr "詳細設定" - -#: settings/serializers/auth/saml2.py:25 -msgid "SP private key" -msgstr "SP プライベートキー" - -#: settings/serializers/auth/saml2.py:29 -msgid "SP cert" -msgstr "SP 証明書" - -#: settings/serializers/auth/sms.py:15 -msgid "Enable SMS" -msgstr "SMSの有効化" - -#: settings/serializers/auth/sms.py:17 -msgid "SMS provider / Protocol" -msgstr "SMSプロバイダ / プロトコル" - -#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:43 -#: settings/serializers/auth/sms.py:51 settings/serializers/auth/sms.py:62 -#: settings/serializers/email.py:65 -msgid "Signature" -msgstr "署名" - -#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:44 -#: settings/serializers/auth/sms.py:52 -msgid "Template code" -msgstr "テンプレートコード" - -#: settings/serializers/auth/sms.py:29 -msgid "Test phone" -msgstr "テスト電話" - -#: settings/serializers/auth/sms.py:58 -msgid "Enterprise code(SP id)" -msgstr "企業コード(SP id)" - -#: settings/serializers/auth/sms.py:59 -msgid "Shared secret(Shared secret)" -msgstr "パスワードを共有する(Shared secret)" - -#: settings/serializers/auth/sms.py:60 -msgid "Original number(Src id)" -msgstr "元の番号(Src id)" - -#: settings/serializers/auth/sms.py:61 -msgid "Business type(Service id)" -msgstr "ビジネス・タイプ(Service id)" - -#: settings/serializers/auth/sms.py:64 -msgid "Template" -msgstr "テンプレート" - -#: settings/serializers/auth/sms.py:65 -#, python-brace-format -msgid "" -"Template need contain {code} and Signature + template length does not exceed " -"67 words. For example, your verification code is {code}, which is valid for " -"5 minutes. Please do not disclose it to others." -msgstr "" -"テンプレートには{code}を含める必要があり、署名+テンプレートの長さは67ワード未" -"満です。たとえば、認証コードは{code}で、有効期間は5分です。他の人には言わない" -"でください。" - -#: settings/serializers/auth/sms.py:74 -#, python-brace-format -msgid "The template needs to contain {code}" -msgstr "テンプレートには{code}を含める必要があります" - -#: settings/serializers/auth/sms.py:77 -msgid "Signature + Template must not exceed 65 words" -msgstr "署名+テンプレートの長さは65文字以内" - -#: settings/serializers/auth/sso.py:11 -msgid "Enable SSO auth" -msgstr "SSO Token認証の有効化" - -#: settings/serializers/auth/sso.py:12 -msgid "Other service can using SSO token login to JumpServer without password" -msgstr "" -"他のサービスはパスワードなしでJumpServerへのSSOトークンログインを使用できます" - -#: settings/serializers/auth/sso.py:15 -msgid "SSO auth key TTL" -msgstr "Token有効期間" - -#: settings/serializers/auth/sso.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:169 -msgid "Unit: second" -msgstr "単位: 秒" - -#: settings/serializers/auth/wecom.py:13 -msgid "Enable WeCom Auth" -msgstr "企業微信認証の有効化" - -#: settings/serializers/basic.py:9 -msgid "Subject" -msgstr "件名" - -#: settings/serializers/basic.py:13 -msgid "More url" -msgstr "もっとURL" - -#: settings/serializers/basic.py:28 -msgid "Site url" -msgstr "サイトURL" - -#: settings/serializers/basic.py:29 -msgid "eg: http://dev.jumpserver.org:8080" -msgstr "例えば: http://dev.jumpserver.org:8080" - -#: settings/serializers/basic.py:32 -msgid "User guide url" -msgstr "ユーザーガイドurl" - -#: settings/serializers/basic.py:33 -msgid "User first login update profile done redirect to it" -msgstr "ユーザーの最初のログイン更新プロファイルがリダイレクトされました" - -#: settings/serializers/basic.py:37 -msgid "" -"The forgot password url on login page, If you use ldap or cas external " -"authentication, you can set it" -msgstr "" -"ログインページでパスワードのURLを忘れてしまいました。ldapまたはcasの外部認証" -"を使用している場合は、設定できます。" - -#: settings/serializers/basic.py:41 -msgid "Global organization name" -msgstr "グローバル組織名" - -#: settings/serializers/basic.py:42 -msgid "The name of global organization to display" -msgstr "表示するグローバル組織の名前" - -#: settings/serializers/basic.py:44 -msgid "Enable announcement" -msgstr "アナウンスの有効化" - -#: settings/serializers/basic.py:45 -msgid "Announcement" -msgstr "発表" - -#: settings/serializers/basic.py:46 -msgid "Enable tickets" -msgstr "チケットを有効にする" - -#: settings/serializers/cleaning.py:10 -msgid "Login log keep days" -msgstr "ログインログは日数を保持します" - -#: settings/serializers/cleaning.py:10 settings/serializers/cleaning.py:14 -#: settings/serializers/cleaning.py:18 settings/serializers/cleaning.py:22 -#: settings/serializers/cleaning.py:26 settings/serializers/other.py:35 -msgid "Unit: day" -msgstr "単位: 日" - -#: settings/serializers/cleaning.py:14 -msgid "Task log keep days" -msgstr "タスクログは日数を保持します" - -#: settings/serializers/cleaning.py:18 -msgid "Operate log keep days" -msgstr "ログ管理日を操作する" - -#: settings/serializers/cleaning.py:22 -msgid "FTP log keep days" -msgstr "ダウンロードのアップロード" - -#: settings/serializers/cleaning.py:26 -msgid "Cloud sync record keep days" -msgstr "クラウド同期レコードは日数を保持します" - -#: settings/serializers/cleaning.py:29 -msgid "Session keep duration" -msgstr "セッション維持期間" - -#: settings/serializers/cleaning.py:30 -msgid "" -"Unit: days, Session, record, command will be delete if more than duration, " -"only in database" -msgstr "" -"単位:日。セッション、録画、コマンドレコードがそれを超えると削除されます(デー" -"タベースストレージにのみ影響します。ossなどは影響しません」影響を受ける)" - -#: settings/serializers/email.py:20 -msgid "SMTP host" -msgstr "SMTPホスト" - -#: settings/serializers/email.py:21 -msgid "SMTP port" -msgstr "SMTPポート" - -#: settings/serializers/email.py:22 -msgid "SMTP account" -msgstr "SMTPアカウント" - -#: settings/serializers/email.py:24 -msgid "SMTP password" -msgstr "SMTPパスワード" - -#: settings/serializers/email.py:25 -msgid "Tips: Some provider use token except password" -msgstr "ヒント: 一部のプロバイダーはパスワード以外のトークンを使用します" - -#: settings/serializers/email.py:28 -msgid "Send user" -msgstr "ユーザーを送信" - -#: settings/serializers/email.py:29 -msgid "Tips: Send mail account, default SMTP account as the send account" -msgstr "" -"ヒント: 送信メールアカウント、送信アカウントとしてのデフォルトのSMTPアカウン" -"ト" - -#: settings/serializers/email.py:32 -msgid "Test recipient" -msgstr "テスト受信者" - -#: settings/serializers/email.py:33 -msgid "Tips: Used only as a test mail recipient" -msgstr "ヒント: テストメールの受信者としてのみ使用" - -#: settings/serializers/email.py:37 -msgid "If SMTP port is 465, may be select" -msgstr "SMTPポートが465の場合は、" - -#: settings/serializers/email.py:40 -msgid "Use TLS" -msgstr "TLSの使用" - -#: settings/serializers/email.py:41 -msgid "If SMTP port is 587, may be select" -msgstr "SMTPポートが587の場合は、" - -#: settings/serializers/email.py:44 -msgid "Subject prefix" -msgstr "件名プレフィックス" - -#: settings/serializers/email.py:51 -msgid "Create user email subject" -msgstr "ユーザーメール件名の作成" - -#: settings/serializers/email.py:52 -msgid "" -"Tips: When creating a user, send the subject of the email (eg:Create account " -"successfully)" -msgstr "" -"ヒント: ユーザーを作成するときに、メールの件名を送信します (例: アカウントを" -"正常に作成)" - -#: settings/serializers/email.py:56 -msgid "Create user honorific" -msgstr "ユーザー敬語の作成" - -#: settings/serializers/email.py:57 -msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" -msgstr "" -"ヒント: ユーザーを作成するときは、メールの敬語を送信します (例: こんにちは)" - -#: settings/serializers/email.py:61 -msgid "Create user email content" -msgstr "ユーザーのメールコンテンツを作成する" - -#: settings/serializers/email.py:62 -#, python-brace-format -msgid "" -"Tips: When creating a user, send the content of the email, support " -"{username} {name} {email} label" -msgstr "" -"ヒント:ユーザーの作成時にパスワード設定メールの内容を送信し、{username}{name}" -"{email}ラベルをサポートします。" - -#: settings/serializers/email.py:66 -msgid "Tips: Email signature (eg:jumpserver)" -msgstr "ヒント: メール署名 (例: jumpserver)" - -#: settings/serializers/other.py:7 -msgid "Email suffix" -msgstr "メールのサフィックス" - -#: settings/serializers/other.py:8 -msgid "" -"This is used by default if no email is returned during SSO authentication" -msgstr "これは、SSO認証中にメールが返されない場合にデフォルトで使用されます。" - -#: settings/serializers/other.py:12 -msgid "OTP issuer name" -msgstr "OTP発行者名" - -#: settings/serializers/other.py:16 -msgid "OTP valid window" -msgstr "OTP有効なウィンドウ" - -#: settings/serializers/other.py:21 -msgid "CMD" -msgstr "CMD" - -#: settings/serializers/other.py:22 -msgid "PowerShell" -msgstr "PowerShell" - -#: settings/serializers/other.py:24 -msgid "Shell (Windows)" -msgstr "シェル (Windows)" - -#: settings/serializers/other.py:25 -msgid "The shell type used when Windows assets perform ansible tasks" -msgstr "" -"Windowsアセットが実行可能なタスクを実行するときに使用されるシェルタイプ" - -#: settings/serializers/other.py:29 -msgid "Perm ungroup node" -msgstr "グループ化されていないノードを表示" - -#: settings/serializers/other.py:30 -msgid "Perm single to ungroup node" -msgstr "" -"グループ化されていないノードに個別に許可された資産を配置し、資産が存在する" -"ノードが表示されないようにしますが、そのノードが許可されていないという質問に" -"質問" - -#: settings/serializers/other.py:35 -msgid "Ticket authorize default time" -msgstr "デフォルト製造オーダ承認時間" - -#: settings/serializers/other.py:39 -msgid "Help Docs URL" -msgstr "ドキュメントリンク" - -#: settings/serializers/other.py:40 -msgid "default: http://docs.jumpserver.org" -msgstr "デフォルト: http://docs.jumpserver.org" - -#: settings/serializers/other.py:44 -msgid "Help Support URL" -msgstr "サポートリンク" - -#: settings/serializers/other.py:45 -msgid "default: http://www.jumpserver.org/support/" -msgstr "デフォルト: http://www.jumpserver.org/support/" - -#: settings/serializers/security.py:10 -msgid "Password minimum length" -msgstr "パスワードの最小長" - -#: settings/serializers/security.py:14 -msgid "Admin user password minimum length" -msgstr "管理者ユーザーパスワードの最小長" - -#: settings/serializers/security.py:17 -msgid "Must contain capital" -msgstr "資本を含める必要があります" - -#: settings/serializers/security.py:20 -msgid "Must contain lowercase" -msgstr "小文字を含める必要があります。" - -#: settings/serializers/security.py:23 -msgid "Must contain numeric" -msgstr "数値を含める必要があります" - -#: settings/serializers/security.py:26 -msgid "Must contain special" -msgstr "特別な" - -#: settings/serializers/security.py:31 -msgid "" -"Unit: minute, If the user has failed to log in for a limited number of " -"times, no login is allowed during this time interval." -msgstr "" -"単位: 分。ユーザーが限られた回数だけログインできなかった場合、この時間間隔で" -"はログインはできません。" - -#: settings/serializers/security.py:40 -msgid "All users" -msgstr "すべてのユーザー" - -#: settings/serializers/security.py:41 -msgid "Only admin users" -msgstr "管理者のみ" - -#: settings/serializers/security.py:43 -msgid "Global MFA auth" -msgstr "グローバル有効化MFA認証" - -#: settings/serializers/security.py:47 -msgid "Third-party login users perform MFA authentication" -msgstr "サードパーティのログインユーザーがMFA認証を実行" - -#: settings/serializers/security.py:48 -msgid "The third-party login modes include OIDC, CAS, and SAML2" -msgstr "サードパーティのログインモードには、OIDC、CAS、SAML2" - -#: settings/serializers/security.py:52 -msgid "Limit the number of user login failures" -msgstr "ユーザーログインの失敗数を制限する" - -#: settings/serializers/security.py:56 -msgid "Block user login interval" -msgstr "ユーザーのログイン間隔をブロックする" - -#: settings/serializers/security.py:61 -msgid "Limit the number of IP login failures" -msgstr "IPログイン失敗の数を制限する" - -#: settings/serializers/security.py:65 -msgid "Block IP login interval" -msgstr "IPログイン間隔をブロックする" - -#: settings/serializers/security.py:69 -msgid "Login IP White List" -msgstr "ログインIPホワイトリスト" - -#: settings/serializers/security.py:74 -msgid "Login IP Black List" -msgstr "ログインIPブラックリスト" - -#: settings/serializers/security.py:80 -msgid "User password expiration" -msgstr "ユーザーパスワードの有効期限" - -#: settings/serializers/security.py:82 -msgid "" -"Unit: day, If the user does not update the password during the time, the " -"user password will expire failure;The password expiration reminder mail will " -"be automatic sent to the user by system within 5 days (daily) before the " -"password expires" -msgstr "" -"単位: 日。ユーザーがその期間中にパスワードを更新しなかった場合、ユーザーパス" -"ワードの有効期限が切れます。パスワードの有効期限が切れる前の5日 (毎日) 以内" -"に、パスワードの有効期限が切れるリマインダーメールがシステムからユーザーに自" -"動的に送信されます。" - -#: settings/serializers/security.py:89 -msgid "Number of repeated historical passwords" -msgstr "繰り返された履歴パスワードの数" - -#: settings/serializers/security.py:91 -msgid "" -"Tip: When the user resets the password, it cannot be the previous n " -"historical passwords of the user" -msgstr "" -"ヒント: ユーザーがパスワードをリセットすると、ユーザーの前のnの履歴パスワード" -"にすることはできません" - -#: settings/serializers/security.py:96 -msgid "Only single device login" -msgstr "単一デバイスログインのみ" - -#: settings/serializers/security.py:97 -msgid "Next device login, pre login will be logout" -msgstr "次のデバイスログイン、事前ログインはログアウトになります" - -#: settings/serializers/security.py:100 -msgid "Only exist user login" -msgstr "ユーザーログインのみ存在" - -#: settings/serializers/security.py:101 -msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" -msgstr "Enableの場合、ユーザーがまだ存在しない場合、CAS、OIDC authは失敗します" - -#: settings/serializers/security.py:104 -msgid "Only from source login" -msgstr "ソースログインからのみ" - -#: settings/serializers/security.py:105 -msgid "Only log in from the user source property" -msgstr "ユーザーソースのプロパティからのみログイン" - -#: settings/serializers/security.py:109 -msgid "MFA verify TTL" -msgstr "MFAはTTLを確認します" - -#: settings/serializers/security.py:111 -msgid "" -"Unit: second, The verification MFA takes effect only when you view the " -"account password" -msgstr "" -"単位: 2番目に、検証MFAはアカウントのパスワードを表示したときにのみ有効になり" -"ます。" - -#: settings/serializers/security.py:116 -msgid "Enable Login dynamic code" -msgstr "ログイン動的コードの有効化" - -#: settings/serializers/security.py:117 -msgid "" -"The password and additional code are sent to a third party authentication " -"system for verification" -msgstr "" -"パスワードと追加コードは、検証のためにサードパーティの認証システムに送信され" -"ます" - -#: settings/serializers/security.py:122 -msgid "MFA in login page" -msgstr "ログインページのMFA" - -#: settings/serializers/security.py:123 -msgid "Eu security regulations(GDPR) require MFA to be on the login page" -msgstr "" -"Euセキュリティ規制 (GDPR) では、MFAがログインページにある必要があります" - -#: settings/serializers/security.py:126 -msgid "Enable Login captcha" -msgstr "ログインcaptchaの有効化" - -#: settings/serializers/security.py:127 -msgid "Enable captcha to prevent robot authentication" -msgstr "Captchaを有効にしてロボット認証を防止する" - -#: settings/serializers/security.py:147 -msgid "Enable terminal register" -msgstr "ターミナルレジスタの有効化" - -#: settings/serializers/security.py:149 -msgid "" -"Allow terminal register, after all terminal setup, you should disable this " -"for security" -msgstr "" -"ターミナルレジスタを許可し、すべてのターミナルセットアップの後、セキュリティ" -"のためにこれを無効にする必要があります" - -#: settings/serializers/security.py:153 -msgid "Enable watermark" -msgstr "透かしの有効化" - -#: settings/serializers/security.py:154 -msgid "Enabled, the web session and replay contains watermark information" -msgstr "Webセッションとリプレイには透かし情報が含まれています。" - -#: settings/serializers/security.py:158 -msgid "Connection max idle time" -msgstr "接続最大アイドル時間" - -#: settings/serializers/security.py:159 -msgid "If idle time more than it, disconnect connection Unit: minute" -msgstr "アイドル時間がそれ以上の場合は、接続単位を切断します: 分" - -#: settings/serializers/security.py:162 -msgid "Remember manual auth" -msgstr "手動入力パスワードの保存" - -#: settings/serializers/security.py:165 -msgid "Enable change auth secure mode" -msgstr "安全モードの変更を有効にする" - -#: settings/serializers/security.py:168 -msgid "Insecure command alert" -msgstr "安全でないコマンドアラート" - -#: settings/serializers/security.py:171 -msgid "Email recipient" -msgstr "メール受信者" - -#: settings/serializers/security.py:172 -msgid "Multiple user using , split" -msgstr "複数のユーザーを使用して、分割" - -#: settings/serializers/security.py:175 -msgid "Batch command execution" -msgstr "バッチコマンドの実行" - -#: settings/serializers/security.py:176 -msgid "Allow user run batch command or not using ansible" -msgstr "ユーザー実行バッチコマンドを許可するか、ansibleを使用しない" - -#: settings/serializers/security.py:179 -msgid "Session share" -msgstr "セッション共有" - -#: settings/serializers/security.py:180 -msgid "Enabled, Allows user active session to be shared with other users" -msgstr "" -"ユーザーのアクティブなセッションを他のユーザーと共有できるようにします。" - -#: settings/serializers/security.py:183 -msgid "Remote Login Protection" -msgstr "リモートログイン保護" - -#: settings/serializers/security.py:185 -msgid "" -"The system determines whether the login IP address belongs to a common login " -"city. If the account is logged in from a common login city, the system sends " -"a remote login reminder" -msgstr "" -"システムは、ログインIPアドレスが共通のログイン都市に属しているかどうかを判断" -"します。アカウントが共通のログイン都市からログインしている場合、システムはリ" -"モートログインリマインダーを送信します" - -#: settings/serializers/terminal.py:13 -msgid "Auto" -msgstr "自動" - -#: settings/serializers/terminal.py:19 -msgid "Password auth" -msgstr "パスワード認証" - -#: settings/serializers/terminal.py:21 -msgid "Public key auth" -msgstr "鍵認証" - -#: settings/serializers/terminal.py:22 -msgid "" -"Tips: If use other auth method, like AD/LDAP, you should disable this to " -"avoid being able to log in after deleting" -msgstr "" -"ヒント: AD/LDAPなどの他の認証方法を使用する場合は、サードパーティ製システムの" -"削除後にこの項目を無効にする必要があります, ログインも可能" - -#: settings/serializers/terminal.py:26 -msgid "List sort by" -msgstr "リストの並べ替え" - -#: settings/serializers/terminal.py:29 -msgid "List page size" -msgstr "ページサイズを一覧表示" - -#: settings/serializers/terminal.py:32 -msgid "Telnet login regex" -msgstr "Telnetログインregex" - -#: settings/serializers/terminal.py:33 -msgid "" -"Tips: The login success message varies with devices. if you cannot log in to " -"the device through Telnet, set this parameter" -msgstr "" -"ヒント: ログイン成功メッセージはデバイスによって異なります。Telnet経由でデバ" -"イスにログインできない場合は、このパラメーターを設定します。" - -#: settings/serializers/terminal.py:36 -msgid "Enable database proxy" -msgstr "属性マップの有効化" - -#: settings/serializers/terminal.py:37 -msgid "Enable Razor" -msgstr "Razor の有効化" - -#: settings/serializers/terminal.py:38 -msgid "Enable SSH Client" -msgstr "SSH Clientの有効化" - -#: settings/utils/ldap.py:467 -msgid "ldap:// or ldaps:// protocol is used." -msgstr "ldap:// または ldaps:// プロトコルが使用されます。" - -#: settings/utils/ldap.py:478 -msgid "Host or port is disconnected: {}" -msgstr "ホストまたはポートが切断されました: {}" - -#: settings/utils/ldap.py:480 -msgid "The port is not the port of the LDAP service: {}" -msgstr "ポートはLDAPサービスのポートではありません: {}" - -#: settings/utils/ldap.py:482 -msgid "Please add certificate: {}" -msgstr "証明書を追加してください: {}" - -#: settings/utils/ldap.py:486 settings/utils/ldap.py:513 -#: settings/utils/ldap.py:543 settings/utils/ldap.py:571 -msgid "Unknown error: {}" -msgstr "不明なエラー: {}" - -#: settings/utils/ldap.py:500 -msgid "Bind DN or Password incorrect" -msgstr "DNまたはパスワードのバインドが正しくありません" - -#: settings/utils/ldap.py:507 -msgid "Please enter Bind DN: {}" -msgstr "バインドDN: {} を入力してください" - -#: settings/utils/ldap.py:509 -msgid "Please enter Password: {}" -msgstr "パスワードを入力してください: {}" - -#: settings/utils/ldap.py:511 -msgid "Please enter correct Bind DN and Password: {}" -msgstr "正しいバインドDNとパスワードを入力してください: {}" - -#: settings/utils/ldap.py:529 -msgid "Invalid User OU or User search filter: {}" -msgstr "無効なユーザー OU またはユーザー検索フィルター: {}" - -#: settings/utils/ldap.py:560 -msgid "LDAP User attr map not include: {}" -msgstr "LDAP ユーザーattrマップは含まれません: {}" - -#: settings/utils/ldap.py:567 -msgid "LDAP User attr map is not dict" -msgstr "LDAPユーザーattrマップはdictではありません" - -#: settings/utils/ldap.py:586 -msgid "LDAP authentication is not enabled" -msgstr "LDAP 認証が有効になっていない" - -#: settings/utils/ldap.py:604 -msgid "Error (Invalid LDAP server): {}" -msgstr "エラー (LDAPサーバーが無効): {}" - -#: settings/utils/ldap.py:606 -msgid "Error (Invalid Bind DN): {}" -msgstr "エラー (DNのバインドが無効): {}" - -#: settings/utils/ldap.py:608 -msgid "Error (Invalid LDAP User attr map): {}" -msgstr "エラー (LDAPユーザーattrマップが無効): {}" - -#: settings/utils/ldap.py:610 -msgid "Error (Invalid User OU or User search filter): {}" -msgstr "エラー (ユーザーOUまたはユーザー検索フィルターが無効): {}" - -#: settings/utils/ldap.py:612 -msgid "Error (Not enabled LDAP authentication): {}" -msgstr "エラー (LDAP認証が有効化されていません): {}" - -#: settings/utils/ldap.py:614 -msgid "Error (Unknown): {}" -msgstr "エラー (不明): {}" - -#: settings/utils/ldap.py:617 -msgid "Succeed: Match {} s user" -msgstr "成功: {} 人のユーザーに一致" - -#: settings/utils/ldap.py:650 -msgid "Authentication failed (configuration incorrect): {}" -msgstr "認証に失敗しました (設定が正しくありません): {}" - -#: settings/utils/ldap.py:654 -msgid "Authentication failed (username or password incorrect): {}" -msgstr "認証に失敗しました (ユーザー名またはパスワードが正しくありません): {}" - -#: settings/utils/ldap.py:656 -msgid "Authentication failed (Unknown): {}" -msgstr "認証に失敗しました (不明): {}" - -#: settings/utils/ldap.py:659 -msgid "Authentication success: {}" -msgstr "認証成功: {}" - -#: templates/_csv_import_export.html:8 -msgid "Export" -msgstr "エクスポート" - -#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 -msgid "Import" -msgstr "インポート" - -#: templates/_csv_import_modal.html:12 -msgid "Download the imported template or use the exported CSV file format" -msgstr "" -"インポートしたテンプレートをダウンロードするか、エクスポートしたCSVファイル形" -"式を使用する" - -#: templates/_csv_import_modal.html:13 -msgid "Download the import template" -msgstr "インポートテンプレートのダウンロード" - -#: templates/_csv_import_modal.html:17 templates/_csv_update_modal.html:17 -msgid "Select the CSV file to import" -msgstr "インポートするCSVファイルの選択" - -#: templates/_csv_import_modal.html:39 templates/_csv_update_modal.html:42 -msgid "Please select file" -msgstr "ファイルを選択してください" - -#: templates/_csv_update_modal.html:12 -msgid "Download the update template or use the exported CSV file format" -msgstr "" -"更新テンプレートをダウンロードするか、エクスポートしたCSVファイル形式を使用す" -"る" - -#: templates/_csv_update_modal.html:13 -msgid "Download the update template" -msgstr "更新テンプレートのダウンロード" - -#: templates/_header_bar.html:12 -msgid "Help" -msgstr "ヘルプ" - -#: templates/_header_bar.html:19 -msgid "Docs" -msgstr "ドキュメント" - -#: templates/_header_bar.html:25 -msgid "Commercial support" -msgstr "商用サポート" - -#: templates/_header_bar.html:76 users/forms/profile.py:44 -msgid "Profile" -msgstr "プロフィール" - -#: templates/_header_bar.html:79 -msgid "Admin page" -msgstr "ページの管理" - -#: templates/_header_bar.html:81 -msgid "User page" -msgstr "ユーザーページ" - -#: templates/_header_bar.html:84 -msgid "API Key" -msgstr "API Key" - -#: templates/_header_bar.html:85 -msgid "Logout" -msgstr "ログアウト" - -#: templates/_message.html:6 -msgid "" -"\n" -" Your account has expired, please contact the administrator.\n" -" " -msgstr "" -"\n" -" アカウントが期限切れになったので、管理者に連絡してくださ" -"い。 " - -#: templates/_message.html:13 -msgid "Your account will at" -msgstr "あなたのアカウントは" - -#: templates/_message.html:13 templates/_message.html:30 -msgid "expired. " -msgstr "期限切れです。" - -#: templates/_message.html:23 -#, python-format -msgid "" -"\n" -" Your password has expired, please click this link update password.\n" -" " -msgstr "" -"\n" -" パスワードが期限切れになりましたので、クリックしてください " -" リンク パスワードの更新\n" -" " - -#: templates/_message.html:30 -msgid "Your password will at" -msgstr "あなたのパスワードは" - -#: templates/_message.html:31 -#, python-format -msgid "" -"\n" -" please click this " -"link to update your password.\n" -" " -msgstr "" -"\n" -" クリックしてください リンク パスワードの更新\n" -" " - -#: templates/_message.html:43 -#, python-format -msgid "" -"\n" -" Your information was incomplete. Please click this link to complete your information.\n" -" " -msgstr "" -"\n" -" あなたの情報が不完全なので、クリックしてください。 リンク 補完\n" -" " - -#: templates/_message.html:56 -#, python-format -msgid "" -"\n" -" Your ssh public key not set or expired. Please click this link to update\n" -" " -msgstr "" -"\n" -" SSHキーが設定されていないか無効になっている場合は、 リンク 更新\n" -" " - -#: templates/_mfa_login_field.html:28 -msgid "Send verification code" -msgstr "確認コードを送信" - -#: templates/_mfa_login_field.html:106 -msgid "Wait: " -msgstr "待つ:" - -#: templates/_mfa_login_field.html:116 -msgid "The verification code has been sent" -msgstr "確認コードが送信されました" - -#: templates/_without_nav_base.html:26 -msgid "Home page" -msgstr "ホームページ" - -#: templates/resource_download.html:18 templates/resource_download.html:31 -msgid "Client" -msgstr "クライアント" - -#: templates/resource_download.html:20 -msgid "" -"JumpServer Client, currently used to launch the client, now only support " -"launch RDP SSH client, The Telnet client will next" -msgstr "" -"JumpServerクライアントは、現在特定のクライアントプログラムの接続資産を喚起す" -"るために使用されており、現在はRDP SSHクライアントのみをサポートしています。" -"「Telnetは将来的にサポートする" - -#: templates/resource_download.html:31 -msgid "Microsoft" -msgstr "マイクロソフト" - -#: templates/resource_download.html:31 -msgid "Official" -msgstr "公式" - -#: templates/resource_download.html:33 -msgid "" -"macOS needs to download the client to connect RDP asset, which comes with " -"Windows" -msgstr "" -"MacOSは、Windowsに付属のRDPアセットを接続するためにクライアントをダウンロード" -"する必要があります" - -#: templates/resource_download.html:42 -msgid "Windows Remote application publisher tools" -msgstr "Windowsリモートアプリケーション発行者ツール" - -#: templates/resource_download.html:43 -msgid "" -"Jmservisor is the program used to pull up remote applications in Windows " -"Remote Application publisher" -msgstr "" -"Jmservisorはwindowsリモートアプリケーションパブリケーションサーバでリモートア" -"プリケーションを引き出すためのプログラムです" - -#: templates/resource_download.html:51 -msgid "Offline video player" -msgstr "オフラインビデオプレーヤー" - -#: terminal/api/component/endpoint.py:31 -msgid "Not found protocol query params" -msgstr "プロトコルクエリパラメータが見つかりません" - -#: terminal/api/component/storage.py:28 -msgid "Deleting the default storage is not allowed" -msgstr "デフォルトのストレージの削除は許可されていません" - -#: terminal/api/component/storage.py:31 -msgid "Cannot delete storage that is being used" -msgstr "使用中のストレージを削除できません" - -#: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 -msgid "Command storages" -msgstr "コマンドストア" - -#: terminal/api/component/storage.py:79 -msgid "Invalid" -msgstr "無効" - -#: terminal/api/component/storage.py:119 -msgid "Test failure: {}" -msgstr "テスト失敗: {}" - -#: terminal/api/component/storage.py:122 -msgid "Test successful" -msgstr "テスト成功" - -#: terminal/api/component/storage.py:124 -msgid "Test failure: Account invalid" -msgstr "テスト失敗: アカウントが無効" - -#: terminal/api/component/terminal.py:38 -msgid "Have online sessions" -msgstr "オンラインセッションを持つ" - -#: terminal/api/session/session.py:217 -msgid "Session does not exist: {}" -msgstr "セッションが存在しません: {}" - -#: terminal/api/session/session.py:220 -msgid "Session is finished or the protocol not supported" -msgstr "セッションが終了したか、プロトコルがサポートされていません" - -#: terminal/api/session/session.py:225 -msgid "User does not exist: {}" -msgstr "ユーザーが存在しない: {}" - -#: terminal/api/session/session.py:233 -msgid "User does not have permission" -msgstr "ユーザーに権限がありません" - -#: terminal/api/session/sharing.py:29 -msgid "Secure session sharing settings is disabled" -msgstr "安全なセッション共有設定が無効になっています" - -#: terminal/apps.py:9 -msgid "Terminals" -msgstr "ターミナル管理" - -#: terminal/backends/command/es.py:28 -msgid "Invalid elasticsearch config" -msgstr "無効なElasticsearch構成" - -#: terminal/backends/command/es.py:33 -msgid "Not Support Elasticsearch8" -msgstr "サポートされていません Elasticsearch8" - -#: terminal/backends/command/models.py:16 -msgid "Ordinary" -msgstr "普通" - -#: terminal/backends/command/models.py:17 -msgid "Dangerous" -msgstr "危険" - -#: terminal/backends/command/models.py:23 -msgid "Input" -msgstr "入力" - -#: terminal/backends/command/models.py:24 -#: terminal/backends/command/serializers.py:37 -msgid "Output" -msgstr "出力" - -#: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 -#: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 -#: terminal/templates/terminal/_msg_command_alert.html:10 -#: tickets/models/ticket/command_confirm.py:17 -msgid "Session" -msgstr "セッション" - -#: terminal/backends/command/models.py:26 -#: terminal/backends/command/serializers.py:18 -msgid "Risk level" -msgstr "リスクレベル" - -#: terminal/backends/command/serializers.py:16 -msgid "Session ID" -msgstr "セッションID" - -#: terminal/backends/command/serializers.py:38 -msgid "Risk level display" -msgstr "リスクレベル表示" - -#: terminal/backends/command/serializers.py:39 -msgid "Timestamp" -msgstr "タイムスタンプ" - -#: terminal/backends/command/serializers.py:41 -#: terminal/models/component/terminal.py:84 -msgid "Remote Address" -msgstr "リモートアドレス" - -#: terminal/const.py:37 -msgid "Critical" -msgstr "クリティカル" - -#: terminal/const.py:38 -msgid "High" -msgstr "高い" - -#: terminal/const.py:39 users/templates/users/reset_password.html:50 -msgid "Normal" -msgstr "正常" - -#: terminal/const.py:40 -msgid "Offline" -msgstr "オフライン" - -#: terminal/const.py:80 terminal/const.py:81 terminal/const.py:82 -#: terminal/const.py:83 terminal/const.py:84 -#, fuzzy -#| msgid "Client" -msgid "DB Client" -msgstr "クライアント" - -#: terminal/exceptions.py:8 -msgid "Bulk create not support" -msgstr "一括作成非サポート" - -#: terminal/exceptions.py:13 -msgid "Storage is invalid" -msgstr "ストレージが無効です" - -#: terminal/models/applet/applet.py:23 -#, fuzzy -#| msgid "Auth url" -msgid "Author" -msgstr "認証アドレス" - -#: terminal/models/applet/applet.py:27 -msgid "Tags" -msgstr "" - -#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 -msgid "Hosts" -msgstr "ホスト" - -#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 -#, fuzzy -#| msgid "Apply assets" -msgid "Applet" -msgstr "資産の適用" - -#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 -#, fuzzy -#| msgid "More login options" -msgid "Deploy options" -msgstr "その他のログインオプション" - -#: terminal/models/applet/host.py:19 -msgid "Inited" -msgstr "" - -#: terminal/models/applet/host.py:20 -#, fuzzy -#| msgid "Date finished" -msgid "Date inited" -msgstr "終了日" - -#: terminal/models/applet/host.py:21 -#, fuzzy -#| msgid "Date sync" -msgid "Date synced" -msgstr "日付の同期" - -#: terminal/models/applet/host.py:102 -#, fuzzy -#| msgid "Host" -msgid "Hosting" -msgstr "ホスト" - -#: terminal/models/applet/host.py:103 -msgid "Initial" -msgstr "" - -#: terminal/models/component/endpoint.py:14 -msgid "HTTPS Port" -msgstr "HTTPS ポート" - -#: terminal/models/component/endpoint.py:15 -msgid "HTTP Port" -msgstr "HTTP ポート" - -#: terminal/models/component/endpoint.py:16 -msgid "SSH Port" -msgstr "SSH ポート" - -#: terminal/models/component/endpoint.py:17 -msgid "RDP Port" -msgstr "RDP ポート" - -#: terminal/models/component/endpoint.py:18 -msgid "MySQL Port" -msgstr "MySQL ポート" - -#: terminal/models/component/endpoint.py:19 -msgid "MariaDB Port" -msgstr "MariaDB ポート" - -#: terminal/models/component/endpoint.py:20 -msgid "PostgreSQL Port" -msgstr "PostgreSQL ポート" - -#: terminal/models/component/endpoint.py:21 -msgid "Redis Port" -msgstr "Redis ポート" - -#: terminal/models/component/endpoint.py:22 -msgid "Oracle 11g Port" -msgstr "Oracle 11g ポート" - -#: terminal/models/component/endpoint.py:23 -msgid "Oracle 12c Port" -msgstr "Oracle 12c ポート" - -#: terminal/models/component/endpoint.py:29 -#: terminal/models/component/endpoint.py:95 terminal/serializers/endpoint.py:57 -#: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50 -#: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 -#: terminal/serializers/storage.py:98 -msgid "Endpoint" -msgstr "エンドポイント" - -#: terminal/models/component/endpoint.py:88 -msgid "IP group" -msgstr "IP グループ" - -#: terminal/models/component/endpoint.py:100 -msgid "Endpoint rule" -msgstr "エンドポイントルール" - -#: terminal/models/component/status.py:14 -msgid "Session Online" -msgstr "セッションオンライン" - -#: terminal/models/component/status.py:15 -msgid "CPU Load" -msgstr "CPUロード" - -#: terminal/models/component/status.py:16 -msgid "Memory Used" -msgstr "使用メモリ" - -#: terminal/models/component/status.py:17 -msgid "Disk Used" -msgstr "使用済みディスク" - -#: terminal/models/component/status.py:18 -msgid "Connections" -msgstr "接続" - -#: terminal/models/component/status.py:19 -msgid "Threads" -msgstr "スレッド" - -#: terminal/models/component/status.py:20 -msgid "Boot Time" -msgstr "ブート時間" - -#: terminal/models/component/storage.py:27 -msgid "Default storage" -msgstr "デフォルトのストレージ" - -#: terminal/models/component/storage.py:136 -#: terminal/models/component/terminal.py:85 -msgid "Command storage" -msgstr "コマンドストレージ" - -#: terminal/models/component/storage.py:196 -#: terminal/models/component/terminal.py:86 -msgid "Replay storage" -msgstr "再生ストレージ" - -#: terminal/models/component/terminal.py:82 -msgid "type" -msgstr "タイプ" - -#: terminal/models/component/terminal.py:159 -msgid "Can view terminal config" -msgstr "ターミナル構成を表示できます" - -#: terminal/models/session/command.py:66 -msgid "Command record" -msgstr "コマンドレコード" - -#: terminal/models/session/replay.py:12 -msgid "Session replay" -msgstr "セッション再生" - -#: terminal/models/session/replay.py:14 -msgid "Can upload session replay" -msgstr "セッションのリプレイをアップロードできます" - -#: terminal/models/session/replay.py:15 -msgid "Can download session replay" -msgstr "セッション再生をダウンロードできます" - -#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 -msgid "Login from" -msgstr "ログイン元" - -#: terminal/models/session/session.py:40 -msgid "Replay" -msgstr "リプレイ" - -#: terminal/models/session/session.py:44 -msgid "Date end" -msgstr "終了日" - -#: terminal/models/session/session.py:236 -msgid "Session record" -msgstr "セッション記録" - -#: terminal/models/session/session.py:238 -msgid "Can monitor session" -msgstr "セッションを監視できます" - -#: terminal/models/session/session.py:239 -msgid "Can share session" -msgstr "セッションを共有できます" - -#: terminal/models/session/session.py:240 -msgid "Can terminate session" -msgstr "セッションを終了できます" - -#: terminal/models/session/session.py:241 -msgid "Can validate session action perm" -msgstr "セッションアクションのパーマを検証できます" - -#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 -msgid "Verify code" -msgstr "コードの確認" - -#: terminal/models/session/sharing.py:31 -msgid "Expired time (min)" -msgstr "期限切れ時間 (分)" - -#: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 -msgid "Session sharing" -msgstr "セッション共有" - -#: terminal/models/session/sharing.py:39 -msgid "Can add super session sharing" -msgstr "スーパーセッション共有を追加できます" - -#: terminal/models/session/sharing.py:66 -msgid "Link not active" -msgstr "リンクがアクティブでない" - -#: terminal/models/session/sharing.py:68 -msgid "Link expired" -msgstr "リンク期限切れ" - -#: terminal/models/session/sharing.py:70 -msgid "User not allowed to join" -msgstr "ユーザーはセッションに参加できません" - -#: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 -msgid "Joiner" -msgstr "ジョイナー" - -#: terminal/models/session/sharing.py:90 -msgid "Date joined" -msgstr "参加日" - -#: terminal/models/session/sharing.py:93 -msgid "Date left" -msgstr "日付が残っています" - -#: terminal/models/session/sharing.py:116 -msgid "Session join record" -msgstr "セッション参加記録" - -#: terminal/models/session/sharing.py:132 -msgid "Invalid verification code" -msgstr "検証コードが無効" - -#: terminal/notifications.py:22 -msgid "Sessions" -msgstr "セッション" - -#: terminal/notifications.py:68 -msgid "Danger command alert" -msgstr "危険コマンドアラート" - -#: terminal/notifications.py:92 terminal/notifications.py:140 -msgid "Level" -msgstr "レベル" - -#: terminal/notifications.py:110 -msgid "Batch danger command alert" -msgstr "一括危険コマンド警告" - -#: terminal/serializers/applet.py:16 -#, fuzzy -#| msgid "Public key" -msgid "Published" -msgstr "公開キー" - -#: terminal/serializers/applet.py:17 -#, fuzzy -#| msgid "Finished" -msgid "Unpublished" -msgstr "終了" - -#: terminal/serializers/applet.py:18 -#, fuzzy -#| msgid "Phone not set" -msgid "Not match" -msgstr "電話が設定されていない" - -#: terminal/serializers/applet.py:32 -msgid "Icon" -msgstr "" - -#: terminal/serializers/applet_host.py:21 -#, fuzzy -#| msgid "Session" -msgid "Per Session" -msgstr "セッション" - -#: terminal/serializers/applet_host.py:22 -msgid "Per Device" -msgstr "" - -#: terminal/serializers/applet_host.py:28 -#, fuzzy -#| msgid "License" -msgid "RDS Licensing" -msgstr "ライセンス" - -#: terminal/serializers/applet_host.py:29 -msgid "RDS License Server" -msgstr "" - -#: terminal/serializers/applet_host.py:30 -msgid "RDS Licensing Mode" -msgstr "" - -#: terminal/serializers/applet_host.py:32 -msgid "RDS fSingleSessionPerUser" -msgstr "" - -#: terminal/serializers/applet_host.py:33 -msgid "RDS Max Disconnection Time" -msgstr "" - -#: terminal/serializers/applet_host.py:34 -msgid "RDS Remote App Logoff Time Limit" -msgstr "" - -#: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 -msgid "Load status" -msgstr "ロードステータス" - -#: terminal/serializers/endpoint.py:12 -msgid "Oracle port" -msgstr "" - -#: terminal/serializers/endpoint.py:51 -msgid "" -"If asset IP addresses under different endpoints conflict, use asset labels" -msgstr "" -"異なるエンドポイントの下に競合するアセットIPがある場合は、アセットタグを使用" -"して実装します" - -#: terminal/serializers/session.py:17 terminal/serializers/session.py:42 -msgid "Terminal display" -msgstr "ターミナルディスプレイ" - -#: terminal/serializers/session.py:33 -msgid "User ID" -msgstr "ユーザーID" - -#: terminal/serializers/session.py:34 -msgid "Asset ID" -msgstr "資産ID" - -#: terminal/serializers/session.py:35 -msgid "Login from display" -msgstr "表示からのログイン" - -#: terminal/serializers/session.py:37 -msgid "Can replay" -msgstr "再生できます" - -#: terminal/serializers/session.py:38 -msgid "Can join" -msgstr "参加できます" - -#: terminal/serializers/session.py:39 -msgid "Terminal ID" -msgstr "ターミナル ID" - -#: terminal/serializers/session.py:40 -msgid "Is finished" -msgstr "終了しました" - -#: terminal/serializers/session.py:41 -msgid "Can terminate" -msgstr "終了できます" - -#: terminal/serializers/session.py:47 -msgid "Command amount" -msgstr "コマンド量" - -#: terminal/serializers/storage.py:20 -msgid "Endpoint invalid: remove path `{}`" -msgstr "エンドポイントが無効: パス '{}' を削除" - -#: terminal/serializers/storage.py:26 -msgid "Bucket" -msgstr "バケット" - -#: terminal/serializers/storage.py:30 -#: xpack/plugins/cloud/serializers/account_attrs.py:17 -msgid "Access key id" -msgstr "アクセスキー" - -#: terminal/serializers/storage.py:34 -#: xpack/plugins/cloud/serializers/account_attrs.py:20 -msgid "Access key secret" -msgstr "アクセスキーシークレット" - -#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:216 -msgid "Region" -msgstr "リージョン" - -#: terminal/serializers/storage.py:109 -msgid "Container name" -msgstr "コンテナー名" - -#: terminal/serializers/storage.py:112 -msgid "Account key" -msgstr "アカウントキー" - -#: terminal/serializers/storage.py:115 -msgid "Endpoint suffix" -msgstr "エンドポイントサフィックス" - -#: terminal/serializers/storage.py:135 -msgid "The address format is incorrect" -msgstr "アドレス形式が正しくありません" - -#: terminal/serializers/storage.py:142 -msgid "Host invalid" -msgstr "ホスト無効" - -#: terminal/serializers/storage.py:145 -msgid "Port invalid" -msgstr "ポートが無効" - -#: terminal/serializers/storage.py:160 -msgid "Index by date" -msgstr "日付による索引付け" - -#: terminal/serializers/storage.py:161 -msgid "Whether to create an index by date" -msgstr "現在の日付に基づいてインデックスを動的に作成するかどうか" - -#: terminal/serializers/storage.py:164 -msgid "Index" -msgstr "インデックス" - -#: terminal/serializers/storage.py:166 -msgid "Doc type" -msgstr "Docタイプ" - -#: terminal/serializers/storage.py:168 -msgid "Ignore Certificate Verification" -msgstr "証明書の検証を無視する" - -#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 -msgid "Not found" -msgstr "見つかりません" - -#: terminal/templates/terminal/_msg_command_alert.html:10 -msgid "view" -msgstr "表示" - -#: tickets/apps.py:7 -msgid "Tickets" -msgstr "チケット" - -#: tickets/const.py:9 -msgid "Apply for asset" -msgstr "資産の申請" - -#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 -msgid "Open" -msgstr "オープン" - -#: tickets/const.py:18 tickets/const.py:31 -msgid "Reopen" -msgstr "" - -#: tickets/const.py:19 tickets/const.py:32 -msgid "Approved" -msgstr "承認済み" - -#: tickets/const.py:20 tickets/const.py:33 -msgid "Rejected" -msgstr "拒否" - -#: tickets/const.py:30 tickets/const.py:38 -msgid "Closed" -msgstr "クローズ" - -#: tickets/const.py:46 -msgid "Approve" -msgstr "承認" - -#: tickets/const.py:50 -msgid "One level" -msgstr "1つのレベル" - -#: tickets/const.py:51 -msgid "Two level" -msgstr "2つのレベル" - -#: tickets/const.py:55 -msgid "Org admin" -msgstr "Org admin" - -#: tickets/const.py:56 -msgid "Custom user" -msgstr "カスタムユーザー" - -#: tickets/const.py:57 -msgid "Super admin" -msgstr "スーパー管理者" - -#: tickets/const.py:58 -msgid "Super admin and org admin" -msgstr "スーパーadminとorg admin" - -#: tickets/errors.py:9 -msgid "Ticket already closed" -msgstr "チケットはすでに閉じています" - -#: tickets/handlers/apply_asset.py:36 -msgid "" -"Created by the ticket ticket title: {} ticket applicant: {} ticket " -"processor: {} ticket ID: {}" -msgstr "" -"チケットのタイトル: {} チケット申請者: {} チケットプロセッサ: {} チケットID: " -"{}" - -#: tickets/handlers/base.py:84 -msgid "Change field" -msgstr "フィールドを変更" - -#: tickets/handlers/base.py:84 -msgid "Before change" -msgstr "変更前" - -#: tickets/handlers/base.py:84 -msgid "After change" -msgstr "変更後" - -#: tickets/handlers/base.py:96 -msgid "{} {} the ticket" -msgstr "{} {} チケット" - -#: tickets/models/comment.py:14 -msgid "common" -msgstr "" - -#: tickets/models/comment.py:23 -msgid "User display name" -msgstr "ユーザー表示名" - -#: tickets/models/comment.py:24 -msgid "Body" -msgstr "ボディ" - -#: tickets/models/flow.py:20 tickets/models/flow.py:62 -#: tickets/models/ticket/general.py:39 -msgid "Approve level" -msgstr "レベルを承認する" - -#: tickets/models/flow.py:25 tickets/serializers/flow.py:18 -msgid "Approve strategy" -msgstr "戦略を承認する" - -#: tickets/models/flow.py:30 tickets/serializers/flow.py:20 -msgid "Assignees" -msgstr "アシニーズ" - -#: tickets/models/flow.py:34 -msgid "Ticket flow approval rule" -msgstr "チケットフロー承認ルール" - -#: tickets/models/flow.py:67 -msgid "Ticket flow" -msgstr "チケットの流れ" - -#: tickets/models/relation.py:10 -msgid "Ticket session relation" -msgstr "チケットセッションの関係" - -#: tickets/models/ticket/apply_application.py:10 -#: tickets/models/ticket/apply_asset.py:13 -msgid "Permission name" -msgstr "認可ルール名" - -#: tickets/models/ticket/apply_application.py:19 -msgid "Apply applications" -msgstr "アプリケーションの適用" - -#: tickets/models/ticket/apply_application.py:22 -msgid "Apply system users" -msgstr "システムユーザーの適用" - -#: tickets/models/ticket/apply_asset.py:9 -#: tickets/serializers/ticket/apply_asset.py:14 -msgid "Select at least one asset or node" -msgstr "少なくとも1つのアセットまたはノードを選択します。" - -#: tickets/models/ticket/apply_asset.py:14 -#: tickets/serializers/ticket/apply_asset.py:19 -msgid "Apply nodes" -msgstr "ノードの適用" - -#: tickets/models/ticket/apply_asset.py:16 -#: tickets/serializers/ticket/apply_asset.py:18 -msgid "Apply assets" -msgstr "資産の適用" - -#: tickets/models/ticket/apply_asset.py:17 -#, fuzzy -#| msgid "Application account" -msgid "Apply accounts" -msgstr "アプリケーションアカウント" - -#: tickets/models/ticket/command_confirm.py:10 -msgid "Run user" -msgstr "ユーザーの実行" - -#: tickets/models/ticket/command_confirm.py:12 -msgid "Run asset" -msgstr "アセットの実行" - -#: tickets/models/ticket/command_confirm.py:13 -msgid "Run command" -msgstr "実行コマンド" - -#: tickets/models/ticket/command_confirm.py:14 -#, fuzzy -#| msgid "account" -msgid "Run account" -msgstr "アカウント" - -#: tickets/models/ticket/command_confirm.py:21 -msgid "From cmd filter" -msgstr "コマンドフィルタ規則から" - -#: tickets/models/ticket/command_confirm.py:25 -msgid "From cmd filter rule" -msgstr "コマンドフィルタ規則から" - -#: tickets/models/ticket/general.py:74 -msgid "Ticket step" -msgstr "チケットステップ" - -#: tickets/models/ticket/general.py:92 -msgid "Ticket assignee" -msgstr "割り当てられたチケット" - -#: tickets/models/ticket/general.py:271 -msgid "Title" -msgstr "タイトル" - -#: tickets/models/ticket/general.py:287 -msgid "Applicant" -msgstr "応募者" - -#: tickets/models/ticket/general.py:291 -msgid "TicketFlow" -msgstr "作業指示プロセス" - -#: tickets/models/ticket/general.py:294 -msgid "Approval step" -msgstr "承認ステップ" - -#: tickets/models/ticket/general.py:297 -msgid "Relation snapshot" -msgstr "製造オーダスナップショット" - -#: tickets/models/ticket/general.py:391 -msgid "Please try again" -msgstr "もう一度お試しください" - -#: tickets/models/ticket/general.py:424 -msgid "Super ticket" -msgstr "スーパーチケット" - -#: tickets/models/ticket/login_asset_confirm.py:11 -msgid "Login user" -msgstr "ログインユーザー" - -#: tickets/models/ticket/login_asset_confirm.py:14 -msgid "Login asset" -msgstr "ログイン資産" - -#: tickets/models/ticket/login_asset_confirm.py:17 -#, fuzzy -#| msgid "Login acl" -msgid "Login account" -msgstr "ログインacl" - -#: tickets/models/ticket/login_confirm.py:12 -msgid "Login datetime" -msgstr "ログイン日時" - -#: tickets/notifications.py:63 -msgid "Ticket basic info" -msgstr "チケット基本情報" - -#: tickets/notifications.py:64 -msgid "Ticket applied info" -msgstr "チケット適用情報" - -#: tickets/notifications.py:109 -msgid "Your has a new ticket, applicant - {}" -msgstr "新しいチケットがあります- {}" - -#: tickets/notifications.py:113 -msgid "{}: New Ticket - {} ({})" -msgstr "新しいチケット- {} ({})" - -#: tickets/notifications.py:157 -msgid "Your ticket has been processed, processor - {}" -msgstr "チケットが処理されました。プロセッサー- {}" - -#: tickets/notifications.py:161 -msgid "Ticket has processed - {} ({})" -msgstr "チケットが処理済み- {} ({})" - -#: tickets/serializers/flow.py:21 -msgid "Assignees display" -msgstr "受付者名" - -#: tickets/serializers/flow.py:47 -msgid "Please select the Assignees" -msgstr "受付をお選びください" - -#: tickets/serializers/flow.py:75 -msgid "The current organization type already exists" -msgstr "現在の組織タイプは既に存在します。" - -#: tickets/serializers/super_ticket.py:11 -msgid "Processor" -msgstr "プロセッサ" - -#: tickets/serializers/ticket/apply_asset.py:20 -#, fuzzy -#| msgid "Apply applications" -msgid "Apply actions" -msgstr "アプリケーションの適用" - -#: tickets/serializers/ticket/common.py:15 -#: tickets/serializers/ticket/common.py:77 -msgid "Created by ticket ({}-{})" -msgstr "チケットで作成 ({}-{})" - -#: tickets/serializers/ticket/common.py:67 -msgid "The expiration date should be greater than the start date" -msgstr "有効期限は開始日より大きくする必要があります" - -#: tickets/serializers/ticket/common.py:84 -msgid "Permission named `{}` already exists" -msgstr "'{}'という名前の権限は既に存在します" - -#: tickets/serializers/ticket/ticket.py:83 -msgid "The ticket flow `{}` does not exist" -msgstr "チケットフロー '{}'が存在しない" - -#: tickets/templates/tickets/_msg_ticket.html:20 -msgid "View details" -msgstr "詳細の表示" - -#: tickets/templates/tickets/_msg_ticket.html:25 -msgid "Direct approval" -msgstr "直接承認" - -#: tickets/templates/tickets/approve_check_password.html:11 -msgid "Ticket information" -msgstr "作業指示情報" - -#: tickets/templates/tickets/approve_check_password.html:29 -#: tickets/views/approve.py:38 -msgid "Ticket approval" -msgstr "作業指示の承認" - -#: tickets/templates/tickets/approve_check_password.html:45 -msgid "Approval" -msgstr "承認" - -#: tickets/templates/tickets/approve_check_password.html:54 -msgid "Go Login" -msgstr "ログイン" - -#: tickets/views/approve.py:39 -msgid "" -"This ticket does not exist, the process has ended, or this link has expired" -msgstr "" -"このワークシートが存在しないか、ワークシートが終了したか、このリンクが無効に" -"なっています" - -#: tickets/views/approve.py:68 -msgid "Click the button below to approve or reject" -msgstr "下のボタンをクリックして同意または拒否。" - -#: tickets/views/approve.py:70 -msgid "After successful authentication, this ticket can be approved directly" -msgstr "認証に成功した後、作業指示書は直接承認することができる。" - -#: tickets/views/approve.py:92 -msgid "Illegal approval action" -msgstr "無効な承認アクション" - -#: tickets/views/approve.py:105 -msgid "This user is not authorized to approve this ticket" -msgstr "このユーザーはこの作業指示を承認する権限がありません" - -#: users/api/user.py:183 -msgid "Could not reset self otp, use profile reset instead" -msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用" - -#: users/apps.py:9 -msgid "Users" -msgstr "ユーザー" - -#: users/const.py:10 -msgid "System administrator" -msgstr "システム管理者" - -#: users/const.py:11 -msgid "System auditor" -msgstr "システム監査人" - -#: users/const.py:12 -msgid "Organization administrator" -msgstr "組織管理者" - -#: users/const.py:13 -msgid "Organization auditor" -msgstr "組織監査人" - -#: users/const.py:18 -msgid "Reset link will be generated and sent to the user" -msgstr "リセットリンクが生成され、ユーザーに送信されます" - -#: users/const.py:19 -msgid "Set password" -msgstr "パスワードの設定" - -#: users/exceptions.py:10 -msgid "MFA not enabled" -msgstr "MFAが有効化されていません" - -#: users/exceptions.py:20 -msgid "MFA method not support" -msgstr "MFAメソッドはサポートしていません" - -#: users/forms/profile.py:50 -msgid "" -"When enabled, you will enter the MFA binding process the next time you log " -"in. you can also directly bind in \"personal information -> quick " -"modification -> change MFA Settings\"!" -msgstr "" -"有効にすると、次回のログイン時にマルチファクタ認証バインドプロセスに入りま" -"す。(個人情報->クイック修正->MFAマルチファクタ認証の設定)で直接バインド!" - -#: users/forms/profile.py:61 -msgid "* Enable MFA to make the account more secure." -msgstr "* アカウントをより安全にするためにMFAを有効にします。" - -#: users/forms/profile.py:70 -msgid "" -"In order to protect you and your company, please keep your account, password " -"and key sensitive information properly. (for example: setting complex " -"password, enabling MFA)" -msgstr "" -"あなたとあなたの会社を保護するために、アカウント、パスワード、キーの機密情報" -"を適切に保管してください。(例: 複雑なパスワードの設定、MFAの有効化)" - -#: users/forms/profile.py:77 -msgid "Finish" -msgstr "仕上げ" - -#: users/forms/profile.py:84 -msgid "New password" -msgstr "新しいパスワード" - -#: users/forms/profile.py:89 -msgid "Confirm password" -msgstr "パスワードの確認" - -#: users/forms/profile.py:97 -msgid "Password does not match" -msgstr "パスワードが一致しない" - -#: users/forms/profile.py:109 -msgid "Old password" -msgstr "古いパスワード" - -#: users/forms/profile.py:119 -msgid "Old password error" -msgstr "古いパスワードエラー" - -#: users/forms/profile.py:129 -msgid "Automatically configure and download the SSH key" -msgstr "SSHキーの自動設定とダウンロード" - -#: users/forms/profile.py:131 -msgid "ssh public key" -msgstr "ssh公開キー" - -#: users/forms/profile.py:132 -msgid "ssh-rsa AAAA..." -msgstr "ssh-rsa AAAA.." - -#: users/forms/profile.py:133 -msgid "Paste your id_rsa.pub here." -msgstr "ここにid_rsa.pubを貼り付けます。" - -#: users/forms/profile.py:146 -msgid "Public key should not be the same as your old one." -msgstr "公開鍵は古いものと同じであってはなりません。" - -#: users/forms/profile.py:150 users/serializers/profile.py:100 -#: users/serializers/profile.py:183 users/serializers/profile.py:210 -msgid "Not a valid ssh public key" -msgstr "有効なssh公開鍵ではありません" - -#: users/forms/profile.py:161 users/models/user.py:696 -msgid "Public key" -msgstr "公開キー" - -#: users/models/user.py:558 -msgid "Force enable" -msgstr "強制有効" - -#: users/models/user.py:625 -msgid "Local" -msgstr "ローカル" - -#: users/models/user.py:677 users/serializers/user.py:204 -msgid "Is service account" -msgstr "サービスアカウントです" - -#: users/models/user.py:679 -msgid "Avatar" -msgstr "アバター" - -#: users/models/user.py:682 -msgid "Wechat" -msgstr "微信" - -#: users/models/user.py:685 -msgid "Phone" -msgstr "電話" - -#: users/models/user.py:693 -msgid "Private key" -msgstr "ssh秘密鍵" - -#: users/models/user.py:699 -msgid "Secret key" -msgstr "秘密キー" - -#: users/models/user.py:715 -msgid "Source" -msgstr "ソース" - -#: users/models/user.py:719 -msgid "Date password last updated" -msgstr "最終更新日パスワード" - -#: users/models/user.py:722 -msgid "Need update password" -msgstr "更新パスワードが必要" - -#: users/models/user.py:897 -msgid "Can invite user" -msgstr "ユーザーを招待できます" - -#: users/models/user.py:898 -msgid "Can remove user" -msgstr "ユーザーを削除できます" - -#: users/models/user.py:899 -msgid "Can match user" -msgstr "ユーザーに一致できます" - -#: users/models/user.py:908 -msgid "Administrator" -msgstr "管理者" - -#: users/models/user.py:911 -msgid "Administrator is the super user of system" -msgstr "管理者はシステムのスーパーユーザーです" - -#: users/models/user.py:936 -msgid "User password history" -msgstr "ユーザーパスワード履歴" - -#: users/notifications.py:55 -#: users/templates/users/_msg_password_expire_reminder.html:17 -#: users/templates/users/reset_password.html:5 -#: users/templates/users/reset_password.html:6 -msgid "Reset password" -msgstr "パスワードのリセット" - -#: users/notifications.py:85 users/views/profile/reset.py:127 -msgid "Reset password success" -msgstr "パスワードのリセット成功" - -#: users/notifications.py:117 -msgid "Reset public key success" -msgstr "公開鍵のリセット成功" - -#: users/notifications.py:143 -msgid "Password is about expire" -msgstr "パスワードの有効期限が近づいています" - -#: users/notifications.py:171 -msgid "Account is about expire" -msgstr "アカウントの有効期限が近づいています" - -#: users/notifications.py:193 -msgid "Reset SSH Key" -msgstr "SSHキーのリセット" - -#: users/notifications.py:214 -msgid "Reset MFA" -msgstr "MFAのリセット" - -#: users/serializers/profile.py:30 -msgid "The old password is incorrect" -msgstr "古いパスワードが正しくありません" - -#: users/serializers/profile.py:37 users/serializers/profile.py:197 -msgid "Password does not match security rules" -msgstr "パスワードがセキュリティルールと一致しない" - -#: users/serializers/profile.py:41 -msgid "The new password cannot be the last {} passwords" -msgstr "新しいパスワードを最後の {} 個のパスワードにすることはできません" - -#: users/serializers/profile.py:49 users/serializers/profile.py:71 -msgid "The newly set password is inconsistent" -msgstr "新しく設定されたパスワードが一致しない" - -#: users/serializers/profile.py:149 users/serializers/user.py:201 -msgid "Is first login" -msgstr "最初のログインです" - -#: users/serializers/user.py:30 -msgid "System roles" -msgstr "システムの役割" - -#: users/serializers/user.py:35 -msgid "Org roles" -msgstr "組織ロール" - -#: users/serializers/user.py:38 -msgid "System roles display" -msgstr "システムロール表示" - -#: users/serializers/user.py:40 -msgid "Org roles display" -msgstr "組織ロール表示" - -#: users/serializers/user.py:90 -#: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:27 -msgid "Password strategy" -msgstr "パスワード戦略" - -#: users/serializers/user.py:92 -msgid "MFA enabled" -msgstr "MFA有効化" - -#: users/serializers/user.py:94 -msgid "MFA force enabled" -msgstr "MFAフォース有効化" - -#: users/serializers/user.py:97 -msgid "MFA level display" -msgstr "MFAレベル表示" - -#: users/serializers/user.py:99 -msgid "Login blocked" -msgstr "ログインブロック" - -#: users/serializers/user.py:102 -msgid "Can public key authentication" -msgstr "公開鍵認証が可能" - -#: users/serializers/user.py:206 -msgid "Avatar url" -msgstr "アバターURL" - -#: users/serializers/user.py:208 -msgid "Groups name" -msgstr "グループ名" - -#: users/serializers/user.py:209 -msgid "Source name" -msgstr "ソース名" - -#: users/serializers/user.py:210 -msgid "Organization role name" -msgstr "組織の役割名" - -#: users/serializers/user.py:211 -msgid "Super role name" -msgstr "スーパーロール名" - -#: users/serializers/user.py:212 -msgid "Total role name" -msgstr "合計ロール名" - -#: users/serializers/user.py:214 -msgid "Is wecom bound" -msgstr "企業の微信をバインドしているかどうか" - -#: users/serializers/user.py:215 -msgid "Is dingtalk bound" -msgstr "ピンをバインドしているかどうか" - -#: users/serializers/user.py:216 -msgid "Is feishu bound" -msgstr "飛本を縛ったかどうか" - -#: users/serializers/user.py:217 -msgid "Is OTP bound" -msgstr "仮想MFAがバインドされているか" - -#: users/serializers/user.py:219 -msgid "System role name" -msgstr "システムロール名" - -#: users/serializers/user.py:325 -msgid "Select users" -msgstr "ユーザーの選択" - -#: users/serializers/user.py:326 -msgid "For security, only list several users" -msgstr "セキュリティのために、複数のユーザーのみをリストします" - -#: users/serializers/user.py:362 -msgid "name not unique" -msgstr "名前が一意ではない" - -#: users/templates/users/_msg_account_expire_reminder.html:7 -msgid "Your account will expire in" -msgstr "アカウントの有効期限は" - -#: users/templates/users/_msg_account_expire_reminder.html:8 -msgid "" -"In order not to affect your normal work, please contact the administrator " -"for confirmation." -msgstr "" -"通常の作業に影響を与えないように、確認のために管理者に連絡してください。" - -#: users/templates/users/_msg_password_expire_reminder.html:7 -msgid "Your password will expire in" -msgstr "パスワードは" - -#: users/templates/users/_msg_password_expire_reminder.html:8 -msgid "" -"For your account security, please click on the link below to update your " -"password in time" -msgstr "" -"アカウントのセキュリティについては、下のリンクをクリックしてパスワードを時間" -"内に更新してください" - -#: users/templates/users/_msg_password_expire_reminder.html:11 -msgid "Click here update password" -msgstr "ここをクリック更新パスワード" - -#: users/templates/users/_msg_password_expire_reminder.html:16 -msgid "If your password has expired, please click the link below to" -msgstr "" -"パスワードの有効期限が切れている場合は、以下のリンクをクリックしてください" - -#: users/templates/users/_msg_reset_mfa.html:7 -msgid "Your MFA has been reset by site administrator" -msgstr "MFAはサイト管理者によってリセットされました" - -#: users/templates/users/_msg_reset_mfa.html:8 -#: users/templates/users/_msg_reset_ssh_key.html:8 -msgid "Please click the link below to set" -msgstr "以下のリンクをクリックして設定してください" - -#: users/templates/users/_msg_reset_mfa.html:11 -#: users/templates/users/_msg_reset_ssh_key.html:11 -msgid "Click here set" -msgstr "ここをクリックセット" - -#: users/templates/users/_msg_reset_ssh_key.html:7 -msgid "Your ssh public key has been reset by site administrator" -msgstr "あなたのssh公開鍵はサイト管理者によってリセットされました" - -#: users/templates/users/_msg_user_created.html:15 -msgid "click here to set your password" -msgstr "ここをクリックしてパスワードを設定してください" - -#: users/templates/users/forgot_password.html:24 -msgid "Input your email, that will send a mail to your" -msgstr "あなたのメールを入力し、それはあなたにメールを送信します" - -#: users/templates/users/forgot_password.html:33 -msgid "Submit" -msgstr "送信" - -#: users/templates/users/mfa_setting.html:24 -msgid "Enable MFA" -msgstr "MFAの有効化" - -#: users/templates/users/mfa_setting.html:30 -msgid "MFA force enable, cannot disable" -msgstr "MFA強制有効化、無効化できません" - -#: users/templates/users/mfa_setting.html:48 -msgid "MFA setting" -msgstr "MFAの設定" - -#: users/templates/users/reset_password.html:23 -msgid "Your password must satisfy" -msgstr "パスワードを満たす必要があります" - -#: users/templates/users/reset_password.html:24 -msgid "Password strength" -msgstr "パスワードの強さ" - -#: users/templates/users/reset_password.html:48 -msgid "Very weak" -msgstr "非常に弱い" - -#: users/templates/users/reset_password.html:49 -msgid "Weak" -msgstr "弱い" - -#: users/templates/users/reset_password.html:51 -msgid "Medium" -msgstr "中" - -#: users/templates/users/reset_password.html:52 -msgid "Strong" -msgstr "強い" - -#: users/templates/users/reset_password.html:53 -msgid "Very strong" -msgstr "非常に強い" - -#: users/templates/users/user_otp_check_password.html:6 -msgid "Enable OTP" -msgstr "OTPの有効化" - -#: users/templates/users/user_otp_enable_bind.html:6 -msgid "Bind one-time password authenticator" -msgstr "ワンタイムパスワード認証子のバインド" - -#: users/templates/users/user_otp_enable_bind.html:13 -msgid "" -"Use the MFA Authenticator application to scan the following qr code for a 6-" -"bit verification code" -msgstr "" -"MFA Authenticatorアプリケーションを使用して、次のqrコードを6ビット検証コード" -"でスキャンします。" - -#: users/templates/users/user_otp_enable_bind.html:22 -#: users/templates/users/user_verify_mfa.html:27 -msgid "Six figures" -msgstr "6つの数字" - -#: users/templates/users/user_otp_enable_install_app.html:6 -msgid "Install app" -msgstr "アプリのインストール" - -#: users/templates/users/user_otp_enable_install_app.html:13 -msgid "" -"Download and install the MFA Authenticator application on your phone or " -"applet of WeChat" -msgstr "" -"携帯電話またはWeChatのアプレットにMFA Authenticatorアプリケーションをダウン" -"ロードしてインストールします" - -#: users/templates/users/user_otp_enable_install_app.html:18 -msgid "Android downloads" -msgstr "Androidのダウンロード" - -#: users/templates/users/user_otp_enable_install_app.html:23 -msgid "iPhone downloads" -msgstr "IPhoneのダウンロード" - -#: users/templates/users/user_otp_enable_install_app.html:26 -msgid "" -"After installation, click the next step to enter the binding page (if " -"installed, go to the next step directly)." -msgstr "" -"インストール後、次のステップをクリックしてバインディングページに入ります (イ" -"ンストールされている場合は、次のステップに直接進みます)。" - -#: users/templates/users/user_password_verify.html:8 -#: users/templates/users/user_password_verify.html:9 -msgid "Verify password" -msgstr "パスワードの確認" - -#: users/templates/users/user_verify_mfa.html:9 -msgid "Authenticate" -msgstr "認証" - -#: users/templates/users/user_verify_mfa.html:15 -msgid "" -"The account protection has been opened, please complete the following " -"operations according to the prompts" -msgstr "" -"アカウント保護が開始されました。プロンプトに従って次の操作を完了してください" - -#: users/templates/users/user_verify_mfa.html:17 -msgid "Open MFA Authenticator and enter the 6-bit dynamic code" -msgstr "MFA Authenticatorを開き、6ビットの動的コードを入力します" - -#: users/views/profile/otp.py:87 -msgid "Already bound" -msgstr "すでにバインド済み" - -#: users/views/profile/otp.py:88 -msgid "MFA already bound, disable first, then bound" -msgstr "" -"MFAはすでにバインドされており、最初に無効にしてからバインドされています。" - -#: users/views/profile/otp.py:115 -msgid "OTP enable success" -msgstr "OTP有効化成功" - -#: users/views/profile/otp.py:116 -msgid "OTP enable success, return login page" -msgstr "OTP有効化成功、ログインページを返す" - -#: users/views/profile/otp.py:158 -msgid "Disable OTP" -msgstr "OTPの無効化" - -#: users/views/profile/otp.py:164 -msgid "OTP disable success" -msgstr "OTP無効化成功" - -#: users/views/profile/otp.py:165 -msgid "OTP disable success, return login page" -msgstr "OTP無効化成功、ログインページを返す" - -#: users/views/profile/password.py:36 users/views/profile/password.py:41 -msgid "Password invalid" -msgstr "パスワード無効" - -#: users/views/profile/reset.py:40 -msgid "Send reset password message" -msgstr "リセットパスワードメッセージを送信" - -#: users/views/profile/reset.py:41 -msgid "Send reset password mail success, login your mail box and follow it " -msgstr "" -"リセットパスワードメールの成功を送信し、メールボックスにログインしてそれに従" -"う" - -#: users/views/profile/reset.py:52 -msgid "Email address invalid, please input again" -msgstr "メールアドレスが無効です。再度入力してください" - -#: users/views/profile/reset.py:58 -msgid "" -"The user is from {}, please go to the corresponding system to change the " -"password" -msgstr "" -"ユーザーは {}からです。対応するシステムにアクセスしてパスワードを変更してくだ" -"さい。" - -#: users/views/profile/reset.py:84 users/views/profile/reset.py:95 -msgid "Token invalid or expired" -msgstr "トークンが無効または期限切れ" - -#: users/views/profile/reset.py:100 -msgid "User auth from {}, go there change password" -msgstr "ユーザー認証ソース {}, 対応するシステムにパスワードを変更してください" - -#: users/views/profile/reset.py:107 -msgid "* Your password does not meet the requirements" -msgstr "* パスワードが要件を満たしていない" - -#: users/views/profile/reset.py:113 -msgid "* The new password cannot be the last {} passwords" -msgstr "* 新しいパスワードを最後の {} パスワードにすることはできません" - -#: users/views/profile/reset.py:128 -msgid "Reset password success, return to login page" -msgstr "パスワードの成功をリセットし、ログインページに戻る" - -#: xpack/apps.py:8 -msgid "XPACK" -msgstr "XPack" - -#: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models/asset.py:124 -msgid "Change auth plan" -msgstr "密かな計画" - -#: xpack/plugins/change_auth_plan/models/app.py:45 -#: xpack/plugins/change_auth_plan/models/app.py:94 -msgid "Application change auth plan" -msgstr "改密計画の適用" - -#: xpack/plugins/change_auth_plan/models/app.py:98 -#: xpack/plugins/change_auth_plan/models/app.py:150 -msgid "Application change auth plan execution" -msgstr "改密計画実行の適用" - -#: xpack/plugins/change_auth_plan/models/app.py:143 -msgid "App" -msgstr "適用" - -#: xpack/plugins/change_auth_plan/models/app.py:155 -msgid "Application change auth plan task" -msgstr "改密計画タスクの適用" - -#: xpack/plugins/change_auth_plan/models/app.py:179 -#: xpack/plugins/change_auth_plan/models/asset.py:264 -msgid "Password cannot be set to blank, exit. " -msgstr "パスワードを空白に設定することはできません。" - -#: xpack/plugins/change_auth_plan/models/asset.py:68 -msgid "Asset change auth plan" -msgstr "資産変更のオースプラン" - -#: xpack/plugins/change_auth_plan/models/asset.py:135 -msgid "Asset change auth plan execution" -msgstr "資産変更のオースプランの実行" - -#: xpack/plugins/change_auth_plan/models/asset.py:211 -msgid "Change auth plan execution" -msgstr "改密計画の実行" - -#: xpack/plugins/change_auth_plan/models/asset.py:218 -msgid "Asset change auth plan task" -msgstr "資産改密計画タスク" - -#: xpack/plugins/change_auth_plan/models/asset.py:253 -msgid "This asset does not have a privileged user set: " -msgstr "このアセットには特権ユーザーセットがありません。" - -#: xpack/plugins/change_auth_plan/models/asset.py:259 -msgid "" -"The password and key of the current asset privileged user cannot be changed: " -msgstr "現在のアセット特権ユーザーのパスワードとキーは変更できません。" - -#: xpack/plugins/change_auth_plan/models/asset.py:270 -msgid "Public key cannot be set to null, exit. " -msgstr "公開鍵をnull、exitに設定することはできません。" - -#: xpack/plugins/change_auth_plan/models/base.py:114 -msgid "Change auth plan snapshot" -msgstr "計画スナップショットの暗号化" - -#: xpack/plugins/change_auth_plan/models/base.py:184 -msgid "Preflight check" -msgstr "プリフライトチェック" - -#: xpack/plugins/change_auth_plan/models/base.py:185 -msgid "Change auth" -msgstr "秘密を改める" - -#: xpack/plugins/change_auth_plan/models/base.py:186 -msgid "Verify auth" -msgstr "パスワード/キーの確認" - -#: xpack/plugins/change_auth_plan/models/base.py:187 -msgid "Keep auth" -msgstr "パスワード/キーの保存" - -#: xpack/plugins/change_auth_plan/models/base.py:195 -msgid "Step" -msgstr "ステップ" - -#: xpack/plugins/change_auth_plan/serializers/asset.py:30 -msgid "Change Password" -msgstr "パスワードの変更" - -#: xpack/plugins/change_auth_plan/serializers/asset.py:31 -msgid "Change SSH Key" -msgstr "SSHキーの変更" - -#: xpack/plugins/change_auth_plan/serializers/base.py:44 -msgid "Run times" -msgstr "実行時間" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 -msgid "After many attempts to change the secret, it still failed" -msgstr "秘密を変更しようとする多くの試みの後、それはまだ失敗しました" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:255 -msgid "Invalid/incorrect password" -msgstr "パスワードが無効/間違っている" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:257 -msgid "Failed to connect to the host" -msgstr "ホストへの接続に失敗しました" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:259 -msgid "Data could not be sent to remote" -msgstr "データをリモートに送信できませんでした" - -#: xpack/plugins/change_auth_plan/tasks.py:13 -#, fuzzy -#| msgid "Asset change auth plan task" -msgid "Execute change authentication task" -msgstr "資産改密計画タスク" - -#: xpack/plugins/change_auth_plan/tasks.py:24 -#, fuzzy -#| msgid "Asset change auth plan task" -msgid "Start change authentication task" -msgstr "資産改密計画タスク" - -#: xpack/plugins/change_auth_plan/tasks.py:36 -msgid "Test the validity of the change authentication plan " -msgstr "" - -#: xpack/plugins/cloud/api.py:40 -msgid "Test connection successful" -msgstr "テスト接続成功" - -#: xpack/plugins/cloud/api.py:42 -msgid "Test connection failed: {}" -msgstr "テスト接続に失敗しました: {}" - -#: xpack/plugins/cloud/const.py:8 -msgid "Alibaba Cloud" -msgstr "アリ雲" - -#: xpack/plugins/cloud/const.py:9 -msgid "AWS (International)" -msgstr "AWS (国際)" - -#: xpack/plugins/cloud/const.py:10 -msgid "AWS (China)" -msgstr "AWS (中国)" - -#: xpack/plugins/cloud/const.py:11 -msgid "Azure (China)" -msgstr "Azure (中国)" - -#: xpack/plugins/cloud/const.py:12 -msgid "Azure (International)" -msgstr "Azure (国際)" - -#: xpack/plugins/cloud/const.py:13 -msgid "Huawei Cloud" -msgstr "華為雲" - -#: xpack/plugins/cloud/const.py:14 -msgid "Baidu Cloud" -msgstr "百度雲" - -#: xpack/plugins/cloud/const.py:15 -msgid "JD Cloud" -msgstr "京東雲" - -#: xpack/plugins/cloud/const.py:16 -msgid "Tencent Cloud" -msgstr "テンセント雲" - -#: xpack/plugins/cloud/const.py:17 -msgid "VMware" -msgstr "VMware" - -#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 -msgid "Nutanix" -msgstr "Nutanix" - -#: xpack/plugins/cloud/const.py:19 -msgid "Huawei Private Cloud" -msgstr "華為私有雲" - -#: xpack/plugins/cloud/const.py:20 -msgid "Qingyun Private Cloud" -msgstr "青雲私有雲" - -#: xpack/plugins/cloud/const.py:21 -msgid "OpenStack" -msgstr "OpenStack" - -#: xpack/plugins/cloud/const.py:22 -msgid "Google Cloud Platform" -msgstr "谷歌雲" - -#: xpack/plugins/cloud/const.py:23 -msgid "Fusion Compute" -msgstr "" - -#: xpack/plugins/cloud/const.py:28 -msgid "Instance name" -msgstr "インスタンス名" - -#: xpack/plugins/cloud/const.py:29 -msgid "Instance name and Partial IP" -msgstr "インスタンス名と部分IP" - -#: xpack/plugins/cloud/const.py:34 -msgid "Succeed" -msgstr "成功" - -#: xpack/plugins/cloud/const.py:38 -msgid "Unsync" -msgstr "同期していません" - -#: xpack/plugins/cloud/const.py:39 -msgid "New Sync" -msgstr "新しい同期" - -#: xpack/plugins/cloud/const.py:40 -msgid "Synced" -msgstr "同期済み" - -#: xpack/plugins/cloud/const.py:41 -msgid "Released" -msgstr "リリース済み" - -#: xpack/plugins/cloud/meta.py:9 -msgid "Cloud center" -msgstr "クラウドセンター" - -#: xpack/plugins/cloud/models.py:32 -msgid "Provider" -msgstr "プロバイダー" - -#: xpack/plugins/cloud/models.py:36 -msgid "Validity" -msgstr "有効性" - -#: xpack/plugins/cloud/models.py:41 -msgid "Cloud account" -msgstr "クラウドアカウント" - -#: xpack/plugins/cloud/models.py:43 -msgid "Test cloud account" -msgstr "クラウドアカウントのテスト" - -#: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:37 -msgid "Regions" -msgstr "リージョン" - -#: xpack/plugins/cloud/models.py:93 -msgid "Hostname strategy" -msgstr "ホスト名戦略" - -#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:66 -msgid "Unix admin user" -msgstr "Unix adminユーザー" - -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:67 -msgid "Windows admin user" -msgstr "Windows管理者" - -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:44 -msgid "IP network segment group" -msgstr "IPネットワークセグメントグループ" - -#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:70 -msgid "Always update" -msgstr "常に更新" - -#: xpack/plugins/cloud/models.py:121 -msgid "Date last sync" -msgstr "最終同期日" - -#: xpack/plugins/cloud/models.py:126 xpack/plugins/cloud/models.py:167 -msgid "Sync instance task" -msgstr "インスタンスの同期タスク" - -#: xpack/plugins/cloud/models.py:178 xpack/plugins/cloud/models.py:226 -msgid "Date sync" -msgstr "日付の同期" - -#: xpack/plugins/cloud/models.py:182 -msgid "Sync instance task execution" -msgstr "インスタンスタスクの同期実行" - -#: xpack/plugins/cloud/models.py:206 -msgid "Sync task" -msgstr "同期タスク" - -#: xpack/plugins/cloud/models.py:210 -msgid "Sync instance task history" -msgstr "インスタンスタスク履歴の同期" - -#: xpack/plugins/cloud/models.py:213 -msgid "Instance" -msgstr "インスタンス" - -#: xpack/plugins/cloud/models.py:230 -msgid "Sync instance detail" -msgstr "同期インスタンスの詳細" - -#: xpack/plugins/cloud/providers/aws_international.py:17 -msgid "China (Beijing)" -msgstr "中国 (北京)" - -#: xpack/plugins/cloud/providers/aws_international.py:18 -msgid "China (Ningxia)" -msgstr "中国 (寧夏)" - -#: xpack/plugins/cloud/providers/aws_international.py:21 -msgid "US East (Ohio)" -msgstr "米国東部 (オハイオ州)" - -#: xpack/plugins/cloud/providers/aws_international.py:22 -msgid "US East (N. Virginia)" -msgstr "米国東部 (N. バージニア州)" - -#: xpack/plugins/cloud/providers/aws_international.py:23 -msgid "US West (N. California)" -msgstr "米国西部 (N. カリフォルニア州)" - -#: xpack/plugins/cloud/providers/aws_international.py:24 -msgid "US West (Oregon)" -msgstr "米国西部 (オレゴン州)" - -#: xpack/plugins/cloud/providers/aws_international.py:25 -msgid "Africa (Cape Town)" -msgstr "アフリカ (ケープタウン)" - -#: xpack/plugins/cloud/providers/aws_international.py:26 -msgid "Asia Pacific (Hong Kong)" -msgstr "アジアパシフィック (香港)" - -#: xpack/plugins/cloud/providers/aws_international.py:27 -msgid "Asia Pacific (Mumbai)" -msgstr "アジア太平洋 (ムンバイ)" - -#: xpack/plugins/cloud/providers/aws_international.py:28 -msgid "Asia Pacific (Osaka-Local)" -msgstr "アジアパシフィック (大阪-ローカル)" - -#: xpack/plugins/cloud/providers/aws_international.py:29 -msgid "Asia Pacific (Seoul)" -msgstr "アジア太平洋地域 (ソウル)" - -#: xpack/plugins/cloud/providers/aws_international.py:30 -msgid "Asia Pacific (Singapore)" -msgstr "アジア太平洋 (シンガポール)" - -#: xpack/plugins/cloud/providers/aws_international.py:31 -msgid "Asia Pacific (Sydney)" -msgstr "アジア太平洋 (シドニー)" - -#: xpack/plugins/cloud/providers/aws_international.py:32 -msgid "Asia Pacific (Tokyo)" -msgstr "アジアパシフィック (東京)" - -#: xpack/plugins/cloud/providers/aws_international.py:33 -msgid "Canada (Central)" -msgstr "カナダ (中央)" - -#: xpack/plugins/cloud/providers/aws_international.py:34 -msgid "Europe (Frankfurt)" -msgstr "ヨーロッパ (フランクフルト)" - -#: xpack/plugins/cloud/providers/aws_international.py:35 -msgid "Europe (Ireland)" -msgstr "ヨーロッパ (アイルランド)" - -#: xpack/plugins/cloud/providers/aws_international.py:36 -msgid "Europe (London)" -msgstr "ヨーロッパ (ロンドン)" - -#: xpack/plugins/cloud/providers/aws_international.py:37 -msgid "Europe (Milan)" -msgstr "ヨーロッパ (ミラノ)" - -#: xpack/plugins/cloud/providers/aws_international.py:38 -msgid "Europe (Paris)" -msgstr "ヨーロッパ (パリ)" - -#: xpack/plugins/cloud/providers/aws_international.py:39 -msgid "Europe (Stockholm)" -msgstr "ヨーロッパ (ストックホルム)" - -#: xpack/plugins/cloud/providers/aws_international.py:40 -msgid "Middle East (Bahrain)" -msgstr "中东 (バーレーン)" - -#: xpack/plugins/cloud/providers/aws_international.py:41 -msgid "South America (São Paulo)" -msgstr "南米 (サンパウロ)" - -#: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 -msgid "CN North-Beijing" -msgstr "華北-北京" - -#: xpack/plugins/cloud/providers/baiducloud.py:55 -#: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 -msgid "CN South-Guangzhou" -msgstr "華南-広州" - -#: xpack/plugins/cloud/providers/baiducloud.py:56 -msgid "CN East-Suzhou" -msgstr "華東-蘇州" - -#: xpack/plugins/cloud/providers/baiducloud.py:57 -#: xpack/plugins/cloud/providers/huaweicloud.py:48 -msgid "CN-Hong Kong" -msgstr "中国-香港" - -#: xpack/plugins/cloud/providers/baiducloud.py:58 -msgid "CN Center-Wuhan" -msgstr "華中-武漢" - -#: xpack/plugins/cloud/providers/baiducloud.py:59 -msgid "CN North-Baoding" -msgstr "華北-保定" - -#: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 -msgid "CN East-Shanghai" -msgstr "華東-上海" - -#: xpack/plugins/cloud/providers/baiducloud.py:61 -#: xpack/plugins/cloud/providers/huaweicloud.py:47 -msgid "AP-Singapore" -msgstr "アジア太平洋-シンガポール" - -#: xpack/plugins/cloud/providers/huaweicloud.py:35 -msgid "AF-Johannesburg" -msgstr "アフリカ-ヨハネスブルク" - -#: xpack/plugins/cloud/providers/huaweicloud.py:36 -msgid "CN North-Beijing4" -msgstr "華北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:37 -msgid "CN North-Beijing1" -msgstr "華北-北京1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:38 -msgid "CN East-Shanghai2" -msgstr "華東-上海2" - -#: xpack/plugins/cloud/providers/huaweicloud.py:39 -msgid "CN East-Shanghai1" -msgstr "華東-上海1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:41 -msgid "LA-Mexico City1" -msgstr "LA-メキシコCity1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:42 -msgid "LA-Santiago" -msgstr "ラテンアメリカ-サンディエゴ" - -#: xpack/plugins/cloud/providers/huaweicloud.py:43 -msgid "LA-Sao Paulo1" -msgstr "ラミー・サンパウロ1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:44 -msgid "EU-Paris" -msgstr "ヨーロッパ-パリ" - -#: xpack/plugins/cloud/providers/huaweicloud.py:45 -msgid "CN Southwest-Guiyang1" -msgstr "南西-貴陽1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:46 -msgid "AP-Bangkok" -msgstr "アジア太平洋-バンコク" - -#: xpack/plugins/cloud/providers/huaweicloud.py:50 -msgid "CN Northeast-Dalian" -msgstr "华北-大连" - -#: xpack/plugins/cloud/providers/huaweicloud.py:51 -msgid "CN North-Ulanqab1" -msgstr "華北-ウランチャブ一" - -#: xpack/plugins/cloud/providers/huaweicloud.py:52 -msgid "CN South-Guangzhou-InvitationOnly" -msgstr "華南-広州-友好ユーザー環境" - -#: xpack/plugins/cloud/providers/jdcloud.py:128 -msgid "CN East-Suqian" -msgstr "華東-宿遷" - -#: xpack/plugins/cloud/serializers/account.py:62 -msgid "Validity display" -msgstr "有効表示" - -#: xpack/plugins/cloud/serializers/account.py:63 -msgid "Provider display" -msgstr "プロバイダ表示" - -#: xpack/plugins/cloud/serializers/account_attrs.py:35 -msgid "Client ID" -msgstr "クライアントID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:41 -msgid "Tenant ID" -msgstr "テナントID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:44 -msgid "Subscription ID" -msgstr "サブスクリプションID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:95 -#: xpack/plugins/cloud/serializers/account_attrs.py:100 -#: xpack/plugins/cloud/serializers/account_attrs.py:134 -msgid "API Endpoint" -msgstr "APIエンドポイント" - -#: xpack/plugins/cloud/serializers/account_attrs.py:106 -msgid "Auth url" -msgstr "認証アドレス" - -#: xpack/plugins/cloud/serializers/account_attrs.py:107 -msgid "eg: http://openstack.example.com:5000/v3" -msgstr "例えば: http://openstack.example.com:5000/v3" - -#: xpack/plugins/cloud/serializers/account_attrs.py:110 -msgid "User domain" -msgstr "ユーザードメイン" - -#: xpack/plugins/cloud/serializers/account_attrs.py:127 -msgid "Service account key" -msgstr "サービスアカウントキー" - -#: xpack/plugins/cloud/serializers/account_attrs.py:128 -msgid "The file is in JSON format" -msgstr "ファイルはJSON形式です。" - -#: xpack/plugins/cloud/serializers/account_attrs.py:141 -msgid "IP address invalid `{}`, {}" -msgstr "IPアドレスが無効: '{}', {}" - -#: xpack/plugins/cloud/serializers/account_attrs.py:147 -msgid "" -"Format for comma-delimited string,Such as: 192.168.1.0/24, " -"10.0.0.0-10.0.0.255" -msgstr "形式はコンマ区切りの文字列です,例:192.168.1.0/24,10.0.0.0-10.0.0.255" - -#: xpack/plugins/cloud/serializers/account_attrs.py:151 -msgid "" -"The port is used to detect the validity of the IP address. When the " -"synchronization task is executed, only the valid IP address will be " -"synchronized.
If the port is 0, all IP addresses are valid." -msgstr "" -"このポートは、 IP アドレスの有効性を検出するために使用されます。同期タスクが" -"実行されると、有効な IP アドレスのみが同期されます。
ポートが0の場合、す" -"べてのIPアドレスが有効です。" - -#: xpack/plugins/cloud/serializers/account_attrs.py:159 -msgid "Hostname prefix" -msgstr "ホスト名プレフィックス" - -#: xpack/plugins/cloud/serializers/account_attrs.py:162 -msgid "IP segment" -msgstr "IP セグメント" - -#: xpack/plugins/cloud/serializers/account_attrs.py:166 -msgid "Test port" -msgstr "テストポート" - -#: xpack/plugins/cloud/serializers/account_attrs.py:169 -msgid "Test timeout" -msgstr "テストタイムアウト" - -#: xpack/plugins/cloud/serializers/task.py:28 -msgid "" -"Only instances matching the IP range will be synced.
If the instance " -"contains multiple IP addresses, the first IP address that matches will be " -"used as the IP for the created asset.
The default value of * means sync " -"all instances and randomly match IP addresses.
Format for comma-" -"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" -msgstr "" -"IP範囲に一致するインスタンスのみが同期されます。
インスタンスに複数のIPア" -"ドレスが含まれている場合、一致する最初のIPアドレスが作成されたアセットのIPと" -"して使用されます。
デフォルト値の*は、すべてのインスタンスを同期し、IPア" -"ドレスをランダムに一致させることを意味します。
形式はコンマ区切りの文字列" -"です。例:192.168.1.0/24,10.1.1.1-10.1.1.20" - -#: xpack/plugins/cloud/serializers/task.py:35 -msgid "History count" -msgstr "実行回数" - -#: xpack/plugins/cloud/serializers/task.py:36 -msgid "Instance count" -msgstr "インスタンス数" - -#: xpack/plugins/cloud/serializers/task.py:64 -msgid "Linux admin user" -msgstr "Linux管理者" - -#: xpack/plugins/cloud/serializers/task.py:69 -#: xpack/plugins/gathered_user/serializers.py:20 -msgid "Periodic display" -msgstr "定期的な表示" - -#: xpack/plugins/cloud/utils.py:69 -msgid "Account unavailable" -msgstr "利用できないアカウント" - -#: xpack/plugins/gathered_user/meta.py:11 -msgid "Gathered user" -msgstr "収集されたユーザー" - -#: xpack/plugins/gathered_user/models.py:34 -msgid "Gather user task" -msgstr "ユーザータスクの収集" - -#: xpack/plugins/gathered_user/models.py:80 -msgid "gather user task execution" -msgstr "ユーザータスクの実行を収集" - -#: xpack/plugins/gathered_user/models.py:86 -msgid "Assets is empty, please change nodes" -msgstr "資産は空です。ノードを変更してください" - -#: xpack/plugins/gathered_user/serializers.py:21 -msgid "Executed times" -msgstr "実行時間" - -#: xpack/plugins/interface/api.py:52 -msgid "Restore default successfully." -msgstr "デフォルトの復元に成功しました。" - -#: xpack/plugins/interface/meta.py:10 -msgid "Interface settings" -msgstr "インターフェイスの設定" - -#: xpack/plugins/interface/models.py:22 -msgid "Title of login page" -msgstr "ログインページのタイトル" - -#: xpack/plugins/interface/models.py:26 -msgid "Image of login page" -msgstr "ログインページのイメージ" - -#: xpack/plugins/interface/models.py:30 -msgid "Website icon" -msgstr "ウェブサイトのアイコン" - -#: xpack/plugins/interface/models.py:34 -msgid "Logo of management page" -msgstr "管理ページのロゴ" - -#: xpack/plugins/interface/models.py:38 -msgid "Logo of logout page" -msgstr "ログアウトページのロゴ" - -#: xpack/plugins/interface/models.py:40 -msgid "Theme" -msgstr "テーマ" - -#: xpack/plugins/interface/models.py:43 -msgid "Interface setting" -msgstr "インターフェイスの設定" - -#: xpack/plugins/license/api.py:50 -msgid "License import successfully" -msgstr "ライセンスのインポートに成功" - -#: xpack/plugins/license/api.py:51 -msgid "License is invalid" -msgstr "ライセンスが無効です" - -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:127 -msgid "License" -msgstr "ライセンス" - -#: xpack/plugins/license/models.py:71 -msgid "Standard edition" -msgstr "標準版" - -#: xpack/plugins/license/models.py:73 -msgid "Enterprise edition" -msgstr "エンタープライズ版" - -#: xpack/plugins/license/models.py:75 -msgid "Ultimate edition" -msgstr "究極のエディション" - -#: xpack/plugins/license/models.py:77 -msgid "Community edition" -msgstr "コミュニティ版" - -#~ msgid "Gateway" -#~ msgstr "ゲートウェイ" - -#~ msgid "Test gateway" -#~ msgstr "テストゲートウェイ" - -#~ msgid "" -#~ "Format for comma-delimited string, with * indicating a match all. " -#~ "Protocol options: {}" -#~ msgstr "" -#~ "コンマ区切り文字列の形式。* はすべて一致することを示します。プロトコルオプ" -#~ "ション: {}" - -#~ msgid "User has no permission to access asset or permission expired" -#~ msgstr "" -#~ "ユーザーがアセットにアクセスする権限を持っていないか、権限の有効期限が切れ" -#~ "ています" - -#~ msgid "AdHoc execution" -#~ msgstr "アドホックエキューション" - -#, fuzzy -#~| msgid "Disable" -#~ msgid "Variables" -#~ msgstr "無効化" - -#~ msgid "Applied login IP" -#~ msgstr "応用ログインIP" - -#~ msgid "Applied login city" -#~ msgstr "応用ログイン都市" - -#~ msgid "Applied login datetime" -#~ msgstr "適用されたログインの日付時間" - -#~ msgid "Type display" -#~ msgstr "タイプ表示" - -#~ msgid "Status display" -#~ msgstr "ステータス表示" - -#, fuzzy -#~| msgid "Run command" -#~ msgid "Run ansible command" -#~ msgstr "実行コマンド" - -#~ msgid "Clean task history period" -#~ msgstr "クリーンなタスク履歴期間" - -#, fuzzy -#~| msgid "WeCom Error" -#~ msgid "Hello Error" -#~ msgstr "企業微信エラー" - -#~ msgid "Operate display" -#~ msgstr "ディスプレイを操作する" - -#~ msgid "MFA display" -#~ msgstr "MFAディスプレイ" - -#, fuzzy -#~| msgid "Run user" -#~ msgid "Run dir" -#~ msgstr "ユーザーの実行" - -#~ msgid "Upload file" -#~ msgstr "ファイルのアップロード" - -#~ msgid "Download file" -#~ msgstr "ファイルのダウンロード" - -#~ msgid "Upload download" -#~ msgstr "ダウンロードのアップロード" - -#~ msgid "Clipboard paste" -#~ msgstr "クリップボードペースト" - -#~ msgid "Clipboard copy paste" -#~ msgstr "クリップボードコピーペースト" - -#~ msgid "Users display" -#~ msgstr "ユーザー表示" - -#~ msgid "User groups display" -#~ msgstr "ユーザーグループの表示" - -#~ msgid "Assets display" -#~ msgstr "資産表示" - -#~ msgid "Nodes display" -#~ msgstr "ノード表示" - -#~ msgid "User groups amount" -#~ msgstr "ユーザーグループの量" - -#~ msgid "Nodes amount" -#~ msgstr "ノード量" - -#~ msgid "The asset {} system platform {} does not support run Ansible tasks" -#~ msgstr "" -#~ "資産 {} システムプラットフォーム {} はAnsibleタスクの実行をサポートしてい" -#~ "ません。" - -#~ msgid "Test assets connectivity: " -#~ msgstr "資産の接続性のテスト:" - -#~ msgid "Unreachable" -#~ msgstr "達成できない" - -#~ msgid "Reachable" -#~ msgstr "接続可能" - -#~ msgid "Get asset info failed: {}" -#~ msgstr "資産情報の取得に失敗しました: {}" - -#~ msgid "Update asset hardware info: " -#~ msgstr "資産ハードウェア情報の更新:" - -#~ msgid "Gather assets users" -#~ msgstr "資産ユーザーの収集" - -#, fuzzy -#~| msgid "Automatic managed" -#~ msgid "Push automation" -#~ msgstr "自動管理" - -#, fuzzy -#~| msgid "Verify auth" -#~ msgid "Verify account automation" -#~ msgstr "パスワード/キーの確認" - -#, fuzzy -#~| msgid "Action display" -#~ msgid "Account display" -#~ msgstr "アクション表示" - -#~ msgid "User not exists" -#~ msgstr "ユーザーは存在しません" - -#~ msgid "System user not exists" -#~ msgstr "システムユーザーが存在しません" - -#~ msgid "Asset not exists" -#~ msgstr "アセットが存在しません" - -#~ msgid "Application not exists" -#~ msgstr "アプリが存在しません" - -#~ msgid "User has no permission to access application or permission expired" -#~ msgstr "" -#~ "ユーザーがアプリにアクセスする権限を持っていないか、権限の有効期限が切れて" -#~ "います" - -#~ msgid "Asset or application required" -#~ msgstr "アセットまたはアプリが必要" - -#, fuzzy -#~| msgid "Manually input" -#~ msgid "Manual" -#~ msgstr "手動入力" - -#, fuzzy -#~| msgid "Remote app" -#~ msgid "Remote gzip" -#~ msgstr "リモートアプリ" - -#, fuzzy -#~| msgid "Verify code" -#~ msgid "Verify secret" -#~ msgstr "コードの確認" - -#, fuzzy -#~| msgid "Secret key" -#~ msgid "Secret types" -#~ msgstr "秘密キー" - -#, fuzzy -#~| msgid "Hostname strategy" -#~ msgid "Automation strategy" -#~ msgstr "ホスト名戦略" - -#, fuzzy -#~| msgid "Approve strategy" -#~ msgid "Discovery strategy" -#~ msgstr "戦略を承認する" - -#, fuzzy -#~| msgid "Hostname strategy" -#~ msgid "Reconcile strategy" -#~ msgstr "ホスト名戦略" - -#, fuzzy -#~| msgid "Can change auth setting" -#~ msgid "Change auth strategy" -#~ msgstr "資格認定の設定" - -#~ msgid "System User" -#~ msgstr "システムユーザー" - -#~ msgid "Unsupported protocols: {}" -#~ msgstr "サポートされていないプロトコル: {}" - -#~ msgid "Custom" -#~ msgstr "カスタム" - -#~ msgid "Can view application account secret" -#~ msgstr "アプリケーションアカウントの秘密を表示できます" - -#~ msgid "Can change application account secret" -#~ msgstr "アプリケーションアカウントの秘密を変更できます" - -#~ msgid "Application user" -#~ msgstr "アプリケーションユーザー" - -#~ msgid "Application display" -#~ msgstr "アプリケーション表示" - -#~ msgid "Cluster" -#~ msgstr "クラスター" - -#~ msgid "Asset Info" -#~ msgstr "資産情報" - -#~ msgid "Application path" -#~ msgstr "アプリケーションパス" - -#~ msgid "Target URL" -#~ msgstr "ターゲットURL" - -#~ msgid "Chrome username" -#~ msgstr "Chromeユーザー名" - -#~ msgid "Chrome password" -#~ msgstr "Chromeパスワード" - -#~ msgid "Operating parameter" -#~ msgstr "操作パラメータ" - -#~ msgid "Target url" -#~ msgstr "ターゲットURL" - -#~ msgid "Mysql workbench username" -#~ msgstr "Mysql workbench のユーザー名" - -#~ msgid "Mysql workbench password" -#~ msgstr "Mysql workbench パスワード" - -#~ msgid "Magnus currently supports only 11g and 12c connections" -#~ msgstr "" -#~ "現在、Magnusは11gおよび12cバージョンへの接続のみをサポートしています" - -#~ msgid "Vmware username" -#~ msgstr "Vmware ユーザー名" - -#~ msgid "Vmware password" -#~ msgstr "Vmware パスワード" - -#~ msgid "Base" -#~ msgstr "ベース" - -#~ msgid "Public IP" -#~ msgstr "パブリックIP" - -#~ msgid "AuthBook" -#~ msgstr "資産アカウント" - -#~ msgid "Can test asset account connectivity" -#~ msgstr "アセットアカウントの接続性をテストできます" - -#~ msgid "Bandwidth" -#~ msgstr "帯域幅" - -#~ msgid "Contact" -#~ msgstr "連絡先" - -#~ msgid "Intranet" -#~ msgstr "イントラネット" - -#~ msgid "Extranet" -#~ msgstr "エクストラネット" - -#~ msgid "Operator" -#~ msgstr "オペレーター" - -#~ msgid "Default Cluster" -#~ msgstr "デフォルトクラスター" - -#~ msgid "User groups" -#~ msgstr "ユーザーグループ" - -#~ msgid "System user display" -#~ msgstr "システムユーザー表示" - -#~ msgid "Protocol format should {}/{}" -#~ msgstr "プロトコル形式は {}/{}" - -#~ msgid "Nodes name" -#~ msgstr "ノード名" - -#~ msgid "Labels name" -#~ msgstr "ラベル名" - -#~ msgid "Hardware info" -#~ msgstr "ハードウェア情報" - -#~ msgid "Admin user display" -#~ msgstr "管理者ユーザー表示" - -#~ msgid "CPU info" -#~ msgstr "CPU情報" - -#~ msgid "Applications amount" -#~ msgstr "申し込み金額" - -#~ msgid "SSH key fingerprint" -#~ msgstr "SSHキー指紋" - -#~ msgid "Apps amount" -#~ msgstr "アプリの量" - -#~ msgid "Login mode display" -#~ msgstr "ログインモード表示" - -#~ msgid "Ad domain" -#~ msgstr "広告ドメイン" - -#~ msgid "Is asset protocol" -#~ msgstr "資産プロトコルです" - -#~ msgid "Only ssh and automatic login system users are supported" -#~ msgstr "sshと自動ログインシステムのユーザーのみがサポートされています" - -#~ msgid "Username same with user with protocol {} only allow 1" -#~ msgstr "プロトコル {} のユーザーと同じユーザー名は1のみ許可します" - -#~ msgid "* Automatic login mode must fill in the username." -#~ msgstr "* 自動ログインモードはユーザー名を入力する必要があります。" - -#~ msgid "Path should starts with /" -#~ msgstr "パスは/で始まる必要があります" - -#~ msgid "Password or private key required" -#~ msgstr "パスワードまたは秘密鍵が必要" - -#~ msgid "Only ssh protocol system users are allowed" -#~ msgstr "Sshプロトコルシステムユーザーのみが許可されています" - -#~ msgid "The protocol must be consistent with the current user: {}" -#~ msgstr "プロトコルは現在のユーザーと一致している必要があります: {}" - -#~ msgid "Only system users with automatic login are allowed" -#~ msgstr "自動ログインを持つシステムユーザーのみが許可されます" - -#~ msgid "System user name" -#~ msgstr "システムユーザー名" - -#~ msgid "Asset hostname" -#~ msgstr "資産ホスト名" - -#~ msgid "System user is dynamic: {}" -#~ msgstr "システムユーザーは動的です: {}" - -#~ msgid "Start push system user for platform: [{}]" -#~ msgstr "プラットフォームのプッシュシステムユーザーを開始: [{}]" - -#~ msgid "Hosts count: {}" -#~ msgstr "ホスト数: {}" - -#~ msgid "Push system users to assets: " -#~ msgstr "システムユーザーを資産にプッシュする:" - -#~ msgid "Push system users to asset: " -#~ msgstr "システムユーザーをアセットにプッシュする:" - -#~ msgid "Dynamic system user not support test" -#~ msgstr "動的システムユーザーがテストをサポートしていない" - -#~ msgid "Start test system user connectivity for platform: [{}]" -#~ msgstr "プラットフォームのテストシステムのユーザー接続を開始: [{}]" - -#~ msgid "Test system user connectivity: " -#~ msgstr "テストシステムユーザー接続:" - -#~ msgid "Test system user connectivity period: " -#~ msgstr "テストシステムユーザー接続期间:" - -#~ msgid "Hosts display" -#~ msgstr "ホスト表示" - -#~ msgid "Run as" -#~ msgstr "として実行" - -#~ msgid "Run as display" -#~ msgstr "ディスプレイとして実行する" - -#~ msgid "Asset and SystemUser" -#~ msgstr "資産およびシステム・ユーザー" - -#~ msgid "{Asset} ADD {SystemUser}" -#~ msgstr "{Asset} 追加 {SystemUser}" - -#~ msgid "{Asset} REMOVE {SystemUser}" -#~ msgstr "{Asset} 削除 {SystemUser}" - -#~ msgid "Asset permission and SystemUser" -#~ msgstr "資産権限とSystemUser" - -#~ msgid "{AssetPermission} ADD {SystemUser}" -#~ msgstr "{AssetPermission} 追加 {SystemUser}" - -#~ msgid "{AssetPermission} REMOVE {SystemUser}" -#~ msgstr "{AssetPermission} 削除 {SystemUser}" - -#~ msgid "User application permissions" -#~ msgstr "ユーザーアプリケーションの権限" - -#~ msgid "{ApplicationPermission} ADD {User}" -#~ msgstr "{ApplicationPermission} 追加 {User}" - -#~ msgid "{ApplicationPermission} REMOVE {User}" -#~ msgstr "{ApplicationPermission} 削除 {User}" - -#~ msgid "User group application permissions" -#~ msgstr "ユーザーグループアプリケーションの権限" - -#~ msgid "{ApplicationPermission} ADD {UserGroup}" -#~ msgstr "{ApplicationPermission} 追加 {UserGroup}" - -#~ msgid "{ApplicationPermission} REMOVE {UserGroup}" -#~ msgstr "{ApplicationPermission} 削除 {UserGroup}" - -#~ msgid "Application permission" -#~ msgstr "申請許可" - -#~ msgid "{ApplicationPermission} ADD {Application}" -#~ msgstr "{ApplicationPermission} 追加 {Application}" - -#~ msgid "{ApplicationPermission} REMOVE {Application}" -#~ msgstr "{ApplicationPermission} 削除 {Application}" - -#~ msgid "Application permission and SystemUser" -#~ msgstr "アプリケーション権限とSystemUser" - -#~ msgid "{ApplicationPermission} ADD {SystemUser}" -#~ msgstr "{ApplicationPermission} 追加 {SystemUser}" - -#~ msgid "{ApplicationPermission} REMOVE {SystemUser}" -#~ msgstr "{ApplicationPermission} 削除 {SystemUser}" - -#~ msgid "Not has host {} permission" -#~ msgstr "ホスト {} 権限がありません" - -#~ msgid "" -#~ "eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " -#~ "crontab expressions (Online tools)
Note: If both Regularly " -#~ "perform and Cycle perform are set, give priority to Regularly perform" -#~ msgstr "" -#~ "eg:毎週日03:05<5 3**0>
ヒント:5ビットLinux crontab式<分時日月曜日>(オンラインワーク)
" -#~ "注意:定期実行と周期実行を同時に設定した場合は、定期実行を優先します。" - -#~ msgid "Unit: hour" -#~ msgstr "単位: 時間" - -#~ msgid "Callback" -#~ msgstr "コールバック" - -#~ msgid "Can view task monitor" -#~ msgstr "タスクモニターを表示できます" - -#~ msgid "Tasks" -#~ msgstr "タスク" - -#~ msgid "Options" -#~ msgstr "オプション" - -#~ msgid "Run as admin" -#~ msgstr "再実行" - -#~ msgid "Become" -#~ msgstr "になる" - -#~ msgid "Create by" -#~ msgstr "による作成" - -#~ msgid "AdHoc" -#~ msgstr "タスクの各バージョン" - -#~ msgid "Task display" -#~ msgstr "タスク表示" - -#~ msgid "Host amount" -#~ msgstr "ホスト量" - -#~ msgid "Start time" -#~ msgstr "開始時間" - -#~ msgid "End time" -#~ msgstr "終了時間" - -#~ msgid "Adhoc raw result" -#~ msgstr "アドホック生の結果" - -#~ msgid "Adhoc result summary" -#~ msgstr "アドホック結果の概要" - -#~ msgid "Task start" -#~ msgstr "タスクの開始" - -#~ msgid "Command `{}` is forbidden ........" -#~ msgstr "コマンド '{}' は禁止されています ........" - -#~ msgid "Task end" -#~ msgstr "タスク" - -#~ msgid "The administrator is modifying permissions. Please wait" -#~ msgstr "管理者は権限を変更しています。お待ちください" - -#~ msgid "The authorization cannot be revoked for the time being" -#~ msgstr "当分の間、承認を取り消すことはできません。" - -#~ msgid "Permed application" -#~ msgstr "許可されたアプリケーション" - -#~ msgid "Can view my apps" -#~ msgstr "自分のアプリを表示できます" - -#~ msgid "Can view user apps" -#~ msgstr "ユーザーアプリを表示できます" - -#~ msgid "Can view usergroup apps" -#~ msgstr "ユーザー・グループ認可の適用を表示できます" - -#~ msgid "Your permed applications is about to expire" -#~ msgstr "パーマアプリケーションの有効期限が近づいています" - -#~ msgid "permed applications" -#~ msgstr "Permedアプリケーション" - -#~ msgid "Application permissions is about to expire" -#~ msgstr "アプリケーション権限の有効期限が近づいています" - -#~ msgid "application permissions of organization {}" -#~ msgstr "Organization {} のアプリケーション権限" - -#~ msgid "System users amount" -#~ msgstr "システムユーザー数" - -#~ msgid "" -#~ "The application list contains applications that are different from the " -#~ "permission type. ({})" -#~ msgstr "" -#~ "アプリケーションリストには、権限タイプとは異なるアプリケーションが含まれて" -#~ "います。({})" - -#~ msgid "System users display" -#~ msgstr "システムユーザーの表示" - -#~ msgid "My applications" -#~ msgstr "私のアプリケーション" - -#~ msgid "Empty" -#~ msgstr "空" - -#~ msgid "System user ID" -#~ msgstr "システムユーザーID" - -#~ msgid "Apply for application" -#~ msgstr "申し込み" - -#~ msgid "" -#~ "Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " -#~ "processor: {}, ticket ID: {}" -#~ msgstr "" -#~ "チケットによって作成されたチケットタイトル: {}、チケット申請者: {}、チケッ" -#~ "ト処理者: {}、チケットID: {}" - -#~ msgid "Login system user" -#~ msgstr "ログインシステムユーザー" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 3f1c8bffe..0ff05dd98 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:3bd4186e087ac63f435e771d9238bcc249cf01332a7e169475ff80f7ecb7fdf9 -size 103601 +oid sha256:1c09abdddb5699aeaf832e1162b58ea9b520c10df3f80390c0ec680da3e18f4d +size 103641 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po deleted file mode 100644 index 1f91ddca8..000000000 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ /dev/null @@ -1,7552 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: JumpServer 0.3.3\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-12-04 17:56+0800\n" -"PO-Revision-Date: 2021-05-20 10:54+0800\n" -"Last-Translator: ibuler \n" -"Language-Team: JumpServer team\n" -"Language: zh_CN\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.4.3\n" - -#: acls/apps.py:7 -msgid "Acls" -msgstr "访问控制" - -#: acls/models/base.py:20 tickets/const.py:45 -#: tickets/templates/tickets/approve_check_password.html:49 -msgid "Reject" -msgstr "拒绝" - -#: acls/models/base.py:21 -msgid "Accept" -msgstr "同意" - -#: acls/models/base.py:22 -msgid "Review" -msgstr "复核" - -#: acls/models/base.py:71 acls/models/command_acl.py:22 -#: acls/serializers/base.py:34 applications/models.py:10 -#: assets/models/_user.py:33 assets/models/asset/common.py:81 -#: assets/models/asset/common.py:91 assets/models/base.py:54 -#: assets/models/cmd_filter.py:21 assets/models/domain.py:24 -#: assets/models/group.py:20 assets/models/label.py:17 -#: assets/models/platform.py:21 assets/models/platform.py:72 -#: assets/serializers/asset/common.py:86 assets/serializers/platform.py:138 -#: ops/mixin.py:20 ops/models/adhoc.py:21 ops/models/celery.py:15 -#: ops/models/job.py:34 ops/models/playbook.py:14 orgs/models.py:70 -#: perms/models/asset_permission.py:51 rbac/models/role.py:29 -#: settings/models.py:33 settings/serializers/sms.py:6 -#: terminal/models/applet/applet.py:20 terminal/models/component/endpoint.py:11 -#: terminal/models/component/endpoint.py:87 -#: terminal/models/component/storage.py:25 terminal/models/component/task.py:16 -#: terminal/models/component/terminal.py:79 users/forms/profile.py:33 -#: users/models/group.py:15 users/models/user.py:665 -#: xpack/plugins/cloud/models.py:30 -msgid "Name" -msgstr "名称" - -#: acls/models/base.py:73 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:90 -msgid "Priority" -msgstr "优先级" - -#: acls/models/base.py:74 assets/models/_user.py:47 -#: assets/models/cmd_filter.py:72 terminal/models/component/endpoint.py:91 -msgid "1-100, the lower the value will be match first" -msgstr "优先级可选范围为 1-100 (数值越小越优先)" - -#: acls/models/base.py:77 acls/serializers/base.py:63 -#: assets/models/cmd_filter.py:77 audits/models.py:50 audits/serializers.py:69 -#: authentication/templates/authentication/_access_key_modal.html:34 -msgid "Action" -msgstr "动作" - -#: acls/models/base.py:78 acls/serializers/base.py:59 -#: acls/serializers/login_acl.py:23 assets/models/cmd_filter.py:82 -msgid "Reviewers" -msgstr "复核人" - -#: acls/models/base.py:79 authentication/models/access_key.py:15 -#: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/asset_permission.py:72 terminal/models/session/sharing.py:28 -#: tickets/const.py:37 -msgid "Active" -msgstr "激活中" - -#: acls/models/base.py:80 acls/models/command_acl.py:29 -#: applications/models.py:19 assets/models/_user.py:40 -#: assets/models/asset/common.py:100 assets/models/automations/base.py:22 -#: assets/models/backup.py:29 assets/models/base.py:62 -#: assets/models/cmd_filter.py:36 assets/models/cmd_filter.py:84 -#: assets/models/domain.py:25 assets/models/group.py:23 -#: assets/models/label.py:22 assets/models/platform.py:77 -#: ops/models/adhoc.py:27 ops/models/job.py:50 ops/models/playbook.py:17 -#: orgs/models.py:74 perms/models/asset_permission.py:71 rbac/models/role.py:37 -#: settings/models.py:38 terminal/models/applet/applet.py:28 -#: terminal/models/applet/applet.py:61 terminal/models/applet/host.py:107 -#: terminal/models/component/endpoint.py:24 -#: terminal/models/component/endpoint.py:97 -#: terminal/models/component/storage.py:28 -#: terminal/models/component/terminal.py:91 tickets/models/comment.py:32 -#: tickets/models/ticket/general.py:296 users/models/group.py:16 -#: users/models/user.py:702 xpack/plugins/change_auth_plan/models/base.py:44 -#: xpack/plugins/cloud/models.py:37 xpack/plugins/cloud/models.py:118 -#: xpack/plugins/gathered_user/models.py:26 -msgid "Comment" -msgstr "备注" - -#: acls/models/base.py:91 acls/models/login_acl.py:13 -#: acls/serializers/base.py:55 acls/serializers/login_acl.py:21 -#: assets/models/cmd_filter.py:24 assets/models/label.py:15 audits/models.py:29 -#: audits/models.py:48 audits/models.py:79 -#: authentication/models/connection_token.py:25 -#: authentication/models/sso_token.py:15 perms/api/user_permission/mixin.py:69 -#: perms/models/asset_permission.py:53 perms/models/perm_token.py:12 -#: rbac/builtin.py:120 rbac/models/rolebinding.py:41 -#: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:13 -#: terminal/models/session/session.py:30 terminal/models/session/sharing.py:33 -#: terminal/notifications.py:91 terminal/notifications.py:139 -#: tickets/models/comment.py:21 users/const.py:14 users/models/user.py:895 -#: users/models/user.py:926 users/serializers/group.py:19 -msgid "User" -msgstr "用户" - -#: acls/models/base.py:93 acls/serializers/base.py:56 -#: assets/models/account.py:51 assets/models/asset/common.py:83 -#: assets/models/asset/common.py:212 assets/models/cmd_filter.py:32 -#: assets/models/gathered_user.py:14 assets/serializers/account/account.py:59 -#: assets/serializers/automations/change_secret.py:100 -#: assets/serializers/automations/change_secret.py:122 -#: assets/serializers/domain.py:17 assets/serializers/gathered_user.py:11 -#: assets/serializers/label.py:30 audits/models.py:33 -#: authentication/models/connection_token.py:29 -#: perms/models/asset_permission.py:59 perms/models/perm_token.py:13 -#: terminal/backends/command/models.py:21 -#: terminal/backends/command/serializers.py:14 -#: terminal/models/session/session.py:32 terminal/notifications.py:90 -#: xpack/plugins/change_auth_plan/models/asset.py:200 -#: xpack/plugins/change_auth_plan/serializers/asset.py:172 -#: xpack/plugins/cloud/models.py:219 -msgid "Asset" -msgstr "资产" - -#: acls/models/base.py:95 acls/serializers/base.py:57 -#: assets/models/account.py:61 -#: assets/serializers/automations/change_secret.py:101 -#: assets/serializers/automations/change_secret.py:123 ops/models/base.py:18 -#: perms/models/perm_token.py:14 terminal/models/session/session.py:34 -#: xpack/plugins/cloud/models.py:87 xpack/plugins/cloud/serializers/task.py:65 -msgid "Account" -msgstr "账号" - -#: acls/models/command_acl.py:17 assets/models/cmd_filter.py:56 -#: terminal/backends/command/serializers.py:15 -#: terminal/models/session/session.py:41 -#: terminal/templates/terminal/_msg_command_alert.html:12 -#: terminal/templates/terminal/_msg_command_execute_alert.html:10 -msgid "Command" -msgstr "命令" - -#: acls/models/command_acl.py:18 assets/models/cmd_filter.py:55 -msgid "Regex" -msgstr "正则表达式" - -#: acls/models/command_acl.py:25 applications/models.py:15 -#: assets/models/_user.py:46 assets/models/automations/base.py:20 -#: assets/models/cmd_filter.py:70 assets/models/platform.py:74 -#: assets/serializers/asset/common.py:63 -#: assets/serializers/automations/base.py:40 assets/serializers/platform.py:98 -#: audits/serializers.py:40 ops/models/job.py:42 -#: perms/serializers/user_permission.py:24 terminal/models/applet/applet.py:24 -#: terminal/models/component/storage.py:57 -#: terminal/models/component/storage.py:142 terminal/serializers/applet.py:33 -#: tickets/models/comment.py:26 tickets/models/flow.py:57 -#: tickets/models/ticket/apply_application.py:16 -#: tickets/models/ticket/general.py:274 tickets/serializers/flow.py:54 -#: tickets/serializers/ticket/ticket.py:18 -#: xpack/plugins/change_auth_plan/models/app.py:27 -#: xpack/plugins/change_auth_plan/models/app.py:152 -msgid "Type" -msgstr "类型" - -#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 -#: settings/serializers/basic.py:10 xpack/plugins/license/models.py:29 -msgid "Content" -msgstr "内容" - -#: acls/models/command_acl.py:27 assets/models/cmd_filter.py:75 -msgid "One line one command" -msgstr "每行一个命令" - -#: acls/models/command_acl.py:28 assets/models/cmd_filter.py:76 -msgid "Ignore case" -msgstr "忽略大小写" - -#: acls/models/command_acl.py:35 -#, fuzzy -#| msgid "Command record" -msgid "Command group" -msgstr "命令记录" - -#: acls/models/command_acl.py:88 -msgid "The generated regular expression is incorrect: {}" -msgstr "生成的正则表达式有误" - -#: acls/models/command_acl.py:107 acls/serializers/command_filter.py:19 -#, fuzzy -#| msgid "Command" -msgid "Commands" -msgstr "命令" - -#: acls/models/command_acl.py:114 -#, fuzzy -#| msgid "Command" -msgid "Command acl" -msgstr "命令" - -#: acls/models/command_acl.py:120 tickets/const.py:11 -msgid "Command confirm" -msgstr "命令复核" - -#: acls/models/login_acl.py:16 -msgid "Rule" -msgstr "规则" - -#: acls/models/login_acl.py:20 -msgid "Login acl" -msgstr "登录访问控制" - -#: acls/models/login_acl.py:54 tickets/const.py:10 -msgid "Login confirm" -msgstr "登录复核" - -#: acls/models/login_asset_acl.py:12 -msgid "Login asset acl" -msgstr "登录资产访问控制" - -#: acls/models/login_asset_acl.py:21 tickets/const.py:12 -msgid "Login asset confirm" -msgstr "登录资产复核" - -#: acls/serializers/base.py:10 acls/serializers/login_acl.py:16 -msgid "Format for comma-delimited string, with * indicating a match all. " -msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " - -#: acls/serializers/base.py:18 acls/serializers/base.py:49 -#: assets/models/_user.py:34 assets/models/base.py:55 -#: assets/models/gathered_user.py:15 audits/models.py:95 -#: authentication/forms.py:25 authentication/forms.py:27 -#: authentication/models/temp_token.py:9 -#: authentication/templates/authentication/_msg_different_city.html:9 -#: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: users/forms/profile.py:32 users/models/user.py:663 -#: users/templates/users/_msg_user_created.html:12 -#: xpack/plugins/change_auth_plan/models/asset.py:35 -#: xpack/plugins/change_auth_plan/models/asset.py:196 -#: xpack/plugins/cloud/serializers/account_attrs.py:26 -msgid "Username" -msgstr "用户名" - -#: acls/serializers/base.py:25 -msgid "" -"Format for comma-delimited string, with * indicating a match all. Such as: " -"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" -"db8:1a:1110::/64 (Domain name support)" -msgstr "" -"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " -"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" - -#: acls/serializers/base.py:40 assets/serializers/asset/host.py:40 -msgid "IP/Host" -msgstr "IP/主机名" - -#: acls/serializers/base.py:85 tickets/serializers/ticket/ticket.py:66 -msgid "The organization `{}` does not exist" -msgstr "组织 `{}` 不存在" - -#: acls/serializers/base.py:91 -msgid "None of the reviewers belong to Organization `{}`" -msgstr "所有复核人都不属于组织 `{}`" - -#: acls/serializers/rules/rules.py:20 -#: xpack/plugins/cloud/serializers/task.py:22 -msgid "IP address invalid: `{}`" -msgstr "IP 地址无效: `{}`" - -#: acls/serializers/rules/rules.py:25 -msgid "" -"Format for comma-delimited string, with * indicating a match all. Such as: " -"192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:" -"db8:1a:1110::/64 " -msgstr "" -"格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, " -"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64" - -#: acls/serializers/rules/rules.py:33 assets/models/asset/common.py:92 -#: authentication/templates/authentication/_msg_oauth_bind.html:12 -#: authentication/templates/authentication/_msg_rest_password_success.html:8 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:8 -#: settings/serializers/terminal.py:8 terminal/serializers/endpoint.py:54 -msgid "IP" -msgstr "IP" - -#: acls/serializers/rules/rules.py:35 -msgid "Time Period" -msgstr "时段" - -#: applications/apps.py:9 -msgid "Applications" -msgstr "应用管理" - -#: applications/models.py:12 assets/models/label.py:20 -#: assets/models/platform.py:73 assets/serializers/asset/common.py:62 -#: assets/serializers/cagegory.py:8 assets/serializers/platform.py:99 -#: assets/serializers/platform.py:139 perms/serializers/user_permission.py:23 -#: tickets/models/ticket/apply_application.py:13 -#: xpack/plugins/change_auth_plan/models/app.py:24 -msgid "Category" -msgstr "类别" - -#: applications/models.py:17 xpack/plugins/cloud/models.py:35 -#: xpack/plugins/cloud/serializers/account.py:61 -msgid "Attrs" -msgstr "属性" - -#: applications/models.py:23 -msgid "Application" -msgstr "应用程序" - -#: applications/models.py:27 -msgid "Can match application" -msgstr "匹配应用" - -#: assets/api/automations/base.py:76 -#: xpack/plugins/change_auth_plan/api/asset.py:94 -msgid "The parameter 'action' must be [{}]" -msgstr "参数 'action' 必须是 [{}]" - -#: assets/api/domain.py:56 -msgid "Number required" -msgstr "需要为数字" - -#: assets/api/node.py:62 -msgid "You can't update the root node name" -msgstr "不能修改根节点名称" - -#: assets/api/node.py:69 -msgid "You can't delete the root node ({})" -msgstr "不能删除根节点 ({})" - -#: assets/api/node.py:72 -msgid "Deletion failed and the node contains assets" -msgstr "删除失败,节点包含资产" - -#: assets/apps.py:9 -msgid "App assets" -msgstr "资产管理" - -#: assets/automations/base/manager.py:123 -msgid "{} disabled" -msgstr "{} 已禁用" - -#: assets/const/account.py:6 audits/const.py:6 audits/const.py:63 -#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37 -#: common/utils/ip/utils.py:84 -msgid "Unknown" -msgstr "未知" - -#: assets/const/account.py:7 -msgid "Ok" -msgstr "成功" - -#: assets/const/account.py:8 -#: assets/serializers/automations/change_secret.py:118 -#: assets/serializers/automations/change_secret.py:146 audits/const.py:74 -#: common/const/choices.py:19 -#: xpack/plugins/change_auth_plan/serializers/asset.py:190 -#: xpack/plugins/cloud/const.py:33 -msgid "Failed" -msgstr "失败" - -#: assets/const/account.py:12 assets/models/_user.py:35 -#: audits/signal_handlers.py:46 authentication/confirm/password.py:9 -#: authentication/forms.py:32 -#: authentication/templates/authentication/login.html:228 -#: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46 -#: users/forms/profile.py:22 users/serializers/user.py:105 -#: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models/base.py:42 -#: xpack/plugins/change_auth_plan/models/base.py:117 -#: xpack/plugins/change_auth_plan/models/base.py:192 -#: xpack/plugins/change_auth_plan/serializers/base.py:21 -#: xpack/plugins/change_auth_plan/serializers/base.py:73 -#: xpack/plugins/cloud/serializers/account_attrs.py:28 -msgid "Password" -msgstr "密码" - -#: assets/const/account.py:13 -msgid "SSH key" -msgstr "SSH 密钥" - -#: assets/const/account.py:14 authentication/models/access_key.py:31 -msgid "Access key" -msgstr "Access key" - -#: assets/const/account.py:15 assets/models/_user.py:38 -#: authentication/models/sso_token.py:13 -msgid "Token" -msgstr "Token" - -#: assets/const/automation.py:13 -msgid "Ping" -msgstr "" - -#: assets/const/automation.py:14 -msgid "Gather facts" -msgstr "收集信息" - -#: assets/const/automation.py:15 -msgid "Create account" -msgstr "收集账号" - -#: assets/const/automation.py:16 -msgid "Change secret" -msgstr "改密" - -#: assets/const/automation.py:17 -msgid "Verify account" -msgstr "验证密钥" - -#: assets/const/automation.py:18 -msgid "Gather accounts" -msgstr "收集账号" - -#: assets/const/automation.py:38 assets/serializers/account/base.py:26 -msgid "Specific" -msgstr "指定" - -#: assets/const/automation.py:39 ops/const.py:20 -#: xpack/plugins/change_auth_plan/models/base.py:28 -msgid "All assets use the same random password" -msgstr "使用相同的随机密码" - -#: assets/const/automation.py:40 ops/const.py:21 -#: xpack/plugins/change_auth_plan/models/base.py:29 -msgid "All assets use different random password" -msgstr "使用不同的随机密码" - -#: assets/const/automation.py:44 ops/const.py:13 -#: xpack/plugins/change_auth_plan/models/asset.py:30 -msgid "Append SSH KEY" -msgstr "追加" - -#: assets/const/automation.py:45 ops/const.py:14 -#: xpack/plugins/change_auth_plan/models/asset.py:31 -msgid "Empty and append SSH KEY" -msgstr "清空所有并添加" - -#: assets/const/automation.py:46 ops/const.py:15 -#: xpack/plugins/change_auth_plan/models/asset.py:32 -msgid "Replace (The key generated by JumpServer) " -msgstr "替换 (由 JumpServer 生成的密钥)" - -#: assets/const/category.py:11 settings/serializers/auth/radius.py:14 -#: settings/serializers/auth/sms.py:56 terminal/models/applet/applet.py:59 -#: terminal/models/component/endpoint.py:12 -#: xpack/plugins/cloud/serializers/account_attrs.py:72 -msgid "Host" -msgstr "主机" - -#: assets/const/category.py:12 -msgid "Device" -msgstr "网络设备" - -#: assets/const/category.py:13 assets/models/asset/database.py:8 -#: assets/models/asset/database.py:34 -#: xpack/plugins/change_auth_plan/models/app.py:31 -msgid "Database" -msgstr "数据库" - -#: assets/const/category.py:14 -msgid "Cloud service" -msgstr "云服务" - -#: assets/const/category.py:15 audits/const.py:61 -#: terminal/models/applet/applet.py:18 -msgid "Web" -msgstr "Web" - -#: assets/const/device.py:7 terminal/models/applet/applet.py:17 -#: tickets/const.py:8 -msgid "General" -msgstr "通用" - -#: assets/const/device.py:8 -msgid "Switch" -msgstr "交换机" - -#: assets/const/device.py:9 -msgid "Router" -msgstr "路由器" - -#: assets/const/device.py:10 -msgid "Firewall" -msgstr "防火墙" - -#: assets/const/web.py:7 -msgid "Website" -msgstr "网站" - -#: assets/models/_user.py:24 -msgid "Automatic managed" -msgstr "托管的" - -#: assets/models/_user.py:25 -msgid "Manually input" -msgstr "手动输入" - -#: assets/models/_user.py:29 -msgid "Common user" -msgstr "普通用户" - -#: assets/models/_user.py:30 -msgid "Admin user" -msgstr "特权用户" - -#: assets/models/_user.py:36 xpack/plugins/change_auth_plan/models/asset.py:54 -#: xpack/plugins/change_auth_plan/models/asset.py:131 -#: xpack/plugins/change_auth_plan/models/asset.py:207 -msgid "SSH private key" -msgstr "SSH 密钥" - -#: assets/models/_user.py:37 xpack/plugins/change_auth_plan/models/asset.py:57 -#: xpack/plugins/change_auth_plan/models/asset.py:127 -#: xpack/plugins/change_auth_plan/models/asset.py:203 -msgid "SSH public key" -msgstr "SSH 公钥" - -#: assets/models/_user.py:41 assets/models/automations/base.py:92 -#: assets/models/domain.py:26 assets/models/gathered_user.py:19 -#: assets/models/group.py:22 common/db/models.py:76 common/mixins/models.py:50 -#: ops/models/base.py:54 ops/models/job.py:108 orgs/models.py:73 -#: perms/models/asset_permission.py:74 users/models/group.py:18 -#: users/models/user.py:927 -msgid "Date created" -msgstr "创建日期" - -#: assets/models/_user.py:42 assets/models/gathered_user.py:20 -#: common/db/models.py:77 common/mixins/models.py:51 -msgid "Date updated" -msgstr "更新日期" - -#: assets/models/_user.py:43 assets/models/base.py:63 -#: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:87 -#: assets/models/group.py:21 common/db/models.py:74 common/mixins/models.py:49 -#: orgs/models.py:71 perms/models/asset_permission.py:75 -#: users/models/user.py:710 users/serializers/group.py:33 -#: xpack/plugins/change_auth_plan/models/base.py:48 -msgid "Created by" -msgstr "创建者" - -#: assets/models/_user.py:45 -msgid "Username same with user" -msgstr "用户名与用户相同" - -#: assets/models/_user.py:48 authentication/models/connection_token.py:35 -#: perms/models/perm_token.py:16 terminal/models/applet/applet.py:26 -#: terminal/serializers/session.py:18 terminal/serializers/session.py:32 -#: terminal/serializers/storage.py:68 -msgid "Protocol" -msgstr "协议" - -#: assets/models/_user.py:49 -msgid "Auto push" -msgstr "自动推送" - -#: assets/models/_user.py:50 -msgid "Sudo" -msgstr "Sudo" - -#: assets/models/_user.py:51 ops/models/adhoc.py:17 ops/models/job.py:30 -msgid "Shell" -msgstr "Shell" - -#: assets/models/_user.py:52 -msgid "Login mode" -msgstr "认证方式" - -#: assets/models/_user.py:53 -msgid "SFTP Root" -msgstr "SFTP根路径" - -#: assets/models/_user.py:54 -msgid "Home" -msgstr "家目录" - -#: assets/models/_user.py:55 -msgid "System groups" -msgstr "用户组" - -#: assets/models/_user.py:58 -msgid "User switch" -msgstr "用户切换" - -#: assets/models/_user.py:59 -msgid "Switch from" -msgstr "切换自" - -#: assets/models/_user.py:65 audits/models.py:34 -#: terminal/backends/command/models.py:22 -#: terminal/backends/command/serializers.py:36 -#: xpack/plugins/change_auth_plan/models/app.py:35 -#: xpack/plugins/change_auth_plan/models/app.py:146 -msgid "System user" -msgstr "系统用户" - -#: assets/models/_user.py:67 -msgid "Can match system user" -msgstr "可以匹配系统用户" - -#: assets/models/account.py:45 common/db/fields.py:222 -#: settings/serializers/terminal.py:12 -msgid "All" -msgstr "全部" - -#: assets/models/account.py:46 -msgid "Manual input" -msgstr "手动输入" - -#: assets/models/account.py:47 -msgid "Dynamic user" -msgstr "动态用户" - -#: assets/models/account.py:55 -#: authentication/serializers/connection_token.py:108 -msgid "Su from" -msgstr "切换自" - -#: assets/models/account.py:57 settings/serializers/auth/cas.py:18 -#: terminal/models/applet/applet.py:22 -msgid "Version" -msgstr "版本" - -#: assets/models/account.py:67 -msgid "Can view asset account secret" -msgstr "可以查看资产账号密码" - -#: assets/models/account.py:68 -msgid "Can change asset account secret" -msgstr "可以更改资产账号密码" - -#: assets/models/account.py:69 -msgid "Can view asset history account" -msgstr "可以查看资产历史账号" - -#: assets/models/account.py:70 -msgid "Can view asset history account secret" -msgstr "可以查看资产历史账号密码" - -#: assets/models/account.py:93 assets/serializers/account/account.py:15 -msgid "Account template" -msgstr "账号模版" - -#: assets/models/account.py:98 -#, fuzzy -#| msgid "Can view asset account secret" -msgid "Can view asset account template secret" -msgstr "可以查看资产账号密码" - -#: assets/models/account.py:99 -#, fuzzy -#| msgid "Can change asset account secret" -msgid "Can change asset account template secret" -msgstr "可以更改资产账号密码" - -#: assets/models/asset/common.py:82 assets/models/platform.py:22 -#: settings/serializers/auth/radius.py:15 settings/serializers/auth/sms.py:57 -#: xpack/plugins/cloud/serializers/account_attrs.py:73 -msgid "Port" -msgstr "端口" - -#: assets/models/asset/common.py:93 assets/models/platform.py:110 -#: assets/serializers/asset/common.py:65 -#: perms/serializers/user_permission.py:21 -#: xpack/plugins/cloud/serializers/account_attrs.py:172 -msgid "Platform" -msgstr "资产平台" - -#: assets/models/asset/common.py:95 assets/models/domain.py:29 -#: assets/serializers/asset/common.py:64 -msgid "Domain" -msgstr "网域" - -#: assets/models/asset/common.py:97 assets/models/automations/base.py:18 -#: assets/serializers/asset/common.py:66 -#: assets/serializers/automations/base.py:21 -#: perms/models/asset_permission.py:62 -#: xpack/plugins/change_auth_plan/models/asset.py:44 -#: xpack/plugins/gathered_user/models.py:24 -msgid "Nodes" -msgstr "节点" - -#: assets/models/asset/common.py:98 assets/models/automations/base.py:21 -#: assets/models/base.py:61 assets/models/cmd_filter.py:35 -#: assets/models/label.py:21 terminal/models/applet/applet.py:25 -#: users/serializers/user.py:202 -msgid "Is active" -msgstr "激活" - -#: assets/models/asset/common.py:99 assets/serializers/asset/common.py:67 -msgid "Labels" -msgstr "标签管理" - -#: assets/models/asset/common.py:215 -msgid "Can refresh asset hardware info" -msgstr "可以更新资产硬件信息" - -#: assets/models/asset/common.py:216 -msgid "Can test asset connectivity" -msgstr "可以测试资产连接性" - -#: assets/models/asset/common.py:217 -#, fuzzy -#| msgid "Can push system user to asset" -msgid "Can push account to asset" -msgstr "可以推送系统用户到资产" - -#: assets/models/asset/common.py:218 -msgid "Can match asset" -msgstr "可以匹配资产" - -#: assets/models/asset/common.py:219 -msgid "Add asset to node" -msgstr "添加资产到节点" - -#: assets/models/asset/common.py:220 -msgid "Move asset to node" -msgstr "移动资产到节点" - -#: assets/models/asset/database.py:9 settings/serializers/email.py:36 -msgid "Use SSL" -msgstr "使用 SSL" - -#: assets/models/asset/database.py:10 -#, fuzzy -#| msgid "SP cert" -msgid "CA cert" -msgstr "SP 证书" - -#: assets/models/asset/database.py:11 -#, fuzzy -#| msgid "Client Secret" -msgid "Client cert" -msgstr "客户端密钥" - -#: assets/models/asset/database.py:12 -#, fuzzy -#| msgid "Client" -msgid "Client key" -msgstr "客户端" - -#: assets/models/asset/database.py:13 -#, fuzzy -#| msgid "Host invalid" -msgid "Allow invalid cert" -msgstr "主机无效" - -#: assets/models/asset/web.py:9 audits/const.py:67 -#: terminal/serializers/applet_host.py:25 -msgid "Disabled" -msgstr "禁用" - -#: assets/models/asset/web.py:10 -msgid "Basic" -msgstr "" - -#: assets/models/asset/web.py:11 assets/models/asset/web.py:17 -msgid "Script" -msgstr "" - -#: assets/models/asset/web.py:13 -msgid "Autofill" -msgstr "自动填充" - -#: assets/models/asset/web.py:14 assets/serializers/platform.py:30 -msgid "Username selector" -msgstr "用户名选择器" - -#: assets/models/asset/web.py:15 assets/serializers/platform.py:33 -msgid "Password selector" -msgstr "密码选择器" - -#: assets/models/asset/web.py:16 assets/serializers/platform.py:36 -msgid "Submit selector" -msgstr "提交按钮选择器" - -#: assets/models/automations/base.py:17 assets/models/cmd_filter.py:34 -#: assets/serializers/asset/common.py:69 perms/models/asset_permission.py:65 -#: perms/serializers/permission.py:32 rbac/tree.py:37 -msgid "Accounts" -msgstr "账号管理" - -#: assets/models/automations/base.py:19 -#: assets/serializers/automations/base.py:20 assets/serializers/domain.py:29 -#: ops/models/base.py:17 ops/models/job.py:44 -#: terminal/templates/terminal/_msg_command_execute_alert.html:16 -#: xpack/plugins/change_auth_plan/models/asset.py:40 -msgid "Assets" -msgstr "资产" - -#: assets/models/automations/base.py:82 assets/models/automations/base.py:89 -msgid "Automation task" -msgstr "自动化任务" - -#: assets/models/automations/base.py:91 audits/models.py:115 -#: audits/serializers.py:41 ops/models/base.py:49 ops/models/job.py:102 -#: terminal/models/applet/applet.py:60 terminal/models/applet/host.py:104 -#: terminal/models/component/status.py:27 terminal/serializers/applet.py:22 -#: tickets/models/ticket/general.py:282 tickets/serializers/ticket/ticket.py:19 -#: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:223 -msgid "Status" -msgstr "状态" - -#: assets/models/automations/base.py:93 assets/models/backup.py:76 -#: audits/models.py:40 ops/models/base.py:55 ops/models/celery.py:59 -#: ops/models/job.py:109 perms/models/asset_permission.py:67 -#: terminal/models/applet/host.py:105 terminal/models/session/session.py:43 -#: tickets/models/ticket/apply_application.py:30 -#: tickets/models/ticket/apply_asset.py:19 -#: xpack/plugins/change_auth_plan/models/base.py:108 -#: xpack/plugins/change_auth_plan/models/base.py:199 -#: xpack/plugins/gathered_user/models.py:71 -msgid "Date start" -msgstr "开始日期" - -#: assets/models/automations/base.py:94 -#: assets/models/automations/change_secret.py:59 ops/models/base.py:56 -#: ops/models/celery.py:60 ops/models/job.py:110 -#: terminal/models/applet/host.py:106 -msgid "Date finished" -msgstr "结束日期" - -#: assets/models/automations/base.py:96 -#: assets/serializers/automations/base.py:39 -msgid "Automation snapshot" -msgstr "自动化快照" - -#: assets/models/automations/base.py:100 assets/models/backup.py:87 -#: assets/serializers/account/backup.py:37 -#: assets/serializers/automations/base.py:41 -#: xpack/plugins/change_auth_plan/models/base.py:121 -#: xpack/plugins/change_auth_plan/serializers/base.py:78 -msgid "Trigger mode" -msgstr "触发模式" - -#: assets/models/automations/base.py:104 -#: assets/serializers/automations/change_secret.py:103 -msgid "Automation task execution" -msgstr "自动化任务执行" - -#: assets/models/automations/change_secret.py:15 assets/models/base.py:57 -#: assets/serializers/account/account.py:97 assets/serializers/base.py:13 -msgid "Secret type" -msgstr "密文类型" - -#: assets/models/automations/change_secret.py:19 -#: assets/serializers/automations/change_secret.py:25 -msgid "Secret strategy" -msgstr "密钥策略" - -#: assets/models/automations/change_secret.py:21 -#: assets/models/automations/change_secret.py:57 assets/models/base.py:59 -#: assets/serializers/base.py:16 authentication/models/temp_token.py:10 -#: authentication/templates/authentication/_access_key_modal.html:31 -#: perms/models/perm_token.py:15 settings/serializers/auth/radius.py:17 -msgid "Secret" -msgstr "密钥" - -#: assets/models/automations/change_secret.py:22 -#: xpack/plugins/change_auth_plan/models/base.py:39 -msgid "Password rules" -msgstr "密码规则" - -#: assets/models/automations/change_secret.py:25 -msgid "SSH key change strategy" -msgstr "SSH 密钥策略" - -#: assets/models/automations/change_secret.py:27 assets/models/backup.py:27 -#: assets/serializers/account/backup.py:30 -#: assets/serializers/automations/change_secret.py:40 -#: xpack/plugins/change_auth_plan/models/app.py:40 -#: xpack/plugins/change_auth_plan/models/asset.py:63 -#: xpack/plugins/change_auth_plan/serializers/base.py:45 -msgid "Recipient" -msgstr "收件人" - -#: assets/models/automations/change_secret.py:34 -msgid "Change secret automation" -msgstr "自动化改密" - -#: assets/models/automations/change_secret.py:56 -msgid "Old secret" -msgstr "原来密码" - -#: assets/models/automations/change_secret.py:58 -msgid "Date started" -msgstr "开始日期" - -#: assets/models/automations/change_secret.py:61 common/const/choices.py:20 -msgid "Error" -msgstr "错误" - -#: assets/models/automations/change_secret.py:64 -msgid "Change secret record" -msgstr "改密记录" - -#: assets/models/automations/discovery_account.py:8 -msgid "Discovery account automation" -msgstr "自动化账号发现" - -#: assets/models/automations/gather_accounts.py:15 -#: assets/tasks/gather_accounts.py:28 -msgid "Gather asset accounts" -msgstr "收集资产账号" - -#: assets/models/automations/gather_facts.py:15 -msgid "Gather asset facts" -msgstr "收集资产信息" - -#: assets/models/automations/ping.py:15 -#, fuzzy -#| msgid "Login asset" -msgid "Ping asset" -msgstr "登录资产" - -#: assets/models/automations/push_account.py:16 -#, fuzzy -#| msgid "Is service account" -msgid "Push asset account" -msgstr "服务账号" - -#: assets/models/automations/verify_account.py:15 -#, fuzzy -#| msgid "Verify account" -msgid "Verify asset account" -msgstr "验证密钥" - -#: assets/models/backup.py:37 assets/models/backup.py:95 -msgid "Account backup plan" -msgstr "账号备份计划" - -#: assets/models/backup.py:79 -#: authentication/templates/authentication/_msg_oauth_bind.html:11 -#: notifications/notifications.py:186 -#: xpack/plugins/change_auth_plan/models/base.py:111 -#: xpack/plugins/change_auth_plan/models/base.py:200 -#: xpack/plugins/gathered_user/models.py:74 -msgid "Time" -msgstr "时间" - -#: assets/models/backup.py:83 -msgid "Account backup snapshot" -msgstr "账号备份快照" - -#: assets/models/backup.py:90 audits/models.py:110 -#: terminal/models/session/sharing.py:108 -#: xpack/plugins/change_auth_plan/models/base.py:197 -#: xpack/plugins/change_auth_plan/serializers/asset.py:171 -#: xpack/plugins/cloud/models.py:175 -msgid "Reason" -msgstr "原因" - -#: assets/models/backup.py:92 -#: assets/serializers/automations/change_secret.py:99 -#: assets/serializers/automations/change_secret.py:124 -#: terminal/serializers/session.py:36 -#: xpack/plugins/change_auth_plan/models/base.py:198 -#: xpack/plugins/change_auth_plan/serializers/asset.py:173 -msgid "Is success" -msgstr "是否成功" - -#: assets/models/backup.py:99 -msgid "Account backup execution" -msgstr "账号备份执行" - -#: assets/models/base.py:26 -msgid "Connectivity" -msgstr "可连接性" - -#: assets/models/base.py:28 authentication/models/temp_token.py:12 -msgid "Date verified" -msgstr "校验日期" - -#: assets/models/base.py:60 -msgid "Privileged" -msgstr "特权账号" - -#: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:56 -#: users/models/group.py:31 users/models/user.py:671 -msgid "User group" -msgstr "用户组" - -#: assets/models/cmd_filter.py:48 -msgid "Command filter" -msgstr "命令过滤器" - -#: assets/models/cmd_filter.py:62 -msgid "Deny" -msgstr "拒绝" - -#: assets/models/cmd_filter.py:63 -msgid "Allow" -msgstr "允许" - -#: assets/models/cmd_filter.py:64 -msgid "Reconfirm" -msgstr "复核" - -#: assets/models/cmd_filter.py:68 -msgid "Filter" -msgstr "过滤器" - -#: assets/models/cmd_filter.py:91 -msgid "Command filter rule" -msgstr "命令过滤规则" - -#: assets/models/domain.py:153 -#, python-brace-format -msgid "Unable to connect to port {port} on {address}" -msgstr "无法连接到 {address} 上的端口 {port}" - -#: assets/models/domain.py:156 authentication/middleware.py:76 -#: xpack/plugins/cloud/providers/fc.py:48 -msgid "Authentication failed" -msgstr "认证失败" - -#: assets/models/domain.py:158 assets/models/domain.py:185 -msgid "Connect failed" -msgstr "连接失败" - -#: assets/models/gathered_user.py:16 -msgid "Present" -msgstr "存在" - -#: assets/models/gathered_user.py:17 -msgid "Date last login" -msgstr "最后登录日期" - -#: assets/models/gathered_user.py:18 -msgid "IP last login" -msgstr "最后登录IP" - -#: assets/models/gathered_user.py:31 -msgid "GatherUser" -msgstr "收集用户" - -#: assets/models/group.py:30 -msgid "Asset group" -msgstr "资产组" - -#: assets/models/group.py:34 assets/models/platform.py:19 -#: xpack/plugins/cloud/providers/nutanix.py:30 -msgid "Default" -msgstr "默认" - -#: assets/models/group.py:34 -msgid "Default asset group" -msgstr "默认资产组" - -#: assets/models/label.py:14 rbac/const.py:6 users/models/user.py:912 -msgid "System" -msgstr "系统" - -#: assets/models/label.py:18 assets/models/node.py:553 -#: assets/serializers/cagegory.py:7 assets/serializers/cagegory.py:14 -#: authentication/models/connection_token.py:22 -#: common/drf/serializers/common.py:82 settings/models.py:34 -msgid "Value" -msgstr "值" - -#: assets/models/label.py:36 assets/serializers/cagegory.py:6 -#: assets/serializers/cagegory.py:13 common/drf/serializers/common.py:81 -#: settings/serializers/sms.py:7 -msgid "Label" -msgstr "标签" - -#: assets/models/node.py:158 -msgid "New node" -msgstr "新节点" - -#: assets/models/node.py:481 -msgid "empty" -msgstr "空" - -#: assets/models/node.py:552 perms/models/perm_node.py:21 -msgid "Key" -msgstr "键" - -#: assets/models/node.py:554 assets/serializers/node.py:20 -msgid "Full value" -msgstr "全称" - -#: assets/models/node.py:558 perms/models/perm_node.py:22 -msgid "Parent key" -msgstr "ssh私钥" - -#: assets/models/node.py:567 xpack/plugins/cloud/models.py:98 -#: xpack/plugins/cloud/serializers/task.py:68 -msgid "Node" -msgstr "节点" - -#: assets/models/node.py:570 -msgid "Can match node" -msgstr "可以匹配节点" - -#: assets/models/platform.py:20 -msgid "Required" -msgstr "必需的" - -#: assets/models/platform.py:23 users/templates/users/reset_password.html:29 -msgid "Setting" -msgstr "设置" - -#: assets/models/platform.py:42 audits/const.py:68 settings/models.py:37 -#: terminal/serializers/applet_host.py:26 -msgid "Enabled" -msgstr "启用" - -#: assets/models/platform.py:43 -msgid "Ansible config" -msgstr "Ansible 配置" - -#: assets/models/platform.py:44 -msgid "Ping enabled" -msgstr "探测资产" - -#: assets/models/platform.py:45 -msgid "Ping method" -msgstr "探测方式" - -#: assets/models/platform.py:46 assets/models/platform.py:56 -msgid "Gather facts enabled" -msgstr "收集资产信息" - -#: assets/models/platform.py:47 assets/models/platform.py:58 -msgid "Gather facts method" -msgstr "收集资产信息方式" - -#: assets/models/platform.py:48 -msgid "Push account enabled" -msgstr "推送账号" - -#: assets/models/platform.py:49 -msgid "Push account method" -msgstr "推送方式" - -#: assets/models/platform.py:50 -msgid "Change password enabled" -msgstr "更改密码" - -#: assets/models/platform.py:52 -msgid "Change password method" -msgstr "改密方式" - -#: assets/models/platform.py:53 -msgid "Verify account enabled" -msgstr "校验账号" - -#: assets/models/platform.py:55 -msgid "Verify account method" -msgstr "验证z" - -#: assets/models/platform.py:75 tickets/models/ticket/general.py:299 -msgid "Meta" -msgstr "元数据" - -#: assets/models/platform.py:76 -msgid "Internal" -msgstr "内部的" - -#: assets/models/platform.py:80 assets/serializers/platform.py:96 -msgid "Charset" -msgstr "编码" - -#: assets/models/platform.py:82 -msgid "Domain enabled" -msgstr "支持网域" - -#: assets/models/platform.py:83 -msgid "Protocols enabled" -msgstr "协议支持" - -#: assets/models/platform.py:85 -msgid "Su enabled" -msgstr "账号切换" - -#: assets/models/platform.py:86 -msgid "SU method" -msgstr "切换方式" - -#: assets/models/platform.py:88 assets/serializers/platform.py:103 -msgid "Automation" -msgstr "自动化" - -#: assets/models/utils.py:19 -#, python-format -msgid "%(value)s is not an even number" -msgstr "%(value)s is not an even number" - -#: assets/notifications.py:8 -msgid "Notification of account backup route task results" -msgstr "账号备份任务结果通知" - -#: assets/notifications.py:18 -msgid "" -"{} - The account backup passage task has been completed. See the attachment " -"for details" -msgstr "{} - 账号备份任务已完成, 详情见附件" - -#: assets/notifications.py:20 -msgid "" -"{} - The account backup passage task has been completed: the encryption " -"password has not been set - please go to personal information -> file " -"encryption password to set the encryption password" -msgstr "" -"{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" -"置加密密码" - -#: assets/notifications.py:31 xpack/plugins/change_auth_plan/notifications.py:8 -msgid "Notification of implementation result of encryption change plan" -msgstr "改密计划任务结果通知" - -#: assets/notifications.py:41 -#: xpack/plugins/change_auth_plan/notifications.py:18 -msgid "" -"{} - The encryption change task has been completed. See the attachment for " -"details" -msgstr "{} - 改密任务已完成, 详情见附件" - -#: assets/notifications.py:42 -#: xpack/plugins/change_auth_plan/notifications.py:19 -msgid "" -"{} - The encryption change task has been completed: the encryption password " -"has not been set - please go to personal information -> file encryption " -"password to set the encryption password" -msgstr "" -"{} - 改密任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设置加" -"密密码" - -#: assets/serializers/account/account.py:18 -msgid "Push now" -msgstr "立刻推送" - -#: assets/serializers/account/account.py:20 -msgid "Has secret" -msgstr "存在密码" - -#: assets/serializers/account/account.py:27 -msgid "Account template not found" -msgstr "账号模版没有发现" - -#: assets/serializers/account/backup.py:29 -#: assets/serializers/automations/base.py:34 ops/mixin.py:102 -#: settings/serializers/auth/ldap.py:65 -#: xpack/plugins/change_auth_plan/serializers/base.py:43 -msgid "Periodic perform" -msgstr "定时执行" - -#: assets/serializers/account/backup.py:31 -#: assets/serializers/automations/change_secret.py:41 -#: xpack/plugins/change_auth_plan/serializers/base.py:46 -msgid "Currently only mail sending is supported" -msgstr "当前只支持邮件发送" - -#: assets/serializers/asset/common.py:68 assets/serializers/platform.py:101 -#: authentication/serializers/connection_token.py:89 -#: perms/serializers/user_permission.py:22 xpack/plugins/cloud/models.py:109 -msgid "Protocols" -msgstr "协议组" - -#: assets/serializers/asset/common.py:87 -msgid "Address" -msgstr "地址" - -#: assets/serializers/asset/common.py:138 -msgid "Platform not exist" -msgstr "平台不存在" - -#: assets/serializers/asset/common.py:154 -msgid "Protocol is required: {}" -msgstr "协议是必须的: {}" - -#: assets/serializers/asset/host.py:12 -msgid "Vendor" -msgstr "制造商" - -#: assets/serializers/asset/host.py:13 -msgid "Model" -msgstr "型号" - -#: assets/serializers/asset/host.py:14 tickets/models/ticket/general.py:298 -msgid "Serial number" -msgstr "序列号" - -#: assets/serializers/asset/host.py:16 -msgid "CPU model" -msgstr "CPU型号" - -#: assets/serializers/asset/host.py:17 -msgid "CPU count" -msgstr "CPU数量" - -#: assets/serializers/asset/host.py:18 -msgid "CPU cores" -msgstr "CPU核数" - -#: assets/serializers/asset/host.py:19 -msgid "CPU vcpus" -msgstr "CPU总数" - -#: assets/serializers/asset/host.py:20 -msgid "Memory" -msgstr "内存" - -#: assets/serializers/asset/host.py:21 -msgid "Disk total" -msgstr "硬盘大小" - -#: assets/serializers/asset/host.py:22 -msgid "Disk info" -msgstr "硬盘信息" - -#: assets/serializers/asset/host.py:24 -msgid "OS" -msgstr "操作系统" - -#: assets/serializers/asset/host.py:25 -msgid "OS version" -msgstr "系统版本" - -#: assets/serializers/asset/host.py:26 -msgid "OS arch" -msgstr "系统架构" - -#: assets/serializers/asset/host.py:27 -msgid "Hostname raw" -msgstr "主机名原始" - -#: assets/serializers/asset/host.py:28 -msgid "Asset number" -msgstr "资产编号" - -#: assets/serializers/automations/change_secret.py:28 -#: xpack/plugins/change_auth_plan/models/asset.py:50 -#: xpack/plugins/change_auth_plan/serializers/asset.py:33 -msgid "SSH Key strategy" -msgstr "SSH 密钥策略" - -#: assets/serializers/automations/change_secret.py:70 -#: xpack/plugins/change_auth_plan/serializers/base.py:58 -msgid "* Please enter the correct password length" -msgstr "* 请输入正确的密码长度" - -#: assets/serializers/automations/change_secret.py:73 -#: xpack/plugins/change_auth_plan/serializers/base.py:61 -msgid "* Password length range 6-30 bits" -msgstr "* 密码长度范围 6-30 位" - -#: assets/serializers/automations/change_secret.py:117 -#: assets/serializers/automations/change_secret.py:145 audits/const.py:73 -#: audits/models.py:39 common/const/choices.py:18 ops/serializers/celery.py:39 -#: terminal/models/session/sharing.py:104 tickets/views/approve.py:114 -#: xpack/plugins/change_auth_plan/serializers/asset.py:189 -msgid "Success" -msgstr "成功" - -#: assets/serializers/automations/gather_accounts.py:23 -#, fuzzy -#| msgid "Executed times" -msgid "Executed amount" -msgstr "执行次数" - -#: assets/serializers/base.py:21 -msgid "Key password" -msgstr "密钥密码" - -#: assets/serializers/cagegory.py:9 -msgid "Constraints" -msgstr "约束" - -#: assets/serializers/cagegory.py:15 -msgid "Types" -msgstr "类型" - -#: assets/serializers/domain.py:14 assets/serializers/label.py:12 -msgid "Assets amount" -msgstr "资产数量" - -#: assets/serializers/domain.py:15 -msgid "Gateways count" -msgstr "网关数量" - -#: assets/serializers/gathered_user.py:24 settings/serializers/terminal.py:7 -msgid "Hostname" -msgstr "主机名" - -#: assets/serializers/label.py:13 -msgid "Category display" -msgstr "类别名称" - -#: assets/serializers/node.py:17 -msgid "value" -msgstr "值" - -#: assets/serializers/node.py:31 -msgid "Can't contains: /" -msgstr "不能包含: /" - -#: assets/serializers/node.py:41 -msgid "The same level node name cannot be the same" -msgstr "同级别节点名字不能重复" - -#: assets/serializers/platform.py:24 -msgid "SFTP enabled" -msgstr "SFTP 启用" - -#: assets/serializers/platform.py:25 -msgid "SFTP home" -msgstr "SFTP 根路径" - -#: assets/serializers/platform.py:28 -msgid "Auto fill" -msgstr "自动填充" - -#: assets/serializers/platform.py:78 -msgid "Primary" -msgstr "主要的" - -#: assets/serializers/utils.py:13 -msgid "Password can not contains `{{` " -msgstr "密码不能包含 `{{` 字符" - -#: assets/serializers/utils.py:16 -msgid "Password can not contains `'` " -msgstr "密码不能包含 `'` 字符" - -#: assets/serializers/utils.py:18 -msgid "Password can not contains `\"` " -msgstr "密码不能包含 `\"` 字符" - -#: assets/serializers/utils.py:24 -msgid "private key invalid or passphrase error" -msgstr "密钥不合法或密钥密码错误" - -#: assets/tasks/automation.py:11 -msgid "Execute automation" -msgstr "执行自动化" - -#: assets/tasks/backup.py:13 -msgid "Execute account backup plan" -msgstr "执行账号备份计划" - -#: assets/tasks/gather_accounts.py:31 -msgid "Gather assets accounts" -msgstr "收集资产账号" - -#: assets/tasks/gather_facts.py:26 -msgid "Update some assets hardware info. " -msgstr "更新资产硬件信息. " - -#: assets/tasks/gather_facts.py:44 -msgid "Manually update the hardware information of assets" -msgstr "手动更新节点资产硬件信息: " - -#: assets/tasks/gather_facts.py:49 -msgid "Update assets hardware info: " -msgstr "更新资产硬件信息: " - -#: assets/tasks/gather_facts.py:53 -msgid "Manually update the hardware information of assets under a node" -msgstr "手动更新节点下的资产硬件信息" - -#: assets/tasks/gather_facts.py:59 -#, fuzzy -#| msgid "Update node asset hardware information" -msgid "Update node asset hardware information: " -msgstr "更新节点资产硬件信息" - -#: assets/tasks/nodes_amount.py:16 -msgid "Check the amount of assets under the node" -msgstr "校准节点下的资产数量" - -#: assets/tasks/nodes_amount.py:28 -msgid "" -"The task of self-checking is already running and cannot be started repeatedly" -msgstr "自检程序已经在运行,不能重复启动" - -#: assets/tasks/nodes_amount.py:34 -msgid "Periodic check the amount of assets under the node" -msgstr "定时校准节点下的资产数量" - -#: assets/tasks/ping.py:21 assets/tasks/ping.py:39 -msgid "Test assets connectivity " -msgstr "测试资产可连接性. " - -#: assets/tasks/ping.py:33 -msgid "Manually test the connectivity of a asset" -msgstr "手动测试资产连接性" - -#: assets/tasks/ping.py:43 -msgid "Manually test the connectivity of assets under a node" -msgstr "手动测试节点下的资产可连接性" - -#: assets/tasks/ping.py:49 -msgid "Test if the assets under the node are connectable " -msgstr "测试节点下资产是否可连接: " - -#: assets/tasks/push_account.py:17 assets/tasks/push_account.py:34 -msgid "Push accounts to assets" -msgstr "推送账号到资产" - -#: assets/tasks/utils.py:17 -msgid "Asset has been disabled, skipped: {}" -msgstr "资产已经被禁用, 跳过: {}" - -#: assets/tasks/utils.py:21 -msgid "Asset may not be support ansible, skipped: {}" -msgstr "资产或许不支持ansible, 跳过: {}" - -#: assets/tasks/utils.py:39 -msgid "For security, do not push user {}" -msgstr "为了安全,禁止推送用户 {}" - -#: assets/tasks/utils.py:55 -msgid "No assets matched, stop task" -msgstr "没有匹配到资产,结束任务" - -#: assets/tasks/verify_account.py:30 -msgid "Verify asset account availability" -msgstr "验证资产账号的有效性" - -#: assets/tasks/verify_account.py:37 -msgid "Verify accounts connectivity" -msgstr "测试账号可连接性: " - -#: audits/apps.py:9 -msgid "Audits" -msgstr "日志审计" - -#: audits/const.py:44 -msgid "Mkdir" -msgstr "创建目录" - -#: audits/const.py:45 -msgid "Rmdir" -msgstr "删除目录" - -#: audits/const.py:46 audits/const.py:56 -#: authentication/templates/authentication/_access_key_modal.html:65 -#: rbac/tree.py:226 -msgid "Delete" -msgstr "删除" - -#: audits/const.py:47 perms/const.py:13 -msgid "Upload" -msgstr "上传文件" - -#: audits/const.py:48 -msgid "Rename" -msgstr "重命名" - -#: audits/const.py:49 -msgid "Symlink" -msgstr "建立软链接" - -#: audits/const.py:50 perms/const.py:14 -msgid "Download" -msgstr "下载文件" - -#: audits/const.py:54 rbac/tree.py:224 -msgid "View" -msgstr "查看" - -#: audits/const.py:55 rbac/tree.py:225 templates/_csv_import_export.html:18 -#: templates/_csv_update_modal.html:6 -msgid "Update" -msgstr "更新" - -#: audits/const.py:57 -#: authentication/templates/authentication/_access_key_modal.html:22 -#: rbac/tree.py:223 -msgid "Create" -msgstr "创建" - -#: audits/const.py:62 terminal/models/applet/host.py:24 -#: terminal/models/component/terminal.py:157 -msgid "Terminal" -msgstr "终端" - -#: audits/const.py:69 -msgid "-" -msgstr "-" - -#: audits/models.py:31 audits/models.py:55 audits/models.py:82 -#: terminal/models/session/session.py:37 terminal/models/session/sharing.py:96 -msgid "Remote addr" -msgstr "远端地址" - -#: audits/models.py:36 audits/serializers.py:19 -msgid "Operate" -msgstr "操作" - -#: audits/models.py:38 -msgid "Filename" -msgstr "文件名" - -#: audits/models.py:43 -msgid "File transfer log" -msgstr "文件管理" - -#: audits/models.py:52 audits/serializers.py:85 -msgid "Resource Type" -msgstr "资源类型" - -#: audits/models.py:53 -msgid "Resource" -msgstr "资源" - -#: audits/models.py:58 audits/models.py:84 -#: terminal/backends/command/serializers.py:40 -msgid "Datetime" -msgstr "日期" - -#: audits/models.py:74 -msgid "Operate log" -msgstr "操作日志" - -#: audits/models.py:80 -msgid "Change by" -msgstr "修改者" - -#: audits/models.py:90 -msgid "Password change log" -msgstr "改密日志" - -#: audits/models.py:97 -msgid "Login type" -msgstr "登录方式" - -#: audits/models.py:99 tickets/models/ticket/login_confirm.py:10 -msgid "Login ip" -msgstr "登录IP" - -#: audits/models.py:101 -#: authentication/templates/authentication/_msg_different_city.html:11 -#: tickets/models/ticket/login_confirm.py:11 -msgid "Login city" -msgstr "登录城市" - -#: audits/models.py:104 audits/serializers.py:62 -msgid "User agent" -msgstr "用户代理" - -#: audits/models.py:107 audits/serializers.py:39 -#: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms/profile.py:65 users/models/user.py:688 -#: users/serializers/profile.py:126 -msgid "MFA" -msgstr "MFA" - -#: audits/models.py:117 -msgid "Date login" -msgstr "登录日期" - -#: audits/models.py:119 audits/serializers.py:64 -msgid "Authentication backend" -msgstr "认证方式" - -#: audits/models.py:160 -msgid "User login log" -msgstr "用户登录日志" - -#: audits/serializers.py:63 -msgid "Reason display" -msgstr "原因描述" - -#: audits/signal_handlers.py:45 -msgid "SSH Key" -msgstr "SSH 密钥" - -#: audits/signal_handlers.py:47 -msgid "SSO" -msgstr "SSO" - -#: audits/signal_handlers.py:48 -msgid "Auth Token" -msgstr "认证令牌" - -#: audits/signal_handlers.py:49 authentication/notifications.py:73 -#: authentication/views/login.py:73 authentication/views/wecom.py:178 -#: notifications/backends/__init__.py:11 users/models/user.py:724 -msgid "WeCom" -msgstr "企业微信" - -#: audits/signal_handlers.py:50 authentication/views/feishu.py:145 -#: authentication/views/login.py:85 notifications/backends/__init__.py:14 -#: users/models/user.py:726 -msgid "FeiShu" -msgstr "飞书" - -#: audits/signal_handlers.py:51 authentication/views/dingtalk.py:180 -#: authentication/views/login.py:79 notifications/backends/__init__.py:12 -#: users/models/user.py:725 -msgid "DingTalk" -msgstr "钉钉" - -#: audits/signal_handlers.py:52 authentication/models/temp_token.py:16 -msgid "Temporary token" -msgstr "临时密码" - -#: audits/signal_handlers.py:64 -msgid "User and Group" -msgstr "用户与用户组" - -#: audits/signal_handlers.py:65 -#, python-brace-format -msgid "{User} JOINED {UserGroup}" -msgstr "{User} 加入 {UserGroup}" - -#: audits/signal_handlers.py:66 -#, python-brace-format -msgid "{User} LEFT {UserGroup}" -msgstr "{User} 离开 {UserGroup}" - -#: audits/signal_handlers.py:69 -msgid "Node and Asset" -msgstr "节点与资产" - -#: audits/signal_handlers.py:70 -#, python-brace-format -msgid "{Node} ADD {Asset}" -msgstr "{Node} 添加 {Asset}" - -#: audits/signal_handlers.py:71 -#, python-brace-format -msgid "{Node} REMOVE {Asset}" -msgstr "{Node} 移除 {Asset}" - -#: audits/signal_handlers.py:74 -msgid "User asset permissions" -msgstr "用户资产授权" - -#: audits/signal_handlers.py:75 -#, python-brace-format -msgid "{AssetPermission} ADD {User}" -msgstr "{AssetPermission} 添加 {User}" - -#: audits/signal_handlers.py:76 -#, python-brace-format -msgid "{AssetPermission} REMOVE {User}" -msgstr "{AssetPermission} 移除 {User}" - -#: audits/signal_handlers.py:79 -msgid "User group asset permissions" -msgstr "用户组资产授权" - -#: audits/signal_handlers.py:80 -#, python-brace-format -msgid "{AssetPermission} ADD {UserGroup}" -msgstr "{AssetPermission} 添加 {UserGroup}" - -#: audits/signal_handlers.py:81 -#, python-brace-format -msgid "{AssetPermission} REMOVE {UserGroup}" -msgstr "{AssetPermission} 移除 {UserGroup}" - -#: audits/signal_handlers.py:84 perms/models/asset_permission.py:81 -msgid "Asset permission" -msgstr "资产授权" - -#: audits/signal_handlers.py:85 -#, python-brace-format -msgid "{AssetPermission} ADD {Asset}" -msgstr "{AssetPermission} 添加 {Asset}" - -#: audits/signal_handlers.py:86 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Asset}" -msgstr "{AssetPermission} 移除 {Asset}" - -#: audits/signal_handlers.py:89 -msgid "Node permission" -msgstr "节点授权" - -#: audits/signal_handlers.py:90 -#, python-brace-format -msgid "{AssetPermission} ADD {Node}" -msgstr "{AssetPermission} 添加 {Node}" - -#: audits/signal_handlers.py:91 -#, python-brace-format -msgid "{AssetPermission} REMOVE {Node}" -msgstr "{AssetPermission} 移除 {Node}" - -#: authentication/api/confirm.py:40 -msgid "This action require verify your MFA" -msgstr "此操作需要验证您的 MFA" - -#: authentication/api/mfa.py:59 -msgid "Current user not support mfa type: {}" -msgstr "当前用户不支持 MFA 类型: {}" - -#: authentication/apps.py:7 -msgid "Authentication" -msgstr "认证" - -#: authentication/backends/drf.py:56 -msgid "Invalid signature header. No credentials provided." -msgstr "不合法的签名头" - -#: authentication/backends/drf.py:59 -msgid "Invalid signature header. Signature string should not contain spaces." -msgstr "不合法的签名头" - -#: authentication/backends/drf.py:66 -msgid "Invalid signature header. Format like AccessKeyId:Signature" -msgstr "不合法的签名头" - -#: authentication/backends/drf.py:70 -msgid "" -"Invalid signature header. Signature string should not contain invalid " -"characters." -msgstr "不合法的签名头" - -#: authentication/backends/drf.py:90 authentication/backends/drf.py:106 -msgid "Invalid signature." -msgstr "签名无效" - -#: authentication/backends/drf.py:97 -msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" -msgstr "HTTP header not valid" - -#: authentication/backends/drf.py:102 -msgid "Expired, more than 15 minutes" -msgstr "已过期,超过15分钟" - -#: authentication/backends/drf.py:109 -msgid "User disabled." -msgstr "用户已禁用" - -#: authentication/backends/drf.py:127 -msgid "Invalid token header. No credentials provided." -msgstr "无效的令牌头。没有提供任何凭据。" - -#: authentication/backends/drf.py:130 -msgid "Invalid token header. Sign string should not contain spaces." -msgstr "无效的令牌头。符号字符串不应包含空格。" - -#: authentication/backends/drf.py:137 -msgid "" -"Invalid token header. Sign string should not contain invalid characters." -msgstr "无效的令牌头。符号字符串不应包含无效字符。" - -#: authentication/backends/drf.py:148 -msgid "Invalid token or cache refreshed." -msgstr "刷新的令牌或缓存无效。" - -#: authentication/backends/oauth2/backends.py:155 -msgid "User invalid, disabled or expired" -msgstr "用户无效,已禁用或已过期" - -#: authentication/confirm/password.py:16 -msgid "Authentication failed password incorrect" -msgstr "认证失败 (用户名或密码不正确)" - -#: authentication/confirm/relogin.py:10 -msgid "Login time has exceeded {} minutes, please login again" -msgstr "登录时长已超过 {} 分钟,请重新登录" - -#: authentication/errors/const.py:18 -msgid "Username/password check failed" -msgstr "用户名/密码 校验失败" - -#: authentication/errors/const.py:19 -msgid "Password decrypt failed" -msgstr "密码解密失败" - -#: authentication/errors/const.py:20 -msgid "MFA failed" -msgstr "MFA 校验失败" - -#: authentication/errors/const.py:21 -msgid "MFA unset" -msgstr "MFA 没有设定" - -#: authentication/errors/const.py:22 -msgid "Username does not exist" -msgstr "用户名不存在" - -#: authentication/errors/const.py:23 -msgid "Password expired" -msgstr "密码已过期" - -#: authentication/errors/const.py:24 -msgid "Disabled or expired" -msgstr "禁用或失效" - -#: authentication/errors/const.py:25 -msgid "This account is inactive." -msgstr "此账号已禁用" - -#: authentication/errors/const.py:26 -msgid "This account is expired" -msgstr "此账号已过期" - -#: authentication/errors/const.py:27 -msgid "Auth backend not match" -msgstr "没有匹配到认证后端" - -#: authentication/errors/const.py:28 -msgid "ACL is not allowed" -msgstr "登录访问控制不被允许" - -#: authentication/errors/const.py:29 -msgid "Only local users are allowed" -msgstr "仅允许本地用户" - -#: authentication/errors/const.py:39 -msgid "No session found, check your cookie" -msgstr "会话已变更,刷新页面" - -#: authentication/errors/const.py:41 -#, python-brace-format -msgid "" -"The username or password you entered is incorrect, please enter it again. " -"You can also try {times_try} times (The account will be temporarily locked " -"for {block_time} minutes)" -msgstr "" -"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将" -"被临时 锁定 {block_time} 分钟)" - -#: authentication/errors/const.py:47 authentication/errors/const.py:55 -msgid "" -"The account has been locked (please contact admin to unlock it or try again " -"after {} minutes)" -msgstr "账号已被锁定(请联系管理员解锁或{}分钟后重试)" - -#: authentication/errors/const.py:51 -msgid "" -"The address has been locked (please contact admin to unlock it or try again " -"after {} minutes)" -msgstr "IP 已被锁定(请联系管理员解锁或{}分钟后重试)" - -#: authentication/errors/const.py:59 -#, python-brace-format -msgid "" -"{error}, You can also try {times_try} times (The account will be temporarily " -"locked for {block_time} minutes)" -msgstr "" -"{error},您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)" - -#: authentication/errors/const.py:63 -msgid "MFA required" -msgstr "需要 MFA 认证" - -#: authentication/errors/const.py:64 -msgid "MFA not set, please set it first" -msgstr "MFA 没有设置,请先完成设置" - -#: authentication/errors/const.py:65 -msgid "Login confirm required" -msgstr "需要登录复核" - -#: authentication/errors/const.py:66 -msgid "Wait login confirm ticket for accept" -msgstr "等待登录复核处理" - -#: authentication/errors/const.py:67 -msgid "Login confirm ticket was {}" -msgstr "登录复核: {}" - -#: authentication/errors/failed.py:146 -msgid "Current IP and Time period is not allowed" -msgstr "当前 IP 和时间段不被允许登录" - -#: authentication/errors/failed.py:151 -msgid "Please enter MFA code" -msgstr "请输入 MFA 验证码" - -#: authentication/errors/failed.py:156 -msgid "Please enter SMS code" -msgstr "请输入短信验证码" - -#: authentication/errors/failed.py:161 users/exceptions.py:15 -msgid "Phone not set" -msgstr "手机号没有设置" - -#: authentication/errors/mfa.py:8 -msgid "SSO auth closed" -msgstr "SSO 认证关闭了" - -#: authentication/errors/mfa.py:18 authentication/views/wecom.py:80 -msgid "WeCom is already bound" -msgstr "企业微信已经绑定" - -#: authentication/errors/mfa.py:23 authentication/views/wecom.py:237 -#: authentication/views/wecom.py:291 -msgid "WeCom is not bound" -msgstr "没有绑定企业微信" - -#: authentication/errors/mfa.py:28 authentication/views/dingtalk.py:243 -#: authentication/views/dingtalk.py:297 -msgid "DingTalk is not bound" -msgstr "钉钉没有绑定" - -#: authentication/errors/mfa.py:33 authentication/views/feishu.py:204 -msgid "FeiShu is not bound" -msgstr "没有绑定飞书" - -#: authentication/errors/mfa.py:38 -msgid "Your password is invalid" -msgstr "您的密码无效" - -#: authentication/errors/redirect.py:85 authentication/mixins.py:306 -msgid "Your password is too simple, please change it for security" -msgstr "你的密码过于简单,为了安全,请修改" - -#: authentication/errors/redirect.py:93 authentication/mixins.py:313 -msgid "You should to change your password before login" -msgstr "登录完成前,请先修改密码" - -#: authentication/errors/redirect.py:101 authentication/mixins.py:320 -msgid "Your password has expired, please reset before logging in" -msgstr "您的密码已过期,先修改再登录" - -#: authentication/forms.py:45 -msgid "{} days auto login" -msgstr "{} 天内自动登录" - -#: authentication/forms.py:56 -msgid "MFA Code" -msgstr "MFA 验证码" - -#: authentication/forms.py:57 -msgid "MFA type" -msgstr "MFA 类型" - -#: authentication/forms.py:70 users/forms/profile.py:28 -msgid "MFA code" -msgstr "MFA 验证码" - -#: authentication/forms.py:72 -msgid "Dynamic code" -msgstr "动态码" - -#: authentication/mfa/base.py:7 -msgid "Please input security code" -msgstr "请输入动态安全码" - -#: authentication/mfa/otp.py:7 -msgid "OTP code invalid, or server time error" -msgstr "虚拟 MFA 验证码错误,或者服务器端时间不对" - -#: authentication/mfa/otp.py:12 -msgid "OTP" -msgstr "虚拟 MFA" - -#: authentication/mfa/otp.py:13 -msgid "OTP verification code" -msgstr "虚拟 MFA 验证码" - -#: authentication/mfa/otp.py:48 -msgid "Virtual OTP based MFA" -msgstr "虚拟 MFA(OTP)" - -#: authentication/mfa/radius.py:7 -msgid "Radius verify code invalid" -msgstr "Radius 校验失败" - -#: authentication/mfa/radius.py:13 -msgid "Radius verification code" -msgstr "Radius 动态安全码" - -#: authentication/mfa/radius.py:44 -msgid "Radius global enabled, cannot disable" -msgstr "Radius MFA 全局开启,无法被禁用" - -#: authentication/mfa/sms.py:7 -msgid "SMS verify code invalid" -msgstr "短信验证码校验失败" - -#: authentication/mfa/sms.py:12 -msgid "SMS" -msgstr "短信" - -#: authentication/mfa/sms.py:13 -msgid "SMS verification code" -msgstr "短信验证码" - -#: authentication/mfa/sms.py:57 -msgid "Set phone number to enable" -msgstr "设置手机号码启用" - -#: authentication/mfa/sms.py:61 -msgid "Clear phone number to disable" -msgstr "清空手机号码禁用" - -#: authentication/middleware.py:77 settings/utils/ldap.py:652 -msgid "Authentication failed (before login check failed): {}" -msgstr "认证失败(登录前检查失败): {}" - -#: authentication/mixins.py:256 -msgid "The MFA type ({}) is not enabled" -msgstr "该 MFA ({}) 方式没有启用" - -#: authentication/mixins.py:296 -msgid "Please change your password" -msgstr "请修改密码" - -#: authentication/models/connection_token.py:31 -#: terminal/serializers/storage.py:111 -msgid "Account name" -msgstr "账号名称" - -#: authentication/models/connection_token.py:32 -#, fuzzy -#| msgid "Custom Username" -msgid "Input Username" -msgstr "自定义用户名" - -#: authentication/models/connection_token.py:33 -#, fuzzy -#| msgid "Client Secret" -msgid "Input Secret" -msgstr "客户端密钥" - -#: authentication/models/connection_token.py:37 perms/models/perm_token.py:17 -#, fuzzy -#| msgid "Connect timeout" -msgid "Connect method" -msgstr "连接超时时间" - -#: authentication/models/connection_token.py:38 -#: rbac/serializers/rolebinding.py:21 -msgid "User display" -msgstr "用户名称" - -#: authentication/models/connection_token.py:39 -msgid "Asset display" -msgstr "资产名称" - -#: authentication/models/connection_token.py:41 -#: authentication/models/temp_token.py:13 perms/models/asset_permission.py:69 -#: tickets/models/ticket/apply_application.py:31 -#: tickets/models/ticket/apply_asset.py:20 users/models/user.py:707 -msgid "Date expired" -msgstr "失效日期" - -#: authentication/models/connection_token.py:46 -msgid "Connection token" -msgstr "连接令牌" - -#: authentication/models/connection_token.py:48 -msgid "Can view connection token secret" -msgstr "可以查看连接令牌密文" - -#: authentication/models/connection_token.py:95 -msgid "Connection token expired at: {}" -msgstr "连接令牌过期: {}" - -#: authentication/models/connection_token.py:98 -msgid "No user or invalid user" -msgstr "" - -#: authentication/models/connection_token.py:102 -#, fuzzy -#| msgid "Asset inactive" -msgid "No asset or inactive asset" -msgstr "资产未激活" - -#: authentication/models/connection_token.py:105 -#, fuzzy -#| msgid "Login account" -msgid "No account" -msgstr "登录账号" - -#: authentication/models/connection_token.py:177 -msgid "Super connection token" -msgstr "超级连接令牌" - -#: authentication/models/private_token.py:9 -msgid "Private Token" -msgstr "SSH 密钥" - -#: authentication/models/sso_token.py:14 -msgid "Expired" -msgstr "过期时间" - -#: authentication/models/sso_token.py:18 -msgid "SSO token" -msgstr "SSO token" - -#: authentication/models/temp_token.py:11 -msgid "Verified" -msgstr "已校验" - -#: authentication/notifications.py:19 -msgid "Different city login reminder" -msgstr "异地登录提醒" - -#: authentication/notifications.py:52 -msgid "binding reminder" -msgstr "绑定提醒" - -#: authentication/serializers/connection_token.py:19 -msgid "Expired time" -msgstr "过期时间" - -#: authentication/serializers/connection_token.py:157 -#, fuzzy -#| msgid "Expired" -msgid "Expired now" -msgstr "过期时间" - -#: authentication/serializers/token.py:79 perms/serializers/permission.py:30 -#: perms/serializers/permission.py:61 users/serializers/user.py:203 -msgid "Is valid" -msgstr "账号是否有效" - -#: authentication/templates/authentication/_access_key_modal.html:6 -msgid "API key list" -msgstr "API Key列表" - -#: authentication/templates/authentication/_access_key_modal.html:18 -msgid "Using api key sign api header, every requests header difference" -msgstr "使用api key签名请求头,每个请求的头部是不一样的" - -#: authentication/templates/authentication/_access_key_modal.html:19 -msgid "docs" -msgstr "文档" - -#: authentication/templates/authentication/_access_key_modal.html:30 -#: users/serializers/group.py:35 -msgid "ID" -msgstr "ID" - -#: authentication/templates/authentication/_access_key_modal.html:33 -#: terminal/notifications.py:93 terminal/notifications.py:141 -msgid "Date" -msgstr "日期" - -#: authentication/templates/authentication/_access_key_modal.html:48 -msgid "Show" -msgstr "显示" - -#: authentication/templates/authentication/_access_key_modal.html:66 -#: settings/serializers/security.py:39 users/models/user.py:556 -#: users/serializers/profile.py:116 users/templates/users/mfa_setting.html:61 -#: users/templates/users/user_verify_mfa.html:36 -msgid "Disable" -msgstr "禁用" - -#: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:557 users/serializers/profile.py:117 -#: users/templates/users/mfa_setting.html:26 -#: users/templates/users/mfa_setting.html:68 -msgid "Enable" -msgstr "启用" - -#: authentication/templates/authentication/_access_key_modal.html:147 -msgid "Delete success" -msgstr "删除成功" - -#: authentication/templates/authentication/_access_key_modal.html:155 -#: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:44 -msgid "Close" -msgstr "关闭" - -#: authentication/templates/authentication/_captcha_field.html:8 -msgid "Play CAPTCHA as audio file" -msgstr "语言播放验证码" - -#: authentication/templates/authentication/_captcha_field.html:15 -#: users/forms/profile.py:103 -msgid "Captcha" -msgstr "验证码" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:5 -msgid "MFA confirm" -msgstr "MFA 认证校验" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:17 -msgid "Need MFA for view auth" -msgstr "需要 MFA 认证来查看账号信息" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:20 -#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:37 -#: templates/_modal.html:23 templates/flash_message_standalone.html:37 -#: users/templates/users/user_password_verify.html:20 -msgid "Confirm" -msgstr "确认" - -#: authentication/templates/authentication/_mfa_confirm_modal.html:25 -msgid "Code error" -msgstr "代码错误" - -#: authentication/templates/authentication/_msg_different_city.html:3 -#: authentication/templates/authentication/_msg_oauth_bind.html:3 -#: authentication/templates/authentication/_msg_reset_password.html:3 -#: authentication/templates/authentication/_msg_rest_password_success.html:2 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 -#: jumpserver/conf.py:389 -#: perms/templates/perms/_msg_item_permissions_expire.html:3 -#: perms/templates/perms/_msg_permed_items_expire.html:3 -#: tickets/templates/tickets/approve_check_password.html:33 -#: users/templates/users/_msg_account_expire_reminder.html:4 -#: users/templates/users/_msg_password_expire_reminder.html:4 -#: users/templates/users/_msg_reset_mfa.html:4 -#: users/templates/users/_msg_reset_ssh_key.html:4 -msgid "Hello" -msgstr "你好" - -#: authentication/templates/authentication/_msg_different_city.html:6 -msgid "Your account has remote login behavior, please pay attention" -msgstr "你的账号存在异地登录行为,请关注。" - -#: authentication/templates/authentication/_msg_different_city.html:10 -msgid "Login time" -msgstr "登录日期" - -#: authentication/templates/authentication/_msg_different_city.html:16 -msgid "" -"If you suspect that the login behavior is abnormal, please modify the " -"account password in time." -msgstr "若怀疑此次登录行为异常,请及时修改账号密码" - -#: authentication/templates/authentication/_msg_oauth_bind.html:6 -msgid "Your account has just been bound to" -msgstr "您的帐户刚刚绑定到" - -#: authentication/templates/authentication/_msg_oauth_bind.html:17 -msgid "If the operation is not your own, unbind and change the password." -msgstr "如果操作不是您本人,请解绑并且修改密码" - -#: authentication/templates/authentication/_msg_reset_password.html:6 -msgid "" -"Please click the link below to reset your password, if not your request, " -"concern your account security" -msgstr "请点击下面链接重置密码, 如果不是您申请的,请关注账号安全" - -#: authentication/templates/authentication/_msg_reset_password.html:10 -msgid "Click here reset password" -msgstr "点击这里重置密码" - -#: authentication/templates/authentication/_msg_reset_password.html:16 -#: users/templates/users/_msg_user_created.html:22 -msgid "This link is valid for 1 hour. After it expires" -msgstr "这个链接有效期1小时, 超过时间您可以" - -#: authentication/templates/authentication/_msg_reset_password.html:17 -#: users/templates/users/_msg_user_created.html:23 -msgid "request new one" -msgstr "重新申请" - -#: authentication/templates/authentication/_msg_rest_password_success.html:5 -msgid "Your password has just been successfully updated" -msgstr "你的密码刚刚成功更新" - -#: authentication/templates/authentication/_msg_rest_password_success.html:9 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:9 -msgid "Browser" -msgstr "浏览器" - -#: authentication/templates/authentication/_msg_rest_password_success.html:13 -msgid "" -"If the password update was not initiated by you, your account may have " -"security issues" -msgstr "如果这次密码更新不是由你发起的,那么你的账号可能存在安全问题" - -#: authentication/templates/authentication/_msg_rest_password_success.html:14 -#: authentication/templates/authentication/_msg_rest_public_key_success.html:14 -msgid "If you have any questions, you can contact the administrator" -msgstr "如果有疑问或需求,请联系系统管理员" - -#: authentication/templates/authentication/_msg_rest_public_key_success.html:5 -msgid "Your public key has just been successfully updated" -msgstr "你的公钥刚刚成功更新" - -#: authentication/templates/authentication/_msg_rest_public_key_success.html:13 -msgid "" -"If the public key update was not initiated by you, your account may have " -"security issues" -msgstr "如果这次公钥更新不是由你发起的,那么你的账号可能存在安全问题" - -#: authentication/templates/authentication/auth_fail_flash_message_standalone.html:28 -#: templates/flash_message_standalone.html:28 tickets/const.py:17 -msgid "Cancel" -msgstr "取消" - -#: authentication/templates/authentication/login.html:221 -msgid "Welcome back, please enter username and password to login" -msgstr "欢迎回来,请输入用户名和密码登录" - -#: authentication/templates/authentication/login.html:256 -#: users/templates/users/forgot_password.html:16 -#: users/templates/users/forgot_password.html:17 -msgid "Forgot password" -msgstr "忘记密码" - -#: authentication/templates/authentication/login.html:264 -#: templates/_header_bar.html:89 -msgid "Login" -msgstr "登录" - -#: authentication/templates/authentication/login.html:271 -msgid "More login options" -msgstr "其他方式登录" - -#: authentication/templates/authentication/login_mfa.html:6 -msgid "MFA Auth" -msgstr "MFA 多因子认证" - -#: authentication/templates/authentication/login_mfa.html:19 -#: users/templates/users/user_otp_check_password.html:12 -#: users/templates/users/user_otp_enable_bind.html:24 -#: users/templates/users/user_otp_enable_install_app.html:29 -#: users/templates/users/user_verify_mfa.html:30 -msgid "Next" -msgstr "下一步" - -#: authentication/templates/authentication/login_mfa.html:22 -msgid "Can't provide security? Please contact the administrator!" -msgstr "如果不能提供 MFA 验证码,请联系管理员!" - -#: authentication/templates/authentication/login_wait_confirm.html:41 -msgid "Refresh" -msgstr "刷新" - -#: authentication/templates/authentication/login_wait_confirm.html:46 -msgid "Copy link" -msgstr "复制链接" - -#: authentication/templates/authentication/login_wait_confirm.html:51 -msgid "Return" -msgstr "返回" - -#: authentication/templates/authentication/login_wait_confirm.html:116 -msgid "Copy success" -msgstr "复制成功" - -#: authentication/utils.py:28 common/utils/ip/geoip/utils.py:24 -#: xpack/plugins/cloud/const.py:24 -msgid "LAN" -msgstr "局域网" - -#: authentication/views/dingtalk.py:42 -msgid "DingTalk Error, Please contact your system administrator" -msgstr "钉钉错误,请联系系统管理员" - -#: authentication/views/dingtalk.py:45 -msgid "DingTalk Error" -msgstr "钉钉错误" - -#: authentication/views/dingtalk.py:57 authentication/views/feishu.py:52 -#: authentication/views/wecom.py:56 -msgid "" -"The system configuration is incorrect. Please contact your administrator" -msgstr "企业配置错误,请联系系统管理员" - -#: authentication/views/dingtalk.py:81 -msgid "DingTalk is already bound" -msgstr "钉钉已经绑定" - -#: authentication/views/dingtalk.py:149 authentication/views/wecom.py:148 -msgid "Invalid user_id" -msgstr "无效的 user_id" - -#: authentication/views/dingtalk.py:165 -msgid "DingTalk query user failed" -msgstr "钉钉查询用户失败" - -#: authentication/views/dingtalk.py:174 -msgid "The DingTalk is already bound to another user" -msgstr "该钉钉已经绑定其他用户" - -#: authentication/views/dingtalk.py:181 -msgid "Binding DingTalk successfully" -msgstr "绑定 钉钉 成功" - -#: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:291 -msgid "Failed to get user from DingTalk" -msgstr "从钉钉获取用户失败" - -#: authentication/views/dingtalk.py:244 authentication/views/dingtalk.py:298 -msgid "Please login with a password and then bind the DingTalk" -msgstr "请使用密码登录,然后绑定钉钉" - -#: authentication/views/feishu.py:40 -msgid "FeiShu Error" -msgstr "飞书错误" - -#: authentication/views/feishu.py:88 -msgid "FeiShu is already bound" -msgstr "飞书已经绑定" - -#: authentication/views/feishu.py:130 -msgid "FeiShu query user failed" -msgstr "飞书查询用户失败" - -#: authentication/views/feishu.py:139 -msgid "The FeiShu is already bound to another user" -msgstr "该飞书已经绑定其他用户" - -#: authentication/views/feishu.py:146 -msgid "Binding FeiShu successfully" -msgstr "绑定 飞书 成功" - -#: authentication/views/feishu.py:198 -msgid "Failed to get user from FeiShu" -msgstr "从飞书获取用户失败" - -#: authentication/views/feishu.py:205 -msgid "Please login with a password and then bind the FeiShu" -msgstr "请使用密码登录,然后绑定飞书" - -#: authentication/views/login.py:181 -msgid "Redirecting" -msgstr "跳转中" - -#: authentication/views/login.py:182 -msgid "Redirecting to {} authentication" -msgstr "正在跳转到 {} 认证" - -#: authentication/views/login.py:205 -msgid "Please enable cookies and try again." -msgstr "设置你的浏览器支持cookie" - -#: authentication/views/login.py:307 -msgid "" -"Wait for {} confirm, You also can copy link to her/him
\n" -" Don't close this page" -msgstr "" -"等待 {} 确认, 你也可以复制链接发给他/她
\n" -" 不要关闭本页面" - -#: authentication/views/login.py:312 -msgid "No ticket found" -msgstr "没有发现工单" - -#: authentication/views/login.py:346 -msgid "Logout success" -msgstr "退出登录成功" - -#: authentication/views/login.py:347 -msgid "Logout success, return login page" -msgstr "退出登录成功,返回到登录页面" - -#: authentication/views/wecom.py:41 -msgid "WeCom Error, Please contact your system administrator" -msgstr "企业微信错误,请联系系统管理员" - -#: authentication/views/wecom.py:44 -msgid "WeCom Error" -msgstr "企业微信错误" - -#: authentication/views/wecom.py:163 -msgid "WeCom query user failed" -msgstr "企业微信查询用户失败" - -#: authentication/views/wecom.py:172 -msgid "The WeCom is already bound to another user" -msgstr "该企业微信已经绑定其他用户" - -#: authentication/views/wecom.py:179 -msgid "Binding WeCom successfully" -msgstr "绑定 企业微信 成功" - -#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 -msgid "Failed to get user from WeCom" -msgstr "从企业微信获取用户失败" - -#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 -msgid "Please login with a password and then bind the WeCom" -msgstr "请使用密码登录,然后绑定企业微信" - -#: common/const/__init__.py:6 -#, python-format -msgid "%(name)s was created successfully" -msgstr "%(name)s 创建成功" - -#: common/const/__init__.py:7 -#, python-format -msgid "%(name)s was updated successfully" -msgstr "%(name)s 更新成功" - -#: common/const/choices.py:10 -msgid "Manual trigger" -msgstr "手动触发" - -#: common/const/choices.py:11 -msgid "Timing trigger" -msgstr "定时触发" - -#: common/const/choices.py:15 xpack/plugins/change_auth_plan/models/base.py:183 -msgid "Ready" -msgstr "准备" - -#: common/const/choices.py:16 tickets/const.py:29 tickets/const.py:39 -msgid "Pending" -msgstr "待定的" - -#: common/const/choices.py:17 -msgid "Running" -msgstr "" - -#: common/const/choices.py:21 -#, fuzzy -#| msgid "Cancel" -msgid "Canceled" -msgstr "取消" - -#: common/db/encoder.py:11 -msgid "ugettext_lazy" -msgstr "ugettext_lazy" - -#: common/db/fields.py:93 -msgid "Marshal dict data to char field" -msgstr "编码 dict 为 char" - -#: common/db/fields.py:97 -msgid "Marshal dict data to text field" -msgstr "编码 dict 为 text" - -#: common/db/fields.py:109 -msgid "Marshal list data to char field" -msgstr "编码 list 为 char" - -#: common/db/fields.py:113 -msgid "Marshal list data to text field" -msgstr "编码 list 为 text" - -#: common/db/fields.py:117 -msgid "Marshal data to char field" -msgstr "编码数据为 char" - -#: common/db/fields.py:121 -msgid "Marshal data to text field" -msgstr "编码数据为 text" - -#: common/db/fields.py:163 -msgid "Encrypt field using Secret Key" -msgstr "加密的字段" - -#: common/db/models.py:75 -msgid "Updated by" -msgstr "更新人" - -#: common/drf/exc_handlers.py:25 -msgid "Object" -msgstr "对象" - -#: common/drf/fields.py:74 tickets/serializers/ticket/common.py:58 -#: xpack/plugins/change_auth_plan/serializers/asset.py:64 -#: xpack/plugins/change_auth_plan/serializers/asset.py:67 -#: xpack/plugins/change_auth_plan/serializers/asset.py:70 -#: xpack/plugins/change_auth_plan/serializers/asset.py:101 -#: xpack/plugins/cloud/serializers/account_attrs.py:56 -msgid "This field is required." -msgstr "该字段是必填项。" - -#: common/drf/fields.py:75 -#, python-brace-format -msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "{pk_value} 对象不存在" - -#: common/drf/fields.py:76 -#, python-brace-format -msgid "Incorrect type. Expected pk value, received {data_type}." -msgstr "不正确的类型。期望 pk 值,收到 {data_type} 类型。" - -#: common/drf/fields.py:138 -msgid "Invalid data type, should be list" -msgstr "" - -#: common/drf/fields.py:153 -#, fuzzy -#| msgid "Invalid ip" -msgid "Invalid choice: {}" -msgstr "无效IP" - -#: common/drf/parsers/base.py:17 -msgid "The file content overflowed (The maximum length `{}` bytes)" -msgstr "文件内容太大 (最大长度 `{}` 字节)" - -#: common/drf/parsers/base.py:159 -msgid "Parse file error: {}" -msgstr "解析文件错误: {}" - -#: common/drf/serializers/common.py:86 -msgid "Children" -msgstr "" - -#: common/drf/serializers/common.py:94 -#, fuzzy -#| msgid "Filename" -msgid "File" -msgstr "文件名" - -#: common/exceptions.py:15 -#, python-format -msgid "%s object does not exist." -msgstr "%s对象不存在" - -#: common/exceptions.py:25 -msgid "Someone else is doing this. Please wait for complete" -msgstr "其他人正在操作,请等待他人完成" - -#: common/exceptions.py:30 -msgid "Your request timeout" -msgstr "您的请求超时了" - -#: common/exceptions.py:35 -msgid "M2M reverse not allowed" -msgstr "多对多反向是不被允许的" - -#: common/exceptions.py:41 -msgid "Is referenced by other objects and cannot be deleted" -msgstr "被其他对象关联,不能删除" - -#: common/exceptions.py:48 -msgid "This action require confirm current user" -msgstr "此操作需要确认当前用户" - -#: common/exceptions.py:56 -msgid "Unexpect error occur" -msgstr "发生意外错误" - -#: common/mixins/api/action.py:52 -msgid "Request file format may be wrong" -msgstr "上传的文件格式错误 或 其它类型资源的文件" - -#: common/mixins/models.py:33 -msgid "is discard" -msgstr "忽略的" - -#: common/mixins/models.py:34 -msgid "discard time" -msgstr "忽略时间" - -#: common/mixins/views.py:58 -msgid "Export all" -msgstr "导出所有" - -#: common/mixins/views.py:60 -msgid "Export only selected items" -msgstr "仅导出选择项" - -#: common/mixins/views.py:65 -#, python-format -msgid "Export filtered: %s" -msgstr "导出搜素: %s" - -#: common/sdk/im/exceptions.py:23 -msgid "Network error, please contact system administrator" -msgstr "网络错误,请联系系统管理员" - -#: common/sdk/im/wecom/__init__.py:15 -msgid "WeCom error, please contact system administrator" -msgstr "企业微信错误,请联系系统管理员" - -#: common/sdk/sms/alibaba.py:56 -msgid "Signature does not match" -msgstr "签名不匹配" - -#: common/sdk/sms/cmpp2.py:46 -msgid "sp_id is 6 bits" -msgstr "SP_id 为6位" - -#: common/sdk/sms/cmpp2.py:216 -msgid "Failed to connect to the CMPP gateway server, err: {}" -msgstr "连接网关服务器错误,错误:{}" - -#: common/sdk/sms/endpoint.py:16 -msgid "Alibaba cloud" -msgstr "阿里云" - -#: common/sdk/sms/endpoint.py:17 -msgid "Tencent cloud" -msgstr "腾讯云" - -#: common/sdk/sms/endpoint.py:18 -msgid "CMPP v2.0" -msgstr "CMPP v2.0" - -#: common/sdk/sms/endpoint.py:29 -msgid "SMS provider not support: {}" -msgstr "短信服务商不支持:{}" - -#: common/sdk/sms/endpoint.py:50 -msgid "SMS verification code signature or template invalid" -msgstr "短信验证码签名或模版无效" - -#: common/sdk/sms/utils.py:15 -msgid "The verification code has expired. Please resend it" -msgstr "验证码已过期,请重新发送" - -#: common/sdk/sms/utils.py:20 -msgid "The verification code is incorrect" -msgstr "验证码错误" - -#: common/sdk/sms/utils.py:25 -msgid "Please wait {} seconds before sending" -msgstr "请在 {} 秒后发送" - -#: common/tasks.py:13 -msgid "Send email" -msgstr "发送邮件" - -#: common/tasks.py:40 -msgid "Send email attachment" -msgstr "发送邮件附件" - -#: common/utils/ip/geoip/utils.py:26 -msgid "Invalid ip" -msgstr "无效IP" - -#: common/utils/ip/utils.py:78 -msgid "Invalid address" -msgstr "不合理的地址" - -#: common/validators.py:14 -msgid "Special char not allowed" -msgstr "不能包含特殊字符" - -#: common/validators.py:32 -msgid "This field must be unique." -msgstr "字段必须唯一" - -#: common/validators.py:40 -msgid "Should not contains special characters" -msgstr "不能包含特殊字符" - -#: common/validators.py:46 -msgid "The mobile phone number format is incorrect" -msgstr "手机号格式不正确" - -#: jumpserver/conf.py:388 -msgid "Create account successfully" -msgstr "创建账号成功" - -#: jumpserver/conf.py:390 -msgid "Your account has been created successfully" -msgstr "你的账号已创建成功" - -#: jumpserver/context_processor.py:12 -msgid "JumpServer Open Source Bastion Host" -msgstr "JumpServer 开源堡垒机" - -#: jumpserver/views/celery_flower.py:23 -msgid "

Flower service unavailable, check it

" -msgstr "Flower 服务不可用,请检查" - -#: jumpserver/views/other.py:26 -msgid "" -"
Luna is a separately deployed program, you need to deploy Luna, koko, " -"configure nginx for url distribution,
If you see this page, " -"prove that you are not accessing the nginx listening port. Good luck." -msgstr "" -"
Luna是单独部署的一个程序,你需要部署luna,koko,
如果你看到了" -"这个页面,证明你访问的不是nginx监听的端口,祝你好运
" - -#: jumpserver/views/other.py:70 -msgid "Websocket server run on port: {}, you should proxy it on nginx" -msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置" - -#: jumpserver/views/other.py:84 -msgid "" -"
Koko is a separately deployed program, you need to deploy Koko, " -"configure nginx for url distribution,
If you see this page, " -"prove that you are not accessing the nginx listening port. Good luck." -msgstr "" -"
Koko是单独部署的一个程序,你需要部署Koko, 并确保nginx配置转发,
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运" - -#: notifications/apps.py:7 -msgid "Notifications" -msgstr "通知" - -#: notifications/backends/__init__.py:10 users/forms/profile.py:102 -#: users/models/user.py:667 -msgid "Email" -msgstr "邮件" - -#: notifications/backends/__init__.py:13 -msgid "Site message" -msgstr "站内信" - -#: notifications/notifications.py:46 -msgid "Publish the station message" -msgstr "发布站内信" - -#: ops/ansible/inventory.py:75 -msgid "No account available" -msgstr "没有账号可以使用" - -#: ops/ansible/inventory.py:178 -msgid "Ansible disabled" -msgstr "Ansible 已禁用" - -#: ops/ansible/inventory.py:194 -msgid "Skip hosts below:" -msgstr "跳过一下主机:" - -#: ops/api/celery.py:63 ops/api/celery.py:78 -msgid "Waiting task start" -msgstr "等待任务开始" - -#: ops/apps.py:9 ops/notifications.py:16 -msgid "App ops" -msgstr "作业中心" - -#: ops/const.py:6 -msgid "Push" -msgstr "推送" - -#: ops/const.py:7 -msgid "Verify" -msgstr "校验" - -#: ops/const.py:8 -msgid "Collect" -msgstr "" - -#: ops/const.py:9 -msgid "Change password" -msgstr "更改密码" - -#: ops/const.py:19 xpack/plugins/change_auth_plan/models/base.py:27 -msgid "Custom password" -msgstr "自定义密码" - -#: ops/exception.py:6 -msgid "no valid program entry found." -msgstr "" - -#: ops/mixin.py:25 ops/mixin.py:88 settings/serializers/auth/ldap.py:72 -msgid "Cycle perform" -msgstr "周期执行" - -#: ops/mixin.py:29 ops/mixin.py:86 ops/mixin.py:105 -#: settings/serializers/auth/ldap.py:69 -msgid "Regularly perform" -msgstr "定期执行" - -#: ops/mixin.py:108 -msgid "Interval" -msgstr "间隔" - -#: ops/mixin.py:118 -msgid "* Please enter a valid crontab expression" -msgstr "* 请输入有效的 crontab 表达式" - -#: ops/mixin.py:125 -msgid "Range {} to {}" -msgstr "输入在 {} - {} 范围之间" - -#: ops/mixin.py:136 -msgid "Require periodic or regularly perform setting" -msgstr "需要周期或定期设置" - -#: ops/models/adhoc.py:18 ops/models/job.py:31 -#, fuzzy -#| msgid "PowerShell" -msgid "Powershell" -msgstr "PowerShell" - -#: ops/models/adhoc.py:22 -msgid "Pattern" -msgstr "模式" - -#: ops/models/adhoc.py:24 ops/models/job.py:38 -msgid "Module" -msgstr "" - -#: ops/models/adhoc.py:25 ops/models/celery.py:54 ops/models/job.py:36 -#: terminal/models/component/task.py:17 -msgid "Args" -msgstr "参数" - -#: ops/models/adhoc.py:26 ops/models/base.py:16 ops/models/base.py:53 -#: ops/models/job.py:43 ops/models/job.py:107 ops/models/playbook.py:16 -#: terminal/models/session/sharing.py:24 -msgid "Creator" -msgstr "创建者" - -#: ops/models/base.py:19 -msgid "Account policy" -msgstr "账号策略" - -#: ops/models/base.py:20 -msgid "Last execution" -msgstr "最后执行" - -#: ops/models/base.py:22 -msgid "Date last run" -msgstr "最后执行日期" - -#: ops/models/base.py:51 ops/models/job.py:105 -#: xpack/plugins/cloud/models.py:169 -msgid "Result" -msgstr "结果" - -#: ops/models/base.py:52 ops/models/job.py:106 -msgid "Summary" -msgstr "汇总" - -#: ops/models/celery.py:55 terminal/models/component/task.py:18 -msgid "Kwargs" -msgstr "其它参数" - -#: ops/models/celery.py:56 tickets/models/comment.py:13 -#: tickets/models/ticket/general.py:43 tickets/models/ticket/general.py:278 -#: tickets/serializers/ticket/ticket.py:20 -msgid "State" -msgstr "状态" - -#: ops/models/celery.py:57 terminal/models/session/sharing.py:111 -#: tickets/const.py:25 xpack/plugins/change_auth_plan/models/base.py:188 -msgid "Finished" -msgstr "结束" - -#: ops/models/celery.py:58 -msgid "Date published" -msgstr "发布日期" - -#: ops/models/job.py:21 -msgid "Adhoc" -msgstr "" - -#: ops/models/job.py:22 ops/models/job.py:41 -msgid "Playbook" -msgstr "Playbook" - -#: ops/models/job.py:25 -#, fuzzy -#| msgid "Privileged" -msgid "Privileged Only" -msgstr "特权账号" - -#: ops/models/job.py:26 -#, fuzzy -#| msgid "Privileged" -msgid "Privileged First" -msgstr "特权账号" - -#: ops/models/job.py:27 -msgid "Skip" -msgstr "" - -#: ops/models/job.py:39 -msgid "Chdir" -msgstr "执行路径" - -#: ops/models/job.py:40 -msgid "Timeout (Seconds)" -msgstr "超时时间(秒)" - -#: ops/models/job.py:45 -msgid "Runas" -msgstr "" - -#: ops/models/job.py:47 -#, fuzzy -#| msgid "Account policy" -msgid "Runas policy" -msgstr "账号策略" - -#: ops/models/job.py:48 -msgid "Use Parameter Define" -msgstr "定义参数" - -#: ops/models/job.py:49 -msgid "Parameters define" -msgstr "" - -#: ops/models/job.py:104 -msgid "Parameters" -msgstr "" - -#: ops/notifications.py:17 -msgid "Server performance" -msgstr "监控告警" - -#: ops/notifications.py:23 -msgid "Terminal health check warning" -msgstr "终端健康状况检查警告" - -#: ops/notifications.py:68 -#, python-brace-format -msgid "The terminal is offline: {name}" -msgstr "终端已离线: {name}" - -#: ops/notifications.py:73 -#, python-brace-format -msgid "Disk used more than {max_threshold}%: => {value}" -msgstr "硬盘使用率超过 {max_threshold}%: => {value}" - -#: ops/notifications.py:78 -#, python-brace-format -msgid "Memory used more than {max_threshold}%: => {value}" -msgstr "内存使用率超过 {max_threshold}%: => {value}" - -#: ops/notifications.py:83 -#, python-brace-format -msgid "CPU load more than {max_threshold}: => {value}" -msgstr "CPU 使用率超过 {max_threshold}: => {value}" - -#: ops/serializers/job.py:11 -msgid "Run after save" -msgstr "保存后运行" - -#: ops/signal_handlers.py:65 terminal/models/applet/host.py:108 -#: terminal/models/component/task.py:26 -#: xpack/plugins/gathered_user/models.py:68 -msgid "Task" -msgstr "任务" - -#: ops/tasks.py:28 -msgid "Run ansible task" -msgstr "运行 ansible 任务" - -#: ops/tasks.py:36 -msgid "Run ansible task execution" -msgstr "运行 ansible 任务" - -#: ops/tasks.py:50 -msgid "Periodic clear celery tasks" -msgstr "定时清理 Celery 任务" - -#: ops/tasks.py:52 -msgid "Clean celery log period" -msgstr "定期清理 Celery 日志" - -#: ops/tasks.py:69 -msgid "Clear celery periodic tasks" -msgstr "清理 Celery 定时任务" - -#: ops/tasks.py:92 -msgid "Create or update periodic tasks" -msgstr "创建或更新定时任务" - -#: ops/tasks.py:100 -msgid "Periodic check service performance" -msgstr "定时检查服务性能" - -#: ops/templates/ops/celery_task_log.html:4 -msgid "Task log" -msgstr "任务列表" - -#: ops/utils.py:64 -msgid "Update task content: {}" -msgstr "更新任务内容: {}" - -#: orgs/api.py:67 -msgid "The current organization ({}) cannot be deleted" -msgstr "当前组织 ({}) 不能被删除" - -#: orgs/api.py:72 -msgid "" -"LDAP synchronization is set to the current organization. Please switch to " -"another organization before deleting" -msgstr "LDAP 同步设置组织为当前组织,请切换其他组织后再进行删除操作" - -#: orgs/api.py:81 -msgid "The organization have resource ({}) cannot be deleted" -msgstr "组织存在资源 ({}) 不能被删除" - -#: orgs/apps.py:7 rbac/tree.py:113 -msgid "App organizations" -msgstr "组织管理" - -#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:88 -#: rbac/const.py:7 rbac/models/rolebinding.py:48 -#: rbac/serializers/rolebinding.py:40 settings/serializers/auth/ldap.py:62 -#: tickets/models/ticket/general.py:301 tickets/serializers/ticket/ticket.py:60 -msgid "Organization" -msgstr "组织" - -#: orgs/mixins/serializers.py:26 rbac/serializers/rolebinding.py:23 -msgid "Org name" -msgstr "组织名称" - -#: orgs/models.py:72 -#, fuzzy -#| msgid "Built-in" -msgid "Builtin" -msgstr "内置" - -#: orgs/models.py:80 -msgid "GLOBAL" -msgstr "全局组织" - -#: orgs/models.py:82 -msgid "DEFAULT" -msgstr "" - -#: orgs/models.py:84 -msgid "SYSTEM" -msgstr "" - -#: orgs/models.py:90 -msgid "Can view root org" -msgstr "可以查看全局组织" - -#: orgs/models.py:91 -msgid "Can view all joined org" -msgstr "可以查看所有加入的组织" - -#: orgs/tasks.py:9 -msgid "Refresh organization cache" -msgstr "刷新组织缓存" - -#: perms/apps.py:9 -msgid "App permissions" -msgstr "授权管理" - -#: perms/const.py:12 -msgid "Connect" -msgstr "连接" - -#: perms/const.py:15 -#, fuzzy -#| msgid "Copy link" -msgid "Copy" -msgstr "复制链接" - -#: perms/const.py:16 -msgid "Paste" -msgstr "" - -#: perms/const.py:26 -msgid "Transfer" -msgstr "" - -#: perms/const.py:27 -#, fuzzy -#| msgid "Clipboard copy" -msgid "Clipboard" -msgstr "剪贴板复制" - -#: perms/models/asset_permission.py:66 perms/models/perm_token.py:18 -#: perms/serializers/permission.py:29 perms/serializers/permission.py:59 -#: tickets/models/ticket/apply_application.py:28 -#: tickets/models/ticket/apply_asset.py:18 -msgid "Actions" -msgstr "动作" - -#: perms/models/asset_permission.py:73 -msgid "From ticket" -msgstr "来自工单" - -#: perms/models/perm_node.py:55 -msgid "Ungrouped" -msgstr "未分组" - -#: perms/models/perm_node.py:57 -msgid "Favorite" -msgstr "收藏夹" - -#: perms/models/perm_node.py:104 -msgid "Permed asset" -msgstr "授权的资产" - -#: perms/models/perm_node.py:106 -msgid "Can view my assets" -msgstr "可以查看我的资产" - -#: perms/models/perm_node.py:107 -msgid "Can view user assets" -msgstr "可以查看用户授权的资产" - -#: perms/models/perm_node.py:108 -msgid "Can view usergroup assets" -msgstr "可以查看用户组授权的资产" - -#: perms/models/perm_node.py:119 -#, fuzzy -#| msgid "Create account" -msgid "Permed account" -msgstr "收集账号" - -#: perms/notifications.py:12 perms/notifications.py:44 -msgid "today" -msgstr "今" - -#: perms/notifications.py:15 -msgid "You permed assets is about to expire" -msgstr "你授权的资产即将到期" - -#: perms/notifications.py:20 -msgid "permed assets" -msgstr "授权的资产" - -#: perms/notifications.py:59 -msgid "Asset permissions is about to expire" -msgstr "资产授权规则将要过期" - -#: perms/notifications.py:64 -msgid "asset permissions of organization {}" -msgstr "组织 ({}) 的资产授权" - -#: perms/serializers/permission.py:31 perms/serializers/permission.py:60 -#: users/serializers/user.py:100 users/serializers/user.py:205 -msgid "Is expired" -msgstr "已过期" - -#: perms/templates/perms/_msg_item_permissions_expire.html:7 -#: perms/templates/perms/_msg_permed_items_expire.html:7 -#, python-format -msgid "" -"\n" -" The following %(item_type)s will expire in %(count)s days\n" -" " -msgstr "" -"\n" -" 以下 %(item_type)s 即将在 %(count)s 天后过期\n" -" " - -#: perms/templates/perms/_msg_permed_items_expire.html:21 -msgid "If you have any question, please contact the administrator" -msgstr "如果有疑问或需求,请联系系统管理员" - -#: perms/utils/user_permission.py:627 rbac/tree.py:57 -msgid "My assets" -msgstr "我的资产" - -#: rbac/api/role.py:34 -msgid "Internal role, can't be destroy" -msgstr "内部角色,不能删除" - -#: rbac/api/role.py:38 -msgid "The role has been bound to users, can't be destroy" -msgstr "角色已绑定用户,不能删除" - -#: rbac/api/role.py:60 -msgid "Internal role, can't be update" -msgstr "内部角色,不能更新" - -#: rbac/api/rolebinding.py:52 -msgid "{} at least one system role" -msgstr "{} 至少有一个系统角色" - -#: rbac/apps.py:7 -msgid "RBAC" -msgstr "RBAC" - -#: rbac/builtin.py:111 -msgid "SystemAdmin" -msgstr "系统管理员" - -#: rbac/builtin.py:114 -msgid "SystemAuditor" -msgstr "系统审计员" - -#: rbac/builtin.py:117 -msgid "SystemComponent" -msgstr "系统组件" - -#: rbac/builtin.py:123 -msgid "OrgAdmin" -msgstr "组织管理员" - -#: rbac/builtin.py:126 -msgid "OrgAuditor" -msgstr "组织审计员" - -#: rbac/builtin.py:129 -msgid "OrgUser" -msgstr "组织用户" - -#: rbac/models/menu.py:13 -msgid "Menu permission" -msgstr "菜单授权" - -#: rbac/models/menu.py:15 -msgid "Can view console view" -msgstr "可以显示控制台" - -#: rbac/models/menu.py:16 -msgid "Can view audit view" -msgstr "可以显示审计台" - -#: rbac/models/menu.py:17 -msgid "Can view workbench view" -msgstr "可以显示工作台" - -#: rbac/models/menu.py:18 -msgid "Can view web terminal" -msgstr "Web终端" - -#: rbac/models/menu.py:19 -msgid "Can view file manager" -msgstr "文件管理" - -#: rbac/models/permission.py:26 -msgid "Permission" -msgstr "权限" - -#: rbac/models/role.py:31 rbac/models/rolebinding.py:38 -#: settings/serializers/auth/oauth2.py:35 -msgid "Scope" -msgstr "范围" - -#: rbac/models/role.py:34 -msgid "Permissions" -msgstr "授权" - -#: rbac/models/role.py:36 -msgid "Built-in" -msgstr "内置" - -#: rbac/models/role.py:46 rbac/models/rolebinding.py:44 -#: users/models/user.py:675 -msgid "Role" -msgstr "角色" - -#: rbac/models/role.py:144 -msgid "System role" -msgstr "系统角色" - -#: rbac/models/role.py:152 -msgid "Organization role" -msgstr "组织角色" - -#: rbac/models/rolebinding.py:53 -msgid "Role binding" -msgstr "角色绑定" - -#: rbac/models/rolebinding.py:137 -msgid "All organizations" -msgstr "所有组织" - -#: rbac/models/rolebinding.py:166 -msgid "" -"User last role in org, can not be delete, you can remove user from org " -"instead" -msgstr "用户最后一个角色,不能删除,你可以将用户从组织移除" - -#: rbac/models/rolebinding.py:173 -msgid "Organization role binding" -msgstr "组织角色绑定" - -#: rbac/models/rolebinding.py:188 -msgid "System role binding" -msgstr "系统角色绑定" - -#: rbac/serializers/permission.py:26 users/serializers/profile.py:132 -msgid "Perms" -msgstr "权限" - -#: rbac/serializers/role.py:11 -msgid "Scope display" -msgstr "范围名称" - -#: rbac/serializers/role.py:26 users/serializers/group.py:34 -msgid "Users amount" -msgstr "用户数量" - -#: rbac/serializers/role.py:27 terminal/models/applet/applet.py:21 -msgid "Display name" -msgstr "显示名称" - -#: rbac/serializers/rolebinding.py:22 -msgid "Role display" -msgstr "角色显示" - -#: rbac/serializers/rolebinding.py:56 -msgid "Has bound this role" -msgstr "已经绑定" - -#: rbac/tree.py:18 rbac/tree.py:19 -msgid "All permissions" -msgstr "所有权限" - -#: rbac/tree.py:25 -msgid "Console view" -msgstr "控制台" - -#: rbac/tree.py:26 -msgid "Workbench view" -msgstr "工作台" - -#: rbac/tree.py:27 -msgid "Audit view" -msgstr "审计台" - -#: rbac/tree.py:28 settings/models.py:156 -msgid "System setting" -msgstr "系统设置" - -#: rbac/tree.py:29 -msgid "Other" -msgstr "其它" - -#: rbac/tree.py:41 -msgid "Session audits" -msgstr "会话审计" - -#: rbac/tree.py:51 -msgid "Cloud import" -msgstr "云同步" - -#: rbac/tree.py:52 -msgid "Backup account" -msgstr "备份账号" - -#: rbac/tree.py:53 -msgid "Gather account" -msgstr "收集账号" - -#: rbac/tree.py:54 -msgid "App change auth" -msgstr "应用改密" - -#: rbac/tree.py:55 -msgid "Asset change auth" -msgstr "资产改密" - -#: rbac/tree.py:56 -msgid "Terminal setting" -msgstr "终端设置" - -#: rbac/tree.py:58 -msgid "My apps" -msgstr "我的应用" - -#: rbac/tree.py:114 -msgid "Ticket comment" -msgstr "工单评论" - -#: rbac/tree.py:115 tickets/models/ticket/general.py:306 -msgid "Ticket" -msgstr "工单管理" - -#: rbac/tree.py:116 -msgid "Common setting" -msgstr "一般设置" - -#: rbac/tree.py:117 -msgid "View permission tree" -msgstr "查看授权树" - -#: rbac/tree.py:118 -msgid "Execute batch command" -msgstr "执行批量命令" - -#: settings/api/dingtalk.py:31 settings/api/feishu.py:36 -#: settings/api/sms.py:131 settings/api/wecom.py:37 -msgid "Test success" -msgstr "测试成功" - -#: settings/api/email.py:20 -msgid "Test mail sent to {}, please check" -msgstr "邮件已经发送{}, 请检查" - -#: settings/api/ldap.py:166 -msgid "Synchronization start, please wait." -msgstr "同步开始,请稍等" - -#: settings/api/ldap.py:170 -msgid "Synchronization is running, please wait." -msgstr "同步正在运行,请稍等" - -#: settings/api/ldap.py:175 -msgid "Synchronization error: {}" -msgstr "同步错误: {}" - -#: settings/api/ldap.py:213 -msgid "Get ldap users is None" -msgstr "获取 LDAP 用户为 None" - -#: settings/api/ldap.py:222 -msgid "Imported {} users successfully (Organization: {})" -msgstr "成功导入 {} 个用户 ( 组织: {} )" - -#: settings/api/sms.py:113 -msgid "Invalid SMS platform" -msgstr "无效的短信平台" - -#: settings/api/sms.py:119 -msgid "test_phone is required" -msgstr "测试手机号 该字段是必填项。" - -#: settings/apps.py:7 -msgid "Settings" -msgstr "系统设置" - -#: settings/models.py:158 -msgid "Can change email setting" -msgstr "邮件设置" - -#: settings/models.py:159 -msgid "Can change auth setting" -msgstr "认证设置" - -#: settings/models.py:160 -msgid "Can change system msg sub setting" -msgstr "消息订阅设置" - -#: settings/models.py:161 -msgid "Can change sms setting" -msgstr "短信设置" - -#: settings/models.py:162 -msgid "Can change security setting" -msgstr "安全设置" - -#: settings/models.py:163 -msgid "Can change clean setting" -msgstr "定期清理" - -#: settings/models.py:164 -msgid "Can change interface setting" -msgstr "界面设置" - -#: settings/models.py:165 -msgid "Can change license setting" -msgstr "许可证设置" - -#: settings/models.py:166 -msgid "Can change terminal setting" -msgstr "终端设置" - -#: settings/models.py:167 -msgid "Can change other setting" -msgstr "其它设置" - -#: settings/serializers/auth/base.py:10 -msgid "CAS Auth" -msgstr "CAS 认证" - -#: settings/serializers/auth/base.py:11 -msgid "OPENID Auth" -msgstr "OIDC 认证" - -#: settings/serializers/auth/base.py:12 -msgid "RADIUS Auth" -msgstr "RADIUS 认证" - -#: settings/serializers/auth/base.py:13 -msgid "DingTalk Auth" -msgstr "钉钉 认证" - -#: settings/serializers/auth/base.py:14 -msgid "FeiShu Auth" -msgstr "飞书 认证" - -#: settings/serializers/auth/base.py:15 -msgid "WeCom Auth" -msgstr "企业微信 认证" - -#: settings/serializers/auth/base.py:16 -msgid "SSO Auth" -msgstr "SSO Token 认证" - -#: settings/serializers/auth/base.py:17 -msgid "SAML2 Auth" -msgstr "SAML2 认证" - -#: settings/serializers/auth/base.py:20 settings/serializers/basic.py:36 -msgid "Forgot password url" -msgstr "忘记密码 URL" - -#: settings/serializers/auth/base.py:26 -msgid "Enable login redirect msg" -msgstr "启用登录跳转提示" - -#: settings/serializers/auth/cas.py:10 -msgid "Enable CAS Auth" -msgstr "启用 CAS 认证" - -#: settings/serializers/auth/cas.py:11 settings/serializers/auth/oidc.py:48 -msgid "Server url" -msgstr "服务端地址" - -#: settings/serializers/auth/cas.py:14 -msgid "Proxy server url" -msgstr "回调地址" - -#: settings/serializers/auth/cas.py:16 settings/serializers/auth/saml2.py:32 -msgid "Logout completely" -msgstr "同步注销" - -#: settings/serializers/auth/cas.py:21 -msgid "Username attr" -msgstr "用户名属性" - -#: settings/serializers/auth/cas.py:24 -msgid "Enable attributes map" -msgstr "启用属性映射" - -#: settings/serializers/auth/cas.py:26 settings/serializers/auth/saml2.py:31 -msgid "Rename attr" -msgstr "映射属性" - -#: settings/serializers/auth/cas.py:27 -msgid "Create user if not" -msgstr "创建用户(如果不存在)" - -#: settings/serializers/auth/dingtalk.py:13 -msgid "Enable DingTalk Auth" -msgstr "启用钉钉认证" - -#: settings/serializers/auth/feishu.py:12 -msgid "Enable FeiShu Auth" -msgstr "启用飞书认证" - -#: settings/serializers/auth/ldap.py:41 -msgid "LDAP server" -msgstr "LDAP 地址" - -#: settings/serializers/auth/ldap.py:42 -msgid "eg: ldap://localhost:389" -msgstr "如: ldap://localhost:389" - -#: settings/serializers/auth/ldap.py:44 -msgid "Bind DN" -msgstr "绑定 DN" - -#: settings/serializers/auth/ldap.py:49 -msgid "User OU" -msgstr "用户 OU" - -#: settings/serializers/auth/ldap.py:50 -msgid "Use | split multi OUs" -msgstr "多个 OU 使用 | 分割" - -#: settings/serializers/auth/ldap.py:53 -msgid "User search filter" -msgstr "用户过滤器" - -#: settings/serializers/auth/ldap.py:54 -#, python-format -msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" -msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" - -#: settings/serializers/auth/ldap.py:57 settings/serializers/auth/oauth2.py:51 -#: settings/serializers/auth/oidc.py:36 -msgid "User attr map" -msgstr "用户属性映射" - -#: settings/serializers/auth/ldap.py:58 -msgid "" -"User attr map present how to map LDAP user attr to jumpserver, username,name," -"email is jumpserver attr" -msgstr "" -"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," -"email 是jumpserver的用户需要属性" - -#: settings/serializers/auth/ldap.py:76 -msgid "Connect timeout" -msgstr "连接超时时间" - -#: settings/serializers/auth/ldap.py:78 -msgid "Search paged size" -msgstr "搜索分页数量" - -#: settings/serializers/auth/ldap.py:80 -msgid "Enable LDAP auth" -msgstr "启用 LDAP 认证" - -#: settings/serializers/auth/oauth2.py:20 -msgid "Enable OAuth2 Auth" -msgstr "启用 OAuth2 认证" - -#: settings/serializers/auth/oauth2.py:23 -msgid "Logo" -msgstr "图标" - -#: settings/serializers/auth/oauth2.py:26 -msgid "Service provider" -msgstr "服务提供商" - -#: settings/serializers/auth/oauth2.py:29 settings/serializers/auth/oidc.py:18 -msgid "Client Id" -msgstr "客户端 ID" - -#: settings/serializers/auth/oauth2.py:32 settings/serializers/auth/oidc.py:21 -#: xpack/plugins/cloud/serializers/account_attrs.py:38 -msgid "Client Secret" -msgstr "客户端密钥" - -#: settings/serializers/auth/oauth2.py:38 settings/serializers/auth/oidc.py:62 -msgid "Provider auth endpoint" -msgstr "授权端点地址" - -#: settings/serializers/auth/oauth2.py:41 settings/serializers/auth/oidc.py:65 -msgid "Provider token endpoint" -msgstr "token 端点地址" - -#: settings/serializers/auth/oauth2.py:44 settings/serializers/auth/oidc.py:29 -msgid "Client authentication method" -msgstr "客户端认证方式" - -#: settings/serializers/auth/oauth2.py:48 settings/serializers/auth/oidc.py:71 -msgid "Provider userinfo endpoint" -msgstr "用户信息端点地址" - -#: settings/serializers/auth/oauth2.py:54 settings/serializers/auth/oidc.py:92 -#: settings/serializers/auth/saml2.py:33 -msgid "Always update user" -msgstr "总是更新用户信息" - -#: settings/serializers/auth/oidc.py:15 -msgid "Base site url" -msgstr "JumpServer 地址" - -#: settings/serializers/auth/oidc.py:31 -msgid "Share session" -msgstr "共享会话" - -#: settings/serializers/auth/oidc.py:33 -msgid "Ignore ssl verification" -msgstr "忽略 SSL 证书验证" - -#: settings/serializers/auth/oidc.py:37 -msgid "" -"User attr map present how to map OpenID user attr to jumpserver, username," -"name,email is jumpserver attr" -msgstr "" -"用户属性映射代表怎样将OpenID中用户属性映射到jumpserver用户上,username, name," -"email 是jumpserver的用户需要属性" - -#: settings/serializers/auth/oidc.py:45 -msgid "Use Keycloak" -msgstr "使用 Keycloak" - -#: settings/serializers/auth/oidc.py:51 -msgid "Realm name" -msgstr "域" - -#: settings/serializers/auth/oidc.py:57 -msgid "Enable OPENID Auth" -msgstr "启用 OIDC 认证" - -#: settings/serializers/auth/oidc.py:59 -msgid "Provider endpoint" -msgstr "端点地址" - -#: settings/serializers/auth/oidc.py:68 -msgid "Provider jwks endpoint" -msgstr "jwks 端点地址" - -#: settings/serializers/auth/oidc.py:74 -msgid "Provider end session endpoint" -msgstr "注销会话端点地址" - -#: settings/serializers/auth/oidc.py:77 -msgid "Provider sign alg" -msgstr "签名算法" - -#: settings/serializers/auth/oidc.py:80 -msgid "Provider sign key" -msgstr "签名 Key" - -#: settings/serializers/auth/oidc.py:82 -msgid "Scopes" -msgstr "连接范围" - -#: settings/serializers/auth/oidc.py:84 -msgid "Id token max age" -msgstr "令牌有效时间" - -#: settings/serializers/auth/oidc.py:87 -msgid "Id token include claims" -msgstr "声明" - -#: settings/serializers/auth/oidc.py:89 -msgid "Use state" -msgstr "使用状态" - -#: settings/serializers/auth/oidc.py:90 -msgid "Use nonce" -msgstr "临时使用" - -#: settings/serializers/auth/radius.py:13 -msgid "Enable Radius Auth" -msgstr "启用 Radius 认证" - -#: settings/serializers/auth/radius.py:19 -msgid "OTP in Radius" -msgstr "使用 Radius OTP" - -#: settings/serializers/auth/saml2.py:12 -msgid "Enable SAML2 Auth" -msgstr "启用 SAML2 认证" - -#: settings/serializers/auth/saml2.py:15 -msgid "IDP metadata URL" -msgstr "IDP metadata 地址" - -#: settings/serializers/auth/saml2.py:18 -msgid "IDP metadata XML" -msgstr "IDP metadata XML" - -#: settings/serializers/auth/saml2.py:21 -msgid "SP advanced settings" -msgstr "高级设置" - -#: settings/serializers/auth/saml2.py:25 -msgid "SP private key" -msgstr "SP 密钥" - -#: settings/serializers/auth/saml2.py:29 -msgid "SP cert" -msgstr "SP 证书" - -#: settings/serializers/auth/sms.py:15 -msgid "Enable SMS" -msgstr "启用 SMS" - -#: settings/serializers/auth/sms.py:17 -msgid "SMS provider / Protocol" -msgstr "短信服务商 / 协议" - -#: settings/serializers/auth/sms.py:22 settings/serializers/auth/sms.py:43 -#: settings/serializers/auth/sms.py:51 settings/serializers/auth/sms.py:62 -#: settings/serializers/email.py:65 -msgid "Signature" -msgstr "签名" - -#: settings/serializers/auth/sms.py:23 settings/serializers/auth/sms.py:44 -#: settings/serializers/auth/sms.py:52 -msgid "Template code" -msgstr "模板" - -#: settings/serializers/auth/sms.py:29 -msgid "Test phone" -msgstr "测试手机号" - -#: settings/serializers/auth/sms.py:58 -msgid "Enterprise code(SP id)" -msgstr "企业代码(SP id)" - -#: settings/serializers/auth/sms.py:59 -msgid "Shared secret(Shared secret)" -msgstr "共享密码(Shared secret)" - -#: settings/serializers/auth/sms.py:60 -msgid "Original number(Src id)" -msgstr "原始号码(Src id)" - -#: settings/serializers/auth/sms.py:61 -msgid "Business type(Service id)" -msgstr "业务类型(Service id)" - -#: settings/serializers/auth/sms.py:64 -msgid "Template" -msgstr "模板" - -#: settings/serializers/auth/sms.py:65 -#, python-brace-format -msgid "" -"Template need contain {code} and Signature + template length does not exceed " -"67 words. For example, your verification code is {code}, which is valid for " -"5 minutes. Please do not disclose it to others." -msgstr "" -"模板需要包含 {code},并且模板+签名长度不能超过67个字。例如, 您的验证码是 " -"{code}, 有效期为5分钟。请不要泄露给其他人。" - -#: settings/serializers/auth/sms.py:74 -#, python-brace-format -msgid "The template needs to contain {code}" -msgstr "模板需要包含 {code}" - -#: settings/serializers/auth/sms.py:77 -msgid "Signature + Template must not exceed 65 words" -msgstr "模板+签名不能超过65个字" - -#: settings/serializers/auth/sso.py:11 -msgid "Enable SSO auth" -msgstr "启用 SSO Token 认证" - -#: settings/serializers/auth/sso.py:12 -msgid "Other service can using SSO token login to JumpServer without password" -msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过程" - -#: settings/serializers/auth/sso.py:15 -msgid "SSO auth key TTL" -msgstr "Token 有效期" - -#: settings/serializers/auth/sso.py:15 -#: xpack/plugins/cloud/serializers/account_attrs.py:169 -msgid "Unit: second" -msgstr "单位: 秒" - -#: settings/serializers/auth/wecom.py:13 -msgid "Enable WeCom Auth" -msgstr "启用企业微信认证" - -#: settings/serializers/basic.py:9 -msgid "Subject" -msgstr "主题" - -#: settings/serializers/basic.py:13 -msgid "More url" -msgstr "更多信息 URL" - -#: settings/serializers/basic.py:28 -msgid "Site url" -msgstr "当前站点URL" - -#: settings/serializers/basic.py:29 -msgid "eg: http://dev.jumpserver.org:8080" -msgstr "如: http://dev.jumpserver.org:8080" - -#: settings/serializers/basic.py:32 -msgid "User guide url" -msgstr "用户向导URL" - -#: settings/serializers/basic.py:33 -msgid "User first login update profile done redirect to it" -msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档" - -#: settings/serializers/basic.py:37 -msgid "" -"The forgot password url on login page, If you use ldap or cas external " -"authentication, you can set it" -msgstr "" -"登录页面忘记密码URL, 如果使用了 LDAP, OPENID 等外部认证系统,可以自定义用户重" -"置密码访问的地址" - -#: settings/serializers/basic.py:41 -msgid "Global organization name" -msgstr "全局组织名" - -#: settings/serializers/basic.py:42 -msgid "The name of global organization to display" -msgstr "全局组织的显示名称,默认为 全局组织" - -#: settings/serializers/basic.py:44 -msgid "Enable announcement" -msgstr "启用公告" - -#: settings/serializers/basic.py:45 -msgid "Announcement" -msgstr "公告" - -#: settings/serializers/basic.py:46 -msgid "Enable tickets" -msgstr "启用工单" - -#: settings/serializers/cleaning.py:10 -msgid "Login log keep days" -msgstr "登录日志" - -#: settings/serializers/cleaning.py:10 settings/serializers/cleaning.py:14 -#: settings/serializers/cleaning.py:18 settings/serializers/cleaning.py:22 -#: settings/serializers/cleaning.py:26 settings/serializers/other.py:35 -msgid "Unit: day" -msgstr "单位: 天" - -#: settings/serializers/cleaning.py:14 -msgid "Task log keep days" -msgstr "任务日志" - -#: settings/serializers/cleaning.py:18 -msgid "Operate log keep days" -msgstr "操作日志" - -#: settings/serializers/cleaning.py:22 -msgid "FTP log keep days" -msgstr "上传下载" - -#: settings/serializers/cleaning.py:26 -msgid "Cloud sync record keep days" -msgstr "云同步记录" - -#: settings/serializers/cleaning.py:29 -msgid "Session keep duration" -msgstr "会话日志保存时间" - -#: settings/serializers/cleaning.py:30 -msgid "" -"Unit: days, Session, record, command will be delete if more than duration, " -"only in database" -msgstr "" -"单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" -"受影响)" - -#: settings/serializers/email.py:20 -msgid "SMTP host" -msgstr "SMTP 主机" - -#: settings/serializers/email.py:21 -msgid "SMTP port" -msgstr "SMTP 端口" - -#: settings/serializers/email.py:22 -msgid "SMTP account" -msgstr "SMTP 账号" - -#: settings/serializers/email.py:24 -msgid "SMTP password" -msgstr "SMTP 密码" - -#: settings/serializers/email.py:25 -msgid "Tips: Some provider use token except password" -msgstr "提示:一些邮件提供商需要输入的是授权码" - -#: settings/serializers/email.py:28 -msgid "Send user" -msgstr "发件人" - -#: settings/serializers/email.py:29 -msgid "Tips: Send mail account, default SMTP account as the send account" -msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" - -#: settings/serializers/email.py:32 -msgid "Test recipient" -msgstr "测试收件人" - -#: settings/serializers/email.py:33 -msgid "Tips: Used only as a test mail recipient" -msgstr "提示:仅用来作为测试邮件收件人" - -#: settings/serializers/email.py:37 -msgid "If SMTP port is 465, may be select" -msgstr "如果SMTP端口是465,通常需要启用 SSL" - -#: settings/serializers/email.py:40 -msgid "Use TLS" -msgstr "使用 TLS" - -#: settings/serializers/email.py:41 -msgid "If SMTP port is 587, may be select" -msgstr "如果SMTP端口是587,通常需要启用 TLS" - -#: settings/serializers/email.py:44 -msgid "Subject prefix" -msgstr "主题前缀" - -#: settings/serializers/email.py:51 -msgid "Create user email subject" -msgstr "邮件主题" - -#: settings/serializers/email.py:52 -msgid "" -"Tips: When creating a user, send the subject of the email (eg:Create account " -"successfully)" -msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" - -#: settings/serializers/email.py:56 -msgid "Create user honorific" -msgstr "邮件问候语" - -#: settings/serializers/email.py:57 -msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" -msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 你好)" - -#: settings/serializers/email.py:61 -msgid "Create user email content" -msgstr "邮件的内容" - -#: settings/serializers/email.py:62 -#, python-brace-format -msgid "" -"Tips: When creating a user, send the content of the email, support " -"{username} {name} {email} label" -msgstr "" -"提示: 创建用户时,发送设置密码邮件的内容, 支持 {username} {name} {email} 标签" - -#: settings/serializers/email.py:66 -msgid "Tips: Email signature (eg:jumpserver)" -msgstr "邮件署名 (如:jumpserver)" - -#: settings/serializers/other.py:7 -msgid "Email suffix" -msgstr "邮件后缀" - -#: settings/serializers/other.py:8 -msgid "" -"This is used by default if no email is returned during SSO authentication" -msgstr "SSO认证时,如果没有返回邮件地址,将使用该后缀" - -#: settings/serializers/other.py:12 -msgid "OTP issuer name" -msgstr "OTP 扫描后的名称" - -#: settings/serializers/other.py:16 -msgid "OTP valid window" -msgstr "OTP 延迟有效次数" - -#: settings/serializers/other.py:21 -msgid "CMD" -msgstr "CMD" - -#: settings/serializers/other.py:22 -msgid "PowerShell" -msgstr "PowerShell" - -#: settings/serializers/other.py:24 -msgid "Shell (Windows)" -msgstr "Windows shell" - -#: settings/serializers/other.py:25 -msgid "The shell type used when Windows assets perform ansible tasks" -msgstr "windows 资产执行 Ansible 任务时,使用的 Shell 类型。" - -#: settings/serializers/other.py:29 -msgid "Perm ungroup node" -msgstr "显示未分组节点" - -#: settings/serializers/other.py:30 -msgid "Perm single to ungroup node" -msgstr "" -"放置单独授权的资产到未分组节点, 避免能看到资产所在节点,但该节点未被授权的问" -"题" - -#: settings/serializers/other.py:35 -msgid "Ticket authorize default time" -msgstr "默认工单授权时间" - -#: settings/serializers/other.py:39 -msgid "Help Docs URL" -msgstr "文档链接" - -#: settings/serializers/other.py:40 -msgid "default: http://docs.jumpserver.org" -msgstr "默认: http://dev.jumpserver.org:8080" - -#: settings/serializers/other.py:44 -msgid "Help Support URL" -msgstr "支持链接" - -#: settings/serializers/other.py:45 -msgid "default: http://www.jumpserver.org/support/" -msgstr "默认: http://www.jumpserver.org/support/" - -#: settings/serializers/security.py:10 -msgid "Password minimum length" -msgstr "密码最小长度" - -#: settings/serializers/security.py:14 -msgid "Admin user password minimum length" -msgstr "管理员密码最小长度" - -#: settings/serializers/security.py:17 -msgid "Must contain capital" -msgstr "必须包含大写字符" - -#: settings/serializers/security.py:20 -msgid "Must contain lowercase" -msgstr "必须包含小写字符" - -#: settings/serializers/security.py:23 -msgid "Must contain numeric" -msgstr "必须包含数字" - -#: settings/serializers/security.py:26 -msgid "Must contain special" -msgstr "必须包含特殊字符" - -#: settings/serializers/security.py:31 -msgid "" -"Unit: minute, If the user has failed to log in for a limited number of " -"times, no login is allowed during this time interval." -msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" - -#: settings/serializers/security.py:40 -msgid "All users" -msgstr "所有用户" - -#: settings/serializers/security.py:41 -msgid "Only admin users" -msgstr "仅管理员" - -#: settings/serializers/security.py:43 -msgid "Global MFA auth" -msgstr "全局启用 MFA 认证" - -#: settings/serializers/security.py:47 -msgid "Third-party login users perform MFA authentication" -msgstr "第三方登录用户进行MFA认证" - -#: settings/serializers/security.py:48 -msgid "The third-party login modes include OIDC, CAS, and SAML2" -msgstr "第三方登录方式包括: OIDC、CAS、SAML2" - -#: settings/serializers/security.py:52 -msgid "Limit the number of user login failures" -msgstr "限制用户登录失败次数" - -#: settings/serializers/security.py:56 -msgid "Block user login interval" -msgstr "禁止用户登录时间间隔" - -#: settings/serializers/security.py:61 -msgid "Limit the number of IP login failures" -msgstr "限制 IP 登录失败次数" - -#: settings/serializers/security.py:65 -msgid "Block IP login interval" -msgstr "禁止 IP 登录时间间隔" - -#: settings/serializers/security.py:69 -msgid "Login IP White List" -msgstr "IP 登录白名单" - -#: settings/serializers/security.py:74 -msgid "Login IP Black List" -msgstr "IP 登录黑名单" - -#: settings/serializers/security.py:80 -msgid "User password expiration" -msgstr "用户密码过期时间" - -#: settings/serializers/security.py:82 -msgid "" -"Unit: day, If the user does not update the password during the time, the " -"user password will expire failure;The password expiration reminder mail will " -"be automatic sent to the user by system within 5 days (daily) before the " -"password expires" -msgstr "" -"单位:天, 如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期提醒邮件" -"将在密码过期前5天内由系统(每天)自动发送给用户" - -#: settings/serializers/security.py:89 -msgid "Number of repeated historical passwords" -msgstr "不能设置近几次密码" - -#: settings/serializers/security.py:91 -msgid "" -"Tip: When the user resets the password, it cannot be the previous n " -"historical passwords of the user" -msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码" - -#: settings/serializers/security.py:96 -msgid "Only single device login" -msgstr "仅一台设备登录" - -#: settings/serializers/security.py:97 -msgid "Next device login, pre login will be logout" -msgstr "下个设备登录,上次登录会被顶掉" - -#: settings/serializers/security.py:100 -msgid "Only exist user login" -msgstr "仅已存在用户登录" - -#: settings/serializers/security.py:101 -msgid "If enable, CAS、OIDC auth will be failed, if user not exist yet" -msgstr "开启后,如果系统中不存在该用户,CAS、OIDC 登录将会失败" - -#: settings/serializers/security.py:104 -msgid "Only from source login" -msgstr "仅从用户来源登录" - -#: settings/serializers/security.py:105 -msgid "Only log in from the user source property" -msgstr "开启后,如果用户来源为本地,CAS、OIDC 登录将会失败" - -#: settings/serializers/security.py:109 -msgid "MFA verify TTL" -msgstr "MFA 校验有效期" - -#: settings/serializers/security.py:111 -msgid "" -"Unit: second, The verification MFA takes effect only when you view the " -"account password" -msgstr "单位: 秒, 目前仅在查看账号密码校验 MFA 时生效" - -#: settings/serializers/security.py:116 -msgid "Enable Login dynamic code" -msgstr "启用登录附加码" - -#: settings/serializers/security.py:117 -msgid "" -"The password and additional code are sent to a third party authentication " -"system for verification" -msgstr "" -"密码和附加码一并发送给第三方认证系统进行校验, 如:有的第三方认证系统,需要 密" -"码+6位数字 完成认证" - -#: settings/serializers/security.py:122 -msgid "MFA in login page" -msgstr "MFA 在登录页面输入" - -#: settings/serializers/security.py:123 -msgid "Eu security regulations(GDPR) require MFA to be on the login page" -msgstr "欧盟数据安全法规(GDPR) 要求 MFA 在登录页面,来确保系统登录安全" - -#: settings/serializers/security.py:126 -msgid "Enable Login captcha" -msgstr "启用登录验证码" - -#: settings/serializers/security.py:127 -msgid "Enable captcha to prevent robot authentication" -msgstr "开启验证码,防止机器人登录" - -#: settings/serializers/security.py:147 -msgid "Enable terminal register" -msgstr "终端注册" - -#: settings/serializers/security.py:149 -msgid "" -"Allow terminal register, after all terminal setup, you should disable this " -"for security" -msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" - -#: settings/serializers/security.py:153 -msgid "Enable watermark" -msgstr "开启水印" - -#: settings/serializers/security.py:154 -msgid "Enabled, the web session and replay contains watermark information" -msgstr "启用后,Web 会话和录像将包含水印信息" - -#: settings/serializers/security.py:158 -msgid "Connection max idle time" -msgstr "连接最大空闲时间" - -#: settings/serializers/security.py:159 -msgid "If idle time more than it, disconnect connection Unit: minute" -msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" - -#: settings/serializers/security.py:162 -msgid "Remember manual auth" -msgstr "保存手动输入密码" - -#: settings/serializers/security.py:165 -msgid "Enable change auth secure mode" -msgstr "启用改密安全模式" - -#: settings/serializers/security.py:168 -msgid "Insecure command alert" -msgstr "危险命令告警" - -#: settings/serializers/security.py:171 -msgid "Email recipient" -msgstr "邮件收件人" - -#: settings/serializers/security.py:172 -msgid "Multiple user using , split" -msgstr "多个用户,使用 , 分割" - -#: settings/serializers/security.py:175 -msgid "Batch command execution" -msgstr "批量命令执行" - -#: settings/serializers/security.py:176 -msgid "Allow user run batch command or not using ansible" -msgstr "是否允许用户使用 ansible 执行批量命令" - -#: settings/serializers/security.py:179 -msgid "Session share" -msgstr "会话分享" - -#: settings/serializers/security.py:180 -msgid "Enabled, Allows user active session to be shared with other users" -msgstr "开启后允许用户分享已连接的资产会话给他人,协同工作" - -#: settings/serializers/security.py:183 -msgid "Remote Login Protection" -msgstr "异地登录保护" - -#: settings/serializers/security.py:185 -msgid "" -"The system determines whether the login IP address belongs to a common login " -"city. If the account is logged in from a common login city, the system sends " -"a remote login reminder" -msgstr "" -"根据登录 IP 是否所属常用登录城市进行判断,若账号在非常用城市登录,会发送异地" -"登录提醒" - -#: settings/serializers/terminal.py:13 -msgid "Auto" -msgstr "自动" - -#: settings/serializers/terminal.py:19 -msgid "Password auth" -msgstr "密码认证" - -#: settings/serializers/terminal.py:21 -msgid "Public key auth" -msgstr "密钥认证" - -#: settings/serializers/terminal.py:22 -msgid "" -"Tips: If use other auth method, like AD/LDAP, you should disable this to " -"avoid being able to log in after deleting" -msgstr "" -"提示:如果你使用其它认证方式,如 AD/LDAP,你应该禁用此项,以避免第三方系统删" -"除后,还可以登录" - -#: settings/serializers/terminal.py:26 -msgid "List sort by" -msgstr "资产列表排序" - -#: settings/serializers/terminal.py:29 -msgid "List page size" -msgstr "资产列表每页数量" - -#: settings/serializers/terminal.py:32 -msgid "Telnet login regex" -msgstr "Telnet 成功正则表达式" - -#: settings/serializers/terminal.py:33 -msgid "" -"Tips: The login success message varies with devices. if you cannot log in to " -"the device through Telnet, set this parameter" -msgstr "" -"提示: 不同设备登录成功提示不一样,所以如果 telnet 不能正常登录,可以这里设置" - -#: settings/serializers/terminal.py:36 -msgid "Enable database proxy" -msgstr "启用数据库组件" - -#: settings/serializers/terminal.py:37 -msgid "Enable Razor" -msgstr "启用 Razor 服务" - -#: settings/serializers/terminal.py:38 -msgid "Enable SSH Client" -msgstr "启用 SSH Client" - -#: settings/utils/ldap.py:467 -msgid "ldap:// or ldaps:// protocol is used." -msgstr "使用 ldap:// 或 ldaps:// 协议" - -#: settings/utils/ldap.py:478 -msgid "Host or port is disconnected: {}" -msgstr "主机或端口不可连接: {}" - -#: settings/utils/ldap.py:480 -msgid "The port is not the port of the LDAP service: {}" -msgstr "端口不是LDAP服务端口: {}" - -#: settings/utils/ldap.py:482 -msgid "Please add certificate: {}" -msgstr "请添加证书" - -#: settings/utils/ldap.py:486 settings/utils/ldap.py:513 -#: settings/utils/ldap.py:543 settings/utils/ldap.py:571 -msgid "Unknown error: {}" -msgstr "未知错误: {}" - -#: settings/utils/ldap.py:500 -msgid "Bind DN or Password incorrect" -msgstr "绑定DN或密码错误" - -#: settings/utils/ldap.py:507 -msgid "Please enter Bind DN: {}" -msgstr "请输入绑定DN: {}" - -#: settings/utils/ldap.py:509 -msgid "Please enter Password: {}" -msgstr "请输入密码: {}" - -#: settings/utils/ldap.py:511 -msgid "Please enter correct Bind DN and Password: {}" -msgstr "请输入正确的绑定DN和密码: {}" - -#: settings/utils/ldap.py:529 -msgid "Invalid User OU or User search filter: {}" -msgstr "不合法的用户OU或用户过滤器: {}" - -#: settings/utils/ldap.py:560 -msgid "LDAP User attr map not include: {}" -msgstr "LDAP属性映射没有包含: {}" - -#: settings/utils/ldap.py:567 -msgid "LDAP User attr map is not dict" -msgstr "LDAP属性映射不合法" - -#: settings/utils/ldap.py:586 -msgid "LDAP authentication is not enabled" -msgstr "LDAP认证没有启用" - -#: settings/utils/ldap.py:604 -msgid "Error (Invalid LDAP server): {}" -msgstr "错误 (不合法的LDAP服务器地址): {}" - -#: settings/utils/ldap.py:606 -msgid "Error (Invalid Bind DN): {}" -msgstr "错误(不合法的绑定DN): {}" - -#: settings/utils/ldap.py:608 -msgid "Error (Invalid LDAP User attr map): {}" -msgstr "错误(不合法的LDAP属性映射): {}" - -#: settings/utils/ldap.py:610 -msgid "Error (Invalid User OU or User search filter): {}" -msgstr "错误(不合法的用户OU或用户过滤器): {}" - -#: settings/utils/ldap.py:612 -msgid "Error (Not enabled LDAP authentication): {}" -msgstr "错误(没有启用LDAP认证): {}" - -#: settings/utils/ldap.py:614 -msgid "Error (Unknown): {}" -msgstr "错误(未知): {}" - -#: settings/utils/ldap.py:617 -msgid "Succeed: Match {} s user" -msgstr "成功匹配 {} 个用户" - -#: settings/utils/ldap.py:650 -msgid "Authentication failed (configuration incorrect): {}" -msgstr "认证失败(配置错误): {}" - -#: settings/utils/ldap.py:654 -msgid "Authentication failed (username or password incorrect): {}" -msgstr "认证失败 (用户名或密码不正确): {}" - -#: settings/utils/ldap.py:656 -msgid "Authentication failed (Unknown): {}" -msgstr "认证失败: (未知): {}" - -#: settings/utils/ldap.py:659 -msgid "Authentication success: {}" -msgstr "认证成功: {}" - -#: templates/_csv_import_export.html:8 -msgid "Export" -msgstr "导出" - -#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 -msgid "Import" -msgstr "导入" - -#: templates/_csv_import_modal.html:12 -msgid "Download the imported template or use the exported CSV file format" -msgstr "下载导入的模板或使用导出的csv格式" - -#: templates/_csv_import_modal.html:13 -msgid "Download the import template" -msgstr "下载导入模版" - -#: templates/_csv_import_modal.html:17 templates/_csv_update_modal.html:17 -msgid "Select the CSV file to import" -msgstr "请选择csv文件导入" - -#: templates/_csv_import_modal.html:39 templates/_csv_update_modal.html:42 -msgid "Please select file" -msgstr "选择文件" - -#: templates/_csv_update_modal.html:12 -msgid "Download the update template or use the exported CSV file format" -msgstr "下载更新的模板或使用导出的csv格式" - -#: templates/_csv_update_modal.html:13 -msgid "Download the update template" -msgstr "下载更新模版" - -#: templates/_header_bar.html:12 -msgid "Help" -msgstr "帮助" - -#: templates/_header_bar.html:19 -msgid "Docs" -msgstr "文档" - -#: templates/_header_bar.html:25 -msgid "Commercial support" -msgstr "商业支持" - -#: templates/_header_bar.html:76 users/forms/profile.py:44 -msgid "Profile" -msgstr "个人信息" - -#: templates/_header_bar.html:79 -msgid "Admin page" -msgstr "管理页面" - -#: templates/_header_bar.html:81 -msgid "User page" -msgstr "用户页面" - -#: templates/_header_bar.html:84 -msgid "API Key" -msgstr "API Key" - -#: templates/_header_bar.html:85 -msgid "Logout" -msgstr "注销登录" - -#: templates/_message.html:6 -msgid "" -"\n" -" Your account has expired, please contact the administrator.\n" -" " -msgstr "" -"\n" -" 您的账号已经过期,请联系管理员。 " - -#: templates/_message.html:13 -msgid "Your account will at" -msgstr "您的账号将于" - -#: templates/_message.html:13 templates/_message.html:30 -msgid "expired. " -msgstr "过期。" - -#: templates/_message.html:23 -#, python-format -msgid "" -"\n" -" Your password has expired, please click this link update password.\n" -" " -msgstr "" -"\n" -" 您的密码已经过期,请点击 链接 更新密码\n" -" " - -#: templates/_message.html:30 -msgid "Your password will at" -msgstr "您的密码将于" - -#: templates/_message.html:31 -#, python-format -msgid "" -"\n" -" please click this " -"link to update your password.\n" -" " -msgstr "" -"\n" -" 请点击 链接 更" -"新密码\n" -" " - -#: templates/_message.html:43 -#, python-format -msgid "" -"\n" -" Your information was incomplete. Please click this link to complete your information.\n" -" " -msgstr "" -"\n" -" 你的信息不完整,请点击 链接 " -" 补充完整\n" -" " - -#: templates/_message.html:56 -#, python-format -msgid "" -"\n" -" Your ssh public key not set or expired. Please click this link to update\n" -" " -msgstr "" -"\n" -" 您的SSH密钥没有设置或已失效,请点击 链接 更新\n" -" " - -#: templates/_mfa_login_field.html:28 -msgid "Send verification code" -msgstr "发送验证码" - -#: templates/_mfa_login_field.html:106 -msgid "Wait: " -msgstr "等待:" - -#: templates/_mfa_login_field.html:116 -msgid "The verification code has been sent" -msgstr "验证码已发送" - -#: templates/_without_nav_base.html:26 -msgid "Home page" -msgstr "首页" - -#: templates/resource_download.html:18 templates/resource_download.html:31 -msgid "Client" -msgstr "客户端" - -#: templates/resource_download.html:20 -msgid "" -"JumpServer Client, currently used to launch the client, now only support " -"launch RDP SSH client, The Telnet client will next" -msgstr "" -"JumpServer 客户端,目前用来唤起 特定客户端程序 连接资产, 目前仅支持 RDP SSH " -"客户端,Telnet 会在未来支持" - -#: templates/resource_download.html:31 -msgid "Microsoft" -msgstr "微软" - -#: templates/resource_download.html:31 -msgid "Official" -msgstr "官方" - -#: templates/resource_download.html:33 -msgid "" -"macOS needs to download the client to connect RDP asset, which comes with " -"Windows" -msgstr "macOS 需要下载客户端来连接 RDP 资产,Windows 系统默认安装了该程序" - -#: templates/resource_download.html:42 -msgid "Windows Remote application publisher tools" -msgstr "Windows 远程应用发布服务器工具" - -#: templates/resource_download.html:43 -msgid "" -"Jmservisor is the program used to pull up remote applications in Windows " -"Remote Application publisher" -msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远程应用的程序" - -#: templates/resource_download.html:51 -msgid "Offline video player" -msgstr "离线录像播放器" - -#: terminal/api/component/endpoint.py:31 -msgid "Not found protocol query params" -msgstr "" - -#: terminal/api/component/storage.py:28 -msgid "Deleting the default storage is not allowed" -msgstr "不允许删除默认存储配置" - -#: terminal/api/component/storage.py:31 -msgid "Cannot delete storage that is being used" -msgstr "不允许删除正在使用的存储配置" - -#: terminal/api/component/storage.py:72 terminal/api/component/storage.py:73 -msgid "Command storages" -msgstr "命令存储" - -#: terminal/api/component/storage.py:79 -msgid "Invalid" -msgstr "无效" - -#: terminal/api/component/storage.py:119 -msgid "Test failure: {}" -msgstr "测试失败: {}" - -#: terminal/api/component/storage.py:122 -msgid "Test successful" -msgstr "测试成功" - -#: terminal/api/component/storage.py:124 -msgid "Test failure: Account invalid" -msgstr "测试失败: 账号无效" - -#: terminal/api/component/terminal.py:38 -msgid "Have online sessions" -msgstr "有在线会话" - -#: terminal/api/session/session.py:217 -msgid "Session does not exist: {}" -msgstr "会话不存在: {}" - -#: terminal/api/session/session.py:220 -msgid "Session is finished or the protocol not supported" -msgstr "会话已经完成或协议不支持" - -#: terminal/api/session/session.py:225 -msgid "User does not exist: {}" -msgstr "用户不存在: {}" - -#: terminal/api/session/session.py:233 -msgid "User does not have permission" -msgstr "用户没有权限" - -#: terminal/api/session/sharing.py:29 -msgid "Secure session sharing settings is disabled" -msgstr "未开启会话共享" - -#: terminal/apps.py:9 -msgid "Terminals" -msgstr "终端管理" - -#: terminal/backends/command/es.py:28 -msgid "Invalid elasticsearch config" -msgstr "无效的 Elasticsearch 配置" - -#: terminal/backends/command/es.py:33 -msgid "Not Support Elasticsearch8" -msgstr "不支持 Elasticsearch8" - -#: terminal/backends/command/models.py:16 -msgid "Ordinary" -msgstr "普通" - -#: terminal/backends/command/models.py:17 -msgid "Dangerous" -msgstr "危险" - -#: terminal/backends/command/models.py:23 -msgid "Input" -msgstr "输入" - -#: terminal/backends/command/models.py:24 -#: terminal/backends/command/serializers.py:37 -msgid "Output" -msgstr "输出" - -#: terminal/backends/command/models.py:25 terminal/models/session/replay.py:9 -#: terminal/models/session/sharing.py:19 terminal/models/session/sharing.py:78 -#: terminal/templates/terminal/_msg_command_alert.html:10 -#: tickets/models/ticket/command_confirm.py:17 -msgid "Session" -msgstr "会话" - -#: terminal/backends/command/models.py:26 -#: terminal/backends/command/serializers.py:18 -msgid "Risk level" -msgstr "风险等级" - -#: terminal/backends/command/serializers.py:16 -msgid "Session ID" -msgstr "会话ID" - -#: terminal/backends/command/serializers.py:38 -msgid "Risk level display" -msgstr "风险等级名称" - -#: terminal/backends/command/serializers.py:39 -msgid "Timestamp" -msgstr "时间戳" - -#: terminal/backends/command/serializers.py:41 -#: terminal/models/component/terminal.py:84 -msgid "Remote Address" -msgstr "远端地址" - -#: terminal/const.py:37 -msgid "Critical" -msgstr "严重" - -#: terminal/const.py:38 -msgid "High" -msgstr "较高" - -#: terminal/const.py:39 users/templates/users/reset_password.html:50 -msgid "Normal" -msgstr "正常" - -#: terminal/const.py:40 -msgid "Offline" -msgstr "离线" - -#: terminal/const.py:80 terminal/const.py:81 terminal/const.py:82 -#: terminal/const.py:83 terminal/const.py:84 -#, fuzzy -#| msgid "Client" -msgid "DB Client" -msgstr "客户端" - -#: terminal/exceptions.py:8 -msgid "Bulk create not support" -msgstr "不支持批量创建" - -#: terminal/exceptions.py:13 -msgid "Storage is invalid" -msgstr "存储无效" - -#: terminal/models/applet/applet.py:23 -msgid "Author" -msgstr "作者" - -#: terminal/models/applet/applet.py:27 -msgid "Tags" -msgstr "标签" - -#: terminal/models/applet/applet.py:31 terminal/serializers/storage.py:157 -msgid "Hosts" -msgstr "主机" - -#: terminal/models/applet/applet.py:58 terminal/models/applet/host.py:27 -msgid "Applet" -msgstr "远程应用" - -#: terminal/models/applet/host.py:18 terminal/serializers/applet_host.py:38 -#, fuzzy -#| msgid "More login options" -msgid "Deploy options" -msgstr "其他方式登录" - -#: terminal/models/applet/host.py:19 -msgid "Inited" -msgstr "" - -#: terminal/models/applet/host.py:20 -#, fuzzy -#| msgid "Date finished" -msgid "Date inited" -msgstr "结束日期" - -#: terminal/models/applet/host.py:21 -msgid "Date synced" -msgstr "最后同步日期" - -#: terminal/models/applet/host.py:102 -msgid "Hosting" -msgstr "主机" - -#: terminal/models/applet/host.py:103 -msgid "Initial" -msgstr "" - -#: terminal/models/component/endpoint.py:14 -msgid "HTTPS Port" -msgstr "HTTPS 端口" - -#: terminal/models/component/endpoint.py:15 -msgid "HTTP Port" -msgstr "HTTP 端口" - -#: terminal/models/component/endpoint.py:16 -msgid "SSH Port" -msgstr "SSH 端口" - -#: terminal/models/component/endpoint.py:17 -msgid "RDP Port" -msgstr "RDP 端口" - -#: terminal/models/component/endpoint.py:18 -msgid "MySQL Port" -msgstr "MySQL 端口" - -#: terminal/models/component/endpoint.py:19 -msgid "MariaDB Port" -msgstr "MariaDB 端口" - -#: terminal/models/component/endpoint.py:20 -msgid "PostgreSQL Port" -msgstr "PostgreSQL 端口" - -#: terminal/models/component/endpoint.py:21 -msgid "Redis Port" -msgstr "Redis 端口" - -#: terminal/models/component/endpoint.py:22 -msgid "Oracle 11g Port" -msgstr "Oracle 11g 端口" - -#: terminal/models/component/endpoint.py:23 -msgid "Oracle 12c Port" -msgstr "Oracle 12c 端口" - -#: terminal/models/component/endpoint.py:29 -#: terminal/models/component/endpoint.py:95 terminal/serializers/endpoint.py:57 -#: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50 -#: terminal/serializers/storage.py:80 terminal/serializers/storage.py:90 -#: terminal/serializers/storage.py:98 -msgid "Endpoint" -msgstr "端点" - -#: terminal/models/component/endpoint.py:88 -msgid "IP group" -msgstr "IP 组" - -#: terminal/models/component/endpoint.py:100 -msgid "Endpoint rule" -msgstr "端点规则" - -#: terminal/models/component/status.py:14 -msgid "Session Online" -msgstr "在线会话" - -#: terminal/models/component/status.py:15 -msgid "CPU Load" -msgstr "CPU负载" - -#: terminal/models/component/status.py:16 -msgid "Memory Used" -msgstr "内存使用" - -#: terminal/models/component/status.py:17 -msgid "Disk Used" -msgstr "磁盘使用" - -#: terminal/models/component/status.py:18 -msgid "Connections" -msgstr "连接数" - -#: terminal/models/component/status.py:19 -msgid "Threads" -msgstr "线程数" - -#: terminal/models/component/status.py:20 -msgid "Boot Time" -msgstr "运行时间" - -#: terminal/models/component/storage.py:27 -msgid "Default storage" -msgstr "默认存储" - -#: terminal/models/component/storage.py:136 -#: terminal/models/component/terminal.py:85 -msgid "Command storage" -msgstr "命令存储" - -#: terminal/models/component/storage.py:196 -#: terminal/models/component/terminal.py:86 -msgid "Replay storage" -msgstr "录像存储" - -#: terminal/models/component/terminal.py:82 -msgid "type" -msgstr "类型" - -#: terminal/models/component/terminal.py:159 -msgid "Can view terminal config" -msgstr "可以查看终端配置" - -#: terminal/models/session/command.py:66 -msgid "Command record" -msgstr "命令记录" - -#: terminal/models/session/replay.py:12 -msgid "Session replay" -msgstr "会话录像" - -#: terminal/models/session/replay.py:14 -msgid "Can upload session replay" -msgstr "可以上传会话录像" - -#: terminal/models/session/replay.py:15 -msgid "Can download session replay" -msgstr "可以下载会话录像" - -#: terminal/models/session/session.py:36 terminal/models/session/sharing.py:101 -msgid "Login from" -msgstr "登录来源" - -#: terminal/models/session/session.py:40 -msgid "Replay" -msgstr "回放" - -#: terminal/models/session/session.py:44 -msgid "Date end" -msgstr "结束日期" - -#: terminal/models/session/session.py:236 -msgid "Session record" -msgstr "会话记录" - -#: terminal/models/session/session.py:238 -msgid "Can monitor session" -msgstr "可以监控会话" - -#: terminal/models/session/session.py:239 -msgid "Can share session" -msgstr "可以分享会话" - -#: terminal/models/session/session.py:240 -msgid "Can terminate session" -msgstr "可以终断会话" - -#: terminal/models/session/session.py:241 -msgid "Can validate session action perm" -msgstr "可以验证会话动作权限" - -#: terminal/models/session/sharing.py:26 terminal/models/session/sharing.py:80 -msgid "Verify code" -msgstr "验证码" - -#: terminal/models/session/sharing.py:31 -msgid "Expired time (min)" -msgstr "过期时间 (分)" - -#: terminal/models/session/sharing.py:37 terminal/models/session/sharing.py:83 -msgid "Session sharing" -msgstr "会话分享" - -#: terminal/models/session/sharing.py:39 -msgid "Can add super session sharing" -msgstr "可以创建超级会话分享" - -#: terminal/models/session/sharing.py:66 -msgid "Link not active" -msgstr "链接失效" - -#: terminal/models/session/sharing.py:68 -msgid "Link expired" -msgstr "链接过期" - -#: terminal/models/session/sharing.py:70 -msgid "User not allowed to join" -msgstr "该用户无权加入会话" - -#: terminal/models/session/sharing.py:87 terminal/serializers/sharing.py:59 -msgid "Joiner" -msgstr "加入者" - -#: terminal/models/session/sharing.py:90 -msgid "Date joined" -msgstr "加入日期" - -#: terminal/models/session/sharing.py:93 -msgid "Date left" -msgstr "结束日期" - -#: terminal/models/session/sharing.py:116 -msgid "Session join record" -msgstr "会话加入记录" - -#: terminal/models/session/sharing.py:132 -msgid "Invalid verification code" -msgstr "验证码不正确" - -#: terminal/notifications.py:22 -msgid "Sessions" -msgstr "会话管理" - -#: terminal/notifications.py:68 -msgid "Danger command alert" -msgstr "危险命令告警" - -#: terminal/notifications.py:92 terminal/notifications.py:140 -msgid "Level" -msgstr "级别" - -#: terminal/notifications.py:110 -msgid "Batch danger command alert" -msgstr "批量危险命令告警" - -#: terminal/serializers/applet.py:16 -msgid "Published" -msgstr "已安装" - -#: terminal/serializers/applet.py:17 -msgid "Unpublished" -msgstr "未安装" - -#: terminal/serializers/applet.py:18 -msgid "Not match" -msgstr "不匹配" - -#: terminal/serializers/applet.py:32 -msgid "Icon" -msgstr "图标" - -#: terminal/serializers/applet_host.py:21 -msgid "Per Session" -msgstr "按会话" - -#: terminal/serializers/applet_host.py:22 -msgid "Per Device" -msgstr "按设备" - -#: terminal/serializers/applet_host.py:28 -msgid "RDS Licensing" -msgstr "部署 RDS 许可服务" - -#: terminal/serializers/applet_host.py:29 -msgid "RDS License Server" -msgstr "RDS 许可服务主机" - -#: terminal/serializers/applet_host.py:30 -msgid "RDS Licensing Mode" -msgstr "RDS 许可模式" - -#: terminal/serializers/applet_host.py:32 -msgid "RDS fSingleSessionPerUser" -msgstr "RDS 会话用户数" - -#: terminal/serializers/applet_host.py:33 -msgid "RDS Max Disconnection Time" -msgstr "RDS 会话断开时间" - -#: terminal/serializers/applet_host.py:34 -msgid "RDS Remote App Logoff Time Limit" -msgstr "RDS 远程应用注销时间" - -#: terminal/serializers/applet_host.py:40 terminal/serializers/terminal.py:41 -msgid "Load status" -msgstr "负载状态" - -#: terminal/serializers/endpoint.py:12 -msgid "Oracle port" -msgstr "" - -#: terminal/serializers/endpoint.py:51 -msgid "" -"If asset IP addresses under different endpoints conflict, use asset labels" -msgstr "如果不同端点下的资产 IP 有冲突,使用资产标签实现" - -#: terminal/serializers/session.py:17 terminal/serializers/session.py:42 -msgid "Terminal display" -msgstr "终端显示" - -#: terminal/serializers/session.py:33 -msgid "User ID" -msgstr "用户 ID" - -#: terminal/serializers/session.py:34 -msgid "Asset ID" -msgstr "资产 ID" - -#: terminal/serializers/session.py:35 -msgid "Login from display" -msgstr "登录来源名称" - -#: terminal/serializers/session.py:37 -msgid "Can replay" -msgstr "是否可重放" - -#: terminal/serializers/session.py:38 -msgid "Can join" -msgstr "是否可加入" - -#: terminal/serializers/session.py:39 -msgid "Terminal ID" -msgstr "终端 ID" - -#: terminal/serializers/session.py:40 -msgid "Is finished" -msgstr "是否完成" - -#: terminal/serializers/session.py:41 -msgid "Can terminate" -msgstr "是否可中断" - -#: terminal/serializers/session.py:47 -msgid "Command amount" -msgstr "命令数量" - -#: terminal/serializers/storage.py:20 -msgid "Endpoint invalid: remove path `{}`" -msgstr "端点无效: 移除路径 `{}`" - -#: terminal/serializers/storage.py:26 -msgid "Bucket" -msgstr "桶名称" - -#: terminal/serializers/storage.py:30 -#: xpack/plugins/cloud/serializers/account_attrs.py:17 -msgid "Access key id" -msgstr "访问密钥 ID(AK)" - -#: terminal/serializers/storage.py:34 -#: xpack/plugins/cloud/serializers/account_attrs.py:20 -msgid "Access key secret" -msgstr "访问密钥密文(SK)" - -#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:216 -msgid "Region" -msgstr "地域" - -#: terminal/serializers/storage.py:109 -msgid "Container name" -msgstr "容器名称" - -#: terminal/serializers/storage.py:112 -msgid "Account key" -msgstr "账号密钥" - -#: terminal/serializers/storage.py:115 -msgid "Endpoint suffix" -msgstr "端点后缀" - -#: terminal/serializers/storage.py:135 -msgid "The address format is incorrect" -msgstr "地址格式不正确" - -#: terminal/serializers/storage.py:142 -msgid "Host invalid" -msgstr "主机无效" - -#: terminal/serializers/storage.py:145 -msgid "Port invalid" -msgstr "端口无效" - -#: terminal/serializers/storage.py:160 -msgid "Index by date" -msgstr "按日期建索引" - -#: terminal/serializers/storage.py:161 -msgid "Whether to create an index by date" -msgstr "是否根据日期动态建立索引" - -#: terminal/serializers/storage.py:164 -msgid "Index" -msgstr "索引" - -#: terminal/serializers/storage.py:166 -msgid "Doc type" -msgstr "文档类型" - -#: terminal/serializers/storage.py:168 -msgid "Ignore Certificate Verification" -msgstr "忽略证书认证" - -#: terminal/serializers/terminal.py:77 terminal/serializers/terminal.py:85 -msgid "Not found" -msgstr "没有发现" - -#: terminal/templates/terminal/_msg_command_alert.html:10 -msgid "view" -msgstr "查看" - -#: tickets/apps.py:7 -msgid "Tickets" -msgstr "工单管理" - -#: tickets/const.py:9 -msgid "Apply for asset" -msgstr "申请资产" - -#: tickets/const.py:16 tickets/const.py:24 tickets/const.py:43 -msgid "Open" -msgstr "打开" - -#: tickets/const.py:18 tickets/const.py:31 -msgid "Reopen" -msgstr "" - -#: tickets/const.py:19 tickets/const.py:32 -msgid "Approved" -msgstr "已同意" - -#: tickets/const.py:20 tickets/const.py:33 -msgid "Rejected" -msgstr "已拒绝" - -#: tickets/const.py:30 tickets/const.py:38 -msgid "Closed" -msgstr "关闭的" - -#: tickets/const.py:46 -msgid "Approve" -msgstr "同意" - -#: tickets/const.py:50 -msgid "One level" -msgstr "1 级" - -#: tickets/const.py:51 -msgid "Two level" -msgstr "2 级" - -#: tickets/const.py:55 -msgid "Org admin" -msgstr "组织管理员" - -#: tickets/const.py:56 -msgid "Custom user" -msgstr "自定义用户" - -#: tickets/const.py:57 -msgid "Super admin" -msgstr "超级管理员" - -#: tickets/const.py:58 -msgid "Super admin and org admin" -msgstr "组织管理员或超级管理员" - -#: tickets/errors.py:9 -msgid "Ticket already closed" -msgstr "工单已经关闭" - -#: tickets/handlers/apply_asset.py:36 -msgid "" -"Created by the ticket ticket title: {} ticket applicant: {} ticket " -"processor: {} ticket ID: {}" -msgstr "" -"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" - -#: tickets/handlers/base.py:84 -msgid "Change field" -msgstr "变更字段" - -#: tickets/handlers/base.py:84 -msgid "Before change" -msgstr "变更前" - -#: tickets/handlers/base.py:84 -msgid "After change" -msgstr "变更后" - -#: tickets/handlers/base.py:96 -msgid "{} {} the ticket" -msgstr "{} {} 工单" - -#: tickets/models/comment.py:14 -msgid "common" -msgstr "" - -#: tickets/models/comment.py:23 -msgid "User display name" -msgstr "用户显示名称" - -#: tickets/models/comment.py:24 -msgid "Body" -msgstr "内容" - -#: tickets/models/flow.py:20 tickets/models/flow.py:62 -#: tickets/models/ticket/general.py:39 -msgid "Approve level" -msgstr "审批级别" - -#: tickets/models/flow.py:25 tickets/serializers/flow.py:18 -msgid "Approve strategy" -msgstr "审批策略" - -#: tickets/models/flow.py:30 tickets/serializers/flow.py:20 -msgid "Assignees" -msgstr "受理人" - -#: tickets/models/flow.py:34 -msgid "Ticket flow approval rule" -msgstr "工单批准信息" - -#: tickets/models/flow.py:67 -msgid "Ticket flow" -msgstr "工单流程" - -#: tickets/models/relation.py:10 -msgid "Ticket session relation" -msgstr "工单会话" - -#: tickets/models/ticket/apply_application.py:10 -#: tickets/models/ticket/apply_asset.py:13 -msgid "Permission name" -msgstr "授权规则名称" - -#: tickets/models/ticket/apply_application.py:19 -msgid "Apply applications" -msgstr "申请应用" - -#: tickets/models/ticket/apply_application.py:22 -msgid "Apply system users" -msgstr "申请的系统用户" - -#: tickets/models/ticket/apply_asset.py:9 -#: tickets/serializers/ticket/apply_asset.py:14 -msgid "Select at least one asset or node" -msgstr "资产或者节点至少选择一项" - -#: tickets/models/ticket/apply_asset.py:14 -#: tickets/serializers/ticket/apply_asset.py:19 -msgid "Apply nodes" -msgstr "申请节点" - -#: tickets/models/ticket/apply_asset.py:16 -#: tickets/serializers/ticket/apply_asset.py:18 -msgid "Apply assets" -msgstr "申请资产" - -#: tickets/models/ticket/apply_asset.py:17 -msgid "Apply accounts" -msgstr "申请账号" - -#: tickets/models/ticket/command_confirm.py:10 -msgid "Run user" -msgstr "运行的用户" - -#: tickets/models/ticket/command_confirm.py:12 -msgid "Run asset" -msgstr "运行的资产" - -#: tickets/models/ticket/command_confirm.py:13 -msgid "Run command" -msgstr "运行的命令" - -#: tickets/models/ticket/command_confirm.py:14 -msgid "Run account" -msgstr "账号" - -#: tickets/models/ticket/command_confirm.py:21 -msgid "From cmd filter" -msgstr "来自命令过滤规则" - -#: tickets/models/ticket/command_confirm.py:25 -msgid "From cmd filter rule" -msgstr "来自命令过滤规则" - -#: tickets/models/ticket/general.py:74 -msgid "Ticket step" -msgstr "工单步骤" - -#: tickets/models/ticket/general.py:92 -msgid "Ticket assignee" -msgstr "工单受理人" - -#: tickets/models/ticket/general.py:271 -msgid "Title" -msgstr "标题" - -#: tickets/models/ticket/general.py:287 -msgid "Applicant" -msgstr "申请人" - -#: tickets/models/ticket/general.py:291 -msgid "TicketFlow" -msgstr "工单流程" - -#: tickets/models/ticket/general.py:294 -msgid "Approval step" -msgstr "审批步骤" - -#: tickets/models/ticket/general.py:297 -msgid "Relation snapshot" -msgstr "工单快照" - -#: tickets/models/ticket/general.py:391 -msgid "Please try again" -msgstr "请再次尝试" - -#: tickets/models/ticket/general.py:424 -msgid "Super ticket" -msgstr "超级工单" - -#: tickets/models/ticket/login_asset_confirm.py:11 -msgid "Login user" -msgstr "登录用户" - -#: tickets/models/ticket/login_asset_confirm.py:14 -msgid "Login asset" -msgstr "登录资产" - -#: tickets/models/ticket/login_asset_confirm.py:17 -msgid "Login account" -msgstr "登录账号" - -#: tickets/models/ticket/login_confirm.py:12 -msgid "Login datetime" -msgstr "登录日期" - -#: tickets/notifications.py:63 -msgid "Ticket basic info" -msgstr "工单基本信息" - -#: tickets/notifications.py:64 -msgid "Ticket applied info" -msgstr "工单申请信息" - -#: tickets/notifications.py:109 -msgid "Your has a new ticket, applicant - {}" -msgstr "你有一个新的工单, 申请人 - {}" - -#: tickets/notifications.py:113 -msgid "{}: New Ticket - {} ({})" -msgstr "新工单 - {} ({})" - -#: tickets/notifications.py:157 -msgid "Your ticket has been processed, processor - {}" -msgstr "你的工单已被处理, 处理人 - {}" - -#: tickets/notifications.py:161 -msgid "Ticket has processed - {} ({})" -msgstr "你的工单已被处理, 处理人 - {} ({})" - -#: tickets/serializers/flow.py:21 -msgid "Assignees display" -msgstr "受理人名称" - -#: tickets/serializers/flow.py:47 -msgid "Please select the Assignees" -msgstr "请选择受理人" - -#: tickets/serializers/flow.py:75 -msgid "The current organization type already exists" -msgstr "当前组织已存在该类型" - -#: tickets/serializers/super_ticket.py:11 -msgid "Processor" -msgstr "处理人" - -#: tickets/serializers/ticket/apply_asset.py:20 -#, fuzzy -#| msgid "Apply applications" -msgid "Apply actions" -msgstr "申请应用" - -#: tickets/serializers/ticket/common.py:15 -#: tickets/serializers/ticket/common.py:77 -msgid "Created by ticket ({}-{})" -msgstr "通过工单创建 ({}-{})" - -#: tickets/serializers/ticket/common.py:67 -msgid "The expiration date should be greater than the start date" -msgstr "过期时间要大于开始时间" - -#: tickets/serializers/ticket/common.py:84 -msgid "Permission named `{}` already exists" -msgstr "授权名称 `{}` 已存在" - -#: tickets/serializers/ticket/ticket.py:83 -msgid "The ticket flow `{}` does not exist" -msgstr "工单流程 `{}` 不存在" - -#: tickets/templates/tickets/_msg_ticket.html:20 -msgid "View details" -msgstr "查看详情" - -#: tickets/templates/tickets/_msg_ticket.html:25 -msgid "Direct approval" -msgstr "直接批准" - -#: tickets/templates/tickets/approve_check_password.html:11 -msgid "Ticket information" -msgstr "工单信息" - -#: tickets/templates/tickets/approve_check_password.html:29 -#: tickets/views/approve.py:38 -msgid "Ticket approval" -msgstr "工单审批" - -#: tickets/templates/tickets/approve_check_password.html:45 -msgid "Approval" -msgstr "同意" - -#: tickets/templates/tickets/approve_check_password.html:54 -msgid "Go Login" -msgstr "去登录" - -#: tickets/views/approve.py:39 -msgid "" -"This ticket does not exist, the process has ended, or this link has expired" -msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期" - -#: tickets/views/approve.py:68 -msgid "Click the button below to approve or reject" -msgstr "点击下方按钮同意或者拒绝" - -#: tickets/views/approve.py:70 -msgid "After successful authentication, this ticket can be approved directly" -msgstr "认证成功后,工单可直接审批" - -#: tickets/views/approve.py:92 -msgid "Illegal approval action" -msgstr "无效的审批动作" - -#: tickets/views/approve.py:105 -msgid "This user is not authorized to approve this ticket" -msgstr "此用户无权审批此工单" - -#: users/api/user.py:183 -msgid "Could not reset self otp, use profile reset instead" -msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置" - -#: users/apps.py:9 -msgid "Users" -msgstr "用户管理" - -#: users/const.py:10 -msgid "System administrator" -msgstr "系统管理员" - -#: users/const.py:11 -msgid "System auditor" -msgstr "系统审计员" - -#: users/const.py:12 -msgid "Organization administrator" -msgstr "组织管理员" - -#: users/const.py:13 -msgid "Organization auditor" -msgstr "组织审计员" - -#: users/const.py:18 -msgid "Reset link will be generated and sent to the user" -msgstr "生成重置密码链接,通过邮件发送给用户" - -#: users/const.py:19 -msgid "Set password" -msgstr "设置密码" - -#: users/exceptions.py:10 -msgid "MFA not enabled" -msgstr "MFA 多因子认证没有开启" - -#: users/exceptions.py:20 -msgid "MFA method not support" -msgstr "不支持该 MFA 方式" - -#: users/forms/profile.py:50 -msgid "" -"When enabled, you will enter the MFA binding process the next time you log " -"in. you can also directly bind in \"personal information -> quick " -"modification -> change MFA Settings\"!" -msgstr "" -"启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速" -"修改->设置 MFA 多因子认证)中直接绑定!" - -#: users/forms/profile.py:61 -msgid "* Enable MFA to make the account more secure." -msgstr "* 启用 MFA 多因子认证,使账号更加安全。" - -#: users/forms/profile.py:70 -msgid "" -"In order to protect you and your company, please keep your account, password " -"and key sensitive information properly. (for example: setting complex " -"password, enabling MFA)" -msgstr "" -"为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息;(如:" -"设置复杂密码,并启用 MFA 多因子认证)" - -#: users/forms/profile.py:77 -msgid "Finish" -msgstr "完成" - -#: users/forms/profile.py:84 -msgid "New password" -msgstr "新密码" - -#: users/forms/profile.py:89 -msgid "Confirm password" -msgstr "确认密码" - -#: users/forms/profile.py:97 -msgid "Password does not match" -msgstr "密码不一致" - -#: users/forms/profile.py:109 -msgid "Old password" -msgstr "原来密码" - -#: users/forms/profile.py:119 -msgid "Old password error" -msgstr "原来密码错误" - -#: users/forms/profile.py:129 -msgid "Automatically configure and download the SSH key" -msgstr "自动配置并下载 SSH 密钥" - -#: users/forms/profile.py:131 -msgid "ssh public key" -msgstr "SSH公钥" - -#: users/forms/profile.py:132 -msgid "ssh-rsa AAAA..." -msgstr "ssh-rsa AAAA..." - -#: users/forms/profile.py:133 -msgid "Paste your id_rsa.pub here." -msgstr "复制你的公钥到这里" - -#: users/forms/profile.py:146 -msgid "Public key should not be the same as your old one." -msgstr "不能和原来的密钥相同" - -#: users/forms/profile.py:150 users/serializers/profile.py:100 -#: users/serializers/profile.py:183 users/serializers/profile.py:210 -msgid "Not a valid ssh public key" -msgstr "SSH 密钥不合法" - -#: users/forms/profile.py:161 users/models/user.py:696 -msgid "Public key" -msgstr "SSH 公钥" - -#: users/models/user.py:558 -msgid "Force enable" -msgstr "强制启用" - -#: users/models/user.py:625 -msgid "Local" -msgstr "数据库" - -#: users/models/user.py:677 users/serializers/user.py:204 -msgid "Is service account" -msgstr "服务账号" - -#: users/models/user.py:679 -msgid "Avatar" -msgstr "头像" - -#: users/models/user.py:682 -msgid "Wechat" -msgstr "微信" - -#: users/models/user.py:685 -msgid "Phone" -msgstr "手机" - -#: users/models/user.py:693 -msgid "Private key" -msgstr "ssh私钥" - -#: users/models/user.py:699 -msgid "Secret key" -msgstr "Secret key" - -#: users/models/user.py:715 -msgid "Source" -msgstr "来源" - -#: users/models/user.py:719 -msgid "Date password last updated" -msgstr "最后更新密码日期" - -#: users/models/user.py:722 -msgid "Need update password" -msgstr "需要更新密码" - -#: users/models/user.py:897 -msgid "Can invite user" -msgstr "可以邀请用户" - -#: users/models/user.py:898 -msgid "Can remove user" -msgstr "可以移除用户" - -#: users/models/user.py:899 -msgid "Can match user" -msgstr "可以匹配用户" - -#: users/models/user.py:908 -msgid "Administrator" -msgstr "管理员" - -#: users/models/user.py:911 -msgid "Administrator is the super user of system" -msgstr "Administrator是初始的超级管理员" - -#: users/models/user.py:936 -msgid "User password history" -msgstr "用户密码历史" - -#: users/notifications.py:55 -#: users/templates/users/_msg_password_expire_reminder.html:17 -#: users/templates/users/reset_password.html:5 -#: users/templates/users/reset_password.html:6 -msgid "Reset password" -msgstr "重置密码" - -#: users/notifications.py:85 users/views/profile/reset.py:127 -msgid "Reset password success" -msgstr "重置密码成功" - -#: users/notifications.py:117 -msgid "Reset public key success" -msgstr "重置公钥成功" - -#: users/notifications.py:143 -msgid "Password is about expire" -msgstr "密码即将过期" - -#: users/notifications.py:171 -msgid "Account is about expire" -msgstr "账号即将过期" - -#: users/notifications.py:193 -msgid "Reset SSH Key" -msgstr "重置 SSH 密钥" - -#: users/notifications.py:214 -msgid "Reset MFA" -msgstr "重置 MFA" - -#: users/serializers/profile.py:30 -msgid "The old password is incorrect" -msgstr "旧密码错误" - -#: users/serializers/profile.py:37 users/serializers/profile.py:197 -msgid "Password does not match security rules" -msgstr "密码不满足安全规则" - -#: users/serializers/profile.py:41 -msgid "The new password cannot be the last {} passwords" -msgstr "新密码不能是最近 {} 次的密码" - -#: users/serializers/profile.py:49 users/serializers/profile.py:71 -msgid "The newly set password is inconsistent" -msgstr "两次密码不一致" - -#: users/serializers/profile.py:149 users/serializers/user.py:201 -msgid "Is first login" -msgstr "首次登录" - -#: users/serializers/user.py:30 -msgid "System roles" -msgstr "系统角色" - -#: users/serializers/user.py:35 -msgid "Org roles" -msgstr "组织角色" - -#: users/serializers/user.py:38 -msgid "System roles display" -msgstr "系统角色显示" - -#: users/serializers/user.py:40 -msgid "Org roles display" -msgstr "组织角色显示" - -#: users/serializers/user.py:90 -#: xpack/plugins/change_auth_plan/models/base.py:35 -#: xpack/plugins/change_auth_plan/serializers/base.py:27 -msgid "Password strategy" -msgstr "密码策略" - -#: users/serializers/user.py:92 -msgid "MFA enabled" -msgstr "MFA 已启用" - -#: users/serializers/user.py:94 -msgid "MFA force enabled" -msgstr "强制 MFA" - -#: users/serializers/user.py:97 -msgid "MFA level display" -msgstr "MFA 等级名称" - -#: users/serializers/user.py:99 -msgid "Login blocked" -msgstr "登录被阻塞" - -#: users/serializers/user.py:102 -msgid "Can public key authentication" -msgstr "能否公钥认证" - -#: users/serializers/user.py:206 -msgid "Avatar url" -msgstr "头像路径" - -#: users/serializers/user.py:208 -msgid "Groups name" -msgstr "用户组名" - -#: users/serializers/user.py:209 -msgid "Source name" -msgstr "用户来源名" - -#: users/serializers/user.py:210 -msgid "Organization role name" -msgstr "组织角色名称" - -#: users/serializers/user.py:211 -msgid "Super role name" -msgstr "超级角色名称" - -#: users/serializers/user.py:212 -msgid "Total role name" -msgstr "汇总角色名称" - -#: users/serializers/user.py:214 -msgid "Is wecom bound" -msgstr "是否绑定了企业微信" - -#: users/serializers/user.py:215 -msgid "Is dingtalk bound" -msgstr "是否绑定了钉钉" - -#: users/serializers/user.py:216 -msgid "Is feishu bound" -msgstr "是否绑定了飞书" - -#: users/serializers/user.py:217 -msgid "Is OTP bound" -msgstr "是否绑定了虚拟 MFA" - -#: users/serializers/user.py:219 -msgid "System role name" -msgstr "系统角色名称" - -#: users/serializers/user.py:325 -msgid "Select users" -msgstr "选择用户" - -#: users/serializers/user.py:326 -msgid "For security, only list several users" -msgstr "为了安全,仅列出几个用户" - -#: users/serializers/user.py:362 -msgid "name not unique" -msgstr "名称重复" - -#: users/templates/users/_msg_account_expire_reminder.html:7 -msgid "Your account will expire in" -msgstr "您的账号即将过期" - -#: users/templates/users/_msg_account_expire_reminder.html:8 -msgid "" -"In order not to affect your normal work, please contact the administrator " -"for confirmation." -msgstr "" -"为了不影响您正常工作,请联系管理员确认。\n" -" " - -#: users/templates/users/_msg_password_expire_reminder.html:7 -msgid "Your password will expire in" -msgstr "您的密码将过期" - -#: users/templates/users/_msg_password_expire_reminder.html:8 -msgid "" -"For your account security, please click on the link below to update your " -"password in time" -msgstr "为了您的账号安全,请点击下面的链接及时更新密码" - -#: users/templates/users/_msg_password_expire_reminder.html:11 -msgid "Click here update password" -msgstr "点击这里更新密码" - -#: users/templates/users/_msg_password_expire_reminder.html:16 -msgid "If your password has expired, please click the link below to" -msgstr "如果你的密码已过期,请点击" - -#: users/templates/users/_msg_reset_mfa.html:7 -msgid "Your MFA has been reset by site administrator" -msgstr "你的 MFA 已经被管理员重置" - -#: users/templates/users/_msg_reset_mfa.html:8 -#: users/templates/users/_msg_reset_ssh_key.html:8 -msgid "Please click the link below to set" -msgstr "请点击下面链接设置" - -#: users/templates/users/_msg_reset_mfa.html:11 -#: users/templates/users/_msg_reset_ssh_key.html:11 -msgid "Click here set" -msgstr "点击这里设置" - -#: users/templates/users/_msg_reset_ssh_key.html:7 -msgid "Your ssh public key has been reset by site administrator" -msgstr "你的 SSH 密钥已经被管理员重置" - -#: users/templates/users/_msg_user_created.html:15 -msgid "click here to set your password" -msgstr "点击这里设置密码" - -#: users/templates/users/forgot_password.html:24 -msgid "Input your email, that will send a mail to your" -msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" - -#: users/templates/users/forgot_password.html:33 -msgid "Submit" -msgstr "提交" - -#: users/templates/users/mfa_setting.html:24 -msgid "Enable MFA" -msgstr "启用 MFA 多因子认证" - -#: users/templates/users/mfa_setting.html:30 -msgid "MFA force enable, cannot disable" -msgstr "MFA 已强制启用,无法禁用" - -#: users/templates/users/mfa_setting.html:48 -msgid "MFA setting" -msgstr "设置 MFA 多因子认证" - -#: users/templates/users/reset_password.html:23 -msgid "Your password must satisfy" -msgstr "您的密码必须满足:" - -#: users/templates/users/reset_password.html:24 -msgid "Password strength" -msgstr "密码强度:" - -#: users/templates/users/reset_password.html:48 -msgid "Very weak" -msgstr "很弱" - -#: users/templates/users/reset_password.html:49 -msgid "Weak" -msgstr "弱" - -#: users/templates/users/reset_password.html:51 -msgid "Medium" -msgstr "一般" - -#: users/templates/users/reset_password.html:52 -msgid "Strong" -msgstr "强" - -#: users/templates/users/reset_password.html:53 -msgid "Very strong" -msgstr "很强" - -#: users/templates/users/user_otp_check_password.html:6 -msgid "Enable OTP" -msgstr "启用 MFA(OTP)" - -#: users/templates/users/user_otp_enable_bind.html:6 -msgid "Bind one-time password authenticator" -msgstr "绑定MFA验证器" - -#: users/templates/users/user_otp_enable_bind.html:13 -msgid "" -"Use the MFA Authenticator application to scan the following qr code for a 6-" -"bit verification code" -msgstr "使用 MFA 验证器应用扫描以下二维码,获取6位验证码" - -#: users/templates/users/user_otp_enable_bind.html:22 -#: users/templates/users/user_verify_mfa.html:27 -msgid "Six figures" -msgstr "6 位数字" - -#: users/templates/users/user_otp_enable_install_app.html:6 -msgid "Install app" -msgstr "安装应用" - -#: users/templates/users/user_otp_enable_install_app.html:13 -msgid "" -"Download and install the MFA Authenticator application on your phone or " -"applet of WeChat" -msgstr "请在手机端或微信小程序下载并安装 MFA 验证器应用" - -#: users/templates/users/user_otp_enable_install_app.html:18 -msgid "Android downloads" -msgstr "Android手机下载" - -#: users/templates/users/user_otp_enable_install_app.html:23 -msgid "iPhone downloads" -msgstr "iPhone手机下载" - -#: users/templates/users/user_otp_enable_install_app.html:26 -msgid "" -"After installation, click the next step to enter the binding page (if " -"installed, go to the next step directly)." -msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)" - -#: users/templates/users/user_password_verify.html:8 -#: users/templates/users/user_password_verify.html:9 -msgid "Verify password" -msgstr "校验密码" - -#: users/templates/users/user_verify_mfa.html:9 -msgid "Authenticate" -msgstr "验证身份" - -#: users/templates/users/user_verify_mfa.html:15 -msgid "" -"The account protection has been opened, please complete the following " -"operations according to the prompts" -msgstr "账号保护已开启,请根据提示完成以下操作" - -#: users/templates/users/user_verify_mfa.html:17 -msgid "Open MFA Authenticator and enter the 6-bit dynamic code" -msgstr "请打开 MFA 验证器,输入 6 位动态码" - -#: users/views/profile/otp.py:87 -msgid "Already bound" -msgstr "已经绑定" - -#: users/views/profile/otp.py:88 -msgid "MFA already bound, disable first, then bound" -msgstr "MFA(OTP) 已经绑定,请先禁用,再绑定" - -#: users/views/profile/otp.py:115 -msgid "OTP enable success" -msgstr "MFA(OTP) 启用成功" - -#: users/views/profile/otp.py:116 -msgid "OTP enable success, return login page" -msgstr "MFA(OTP) 启用成功,返回到登录页面" - -#: users/views/profile/otp.py:158 -msgid "Disable OTP" -msgstr "禁用虚拟 MFA(OTP)" - -#: users/views/profile/otp.py:164 -msgid "OTP disable success" -msgstr "MFA(OTP) 禁用成功" - -#: users/views/profile/otp.py:165 -msgid "OTP disable success, return login page" -msgstr "MFA(OTP) 禁用成功,返回登录页面" - -#: users/views/profile/password.py:36 users/views/profile/password.py:41 -msgid "Password invalid" -msgstr "用户名或密码无效" - -#: users/views/profile/reset.py:40 -msgid "Send reset password message" -msgstr "发送重置密码邮件" - -#: users/views/profile/reset.py:41 -msgid "Send reset password mail success, login your mail box and follow it " -msgstr "" -"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" - -#: users/views/profile/reset.py:52 -msgid "Email address invalid, please input again" -msgstr "邮箱地址错误,重新输入" - -#: users/views/profile/reset.py:58 -msgid "" -"The user is from {}, please go to the corresponding system to change the " -"password" -msgstr "用户来自 {} 请去相应系统修改密码" - -#: users/views/profile/reset.py:84 users/views/profile/reset.py:95 -msgid "Token invalid or expired" -msgstr "Token错误或失效" - -#: users/views/profile/reset.py:100 -msgid "User auth from {}, go there change password" -msgstr "用户认证源来自 {}, 请去相应系统修改密码" - -#: users/views/profile/reset.py:107 -msgid "* Your password does not meet the requirements" -msgstr "* 您的密码不符合要求" - -#: users/views/profile/reset.py:113 -msgid "* The new password cannot be the last {} passwords" -msgstr "* 新密码不能是最近 {} 次的密码" - -#: users/views/profile/reset.py:128 -msgid "Reset password success, return to login page" -msgstr "重置密码成功,返回到登录页面" - -#: xpack/apps.py:8 -msgid "XPACK" -msgstr "XPack" - -#: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models/asset.py:124 -msgid "Change auth plan" -msgstr "改密计划" - -#: xpack/plugins/change_auth_plan/models/app.py:45 -#: xpack/plugins/change_auth_plan/models/app.py:94 -msgid "Application change auth plan" -msgstr "应用改密计划" - -#: xpack/plugins/change_auth_plan/models/app.py:98 -#: xpack/plugins/change_auth_plan/models/app.py:150 -msgid "Application change auth plan execution" -msgstr "应用改密计划执行" - -#: xpack/plugins/change_auth_plan/models/app.py:143 -msgid "App" -msgstr "应用" - -#: xpack/plugins/change_auth_plan/models/app.py:155 -msgid "Application change auth plan task" -msgstr "应用改密计划任务" - -#: xpack/plugins/change_auth_plan/models/app.py:179 -#: xpack/plugins/change_auth_plan/models/asset.py:264 -msgid "Password cannot be set to blank, exit. " -msgstr "密码不能设置为空, 退出. " - -#: xpack/plugins/change_auth_plan/models/asset.py:68 -msgid "Asset change auth plan" -msgstr "资产改密计划" - -#: xpack/plugins/change_auth_plan/models/asset.py:135 -msgid "Asset change auth plan execution" -msgstr "资产改密计划执行" - -#: xpack/plugins/change_auth_plan/models/asset.py:211 -msgid "Change auth plan execution" -msgstr "改密计划执行" - -#: xpack/plugins/change_auth_plan/models/asset.py:218 -msgid "Asset change auth plan task" -msgstr "资产改密计划任务" - -#: xpack/plugins/change_auth_plan/models/asset.py:253 -msgid "This asset does not have a privileged user set: " -msgstr "该资产没有设置特权用户: " - -#: xpack/plugins/change_auth_plan/models/asset.py:259 -msgid "" -"The password and key of the current asset privileged user cannot be changed: " -msgstr "不能更改当前资产特权用户的密码及密钥: " - -#: xpack/plugins/change_auth_plan/models/asset.py:270 -msgid "Public key cannot be set to null, exit. " -msgstr "公钥不能设置为空, 退出. " - -#: xpack/plugins/change_auth_plan/models/base.py:114 -msgid "Change auth plan snapshot" -msgstr "改密计划快照" - -#: xpack/plugins/change_auth_plan/models/base.py:184 -msgid "Preflight check" -msgstr "改密前的校验" - -#: xpack/plugins/change_auth_plan/models/base.py:185 -msgid "Change auth" -msgstr "执行改密" - -#: xpack/plugins/change_auth_plan/models/base.py:186 -msgid "Verify auth" -msgstr "验证密码/密钥" - -#: xpack/plugins/change_auth_plan/models/base.py:187 -msgid "Keep auth" -msgstr "保存密码/密钥" - -#: xpack/plugins/change_auth_plan/models/base.py:195 -msgid "Step" -msgstr "步骤" - -#: xpack/plugins/change_auth_plan/serializers/asset.py:30 -msgid "Change Password" -msgstr "更改密码" - -#: xpack/plugins/change_auth_plan/serializers/asset.py:31 -msgid "Change SSH Key" -msgstr "修改 SSH Key" - -#: xpack/plugins/change_auth_plan/serializers/base.py:44 -msgid "Run times" -msgstr "执行次数" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:236 -msgid "After many attempts to change the secret, it still failed" -msgstr "多次尝试改密后, 依然失败" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:255 -msgid "Invalid/incorrect password" -msgstr "无效/错误 密码" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:257 -msgid "Failed to connect to the host" -msgstr "连接主机失败" - -#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:259 -msgid "Data could not be sent to remote" -msgstr "无法将数据发送到远程" - -#: xpack/plugins/change_auth_plan/tasks.py:13 -msgid "Execute change authentication task" -msgstr "执行资产改密计划任务" - -#: xpack/plugins/change_auth_plan/tasks.py:24 -msgid "Start change authentication task" -msgstr "开始资产改密计划任务" - -#: xpack/plugins/change_auth_plan/tasks.py:36 -msgid "Test the validity of the change authentication plan " -msgstr "测试资产改密结果" - -#: xpack/plugins/cloud/api.py:40 -msgid "Test connection successful" -msgstr "测试成功" - -#: xpack/plugins/cloud/api.py:42 -msgid "Test connection failed: {}" -msgstr "测试连接失败:{}" - -#: xpack/plugins/cloud/const.py:8 -msgid "Alibaba Cloud" -msgstr "阿里云" - -#: xpack/plugins/cloud/const.py:9 -msgid "AWS (International)" -msgstr "AWS (国际)" - -#: xpack/plugins/cloud/const.py:10 -msgid "AWS (China)" -msgstr "AWS (中国)" - -#: xpack/plugins/cloud/const.py:11 -msgid "Azure (China)" -msgstr "Azure (中国)" - -#: xpack/plugins/cloud/const.py:12 -msgid "Azure (International)" -msgstr "Azure (国际)" - -#: xpack/plugins/cloud/const.py:13 -msgid "Huawei Cloud" -msgstr "华为云" - -#: xpack/plugins/cloud/const.py:14 -msgid "Baidu Cloud" -msgstr "百度云" - -#: xpack/plugins/cloud/const.py:15 -msgid "JD Cloud" -msgstr "京东云" - -#: xpack/plugins/cloud/const.py:16 -msgid "Tencent Cloud" -msgstr "腾讯云" - -#: xpack/plugins/cloud/const.py:17 -msgid "VMware" -msgstr "VMware" - -#: xpack/plugins/cloud/const.py:18 xpack/plugins/cloud/providers/nutanix.py:13 -msgid "Nutanix" -msgstr "Nutanix" - -#: xpack/plugins/cloud/const.py:19 -msgid "Huawei Private Cloud" -msgstr "华为私有云" - -#: xpack/plugins/cloud/const.py:20 -msgid "Qingyun Private Cloud" -msgstr "青云私有云" - -#: xpack/plugins/cloud/const.py:21 -msgid "OpenStack" -msgstr "OpenStack" - -#: xpack/plugins/cloud/const.py:22 -msgid "Google Cloud Platform" -msgstr "谷歌云" - -#: xpack/plugins/cloud/const.py:23 -msgid "Fusion Compute" -msgstr "" - -#: xpack/plugins/cloud/const.py:28 -msgid "Instance name" -msgstr "实例名称" - -#: xpack/plugins/cloud/const.py:29 -msgid "Instance name and Partial IP" -msgstr "实例名称和部分IP" - -#: xpack/plugins/cloud/const.py:34 -msgid "Succeed" -msgstr "成功" - -#: xpack/plugins/cloud/const.py:38 -msgid "Unsync" -msgstr "未同步" - -#: xpack/plugins/cloud/const.py:39 -msgid "New Sync" -msgstr "新同步" - -#: xpack/plugins/cloud/const.py:40 -msgid "Synced" -msgstr "已同步" - -#: xpack/plugins/cloud/const.py:41 -msgid "Released" -msgstr "已释放" - -#: xpack/plugins/cloud/meta.py:9 -msgid "Cloud center" -msgstr "云管中心" - -#: xpack/plugins/cloud/models.py:32 -msgid "Provider" -msgstr "云服务商" - -#: xpack/plugins/cloud/models.py:36 -msgid "Validity" -msgstr "有效" - -#: xpack/plugins/cloud/models.py:41 -msgid "Cloud account" -msgstr "云账号" - -#: xpack/plugins/cloud/models.py:43 -msgid "Test cloud account" -msgstr "测试云账号" - -#: xpack/plugins/cloud/models.py:90 xpack/plugins/cloud/serializers/task.py:37 -msgid "Regions" -msgstr "地域" - -#: xpack/plugins/cloud/models.py:93 -msgid "Hostname strategy" -msgstr "主机名策略" - -#: xpack/plugins/cloud/models.py:102 xpack/plugins/cloud/serializers/task.py:66 -msgid "Unix admin user" -msgstr "Unix 管理员" - -#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:67 -msgid "Windows admin user" -msgstr "Windows 管理员" - -#: xpack/plugins/cloud/models.py:112 xpack/plugins/cloud/serializers/task.py:44 -msgid "IP network segment group" -msgstr "IP网段组" - -#: xpack/plugins/cloud/models.py:115 xpack/plugins/cloud/serializers/task.py:70 -msgid "Always update" -msgstr "总是更新" - -#: xpack/plugins/cloud/models.py:121 -msgid "Date last sync" -msgstr "最后同步日期" - -#: xpack/plugins/cloud/models.py:126 xpack/plugins/cloud/models.py:167 -msgid "Sync instance task" -msgstr "同步实例任务" - -#: xpack/plugins/cloud/models.py:178 xpack/plugins/cloud/models.py:226 -msgid "Date sync" -msgstr "同步日期" - -#: xpack/plugins/cloud/models.py:182 -msgid "Sync instance task execution" -msgstr "同步实例任务执行" - -#: xpack/plugins/cloud/models.py:206 -msgid "Sync task" -msgstr "同步任务" - -#: xpack/plugins/cloud/models.py:210 -msgid "Sync instance task history" -msgstr "同步实例任务历史" - -#: xpack/plugins/cloud/models.py:213 -msgid "Instance" -msgstr "实例" - -#: xpack/plugins/cloud/models.py:230 -msgid "Sync instance detail" -msgstr "同步实例详情" - -#: xpack/plugins/cloud/providers/aws_international.py:17 -msgid "China (Beijing)" -msgstr "中国(北京)" - -#: xpack/plugins/cloud/providers/aws_international.py:18 -msgid "China (Ningxia)" -msgstr "中国(宁夏)" - -#: xpack/plugins/cloud/providers/aws_international.py:21 -msgid "US East (Ohio)" -msgstr "美国东部(俄亥俄州)" - -#: xpack/plugins/cloud/providers/aws_international.py:22 -msgid "US East (N. Virginia)" -msgstr "美国东部(弗吉尼亚北部)" - -#: xpack/plugins/cloud/providers/aws_international.py:23 -msgid "US West (N. California)" -msgstr "美国西部(加利福尼亚北部)" - -#: xpack/plugins/cloud/providers/aws_international.py:24 -msgid "US West (Oregon)" -msgstr "美国西部(俄勒冈)" - -#: xpack/plugins/cloud/providers/aws_international.py:25 -msgid "Africa (Cape Town)" -msgstr "非洲(开普敦)" - -#: xpack/plugins/cloud/providers/aws_international.py:26 -msgid "Asia Pacific (Hong Kong)" -msgstr "亚太地区(香港)" - -#: xpack/plugins/cloud/providers/aws_international.py:27 -msgid "Asia Pacific (Mumbai)" -msgstr "亚太地区(孟买)" - -#: xpack/plugins/cloud/providers/aws_international.py:28 -msgid "Asia Pacific (Osaka-Local)" -msgstr "亚太区域(大阪当地)" - -#: xpack/plugins/cloud/providers/aws_international.py:29 -msgid "Asia Pacific (Seoul)" -msgstr "亚太区域(首尔)" - -#: xpack/plugins/cloud/providers/aws_international.py:30 -msgid "Asia Pacific (Singapore)" -msgstr "亚太区域(新加坡)" - -#: xpack/plugins/cloud/providers/aws_international.py:31 -msgid "Asia Pacific (Sydney)" -msgstr "亚太区域(悉尼)" - -#: xpack/plugins/cloud/providers/aws_international.py:32 -msgid "Asia Pacific (Tokyo)" -msgstr "亚太区域(东京)" - -#: xpack/plugins/cloud/providers/aws_international.py:33 -msgid "Canada (Central)" -msgstr "加拿大(中部)" - -#: xpack/plugins/cloud/providers/aws_international.py:34 -msgid "Europe (Frankfurt)" -msgstr "欧洲(法兰克福)" - -#: xpack/plugins/cloud/providers/aws_international.py:35 -msgid "Europe (Ireland)" -msgstr "欧洲(爱尔兰)" - -#: xpack/plugins/cloud/providers/aws_international.py:36 -msgid "Europe (London)" -msgstr "欧洲(伦敦)" - -#: xpack/plugins/cloud/providers/aws_international.py:37 -msgid "Europe (Milan)" -msgstr "欧洲(米兰)" - -#: xpack/plugins/cloud/providers/aws_international.py:38 -msgid "Europe (Paris)" -msgstr "欧洲(巴黎)" - -#: xpack/plugins/cloud/providers/aws_international.py:39 -msgid "Europe (Stockholm)" -msgstr "欧洲(斯德哥尔摩)" - -#: xpack/plugins/cloud/providers/aws_international.py:40 -msgid "Middle East (Bahrain)" -msgstr "中东(巴林)" - -#: xpack/plugins/cloud/providers/aws_international.py:41 -msgid "South America (São Paulo)" -msgstr "南美洲(圣保罗)" - -#: xpack/plugins/cloud/providers/baiducloud.py:54 -#: xpack/plugins/cloud/providers/jdcloud.py:127 -msgid "CN North-Beijing" -msgstr "华北-北京" - -#: xpack/plugins/cloud/providers/baiducloud.py:55 -#: xpack/plugins/cloud/providers/huaweicloud.py:40 -#: xpack/plugins/cloud/providers/jdcloud.py:130 -msgid "CN South-Guangzhou" -msgstr "华南-广州" - -#: xpack/plugins/cloud/providers/baiducloud.py:56 -msgid "CN East-Suzhou" -msgstr "华东-苏州" - -#: xpack/plugins/cloud/providers/baiducloud.py:57 -#: xpack/plugins/cloud/providers/huaweicloud.py:48 -msgid "CN-Hong Kong" -msgstr "中国-香港" - -#: xpack/plugins/cloud/providers/baiducloud.py:58 -msgid "CN Center-Wuhan" -msgstr "华中-武汉" - -#: xpack/plugins/cloud/providers/baiducloud.py:59 -msgid "CN North-Baoding" -msgstr "华北-保定" - -#: xpack/plugins/cloud/providers/baiducloud.py:60 -#: xpack/plugins/cloud/providers/jdcloud.py:129 -msgid "CN East-Shanghai" -msgstr "华东-上海" - -#: xpack/plugins/cloud/providers/baiducloud.py:61 -#: xpack/plugins/cloud/providers/huaweicloud.py:47 -msgid "AP-Singapore" -msgstr "亚太-新加坡" - -#: xpack/plugins/cloud/providers/huaweicloud.py:35 -msgid "AF-Johannesburg" -msgstr "非洲-约翰内斯堡" - -#: xpack/plugins/cloud/providers/huaweicloud.py:36 -msgid "CN North-Beijing4" -msgstr "华北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:37 -msgid "CN North-Beijing1" -msgstr "华北-北京1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:38 -msgid "CN East-Shanghai2" -msgstr "华东-上海2" - -#: xpack/plugins/cloud/providers/huaweicloud.py:39 -msgid "CN East-Shanghai1" -msgstr "华东-上海1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:41 -msgid "LA-Mexico City1" -msgstr "拉美-墨西哥城一" - -#: xpack/plugins/cloud/providers/huaweicloud.py:42 -msgid "LA-Santiago" -msgstr "拉美-圣地亚哥" - -#: xpack/plugins/cloud/providers/huaweicloud.py:43 -msgid "LA-Sao Paulo1" -msgstr "拉美-圣保罗一" - -#: xpack/plugins/cloud/providers/huaweicloud.py:44 -msgid "EU-Paris" -msgstr "欧洲-巴黎" - -#: xpack/plugins/cloud/providers/huaweicloud.py:45 -msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:46 -msgid "AP-Bangkok" -msgstr "亚太-曼谷" - -#: xpack/plugins/cloud/providers/huaweicloud.py:50 -msgid "CN Northeast-Dalian" -msgstr "华北-大连" - -#: xpack/plugins/cloud/providers/huaweicloud.py:51 -msgid "CN North-Ulanqab1" -msgstr "华北-乌兰察布一" - -#: xpack/plugins/cloud/providers/huaweicloud.py:52 -msgid "CN South-Guangzhou-InvitationOnly" -msgstr "华南-广州-友好用户环境" - -#: xpack/plugins/cloud/providers/jdcloud.py:128 -msgid "CN East-Suqian" -msgstr "华东-宿迁" - -#: xpack/plugins/cloud/serializers/account.py:62 -msgid "Validity display" -msgstr "有效性显示" - -#: xpack/plugins/cloud/serializers/account.py:63 -msgid "Provider display" -msgstr "服务商显示" - -#: xpack/plugins/cloud/serializers/account_attrs.py:35 -msgid "Client ID" -msgstr "客户端 ID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:41 -msgid "Tenant ID" -msgstr "租户 ID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:44 -msgid "Subscription ID" -msgstr "订阅 ID" - -#: xpack/plugins/cloud/serializers/account_attrs.py:95 -#: xpack/plugins/cloud/serializers/account_attrs.py:100 -#: xpack/plugins/cloud/serializers/account_attrs.py:134 -msgid "API Endpoint" -msgstr "API 端点" - -#: xpack/plugins/cloud/serializers/account_attrs.py:106 -msgid "Auth url" -msgstr "认证地址" - -#: xpack/plugins/cloud/serializers/account_attrs.py:107 -msgid "eg: http://openstack.example.com:5000/v3" -msgstr "如: http://openstack.example.com:5000/v3" - -#: xpack/plugins/cloud/serializers/account_attrs.py:110 -msgid "User domain" -msgstr "用户域" - -#: xpack/plugins/cloud/serializers/account_attrs.py:127 -msgid "Service account key" -msgstr "服务账号密钥" - -#: xpack/plugins/cloud/serializers/account_attrs.py:128 -msgid "The file is in JSON format" -msgstr "JSON 格式的文件" - -#: xpack/plugins/cloud/serializers/account_attrs.py:141 -msgid "IP address invalid `{}`, {}" -msgstr "IP 地址无效: `{}`, {}" - -#: xpack/plugins/cloud/serializers/account_attrs.py:147 -msgid "" -"Format for comma-delimited string,Such as: 192.168.1.0/24, " -"10.0.0.0-10.0.0.255" -msgstr "格式为逗号分隔的字符串,如:192.168.1.0/24,10.0.0.0-10.0.0.255" - -#: xpack/plugins/cloud/serializers/account_attrs.py:151 -msgid "" -"The port is used to detect the validity of the IP address. When the " -"synchronization task is executed, only the valid IP address will be " -"synchronized.
If the port is 0, all IP addresses are valid." -msgstr "" -"端口用来检测 IP 地址的有效性,在同步任务执行时,只会同步有效的 IP 地址。
" -"如果端口为 0,则表示所有 IP 地址均有效。" - -#: xpack/plugins/cloud/serializers/account_attrs.py:159 -msgid "Hostname prefix" -msgstr "主机名前缀" - -#: xpack/plugins/cloud/serializers/account_attrs.py:162 -msgid "IP segment" -msgstr "IP 网段" - -#: xpack/plugins/cloud/serializers/account_attrs.py:166 -msgid "Test port" -msgstr "测试端口" - -#: xpack/plugins/cloud/serializers/account_attrs.py:169 -msgid "Test timeout" -msgstr "测试超时时间" - -#: xpack/plugins/cloud/serializers/task.py:28 -msgid "" -"Only instances matching the IP range will be synced.
If the instance " -"contains multiple IP addresses, the first IP address that matches will be " -"used as the IP for the created asset.
The default value of * means sync " -"all instances and randomly match IP addresses.
Format for comma-" -"delimited string, Such as: 192.168.1.0/24, 10.1.1.1-10.1.1.20" -msgstr "" -"只有匹配到 IP 段的实例会被同步。
如果实例包含多个 IP 地址,那么第一个匹配" -"到的 IP 地址将被用作创建的资产的 IP。
默认值 * 表示同步所有实例和随机匹配 " -"IP 地址。
格式为以逗号分隔的字符串,例如:192.168.1.0/24,10.1.1.1-10.1.1.20" - -#: xpack/plugins/cloud/serializers/task.py:35 -msgid "History count" -msgstr "执行次数" - -#: xpack/plugins/cloud/serializers/task.py:36 -msgid "Instance count" -msgstr "实例个数" - -#: xpack/plugins/cloud/serializers/task.py:64 -msgid "Linux admin user" -msgstr "Linux 管理员" - -#: xpack/plugins/cloud/serializers/task.py:69 -#: xpack/plugins/gathered_user/serializers.py:20 -msgid "Periodic display" -msgstr "定时执行" - -#: xpack/plugins/cloud/utils.py:69 -msgid "Account unavailable" -msgstr "账号无效" - -#: xpack/plugins/gathered_user/meta.py:11 -msgid "Gathered user" -msgstr "收集用户" - -#: xpack/plugins/gathered_user/models.py:34 -msgid "Gather user task" -msgstr "收集用户任务" - -#: xpack/plugins/gathered_user/models.py:80 -msgid "gather user task execution" -msgstr "收集用户执行" - -#: xpack/plugins/gathered_user/models.py:86 -msgid "Assets is empty, please change nodes" -msgstr "资产为空,请更改节点" - -#: xpack/plugins/gathered_user/serializers.py:21 -msgid "Executed times" -msgstr "执行次数" - -#: xpack/plugins/interface/api.py:52 -msgid "Restore default successfully." -msgstr "恢复默认成功!" - -#: xpack/plugins/interface/meta.py:10 -msgid "Interface settings" -msgstr "界面设置" - -#: xpack/plugins/interface/models.py:22 -msgid "Title of login page" -msgstr "登录页面标题" - -#: xpack/plugins/interface/models.py:26 -msgid "Image of login page" -msgstr "登录页面图片" - -#: xpack/plugins/interface/models.py:30 -msgid "Website icon" -msgstr "网站图标" - -#: xpack/plugins/interface/models.py:34 -msgid "Logo of management page" -msgstr "管理页面logo" - -#: xpack/plugins/interface/models.py:38 -msgid "Logo of logout page" -msgstr "退出页面logo" - -#: xpack/plugins/interface/models.py:40 -msgid "Theme" -msgstr "主题" - -#: xpack/plugins/interface/models.py:43 -msgid "Interface setting" -msgstr "界面设置" - -#: xpack/plugins/license/api.py:50 -msgid "License import successfully" -msgstr "许可证导入成功" - -#: xpack/plugins/license/api.py:51 -msgid "License is invalid" -msgstr "无效的许可证" - -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:127 -msgid "License" -msgstr "许可证" - -#: xpack/plugins/license/models.py:71 -msgid "Standard edition" -msgstr "标准版" - -#: xpack/plugins/license/models.py:73 -msgid "Enterprise edition" -msgstr "企业版" - -#: xpack/plugins/license/models.py:75 -msgid "Ultimate edition" -msgstr "旗舰版" - -#: xpack/plugins/license/models.py:77 -msgid "Community edition" -msgstr "社区版" - -#~ msgid "Gateway" -#~ msgstr "网关" - -#~ msgid "Test gateway" -#~ msgstr "测试网关" - -#~ msgid "" -#~ "Format for comma-delimited string, with * indicating a match all. " -#~ "Protocol options: {}" -#~ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" - -#~ msgid "User has no permission to access asset or permission expired" -#~ msgstr "用户没有权限访问资产或权限已过期" - -#~ msgid "AdHoc execution" -#~ msgstr "任务执行" - -#, fuzzy -#~| msgid "Disable" -#~ msgid "Variables" -#~ msgstr "禁用" - -#~ msgid "Owner" -#~ msgstr "Owner" - -#~ msgid "Applied login IP" -#~ msgstr "申请登录的IP" - -#~ msgid "Applied login city" -#~ msgstr "申请登录的城市" - -#~ msgid "Applied login datetime" -#~ msgstr "申请登录的日期" - -#~ msgid "Type display" -#~ msgstr "类型名称" - -#~ msgid "Status display" -#~ msgstr "状态名称" - -#~ msgid "Run ansible command" -#~ msgstr "运行 ansible 命令" - -#~ msgid "Clean task history period" -#~ msgstr "定期清除任务历史" - -#, fuzzy -#~| msgid "WeCom Error" -#~ msgid "Hello Error" -#~ msgstr "企业微信错误" - -#~ msgid "Operate display" -#~ msgstr "操作名称" - -#~ msgid "MFA display" -#~ msgstr "MFA名称" - -#~ msgid "Path" -#~ msgstr "路径" - -#~ msgid "Playbook template" -#~ msgstr "Playbook 模版" - -#~ msgid "Run dir" -#~ msgstr "运行目录" - -#~ msgid "Upload file" -#~ msgstr "上传文件" - -#~ msgid "Download file" -#~ msgstr "下载文件" - -#~ msgid "Upload download" -#~ msgstr "上传下载" - -#~ msgid "Clipboard paste" -#~ msgstr "剪贴板粘贴" - -#~ msgid "Clipboard copy paste" -#~ msgstr "剪贴板复制粘贴" - -#~ msgid "Users display" -#~ msgstr "用户名称" - -#~ msgid "User groups display" -#~ msgstr "用户组名称" - -#~ msgid "Assets display" -#~ msgstr "资产名称" - -#~ msgid "Nodes display" -#~ msgstr "节点名称" - -#~ msgid "User groups amount" -#~ msgstr "用户组数量" - -#~ msgid "Nodes amount" -#~ msgstr "节点数量" - -#~ msgid "The asset {} system platform {} does not support run Ansible tasks" -#~ msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" - -#~ msgid "Test assets connectivity: " -#~ msgstr "测试资产可连接性: " - -#~ msgid "Unreachable" -#~ msgstr "不可达" - -#~ msgid "Reachable" -#~ msgstr "可连接" - -#~ msgid "Get asset info failed: {}" -#~ msgstr "获取资产信息失败:{}" - -#~ msgid "Update asset hardware info: " -#~ msgstr "更新资产硬件信息: " - -#~ msgid "Gather assets users" -#~ msgstr "收集资产上的用户" - -#~ msgid "Push automation" -#~ msgstr "自动化推送" - -#~ msgid "Verify account automation" -#~ msgstr "账号校验自动化" - -#~ msgid "Account display" -#~ msgstr "账号显示" - -#~ msgid "User not exists" -#~ msgstr "用户不存在" - -#~ msgid "System user not exists" -#~ msgstr "系统用户不存在" - -#~ msgid "Asset not exists" -#~ msgstr "资产不存在" - -#~ msgid "Application not exists" -#~ msgstr "应用不存在" - -#~ msgid "User has no permission to access application or permission expired" -#~ msgstr "用户没有权限访问应用或权限已过期" - -#~ msgid "Asset or application required" -#~ msgstr "资产或应用必填" - -#~ msgid "Manual" -#~ msgstr "手动" - -#~ msgid "Remote gzip" -#~ msgstr "远程应用" - -#~ msgid "Verify secret" -#~ msgstr "校验密码" - -#, fuzzy -#~| msgid "Secret key" -#~ msgid "Secret types" -#~ msgstr "Secret key" - -#~ msgid "Verify secret automation" -#~ msgstr "自动化验证" - -#, fuzzy -#~| msgid "Hostname strategy" -#~ msgid "Automation strategy" -#~ msgstr "主机名策略" - -#~ msgid "Discovery strategy" -#~ msgstr "自动发现策略" - -#~ msgid "Reconcile strategy" -#~ msgstr "主机名策略" - -#, fuzzy -#~| msgid "Can change auth setting" -#~ msgid "Change auth strategy" -#~ msgstr "认证设置" - -#~ msgid "System User" -#~ msgstr "系统用户" - -#~ msgid "Unsupported protocols: {}" -#~ msgstr "不支持的协议: {}" - -#~ msgid "Custom" -#~ msgstr "自定义" - -#~ msgid "Can view application account secret" -#~ msgstr "可以查看应用账号密码" - -#~ msgid "Can change application account secret" -#~ msgstr "可以查看应用账号密码" - -#~ msgid "Application user" -#~ msgstr "应用用户" - -#~ msgid "Application display" -#~ msgstr "应用名称" - -#~ msgid "Cluster" -#~ msgstr "集群" - -#~ msgid "Asset Info" -#~ msgstr "资产信息" - -#~ msgid "Application path" -#~ msgstr "应用路径" - -#~ msgid "Target URL" -#~ msgstr "目标URL" - -#~ msgid "Chrome username" -#~ msgstr "Chrome 用户名" - -#~ msgid "Chrome password" -#~ msgstr "Chrome 密码" - -#~ msgid "Operating parameter" -#~ msgstr "运行参数" - -#~ msgid "Target url" -#~ msgstr "目标URL" - -#~ msgid "Mysql workbench username" -#~ msgstr "Mysql 工作台 用户名" - -#~ msgid "Mysql workbench password" -#~ msgstr "Mysql 工作台 密码" - -#~ msgid "Magnus currently supports only 11g and 12c connections" -#~ msgstr "目前 Magnus 只支持连接 11g、12c 版本" - -#~ msgid "Vmware username" -#~ msgstr "Vmware 用户名" - -#~ msgid "Vmware password" -#~ msgstr "Vmware 密码" - -#~ msgid "Base" -#~ msgstr "基础" - -#~ msgid "Public IP" -#~ msgstr "公网IP" - -#~ msgid "AuthBook" -#~ msgstr "资产账号" - -#~ msgid "Can test asset account connectivity" -#~ msgstr "可以测试资产账号连接性" - -#~ msgid "Bandwidth" -#~ msgstr "带宽" - -#~ msgid "Contact" -#~ msgstr "联系人" - -#~ msgid "Intranet" -#~ msgstr "内网" - -#~ msgid "Extranet" -#~ msgstr "外网" - -#~ msgid "Operator" -#~ msgstr "运营商" - -#~ msgid "Default Cluster" -#~ msgstr "默认Cluster" - -#~ msgid "User groups" -#~ msgstr "用户组" - -#~ msgid "System user display" -#~ msgstr "系统用户名称" - -#~ msgid "Protocol format should {}/{}" -#~ msgstr "协议格式 {}/{}" - -#~ msgid "Nodes name" -#~ msgstr "节点名称" - -#~ msgid "Labels name" -#~ msgstr "标签名称" - -#~ msgid "Hardware info" -#~ msgstr "硬件信息" - -#~ msgid "Admin user display" -#~ msgstr "特权用户名称" - -#~ msgid "CPU info" -#~ msgstr "CPU信息" - -#~ msgid "Applications amount" -#~ msgstr "应用数量" - -#~ msgid "SSH key fingerprint" -#~ msgstr "密钥指纹" - -#~ msgid "Apps amount" -#~ msgstr "应用数量" - -#~ msgid "Login mode display" -#~ msgstr "认证方式名称" - -#~ msgid "Ad domain" -#~ msgstr "Ad 网域" - -#~ msgid "Is asset protocol" -#~ msgstr "资产协议" - -#~ msgid "Only ssh and automatic login system users are supported" -#~ msgstr "仅支持ssh协议和自动登录的系统用户" - -#~ msgid "Username same with user with protocol {} only allow 1" -#~ msgstr "用户名和用户相同的一种协议只允许存在一个" - -#~ msgid "* Automatic login mode must fill in the username." -#~ msgstr "自动登录模式,必须填写用户名" - -#~ msgid "Path should starts with /" -#~ msgstr "路径应该以 / 开头" - -#~ msgid "Password or private key required" -#~ msgstr "密码或密钥密码需要一个" - -#~ msgid "Only ssh protocol system users are allowed" -#~ msgstr "仅允许ssh协议的系统用户" - -#~ msgid "The protocol must be consistent with the current user: {}" -#~ msgstr "协议必须和当前用户保持一致: {}" - -#~ msgid "Only system users with automatic login are allowed" -#~ msgstr "仅允许自动登录的系统用户" - -#~ msgid "System user name" -#~ msgstr "系统用户名称" - -#~ msgid "Asset hostname" -#~ msgstr "资产主机名" - -#~ msgid "System user is dynamic: {}" -#~ msgstr "系统用户是动态的: {}" - -#~ msgid "Start push system user for platform: [{}]" -#~ msgstr "推送系统用户到平台: [{}]" - -#~ msgid "Hosts count: {}" -#~ msgstr "主机数量: {}" - -#~ msgid "Push system users to assets: " -#~ msgstr "推送系统用户到入资产: " - -#~ msgid "Push system users to asset: " -#~ msgstr "推送系统用户到入资产: " - -#~ msgid "Dynamic system user not support test" -#~ msgstr "动态系统用户不支持测试" - -#~ msgid "Start test system user connectivity for platform: [{}]" -#~ msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" - -#~ msgid "Test system user connectivity: " -#~ msgstr "测试系统用户可连接性: " - -#~ msgid "Test system user connectivity period: " -#~ msgstr "定期测试系统用户可连接性: " - -#~ msgid "Hosts display" -#~ msgstr "主机名称" - -#~ msgid "Run as" -#~ msgstr "运行用户" - -#~ msgid "Run as display" -#~ msgstr "运行用户名称" - -#~ msgid "Asset and SystemUser" -#~ msgstr "资产与系统用户" - -#~ msgid "{Asset} ADD {SystemUser}" -#~ msgstr "{Asset} 添加 {SystemUser}" - -#~ msgid "{Asset} REMOVE {SystemUser}" -#~ msgstr "{Asset} 移除 {SystemUser}" - -#~ msgid "Asset permission and SystemUser" -#~ msgstr "资产授权与系统用户" - -#~ msgid "{AssetPermission} ADD {SystemUser}" -#~ msgstr "{AssetPermission} 添加 {SystemUser}" - -#~ msgid "{AssetPermission} REMOVE {SystemUser}" -#~ msgstr "{AssetPermission} 移除 {SystemUser}" - -#~ msgid "User application permissions" -#~ msgstr "用户应用授权" - -#~ msgid "{ApplicationPermission} ADD {User}" -#~ msgstr "{ApplicationPermission} 添加 {User}" - -#~ msgid "{ApplicationPermission} REMOVE {User}" -#~ msgstr "{ApplicationPermission} 移除 {User}" - -#~ msgid "User group application permissions" -#~ msgstr "用户组应用授权" - -#~ msgid "{ApplicationPermission} ADD {UserGroup}" -#~ msgstr "{ApplicationPermission} 添加 {UserGroup}" - -#~ msgid "{ApplicationPermission} REMOVE {UserGroup}" -#~ msgstr "{ApplicationPermission} 移除 {UserGroup}" - -#~ msgid "Application permission" -#~ msgstr "应用授权" - -#~ msgid "{ApplicationPermission} ADD {Application}" -#~ msgstr "{ApplicationPermission} 添加 {Application}" - -#~ msgid "{ApplicationPermission} REMOVE {Application}" -#~ msgstr "{ApplicationPermission} 移除 {Application}" - -#~ msgid "Application permission and SystemUser" -#~ msgstr "应用授权与系统用户" - -#~ msgid "{ApplicationPermission} ADD {SystemUser}" -#~ msgstr "{ApplicationPermission} 添加 {SystemUser}" - -#~ msgid "{ApplicationPermission} REMOVE {SystemUser}" -#~ msgstr "{ApplicationPermission} 移除 {SystemUser}" - -#~ msgid "Not has host {} permission" -#~ msgstr "没有该主机 {} 权限" - -#~ msgid "" -#~ "eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " -#~ "crontab expressions (Online tools)
Note: If both Regularly " -#~ "perform and Cycle perform are set, give priority to Regularly perform" -#~ msgstr "" -#~ "eg:每周日 03:05 执行 <5 3 * * 0>
提示: 使用5位 Linux crontab 表达" -#~ "式 <分 时 日 月 星期> (在线工具
注意: 如果同时设置了定期执行和周期执" -#~ "行,优先使用定期执行" - -#~ msgid "Unit: hour" -#~ msgstr "单位: 时" - -#~ msgid "Callback" -#~ msgstr "回调" - -#~ msgid "Can view task monitor" -#~ msgstr "可以查看任务监控" - -#~ msgid "Tasks" -#~ msgstr "任务" - -#~ msgid "Options" -#~ msgstr "选项" - -#~ msgid "Run as admin" -#~ msgstr "再次执行" - -#~ msgid "Become" -#~ msgstr "Become" - -#~ msgid "Create by" -#~ msgstr "创建者" - -#~ msgid "AdHoc" -#~ msgstr "任务各版本" - -#~ msgid "Task display" -#~ msgstr "任务名称" - -#~ msgid "Host amount" -#~ msgstr "主机数量" - -#~ msgid "Start time" -#~ msgstr "开始时间" - -#~ msgid "End time" -#~ msgstr "完成时间" - -#~ msgid "Adhoc raw result" -#~ msgstr "结果" - -#~ msgid "Adhoc result summary" -#~ msgstr "汇总" - -#~ msgid "Task start" -#~ msgstr "任务开始" - -#~ msgid "Command `{}` is forbidden ........" -#~ msgstr "命令 `{}` 不允许被执行 ......." - -#~ msgid "Task end" -#~ msgstr "任务结束" - -#~ msgid "The administrator is modifying permissions. Please wait" -#~ msgstr "管理员正在修改授权,请稍等" - -#~ msgid "The authorization cannot be revoked for the time being" -#~ msgstr "该授权暂时不能撤销" - -#~ msgid "Permed application" -#~ msgstr "授权的应用" - -#~ msgid "Can view my apps" -#~ msgstr "可以查看我的应用" - -#~ msgid "Can view user apps" -#~ msgstr "可以查看用户授权的应用" - -#~ msgid "Can view usergroup apps" -#~ msgstr "可以查看用户组授权的应用" - -#~ msgid "Your permed applications is about to expire" -#~ msgstr "你授权的应用即将过期" - -#~ msgid "permed applications" -#~ msgstr "授权的应用" - -#~ msgid "Application permissions is about to expire" -#~ msgstr "应用授权规则即将过期" - -#~ msgid "application permissions of organization {}" -#~ msgstr "组织 ({}) 的应用授权" - -#~ msgid "System users amount" -#~ msgstr "系统用户数量" - -#~ msgid "" -#~ "The application list contains applications that are different from the " -#~ "permission type. ({})" -#~ msgstr "应用列表中包含与授权类型不同的应用。({})" - -#~ msgid "System users display" -#~ msgstr "系统用户名称" - -#~ msgid "My applications" -#~ msgstr "我的应用" - -#~ msgid "Empty" -#~ msgstr "空" - -#~ msgid "System user ID" -#~ msgstr "系统用户 ID" - -#~ msgid "Apply for application" -#~ msgstr "申请应用" - -#~ msgid "" -#~ "Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " -#~ "processor: {}, ticket ID: {}" -#~ msgstr "" -#~ "通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" - -#~ msgid "Login system user" -#~ msgstr "登录系统用户" From 519e0eac01be900ba50176b58b72af48ded184ae Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 4 Dec 2022 20:51:22 +0800 Subject: [PATCH 478/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20CommandGro?= =?UTF-8?q?upSerializer=20type=20=E5=AD=97=E6=AE=B5=E4=B8=BA=20LabeledChoi?= =?UTF-8?q?ceField?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/serializers/command_acl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/acls/serializers/command_acl.py b/apps/acls/serializers/command_acl.py index 4e0d3edaa..0accb5f56 100644 --- a/apps/acls/serializers/command_acl.py +++ b/apps/acls/serializers/command_acl.py @@ -1,8 +1,7 @@ from django.utils.translation import ugettext_lazy as _ -from rest_framework import serializers from acls.models import CommandGroup, CommandFilterACL -from common.drf.fields import ObjectRelatedField +from common.drf.fields import ObjectRelatedField, LabeledChoiceField from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer @@ -10,7 +9,7 @@ __all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer"] class CommandGroupSerializer(BulkOrgResourceModelSerializer): - type = serializers.ChoiceField( + type = LabeledChoiceField( choices=CommandGroup.TypeChoices.choices, default=CommandGroup.TypeChoices.command, label=_('Type') ) From 0cfcfacb6d37f0aefdf7595a0e68c66dcb09eedc Mon Sep 17 00:00:00 2001 From: Bai Date: Sun, 4 Dec 2022 22:46:47 +0800 Subject: [PATCH 479/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20CommandFil?= =?UTF-8?q?terACL,=20CommandGroup=20=5F=5Fstr=5F=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/models/command_acl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 8c920c5e7..51e8c7388 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -91,7 +91,7 @@ class CommandGroup(JMSOrgBaseModel): return True, '', pattern def __str__(self): - return '{} % {}'.format(self.type, self.content) + return '{} % {}'.format(self.name, self.type) class CommandFilterACLQuerySet(UserAssetAccountACLQuerySet): @@ -113,6 +113,9 @@ class CommandFilterACL(UserAssetAccountBaseACL): ordering = ('priority', '-date_updated', 'name') verbose_name = _('Command acl') + def __str__(self): + return self.name + def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): from tickets.const import TicketType from tickets.models import ApplyCommandTicket From 669ccb502f7e74be26f13b909a4a717274018e5a Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Dec 2022 10:48:19 +0800 Subject: [PATCH 480/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20connect=20?= =?UTF-8?q?token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/serializers/__init__.py | 5 +- .../serializers/connect_token_secret.py | 105 ++++++++++++++ .../serializers/connection_token.py | 130 +----------------- apps/terminal/serializers/terminal.py | 2 +- 4 files changed, 114 insertions(+), 128 deletions(-) create mode 100644 apps/authentication/serializers/connect_token_secret.py diff --git a/apps/authentication/serializers/__init__.py b/apps/authentication/serializers/__init__.py index 65994a58c..d6e1671cf 100644 --- a/apps/authentication/serializers/__init__.py +++ b/apps/authentication/serializers/__init__.py @@ -1,4 +1,5 @@ -from .token import * +from .confirm import * +from .connect_token_secret import * from .connection_token import * from .password_mfa import * -from .confirm import * +from .token import * diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py new file mode 100644 index 000000000..dd8804ac7 --- /dev/null +++ b/apps/authentication/serializers/connect_token_secret.py @@ -0,0 +1,105 @@ +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from acls.models import CommandGroup +from assets.models import Asset, Account, Platform +from assets.serializers import PlatformSerializer, AssetProtocolsSerializer +from authentication.models import ConnectionToken +from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from perms.serializers.permission import ActionChoicesField +from users.models import User + +__all__ = [ + 'ConnectionTokenSecretSerializer', +] + + +class _ConnectionTokenUserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'name', 'username', 'email'] + + +class _ConnectionTokenAssetSerializer(serializers.ModelSerializer): + protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) + + class Meta: + model = Asset + fields = [ + 'id', 'name', 'address', 'protocols', + 'category', 'type', 'org_id', 'specific' + ] + + +class _SimpleAccountSerializer(serializers.ModelSerializer): + """ Account """ + + class Meta: + model = Account + fields = ['name', 'username', 'secret_type', 'secret'] + + +class _ConnectionTokenAccountSerializer(serializers.ModelSerializer): + """ Account """ + su_from = _SimpleAccountSerializer(required=False, label=_('Su from')) + + class Meta: + model = Account + fields = [ + 'name', 'username', 'secret_type', 'secret', 'su_from', + ] + + +class _ConnectionTokenGatewaySerializer(serializers.ModelSerializer): + """ Gateway """ + + class Meta: + model = Asset + fields = [ + 'id', 'address', 'port', + # 'username', 'password', 'private_key' + ] + + +class _ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): + """ ACL command group""" + + class Meta: + model = CommandGroup + fields = [ + 'id', 'type', 'content', 'ignore_case', 'pattern' + ] + + +class _ConnectionTokenPlatformSerializer(PlatformSerializer): + class Meta(PlatformSerializer.Meta): + model = Platform + + def get_field_names(self, declared_fields, info): + names = super().get_field_names(declared_fields, info) + names = [n for n in names if n not in ['automation']] + return names + + +class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): + user = _ConnectionTokenUserSerializer(read_only=True) + asset = _ConnectionTokenAssetSerializer(read_only=True) + account = _ConnectionTokenAccountSerializer(read_only=True) + gateway = _ConnectionTokenGatewaySerializer(read_only=True) + platform = _ConnectionTokenPlatformSerializer(read_only=True) + acl_command_groups = _ConnectionTokenACLCmdGroupSerializer(read_only=True, many=True) + actions = ActionChoicesField() + expire_at = serializers.IntegerField() + expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) + connect_method = serializers.CharField(label=_('Connect method'), write_only=True, default='ssh') + + class Meta: + model = ConnectionToken + fields = [ + 'id', 'value', 'user', 'asset', 'account', + 'platform', 'acl_command_groups', 'protocol', + 'gateway', 'actions', 'expire_at', 'expire_now', + ] + extra_kwargs = { + 'value': {'read_only': True}, + } diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 3fefbb5af..dddcd0866 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,17 +1,11 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from assets.models import Asset, CommandFilterRule, Account, Platform -from acls.models import CommandGroup -from assets.serializers import PlatformSerializer, AssetProtocolsSerializer from authentication.models import ConnectionToken from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from perms.serializers.permission import ActionChoicesField -from users.models import User __all__ = [ - 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer', - 'SuperConnectionTokenSerializer', 'ConnectionTokenDisplaySerializer' + 'ConnectionTokenSerializer', 'SuperConnectionTokenSerializer', ] @@ -22,11 +16,9 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields_mini = ['id', 'value'] fields_small = fields_mini + [ - 'user', 'asset', 'account_name', - 'input_username', 'input_secret', - 'connect_method', 'protocol', - 'actions', 'date_expired', 'date_created', - 'date_updated', 'created_by', + 'user', 'asset', 'account_name', 'input_username', + 'input_secret', 'connect_method', 'protocol', 'actions', + 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', ] read_only_fields = [ @@ -48,121 +40,9 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): return self.get_request_user() -class ConnectionTokenDisplaySerializer(ConnectionTokenSerializer): - class Meta(ConnectionTokenSerializer.Meta): - extra_kwargs = { - 'secret': {'write_only': True}, - } - - -# -# SuperConnectionTokenSerializer -# - - class SuperConnectionTokenSerializer(ConnectionTokenSerializer): class Meta(ConnectionTokenSerializer.Meta): - read_only_fields = [ - 'validity', 'user_display', 'system_user_display', - 'asset_display', 'application_display', - ] + pass def get_user(self, attrs): return attrs.get('user') or self.get_request_user() - - -# -# Connection Token Secret -# - - -class ConnectionTokenUserSerializer(serializers.ModelSerializer): - """ User """ - - class Meta: - model = User - fields = ['id', 'name', 'username', 'email'] - - -class ConnectionTokenAssetSerializer(serializers.ModelSerializer): - """ Asset """ - protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) - - class Meta: - model = Asset - fields = [ - 'id', 'name', 'address', 'protocols', 'category', 'type', 'org_id', 'specific' - ] - - -class SimpleAccountSerializer(serializers.ModelSerializer): - """ Account """ - - class Meta: - model = Account - fields = ['name', 'username', 'secret_type', 'secret'] - - -class ConnectionTokenAccountSerializer(serializers.ModelSerializer): - """ Account """ - su_from = SimpleAccountSerializer(required=False, label=_('Su from')) - - class Meta: - model = Account - fields = [ - 'name', 'username', 'secret_type', 'secret', 'su_from', - ] - - -class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): - """ Gateway """ - - class Meta: - model = Asset - fields = [ - 'id', 'address', 'port', - # 'username', 'password', 'private_key' - ] - - -class ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): - """ ACL command group""" - - class Meta: - model = CommandGroup - fields = [ - 'id', 'type', 'content', 'ignore_case', 'pattern' - ] - - -class ConnectionTokenPlatform(PlatformSerializer): - class Meta(PlatformSerializer.Meta): - model = Platform - - def get_field_names(self, declared_fields, info): - names = super().get_field_names(declared_fields, info) - names = [n for n in names if n not in ['automation']] - return names - - -class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): - user = ConnectionTokenUserSerializer(read_only=True) - asset = ConnectionTokenAssetSerializer(read_only=True) - account = ConnectionTokenAccountSerializer(read_only=True) - gateway = ConnectionTokenGatewaySerializer(read_only=True) - platform = ConnectionTokenPlatform(read_only=True) - acl_command_groups = ConnectionTokenACLCmdGroupSerializer(read_only=True, many=True) - actions = ActionChoicesField() - expire_at = serializers.IntegerField() - expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) - - class Meta: - model = ConnectionToken - fields = [ - 'id', 'value', 'user', 'asset', 'account', 'platform', - 'acl_command_groups', - 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', - ] - extra_kwargs = { - 'value': {'read_only': True}, - } diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index f7f935f50..340a0b0f7 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -139,5 +139,5 @@ class ConnectMethodSerializer(serializers.Serializer): value = serializers.CharField(max_length=128) label = serializers.CharField(max_length=128) type = serializers.CharField(max_length=128) - listen = serializers.CharField(max_length=128) + endpoint_protocol = serializers.CharField(max_length=128) component = serializers.CharField(max_length=128) From d25d580ba46f72a1bce025934b1baaea62311320 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Dec 2022 11:06:50 +0800 Subject: [PATCH 481/488] =?UTF-8?q?perf:=20=E5=90=88=E5=B9=B6=20connect=20?= =?UTF-8?q?token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0013_connectiontoken_protocol.py | 3 +- .../migrations/0014_auto_20221122_2152.py | 35 +++++++++---- .../0015_alter_connectiontoken_login.py | 18 ------- .../migrations/0016_auto_20221125_2240.py | 50 ------------------- .../migrations/0017_auto_20221128_1839.py | 18 ------- .../0018_connectiontoken_endpoint_protocol.py | 19 ------- ...emove_connectiontoken_endpoint_protocol.py | 17 ------- .../authentication/models/connection_token.py | 12 ++--- 8 files changed, 31 insertions(+), 141 deletions(-) delete mode 100644 apps/authentication/migrations/0015_alter_connectiontoken_login.py delete mode 100644 apps/authentication/migrations/0016_auto_20221125_2240.py delete mode 100644 apps/authentication/migrations/0017_auto_20221128_1839.py delete mode 100644 apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py delete mode 100644 apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py diff --git a/apps/authentication/migrations/0013_connectiontoken_protocol.py b/apps/authentication/migrations/0013_connectiontoken_protocol.py index f6e310e24..3ba4785b0 100644 --- a/apps/authentication/migrations/0013_connectiontoken_protocol.py +++ b/apps/authentication/migrations/0013_connectiontoken_protocol.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('authentication', '0012_auto_20220816_1629'), ] @@ -13,6 +12,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='connectiontoken', name='protocol', - field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S'), ('http', 'HTTP'), ('None', ' Settings')], default='ssh', max_length=16, verbose_name='Protocol'), + field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'), ), ] diff --git a/apps/authentication/migrations/0014_auto_20221122_2152.py b/apps/authentication/migrations/0014_auto_20221122_2152.py index b198295a5..483b6d5f0 100644 --- a/apps/authentication/migrations/0014_auto_20221122_2152.py +++ b/apps/authentication/migrations/0014_auto_20221122_2152.py @@ -1,11 +1,11 @@ # Generated by Django 3.2.14 on 2022-11-22 13:52 -import common.db.fields from django.db import migrations, models +import common.db.fields + class Migration(migrations.Migration): - dependencies = [ ('authentication', '0013_connectiontoken_protocol'), ] @@ -14,21 +14,38 @@ class Migration(migrations.Migration): migrations.RenameField( model_name='connectiontoken', old_name='account_username', - new_name='login' + new_name='account_name' ), migrations.AlterField( model_name='connectiontoken', - name='login', - field=models.CharField(max_length=128, verbose_name='Login account'), + name='account_name', + field=models.CharField(max_length=128, verbose_name='Account name'), ), migrations.AddField( model_name='connectiontoken', - name='username', - field=models.CharField(default='', max_length=128, verbose_name='Username'), + name='input_username', + field=models.CharField(blank=True, default='', max_length=128, verbose_name='Input username'), + ), + migrations.AddField( + model_name='connectiontoken', + name='input_secret', + field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, + verbose_name='Input secret'), + ), + migrations.RenameField( + model_name='connectiontoken', + old_name='secret', + new_name='value', ), migrations.AlterField( model_name='connectiontoken', - name='secret', - field=common.db.fields.EncryptCharField(default='', max_length=128, verbose_name='Secret'), + name='value', + field=models.CharField(default='', max_length=64, verbose_name='Value'), + ), + migrations.AddField( + model_name='connectiontoken', + name='connect_method', + field=models.CharField(default='web_ui', max_length=32, verbose_name='Connect method'), + preserve_default=False, ), ] diff --git a/apps/authentication/migrations/0015_alter_connectiontoken_login.py b/apps/authentication/migrations/0015_alter_connectiontoken_login.py deleted file mode 100644 index f2c6abecb..000000000 --- a/apps/authentication/migrations/0015_alter_connectiontoken_login.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-23 02:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0014_auto_20221122_2152'), - ] - - operations = [ - migrations.AlterField( - model_name='connectiontoken', - name='login', - field=models.CharField(max_length=128, verbose_name='Login account'), - ), - ] diff --git a/apps/authentication/migrations/0016_auto_20221125_2240.py b/apps/authentication/migrations/0016_auto_20221125_2240.py deleted file mode 100644 index 041a29fc6..000000000 --- a/apps/authentication/migrations/0016_auto_20221125_2240.py +++ /dev/null @@ -1,50 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-25 14:40 - -from django.db import migrations, models - -import common.db.fields - - -class Migration(migrations.Migration): - dependencies = [ - ('authentication', '0015_alter_connectiontoken_login'), - ] - - operations = [ - migrations.RenameField( - model_name='connectiontoken', - old_name='login', - new_name='account_name' - ), - migrations.RenameField( - model_name='connectiontoken', - old_name='secret', - new_name='value', - ), - migrations.RenameField( - model_name='connectiontoken', - old_name='username', - new_name='input_username', - ), - migrations.AlterField( - model_name='connectiontoken', - name='account_name', - field=models.CharField(max_length=128, verbose_name='Account name'), - ), - migrations.AlterField( - model_name='connectiontoken', - name='value', - field=models.CharField(default='', max_length=64, verbose_name='Value'), - ), - migrations.AddField( - model_name='connectiontoken', - name='input_secret', - field=common.db.fields.EncryptCharField(blank=True, default='', max_length=128, - verbose_name='Input Secret'), - ), - migrations.AlterField( - model_name='connectiontoken', - name='input_username', - field=models.CharField(blank=True, default='', max_length=128, verbose_name='Input Username'), - ), - ] diff --git a/apps/authentication/migrations/0017_auto_20221128_1839.py b/apps/authentication/migrations/0017_auto_20221128_1839.py deleted file mode 100644 index bcdb71020..000000000 --- a/apps/authentication/migrations/0017_auto_20221128_1839.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-28 10:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('authentication', '0016_auto_20221125_2240'), - ] - - operations = [ - migrations.AddField( - model_name='connectiontoken', - name='connect_method', - field=models.CharField(default='web_ui', max_length=32, verbose_name='Connect method'), - preserve_default=False, - ), - ] diff --git a/apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py b/apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py deleted file mode 100644 index f267a62fd..000000000 --- a/apps/authentication/migrations/0018_connectiontoken_endpoint_protocol.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 04:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0017_auto_20221128_1839'), - ] - - operations = [ - migrations.AddField( - model_name='connectiontoken', - name='endpoint_protocol', - field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('oracle', 'Oracle'), ('postgresql', 'PostgreSQL'), ('sqlserver', 'SQLServer'), ('redis', 'Redis'), ('mongodb', 'MongoDB'), ('k8s', 'K8S'), ('http', 'HTTP'), ('None', ' Settings')], default='', max_length=16, verbose_name='Endpoint protocol'), - preserve_default=False, - ), - ] diff --git a/apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py b/apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py deleted file mode 100644 index ef7f401bd..000000000 --- a/apps/authentication/migrations/0019_remove_connectiontoken_endpoint_protocol.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 13:27 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('authentication', '0018_connectiontoken_endpoint_protocol'), - ] - - operations = [ - migrations.RemoveField( - model_name='connectiontoken', - name='endpoint_protocol', - ), - ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index ef4fad0ac..93b137923 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -29,17 +29,13 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): related_name='connection_tokens', verbose_name=_('Asset'), ) account_name = models.CharField(max_length=128, verbose_name=_("Account name")) # 登录账号Name - input_username = models.CharField(max_length=128, default='', blank=True, verbose_name=_("Input Username")) - input_secret = EncryptCharField(max_length=64, default='', blank=True, verbose_name=_("Input Secret")) - protocol = models.CharField( - choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol") - ) + input_username = models.CharField(max_length=128, default='', blank=True, verbose_name=_("Input username")) + input_secret = EncryptCharField(max_length=64, default='', blank=True, verbose_name=_("Input secret")) + protocol = models.CharField(max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")) connect_method = models.CharField(max_length=32, verbose_name=_("Connect method")) user_display = models.CharField(max_length=128, default='', verbose_name=_("User display")) asset_display = models.CharField(max_length=128, default='', verbose_name=_("Asset display")) - date_expired = models.DateTimeField( - default=date_expired_default, verbose_name=_("Date expired") - ) + date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_("Date expired")) class Meta: ordering = ('-date_expired',) From 8a7ecda4f6e18a53e3677dcf4a2468849a47069a Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 5 Dec 2022 11:21:01 +0800 Subject: [PATCH 482/488] perf: asset add automation_enabled_info (#9154) Co-authored-by: feng <1304903146@qq.com> --- apps/assets/serializers/asset/common.py | 20 +++++++++++++++++--- apps/assets/serializers/platform.py | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 179760922..45ca422df 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -53,7 +53,7 @@ class AssetAccountSerializer(AccountSerializer): 'version', 'secret_type', ] fields_write_only = [ - 'secret', 'push_now' + 'secret', 'push_now' ] fields = fields_mini + fields_write_only @@ -67,6 +67,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) labels = AssetLabelSerializer(many=True, required=False, label=_('Labels')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts')) + automation_enabled_info = serializers.SerializerMethodField() class Meta: model = Asset @@ -78,8 +79,8 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) ] read_only_fields = [ 'category', 'type', 'specific', 'info', - 'connectivity', 'date_verified', - 'created_by', 'date_created', + 'connectivity', 'date_verified', 'created_by', + 'date_created', 'automation_enabled_info' ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { @@ -93,6 +94,19 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) names.remove('specific') return names + @staticmethod + def get_automation_enabled_info(obj): + automation = obj.platform.automation + return { + 'ping_enabled': automation.ping_enabled, + 'ansible_enabled': automation.ansible_enabled, + 'gather_facts_enabled': automation.gather_facts_enabled, + 'push_account_enabled': automation.push_account_enabled, + 'change_secret_enabled': automation.change_secret_enabled, + 'verify_account_enabled': automation.verify_account_enabled, + 'gather_accounts_enabled': automation.gather_accounts_enabled, + } + @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 8f8dcb5a3..ccb536bb2 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -38,6 +38,7 @@ class ProtocolSettingSerializer(serializers.Serializer): class PlatformAutomationSerializer(serializers.ModelSerializer): + class Meta: model = PlatformAutomation fields = [ From 74b783f62f42b71c54feac69212164f9e05597f3 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Mon, 5 Dec 2022 11:24:56 +0800 Subject: [PATCH 483/488] =?UTF-8?q?perf:=20=E5=90=88=E5=B9=B6=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ops/migrations/0027_auto_20221024_1709.py | 245 +++++++++++++++++- .../0028_celerytask_last_published_time.py | 18 -- .../ops/migrations/0029_auto_20221111_1919.py | 171 ------------ .../ops/migrations/0030_auto_20221116_1811.py | 42 --- .../ops/migrations/0031_auto_20221116_2024.py | 28 -- .../ops/migrations/0032_auto_20221117_1848.py | 27 -- .../ops/migrations/0033_auto_20221118_1431.py | 28 -- apps/ops/migrations/0034_job_org_id.py | 18 -- .../migrations/0035_jobexecution_org_id.py | 18 -- .../ops/migrations/0036_auto_20221128_1839.py | 21 -- .../ops/migrations/0036_auto_20221129_1529.py | 26 -- .../ops/migrations/0037_auto_20221129_1926.py | 26 -- apps/ops/migrations/0038_playbook_org_id.py | 18 -- .../ops/migrations/0039_auto_20221129_1932.py | 25 -- ...o_20221128_1839_0039_auto_20221129_1932.py | 14 - .../ops/migrations/0041_auto_20221129_1952.py | 23 -- 16 files changed, 233 insertions(+), 515 deletions(-) delete mode 100644 apps/ops/migrations/0028_celerytask_last_published_time.py delete mode 100644 apps/ops/migrations/0029_auto_20221111_1919.py delete mode 100644 apps/ops/migrations/0030_auto_20221116_1811.py delete mode 100644 apps/ops/migrations/0031_auto_20221116_2024.py delete mode 100644 apps/ops/migrations/0032_auto_20221117_1848.py delete mode 100644 apps/ops/migrations/0033_auto_20221118_1431.py delete mode 100644 apps/ops/migrations/0034_job_org_id.py delete mode 100644 apps/ops/migrations/0035_jobexecution_org_id.py delete mode 100644 apps/ops/migrations/0036_auto_20221128_1839.py delete mode 100644 apps/ops/migrations/0036_auto_20221129_1529.py delete mode 100644 apps/ops/migrations/0037_auto_20221129_1926.py delete mode 100644 apps/ops/migrations/0038_playbook_org_id.py delete mode 100644 apps/ops/migrations/0039_auto_20221129_1932.py delete mode 100644 apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py delete mode 100644 apps/ops/migrations/0041_auto_20221129_1952.py diff --git a/apps/ops/migrations/0027_auto_20221024_1709.py b/apps/ops/migrations/0027_auto_20221024_1709.py index 34e244363..4fa117065 100644 --- a/apps/ops/migrations/0027_auto_20221024_1709.py +++ b/apps/ops/migrations/0027_auto_20221024_1709.py @@ -1,24 +1,20 @@ -# Generated by Django 3.2.14 on 2022-10-24 09:09 +# Generated by Django 3.2.14 on 2022-12-05 03:23 +from django.conf import settings from django.db import migrations, models +import django.db.models.deletion import uuid class Migration(migrations.Migration): dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0117_gateway'), ('ops', '0026_auto_20221009_2050'), ] operations = [ - migrations.DeleteModel(name='CeleryTask'), - migrations.CreateModel( - name='CeleryTask', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=1024)), - ] - ), migrations.CreateModel( name='CeleryTaskExecution', fields=[ @@ -28,9 +24,234 @@ class Migration(migrations.Migration): ('kwargs', models.JSONField(verbose_name='Kwargs')), ('state', models.CharField(max_length=16, verbose_name='State')), ('is_finished', models.BooleanField(default=False, verbose_name='Finished')), - ('date_published', models.DateTimeField(auto_now_add=True)), - ('date_start', models.DateTimeField(null=True)), - ('date_finished', models.DateTimeField(null=True)), + ('date_published', models.DateTimeField(auto_now_add=True, verbose_name='Date published')), + ('date_start', models.DateTimeField(null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), ], ), + migrations.CreateModel( + name='Job', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('is_periodic', models.BooleanField(default=False)), + ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), + ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, null=True, verbose_name='Name')), + ('instant', models.BooleanField(default=False)), + ('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')), + ('module', models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, null=True, verbose_name='Module')), + ('chdir', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Chdir')), + ('timeout', models.IntegerField(default=60, verbose_name='Timeout (Seconds)')), + ('type', models.CharField(choices=[('adhoc', 'Adhoc'), ('playbook', 'Playbook')], default='adhoc', max_length=128, verbose_name='Type')), + ('runas', models.CharField(default='root', max_length=128, verbose_name='Runas')), + ('runas_policy', models.CharField(choices=[('privileged_only', 'Privileged Only'), ('privileged_first', 'Privileged First'), ('skip', 'Skip')], default='skip', max_length=128, verbose_name='Runas policy')), + ('use_parameter_define', models.BooleanField(default=False, verbose_name='Use Parameter Define')), + ('parameters_define', models.JSONField(default=dict, verbose_name='Parameters define')), + ('comment', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment')), + ('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')), + ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ], + options={ + 'ordering': ['date_created'], + }, + ), + migrations.CreateModel( + name='JobExecution', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('task_id', models.UUIDField(null=True)), + ('status', models.CharField(default='running', max_length=16, verbose_name='Status')), + ('parameters', models.JSONField(default=dict, verbose_name='Parameters')), + ('result', models.JSONField(blank=True, null=True, verbose_name='Result')), + ('summary', models.JSONField(default=dict, verbose_name='Summary')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), + ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), + ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('job', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='ops.job')), + ], + options={ + 'ordering': ['-date_created'], + }, + ), + migrations.RemoveField( + model_name='playbookexecution', + name='creator', + ), + migrations.RemoveField( + model_name='playbookexecution', + name='task', + ), + migrations.AlterUniqueTogether( + name='playbooktemplate', + unique_together=None, + ), + migrations.AlterModelOptions( + name='celerytask', + options={'ordering': ('name',)}, + ), + migrations.RenameField( + model_name='adhoc', + old_name='owner', + new_name='creator', + ), + migrations.RenameField( + model_name='celerytask', + old_name='date_finished', + new_name='last_published_time', + ), + migrations.RemoveField( + model_name='adhoc', + name='account', + ), + migrations.RemoveField( + model_name='adhoc', + name='account_policy', + ), + migrations.RemoveField( + model_name='adhoc', + name='assets', + ), + migrations.RemoveField( + model_name='adhoc', + name='crontab', + ), + migrations.RemoveField( + model_name='adhoc', + name='date_last_run', + ), + migrations.RemoveField( + model_name='adhoc', + name='interval', + ), + migrations.RemoveField( + model_name='adhoc', + name='is_periodic', + ), + migrations.RemoveField( + model_name='adhoc', + name='last_execution', + ), + migrations.RemoveField( + model_name='celerytask', + name='args', + ), + migrations.RemoveField( + model_name='celerytask', + name='date_published', + ), + migrations.RemoveField( + model_name='celerytask', + name='date_start', + ), + migrations.RemoveField( + model_name='celerytask', + name='is_finished', + ), + migrations.RemoveField( + model_name='celerytask', + name='kwargs', + ), + migrations.RemoveField( + model_name='celerytask', + name='state', + ), + migrations.RemoveField( + model_name='playbook', + name='account', + ), + migrations.RemoveField( + model_name='playbook', + name='account_policy', + ), + migrations.RemoveField( + model_name='playbook', + name='assets', + ), + migrations.RemoveField( + model_name='playbook', + name='crontab', + ), + migrations.RemoveField( + model_name='playbook', + name='date_last_run', + ), + migrations.RemoveField( + model_name='playbook', + name='interval', + ), + migrations.RemoveField( + model_name='playbook', + name='is_periodic', + ), + migrations.RemoveField( + model_name='playbook', + name='last_execution', + ), + migrations.RemoveField( + model_name='playbook', + name='owner', + ), + migrations.RemoveField( + model_name='playbook', + name='template', + ), + migrations.AddField( + model_name='adhoc', + name='comment', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), + ), + migrations.AddField( + model_name='playbook', + name='creator', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator'), + ), + migrations.AlterField( + model_name='adhoc', + name='module', + field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, verbose_name='Module'), + ), + migrations.AlterField( + model_name='celerytask', + name='name', + field=models.CharField(max_length=1024, verbose_name='Name'), + ), + migrations.AlterField( + model_name='playbook', + name='comment', + field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), + ), + migrations.AlterField( + model_name='playbook', + name='name', + field=models.CharField(max_length=128, null=True, verbose_name='Name'), + ), + migrations.AlterField( + model_name='playbook', + name='path', + field=models.FileField(upload_to='playbooks/'), + ), + migrations.DeleteModel( + name='AdHocExecution', + ), + migrations.DeleteModel( + name='PlaybookExecution', + ), + migrations.DeleteModel( + name='PlaybookTemplate', + ), + migrations.AddField( + model_name='job', + name='playbook', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbook', verbose_name='Playbook'), + ), ] diff --git a/apps/ops/migrations/0028_celerytask_last_published_time.py b/apps/ops/migrations/0028_celerytask_last_published_time.py deleted file mode 100644 index 6732508e3..000000000 --- a/apps/ops/migrations/0028_celerytask_last_published_time.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-10-27 06:35 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0027_auto_20221024_1709'), - ] - - operations = [ - migrations.AddField( - model_name='celerytask', - name='last_published_time', - field=models.DateTimeField(null=True), - ), - ] diff --git a/apps/ops/migrations/0029_auto_20221111_1919.py b/apps/ops/migrations/0029_auto_20221111_1919.py deleted file mode 100644 index 072e59867..000000000 --- a/apps/ops/migrations/0029_auto_20221111_1919.py +++ /dev/null @@ -1,171 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-11 11:19 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0111_alter_automationexecution_status'), - ('ops', '0028_celerytask_last_published_time'), - ] - - operations = [ - migrations.CreateModel( - name='Job', - fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128, null=True, verbose_name='Name')), - ('instant', models.BooleanField(default=False)), - ('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')), - ('module', models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, null=True, verbose_name='Module')), - ('type', models.CharField(choices=[('adhoc', 'Adhoc'), ('playbook', 'Playbook')], default='adhoc', max_length=128, verbose_name='Type')), - ('runas', models.CharField(default='root', max_length=128, verbose_name='Runas')), - ('runas_policy', models.CharField(choices=[('privileged_only', 'Privileged Only'), ('privileged_first', 'Privileged First'), ('skip', 'Skip')], default='skip', max_length=128, verbose_name='Runas policy')), - ('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')), - ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='JobExecution', - fields=[ - ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), - ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), - ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('task_id', models.UUIDField(null=True)), - ('status', models.CharField(default='running', max_length=16, verbose_name='Status')), - ('result', models.JSONField(blank=True, null=True, verbose_name='Result')), - ('summary', models.JSONField(default=dict, verbose_name='Summary')), - ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), - ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), - ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), - ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), - ('job', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='ops.job')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AlterUniqueTogether( - name='playbooktemplate', - unique_together=None, - ), - migrations.RemoveField( - model_name='adhoc', - name='account', - ), - migrations.RemoveField( - model_name='adhoc', - name='account_policy', - ), - migrations.RemoveField( - model_name='adhoc', - name='assets', - ), - migrations.RemoveField( - model_name='adhoc', - name='crontab', - ), - migrations.RemoveField( - model_name='adhoc', - name='date_last_run', - ), - migrations.RemoveField( - model_name='adhoc', - name='interval', - ), - migrations.RemoveField( - model_name='adhoc', - name='is_periodic', - ), - migrations.RemoveField( - model_name='adhoc', - name='last_execution', - ), - migrations.RemoveField( - model_name='adhoc', - name='org_id', - ), - migrations.RemoveField( - model_name='playbook', - name='account', - ), - migrations.RemoveField( - model_name='playbook', - name='account_policy', - ), - migrations.RemoveField( - model_name='playbook', - name='assets', - ), - migrations.RemoveField( - model_name='playbook', - name='comment', - ), - migrations.RemoveField( - model_name='playbook', - name='crontab', - ), - migrations.RemoveField( - model_name='playbook', - name='date_last_run', - ), - migrations.RemoveField( - model_name='playbook', - name='interval', - ), - migrations.RemoveField( - model_name='playbook', - name='is_periodic', - ), - migrations.RemoveField( - model_name='playbook', - name='last_execution', - ), - migrations.RemoveField( - model_name='playbook', - name='org_id', - ), - migrations.RemoveField( - model_name='playbook', - name='template', - ), - migrations.AlterField( - model_name='adhoc', - name='module', - field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, verbose_name='Module'), - ), - migrations.AlterField( - model_name='playbook', - name='name', - field=models.CharField(max_length=128, null=True, verbose_name='Name'), - ), - migrations.AlterField( - model_name='playbook', - name='path', - field=models.FileField(upload_to='playbooks/'), - ), - migrations.DeleteModel( - name='PlaybookExecution', - ), - migrations.DeleteModel( - name='PlaybookTemplate', - ), - migrations.AddField( - model_name='job', - name='playbook', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbook', verbose_name='Playbook'), - ), - ] diff --git a/apps/ops/migrations/0030_auto_20221116_1811.py b/apps/ops/migrations/0030_auto_20221116_1811.py deleted file mode 100644 index 3118f26ca..000000000 --- a/apps/ops/migrations/0030_auto_20221116_1811.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-16 10:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0029_auto_20221111_1919'), - ] - - operations = [ - migrations.AlterModelOptions( - name='celerytask', - options={'ordering': ('name',)}, - ), - migrations.AddField( - model_name='job', - name='variables', - field=models.JSONField(default=dict, verbose_name='Variables'), - ), - migrations.AlterField( - model_name='celerytask', - name='name', - field=models.CharField(max_length=1024, verbose_name='Name'), - ), - migrations.AlterField( - model_name='celerytaskexecution', - name='date_finished', - field=models.DateTimeField(null=True, verbose_name='Date finished'), - ), - migrations.AlterField( - model_name='celerytaskexecution', - name='date_published', - field=models.DateTimeField(auto_now_add=True, verbose_name='Date published'), - ), - migrations.AlterField( - model_name='celerytaskexecution', - name='date_start', - field=models.DateTimeField(null=True, verbose_name='Date start'), - ), - ] diff --git a/apps/ops/migrations/0031_auto_20221116_2024.py b/apps/ops/migrations/0031_auto_20221116_2024.py deleted file mode 100644 index 5c132e974..000000000 --- a/apps/ops/migrations/0031_auto_20221116_2024.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-16 12:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0030_auto_20221116_1811'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='chdir', - field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Chdir'), - ), - migrations.AddField( - model_name='job', - name='comment', - field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), - ), - migrations.AddField( - model_name='job', - name='timeout', - field=models.IntegerField(default=60, verbose_name='Timeout (Seconds)'), - ), - ] diff --git a/apps/ops/migrations/0032_auto_20221117_1848.py b/apps/ops/migrations/0032_auto_20221117_1848.py deleted file mode 100644 index ae18c5280..000000000 --- a/apps/ops/migrations/0032_auto_20221117_1848.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-17 10:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0031_auto_20221116_2024'), - ] - - operations = [ - migrations.RemoveField( - model_name='job', - name='variables', - ), - migrations.AddField( - model_name='job', - name='parameters_define', - field=models.JSONField(default=dict, verbose_name='Parameters define'), - ), - migrations.AddField( - model_name='jobexecution', - name='parameters', - field=models.JSONField(default=dict, verbose_name='Parameters'), - ), - ] diff --git a/apps/ops/migrations/0033_auto_20221118_1431.py b/apps/ops/migrations/0033_auto_20221118_1431.py deleted file mode 100644 index 70518eee6..000000000 --- a/apps/ops/migrations/0033_auto_20221118_1431.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-18 06:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0032_auto_20221117_1848'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='crontab', - field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform'), - ), - migrations.AddField( - model_name='job', - name='interval', - field=models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform'), - ), - migrations.AddField( - model_name='job', - name='is_periodic', - field=models.BooleanField(default=False), - ), - ] diff --git a/apps/ops/migrations/0034_job_org_id.py b/apps/ops/migrations/0034_job_org_id.py deleted file mode 100644 index 07926cec3..000000000 --- a/apps/ops/migrations/0034_job_org_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-23 09:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0033_auto_20221118_1431'), - ] - - operations = [ - migrations.AddField( - model_name='job', - name='org_id', - field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), - ), - ] diff --git a/apps/ops/migrations/0035_jobexecution_org_id.py b/apps/ops/migrations/0035_jobexecution_org_id.py deleted file mode 100644 index 1161d10e3..000000000 --- a/apps/ops/migrations/0035_jobexecution_org_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-23 10:22 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0034_job_org_id'), - ] - - operations = [ - migrations.AddField( - model_name='jobexecution', - name='org_id', - field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), - ), - ] diff --git a/apps/ops/migrations/0036_auto_20221128_1839.py b/apps/ops/migrations/0036_auto_20221128_1839.py deleted file mode 100644 index 22bc435e2..000000000 --- a/apps/ops/migrations/0036_auto_20221128_1839.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-28 10:39 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0035_jobexecution_org_id'), - ] - - operations = [ - migrations.AlterModelOptions( - name='job', - options={'ordering': ['date_created']}, - ), - migrations.AlterModelOptions( - name='jobexecution', - options={'ordering': ['-date_created']}, - ), - ] diff --git a/apps/ops/migrations/0036_auto_20221129_1529.py b/apps/ops/migrations/0036_auto_20221129_1529.py deleted file mode 100644 index a766ee72d..000000000 --- a/apps/ops/migrations/0036_auto_20221129_1529.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 07:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0035_jobexecution_org_id'), - ] - - operations = [ - migrations.AlterModelOptions( - name='job', - options={'ordering': ['date_created']}, - ), - migrations.AlterModelOptions( - name='jobexecution', - options={'ordering': ['-date_created']}, - ), - migrations.AddField( - model_name='job', - name='use_parameter_define', - field=models.BooleanField(default=False, verbose_name='Use Parameter Define'), - ), - ] diff --git a/apps/ops/migrations/0037_auto_20221129_1926.py b/apps/ops/migrations/0037_auto_20221129_1926.py deleted file mode 100644 index 086a93cd7..000000000 --- a/apps/ops/migrations/0037_auto_20221129_1926.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 11:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0036_auto_20221129_1529'), - ] - - operations = [ - migrations.RenameField( - model_name='adhoc', - old_name='owner', - new_name='creator', - ), - migrations.AddField( - model_name='adhoc', - name='org_id', - field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), - ), - migrations.DeleteModel( - name='AdHocExecution', - ), - ] diff --git a/apps/ops/migrations/0038_playbook_org_id.py b/apps/ops/migrations/0038_playbook_org_id.py deleted file mode 100644 index 0de95334c..000000000 --- a/apps/ops/migrations/0038_playbook_org_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 11:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0037_auto_20221129_1926'), - ] - - operations = [ - migrations.AddField( - model_name='playbook', - name='org_id', - field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'), - ), - ] diff --git a/apps/ops/migrations/0039_auto_20221129_1932.py b/apps/ops/migrations/0039_auto_20221129_1932.py deleted file mode 100644 index f54ed1cbe..000000000 --- a/apps/ops/migrations/0039_auto_20221129_1932.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 11:32 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('ops', '0038_playbook_org_id'), - ] - - operations = [ - migrations.RemoveField( - model_name='playbook', - name='owner', - ), - migrations.AddField( - model_name='playbook', - name='creator', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator'), - ), - ] diff --git a/apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py b/apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py deleted file mode 100644 index c0bbcb9a1..000000000 --- a/apps/ops/migrations/0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 11:47 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0036_auto_20221128_1839'), - ('ops', '0039_auto_20221129_1932'), - ] - - operations = [ - ] diff --git a/apps/ops/migrations/0041_auto_20221129_1952.py b/apps/ops/migrations/0041_auto_20221129_1952.py deleted file mode 100644 index fb3411a4d..000000000 --- a/apps/ops/migrations/0041_auto_20221129_1952.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-29 11:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ops', '0040_merge_0036_auto_20221128_1839_0039_auto_20221129_1932'), - ] - - operations = [ - migrations.AddField( - model_name='adhoc', - name='comment', - field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), - ), - migrations.AddField( - model_name='playbook', - name='comment', - field=models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment'), - ), - ] From 03e62b5bc14b577939b0dc96993ee60d7dc1bb24 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 5 Dec 2022 11:48:41 +0800 Subject: [PATCH 484/488] perf: asset mini --- apps/assets/serializers/asset/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 45ca422df..0fd9e9e8a 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -71,7 +71,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) class Meta: model = Asset - fields_mini = ['id', 'name', 'address'] + fields_mini = ['id', 'name', 'address', 'automation_enabled_info'] fields_small = fields_mini + ['is_active', 'comment'] fields_fk = ['domain', 'platform', 'platform'] fields_m2m = [ @@ -80,7 +80,7 @@ class AssetSerializer(OrgResourceSerializerMixin, WritableNestedModelSerializer) read_only_fields = [ 'category', 'type', 'specific', 'info', 'connectivity', 'date_verified', 'created_by', - 'date_created', 'automation_enabled_info' + 'date_created' ] fields = fields_small + fields_fk + fields_m2m + read_only_fields extra_kwargs = { From 38b1701b3325d34b913e86215bc5ff7678de3708 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Dec 2022 12:42:15 +0800 Subject: [PATCH 485/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?= =?UTF-8?q?=EF=BC=8C=20=E4=BF=AE=E6=94=B9=20Connect=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0112_gateway_to_asset.py | 15 ++++++++ .../0113_alter_accounttemplate_options.py | 32 ++++++++++++++-- apps/assets/migrations/0114_node_domain.py | 27 ++++++++++++- .../migrations/0115_auto_20221130_1118.py | 38 ------------------- apps/assets/migrations/0116_delete_gateway.py | 16 -------- apps/assets/migrations/0117_gateway.py | 24 ------------ apps/authentication/api/connection_token.py | 2 +- .../migrations/0015_auto_20221205_1136.py | 17 +++++++++ .../authentication/models/connection_token.py | 22 +++++++---- .../serializers/connect_token_secret.py | 16 +++++++- .../serializers/connection_token.py | 2 +- apps/terminal/const.py | 3 +- 12 files changed, 117 insertions(+), 97 deletions(-) delete mode 100644 apps/assets/migrations/0115_auto_20221130_1118.py delete mode 100644 apps/assets/migrations/0116_delete_gateway.py delete mode 100644 apps/assets/migrations/0117_gateway.py create mode 100644 apps/authentication/migrations/0015_auto_20221205_1136.py diff --git a/apps/assets/migrations/0112_gateway_to_asset.py b/apps/assets/migrations/0112_gateway_to_asset.py index da43b3a84..67c874761 100644 --- a/apps/assets/migrations/0112_gateway_to_asset.py +++ b/apps/assets/migrations/0112_gateway_to_asset.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.13 on 2022-09-29 11:03 from django.db import migrations + from assets.const.host import GATEWAY_NAME @@ -70,4 +71,18 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(migrate_gateway_to_asset), + migrations.DeleteModel( + name='Gateway', + ), + migrations.CreateModel( + name='Gateway', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('assets.host',), + ), ] diff --git a/apps/assets/migrations/0113_alter_accounttemplate_options.py b/apps/assets/migrations/0113_alter_accounttemplate_options.py index e635426c1..9488b5499 100644 --- a/apps/assets/migrations/0113_alter_accounttemplate_options.py +++ b/apps/assets/migrations/0113_alter_accounttemplate_options.py @@ -1,10 +1,9 @@ # Generated by Django 3.2.14 on 2022-11-28 10:39 -from django.db import migrations +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('assets', '0112_gateway_to_asset'), ] @@ -12,6 +11,33 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='accounttemplate', - options={'permissions': [('view_accounttemplatesecret', 'Can view asset account template secret'), ('change_accounttemplatesecret', 'Can change asset account template secret')], 'verbose_name': 'Account template'}, + options={'permissions': [('view_accounttemplatesecret', 'Can view asset account template secret'), + ('change_accounttemplatesecret', 'Can change asset account template secret')], + 'verbose_name': 'Account template'}, + ), + migrations.AddField( + model_name='database', + name='allow_invalid_cert', + field=models.BooleanField(default=False, verbose_name='Allow invalid cert'), + ), + migrations.AddField( + model_name='database', + name='ca_cert', + field=models.TextField(blank=True, verbose_name='CA cert'), + ), + migrations.AddField( + model_name='database', + name='client_cert', + field=models.TextField(blank=True, verbose_name='Client cert'), + ), + migrations.AddField( + model_name='database', + name='client_key', + field=models.TextField(blank=True, verbose_name='Client key'), + ), + migrations.AddField( + model_name='database', + name='use_ssl', + field=models.BooleanField(default=False, verbose_name='Use SSL'), ), ] diff --git a/apps/assets/migrations/0114_node_domain.py b/apps/assets/migrations/0114_node_domain.py index 0149a5d11..1abcde400 100644 --- a/apps/assets/migrations/0114_node_domain.py +++ b/apps/assets/migrations/0114_node_domain.py @@ -1,14 +1,37 @@ # Generated by Django 3.2.14 on 2022-11-29 05:14 from django.db import migrations, models -import django.db.models.deletion -# TODO 最后去掉这个迁移 class Migration(migrations.Migration): dependencies = [ ('assets', '0113_alter_accounttemplate_options'), ] operations = [ + migrations.AddField( + model_name='database', + name='allow_invalid_cert', + field=models.BooleanField(default=False, verbose_name='Allow invalid cert'), + ), + migrations.AddField( + model_name='database', + name='ca_cert', + field=models.TextField(blank=True, verbose_name='CA cert'), + ), + migrations.AddField( + model_name='database', + name='client_cert', + field=models.TextField(blank=True, verbose_name='Client cert'), + ), + migrations.AddField( + model_name='database', + name='client_key', + field=models.TextField(blank=True, verbose_name='Client key'), + ), + migrations.AddField( + model_name='database', + name='use_ssl', + field=models.BooleanField(default=False, verbose_name='Use SSL'), + ), ] diff --git a/apps/assets/migrations/0115_auto_20221130_1118.py b/apps/assets/migrations/0115_auto_20221130_1118.py deleted file mode 100644 index 3d5fc9b20..000000000 --- a/apps/assets/migrations/0115_auto_20221130_1118.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 3.2.14 on 2022-11-30 03:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0114_node_domain'), - ] - - operations = [ - migrations.AddField( - model_name='database', - name='allow_invalid_cert', - field=models.BooleanField(default=False, verbose_name='Allow invalid cert'), - ), - migrations.AddField( - model_name='database', - name='ca_cert', - field=models.TextField(blank=True, verbose_name='CA cert'), - ), - migrations.AddField( - model_name='database', - name='client_cert', - field=models.TextField(blank=True, verbose_name='Client cert'), - ), - migrations.AddField( - model_name='database', - name='client_key', - field=models.TextField(blank=True, verbose_name='Client key'), - ), - migrations.AddField( - model_name='database', - name='use_ssl', - field=models.BooleanField(default=False, verbose_name='Use SSL'), - ), - ] diff --git a/apps/assets/migrations/0116_delete_gateway.py b/apps/assets/migrations/0116_delete_gateway.py deleted file mode 100644 index 9ccb1c1b7..000000000 --- a/apps/assets/migrations/0116_delete_gateway.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.14 on 2022-12-01 07:08 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0115_auto_20221130_1118'), - ] - - operations = [ - migrations.DeleteModel( - name='Gateway', - ), - ] diff --git a/apps/assets/migrations/0117_gateway.py b/apps/assets/migrations/0117_gateway.py deleted file mode 100644 index 6bf8ce138..000000000 --- a/apps/assets/migrations/0117_gateway.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.2.14 on 2022-12-01 07:21 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assets', '0116_delete_gateway'), - ] - - operations = [ - migrations.CreateModel( - name='Gateway', - fields=[ - ], - options={ - 'proxy': True, - 'indexes': [], - 'constraints': [], - }, - bases=('assets.host',), - ), - ] diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index e9df9d33b..97ef3b5ff 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -278,7 +278,7 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView data = serializer.validated_data user = self.get_user(serializer) asset = data.get('asset') - account_name = data.get('account_name') + account_name = data.get('account') data['org_id'] = asset.org_id data['user'] = user data['value'] = random_string(16) diff --git a/apps/authentication/migrations/0015_auto_20221205_1136.py b/apps/authentication/migrations/0015_auto_20221205_1136.py new file mode 100644 index 000000000..7de71fe5c --- /dev/null +++ b/apps/authentication/migrations/0015_auto_20221205_1136.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.14 on 2022-12-05 03:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('authentication', '0014_auto_20221122_2152'), + ] + + operations = [ + migrations.RenameField( + model_name='connectiontoken', + old_name='account_name', + new_name='account', + ), + ] diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 93b137923..1b8e27085 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -28,7 +28,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): 'assets.Asset', on_delete=models.SET_NULL, null=True, blank=True, related_name='connection_tokens', verbose_name=_('Asset'), ) - account_name = models.CharField(max_length=128, verbose_name=_("Account name")) # 登录账号Name + account = models.CharField(max_length=128, verbose_name=_("Account name")) # 登录账号Name input_username = models.CharField(max_length=128, default='', blank=True, verbose_name=_("Input username")) input_secret = EncryptCharField(max_length=64, default='', blank=True, verbose_name=_("Input secret")) protocol = models.CharField(max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")) @@ -74,7 +74,7 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def permed_account(self): from perms.utils import PermAccountUtil permed_account = PermAccountUtil().validate_permission( - self.user, self.asset, self.account_name + self.user, self.asset, self.account ) return permed_account @@ -86,6 +86,12 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def expire_at(self): return self.permed_account.date_expired.timestamp() + @lazyproperty + def connect_method_object(self): + from terminal.const import TerminalType + method = TerminalType.get_connect_method(self.connect_method, protocol=self.protocol) + return method + def is_valid(self): if self.is_expired: error = _('Connection token expired at: {}').format(as_current_tz(self.date_expired)) @@ -97,13 +103,13 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): is_valid = False error = _('No asset or inactive asset') return is_valid, error - if not self.account_name: + if not self.account: error = _('No account') raise PermissionDenied(error) if not self.permed_account or not self.permed_account.actions: msg = 'user `{}` not has asset `{}` permission for login `{}`'.format( - self.user, self.asset, self.account_name + self.user, self.asset, self.account ) raise PermissionDenied(msg) @@ -116,15 +122,15 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): return self.asset.platform @lazyproperty - def account(self): + def account_object(self): from assets.models import Account if not self.asset: return None - account = self.asset.accounts.filter(name=self.account_name).first() - if self.account_name == '@INPUT' or not account: + account = self.asset.accounts.filter(name=self.account).first() + if self.account == '@INPUT' or not account: data = { - 'name': self.account_name, + 'name': self.account, 'username': self.input_username, 'secret_type': 'password', 'secret': self.input_secret, diff --git a/apps/authentication/serializers/connect_token_secret.py b/apps/authentication/serializers/connect_token_secret.py index dd8804ac7..48f86ba50 100644 --- a/apps/authentication/serializers/connect_token_secret.py +++ b/apps/authentication/serializers/connect_token_secret.py @@ -84,14 +84,14 @@ class _ConnectionTokenPlatformSerializer(PlatformSerializer): class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): user = _ConnectionTokenUserSerializer(read_only=True) asset = _ConnectionTokenAssetSerializer(read_only=True) - account = _ConnectionTokenAccountSerializer(read_only=True) + account = _ConnectionTokenAccountSerializer(read_only=True, source='account_object') gateway = _ConnectionTokenGatewaySerializer(read_only=True) platform = _ConnectionTokenPlatformSerializer(read_only=True) acl_command_groups = _ConnectionTokenACLCmdGroupSerializer(read_only=True, many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) - connect_method = serializers.CharField(label=_('Connect method'), write_only=True, default='ssh') + connect_method = serializers.SerializerMethodField(label=_('Connect method')) class Meta: model = ConnectionToken @@ -99,7 +99,19 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): 'id', 'value', 'user', 'asset', 'account', 'platform', 'acl_command_groups', 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', + 'connect_method' ] extra_kwargs = { 'value': {'read_only': True}, } + + def get_connect_method(self, obj): + from terminal.const import TerminalType + from common.utils import get_request_os + request = self.context.get('request') + if request: + os = get_request_os(request) + else: + os = 'windows' + method = TerminalType.get_connect_method(obj.connect_method, protocol=obj.protocol, os=os) + return method diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index dddcd0866..b627db320 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -16,7 +16,7 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields_mini = ['id', 'value'] fields_small = fields_mini + [ - 'user', 'asset', 'account_name', 'input_username', + 'user', 'asset', 'account', 'input_username', 'input_secret', 'connect_method', 'protocol', 'actions', 'date_expired', 'date_created', 'date_updated', 'created_by', 'updated_by', 'org_id', 'org_name', diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 6d7a14f9d..c5ceb3c94 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -229,7 +229,7 @@ class TerminalType(TextChoices): return protocols @classmethod - def get_connect_method(cls, name, protocol, os): + def get_connect_method(cls, name, protocol, os='linux'): methods = cls.get_protocols_connect_methods(os) protocol_methods = methods.get(protocol, []) for method in protocol_methods: @@ -267,7 +267,6 @@ class TerminalType(TextChoices): protocol_web_methods = set(web_methods.get(protocol, [])) \ & set(component_protocol.get('web_methods', [])) - print("protocol_web_methods", protocol, protocol_web_methods) methods[protocol.value].extend([ { 'component': component.value, From ca228074e3d7a86dfc8bd608e0e4c674710503fb Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Dec 2022 12:58:09 +0800 Subject: [PATCH 486/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20migrations?= =?UTF-8?q?=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/models/connection_token.py | 6 --- .../ops/migrations/0027_auto_20221024_1709.py | 48 ++++++++++++------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index 1b8e27085..6340da104 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -86,12 +86,6 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): def expire_at(self): return self.permed_account.date_expired.timestamp() - @lazyproperty - def connect_method_object(self): - from terminal.const import TerminalType - method = TerminalType.get_connect_method(self.connect_method, protocol=self.protocol) - return method - def is_valid(self): if self.is_expired: error = _('Connection token expired at: {}').format(as_current_tz(self.date_expired)) diff --git a/apps/ops/migrations/0027_auto_20221024_1709.py b/apps/ops/migrations/0027_auto_20221024_1709.py index 4fa117065..08411d42b 100644 --- a/apps/ops/migrations/0027_auto_20221024_1709.py +++ b/apps/ops/migrations/0027_auto_20221024_1709.py @@ -1,16 +1,16 @@ # Generated by Django 3.2.14 on 2022-12-05 03:23 +import uuid + +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion -import uuid class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('assets', '0117_gateway'), + ('assets', '0112_gateway_to_asset'), ('ops', '0026_auto_20221009_2050'), ] @@ -36,7 +36,8 @@ class Migration(migrations.Migration): ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('is_periodic', models.BooleanField(default=False)), ('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')), ('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')), @@ -44,17 +45,24 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=128, null=True, verbose_name='Name')), ('instant', models.BooleanField(default=False)), ('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')), - ('module', models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, null=True, verbose_name='Module')), + ('module', models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', + max_length=128, null=True, verbose_name='Module')), ('chdir', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Chdir')), ('timeout', models.IntegerField(default=60, verbose_name='Timeout (Seconds)')), - ('type', models.CharField(choices=[('adhoc', 'Adhoc'), ('playbook', 'Playbook')], default='adhoc', max_length=128, verbose_name='Type')), + ('type', models.CharField(choices=[('adhoc', 'Adhoc'), ('playbook', 'Playbook')], default='adhoc', + max_length=128, verbose_name='Type')), ('runas', models.CharField(default='root', max_length=128, verbose_name='Runas')), - ('runas_policy', models.CharField(choices=[('privileged_only', 'Privileged Only'), ('privileged_first', 'Privileged First'), ('skip', 'Skip')], default='skip', max_length=128, verbose_name='Runas policy')), + ('runas_policy', models.CharField( + choices=[('privileged_only', 'Privileged Only'), ('privileged_first', 'Privileged First'), + ('skip', 'Skip')], default='skip', max_length=128, verbose_name='Runas policy')), ('use_parameter_define', models.BooleanField(default=False, verbose_name='Use Parameter Define')), ('parameters_define', models.JSONField(default=dict, verbose_name='Parameters define')), - ('comment', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment')), + ('comment', + models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Comment')), ('assets', models.ManyToManyField(to='assets.Asset', verbose_name='Assets')), - ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), + ('owner', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, + verbose_name='Creator')), ], options={ 'ordering': ['date_created'], @@ -66,7 +74,8 @@ class Migration(migrations.Migration): ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), - ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('org_id', + models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('task_id', models.UUIDField(null=True)), ('status', models.CharField(default='running', max_length=16, verbose_name='Status')), @@ -76,8 +85,12 @@ class Migration(migrations.Migration): ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('date_start', models.DateTimeField(db_index=True, null=True, verbose_name='Date start')), ('date_finished', models.DateTimeField(null=True, verbose_name='Date finished')), - ('creator', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator')), - ('job', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', to='ops.job')), + ('creator', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, + verbose_name='Creator')), + ('job', + models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='executions', + to='ops.job')), ], options={ 'ordering': ['-date_created'], @@ -213,12 +226,14 @@ class Migration(migrations.Migration): migrations.AddField( model_name='playbook', name='creator', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Creator'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, verbose_name='Creator'), ), migrations.AlterField( model_name='adhoc', name='module', - field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', max_length=128, verbose_name='Module'), + field=models.CharField(choices=[('shell', 'Shell'), ('win_shell', 'Powershell')], default='shell', + max_length=128, verbose_name='Module'), ), migrations.AlterField( model_name='celerytask', @@ -252,6 +267,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='job', name='playbook', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbook', verbose_name='Playbook'), + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='ops.playbook', + verbose_name='Playbook'), ), ] From cc7424dbfe78570ad504ed81b138575cd79dfa7c Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 5 Dec 2022 13:27:51 +0800 Subject: [PATCH 487/488] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20CommandFil?= =?UTF-8?q?terACL,=20CommandGroup=20Model=20=E7=9A=84=20Meta=20=E5=86=85?= =?UTF-8?q?=E9=83=A8=E7=B1=BB;=20=E4=BF=AE=E6=94=B9=20Command=20Model=20?= =?UTF-8?q?=E7=9A=84=20system=5Fuser=20->=20account=20=E5=AD=97=E6=AE=B5;?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=20ConnectionToken=20=E7=9A=84=20command?= =?UTF-8?q?=5Ffilter=5Facls=20=E8=BF=94=E5=9B=9E=E5=AD=97=E6=AE=B5;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0010_auto_20221205_1122.py | 25 ++++++++++++++ apps/acls/models/base.py | 5 +-- apps/acls/models/command_acl.py | 18 ++-------- apps/acls/models/login_acl.py | 4 +-- apps/acls/models/login_asset_acl.py | 5 ++- .../authentication/models/connection_token.py | 6 ++-- .../serializers/connection_token.py | 33 ++++++++++++------- apps/terminal/api/session/command.py | 6 ++-- apps/terminal/backends/command/base.py | 4 +-- apps/terminal/backends/command/db.py | 18 +++++----- apps/terminal/backends/command/es.py | 4 +-- apps/terminal/backends/command/models.py | 2 +- apps/terminal/backends/command/serializers.py | 5 +-- apps/terminal/filters.py | 6 ++-- ...0061_rename_system_user_command_account.py | 23 +++++++++++++ apps/terminal/models/session/command.py | 2 +- 16 files changed, 106 insertions(+), 60 deletions(-) create mode 100644 apps/acls/migrations/0010_auto_20221205_1122.py create mode 100644 apps/terminal/migrations/0061_rename_system_user_command_account.py diff --git a/apps/acls/migrations/0010_auto_20221205_1122.py b/apps/acls/migrations/0010_auto_20221205_1122.py new file mode 100644 index 000000000..78adde93b --- /dev/null +++ b/apps/acls/migrations/0010_auto_20221205_1122.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.14 on 2022-12-05 03:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0009_auto_20221204_0001'), + ] + + operations = [ + migrations.AlterModelOptions( + name='commandfilteracl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Command acl'}, + ), + migrations.AlterModelOptions( + name='loginacl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Login acl'}, + ), + migrations.AlterModelOptions( + name='loginassetacl', + options={'ordering': ('priority', 'name'), 'verbose_name': 'Login asset acl'}, + ), + ] diff --git a/apps/acls/models/base.py b/apps/acls/models/base.py index 43577d44c..704e3d743 100644 --- a/apps/acls/models/base.py +++ b/apps/acls/models/base.py @@ -83,6 +83,7 @@ class BaseACL(CommonModelMixin): objects = ACLManager.from_queryset(BaseACLQuerySet)() class Meta: + ordering = ('priority', 'name') abstract = True @@ -96,7 +97,8 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin): objects = ACLManager.from_queryset(UserAssetAccountACLQuerySet)() - class Meta: + class Meta(BaseACL.Meta): + unique_together = ('name', 'org_id') abstract = True @classmethod @@ -118,4 +120,3 @@ class UserAssetAccountBaseACL(BaseACL, OrgModelMixin): if kwargs: queryset = queryset.filter(**kwargs) return queryset - diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index 51e8c7388..f10373a02 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty, get_logger from orgs.mixins.models import JMSOrgBaseModel -from .base import UserAssetAccountBaseACL, UserAssetAccountACLQuerySet, ACLManager +from .base import UserAssetAccountBaseACL logger = get_logger(__file__) @@ -94,23 +94,11 @@ class CommandGroup(JMSOrgBaseModel): return '{} % {}'.format(self.name, self.type) -class CommandFilterACLQuerySet(UserAssetAccountACLQuerySet): - def get_command_groups(self): - ids = self.values_list('id', flat=True) - queryset = CommandFilterACL.command_groups.through.objects.filter(commandfilteracl_id__in=ids) - cmd_group_ids = queryset.values_list('commandgroup_id', flat=True) - command_groups = CommandGroup.objects.filter(id__in=cmd_group_ids) - return command_groups - - class CommandFilterACL(UserAssetAccountBaseACL): command_groups = models.ManyToManyField(CommandGroup, verbose_name=_('Commands')) - objects = ACLManager.from_queryset(CommandFilterACLQuerySet)() - - class Meta: - unique_together = ('name', 'org_id') - ordering = ('priority', '-date_updated', 'name') + class Meta(UserAssetAccountBaseACL.Meta): + abstract = False verbose_name = _('Command acl') def __str__(self): diff --git a/apps/acls/models/login_acl.py b/apps/acls/models/login_acl.py index de6a73d1b..1178993c8 100644 --- a/apps/acls/models/login_acl.py +++ b/apps/acls/models/login_acl.py @@ -15,9 +15,9 @@ class LoginACL(BaseACL): # 规则, ip_group, time_period rules = models.JSONField(default=dict, verbose_name=_('Rule')) - class Meta: - ordering = ('priority', '-date_updated', 'name') + class Meta(BaseACL.Meta): verbose_name = _('Login acl') + abstract = False def __str__(self): return self.name diff --git a/apps/acls/models/login_asset_acl.py b/apps/acls/models/login_asset_acl.py index 695b83e05..bdaf9b60c 100644 --- a/apps/acls/models/login_asset_acl.py +++ b/apps/acls/models/login_asset_acl.py @@ -6,10 +6,9 @@ from .base import UserAssetAccountBaseACL class LoginAssetACL(UserAssetAccountBaseACL): - class Meta: - unique_together = ('name', 'org_id') - ordering = ('priority', '-date_updated', 'name') + class Meta(UserAssetAccountBaseACL.Meta): verbose_name = _('Login asset acl') + abstract = False def __str__(self): return self.name diff --git a/apps/authentication/models/connection_token.py b/apps/authentication/models/connection_token.py index ef4fad0ac..3de6f92af 100644 --- a/apps/authentication/models/connection_token.py +++ b/apps/authentication/models/connection_token.py @@ -160,15 +160,15 @@ class ConnectionToken(OrgModelMixin, JMSBaseModel): return self.domain.random_gateway() @lazyproperty - def acl_command_groups(self): + def command_filter_acls(self): from acls.models import CommandFilterACL kwargs = { 'user': self.user, 'asset': self.asset, 'account': self.account, } - command_groups = CommandFilterACL.filter_queryset(**kwargs).get_command_groups() - return command_groups + acls = CommandFilterACL.filter_queryset(**kwargs).valid() + return acls class SuperConnectionToken(ConnectionToken): diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index 3fefbb5af..070ef2b29 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -1,13 +1,15 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from assets.models import Asset, CommandFilterRule, Account, Platform -from acls.models import CommandGroup -from assets.serializers import PlatformSerializer, AssetProtocolsSerializer -from authentication.models import ConnectionToken -from orgs.mixins.serializers import OrgResourceModelSerializerMixin -from perms.serializers.permission import ActionChoicesField from users.models import User +from assets.models import Asset, Account, Platform +from assets.serializers import PlatformSerializer, AssetProtocolsSerializer +from perms.serializers.permission import ActionChoicesField +from acls.models import CommandGroup, CommandFilterACL +from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from common.drf.fields import ObjectRelatedField + +from ..models import ConnectionToken __all__ = [ 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer', @@ -125,13 +127,20 @@ class ConnectionTokenGatewaySerializer(serializers.ModelSerializer): ] -class ConnectionTokenACLCmdGroupSerializer(serializers.ModelSerializer): - """ ACL command group""" +class ConnectionTokenACLSerializer(serializers.ModelSerializer): + command_groups = ObjectRelatedField( + many=True, required=False, queryset=CommandGroup.objects, + attrs=('id', 'name', 'type', 'content', 'ignore_case', 'pattern'), + label=_('Command group') + ) + reviewers = ObjectRelatedField( + many=True, queryset=User.objects, label=_("Reviewers"), required=False + ) class Meta: - model = CommandGroup + model = CommandFilterACL fields = [ - 'id', 'type', 'content', 'ignore_case', 'pattern' + 'id', 'name', 'command_groups', 'action', 'reviewers', 'priority', 'is_active' ] @@ -151,7 +160,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): account = ConnectionTokenAccountSerializer(read_only=True) gateway = ConnectionTokenGatewaySerializer(read_only=True) platform = ConnectionTokenPlatform(read_only=True) - acl_command_groups = ConnectionTokenACLCmdGroupSerializer(read_only=True, many=True) + command_filter_acls = ConnectionTokenACLSerializer(read_only=True, many=True) actions = ActionChoicesField() expire_at = serializers.IntegerField() expire_now = serializers.BooleanField(label=_('Expired now'), write_only=True, default=True) @@ -160,7 +169,7 @@ class ConnectionTokenSecretSerializer(OrgResourceModelSerializerMixin): model = ConnectionToken fields = [ 'id', 'value', 'user', 'asset', 'account', 'platform', - 'acl_command_groups', + 'command_filter_acls', 'protocol', 'gateway', 'actions', 'expire_at', 'expire_now', ] extra_kwargs = { diff --git a/apps/terminal/api/session/command.py b/apps/terminal/api/session/command.py index fec0848a5..53fc0ca8f 100644 --- a/apps/terminal/api/session/command.py +++ b/apps/terminal/api/session/command.py @@ -26,7 +26,7 @@ __all__ = ['CommandViewSet', 'InsecureCommandAlertAPI'] class CommandQueryMixin: command_store = get_command_storage() filterset_fields = [ - "asset", "system_user", "user", "session", + "asset", "account", "user", "session", "risk_level", "input" ] default_days_ago = 5 @@ -56,7 +56,7 @@ class CommandQueryMixin: multi_command_storage = get_multi_command_storage() queryset = multi_command_storage.filter( date_from=date_from, date_to=date_to, - user=q.get("user"), asset=q.get("asset"), system_user=q.get("system_user"), + user=q.get("user"), asset=q.get("asset"), account=q.get("account"), input=q.get("input"), session=q.get("session_id", q.get('session')), risk_level=self.get_query_risk_level(), org_id=self.get_org_id(), ) @@ -91,7 +91,7 @@ class CommandViewSet(JMSBulkModelViewSet): { "user": "admin", "asset": "localhost", - "system_user": "web", + "account": "web", "session": "xxxxxx", "input": "whoami", "output": "d2hvbWFp", # base64.b64encode(s) diff --git a/apps/terminal/backends/command/base.py b/apps/terminal/backends/command/base.py index 4bb85e127..a07c7090a 100644 --- a/apps/terminal/backends/command/base.py +++ b/apps/terminal/backends/command/base.py @@ -15,13 +15,13 @@ class CommandBase(object): @abc.abstractmethod def filter(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None, risk_level=None, org_id=None): pass @abc.abstractmethod def count(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None): pass diff --git a/apps/terminal/backends/command/db.py b/apps/terminal/backends/command/db.py index 8b11569e9..1cdc56bda 100644 --- a/apps/terminal/backends/command/db.py +++ b/apps/terminal/backends/command/db.py @@ -21,7 +21,7 @@ class CommandStore(CommandBase): """ self.model.objects.create( user=command["user"], asset=command["asset"], - system_user=command["system_user"], input=command["input"], + account=command["account"], input=command["input"], output=command["output"], session=command["session"], risk_level=command.get("risk_level", 0), org_id=command["org_id"], timestamp=command["timestamp"] @@ -36,7 +36,7 @@ class CommandStore(CommandBase): cmd_input = pretty_string(c['input']) cmd_output = pretty_string(c['output'], max_length=1024) _commands.append(self.model( - user=c["user"], asset=c["asset"], system_user=c["system_user"], + user=c["user"], asset=c["asset"], account=c["account"], input=cmd_input, output=cmd_output, session=c["session"], risk_level=c.get("risk_level", 0), org_id=c["org_id"], timestamp=c["timestamp"] @@ -64,7 +64,7 @@ class CommandStore(CommandBase): @staticmethod def make_filter_kwargs( date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None, risk_level=None, org_id=None): filter_kwargs = {} date_from_default = timezone.now() - datetime.timedelta(days=7) @@ -87,8 +87,8 @@ class CommandStore(CommandBase): filter_kwargs["user__startswith"] = user if asset: filter_kwargs['asset'] = asset - if system_user: - filter_kwargs['system_user'] = system_user + if account: + filter_kwargs['account'] = account if input: filter_kwargs['input__icontains'] = input if session: @@ -100,22 +100,22 @@ class CommandStore(CommandBase): return filter_kwargs def filter(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None, risk_level=None, org_id=None): filter_kwargs = self.make_filter_kwargs( date_from=date_from, date_to=date_to, user=user, - asset=asset, system_user=system_user, input=input, + asset=asset, account=account, input=input, session=session, risk_level=risk_level, org_id=org_id, ) queryset = self.model.objects.filter(**filter_kwargs) return queryset def count(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, + user=None, asset=None, account=None, input=None, session=None): filter_kwargs = self.make_filter_kwargs( date_from=date_from, date_to=date_to, user=user, - asset=asset, system_user=system_user, input=input, + asset=asset, account=account, input=input, session=session, ) count = self.model.objects.filter(**filter_kwargs).count() diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 20602769c..ea73cf1b6 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -49,7 +49,7 @@ class CommandStore(object): self.es = Elasticsearch(hosts=hosts, max_retries=0, **kwargs) self.exact_fields = set() - self.match_fields = {'input', 'risk_level', 'user', 'asset', 'system_user'} + self.match_fields = {'input', 'risk_level', 'user', 'asset', 'account'} may_exact_fields = {'session', 'org_id'} if self.is_new_index_type(): @@ -142,7 +142,7 @@ class CommandStore(object): def make_data(command): data = dict( user=command["user"], asset=command["asset"], - system_user=command["system_user"], input=command["input"], + account=command["account"], input=command["input"], output=command["output"], risk_level=command["risk_level"], session=command["session"], timestamp=command["timestamp"], org_id=command["org_id"] diff --git a/apps/terminal/backends/command/models.py b/apps/terminal/backends/command/models.py index d6eb7a458..bf9c56f81 100644 --- a/apps/terminal/backends/command/models.py +++ b/apps/terminal/backends/command/models.py @@ -19,7 +19,7 @@ class AbstractSessionCommand(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=64, db_index=True, verbose_name=_("User")) asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset")) - system_user = models.CharField(max_length=64, db_index=True, verbose_name=_("System user")) + account = models.CharField(max_length=64, db_index=True, verbose_name=_("Account")) input = models.CharField(max_length=128, db_index=True, verbose_name=_("Input")) output = models.CharField(max_length=1024, blank=True, verbose_name=_("Output")) session = models.CharField(max_length=36, db_index=True, verbose_name=_("Session")) diff --git a/apps/terminal/backends/command/serializers.py b/apps/terminal/backends/command/serializers.py index 83b9d55d2..ccf6984b8 100644 --- a/apps/terminal/backends/command/serializers.py +++ b/apps/terminal/backends/command/serializers.py @@ -33,7 +33,8 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer): """使用这个类作为基础Command Log Serializer类, 用来序列化""" id = serializers.UUIDField(read_only=True) - system_user = serializers.CharField(label=_("System user")) # 限制 64 字符,不能直接迁移成 128 字符,命令表数据量会比较大 + # 限制 64 字符,不能直接迁移成 128 字符,命令表数据量会比较大 + account = serializers.CharField(label=_("Account ")) output = serializers.CharField(max_length=2048, allow_blank=True, label=_("Output")) risk_level_display = serializers.SerializerMethodField(label=_('Risk level display')) timestamp = serializers.IntegerField(label=_('Timestamp')) @@ -45,7 +46,7 @@ class SessionCommandSerializer(SimpleSessionCommandSerializer): risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES) return risk_mapper.get(obj.risk_level) - def validate_system_user(self, value): + def validate_account(self, value): if len(value) > 64: value = pretty_string(value, 64) return value diff --git a/apps/terminal/filters.py b/apps/terminal/filters.py index 79f4048fe..a0287b5bd 100644 --- a/apps/terminal/filters.py +++ b/apps/terminal/filters.py @@ -16,7 +16,7 @@ class CommandFilter(filters.FilterSet): class Meta: model = Command fields = [ - 'asset', 'system_user', 'user', 'session', 'risk_level', 'input', + 'asset', 'account', 'user', 'session', 'risk_level', 'input', 'date_from', 'date_to', 'session_id', 'risk_level', 'command_storage_id', ] @@ -49,14 +49,14 @@ class CommandFilter(filters.FilterSet): class CommandFilterForStorageTree(CommandFilter): asset = filters.CharFilter(method='do_nothing') - system_user = filters.CharFilter(method='do_nothing') + account = filters.CharFilter(method='do_nothing') session = filters.CharFilter(method='do_nothing') risk_level = filters.NumberFilter(method='do_nothing') class Meta: model = CommandStorage fields = [ - 'asset', 'system_user', 'user', 'session', 'risk_level', 'input', + 'asset', 'account', 'user', 'session', 'risk_level', 'input', 'date_from', 'date_to', 'session_id', 'risk_level', 'command_storage_id', ] diff --git a/apps/terminal/migrations/0061_rename_system_user_command_account.py b/apps/terminal/migrations/0061_rename_system_user_command_account.py new file mode 100644 index 000000000..ab7ee1f32 --- /dev/null +++ b/apps/terminal/migrations/0061_rename_system_user_command_account.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.14 on 2022-12-05 05:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0060_alter_applethostdeployment_options'), + ] + + operations = [ + migrations.RenameField( + model_name='command', + old_name='system_user', + new_name='account', + ), + migrations.AlterField( + model_name='command', + name='account', + field=models.CharField(db_index=True, max_length=64, verbose_name='Account'), + ), + ] diff --git a/apps/terminal/models/session/command.py b/apps/terminal/models/session/command.py index c940e855b..3d377523b 100644 --- a/apps/terminal/models/session/command.py +++ b/apps/terminal/models/session/command.py @@ -33,7 +33,7 @@ class Command(AbstractSessionCommand): cls(**{ 'user': random_string(6), 'asset': random_string(10), - 'system_user': random_string(6), + 'account': random_string(6), 'session': str(uuid.uuid4()), 'input': random_string(16), 'output': random_string(64), From 80e550b71acc3a694c6d7661ce66cd46488402c2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 5 Dec 2022 13:37:37 +0800 Subject: [PATCH 488/488] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20supertoken?= =?UTF-8?q?=20=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/serializers/connection_token.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/authentication/serializers/connection_token.py b/apps/authentication/serializers/connection_token.py index b627db320..46d3f453a 100644 --- a/apps/authentication/serializers/connection_token.py +++ b/apps/authentication/serializers/connection_token.py @@ -31,18 +31,15 @@ class ConnectionTokenSerializer(OrgResourceModelSerializerMixin): 'value': {'read_only': True}, } - def get_request_user(self): + def get_user(self, attrs): request = self.context.get('request') user = request.user if request else None return user - def get_user(self, attrs): - return self.get_request_user() - class SuperConnectionTokenSerializer(ConnectionTokenSerializer): class Meta(ConnectionTokenSerializer.Meta): - pass + read_only_fields = list(set(ConnectionTokenSerializer.Meta.read_only_fields) - {'user'}) def get_user(self, attrs): - return attrs.get('user') or self.get_request_user() + return attrs.get('user')