perf: 优化账号创建 (#11440)

* feat: 支持账号模版自动推送
* perf: 修改模版
* perf: 优化账号创建

---------

Co-authored-by: ibuler <ibuler@qq.com>
pull/11442/head
fit2bot 2023-08-28 15:43:45 +08:00 committed by GitHub
parent 72bb5a4037
commit 859268f7f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 527 additions and 339 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 相关的信号 """

View File

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

View File

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

View File

@ -1,6 +1,5 @@
from .action import *
from .common import *
from .filter import *
from .generic import *
from .mixin import *
from .patch import *

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9efbbfac755784f3aba000f8e56fe697eb983b0157b832e4ae9970b477bd916
size 154962
oid sha256:f96558642be3e37f62de0ef8772e8ee0d5cf008bafc5063984f3749ab8489323
size 155810

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 "名前が一意ではない"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:12935fb4f142398ebf74c775dc0f0b094b22cd5dc4e379882a65ef220914d459
size 126702
oid sha256:e10dc250ba5d57d7edce01403cd66efb29be1036c67ce026bad55322792d569c
size 127493

View File

@ -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 <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\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 "名称重复"

View File

@ -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']
}