diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py index 8b28bcc37..4fc06807e 100644 --- a/apps/applications/api/account.py +++ b/apps/applications/api/account.py @@ -44,11 +44,7 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet): permission_classes = (IsOrgAdmin,) def get_queryset(self): - queryset = Account.objects.all() \ - .annotate(type=F('app__type')) \ - .annotate(app_display=F('app__name')) \ - .annotate(systemuser_display=F('systemuser__name')) \ - .annotate(category=F('app__category')) + queryset = Account.get_queryset() return queryset diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py index ff514590a..f95b0d0f5 100644 --- a/apps/applications/models/account.py +++ b/apps/applications/models/account.py @@ -1,5 +1,6 @@ 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 @@ -7,8 +8,12 @@ from assets.models.base import BaseUser class Account(BaseUser): - app = models.ForeignKey('applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database')) - systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) + app = models.ForeignKey( + 'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database') + ) + 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() @@ -60,6 +65,10 @@ class Account(BaseUser): def type(self): return self.app.type + @lazyproperty + def attrs(self): + return self.app.attrs + @lazyproperty def app_display(self): return self.systemuser.name @@ -84,5 +93,14 @@ class Account(BaseUser): 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 diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 2d2273dc7..b1827b49a 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -1,6 +1,5 @@ # coding: utf-8 # - from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ @@ -9,7 +8,8 @@ from assets.serializers.base import AuthSerializerMixin from common.drf.serializers import MethodSerializer from .attrs import ( category_serializer_classes_mapping, - type_serializer_classes_mapping + type_serializer_classes_mapping, + type_secret_serializer_classes_mapping ) from .. import models from .. import const @@ -23,17 +23,28 @@ __all__ = [ 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) - if isinstance(self.instance, models.Application): - _type = self.instance.type - _category = self.instance.category + 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: - serializer_class = type_serializer_classes_mapping.get(_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: @@ -84,11 +95,13 @@ class MiniAppSerializer(serializers.ModelSerializer): fields = AppSerializer.Meta.fields_mini -class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +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) @@ -96,10 +109,11 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class Meta: model = models.Account fields_mini = ['id', 'username', 'version'] - fields_write_only = ['password', 'private_key', 'passphrase'] + 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 + [ - 'type', 'type_display', 'category', 'category_display', + fields = fields_mini + fields_fk + fields_write_only + fields_other + [ + 'type', 'type_display', 'category', 'category_display', 'attrs' ] extra_kwargs = { 'username': {'default': '', 'required': False}, @@ -112,6 +126,14 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): '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) @@ -131,6 +153,10 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AppAccountSecretSerializer(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}, diff --git a/apps/applications/serializers/attrs/application_category/cloud.py b/apps/applications/serializers/attrs/application_category/cloud.py index f5fa71810..64306b24e 100644 --- a/apps/applications/serializers/attrs/application_category/cloud.py +++ b/apps/applications/serializers/attrs/application_category/cloud.py @@ -1,7 +1,6 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ - __all__ = ['CloudSerializer'] diff --git a/apps/applications/serializers/attrs/application_category/remote_app.py b/apps/applications/serializers/attrs/application_category/remote_app.py index bace9173b..ad2610791 100644 --- a/apps/applications/serializers/attrs/application_category/remote_app.py +++ b/apps/applications/serializers/attrs/application_category/remote_app.py @@ -10,7 +10,6 @@ from assets.models import Asset logger = get_logger(__file__) - __all__ = ['RemoteAppSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/chrome.py b/apps/applications/serializers/attrs/application_type/chrome.py index 5b9e5147a..96b4587e7 100644 --- a/apps/applications/serializers/attrs/application_type/chrome.py +++ b/apps/applications/serializers/attrs/application_type/chrome.py @@ -3,8 +3,7 @@ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['ChromeSerializer'] +__all__ = ['ChromeSerializer', 'ChromeSecretSerializer'] class ChromeSerializer(RemoteAppSerializer): @@ -17,10 +16,16 @@ class ChromeSerializer(RemoteAppSerializer): 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=_('Username'), allow_null=True, + max_length=128, allow_blank=True, required=False, label=_('Chrome username'), allow_null=True, ) chrome_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Chrome password'), allow_null=True ) + +class ChromeSecretSerializer(ChromeSerializer): + chrome_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, label=_('Chrome password'), + allow_null=True + ) diff --git a/apps/applications/serializers/attrs/application_type/custom.py b/apps/applications/serializers/attrs/application_type/custom.py index 0cbc09cf9..0a58c28b1 100644 --- a/apps/applications/serializers/attrs/application_type/custom.py +++ b/apps/applications/serializers/attrs/application_type/custom.py @@ -1,11 +1,9 @@ - from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['CustomSerializer'] +__all__ = ['CustomSerializer', 'CustomSecretSerializer'] class CustomSerializer(RemoteAppSerializer): @@ -18,10 +16,17 @@ class CustomSerializer(RemoteAppSerializer): allow_null=True, ) custom_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Username'), + max_length=128, allow_blank=True, required=False, label=_('Custom Username'), allow_null=True, ) custom_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Custom password'), + allow_null=True, + ) + + +class CustomSecretSerializer(RemoteAppSerializer): + custom_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, 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 index ac1e023d3..cccfd67a0 100644 --- a/apps/applications/serializers/attrs/application_type/k8s.py +++ b/apps/applications/serializers/attrs/application_type/k8s.py @@ -1,6 +1,5 @@ from ..application_category import CloudSerializer - __all__ = ['K8SSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/mariadb.py b/apps/applications/serializers/attrs/application_type/mariadb.py index d2bcb546f..e5693e429 100644 --- a/apps/applications/serializers/attrs/application_type/mariadb.py +++ b/apps/applications/serializers/attrs/application_type/mariadb.py @@ -1,6 +1,5 @@ from .mysql import MySQLSerializer - __all__ = ['MariaDBSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/mysql.py b/apps/applications/serializers/attrs/application_type/mysql.py index bb4e56c33..78f312ebe 100644 --- a/apps/applications/serializers/attrs/application_type/mysql.py +++ b/apps/applications/serializers/attrs/application_type/mysql.py @@ -3,13 +3,9 @@ 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 index 8025ec80f..bc41a689b 100644 --- a/apps/applications/serializers/attrs/application_type/mysql_workbench.py +++ b/apps/applications/serializers/attrs/application_type/mysql_workbench.py @@ -3,8 +3,7 @@ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['MySQLWorkbenchSerializer'] +__all__ = ['MySQLWorkbenchSerializer', 'MySQLWorkbenchSecretSerializer'] class MySQLWorkbenchSerializer(RemoteAppSerializer): @@ -27,10 +26,17 @@ class MySQLWorkbenchSerializer(RemoteAppSerializer): allow_null=True, ) mysql_workbench_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Username'), + max_length=128, allow_blank=True, required=False, label=_('Mysql workbench username'), allow_null=True, ) mysql_workbench_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Mysql workbench password'), + allow_null=True, + ) + + +class MySQLWorkbenchSecretSerializer(RemoteAppSerializer): + mysql_workbench_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, 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 index 63b905714..c87c4904d 100644 --- a/apps/applications/serializers/attrs/application_type/oracle.py +++ b/apps/applications/serializers/attrs/application_type/oracle.py @@ -3,10 +3,8 @@ 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 index 1b3cc2ef4..0434a0423 100644 --- a/apps/applications/serializers/attrs/application_type/pgsql.py +++ b/apps/applications/serializers/attrs/application_type/pgsql.py @@ -3,10 +3,8 @@ 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 index 3ba9ea715..06cd1ae3b 100644 --- a/apps/applications/serializers/attrs/application_type/redis.py +++ b/apps/applications/serializers/attrs/application_type/redis.py @@ -3,13 +3,9 @@ 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 index af8246bd0..5f9b5d2bf 100644 --- a/apps/applications/serializers/attrs/application_type/sqlserver.py +++ b/apps/applications/serializers/attrs/application_type/sqlserver.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from ..application_category import DBSerializer - __all__ = ['SQLServerSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/vmware_client.py b/apps/applications/serializers/attrs/application_type/vmware_client.py index 1ed44e656..6ec3975cb 100644 --- a/apps/applications/serializers/attrs/application_type/vmware_client.py +++ b/apps/applications/serializers/attrs/application_type/vmware_client.py @@ -3,8 +3,7 @@ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['VMwareClientSerializer'] +__all__ = ['VMwareClientSerializer', 'VMwareClientSecretSerializer'] class VMwareClientSerializer(RemoteAppSerializer): @@ -23,10 +22,17 @@ class VMwareClientSerializer(RemoteAppSerializer): allow_null=True ) vmware_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Username'), + max_length=128, allow_blank=True, required=False, label=_('Vmware username'), allow_null=True ) vmware_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Vmware password'), + allow_null=True + ) + + +class VMwareClientSecretSerializer(RemoteAppSerializer): + vmware_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, label=_('Vmware password'), allow_null=True ) diff --git a/apps/applications/serializers/attrs/attrs.py b/apps/applications/serializers/attrs/attrs.py index f249d7816..a1ca0a3da 100644 --- a/apps/applications/serializers/attrs/attrs.py +++ b/apps/applications/serializers/attrs/attrs.py @@ -1,15 +1,15 @@ -from rest_framework import serializers +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` # --------------------------------------------------- @@ -30,15 +30,32 @@ type_serializer_classes_mapping = { const.AppType.oracle.value: application_type.OracleSerializer, const.AppType.pgsql.value: application_type.PostgreSerializer, const.AppType.sqlserver.value: application_type.SQLServerSerializer, - # 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, # 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/assets/api/accounts.py b/apps/assets/api/accounts.py index e05bee3d2..0c4e01ff2 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -64,9 +64,7 @@ class AccountViewSet(OrgBulkModelViewSet): permission_classes = (IsOrgAdmin,) def get_queryset(self): - queryset = super().get_queryset() \ - .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) + queryset = AuthBook.get_queryset() return queryset @action(methods=['post'], detail=True, url_path='verify') diff --git a/apps/assets/api/backup.py b/apps/assets/api/backup.py index cdd26b406..4c0aaf18f 100644 --- a/apps/assets/api/backup.py +++ b/apps/assets/api/backup.py @@ -32,8 +32,8 @@ class AccountBackupPlanExecutionViewSet( mixins.RetrieveModelMixin, viewsets.GenericViewSet ): serializer_class = serializers.AccountBackupPlanExecutionSerializer - search_fields = ('trigger', 'plan_id') - filterset_fields = search_fields + search_fields = ('trigger',) + filterset_fields = ('trigger', 'plan_id') permission_classes = (IsOrgAdmin,) def get_queryset(self): diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index 2707583da..c489ab608 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -2,6 +2,7 @@ # 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 @@ -116,6 +117,15 @@ class AuthBook(BaseUser, AbsConnectivity): 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/serializers/account.py b/apps/assets/serializers/account.py index 2f80ab936..5de57474b 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -11,10 +11,18 @@ from .utils import validate_password_contains_left_double_curly_bracket 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 + ) + date_updated = serializers.DateTimeField( + label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True + ) class Meta: model = AuthBook - fields_mini = ['id', 'username', 'ip', 'hostname', 'version'] + fields_mini = ['id', 'username', 'ip', 'hostname', 'platform', 'protocols', '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 @@ -32,6 +40,9 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): } ref_name = 'AssetAccountSerializer' + def get_protocols(self, v): + return v.protocols.replace(' ', ', ') + @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ @@ -45,6 +56,10 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AccountSecretSerializer(AccountSerializer): class Meta(AccountSerializer.Meta): + fields_backup = [ + 'hostname', 'ip', '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/asset.py b/apps/assets/serializers/asset.py index de95e3b80..427d0e470 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -5,8 +5,6 @@ from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from users.models import User, UserGroup -from perms.models import AssetPermission from ..models import Asset, Node, Platform, SystemUser __all__ = [ diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index c1b62a730..91bae54d1 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -1,15 +1,18 @@ import os import time import pandas as pd -from collections import defaultdict +from collections import defaultdict, OrderedDict from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers -from assets.models import AuthBook, Asset, BaseUser, ProtocolsMixin +from assets.models import AuthBook +from assets.serializers import AccountSecretSerializer from assets.notifications import AccountBackupExecutionTaskMsg -from applications.models import Account, Application +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 @@ -20,7 +23,46 @@ logger = get_logger(__file__) PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') -class AssetAccountHandler: +class BaseAccountHandler: + @classmethod + def unpack_data(cls, serializer_data, data=None): + if data is None: + data = {} + for k, v in serializer_data.items(): + if isinstance(v, OrderedDict): + cls.unpack_data(v, data) + else: + data[k] = v + return data + + @classmethod + def get_header_fields(cls, serializer: serializers.Serializer): + try: + backup_fields = getattr(serializer, 'Meta').fields_backup + except AttributeError: + backup_fields = serializer.fields.keys() + header_fields = {} + for field in backup_fields: + v = serializer.fields[field] + if isinstance(v, serializers.Serializer): + _fields = cls.get_header_fields(v) + header_fields.update(_fields) + else: + header_fields[field] = v.label + return header_fields + + @classmethod + def create_row(cls, account, serializer_cls): + serializer = serializer_cls(account) + data = cls.unpack_data(serializer.data) + header_fields = cls.get_header_fields(serializer) + row_dict = {} + for field, header_name in header_fields.items(): + row_dict[header_name] = data[field] + return row_dict + + +class AssetAccountHandler(BaseAccountHandler): @staticmethod def get_filename(plan_name): filename = os.path.join( @@ -28,32 +70,24 @@ class AssetAccountHandler: ) return filename - @staticmethod - def create_df(): + @classmethod + def create_df(cls): df_dict = defaultdict(list) - label_key = AuthBook._meta.verbose_name - accounts = AuthBook.objects.all().prefetch_related('systemuser', 'asset') + sheet_name = AuthBook._meta.verbose_name + accounts = AuthBook.get_queryset() for account in accounts: account.load_auth() - protocol = account.asset.protocol - protocol_label = getattr(ProtocolsMixin.Protocol, protocol).label - row = { - getattr(Asset, 'hostname').field.verbose_name: account.asset.hostname, - getattr(Asset, 'ip').field.verbose_name: account.asset.ip, - } - secret_row = AccountBackupHandler.create_secret_row(account) - row.update(secret_row) - row.update({ - getattr(Asset, 'protocol').field.verbose_name: protocol_label, - getattr(AuthBook, 'version').field.verbose_name: account.version - }) - df_dict[label_key].append(row) + row = cls.create_row(account, AccountSecretSerializer) + df_dict[sheet_name].append(row) + for k, v in df_dict.items(): df_dict[k] = pd.DataFrame(v) + + logger.info('\n\033[33m- 共收集{}条资产账号\033[0m'.format(accounts.count())) return df_dict -class AppAccountHandler: +class AppAccountHandler(BaseAccountHandler): @staticmethod def get_filename(plan_name): filename = os.path.join( @@ -61,33 +95,23 @@ class AppAccountHandler: ) return filename - @staticmethod - def create_df(): + @classmethod + def create_df(cls): df_dict = defaultdict(list) - accounts = Account.objects.all().prefetch_related('systemuser', 'app') + accounts = Account.get_queryset() for account in accounts: account.load_auth() - app_type = account.app.type - if app_type == 'postgresql': - label_key = getattr(AppType, 'pgsql').label - else: - label_key = getattr(AppType, app_type).label - row = { - getattr(Application, 'name').field.verbose_name: account.app.name, - getattr(Application, 'attrs').field.verbose_name: account.app.attrs - } - secret_row = AccountBackupHandler.create_secret_row(account) - row.update(secret_row) - row.update({ - getattr(Account, 'version').field.verbose_name: account.version - }) - df_dict[label_key].append(row) + app_type = account.type + sheet_name = AppType.get_label(app_type) + row = cls.create_row(account, AppAccountSecretSerializer) + df_dict[sheet_name].append(row) for k, v in df_dict.items(): df_dict[k] = pd.DataFrame(v) + logger.info('\n\033[33m- 共收集{}条应用账号\033[0m'.format(accounts.count())) return df_dict -HANDLER_MAP = { +handler_map = { 'asset': AssetAccountHandler, 'application': AppAccountHandler } @@ -102,31 +126,37 @@ class AccountBackupHandler: def create_excel(self): logger.info( '\n' - '\033[32m>>> 正在生成资产及应用相关备份信息文件\033[0m' + '\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m' '' ) # Print task start date time_start = time.time() - info = {} + files = [] for account_type in self.execution.types: - if account_type in HANDLER_MAP: - account_handler = HANDLER_MAP[account_type] - df = account_handler.create_df() - filename = account_handler.get_filename(self.plan_name) - info[filename] = df - for filename, df_dict in info.items(): + handler = handler_map.get(account_type) + if not handler: + continue + + df_dict = handler.create_df() + if not df_dict: + continue + + filename = handler.get_filename(self.plan_name) with pd.ExcelWriter(filename) as w: for sheet, df in df_dict.items(): sheet = sheet.replace(' ', '-') getattr(df, 'to_excel')(w, sheet_name=sheet, index=False) + files.append(filename) timedelta = round((time.time() - time_start), 2) logger.info('步骤完成: 用时 {}s'.format(timedelta)) - return list(info.keys()) + return files def send_backup_mail(self, files): recipients = self.execution.plan_snapshot.get('recipients') if not recipients: return + if not files: + return recipients = User.objects.filter(id__in=list(recipients)) logger.info( '\n' @@ -191,13 +221,3 @@ class AccountBackupHandler: logger.info('\n任务结束: {}'.format(local_now_display())) timedelta = round((time.time() - time_start), 2) logger.info('用时: {}'.format(timedelta)) - - @staticmethod - def create_secret_row(instance): - row = { - getattr(BaseUser, 'username').field.verbose_name: instance.username, - getattr(BaseUser, 'password').field.verbose_name: instance.password, - getattr(BaseUser, 'private_key').field.verbose_name: instance.private_key, - getattr(BaseUser, 'public_key').field.verbose_name: instance.public_key - } - return row diff --git a/apps/assets/tasks/account_connectivity.py b/apps/assets/tasks/account_connectivity.py index 4327c0ffe..197979055 100644 --- a/apps/assets/tasks/account_connectivity.py +++ b/apps/assets/tasks/account_connectivity.py @@ -1,7 +1,7 @@ # ~*~ coding: utf-8 ~*~ from celery import shared_task -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, gettext_noop from common.utils import get_logger from orgs.utils import org_aware_func @@ -104,6 +104,6 @@ def test_accounts_connectivity_manual(accounts): :param accounts: 对象 """ for account in accounts: - task_name = _("Test account connectivity: {}").format(account) + task_name = gettext_noop("Test account connectivity: ") + str(account) test_account_connectivity_util(account, task_name) print(".\n") diff --git a/apps/assets/tasks/asset_connectivity.py b/apps/assets/tasks/asset_connectivity.py index 1ae98fd1d..0038c38d3 100644 --- a/apps/assets/tasks/asset_connectivity.py +++ b/apps/assets/tasks/asset_connectivity.py @@ -2,7 +2,7 @@ from itertools import groupby from collections import defaultdict from celery import shared_task -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_noop from common.utils import get_logger from orgs.utils import org_aware_func @@ -46,7 +46,7 @@ def test_asset_connectivity_util(assets, task_name=None): from ops.utils import update_or_create_ansible_task if task_name is None: - task_name = _("Test assets connectivity") + task_name = gettext_noop("Test assets connectivity. ") hosts = clean_ansible_task_hosts(assets) if not hosts: @@ -88,7 +88,7 @@ def test_asset_connectivity_util(assets, task_name=None): @shared_task(queue="ansible") def test_asset_connectivity_manual(asset): - task_name = _("Test assets connectivity: {}").format(asset) + task_name = gettext_noop("Test assets connectivity: ") + str(asset) summary = test_asset_connectivity_util([asset], task_name=task_name) if summary.get('dark'): @@ -99,7 +99,7 @@ def test_asset_connectivity_manual(asset): @shared_task(queue="ansible") def test_assets_connectivity_manual(assets): - task_name = _("Test assets connectivity: {}").format([asset.hostname for asset in assets]) + task_name = gettext_noop("Test assets connectivity: ") + str([asset.hostname for asset in assets]) summary = test_asset_connectivity_util(assets, task_name=task_name) if summary.get('dark'): @@ -110,8 +110,7 @@ def test_assets_connectivity_manual(assets): @shared_task(queue="ansible") def test_node_assets_connectivity_manual(node): - task_name = _("Test if the assets under the node are connectable: {}".format(node.name)) + 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) return result - diff --git a/apps/assets/tasks/gather_asset_hardware_info.py b/apps/assets/tasks/gather_asset_hardware_info.py index daad3d694..ae8107cb2 100644 --- a/apps/assets/tasks/gather_asset_hardware_info.py +++ b/apps/assets/tasks/gather_asset_hardware_info.py @@ -4,7 +4,7 @@ import json import re from celery import shared_task -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, gettext_noop from common.utils import ( capacity_convert, sum_capacity, get_logger @@ -94,7 +94,7 @@ def update_assets_hardware_info_util(assets, task_name=None): """ from ops.utils import update_or_create_ansible_task if task_name is None: - task_name = _("Update some assets hardware info") + task_name = gettext_noop("Update some assets hardware info. ") tasks = const.UPDATE_ASSETS_HARDWARE_TASKS hosts = clean_ansible_task_hosts(assets) if not hosts: @@ -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 = _("Update asset hardware info: {}").format(asset.hostname) + task_name = gettext_noop("Update asset hardware info: ") + str(asset.hostname) update_assets_hardware_info_util([asset], task_name=task_name) @shared_task(queue="ansible") def update_assets_hardware_info_manual(assets): - task_name = _("Update assets hardware info: {}").format([asset.hostname for asset in assets]) + task_name = gettext_noop("Update assets hardware info: ") + str([asset.hostname for asset in assets]) update_assets_hardware_info_util(assets, task_name=task_name) @@ -134,7 +134,7 @@ def update_assets_hardware_info_period(): @shared_task(queue="ansible") def update_node_assets_hardware_info_manual(node): - task_name = _("Update node asset hardware information: {}").format(node.name) + 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) return result diff --git a/apps/assets/tasks/gather_asset_users.py b/apps/assets/tasks/gather_asset_users.py index 6ff2993d7..abc69997a 100644 --- a/apps/assets/tasks/gather_asset_users.py +++ b/apps/assets/tasks/gather_asset_users.py @@ -4,7 +4,7 @@ import re from collections import defaultdict from celery import shared_task -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_noop from django.utils import timezone from orgs.utils import tmp_to_org, org_aware_func @@ -108,7 +108,7 @@ def add_asset_users(assets, results): def gather_asset_users(assets, task_name=None): from ops.utils import update_or_create_ansible_task if task_name is None: - task_name = _("Gather assets users") + task_name = gettext_noop("Gather assets users") assets = clean_ansible_task_hosts(assets) if not assets: return diff --git a/apps/assets/tasks/push_system_user.py b/apps/assets/tasks/push_system_user.py index 2be740eca..75e3dc019 100644 --- a/apps/assets/tasks/push_system_user.py +++ b/apps/assets/tasks/push_system_user.py @@ -3,7 +3,7 @@ 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 _ +from django.utils.translation import ugettext as _, gettext_noop from django.db.models import Empty, Q from common.utils import encrypt_password, get_logger @@ -279,7 +279,7 @@ 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 = _("Push system users to assets: {}").format(system_user.name) + 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) @@ -291,7 +291,7 @@ def push_system_user_a_asset_manual(system_user, asset, username=None): """ # if username is None: # username = system_user.username - task_name = _("Push system users to asset: {}({}) => {}").format( + task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format( system_user.name, username, asset ) return push_system_user_util(system_user, [asset], task_name=task_name, username=username) @@ -312,7 +312,7 @@ 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 = _("Push system users to assets: {}").format(system_user.name) + task_name = gettext_noop("Push system users to assets: ") + system_user.name return push_system_user_util(system_user, assets, task_name, username=username) diff --git a/apps/assets/tasks/system_user_connectivity.py b/apps/assets/tasks/system_user_connectivity.py index d7252fa72..893081163 100644 --- a/apps/assets/tasks/system_user_connectivity.py +++ b/apps/assets/tasks/system_user_connectivity.py @@ -3,7 +3,7 @@ from itertools import groupby from collections import defaultdict from celery import shared_task -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, gettext_noop from assets.models import Asset from common.utils import get_logger @@ -115,7 +115,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name): @shared_task(queue="ansible") @org_aware_func("system_user") def test_system_user_connectivity_manual(system_user, asset_ids=None): - task_name = _("Test system user connectivity: {}").format(system_user) + task_name = gettext_noop("Test system user connectivity: ") + str(system_user) if asset_ids: assets = Asset.objects.filter(id__in=asset_ids) else: @@ -126,7 +126,7 @@ def test_system_user_connectivity_manual(system_user, asset_ids=None): @shared_task(queue="ansible") @org_aware_func("system_user") def test_system_user_connectivity_a_asset(system_user, asset): - task_name = _("Test system user connectivity: {} => {}").format( + task_name = gettext_noop("Test system user connectivity: ") + "{} => {}".format( system_user, asset ) test_system_user_connectivity_util(system_user, [asset], task_name) @@ -145,7 +145,7 @@ def test_system_user_connectivity_period(): return queryset_map = SystemUser.objects.all_group_by_org() for org, system_user in queryset_map.items(): - task_name = _("Test system user connectivity period: {}").format(system_user) + 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/authentication/api/mfa.py b/apps/authentication/api/mfa.py index 52795c630..950152def 100644 --- a/apps/authentication/api/mfa.py +++ b/apps/authentication/api/mfa.py @@ -12,6 +12,7 @@ from rest_framework.response import Response from common.permissions import IsValidUser, NeedMFAVerify from common.utils import get_logger +from common.exceptions import UnexpectError from users.models.user import User from ..serializers import OtpVerifySerializer from .. import serializers @@ -35,30 +36,45 @@ class MFASendCodeApi(AuthMixin, CreateAPIView): """ permission_classes = (AllowAny,) serializer_class = serializers.MFASelectTypeSerializer + username = '' + ip = '' + + def get_user_from_db(self, username): + try: + user = get_object_or_404(User, username=username) + return user + except Exception as e: + self.incr_mfa_failed_time(username, self.ip) + raise e + + def get_user_from_db(self, username): + """避免暴力测试用户名""" + ip = self.get_request_ip() + self.check_mfa_is_block(username, ip) + try: + user = get_object_or_404(User, username=username) + return user + except Exception as e: + self.incr_mfa_failed_time(username, ip) + raise e def perform_create(self, serializer): username = serializer.validated_data.get('username', '') mfa_type = serializer.validated_data['type'] + if not username: user = self.get_user_from_session() else: - user = get_object_or_404(User, username=username) + user = self.get_user_from_db(username) mfa_backend = user.get_active_mfa_backend_by_type(mfa_type) if not mfa_backend or not mfa_backend.challenge_required: - raise ValidationError('MFA type not support: {} {}'.format(mfa_type, mfa_backend)) - mfa_backend.send_challenge() - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - + error = _('Current user not support mfa type: {}').format(mfa_type) + raise ValidationError({'error': error}) try: - self.perform_create(serializer) - return Response(serializer.data, status=201) + mfa_backend.send_challenge() except Exception as e: - logger.exception(e) - return Response({'error': str(e)}, status=400) + raise UnexpectError(str(e)) class MFAChallengeVerifyApi(AuthMixin, CreateAPIView): diff --git a/apps/authentication/backends/saml2/views.py b/apps/authentication/backends/saml2/views.py index 4ce812fe8..6c7fb5cb8 100644 --- a/apps/authentication/backends/saml2/views.py +++ b/apps/authentication/backends/saml2/views.py @@ -76,11 +76,10 @@ class PrepareRequestMixin: @staticmethod def get_attribute_consuming_service(): attr_mapping = settings.SAML2_RENAME_ATTRIBUTES - name_prefix = settings.SITE_URL if attr_mapping and isinstance(attr_mapping, dict): attr_list = [ { - "name": '{}/{}'.format(name_prefix, sp_key), + "name": sp_key, "friendlyName": idp_key, "isRequired": True } for idp_key, sp_key in attr_mapping.items() @@ -168,12 +167,10 @@ class PrepareRequestMixin: def get_attributes(self, saml_instance): user_attrs = {} - real_key_index = len(settings.SITE_URL) + 1 attrs = saml_instance.get_attributes() valid_attrs = ['username', 'name', 'email', 'comment', 'phone'] for attr, value in attrs.items(): - attr = attr[real_key_index:] if attr not in valid_attrs: continue user_attrs[attr] = self.value_to_str(value) diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 2167ab198..c74b1a5ff 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -335,6 +335,11 @@ class MFAMixin: mfa_backends = User.get_user_mfa_backends(user) return {'mfa_backends': mfa_backends} + @staticmethod + def incr_mfa_failed_time(username, ip): + util = MFABlockUtils(username, ip) + util.incr_failed_count() + class AuthPostCheckMixin: @classmethod @@ -450,7 +455,10 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost ) if not user: self.raise_credential_error(errors.reason_password_failed) - elif user.is_expired: + + self.request.session['auth_backend'] = getattr(user, 'backend', settings.AUTH_BACKEND_MODEL) + + if user.is_expired: self.raise_credential_error(errors.reason_user_expired) elif not user.is_active: self.raise_credential_error(errors.reason_user_inactive) diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 6e053b877..b74217cee 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -109,7 +109,7 @@ } .select-con { - width: 30%; + width: 35%; } .mfa-div { diff --git a/apps/common/db/utils.py b/apps/common/db/utils.py index eb6328a9f..23384f2c8 100644 --- a/apps/common/db/utils.py +++ b/apps/common/db/utils.py @@ -44,6 +44,7 @@ def get_objects(model, pks): return objs +# 复制 django.db.close_old_connections, 因为它没有导出,ide 提示有问题 def close_old_connections(): for conn in connections.all(): conn.close_if_unusable_or_obsolete() diff --git a/apps/common/exceptions.py b/apps/common/exceptions.py index 9d3008c50..067750ec6 100644 --- a/apps/common/exceptions.py +++ b/apps/common/exceptions.py @@ -45,3 +45,9 @@ class MFAVerifyRequired(JMSException): status_code = status.HTTP_400_BAD_REQUEST default_code = 'mfa_verify_required' default_detail = _('This action require verify your MFA') + + +class UnexpectError(JMSException): + status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + default_code = 'unexpect_error' + default_detail = _('Unexpect error occur') diff --git a/apps/common/utils/jumpserver.py b/apps/common/utils/jumpserver.py index 402cee610..c9396e1d4 100644 --- a/apps/common/utils/jumpserver.py +++ b/apps/common/utils/jumpserver.py @@ -1,6 +1,6 @@ from django.core.cache import cache -from django.shortcuts import reverse -from django.shortcuts import redirect +from django.shortcuts import reverse, redirect +from django.utils.translation import gettext_noop from .random import random_string diff --git a/apps/jumpserver/utils.py b/apps/jumpserver/utils.py index d19d950e2..7e29b17fd 100644 --- a/apps/jumpserver/utils.py +++ b/apps/jumpserver/utils.py @@ -2,6 +2,7 @@ # from functools import partial from werkzeug.local import LocalProxy +from datetime import datetime from django.conf import settings from common.local import thread_local @@ -26,4 +27,18 @@ def has_valid_xpack_license(): return License.has_valid_license() +def get_xpack_license_info() -> dict: + if has_valid_xpack_license(): + from xpack.plugins.license.models import License + info = License.get_license_detail() + corporation = info.get('corporation', '') + else: + current_year = datetime.now().year + corporation = f'Copyright - FIT2CLOUD 飞致云 © 2014-{current_year}' + info = { + 'corporation': corporation + } + return info + + current_request = LocalProxy(partial(_find, 'current_request')) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 1633ceafe..a97530a02 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:942e981be66e5d0c32efb59583a377503ee3dc285e2794da40c312694c4a9dc2 -size 96378 +oid sha256:a08014e3eed6152aaff1e42758f20666f0e90e1ed4264a9d7cd44e34191d607e +size 96692 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 47e01f362..a8ab01dba 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-01-12 20:51+0800\n" +"POT-Creation-Date: 2022-01-17 19:06+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -123,14 +123,14 @@ msgid "Login acl" msgstr "登录访问控制" #: acls/models/login_asset_acl.py:21 -#: applications/serializers/application.py:108 -#: applications/serializers/application.py:139 +#: applications/serializers/application.py:122 +#: applications/serializers/application.py:165 msgid "System User" msgstr "系统用户" #: acls/models/login_asset_acl.py:22 -#: applications/serializers/attrs/application_category/remote_app.py:37 -#: assets/models/asset.py:356 assets/models/authbook.py:18 +#: applications/serializers/attrs/application_category/remote_app.py:36 +#: assets/models/asset.py:356 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:34 #: assets/models/gathered_user.py:14 assets/serializers/system_user.py:264 #: audits/models.py:38 perms/models/asset_permission.py:24 @@ -157,16 +157,12 @@ 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 -#: applications/serializers/attrs/application_type/chrome.py:20 -#: applications/serializers/attrs/application_type/custom.py:21 -#: applications/serializers/attrs/application_type/mysql_workbench.py:30 -#: applications/serializers/attrs/application_type/vmware_client.py:26 -#: assets/models/base.py:176 assets/models/gathered_user.py:15 -#: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17 +#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 +#: assets/models/gathered_user.py:15 audits/models.py:105 +#: authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 -#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:547 +#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:547 #: users/templates/users/_msg_user_created.html:12 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models/asset.py:34 @@ -185,7 +181,7 @@ msgstr "" "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 +#: applications/serializers/attrs/application_type/mysql_workbench.py:17 #: assets/models/asset.py:211 assets/models/domain.py:61 #: assets/serializers/account.py:12 #: authentication/templates/authentication/_msg_oauth_bind.html:12 @@ -252,9 +248,9 @@ msgstr "时段" msgid "My applications" msgstr "我的应用" -#: applications/const.py:8 applications/models/account.py:10 +#: applications/const.py:8 applications/models/account.py:12 #: applications/serializers/attrs/application_category/db.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:26 +#: applications/serializers/attrs/application_type/mysql_workbench.py:25 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "数据库" @@ -267,7 +263,7 @@ msgstr "远程应用" msgid "Custom" msgstr "自定义" -#: applications/models/account.py:11 assets/models/authbook.py:19 +#: applications/models/account.py:15 assets/models/authbook.py:20 #: assets/models/cmd_filter.py:38 assets/models/user.py:302 audits/models.py:39 #: perms/models/application_permission.py:32 #: perms/models/asset_permission.py:26 templates/_nav.html:45 @@ -284,12 +280,12 @@ msgstr "自定义" msgid "System user" msgstr "系统用户" -#: applications/models/account.py:12 assets/models/authbook.py:20 +#: applications/models/account.py:17 assets/models/authbook.py:21 #: settings/serializers/auth/cas.py:15 msgid "Version" msgstr "版本" -#: applications/models/account.py:18 xpack/plugins/cloud/models.py:82 +#: applications/models/account.py:23 xpack/plugins/cloud/models.py:82 #: xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "账户" @@ -299,7 +295,7 @@ msgid "Applications" msgstr "应用管理" #: applications/models/application.py:204 -#: applications/serializers/application.py:88 assets/models/label.py:21 +#: applications/serializers/application.py:99 assets/models/label.py:21 #: perms/models/application_permission.py:20 #: perms/serializers/application/user_permission.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22 @@ -308,7 +304,7 @@ msgid "Category" msgstr "类别" #: applications/models/application.py:207 -#: applications/serializers/application.py:90 assets/models/backup.py:49 +#: applications/serializers/application.py:101 assets/models/backup.py:49 #: assets/models/cmd_filter.py:76 assets/models/user.py:210 #: perms/models/application_permission.py:23 #: perms/serializers/application/user_permission.py:34 @@ -335,15 +331,15 @@ msgstr "属性" msgid "Application" msgstr "应用程序" -#: applications/serializers/application.py:59 -#: applications/serializers/application.py:89 assets/serializers/label.py:13 +#: applications/serializers/application.py:70 +#: applications/serializers/application.py:100 assets/serializers/label.py:13 #: perms/serializers/application/permission.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26 msgid "Category display" msgstr "类别名称" -#: applications/serializers/application.py:60 -#: applications/serializers/application.py:91 +#: applications/serializers/application.py:71 +#: applications/serializers/application.py:102 #: assets/serializers/system_user.py:27 audits/serializers.py:29 #: perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 @@ -352,43 +348,62 @@ msgstr "类别名称" msgid "Type display" msgstr "类型名称" -#: applications/serializers/application.py:107 -#: applications/serializers/application.py:138 +#: applications/serializers/application.py:103 assets/models/asset.py:231 +#: assets/models/base.py:181 assets/models/cluster.py:26 +#: assets/models/domain.py:27 assets/models/gathered_user.py:19 +#: assets/models/group.py:22 assets/models/label.py:25 +#: assets/serializers/account.py:17 common/db/models.py:113 +#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:29 +#: orgs/models.py:26 orgs/models.py:435 perms/models/base.py:92 +#: users/models/group.py:18 users/models/user.py:783 +#: xpack/plugins/cloud/models.py:122 +msgid "Date created" +msgstr "创建日期" + +#: applications/serializers/application.py:104 assets/models/base.py:182 +#: assets/models/gathered_user.py:20 assets/serializers/account.py:20 +#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:39 +#: orgs/models.py:436 +msgid "Date updated" +msgstr "更新日期" + +#: applications/serializers/application.py:121 +#: applications/serializers/application.py:164 msgid "Application display" msgstr "应用名称" -#: applications/serializers/attrs/application_category/cloud.py:9 +#: 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:146 settings/serializers/auth/radius.py:14 +#: 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/mysql.py:11 -#: applications/serializers/attrs/application_type/mysql_workbench.py:22 -#: applications/serializers/attrs/application_type/oracle.py:11 -#: applications/serializers/attrs/application_type/pgsql.py:11 -#: applications/serializers/attrs/application_type/redis.py:11 -#: applications/serializers/attrs/application_type/sqlserver.py:11 +#: 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:215 assets/models/domain.py:62 #: 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:40 -#: 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 +#: 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 +#: applications/serializers/attrs/application_type/vmware_client.py:17 msgid "Application path" msgstr "应用路径" -#: applications/serializers/attrs/application_category/remote_app.py:45 +#: applications/serializers/attrs/application_category/remote_app.py:44 #: assets/serializers/system_user.py:163 #: xpack/plugins/change_auth_plan/serializers/asset.py:65 #: xpack/plugins/change_auth_plan/serializers/asset.py:68 @@ -398,37 +413,56 @@ msgstr "应用路径" msgid "This field is required." msgstr "该字段是必填项。" -#: applications/serializers/attrs/application_type/chrome.py:17 -#: applications/serializers/attrs/application_type/vmware_client.py:22 +#: applications/serializers/attrs/application_type/chrome.py:16 +#: applications/serializers/attrs/application_type/vmware_client.py:21 msgid "Target URL" msgstr "目标URL" -#: applications/serializers/attrs/application_type/chrome.py:23 -#: applications/serializers/attrs/application_type/custom.py:25 -#: applications/serializers/attrs/application_type/mysql_workbench.py:34 -#: applications/serializers/attrs/application_type/vmware_client.py:30 -#: assets/models/base.py:177 audits/signals_handler.py:65 -#: authentication/forms.py:22 -#: authentication/templates/authentication/login.html:151 -#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 -#: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_update.html:43 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models/base.py:39 -#: xpack/plugins/change_auth_plan/models/base.py:118 -#: xpack/plugins/change_auth_plan/models/base.py:193 -#: xpack/plugins/cloud/serializers/account_attrs.py:24 -msgid "Password" -msgstr "密码" +#: applications/serializers/attrs/application_type/chrome.py:19 +msgid "Chrome username" +msgstr "Chrome 用户名" -#: applications/serializers/attrs/application_type/custom.py:13 +#: applications/serializers/attrs/application_type/chrome.py:22 +#: applications/serializers/attrs/application_type/chrome.py:29 +msgid "Chrome password" +msgstr "Chrome 密码" + +#: applications/serializers/attrs/application_type/custom.py:11 msgid "Operating parameter" msgstr "运行参数" -#: applications/serializers/attrs/application_type/custom.py:17 +#: applications/serializers/attrs/application_type/custom.py:15 msgid "Target url" msgstr "目标URL" +#: applications/serializers/attrs/application_type/custom.py:19 +msgid "Custom Username" +msgstr "自定义用户名" + +#: applications/serializers/attrs/application_type/custom.py:23 +#: applications/serializers/attrs/application_type/custom.py:30 +#: xpack/plugins/change_auth_plan/models/base.py:24 +msgid "Custom password" +msgstr "自定义密码" + +#: applications/serializers/attrs/application_type/mysql_workbench.py:29 +msgid "Mysql workbench username" +msgstr "Mysql 工作台 用户名" + +#: applications/serializers/attrs/application_type/mysql_workbench.py:33 +#: applications/serializers/attrs/application_type/mysql_workbench.py:40 +msgid "Mysql workbench password" +msgstr "Mysql 工作台 密码" + +#: applications/serializers/attrs/application_type/vmware_client.py:25 +msgid "Vmware username" +msgstr "Vmware 用户名" + +#: applications/serializers/attrs/application_type/vmware_client.py:29 +#: applications/serializers/attrs/application_type/vmware_client.py:36 +msgid "Vmware password" +msgstr "Vmware 密码" + #: assets/api/domain.py:52 msgid "Number required" msgstr "需要为数字" @@ -453,7 +487,7 @@ msgstr "基础" msgid "Charset" msgstr "编码" -#: assets/models/asset.py:142 assets/serializers/asset.py:178 +#: assets/models/asset.py:142 assets/serializers/asset.py:176 #: tickets/models/ticket.py:54 msgid "Meta" msgstr "元数据" @@ -463,7 +497,8 @@ msgid "Internal" msgstr "内部的" #: assets/models/asset.py:163 assets/models/asset.py:217 -#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:43 +#: assets/serializers/account.py:14 assets/serializers/asset.py:63 +#: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "系统平台" @@ -523,8 +558,8 @@ msgstr "系统架构" msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:216 assets/serializers/asset.py:67 -#: perms/serializers/asset/user_permission.py:41 +#: assets/models/asset.py:216 assets/serializers/account.py:15 +#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" @@ -569,17 +604,7 @@ msgstr "标签管理" msgid "Created by" msgstr "创建者" -#: assets/models/asset.py:231 assets/models/base.py:181 -#: assets/models/cluster.py:26 assets/models/domain.py:27 -#: assets/models/gathered_user.py:19 assets/models/group.py:22 -#: assets/models/label.py:25 common/db/models.py:113 common/mixins/models.py:50 -#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26 -#: orgs/models.py:435 perms/models/base.py:92 users/models/group.py:18 -#: users/models/user.py:783 xpack/plugins/cloud/models.py:122 -msgid "Date created" -msgstr "创建日期" - -#: assets/models/authbook.py:26 +#: assets/models/authbook.py:27 msgid "AuthBook" msgstr "账号" @@ -621,7 +646,7 @@ msgstr "开始日期" #: assets/models/backup.py:108 #: authentication/templates/authentication/_msg_oauth_bind.html:11 -#: notifications/notifications.py:187 ops/models/adhoc.py:246 +#: notifications/notifications.py:187 ops/models/adhoc.py:257 #: xpack/plugins/change_auth_plan/models/base.py:112 #: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/gathered_user/models.py:79 @@ -646,7 +671,7 @@ msgid "Reason" msgstr "原因" #: assets/models/backup.py:121 audits/serializers.py:76 -#: audits/serializers.py:91 ops/models/adhoc.py:248 +#: audits/serializers.py:91 ops/models/adhoc.py:259 #: terminal/serializers/session.py:35 #: xpack/plugins/change_auth_plan/models/base.py:199 msgid "Is success" @@ -678,6 +703,20 @@ msgstr "可连接性" msgid "Date verified" msgstr "校验日期" +#: assets/models/base.py:177 audits/signals_handler.py:65 +#: authentication/forms.py:22 +#: authentication/templates/authentication/login.html:151 +#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 +#: users/templates/users/_msg_user_created.html:13 +#: users/templates/users/user_password_update.html:43 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models/base.py:39 +#: xpack/plugins/change_auth_plan/models/base.py:118 +#: xpack/plugins/change_auth_plan/models/base.py:193 +#: xpack/plugins/cloud/serializers/account_attrs.py:24 +msgid "Password" +msgstr "密码" + #: assets/models/base.py:178 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 @@ -690,12 +729,6 @@ msgstr "SSH密钥" msgid "SSH public key" msgstr "SSH公钥" -#: assets/models/base.py:182 assets/models/gathered_user.py:20 -#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:39 -#: orgs/models.py:436 -msgid "Date updated" -msgstr "更新日期" - #: assets/models/cluster.py:20 msgid "Bandwidth" msgstr "带宽" @@ -965,39 +998,39 @@ msgstr "" "{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" "置加密密码" -#: assets/serializers/account.py:31 assets/serializers/account.py:52 +#: assets/serializers/account.py:39 assets/serializers/account.py:67 msgid "System user display" msgstr "系统用户名称" -#: assets/serializers/asset.py:22 +#: assets/serializers/asset.py:20 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" -#: assets/serializers/asset.py:39 +#: assets/serializers/asset.py:37 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:68 +#: assets/serializers/asset.py:66 msgid "Domain name" msgstr "网域名称" -#: assets/serializers/asset.py:70 +#: assets/serializers/asset.py:68 msgid "Nodes name" msgstr "节点名称" -#: assets/serializers/asset.py:73 +#: assets/serializers/asset.py:71 msgid "Labels name" msgstr "标签名称" -#: assets/serializers/asset.py:107 +#: assets/serializers/asset.py:105 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:108 +#: assets/serializers/asset.py:106 msgid "Admin user display" msgstr "特权用户名称" -#: assets/serializers/asset.py:109 +#: assets/serializers/asset.py:107 msgid "CPU info" msgstr "CPU信息" @@ -1136,20 +1169,20 @@ msgid "The asset {} system platform {} does not support run Ansible tasks" msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" #: assets/tasks/account_connectivity.py:107 -msgid "Test account connectivity: {}" -msgstr "测试账号可连接性: {}" +msgid "Test account connectivity: " +msgstr "测试账号可连接性: " #: assets/tasks/asset_connectivity.py:49 -msgid "Test assets connectivity" -msgstr "测试资产可连接性" +msgid "Test assets connectivity. " +msgstr "测试资产可连接性. " #: assets/tasks/asset_connectivity.py:91 assets/tasks/asset_connectivity.py:102 -msgid "Test assets connectivity: {}" -msgstr "测试资产可连接性: {}" +msgid "Test assets connectivity: " +msgstr "测试资产可连接性: " #: assets/tasks/asset_connectivity.py:113 -msgid "Test if the assets under the node are connectable: {}" -msgstr "测试节点下资产是否可连接: {}" +msgid "Test if the assets under the node are connectable: " +msgstr "测试节点下资产是否可连接: " #: assets/tasks/const.py:49 msgid "Unreachable" @@ -1164,20 +1197,20 @@ msgid "Get asset info failed: {}" msgstr "获取资产信息失败:{}" #: assets/tasks/gather_asset_hardware_info.py:97 -msgid "Update some assets hardware info" -msgstr "更新资产硬件信息" +msgid "Update some assets hardware info. " +msgstr "更新资产硬件信息. " #: assets/tasks/gather_asset_hardware_info.py:114 -msgid "Update asset hardware info: {}" -msgstr "更新资产硬件信息: {}" +msgid "Update asset hardware info: " +msgstr "更新资产硬件信息: " #: assets/tasks/gather_asset_hardware_info.py:120 -msgid "Update assets hardware info: {}" -msgstr "更新资产硬件信息: {}" +msgid "Update assets hardware info: " +msgstr "更新资产硬件信息: " #: assets/tasks/gather_asset_hardware_info.py:137 -msgid "Update node asset hardware information: {}" -msgstr "更新节点资产硬件信息: {}" +msgid "Update node asset hardware information: " +msgstr "更新节点资产硬件信息: " #: assets/tasks/gather_asset_users.py:111 msgid "Gather assets users" @@ -1202,12 +1235,12 @@ msgid "Hosts count: {}" msgstr "主机数量: {}" #: assets/tasks/push_system_user.py:282 assets/tasks/push_system_user.py:315 -msgid "Push system users to assets: {}" -msgstr "推送系统用户到入资产: {}" +msgid "Push system users to assets: " +msgstr "推送系统用户到入资产: " #: assets/tasks/push_system_user.py:294 -msgid "Push system users to asset: {}({}) => {}" -msgstr "推送系统用户到入资产: {}({}) => {}" +msgid "Push system users to asset: " +msgstr "推送系统用户到入资产: " #: assets/tasks/system_user_connectivity.py:56 msgid "Dynamic system user not support test" @@ -1218,16 +1251,13 @@ msgid "Start test system user connectivity for platform: [{}]" msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" #: assets/tasks/system_user_connectivity.py:118 -msgid "Test system user connectivity: {}" -msgstr "测试系统用户可连接性: {}" - #: assets/tasks/system_user_connectivity.py:129 -msgid "Test system user connectivity: {} => {}" -msgstr "测试系统用户可连接性: {} => {}" +msgid "Test system user connectivity: " +msgstr "测试系统用户可连接性: " #: assets/tasks/system_user_connectivity.py:148 -msgid "Test system user connectivity period: {}" -msgstr "定期测试系统用户可连接性: {}" +msgid "Test system user connectivity period: " +msgstr "定期测试系统用户可连接性: " #: assets/tasks/utils.py:17 msgid "Asset has been disabled, skipped: {}" @@ -1427,14 +1457,14 @@ msgid "Auth Token" msgstr "认证令牌" #: audits/signals_handler.py:68 authentication/notifications.py:73 -#: authentication/views/dingtalk.py:160 authentication/views/feishu.py:148 #: authentication/views/login.py:164 authentication/views/wecom.py:158 #: notifications/backends/__init__.py:11 users/models/user.py:607 msgid "WeCom" msgstr "企业微信" -#: audits/signals_handler.py:69 authentication/views/login.py:170 -#: notifications/backends/__init__.py:12 users/models/user.py:608 +#: audits/signals_handler.py:69 authentication/views/dingtalk.py:160 +#: authentication/views/login.py:170 notifications/backends/__init__.py:12 +#: users/models/user.py:608 msgid "DingTalk" msgstr "钉钉" @@ -1625,7 +1655,11 @@ msgstr "{ApplicationPermission} 移除 {SystemUser}" msgid "Invalid token" msgstr "无效的令牌" -#: authentication/api/mfa.py:103 +#: authentication/api/mfa.py:72 +msgid "Current user not support mfa type: {}" +msgstr "当前用户不支持 MFA 类型: {}" + +#: authentication/api/mfa.py:119 msgid "Code is invalid, {}" msgstr "验证码无效: {}" @@ -1794,15 +1828,15 @@ msgstr "该 时间段 不被允许登录" msgid "SSO auth closed" msgstr "SSO 认证关闭了" -#: authentication/errors.py:300 authentication/mixins.py:359 +#: authentication/errors.py:300 authentication/mixins.py:364 msgid "Your password is too simple, please change it for security" msgstr "你的密码过于简单,为了安全,请修改" -#: authentication/errors.py:309 authentication/mixins.py:366 +#: authentication/errors.py:309 authentication/mixins.py:371 msgid "You should to change your password before login" msgstr "登录完成前,请先修改密码" -#: authentication/errors.py:318 authentication/mixins.py:373 +#: authentication/errors.py:318 authentication/mixins.py:378 msgid "Your password has expired, please reset before logging in" msgstr "您的密码已过期,先修改再登录" @@ -1898,7 +1932,7 @@ msgstr "清空手机号码禁用" msgid "The MFA type ({}) is not enabled" msgstr "该 MFA ({}) 方式没有启用" -#: authentication/mixins.py:349 +#: authentication/mixins.py:354 msgid "Please change your password" msgstr "请修改密码" @@ -2006,7 +2040,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:293 +#: jumpserver/conf.py:293 ops/tasks.py:145 ops/tasks.py:148 #: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3 #: users/templates/users/_msg_account_expire_reminder.html:4 @@ -2206,6 +2240,11 @@ msgstr "飞书查询用户失败" msgid "The FeiShu is already bound to another user" msgstr "该飞书已经绑定其他用户" +#: authentication/views/feishu.py:148 authentication/views/login.py:176 +#: notifications/backends/__init__.py:14 users/models/user.py:609 +msgid "FeiShu" +msgstr "飞书" + #: authentication/views/feishu.py:149 msgid "Binding FeiShu successfully" msgstr "绑定 飞书 成功" @@ -2234,11 +2273,6 @@ msgstr "正在跳转到 {} 认证" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:176 notifications/backends/__init__.py:14 -#: users/models/user.py:609 -msgid "FeiShu" -msgstr "飞书" - #: authentication/views/login.py:265 msgid "" "Wait for {} confirm, You also can copy link to her/him
\n" @@ -2350,6 +2384,10 @@ msgstr "被其他对象关联,不能删除" msgid "This action require verify your MFA" msgstr "这个操作需要验证 MFA" +#: common/exceptions.py:53 +msgid "Unexpect error occur" +msgstr "" + #: common/fields/model.py:80 msgid "Marshal dict data to char field" msgstr "编码 dict 为 char" @@ -2558,56 +2596,56 @@ msgstr "单位: 时" msgid "Callback" msgstr "回调" -#: ops/models/adhoc.py:143 +#: ops/models/adhoc.py:154 msgid "Tasks" msgstr "任务" -#: ops/models/adhoc.py:144 +#: ops/models/adhoc.py:155 msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:145 +#: ops/models/adhoc.py:156 msgid "Options" msgstr "选项" -#: ops/models/adhoc.py:147 +#: ops/models/adhoc.py:158 msgid "Run as admin" msgstr "再次执行" -#: ops/models/adhoc.py:150 +#: ops/models/adhoc.py:161 msgid "Become" msgstr "Become" -#: ops/models/adhoc.py:151 +#: ops/models/adhoc.py:162 msgid "Create by" msgstr "创建者" -#: ops/models/adhoc.py:240 +#: ops/models/adhoc.py:251 msgid "Task display" msgstr "任务名称" -#: ops/models/adhoc.py:242 +#: ops/models/adhoc.py:253 msgid "Host amount" msgstr "主机数量" -#: ops/models/adhoc.py:244 +#: ops/models/adhoc.py:255 msgid "Start time" msgstr "开始时间" -#: ops/models/adhoc.py:245 +#: ops/models/adhoc.py:256 msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:247 ops/models/command.py:28 +#: ops/models/adhoc.py:258 ops/models/command.py:28 #: terminal/serializers/session.py:39 msgid "Is finished" msgstr "是否完成" -#: ops/models/adhoc.py:249 +#: ops/models/adhoc.py:260 msgid "Adhoc raw result" msgstr "结果" -#: ops/models/adhoc.py:250 +#: ops/models/adhoc.py:261 msgid "Adhoc result summary" msgstr "汇总" @@ -2655,11 +2693,11 @@ msgstr "内存使用率超过 {max_threshold}%: => {value}" msgid "CPU load more than {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}" -#: ops/tasks.py:71 +#: ops/tasks.py:72 msgid "Clean task history period" msgstr "定期清除任务历史" -#: ops/tasks.py:84 +#: ops/tasks.py:85 msgid "Clean celery log period" msgstr "定期清除Celery日志" @@ -2819,7 +2857,7 @@ msgstr "用户组数量" msgid "System users amount" msgstr "系统用户数量" -#: perms/serializers/application/permission.py:88 +#: perms/serializers/application/permission.py:79 msgid "" "The application list contains applications that are different from the " "permission type. ({})" @@ -2895,7 +2933,7 @@ msgstr "获取 LDAP 用户为 None" msgid "Imported {} users successfully (Organization: {})" msgstr "成功导入 {} 个用户 ( 组织: {} )" -#: settings/models.py:196 users/templates/users/reset_password.html:29 +#: settings/models.py:195 users/templates/users/reset_password.html:29 msgid "Setting" msgstr "设置" @@ -4938,7 +4976,7 @@ msgstr "流程" msgid "TicketFlow" msgstr "工单流程" -#: tickets/models/ticket.py:296 +#: tickets/models/ticket.py:297 msgid "Please try again" msgstr "请再次尝试" @@ -5750,10 +5788,6 @@ msgstr "改密计划执行" msgid "Change auth plan task" msgstr "改密计划任务" -#: xpack/plugins/change_auth_plan/models/base.py:24 -msgid "Custom password" -msgstr "自定义密码" - #: xpack/plugins/change_auth_plan/models/base.py:25 msgid "All assets use the same random password" msgstr "使用相同的随机密码" @@ -6311,6 +6345,15 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "MFA type not support: {}" +#~ msgstr "MFA 类型不支持:{}" + +#~ msgid "Push system users to asset: {}({}) => {}" +#~ msgstr "推送系统用户到入资产: {}({}) => {}" + +#~ msgid "Test system user connectivity: {} => {}" +#~ msgstr "测试系统用户可连接性: {} => {}" + #~ msgid "Account backup plan execution" #~ msgstr "改密计划执行" diff --git a/apps/ops/apps.py b/apps/ops/apps.py index 5133c6655..43496ebf2 100644 --- a/apps/ops/apps.py +++ b/apps/ops/apps.py @@ -13,5 +13,6 @@ class OpsConfig(AppConfig): from orgs.utils import set_current_org set_current_org(Organization.root()) from .celery import signal_handler + from . import signals_handler from . import notifications super().ready() diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 4c4c549f9..3e95799b5 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -9,7 +9,7 @@ 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 django.utils.translation import ugettext_lazy as _, gettext from common.utils import get_logger, lazyproperty from common.fields.model import ( @@ -58,6 +58,17 @@ class Task(PeriodTaskModelMixin, OrgModelMixin): else: return False + @lazyproperty + def display_name(self): + sps = ['. ', ': '] + spb = {str(sp in self.name): sp for sp in sps} + sp = spb.get('True') + if not sp: + return self.name + + tpl, data = self.name.split(sp, 1) + return gettext(tpl + sp) + data + @property def timedelta(self): if self.latest_execution: diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index badbed2b4..80619c34a 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -56,7 +56,7 @@ class TaskSerializer(BulkOrgResourceModelSerializer): class Meta: model = Task - fields_mini = ['id', 'name'] + fields_mini = ['id', 'name', 'display_name'] fields_small = fields_mini + [ 'interval', 'crontab', 'is_periodic', 'is_deleted', diff --git a/apps/ops/signals_handler.py b/apps/ops/signals_handler.py new file mode 100644 index 000000000..dfd364845 --- /dev/null +++ b/apps/ops/signals_handler.py @@ -0,0 +1,34 @@ +from django.utils import translation +from django.core.cache import cache +from celery.signals import task_prerun, task_postrun, before_task_publish + +from common.db.utils import close_old_connections + + +TASK_LANG_CACHE_KEY = 'TASK_LANG_{}' +TASK_LANG_CACHE_TTL = 1800 + + +@before_task_publish.connect() +def before_task_publish(headers=None, **kwargs): + task_id = headers.get('id') + current_lang = translation.get_language() + key = TASK_LANG_CACHE_KEY.format(task_id) + cache.set(key, current_lang, 1800) + + +@task_prerun.connect() +def on_celery_task_pre_run(task_id='', **kwargs): + # 关闭之前的数据库连接 + close_old_connections() + + # 保存 Lang context + key = TASK_LANG_CACHE_KEY.format(task_id) + task_lang = cache.get(key) + if task_lang: + translation.activate(task_lang) + + +@task_postrun.connect() +def on_celery_task_post_run(**kwargs): + close_old_connections() diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index fb0c2e71c..e68b4c55c 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -5,9 +5,10 @@ import time from django.conf import settings from celery import shared_task, subtask + from celery.exceptions import SoftTimeLimitExceeded from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, gettext 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 @@ -141,10 +142,10 @@ def hello(name, callback=None): import time count = User.objects.count() - print("Hello {}".format(name)) + print(gettext("Hello") + ': ' + name) print("Count: ", count) time.sleep(1) - return count + return gettext("Hello") @shared_task @@ -177,3 +178,4 @@ def add_m(x): s.append(add.s(i)) res = chain(*tuple(s))() return res + diff --git a/apps/perms/serializers/application/permission.py b/apps/perms/serializers/application/permission.py index 1df5b1eca..f3e6443a0 100644 --- a/apps/perms/serializers/application/permission.py +++ b/apps/perms/serializers/application/permission.py @@ -6,14 +6,14 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from perms.models import ApplicationPermission -from ..base import ActionsField +from ..base import ActionsField, BasePermissionSerializer __all__ = [ 'ApplicationPermissionSerializer' ] -class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer): +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')) @@ -46,15 +46,7 @@ class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer): 'applications_amount': {'label': _('Applications amount')}, } - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_actions_choices() - - def set_actions_choices(self): - actions = self.fields.get('actions') - if not actions: - return - choices = actions._choices + def _filter_actions_choices(self, choices): if request := self.context.get('request'): category = request.query_params.get('category') else: @@ -62,8 +54,7 @@ class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer): exclude_choices = ApplicationPermission.get_exclude_actions_choices(category=category) for choice in exclude_choices: choices.pop(choice, None) - actions._choices = choices - actions.default = list(choices.keys()) + return choices @classmethod def setup_eager_loading(cls, queryset): diff --git a/apps/perms/serializers/asset/permission.py b/apps/perms/serializers/asset/permission.py index 0b0d5aaa6..cd6c24723 100644 --- a/apps/perms/serializers/asset/permission.py +++ b/apps/perms/serializers/asset/permission.py @@ -9,12 +9,12 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from perms.models import AssetPermission, Action from assets.models import Asset, Node, SystemUser from users.models import User, UserGroup -from ..base import ActionsField +from ..base import ActionsField, BasePermissionSerializer __all__ = ['AssetPermissionSerializer'] -class AssetPermissionSerializer(BulkOrgResourceModelSerializer): +class AssetPermissionSerializer(BasePermissionSerializer): 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')) diff --git a/apps/perms/serializers/base.py b/apps/perms/serializers/base.py index 7e8e1b63f..81707dbd1 100644 --- a/apps/perms/serializers/base.py +++ b/apps/perms/serializers/base.py @@ -1,7 +1,8 @@ from rest_framework import serializers from perms.models import Action +from orgs.mixins.serializers import BulkOrgResourceModelSerializer -__all__ = ['ActionsDisplayField', 'ActionsField'] +__all__ = ['ActionsDisplayField', 'ActionsField', 'BasePermissionSerializer'] class ActionsField(serializers.MultipleChoiceField): @@ -24,3 +25,21 @@ class ActionsDisplayField(ActionsField): 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/settings/api/public.py b/apps/settings/api/public.py index 48542a1ea..349955007 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -2,7 +2,7 @@ from rest_framework import generics from rest_framework.permissions import AllowAny from django.conf import settings -from jumpserver.utils import has_valid_xpack_license +from jumpserver.utils import has_valid_xpack_license, get_xpack_license_info from common.utils import get_logger from .. import serializers from ..utils import get_interface_setting @@ -40,6 +40,7 @@ class PublicSettingApi(generics.RetrieveAPIView): "SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME, "SECURITY_LUNA_REMEMBER_AUTH": settings.SECURITY_LUNA_REMEMBER_AUTH, "XPACK_LICENSE_IS_VALID": has_valid_xpack_license(), + "XPACK_LICENSE_INFO": get_xpack_license_info(), "LOGIN_TITLE": self.get_login_title(), "LOGO_URLS": self.get_logo_urls(), "TICKETS_ENABLED": settings.TICKETS_ENABLED, diff --git a/apps/settings/models.py b/apps/settings/models.py index f92e09f34..0690590b6 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -79,7 +79,6 @@ class Setting(models.Model): item.refresh_setting() def refresh_setting(self): - logger.debug(f"Refresh setting: {self.name}") if hasattr(self.__class__, f'refresh_{self.name}'): getattr(self.__class__, f'refresh_{self.name}')() else: diff --git a/apps/templates/_mfa_login_field.html b/apps/templates/_mfa_login_field.html index 8aa3cbdd6..d73df5499 100644 --- a/apps/templates/_mfa_login_field.html +++ b/apps/templates/_mfa_login_field.html @@ -121,11 +121,7 @@ url: url, method: "POST", body: JSON.stringify(data), - success: onSuccess, - error: function (text, data) { - toastr.error(data.error) - }, - flash_message: false + success: onSuccess }) } diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index e77ca86e6..80bc034e3 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -29,7 +29,10 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): search_fields = [ 'title', 'action', 'type', 'status', 'applicant_display' ] - ordering_fields = ('title', 'applicant_display', 'status', 'state', 'action_display', 'date_created') + ordering_fields = ( + 'title', 'applicant_display', 'status', 'state', 'action_display', + 'date_created', 'serial_num', + ) ordering = ('-date_created', ) def create(self, request, *args, **kwargs): diff --git a/apps/tickets/migrations/0013_ticket_serial_num.py b/apps/tickets/migrations/0013_ticket_serial_num.py index 3da9f3b8b..4c457d21f 100644 --- a/apps/tickets/migrations/0013_ticket_serial_num.py +++ b/apps/tickets/migrations/0013_ticket_serial_num.py @@ -38,7 +38,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='ticket', name='serial_num', - field=models.CharField(max_length=256, null=True, unique=True, verbose_name='Serial number'), + field=models.CharField(max_length=128, null=True, unique=True, verbose_name='Serial number'), ), migrations.RunPython(fill_ticket_serial_number), ] diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py index 8047c4a06..f61870f55 100644 --- a/apps/tickets/models/ticket.py +++ b/apps/tickets/models/ticket.py @@ -77,7 +77,7 @@ class Ticket(CommonModelMixin, OrgModelMixin): 'TicketFlow', related_name='tickets', on_delete=models.SET_NULL, null=True, verbose_name=_("TicketFlow") ) - serial_num = models.CharField(max_length=256, unique=True, null=True, verbose_name=_('Serial number')) + serial_num = models.CharField(max_length=128, unique=True, null=True, verbose_name=_('Serial number')) class Meta: ordering = ('-date_created',) @@ -195,7 +195,8 @@ class Ticket(CommonModelMixin, OrgModelMixin): def update_current_step_state_and_assignee(self, processor, state): if self.status_closed: raise AlreadyClosed - self.state = state + if state != TicketState.approved: + self.state = state current_node = self.current_node current_node.update(state=state) current_node.first().ticket_assignees.filter(assignee=processor).update(state=state) @@ -260,7 +261,7 @@ class Ticket(CommonModelMixin, OrgModelMixin): date_created = as_current_tz(self.date_created) date_prefix = date_created.strftime('%Y%m%d') - ticket = Ticket.objects.select_for_update().filter( + ticket = Ticket.all().select_for_update().filter( serial_num__startswith=date_prefix ).order_by('-date_created').first() diff --git a/apps/users/templates/users/_msg_user_created.html b/apps/users/templates/users/_msg_user_created.html index 3b1c36ed5..e3e23bb89 100644 --- a/apps/users/templates/users/_msg_user_created.html +++ b/apps/users/templates/users/_msg_user_created.html @@ -6,7 +6,7 @@

- {{ content }} + {{ content | safe }}

{% trans 'Username' %}: {{ user.username }}
diff --git a/requirements/deb_requirements.txt b/requirements/deb_requirements.txt index 53aa37322..39b280ed7 100644 --- a/requirements/deb_requirements.txt +++ b/requirements/deb_requirements.txt @@ -1 +1 @@ -g++ make iputils-ping default-libmysqlclient-dev libpq-dev libffi-dev libldap2-dev libsasl2-dev sshpass pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl +g++ make iputils-ping default-libmysqlclient-dev libpq-dev libffi-dev libldap2-dev libsasl2-dev sshpass pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl libaio-dev freetds-dev