diff --git a/apps/accounts/api/account/account.py b/apps/accounts/api/account/account.py index 5a496b13e..201e63a20 100644 --- a/apps/accounts/api/account/account.py +++ b/apps/accounts/api/account/account.py @@ -8,7 +8,7 @@ from accounts import serializers from accounts.filters import AccountFilterSet from accounts.models import Account from assets.models import Asset, Node -from common.api import ExtraFilterFieldsMixin +from common.api.mixin import ExtraFilterFieldsMixin from common.permissions import UserConfirmation, ConfirmType, IsValidUser from common.views.mixins import RecordViewLogMixin from orgs.mixins.api import OrgBulkModelViewSet diff --git a/apps/accounts/migrations/0015_auto_20230825_1120.py b/apps/accounts/migrations/0015_auto_20230825_1120.py new file mode 100644 index 000000000..8fa898b58 --- /dev/null +++ b/apps/accounts/migrations/0015_auto_20230825_1120.py @@ -0,0 +1,34 @@ +# Generated by Django 4.1.10 on 2023-08-25 03:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0122_auto_20230803_1553'), + ('accounts', '0014_virtualaccount'), + ] + + operations = [ + migrations.AddField( + model_name='accounttemplate', + name='auto_push', + field=models.BooleanField(default=False, verbose_name='Auto push'), + ), + migrations.AddField( + model_name='accounttemplate', + name='platforms', + field=models.ManyToManyField(related_name='account_templates', to='assets.platform', verbose_name='Platforms'), + ), + migrations.AddField( + model_name='accounttemplate', + name='push_params', + field=models.JSONField(default=dict, verbose_name='Push params'), + ), + migrations.AddField( + model_name='accounttemplate', + name='secret_strategy', + field=models.CharField(choices=[('specific', 'Specific password'), ('random', 'Random')], default='specific', max_length=16, verbose_name='Secret strategy'), + ), + ] diff --git a/apps/accounts/models/automations/base.py b/apps/accounts/models/automations/base.py index 9477a7fdb..98c513161 100644 --- a/apps/accounts/models/automations/base.py +++ b/apps/accounts/models/automations/base.py @@ -1,12 +1,16 @@ +from django.db import models from django.utils.translation import gettext_lazy as _ +from accounts.const import SecretStrategy, SSHKeyStrategy, SecretType +from accounts.models import Account from accounts.tasks import execute_account_automation_task from assets.models.automations import ( BaseAutomation as AssetBaseAutomation, AutomationExecution as AssetAutomationExecution ) +from common.db import fields -__all__ = ['AccountBaseAutomation', 'AutomationExecution'] +__all__ = ['AccountBaseAutomation', 'AutomationExecution', 'ChangeSecretMixin'] class AccountBaseAutomation(AssetBaseAutomation): @@ -43,3 +47,56 @@ class AutomationExecution(AssetAutomationExecution): from accounts.automations.endpoint import ExecutionManager manager = ExecutionManager(execution=self) return manager.run() + + +class ChangeSecretRuleMixin(models.Model): + secret_strategy = models.CharField( + choices=SecretStrategy.choices, max_length=16, + default=SecretStrategy.custom, verbose_name=_('Secret strategy') + ) + password_rules = models.JSONField(default=dict, verbose_name=_('Password rules')) + ssh_key_change_strategy = models.CharField( + choices=SSHKeyStrategy.choices, max_length=16, + default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy') + ) + + class Meta: + abstract = True + + +class ChangeSecretMixin(ChangeSecretRuleMixin): + secret_type = models.CharField( + choices=SecretType.choices, max_length=16, + default=SecretType.PASSWORD, verbose_name=_('Secret type') + ) + secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) + get_all_assets: callable # get all assets + + class Meta: + abstract = True + + def create_nonlocal_accounts(self, usernames, asset): + pass + + def get_account_ids(self): + usernames = self.accounts + accounts = Account.objects.none() + for asset in self.get_all_assets(): + self.create_nonlocal_accounts(usernames, asset) + accounts = accounts | asset.accounts.all() + account_ids = accounts.filter( + username__in=usernames, secret_type=self.secret_type + ).values_list('id', flat=True) + return [str(_id) for _id in account_ids] + + def to_attr_json(self): + attr_json = super().to_attr_json() + attr_json.update({ + 'secret': self.secret, + 'secret_type': self.secret_type, + 'accounts': self.get_account_ids(), + 'password_rules': self.password_rules, + 'secret_strategy': self.secret_strategy, + 'ssh_key_change_strategy': self.ssh_key_change_strategy, + }) + return attr_json diff --git a/apps/accounts/models/automations/change_secret.py b/apps/accounts/models/automations/change_secret.py index 0efeff049..9c2086142 100644 --- a/apps/accounts/models/automations/change_secret.py +++ b/apps/accounts/models/automations/change_secret.py @@ -2,62 +2,13 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from accounts.const import ( - AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy + AutomationTypes ) -from accounts.models import Account from common.db import fields from common.db.models import JMSBaseModel -from .base import AccountBaseAutomation +from .base import AccountBaseAutomation, ChangeSecretMixin -__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'ChangeSecretMixin'] - - -class ChangeSecretMixin(models.Model): - secret_type = models.CharField( - choices=SecretType.choices, max_length=16, - default=SecretType.PASSWORD, verbose_name=_('Secret type') - ) - secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret')) - secret_strategy = models.CharField( - choices=SecretStrategy.choices, max_length=16, - default=SecretStrategy.custom, verbose_name=_('Secret strategy') - ) - password_rules = models.JSONField(default=dict, verbose_name=_('Password rules')) - ssh_key_change_strategy = models.CharField( - choices=SSHKeyStrategy.choices, max_length=16, - default=SSHKeyStrategy.add, verbose_name=_('SSH key change strategy') - ) - - get_all_assets: callable # get all assets - - class Meta: - abstract = True - - def create_nonlocal_accounts(self, usernames, asset): - pass - - def get_account_ids(self): - usernames = self.accounts - accounts = Account.objects.none() - for asset in self.get_all_assets(): - self.create_nonlocal_accounts(usernames, asset) - accounts = accounts | asset.accounts.all() - account_ids = accounts.filter( - username__in=usernames, secret_type=self.secret_type - ).values_list('id', flat=True) - return [str(_id) for _id in account_ids] - - def to_attr_json(self): - attr_json = super().to_attr_json() - attr_json.update({ - 'secret': self.secret, - 'secret_type': self.secret_type, - 'accounts': self.get_account_ids(), - 'password_rules': self.password_rules, - 'secret_strategy': self.secret_strategy, - 'ssh_key_change_strategy': self.ssh_key_change_strategy, - }) - return attr_json +__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', ] class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation): diff --git a/apps/accounts/models/base.py b/apps/accounts/models/base.py index b06012c41..37f5deaae 100644 --- a/apps/accounts/models/base.py +++ b/apps/accounts/models/base.py @@ -9,11 +9,11 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from accounts.const import SecretType +from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin from common.utils import ( ssh_key_string_to_obj, ssh_key_gen, get_logger, random_string, lazyproperty, parse_ssh_public_key_str, is_openssh_format_key ) -from accounts.models.mixins import VaultModelMixin, VaultManagerMixin, VaultQuerySetMixin from orgs.mixins.models import JMSOrgBaseModel, OrgManager logger = get_logger(__file__) diff --git a/apps/accounts/models/template.py b/apps/accounts/models/template.py index adee261e6..061876b13 100644 --- a/apps/accounts/models/template.py +++ b/apps/accounts/models/template.py @@ -8,12 +8,24 @@ from .base import BaseAccount __all__ = ['AccountTemplate', ] +from ..const import SecretStrategy + class AccountTemplate(BaseAccount): su_from = models.ForeignKey( 'self', related_name='su_to', null=True, on_delete=models.SET_NULL, verbose_name=_("Su from") ) + secret_strategy = models.CharField( + choices=SecretStrategy.choices, max_length=16, + default=SecretStrategy.custom, verbose_name=_('Secret strategy') + ) + auto_push = models.BooleanField(default=False, verbose_name=_('Auto push')) + platforms = models.ManyToManyField( + 'assets.Platform', related_name='account_templates', + verbose_name=_('Platforms') + ) + push_params = models.JSONField(default=dict, verbose_name=_('Push params')) class Meta: verbose_name = _('Account template') @@ -25,15 +37,15 @@ class AccountTemplate(BaseAccount): ('change_accounttemplatesecret', _('Can change asset account template secret')), ] + def __str__(self): + return f'{self.name}({self.username})' + @classmethod def get_su_from_account_templates(cls, pk=None): if pk is None: return cls.objects.all() return cls.objects.exclude(Q(id=pk) | Q(su_from_id=pk)) - def __str__(self): - return f'{self.name}({self.username})' - def get_su_from_account(self, asset): su_from = self.su_from if su_from and asset.platform.su_enabled: diff --git a/apps/accounts/serializers/account/template.py b/apps/accounts/serializers/account/template.py index 9642c36b0..23f79350d 100644 --- a/apps/accounts/serializers/account/template.py +++ b/apps/accounts/serializers/account/template.py @@ -18,7 +18,19 @@ class AccountTemplateSerializer(BaseAccountSerializer): class Meta(BaseAccountSerializer.Meta): model = AccountTemplate - fields = BaseAccountSerializer.Meta.fields + ['is_sync_account', 'su_from'] + fields = BaseAccountSerializer.Meta.fields + [ + 'secret_strategy', + 'auto_push', 'push_params', 'platforms', + 'is_sync_account', 'su_from' + ] + extra_kwargs = { + 'secret_strategy': {'help_text': _('Secret generation strategy for account creation')}, + 'auto_push': {'help_text': _('Whether to automatically push the account to the asset')}, + 'platforms': {'help_text': _( + 'Associated platform, you can configure push parameters. ' + 'If not associated, default parameters will be used' + )}, + } def sync_accounts_secret(self, instance, diff): if not self._is_sync_account or 'secret' not in diff: diff --git a/apps/accounts/signal_handlers.py b/apps/accounts/signal_handlers.py index 868e85614..6aba9cee4 100644 --- a/apps/accounts/signal_handlers.py +++ b/apps/accounts/signal_handlers.py @@ -1,9 +1,17 @@ -from django.db.models.signals import pre_save, post_save, post_delete +from collections import defaultdict + +from django.db.models.signals import post_delete +from django.db.models.signals import pre_save, post_save from django.dispatch import receiver +from django.utils.translation import gettext_noop from accounts.backends import vault_client -from common.utils import get_logger +from audits.const import ActivityChoices +from audits.signal_handlers import create_activities +from common.decorators import merge_delay_run +from common.utils import get_logger, i18n_fmt from .models import Account, AccountTemplate +from .tasks.push_account import push_accounts_to_assets_task logger = get_logger(__name__) @@ -16,6 +24,39 @@ def on_account_pre_save(sender, instance, **kwargs): instance.version = instance.history.count() +@merge_delay_run(ttl=5) +def push_accounts_if_need(accounts=()): + from .models import AccountTemplate + + template_accounts = defaultdict(list) + for ac in accounts: + # 再强调一次吧 + if ac.source != 'template': + continue + template_accounts[ac.source_id].append(ac) + + for source_id, accounts in template_accounts.items(): + template = AccountTemplate.objects.filter(id=source_id).first() + if not template or not template.auto_push: + continue + logger.debug("Push accounts to source: %s", source_id) + account_ids = [str(ac.id) for ac in accounts] + task = push_accounts_to_assets_task.delay(account_ids, params=template.push_params) + detail = i18n_fmt( + gettext_noop('Push related accounts to assets: %s, by system'), + len(account_ids) + ) + create_activities([str(template.id)], detail, task.id, ActivityChoices.task, template.org_id) + logger.debug("Push accounts to source: %s, task: %s", source_id, task) + + +@receiver(post_save, sender=Account) +def on_account_create_by_template(sender, instance, created=False, **kwargs): + if not created or instance.source != 'template': + return + push_accounts_if_need(accounts=(instance,)) + + class VaultSignalHandler(object): """ 处理 Vault 相关的信号 """ diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index a6e34b38b..b77014305 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -19,7 +19,6 @@ class AssetPlatformViewSet(JMSModelViewSet): 'default': PlatformSerializer, 'categories': GroupedChoiceSerializer, } - filterset_fields = ['name', 'category', 'type'] search_fields = ['name'] rbac_perms = { 'categories': 'assets.view_platform', @@ -49,13 +48,19 @@ class AssetPlatformViewSet(JMSModelViewSet): @action(methods=['post'], detail=False, url_path='filter-nodes-assets') def filter_nodes_assets(self, request, *args, **kwargs): node_ids = request.data.get('node_ids', []) - asset_ids = request.data.get('asset_ids', []) - nodes = Node.objects.filter(id__in=node_ids) - node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) - direct_asset_ids = Asset.objects.filter(id__in=asset_ids).values_list('id', flat=True) - platform_ids = Asset.objects.filter( - id__in=set(list(direct_asset_ids) + list(node_asset_ids)) - ).values_list('platform_id', flat=True) + asset_ids = set(request.data.get('asset_ids', [])) + platform_ids = set(request.data.get('platform_ids', [])) + + if node_ids: + nodes = Node.objects.filter(id__in=node_ids) + node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True) + asset_ids |= set(node_asset_ids) + + if asset_ids: + _platform_ids = Asset.objects \ + .filter(id__in=set(asset_ids)) \ + .values_list('platform_id', flat=True) + platform_ids |= set(_platform_ids) platforms = Platform.objects.filter(id__in=platform_ids) serializer = self.get_serializer(platforms, many=True) return Response(serializer.data) diff --git a/apps/audits/api.py b/apps/audits/api.py index 56fff6297..c635e4170 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,30 +1,26 @@ # -*- coding: utf-8 -*- # -import os from importlib import import_module from django.conf import settings -from django.shortcuts import get_object_or_404 from django.db.models import F, Value, CharField, Q from django.http import HttpResponse, FileResponse from django.utils.encoding import escape_uri_path from rest_framework import generics +from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from rest_framework.views import APIView -from rest_framework.decorators import action -from common.api import AsyncApiMixin -from common.drf.filters import DatetimeRangeFilter +from common.const.http import GET, POST +from common.drf.filters import DatetimeRangeFilterBackend from common.permissions import IsServiceAccount from common.plugins.es import QuerySet as ESQuerySet -from common.utils import is_uuid, get_logger, lazyproperty -from common.const.http import GET, POST from common.storage.ftp_file import FTPFileStorageHandler +from common.utils import is_uuid, get_logger, lazyproperty from orgs.mixins.api import OrgReadonlyModelViewSet, OrgModelViewSet -from orgs.utils import current_org, tmp_to_root_org from orgs.models import Organization +from orgs.utils import current_org, tmp_to_root_org from rbac.permissions import RBACPermission from terminal.models import default_storage from users.models import User @@ -38,13 +34,12 @@ from .serializers import ( FileSerializer ) - logger = get_logger(__name__) class JobAuditViewSet(OrgReadonlyModelViewSet): model = JobLog - extra_filter_backends = [DatetimeRangeFilter] + extra_filter_backends = [DatetimeRangeFilterBackend] date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] @@ -57,7 +52,7 @@ class JobAuditViewSet(OrgReadonlyModelViewSet): class FTPLogViewSet(OrgModelViewSet): model = FTPLog serializer_class = FTPLogSerializer - extra_filter_backends = [DatetimeRangeFilter] + extra_filter_backends = [DatetimeRangeFilterBackend] date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] @@ -113,7 +108,7 @@ class FTPLogViewSet(OrgModelViewSet): class UserLoginCommonMixin: model = UserLoginLog serializer_class = UserLoginLogSerializer - extra_filter_backends = [DatetimeRangeFilter] + extra_filter_backends = [DatetimeRangeFilterBackend] date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) ] @@ -193,7 +188,7 @@ class ResourceActivityAPIView(generics.ListAPIView): class OperateLogViewSet(OrgReadonlyModelViewSet): model = OperateLog serializer_class = OperateLogSerializer - extra_filter_backends = [DatetimeRangeFilter] + extra_filter_backends = [DatetimeRangeFilterBackend] date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) ] @@ -232,7 +227,7 @@ class OperateLogViewSet(OrgReadonlyModelViewSet): class PasswordChangeLogViewSet(OrgReadonlyModelViewSet): model = PasswordChangeLog serializer_class = PasswordChangeLogSerializer - extra_filter_backends = [DatetimeRangeFilter] + extra_filter_backends = [DatetimeRangeFilterBackend] date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) ] diff --git a/apps/common/api/__init__.py b/apps/common/api/__init__.py index 7f2c4dbc9..15797eb69 100644 --- a/apps/common/api/__init__.py +++ b/apps/common/api/__init__.py @@ -1,6 +1,5 @@ from .action import * from .common import * -from .filter import * from .generic import * from .mixin import * from .patch import * diff --git a/apps/common/api/filter.py b/apps/common/api/filter.py deleted file mode 100644 index a23057d8b..000000000 --- a/apps/common/api/filter.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -# -import logging -from itertools import chain - -from django.db import models -from rest_framework.settings import api_settings - -from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter, IDNotFilter - -__all__ = ['ExtraFilterFieldsMixin', 'OrderingFielderFieldsMixin'] - -logger = logging.getLogger('jumpserver.common') - - -class ExtraFilterFieldsMixin: - """ - 额外的 api filter - """ - default_added_filters = [CustomFilter, IDSpmFilter, IDInFilter, IDNotFilter] - filter_backends = api_settings.DEFAULT_FILTER_BACKENDS - extra_filter_fields = [] - extra_filter_backends = [] - - def set_compatible_fields(self): - """ - 兼容老的 filter_fields - """ - if not hasattr(self, 'filter_fields') and hasattr(self, 'filterset_fields'): - self.filter_fields = self.filterset_fields - - def get_filter_backends(self): - self.set_compatible_fields() - if self.filter_backends != self.__class__.filter_backends: - return self.filter_backends - backends = list(chain( - self.filter_backends, - self.default_added_filters, - self.extra_filter_backends - )) - return backends - - def filter_queryset(self, queryset): - for backend in self.get_filter_backends(): - queryset = backend().filter_queryset(self.request, queryset, self) - return queryset - - -class OrderingFielderFieldsMixin: - """ - 额外的 api ordering - """ - ordering_fields = None - extra_ordering_fields = [] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.ordering_fields = self._get_ordering_fields() - - def _get_ordering_fields(self): - if isinstance(self.__class__.ordering_fields, (list, tuple)): - return self.__class__.ordering_fields - - try: - valid_fields = self.get_valid_ordering_fields() - except Exception as e: - logger.debug('get_valid_ordering_fields error: %s' % e) - # 这里千万不要这么用,会让 logging 重复,至于为什么,我也不知道 - # logging.debug('get_valid_ordering_fields error: %s' % e) - valid_fields = [] - - fields = list(chain( - valid_fields, - self.extra_ordering_fields - )) - return fields - - def get_valid_ordering_fields(self): - if getattr(self, 'model', None): - model = self.model - elif getattr(self, 'queryset', None): - model = self.queryset.model - else: - queryset = self.get_queryset() - model = queryset.model - - if not model: - return [] - - excludes_fields = ( - models.UUIDField, models.Model, models.ForeignKey, - models.FileField, models.JSONField, models.ManyToManyField, - models.DurationField, - ) - valid_fields = [] - for field in model._meta.fields: - if isinstance(field, excludes_fields): - continue - valid_fields.append(field.name) - return valid_fields diff --git a/apps/common/api/mixin.py b/apps/common/api/mixin.py index cb8d6ec3e..bd4b6db2e 100644 --- a/apps/common/api/mixin.py +++ b/apps/common/api/mixin.py @@ -1,19 +1,29 @@ # -*- coding: utf-8 -*- # from collections import defaultdict +from itertools import chain from typing import Callable +from django.db import models from django.db.models.signals import m2m_changed from rest_framework.response import Response +from rest_framework.settings import api_settings +from common.drf.filters import ( + IDSpmFilterBackend, CustomFilterBackend, IDInFilterBackend, + IDNotFilterBackend, NotOrRelFilterBackend +) +from common.utils import get_logger from .action import RenderToJsonMixin -from .filter import ExtraFilterFieldsMixin, OrderingFielderFieldsMixin from .serializer import SerializerMixin __all__ = [ 'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', + 'ExtraFilterFieldsMixin', ] +logger = get_logger(__name__) + class PaginatedResponseMixin: @@ -95,6 +105,100 @@ class QuerySetMixin: return queryset -class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, OrderingFielderFieldsMixin, - QuerySetMixin, RenderToJsonMixin, PaginatedResponseMixin): +class ExtraFilterFieldsMixin: + """ + 额外的 api filter + """ + default_added_filters = ( + CustomFilterBackend, IDSpmFilterBackend, IDInFilterBackend, + IDNotFilterBackend, + ) + filter_backends = api_settings.DEFAULT_FILTER_BACKENDS + extra_filter_fields = [] + extra_filter_backends = [] + + def set_compatible_fields(self): + """ + 兼容老的 filter_fields + """ + if not hasattr(self, 'filter_fields') and hasattr(self, 'filterset_fields'): + self.filter_fields = self.filterset_fields + + def get_filter_backends(self): + self.set_compatible_fields() + if self.filter_backends != self.__class__.filter_backends: + return self.filter_backends + backends = list(chain( + self.filter_backends, + self.default_added_filters, + self.extra_filter_backends, + )) + # 这个要放在最后 + backends.append(NotOrRelFilterBackend) + return backends + + def filter_queryset(self, queryset): + for backend in self.get_filter_backends(): + queryset = backend().filter_queryset(self.request, queryset, self) + return queryset + + +class OrderingFielderFieldsMixin: + """ + 额外的 api ordering + """ + ordering_fields = None + extra_ordering_fields = [] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.ordering_fields = self._get_ordering_fields() + + def _get_ordering_fields(self): + if isinstance(self.__class__.ordering_fields, (list, tuple)): + return self.__class__.ordering_fields + + try: + valid_fields = self.get_valid_ordering_fields() + except Exception as e: + logger.debug('get_valid_ordering_fields error: %s' % e) + # 这里千万不要这么用,会让 logging 重复,至于为什么,我也不知道 + # logging.debug('get_valid_ordering_fields error: %s' % e) + valid_fields = [] + + fields = list(chain( + valid_fields, + self.extra_ordering_fields + )) + return fields + + def get_valid_ordering_fields(self): + if getattr(self, 'model', None): + model = self.model + elif getattr(self, 'queryset', None): + model = self.queryset.model + else: + queryset = self.get_queryset() + model = queryset.model + + if not model: + return [] + + excludes_fields = ( + models.UUIDField, models.Model, models.ForeignKey, + models.FileField, models.JSONField, models.ManyToManyField, + models.DurationField, + ) + valid_fields = [] + for field in model._meta.fields: + if isinstance(field, excludes_fields): + continue + valid_fields.append(field.name) + return valid_fields + + +class CommonApiMixin( + SerializerMixin, ExtraFilterFieldsMixin, OrderingFielderFieldsMixin, + QuerySetMixin, RenderToJsonMixin, PaginatedResponseMixin +): pass diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index e99b9b4d1..bd9d3e6a1 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -18,9 +18,10 @@ from common.db.fields import RelatedManager logger = logging.getLogger('jumpserver.common') __all__ = [ - "DatetimeRangeFilter", "IDSpmFilter", - 'IDInFilter', "CustomFilter", - "BaseFilterSet", 'IDNotFilter' + "DatetimeRangeFilterBackend", "IDSpmFilterBackend", + 'IDInFilterBackend', "CustomFilterBackend", + "BaseFilterSet", 'IDNotFilterBackend', + 'NotOrRelFilterBackend', ] @@ -34,7 +35,7 @@ class BaseFilterSet(drf_filters.FilterSet): return default -class DatetimeRangeFilter(filters.BaseFilterBackend): +class DatetimeRangeFilterBackend(filters.BaseFilterBackend): def get_schema_fields(self, view): ret = [] fields = self._get_date_range_filter_fields(view) @@ -101,7 +102,7 @@ class DatetimeRangeFilter(filters.BaseFilterBackend): return queryset -class IDSpmFilter(filters.BaseFilterBackend): +class IDSpmFilterBackend(filters.BaseFilterBackend): def get_schema_fields(self, view): return [ coreapi.Field( @@ -129,7 +130,7 @@ class IDSpmFilter(filters.BaseFilterBackend): return queryset -class IDInFilter(filters.BaseFilterBackend): +class IDInFilterBackend(filters.BaseFilterBackend): def get_schema_fields(self, view): return [ coreapi.Field( @@ -148,7 +149,7 @@ class IDInFilter(filters.BaseFilterBackend): return queryset -class IDNotFilter(filters.BaseFilterBackend): +class IDNotFilterBackend(filters.BaseFilterBackend): def get_schema_fields(self, view): return [ coreapi.Field( @@ -167,7 +168,7 @@ class IDNotFilter(filters.BaseFilterBackend): return queryset -class CustomFilter(filters.BaseFilterBackend): +class CustomFilterBackend(filters.BaseFilterBackend): def get_schema_fields(self, view): fields = [] @@ -236,3 +237,25 @@ class AttrRulesFilterBackend(filters.BaseFilterBackend): logger.debug('attr_rules: %s', attr_rules) q = RelatedManager.get_to_filter_q(attr_rules, queryset.model) return queryset.filter(q).distinct() + + +class NotOrRelFilterBackend(filters.BaseFilterBackend): + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='_rel', location='query', required=False, + type='string', example='/api/v1/users/users?name=abc&username=def&_rel=union', + description='Filter by rel, or not, default is and' + ) + ] + + def filter_queryset(self, request, queryset, view): + _rel = request.query_params.get('_rel') + if not _rel or _rel not in ('or', 'not'): + return queryset + if _rel == 'not': + queryset.query.where.negated = True + elif _rel == 'or': + queryset.query.where.connector = 'OR' + queryset._result_cache = None + return queryset diff --git a/apps/common/views/mixins.py b/apps/common/views/mixins.py index 207888593..6240fee5a 100644 --- a/apps/common/views/mixins.py +++ b/apps/common/views/mixins.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- # -from django.utils import translation -from django.utils.translation import gettext_noop from django.contrib.auth.mixins import UserPassesTestMixin from django.http.response import JsonResponse +from django.utils import translation +from django.utils.translation import gettext_noop from rest_framework import permissions from rest_framework.request import Request +from audits.const import ActionChoices, ActivityChoices +from audits.handler import create_or_update_operate_log +from audits.models import ActivityLog from common.exceptions import UserConfirmRequired from common.utils import i18n_fmt from orgs.utils import current_org -from audits.handler import create_or_update_operate_log -from audits.const import ActionChoices, ActivityChoices -from audits.models import ActivityLog __all__ = [ "PermissionsMixin", diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index e3f730db2..993ad4df1 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:d9efbbfac755784f3aba000f8e56fe697eb983b0157b832e4ae9970b477bd916 -size 154962 +oid sha256:f96558642be3e37f62de0ef8772e8ee0d5cf008bafc5063984f3749ab8489323 +size 155810 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index fbf340353..1829db862 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: 2023-08-17 18:24+0800\n" +"POT-Creation-Date: 2023-08-28 10:55+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -28,7 +28,7 @@ msgstr "パラメータ 'action' は [{}] でなければなりません。" #: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/templates/authentication/login.html:286 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 -#: users/forms/profile.py:22 users/serializers/user.py:104 +#: 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/cloud/serializers/account_attrs.py:28 @@ -217,7 +217,7 @@ msgstr "HashiCorp Vault" msgid "Asset" msgstr "資産" -#: accounts/models/account.py:52 accounts/models/template.py:15 +#: accounts/models/account.py:52 accounts/models/template.py:17 #: accounts/serializers/account/account.py:209 #: accounts/serializers/account/account.py:257 #: accounts/serializers/account/template.py:16 @@ -331,43 +331,56 @@ msgstr "成功は" msgid "Account backup execution" msgstr "アカウントバックアップの実行" -#: accounts/models/automations/base.py:15 +#: accounts/models/automations/base.py:20 msgid "Account automation task" msgstr "アカウント自動化タスク" -#: accounts/models/automations/base.py:29 +#: accounts/models/automations/base.py:34 msgid "Automation execution" msgstr "自動実行" -#: accounts/models/automations/base.py:30 +#: accounts/models/automations/base.py:35 msgid "Automation executions" msgstr "自動実行" -#: accounts/models/automations/base.py:32 +#: accounts/models/automations/base.py:37 msgid "Can view change secret execution" msgstr "改密実行の表示" -#: accounts/models/automations/base.py:33 +#: accounts/models/automations/base.py:38 msgid "Can add change secret execution" msgstr "改密実行の作成" -#: accounts/models/automations/base.py:35 +#: accounts/models/automations/base.py:40 msgid "Can view gather accounts execution" msgstr "コレクションアカウントの実行を表示" -#: accounts/models/automations/base.py:36 +#: accounts/models/automations/base.py:41 msgid "Can add gather accounts execution" msgstr "回収口座作成の実行" -#: accounts/models/automations/base.py:38 +#: accounts/models/automations/base.py:43 msgid "Can view push account execution" msgstr "プッシュ アカウントの実行を表示する" -#: accounts/models/automations/base.py:39 +#: accounts/models/automations/base.py:44 msgid "Can add push account execution" msgstr "プッシュ アカウントの作成の実行" -#: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 +#: accounts/models/automations/base.py:56 accounts/models/template.py:21 +#: accounts/serializers/automations/change_secret.py:40 +msgid "Secret strategy" +msgstr "鍵ポリシー" + +#: accounts/models/automations/base.py:58 +msgid "Password rules" +msgstr "パスワードルール" + +#: accounts/models/automations/base.py:61 +msgid "SSH key change strategy" +msgstr "SSHキープッシュ方式" + +#: accounts/models/automations/base.py:71 accounts/models/base.py:36 #: accounts/serializers/account/account.py:429 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 @@ -376,64 +389,51 @@ msgstr "プッシュ アカウントの作成の実行" msgid "Secret type" msgstr "鍵の種類" -#: accounts/models/automations/change_secret.py:20 -#: accounts/models/mixins/vault.py:48 accounts/serializers/account/base.py:19 +#: accounts/models/automations/base.py:73 accounts/models/mixins/vault.py:48 +#: accounts/serializers/account/base.py:19 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:19 msgid "Secret" msgstr "ひみつ" -#: accounts/models/automations/change_secret.py:23 -#: accounts/serializers/automations/change_secret.py:40 -msgid "Secret strategy" -msgstr "鍵ポリシー" - -#: accounts/models/automations/change_secret.py:25 -msgid "Password rules" -msgstr "パスワードルール" - -#: accounts/models/automations/change_secret.py:28 -msgid "SSH key change strategy" -msgstr "SSHキープッシュ方式" - -#: accounts/models/automations/change_secret.py:64 +#: accounts/models/automations/change_secret.py:15 #: accounts/serializers/account/backup.py:34 #: accounts/serializers/automations/change_secret.py:57 msgid "Recipient" msgstr "受信者" -#: accounts/models/automations/change_secret.py:71 +#: accounts/models/automations/change_secret.py:22 msgid "Change secret automation" msgstr "自動暗号化" -#: accounts/models/automations/change_secret.py:88 +#: accounts/models/automations/change_secret.py:39 msgid "Old secret" msgstr "オリジナルキー" -#: accounts/models/automations/change_secret.py:89 +#: accounts/models/automations/change_secret.py:40 msgid "New secret" msgstr "新しい鍵" -#: accounts/models/automations/change_secret.py:90 +#: accounts/models/automations/change_secret.py:41 msgid "Date started" msgstr "開始日" -#: accounts/models/automations/change_secret.py:91 +#: accounts/models/automations/change_secret.py:42 #: assets/models/automations/base.py:116 ops/models/base.py:56 #: ops/models/celery.py:64 ops/models/job.py:229 #: terminal/models/applet/host.py:141 msgid "Date finished" msgstr "終了日" -#: accounts/models/automations/change_secret.py:93 +#: accounts/models/automations/change_secret.py:44 #: accounts/serializers/account/account.py:249 assets/const/automation.py:8 #: authentication/views/base.py:26 authentication/views/base.py:27 #: authentication/views/base.py:28 common/const/choices.py:20 msgid "Error" msgstr "間違い" -#: accounts/models/automations/change_secret.py:97 +#: accounts/models/automations/change_secret.py:48 msgid "Change secret record" msgstr "パスワード レコードの変更" @@ -529,19 +529,31 @@ msgstr "特権アカウント" #: assets/models/label.py:22 #: authentication/serializers/connect_token_secret.py:114 #: terminal/models/applet/applet.py:39 -#: terminal/models/component/endpoint.py:105 users/serializers/user.py:169 +#: terminal/models/component/endpoint.py:105 users/serializers/user.py:170 msgid "Is active" msgstr "アクティブです。" -#: accounts/models/template.py:19 xpack/plugins/cloud/models.py:325 +#: accounts/models/template.py:23 assets/models/_user.py:53 +msgid "Auto push" +msgstr "オートプッシュ" + +#: accounts/models/template.py:26 +msgid "Platforms" +msgstr "プラットフォーム" + +#: accounts/models/template.py:28 +msgid "Push params" +msgstr "パラメータをプッシュする" + +#: accounts/models/template.py:31 xpack/plugins/cloud/models.py:325 msgid "Account template" msgstr "アカウント テンプレート" -#: accounts/models/template.py:24 +#: accounts/models/template.py:36 msgid "Can view asset account template secret" msgstr "アセット アカウント テンプレートのパスワードを表示できます" -#: accounts/models/template.py:25 +#: accounts/models/template.py:37 msgid "Can change asset account template secret" msgstr "アセット アカウント テンプレートのパスワードを変更できます" @@ -762,6 +774,20 @@ msgstr "" "ヒント: 認証にユーザー名が必要ない場合は、`null`を入力します。ADアカウントの" "場合は、`username@domain`のようになります。" +#: accounts/serializers/account/template.py:27 +msgid "Secret generation strategy for account creation" +msgstr "账号创建时,密文生成策略" + +#: accounts/serializers/account/template.py:28 +msgid "Whether to automatically push the account to the asset" +msgstr "是否自动推送账号到资产" + +#: accounts/serializers/account/template.py:30 +msgid "" +"Associated platform, you can configure push parameters. If not associated, " +"default parameters will be used" +msgstr "关联平台,可以配置推送参数,如果不关联,则使用默认参数" + #: accounts/serializers/account/virtual.py:19 assets/models/_user.py:27 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 @@ -826,6 +852,10 @@ msgstr "自動タスク実行履歴" msgid "Success" msgstr "成功" +#: accounts/signal_handlers.py:47 +msgid "Push related accounts to assets: %s, by system" +msgstr "関連するアカウントをアセットにプッシュ: %s, by system" + #: accounts/tasks/automation.py:24 msgid "Account execute automation" msgstr "アカウント実行の自動化" @@ -1408,10 +1438,6 @@ msgstr "ユーザーと同じユーザー名" msgid "Protocol" msgstr "プロトコル" -#: assets/models/_user.py:53 -msgid "Auto push" -msgstr "オートプッシュ" - #: assets/models/_user.py:54 msgid "Sudo" msgstr "すど" @@ -2899,7 +2925,7 @@ msgstr "アクション" #: authentication/serializers/connection_token.py:42 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57 -#: users/serializers/user.py:96 users/serializers/user.py:173 +#: users/serializers/user.py:97 users/serializers/user.py:174 msgid "Is expired" msgstr "期限切れです" @@ -2919,8 +2945,8 @@ msgid "The {} cannot be empty" msgstr "{} 空にしてはならない" #: authentication/serializers/token.py:79 perms/serializers/permission.py:37 -#: perms/serializers/permission.py:58 users/serializers/user.py:97 -#: users/serializers/user.py:170 +#: perms/serializers/permission.py:58 users/serializers/user.py:98 +#: users/serializers/user.py:171 msgid "Is valid" msgstr "有効です" @@ -3667,11 +3693,11 @@ msgstr "投稿サイトニュース" msgid "No account available" msgstr "利用可能なアカウントがありません" -#: ops/ansible/inventory.py:260 +#: ops/ansible/inventory.py:261 msgid "Ansible disabled" msgstr "Ansible 無効" -#: ops/ansible/inventory.py:276 +#: ops/ansible/inventory.py:277 msgid "Skip hosts below:" msgstr "次のホストをスキップします: " @@ -4913,7 +4939,9 @@ msgstr "サイトURL" msgid "" "External URL, email links or other system callbacks are used to access it, " "eg: http://dev.jumpserver.org:8080" -msgstr "外部URL、メールリンクまたは他のシステムコールバックにアクセスするには、http://dev.jumpserver.org:8080などを使用します" +msgstr "" +"外部URL、メールリンクまたは他のシステムコールバックにアクセスするには、" +"http://dev.jumpserver.org:8080などを使用します" #: settings/serializers/basic.py:16 msgid "User guide url" @@ -7102,7 +7130,7 @@ msgstr "公開キー" msgid "Force enable" msgstr "強制有効" -#: users/models/user.py:799 users/serializers/user.py:171 +#: users/models/user.py:799 users/serializers/user.py:172 msgid "Is service account" msgstr "サービスアカウントです" @@ -7114,7 +7142,7 @@ msgstr "アバター" msgid "Wechat" msgstr "微信" -#: users/models/user.py:807 users/serializers/user.py:108 +#: users/models/user.py:807 users/serializers/user.py:109 msgid "Phone" msgstr "電話" @@ -7132,7 +7160,7 @@ msgid "Secret key" msgstr "秘密キー" #: users/models/user.py:828 users/serializers/profile.py:149 -#: users/serializers/user.py:168 +#: users/serializers/user.py:169 msgid "Is first login" msgstr "最初のログインです" @@ -7223,51 +7251,51 @@ msgstr "システムの役割" msgid "Org roles" msgstr "組織ロール" -#: users/serializers/user.py:89 +#: users/serializers/user.py:90 msgid "Password strategy" msgstr "パスワード戦略" -#: users/serializers/user.py:91 +#: users/serializers/user.py:92 msgid "MFA enabled" msgstr "MFA有効化" -#: users/serializers/user.py:93 +#: users/serializers/user.py:94 msgid "MFA force enabled" msgstr "MFAフォース有効化" -#: users/serializers/user.py:95 +#: users/serializers/user.py:96 msgid "Login blocked" msgstr "ログインがロックされました" -#: users/serializers/user.py:98 users/serializers/user.py:177 +#: users/serializers/user.py:99 users/serializers/user.py:178 msgid "Is OTP bound" msgstr "仮想MFAがバインドされているか" -#: users/serializers/user.py:100 +#: users/serializers/user.py:101 msgid "Can public key authentication" msgstr "公開鍵認証が可能" -#: users/serializers/user.py:172 +#: users/serializers/user.py:173 msgid "Is org admin" msgstr "組織管理者です" -#: users/serializers/user.py:174 +#: users/serializers/user.py:175 msgid "Avatar url" msgstr "アバターURL" -#: users/serializers/user.py:178 +#: users/serializers/user.py:179 msgid "MFA level" msgstr "MFA レベル" -#: users/serializers/user.py:284 +#: users/serializers/user.py:285 msgid "Select users" msgstr "ユーザーの選択" -#: users/serializers/user.py:285 +#: users/serializers/user.py:286 msgid "For security, only list several users" msgstr "セキュリティのために、複数のユーザーのみをリストします" -#: users/serializers/user.py:318 +#: users/serializers/user.py:319 msgid "name not unique" msgstr "名前が一意ではない" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index e3efce31d..5d0978e32 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:12935fb4f142398ebf74c775dc0f0b094b22cd5dc4e379882a65ef220914d459 -size 126702 +oid sha256:e10dc250ba5d57d7edce01403cd66efb29be1036c67ce026bad55322792d569c +size 127493 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index fb5778175..4c1db1387 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: 2023-08-17 18:24+0800\n" +"POT-Creation-Date: 2023-08-28 10:55+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -27,7 +27,7 @@ msgstr "参数 'action' 必须是 [{}]" #: authentication/confirm/password.py:9 authentication/forms.py:32 #: authentication/templates/authentication/login.html:286 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:47 -#: users/forms/profile.py:22 users/serializers/user.py:104 +#: 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/cloud/serializers/account_attrs.py:28 @@ -216,7 +216,7 @@ msgstr "HashiCorp Vault" msgid "Asset" msgstr "资产" -#: accounts/models/account.py:52 accounts/models/template.py:15 +#: accounts/models/account.py:52 accounts/models/template.py:17 #: accounts/serializers/account/account.py:209 #: accounts/serializers/account/account.py:257 #: accounts/serializers/account/template.py:16 @@ -330,43 +330,56 @@ msgstr "是否成功" msgid "Account backup execution" msgstr "账号备份执行" -#: accounts/models/automations/base.py:15 +#: accounts/models/automations/base.py:20 msgid "Account automation task" msgstr "账号自动化任务" -#: accounts/models/automations/base.py:29 +#: accounts/models/automations/base.py:34 msgid "Automation execution" msgstr "自动化执行" -#: accounts/models/automations/base.py:30 +#: accounts/models/automations/base.py:35 msgid "Automation executions" msgstr "自动化执行" -#: accounts/models/automations/base.py:32 +#: accounts/models/automations/base.py:37 msgid "Can view change secret execution" msgstr "查看改密执行" -#: accounts/models/automations/base.py:33 +#: accounts/models/automations/base.py:38 msgid "Can add change secret execution" msgstr "创建改密执行" -#: accounts/models/automations/base.py:35 +#: accounts/models/automations/base.py:40 msgid "Can view gather accounts execution" msgstr "查看收集账号执行" -#: accounts/models/automations/base.py:36 +#: accounts/models/automations/base.py:41 msgid "Can add gather accounts execution" msgstr "创建收集账号执行" -#: accounts/models/automations/base.py:38 +#: accounts/models/automations/base.py:43 msgid "Can view push account execution" msgstr "查看推送账号执行" -#: accounts/models/automations/base.py:39 +#: accounts/models/automations/base.py:44 msgid "Can add push account execution" msgstr "创建推送账号执行" -#: accounts/models/automations/change_secret.py:18 accounts/models/base.py:36 +#: accounts/models/automations/base.py:56 accounts/models/template.py:21 +#: accounts/serializers/automations/change_secret.py:40 +msgid "Secret strategy" +msgstr "密文策略" + +#: accounts/models/automations/base.py:58 +msgid "Password rules" +msgstr "密码规则" + +#: accounts/models/automations/base.py:61 +msgid "SSH key change strategy" +msgstr "SSH 密钥推送方式" + +#: accounts/models/automations/base.py:71 accounts/models/base.py:36 #: accounts/serializers/account/account.py:429 #: accounts/serializers/account/base.py:16 #: accounts/serializers/automations/change_secret.py:46 @@ -375,64 +388,51 @@ msgstr "创建推送账号执行" msgid "Secret type" msgstr "密文类型" -#: accounts/models/automations/change_secret.py:20 -#: accounts/models/mixins/vault.py:48 accounts/serializers/account/base.py:19 +#: accounts/models/automations/base.py:73 accounts/models/mixins/vault.py:48 +#: accounts/serializers/account/base.py:19 #: authentication/models/temp_token.py:10 #: authentication/templates/authentication/_access_key_modal.html:31 #: settings/serializers/auth/radius.py:19 msgid "Secret" msgstr "密钥" -#: accounts/models/automations/change_secret.py:23 -#: accounts/serializers/automations/change_secret.py:40 -msgid "Secret strategy" -msgstr "密文策略" - -#: accounts/models/automations/change_secret.py:25 -msgid "Password rules" -msgstr "密码规则" - -#: accounts/models/automations/change_secret.py:28 -msgid "SSH key change strategy" -msgstr "SSH 密钥推送方式" - -#: accounts/models/automations/change_secret.py:64 +#: accounts/models/automations/change_secret.py:15 #: accounts/serializers/account/backup.py:34 #: accounts/serializers/automations/change_secret.py:57 msgid "Recipient" msgstr "收件人" -#: accounts/models/automations/change_secret.py:71 +#: accounts/models/automations/change_secret.py:22 msgid "Change secret automation" msgstr "自动化改密" -#: accounts/models/automations/change_secret.py:88 +#: accounts/models/automations/change_secret.py:39 msgid "Old secret" msgstr "原密钥" -#: accounts/models/automations/change_secret.py:89 +#: accounts/models/automations/change_secret.py:40 msgid "New secret" msgstr "新密钥" -#: accounts/models/automations/change_secret.py:90 +#: accounts/models/automations/change_secret.py:41 msgid "Date started" msgstr "开始日期" -#: accounts/models/automations/change_secret.py:91 +#: accounts/models/automations/change_secret.py:42 #: assets/models/automations/base.py:116 ops/models/base.py:56 #: ops/models/celery.py:64 ops/models/job.py:229 #: terminal/models/applet/host.py:141 msgid "Date finished" msgstr "结束日期" -#: accounts/models/automations/change_secret.py:93 +#: accounts/models/automations/change_secret.py:44 #: accounts/serializers/account/account.py:249 assets/const/automation.py:8 #: authentication/views/base.py:26 authentication/views/base.py:27 #: authentication/views/base.py:28 common/const/choices.py:20 msgid "Error" msgstr "错误" -#: accounts/models/automations/change_secret.py:97 +#: accounts/models/automations/change_secret.py:48 msgid "Change secret record" msgstr "改密记录" @@ -528,21 +528,31 @@ msgstr "特权账号" #: assets/models/label.py:22 #: authentication/serializers/connect_token_secret.py:114 #: terminal/models/applet/applet.py:39 -#: terminal/models/component/endpoint.py:105 users/serializers/user.py:169 +#: terminal/models/component/endpoint.py:105 users/serializers/user.py:170 msgid "Is active" msgstr "激活" -#: accounts/models/template.py:19 xpack/plugins/cloud/models.py:325 +#: accounts/models/template.py:23 assets/models/_user.py:53 +msgid "Auto push" +msgstr "自动推送" + +#: accounts/models/template.py:26 +msgid "Platforms" +msgstr "系统平台" + +#: accounts/models/template.py:28 +msgid "Push params" +msgstr "账号推送参数" + +#: accounts/models/template.py:31 xpack/plugins/cloud/models.py:325 msgid "Account template" msgstr "账号模版" -# msgid "Account template" -# msgstr "账号模版" -#: accounts/models/template.py:24 +#: accounts/models/template.py:36 msgid "Can view asset account template secret" msgstr "可以查看资产账号模版密码" -#: accounts/models/template.py:25 +#: accounts/models/template.py:37 msgid "Can change asset account template secret" msgstr "可以更改资产账号模版密码" @@ -762,6 +772,20 @@ msgstr "" "提示: 如果认证时不需要用户名,可填写为 null, 如果是 AD 账号,格式为 " "username@domain" +#: accounts/serializers/account/template.py:27 +msgid "Secret generation strategy for account creation" +msgstr "密码生成策略,用于账号创建时,设置密码" + +#: accounts/serializers/account/template.py:28 +msgid "Whether to automatically push the account to the asset" +msgstr "是否自动推送账号到资产" + +#: accounts/serializers/account/template.py:30 +msgid "" +"Associated platform, you can configure push parameters. If not associated, " +"default parameters will be used" +msgstr "关联平台,可配置推送参数,如果不关联,将使用默认参数" + #: accounts/serializers/account/virtual.py:19 assets/models/_user.py:27 #: assets/models/cmd_filter.py:40 assets/models/cmd_filter.py:88 #: assets/models/group.py:20 common/db/models.py:36 ops/models/adhoc.py:26 @@ -826,6 +850,11 @@ msgstr "自动化任务执行历史" msgid "Success" msgstr "成功" +#: accounts/signal_handlers.py:47 +#, fpython-format +msgid "Push related accounts to assets: %s, by system" +msgstr "推送账号到资产: %s, 由系统执行" + #: accounts/tasks/automation.py:24 msgid "Account execute automation" msgstr "账号执行自动化" @@ -1406,10 +1435,6 @@ msgstr "用户名与用户相同" msgid "Protocol" msgstr "协议" -#: assets/models/_user.py:53 -msgid "Auto push" -msgstr "自动推送" - #: assets/models/_user.py:54 msgid "Sudo" msgstr "Sudo" @@ -2872,7 +2897,7 @@ msgstr "动作" #: authentication/serializers/connection_token.py:42 #: perms/serializers/permission.py:38 perms/serializers/permission.py:57 -#: users/serializers/user.py:96 users/serializers/user.py:173 +#: users/serializers/user.py:97 users/serializers/user.py:174 msgid "Is expired" msgstr "已过期" @@ -2892,8 +2917,8 @@ msgid "The {} cannot be empty" msgstr "{} 不能为空" #: authentication/serializers/token.py:79 perms/serializers/permission.py:37 -#: perms/serializers/permission.py:58 users/serializers/user.py:97 -#: users/serializers/user.py:170 +#: perms/serializers/permission.py:58 users/serializers/user.py:98 +#: users/serializers/user.py:171 msgid "Is valid" msgstr "是否有效" @@ -3625,11 +3650,11 @@ msgstr "发布站内消息" msgid "No account available" msgstr "无可用账号" -#: ops/ansible/inventory.py:260 +#: ops/ansible/inventory.py:261 msgid "Ansible disabled" msgstr "Ansible 已禁用" -#: ops/ansible/inventory.py:276 +#: ops/ansible/inventory.py:277 msgid "Skip hosts below:" msgstr "跳过以下主机: " @@ -4867,7 +4892,9 @@ msgstr "当前站点 URL" msgid "" "External URL, email links or other system callbacks are used to access it, " "eg: http://dev.jumpserver.org:8080" -msgstr "外部可访问的 URL, 用于邮件链接或其它系统回调, 例如: http://dev.jumpserver.org:8080" +msgstr "" +"外部可访问的 URL, 用于邮件链接或其它系统回调, 例如: http://dev.jumpserver." +"org:8080" #: settings/serializers/basic.py:16 msgid "User guide url" @@ -7007,7 +7034,7 @@ msgstr "SSH公钥" msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:799 users/serializers/user.py:171 +#: users/models/user.py:799 users/serializers/user.py:172 msgid "Is service account" msgstr "服务账号" @@ -7019,7 +7046,7 @@ msgstr "头像" msgid "Wechat" msgstr "微信" -#: users/models/user.py:807 users/serializers/user.py:108 +#: users/models/user.py:807 users/serializers/user.py:109 msgid "Phone" msgstr "手机" @@ -7037,7 +7064,7 @@ msgid "Secret key" msgstr "Secret key" #: users/models/user.py:828 users/serializers/profile.py:149 -#: users/serializers/user.py:168 +#: users/serializers/user.py:169 msgid "Is first login" msgstr "首次登录" @@ -7128,51 +7155,51 @@ msgstr "系统角色" msgid "Org roles" msgstr "组织角色" -#: users/serializers/user.py:89 +#: users/serializers/user.py:90 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:91 +#: users/serializers/user.py:92 msgid "MFA enabled" msgstr "MFA 已启用" -#: users/serializers/user.py:93 +#: users/serializers/user.py:94 msgid "MFA force enabled" msgstr "强制 MFA" -#: users/serializers/user.py:95 +#: users/serializers/user.py:96 msgid "Login blocked" msgstr "登录被锁定" -#: users/serializers/user.py:98 users/serializers/user.py:177 +#: users/serializers/user.py:99 users/serializers/user.py:178 msgid "Is OTP bound" msgstr "是否绑定了虚拟 MFA" -#: users/serializers/user.py:100 +#: users/serializers/user.py:101 msgid "Can public key authentication" msgstr "可以使用公钥认证" -#: users/serializers/user.py:172 +#: users/serializers/user.py:173 msgid "Is org admin" msgstr "组织管理员" -#: users/serializers/user.py:174 +#: users/serializers/user.py:175 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:178 +#: users/serializers/user.py:179 msgid "MFA level" msgstr "MFA 级别" -#: users/serializers/user.py:284 +#: users/serializers/user.py:285 msgid "Select users" msgstr "选择用户" -#: users/serializers/user.py:285 +#: users/serializers/user.py:286 msgid "For security, only list several users" msgstr "为了安全,仅列出几个用户" -#: users/serializers/user.py:318 +#: users/serializers/user.py:319 msgid "name not unique" msgstr "名称重复" diff --git a/apps/terminal/api/session/session.py b/apps/terminal/api/session/session.py index 730fc99eb..1782b0207 100644 --- a/apps/terminal/api/session/session.py +++ b/apps/terminal/api/session/session.py @@ -19,7 +19,7 @@ from rest_framework.response import Response from common.api import AsyncApiMixin from common.const.http import GET from common.drf.filters import BaseFilterSet -from common.drf.filters import DatetimeRangeFilter +from common.drf.filters import DatetimeRangeFilterBackend from common.drf.renders import PassthroughRenderer from common.storage.replay import ReplayStorageHandler from common.utils import data_to_json, is_uuid @@ -84,7 +84,7 @@ class SessionViewSet(OrgBulkModelViewSet): date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] - extra_filter_backends = [DatetimeRangeFilter] + extra_filter_backends = [DatetimeRangeFilterBackend] rbac_perms = { 'download': ['terminal.download_sessionreplay'] }