Merge pull request #7511 from jumpserver/dev

v2.18.0-rc2
pull/7561/head
Jiangjie.Bai 2022-01-17 19:21:39 +08:00 committed by GitHub
commit 6bd597eadd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 610 additions and 364 deletions

View File

@ -44,11 +44,7 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):
queryset = Account.objects.all() \ queryset = Account.get_queryset()
.annotate(type=F('app__type')) \
.annotate(app_display=F('app__name')) \
.annotate(systemuser_display=F('systemuser__name')) \
.annotate(category=F('app__category'))
return queryset return queryset

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from django.db.models import F
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty from common.utils import lazyproperty
@ -7,8 +8,12 @@ from assets.models.base import BaseUser
class Account(BaseUser): class Account(BaseUser):
app = models.ForeignKey('applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database')) app = models.ForeignKey(
systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) 'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database')
)
systemuser = models.ForeignKey(
'assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")
)
version = models.IntegerField(default=1, verbose_name=_('Version')) version = models.IntegerField(default=1, verbose_name=_('Version'))
history = HistoricalRecords() history = HistoricalRecords()
@ -60,6 +65,10 @@ class Account(BaseUser):
def type(self): def type(self):
return self.app.type return self.app.type
@lazyproperty
def attrs(self):
return self.app.attrs
@lazyproperty @lazyproperty
def app_display(self): def app_display(self):
return self.systemuser.name return self.systemuser.name
@ -84,5 +93,14 @@ class Account(BaseUser):
app = '*' app = '*'
return '{}@{}'.format(username, app) return '{}@{}'.format(username, app)
@classmethod
def get_queryset(cls):
queryset = cls.objects.all() \
.annotate(type=F('app__type')) \
.annotate(app_display=F('app__name')) \
.annotate(systemuser_display=F('systemuser__name')) \
.annotate(category=F('app__category'))
return queryset
def __str__(self): def __str__(self):
return self.smart_name return self.smart_name

View File

@ -1,6 +1,5 @@
# coding: utf-8 # coding: utf-8
# #
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -9,7 +8,8 @@ from assets.serializers.base import AuthSerializerMixin
from common.drf.serializers import MethodSerializer from common.drf.serializers import MethodSerializer
from .attrs import ( from .attrs import (
category_serializer_classes_mapping, category_serializer_classes_mapping,
type_serializer_classes_mapping type_serializer_classes_mapping,
type_secret_serializer_classes_mapping
) )
from .. import models from .. import models
from .. import const from .. import const
@ -23,17 +23,28 @@ __all__ = [
class AppSerializerMixin(serializers.Serializer): class AppSerializerMixin(serializers.Serializer):
attrs = MethodSerializer() attrs = MethodSerializer()
@property
def app(self):
if isinstance(self.instance, models.Application):
instance = self.instance
else:
instance = None
return instance
def get_attrs_serializer(self): def get_attrs_serializer(self):
default_serializer = serializers.Serializer(read_only=True) default_serializer = serializers.Serializer(read_only=True)
if isinstance(self.instance, models.Application): instance = self.app
_type = self.instance.type if instance:
_category = self.instance.category _type = instance.type
_category = instance.category
else: else:
_type = self.context['request'].query_params.get('type') _type = self.context['request'].query_params.get('type')
_category = self.context['request'].query_params.get('category') _category = self.context['request'].query_params.get('category')
if _type: if _type:
serializer_class = type_serializer_classes_mapping.get(_type) if isinstance(self, AppAccountSecretSerializer):
serializer_class = type_secret_serializer_classes_mapping.get(_type)
else:
serializer_class = type_serializer_classes_mapping.get(_type)
elif _category: elif _category:
serializer_class = category_serializer_classes_mapping.get(_category) serializer_class = category_serializer_classes_mapping.get(_category)
else: else:
@ -84,11 +95,13 @@ class MiniAppSerializer(serializers.ModelSerializer):
fields = AppSerializer.Meta.fields_mini fields = AppSerializer.Meta.fields_mini
class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResourceModelSerializer):
category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True) category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True)
category_display = serializers.SerializerMethodField(label=_('Category display')) category_display = serializers.SerializerMethodField(label=_('Category display'))
type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True) type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True)
type_display = serializers.SerializerMethodField(label=_('Type display')) type_display = serializers.SerializerMethodField(label=_('Type display'))
date_created = serializers.DateTimeField(label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True)
date_updated = serializers.DateTimeField(label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True)
category_mapper = dict(const.AppCategory.choices) category_mapper = dict(const.AppCategory.choices)
type_mapper = dict(const.AppType.choices) type_mapper = dict(const.AppType.choices)
@ -96,10 +109,11 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = models.Account model = models.Account
fields_mini = ['id', 'username', 'version'] fields_mini = ['id', 'username', 'version']
fields_write_only = ['password', 'private_key', 'passphrase'] fields_write_only = ['password', 'private_key', 'public_key', 'passphrase']
fields_other = ['date_created', 'date_updated']
fields_fk = ['systemuser', 'systemuser_display', 'app', 'app_display'] fields_fk = ['systemuser', 'systemuser_display', 'app', 'app_display']
fields = fields_mini + fields_fk + fields_write_only + [ fields = fields_mini + fields_fk + fields_write_only + fields_other + [
'type', 'type_display', 'category', 'category_display', 'type', 'type_display', 'category', 'category_display', 'attrs'
] ]
extra_kwargs = { extra_kwargs = {
'username': {'default': '', 'required': False}, 'username': {'default': '', 'required': False},
@ -112,6 +126,14 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'ignore_conflicts': True 'ignore_conflicts': True
} }
@property
def app(self):
if isinstance(self.instance, models.Account):
instance = self.instance.app
else:
instance = None
return instance
def get_category_display(self, obj): def get_category_display(self, obj):
return self.category_mapper.get(obj.category) return self.category_mapper.get(obj.category)
@ -131,6 +153,10 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
class AppAccountSecretSerializer(AppAccountSerializer): class AppAccountSecretSerializer(AppAccountSerializer):
class Meta(AppAccountSerializer.Meta): class Meta(AppAccountSerializer.Meta):
fields_backup = [
'id', 'app_display', 'attrs', 'username', 'password', 'private_key',
'public_key', 'date_created', 'date_updated', 'version'
]
extra_kwargs = { extra_kwargs = {
'password': {'write_only': False}, 'password': {'write_only': False},
'private_key': {'write_only': False}, 'private_key': {'write_only': False},

View File

@ -1,7 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
__all__ = ['CloudSerializer'] __all__ = ['CloudSerializer']

View File

@ -10,7 +10,6 @@ from assets.models import Asset
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['RemoteAppSerializer'] __all__ = ['RemoteAppSerializer']

View File

@ -3,8 +3,7 @@ from rest_framework import serializers
from ..application_category import RemoteAppSerializer from ..application_category import RemoteAppSerializer
__all__ = ['ChromeSerializer', 'ChromeSecretSerializer']
__all__ = ['ChromeSerializer']
class ChromeSerializer(RemoteAppSerializer): class ChromeSerializer(RemoteAppSerializer):
@ -17,10 +16,16 @@ class ChromeSerializer(RemoteAppSerializer):
max_length=128, allow_blank=True, required=False, label=_('Target URL'), allow_null=True, max_length=128, allow_blank=True, required=False, label=_('Target URL'), allow_null=True,
) )
chrome_username = serializers.CharField( chrome_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username'), allow_null=True, max_length=128, allow_blank=True, required=False, label=_('Chrome username'), allow_null=True,
) )
chrome_password = serializers.CharField( chrome_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), max_length=128, allow_blank=True, required=False, write_only=True, label=_('Chrome password'),
allow_null=True allow_null=True
) )
class ChromeSecretSerializer(ChromeSerializer):
chrome_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Chrome password'),
allow_null=True
)

View File

@ -1,11 +1,9 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from ..application_category import RemoteAppSerializer from ..application_category import RemoteAppSerializer
__all__ = ['CustomSerializer', 'CustomSecretSerializer']
__all__ = ['CustomSerializer']
class CustomSerializer(RemoteAppSerializer): class CustomSerializer(RemoteAppSerializer):
@ -18,10 +16,17 @@ class CustomSerializer(RemoteAppSerializer):
allow_null=True, allow_null=True,
) )
custom_username = serializers.CharField( custom_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username'), max_length=128, allow_blank=True, required=False, label=_('Custom Username'),
allow_null=True, allow_null=True,
) )
custom_password = serializers.CharField( custom_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), max_length=128, allow_blank=True, required=False, write_only=True, label=_('Custom password'),
allow_null=True,
)
class CustomSecretSerializer(RemoteAppSerializer):
custom_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Custom password'),
allow_null=True, allow_null=True,
) )

View File

@ -1,6 +1,5 @@
from ..application_category import CloudSerializer from ..application_category import CloudSerializer
__all__ = ['K8SSerializer'] __all__ = ['K8SSerializer']

View File

@ -1,6 +1,5 @@
from .mysql import MySQLSerializer from .mysql import MySQLSerializer
__all__ = ['MariaDBSerializer'] __all__ = ['MariaDBSerializer']

View File

@ -3,13 +3,9 @@ from django.utils.translation import ugettext_lazy as _
from ..application_category import DBSerializer from ..application_category import DBSerializer
__all__ = ['MySQLSerializer'] __all__ = ['MySQLSerializer']
class MySQLSerializer(DBSerializer): class MySQLSerializer(DBSerializer):
port = serializers.IntegerField(default=3306, label=_('Port'), allow_null=True) port = serializers.IntegerField(default=3306, label=_('Port'), allow_null=True)

View File

@ -3,8 +3,7 @@ from rest_framework import serializers
from ..application_category import RemoteAppSerializer from ..application_category import RemoteAppSerializer
__all__ = ['MySQLWorkbenchSerializer', 'MySQLWorkbenchSecretSerializer']
__all__ = ['MySQLWorkbenchSerializer']
class MySQLWorkbenchSerializer(RemoteAppSerializer): class MySQLWorkbenchSerializer(RemoteAppSerializer):
@ -27,10 +26,17 @@ class MySQLWorkbenchSerializer(RemoteAppSerializer):
allow_null=True, allow_null=True,
) )
mysql_workbench_username = serializers.CharField( mysql_workbench_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username'), max_length=128, allow_blank=True, required=False, label=_('Mysql workbench username'),
allow_null=True, allow_null=True,
) )
mysql_workbench_password = serializers.CharField( mysql_workbench_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), max_length=128, allow_blank=True, required=False, write_only=True, label=_('Mysql workbench password'),
allow_null=True,
)
class MySQLWorkbenchSecretSerializer(RemoteAppSerializer):
mysql_workbench_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Mysql workbench password'),
allow_null=True, allow_null=True,
) )

View File

@ -3,10 +3,8 @@ from django.utils.translation import ugettext_lazy as _
from ..application_category import DBSerializer from ..application_category import DBSerializer
__all__ = ['OracleSerializer'] __all__ = ['OracleSerializer']
class OracleSerializer(DBSerializer): class OracleSerializer(DBSerializer):
port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True) port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True)

View File

@ -3,10 +3,8 @@ from django.utils.translation import ugettext_lazy as _
from ..application_category import DBSerializer from ..application_category import DBSerializer
__all__ = ['PostgreSerializer'] __all__ = ['PostgreSerializer']
class PostgreSerializer(DBSerializer): class PostgreSerializer(DBSerializer):
port = serializers.IntegerField(default=5432, label=_('Port'), allow_null=True) port = serializers.IntegerField(default=5432, label=_('Port'), allow_null=True)

View File

@ -3,13 +3,9 @@ from django.utils.translation import ugettext_lazy as _
from ..application_category import DBSerializer from ..application_category import DBSerializer
__all__ = ['RedisSerializer'] __all__ = ['RedisSerializer']
class RedisSerializer(DBSerializer): class RedisSerializer(DBSerializer):
port = serializers.IntegerField(default=6379, label=_('Port'), allow_null=True) port = serializers.IntegerField(default=6379, label=_('Port'), allow_null=True)

View File

@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _
from ..application_category import DBSerializer from ..application_category import DBSerializer
__all__ = ['SQLServerSerializer'] __all__ = ['SQLServerSerializer']

View File

@ -3,8 +3,7 @@ from rest_framework import serializers
from ..application_category import RemoteAppSerializer from ..application_category import RemoteAppSerializer
__all__ = ['VMwareClientSerializer', 'VMwareClientSecretSerializer']
__all__ = ['VMwareClientSerializer']
class VMwareClientSerializer(RemoteAppSerializer): class VMwareClientSerializer(RemoteAppSerializer):
@ -23,10 +22,17 @@ class VMwareClientSerializer(RemoteAppSerializer):
allow_null=True allow_null=True
) )
vmware_username = serializers.CharField( vmware_username = serializers.CharField(
max_length=128, allow_blank=True, required=False, label=_('Username'), max_length=128, allow_blank=True, required=False, label=_('Vmware username'),
allow_null=True allow_null=True
) )
vmware_password = serializers.CharField( vmware_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), max_length=128, allow_blank=True, required=False, write_only=True, label=_('Vmware password'),
allow_null=True
)
class VMwareClientSecretSerializer(RemoteAppSerializer):
vmware_password = serializers.CharField(
max_length=128, allow_blank=True, required=False, read_only=True, label=_('Vmware password'),
allow_null=True allow_null=True
) )

View File

@ -1,15 +1,15 @@
from rest_framework import serializers import copy
from applications import const from applications import const
from . import application_category, application_type from . import application_category, application_type
__all__ = [ __all__ = [
'category_serializer_classes_mapping', 'category_serializer_classes_mapping',
'type_serializer_classes_mapping', 'type_serializer_classes_mapping',
'get_serializer_class_by_application_type', 'get_serializer_class_by_application_type',
'type_secret_serializer_classes_mapping'
] ]
# define `attrs` field `category serializers mapping` # define `attrs` field `category serializers mapping`
# --------------------------------------------------- # ---------------------------------------------------
@ -30,15 +30,32 @@ type_serializer_classes_mapping = {
const.AppType.oracle.value: application_type.OracleSerializer, const.AppType.oracle.value: application_type.OracleSerializer,
const.AppType.pgsql.value: application_type.PostgreSerializer, const.AppType.pgsql.value: application_type.PostgreSerializer,
const.AppType.sqlserver.value: application_type.SQLServerSerializer, const.AppType.sqlserver.value: application_type.SQLServerSerializer,
# remote-app
const.AppType.chrome.value: application_type.ChromeSerializer,
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
const.AppType.vmware_client.value: application_type.VMwareClientSerializer,
const.AppType.custom.value: application_type.CustomSerializer,
# cloud # cloud
const.AppType.k8s.value: application_type.K8SSerializer const.AppType.k8s.value: application_type.K8SSerializer
} }
remote_app_serializer_classes_mapping = {
# remote-app
const.AppType.chrome.value: application_type.ChromeSerializer,
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer,
const.AppType.vmware_client.value: application_type.VMwareClientSerializer,
const.AppType.custom.value: application_type.CustomSerializer
}
type_serializer_classes_mapping.update(remote_app_serializer_classes_mapping)
remote_app_secret_serializer_classes_mapping = {
# remote-app
const.AppType.chrome.value: application_type.ChromeSecretSerializer,
const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSecretSerializer,
const.AppType.vmware_client.value: application_type.VMwareClientSecretSerializer,
const.AppType.custom.value: application_type.CustomSecretSerializer
}
type_secret_serializer_classes_mapping = copy.deepcopy(type_serializer_classes_mapping)
type_secret_serializer_classes_mapping.update(remote_app_secret_serializer_classes_mapping)
def get_serializer_class_by_application_type(_application_type): def get_serializer_class_by_application_type(_application_type):
return type_serializer_classes_mapping.get(_application_type) return type_serializer_classes_mapping.get(_application_type)

View File

@ -64,9 +64,7 @@ class AccountViewSet(OrgBulkModelViewSet):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() \ queryset = AuthBook.get_queryset()
.annotate(ip=F('asset__ip')) \
.annotate(hostname=F('asset__hostname'))
return queryset return queryset
@action(methods=['post'], detail=True, url_path='verify') @action(methods=['post'], detail=True, url_path='verify')

View File

@ -32,8 +32,8 @@ class AccountBackupPlanExecutionViewSet(
mixins.RetrieveModelMixin, viewsets.GenericViewSet mixins.RetrieveModelMixin, viewsets.GenericViewSet
): ):
serializer_class = serializers.AccountBackupPlanExecutionSerializer serializer_class = serializers.AccountBackupPlanExecutionSerializer
search_fields = ('trigger', 'plan_id') search_fields = ('trigger',)
filterset_fields = search_fields filterset_fields = ('trigger', 'plan_id')
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
def get_queryset(self): def get_queryset(self):

View File

@ -2,6 +2,7 @@
# #
from django.db import models from django.db import models
from django.db.models import F
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
@ -116,6 +117,15 @@ class AuthBook(BaseUser, AbsConnectivity):
self.asset.save() self.asset.save()
logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser))
@classmethod
def get_queryset(cls):
queryset = cls.objects.all() \
.annotate(ip=F('asset__ip')) \
.annotate(hostname=F('asset__hostname')) \
.annotate(platform=F('asset__platform__name')) \
.annotate(protocols=F('asset__protocols'))
return queryset
def __str__(self): def __str__(self):
return self.smart_name return self.smart_name

View File

@ -11,10 +11,18 @@ from .utils import validate_password_contains_left_double_curly_bracket
class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
ip = serializers.ReadOnlyField(label=_("IP")) ip = serializers.ReadOnlyField(label=_("IP"))
hostname = serializers.ReadOnlyField(label=_("Hostname")) hostname = serializers.ReadOnlyField(label=_("Hostname"))
platform = serializers.ReadOnlyField(label=_("Platform"))
protocols = serializers.SerializerMethodField(label=_("Protocols"))
date_created = serializers.DateTimeField(
label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True
)
date_updated = serializers.DateTimeField(
label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True
)
class Meta: class Meta:
model = AuthBook model = AuthBook
fields_mini = ['id', 'username', 'ip', 'hostname', 'version'] fields_mini = ['id', 'username', 'ip', 'hostname', 'platform', 'protocols', 'version']
fields_write_only = ['password', 'private_key', "public_key", 'passphrase'] fields_write_only = ['password', 'private_key', "public_key", 'passphrase']
fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment'] fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment']
fields_small = fields_mini + fields_write_only + fields_other fields_small = fields_mini + fields_write_only + fields_other
@ -32,6 +40,9 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
} }
ref_name = 'AssetAccountSerializer' ref_name = 'AssetAccountSerializer'
def get_protocols(self, v):
return v.protocols.replace(' ', ', ')
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
@ -45,6 +56,10 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
class AccountSecretSerializer(AccountSerializer): class AccountSecretSerializer(AccountSerializer):
class Meta(AccountSerializer.Meta): class Meta(AccountSerializer.Meta):
fields_backup = [
'hostname', 'ip', 'platform', 'protocols', 'username', 'password',
'private_key', 'public_key', 'date_created', 'date_updated', 'version'
]
extra_kwargs = { extra_kwargs = {
'password': {'write_only': False}, 'password': {'write_only': False},
'private_key': {'write_only': False}, 'private_key': {'write_only': False},

View File

@ -5,8 +5,6 @@ from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from users.models import User, UserGroup
from perms.models import AssetPermission
from ..models import Asset, Node, Platform, SystemUser from ..models import Asset, Node, Platform, SystemUser
__all__ = [ __all__ = [

View File

@ -1,15 +1,18 @@
import os import os
import time import time
import pandas as pd import pandas as pd
from collections import defaultdict from collections import defaultdict, OrderedDict
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from assets.models import AuthBook, Asset, BaseUser, ProtocolsMixin from assets.models import AuthBook
from assets.serializers import AccountSecretSerializer
from assets.notifications import AccountBackupExecutionTaskMsg from assets.notifications import AccountBackupExecutionTaskMsg
from applications.models import Account, Application from applications.models import Account
from applications.const import AppType from applications.const import AppType
from applications.serializers import AppAccountSecretSerializer
from users.models import User from users.models import User
from common.utils import get_logger from common.utils import get_logger
from common.utils.timezone import local_now_display from common.utils.timezone import local_now_display
@ -20,7 +23,46 @@ logger = get_logger(__file__)
PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
class AssetAccountHandler: class BaseAccountHandler:
@classmethod
def unpack_data(cls, serializer_data, data=None):
if data is None:
data = {}
for k, v in serializer_data.items():
if isinstance(v, OrderedDict):
cls.unpack_data(v, data)
else:
data[k] = v
return data
@classmethod
def get_header_fields(cls, serializer: serializers.Serializer):
try:
backup_fields = getattr(serializer, 'Meta').fields_backup
except AttributeError:
backup_fields = serializer.fields.keys()
header_fields = {}
for field in backup_fields:
v = serializer.fields[field]
if isinstance(v, serializers.Serializer):
_fields = cls.get_header_fields(v)
header_fields.update(_fields)
else:
header_fields[field] = v.label
return header_fields
@classmethod
def create_row(cls, account, serializer_cls):
serializer = serializer_cls(account)
data = cls.unpack_data(serializer.data)
header_fields = cls.get_header_fields(serializer)
row_dict = {}
for field, header_name in header_fields.items():
row_dict[header_name] = data[field]
return row_dict
class AssetAccountHandler(BaseAccountHandler):
@staticmethod @staticmethod
def get_filename(plan_name): def get_filename(plan_name):
filename = os.path.join( filename = os.path.join(
@ -28,32 +70,24 @@ class AssetAccountHandler:
) )
return filename return filename
@staticmethod @classmethod
def create_df(): def create_df(cls):
df_dict = defaultdict(list) df_dict = defaultdict(list)
label_key = AuthBook._meta.verbose_name sheet_name = AuthBook._meta.verbose_name
accounts = AuthBook.objects.all().prefetch_related('systemuser', 'asset') accounts = AuthBook.get_queryset()
for account in accounts: for account in accounts:
account.load_auth() account.load_auth()
protocol = account.asset.protocol row = cls.create_row(account, AccountSecretSerializer)
protocol_label = getattr(ProtocolsMixin.Protocol, protocol).label df_dict[sheet_name].append(row)
row = {
getattr(Asset, 'hostname').field.verbose_name: account.asset.hostname,
getattr(Asset, 'ip').field.verbose_name: account.asset.ip,
}
secret_row = AccountBackupHandler.create_secret_row(account)
row.update(secret_row)
row.update({
getattr(Asset, 'protocol').field.verbose_name: protocol_label,
getattr(AuthBook, 'version').field.verbose_name: account.version
})
df_dict[label_key].append(row)
for k, v in df_dict.items(): for k, v in df_dict.items():
df_dict[k] = pd.DataFrame(v) df_dict[k] = pd.DataFrame(v)
logger.info('\n\033[33m- 共收集{}条资产账号\033[0m'.format(accounts.count()))
return df_dict return df_dict
class AppAccountHandler: class AppAccountHandler(BaseAccountHandler):
@staticmethod @staticmethod
def get_filename(plan_name): def get_filename(plan_name):
filename = os.path.join( filename = os.path.join(
@ -61,33 +95,23 @@ class AppAccountHandler:
) )
return filename return filename
@staticmethod @classmethod
def create_df(): def create_df(cls):
df_dict = defaultdict(list) df_dict = defaultdict(list)
accounts = Account.objects.all().prefetch_related('systemuser', 'app') accounts = Account.get_queryset()
for account in accounts: for account in accounts:
account.load_auth() account.load_auth()
app_type = account.app.type app_type = account.type
if app_type == 'postgresql': sheet_name = AppType.get_label(app_type)
label_key = getattr(AppType, 'pgsql').label row = cls.create_row(account, AppAccountSecretSerializer)
else: df_dict[sheet_name].append(row)
label_key = getattr(AppType, app_type).label
row = {
getattr(Application, 'name').field.verbose_name: account.app.name,
getattr(Application, 'attrs').field.verbose_name: account.app.attrs
}
secret_row = AccountBackupHandler.create_secret_row(account)
row.update(secret_row)
row.update({
getattr(Account, 'version').field.verbose_name: account.version
})
df_dict[label_key].append(row)
for k, v in df_dict.items(): for k, v in df_dict.items():
df_dict[k] = pd.DataFrame(v) df_dict[k] = pd.DataFrame(v)
logger.info('\n\033[33m- 共收集{}条应用账号\033[0m'.format(accounts.count()))
return df_dict return df_dict
HANDLER_MAP = { handler_map = {
'asset': AssetAccountHandler, 'asset': AssetAccountHandler,
'application': AppAccountHandler 'application': AppAccountHandler
} }
@ -102,31 +126,37 @@ class AccountBackupHandler:
def create_excel(self): def create_excel(self):
logger.info( logger.info(
'\n' '\n'
'\033[32m>>> 正在生成资产应用相关备份信息文件\033[0m' '\033[32m>>> 正在生成资产应用相关备份信息文件\033[0m'
'' ''
) )
# Print task start date # Print task start date
time_start = time.time() time_start = time.time()
info = {} files = []
for account_type in self.execution.types: for account_type in self.execution.types:
if account_type in HANDLER_MAP: handler = handler_map.get(account_type)
account_handler = HANDLER_MAP[account_type] if not handler:
df = account_handler.create_df() continue
filename = account_handler.get_filename(self.plan_name)
info[filename] = df df_dict = handler.create_df()
for filename, df_dict in info.items(): if not df_dict:
continue
filename = handler.get_filename(self.plan_name)
with pd.ExcelWriter(filename) as w: with pd.ExcelWriter(filename) as w:
for sheet, df in df_dict.items(): for sheet, df in df_dict.items():
sheet = sheet.replace(' ', '-') sheet = sheet.replace(' ', '-')
getattr(df, 'to_excel')(w, sheet_name=sheet, index=False) getattr(df, 'to_excel')(w, sheet_name=sheet, index=False)
files.append(filename)
timedelta = round((time.time() - time_start), 2) timedelta = round((time.time() - time_start), 2)
logger.info('步骤完成: 用时 {}s'.format(timedelta)) logger.info('步骤完成: 用时 {}s'.format(timedelta))
return list(info.keys()) return files
def send_backup_mail(self, files): def send_backup_mail(self, files):
recipients = self.execution.plan_snapshot.get('recipients') recipients = self.execution.plan_snapshot.get('recipients')
if not recipients: if not recipients:
return return
if not files:
return
recipients = User.objects.filter(id__in=list(recipients)) recipients = User.objects.filter(id__in=list(recipients))
logger.info( logger.info(
'\n' '\n'
@ -191,13 +221,3 @@ class AccountBackupHandler:
logger.info('\n任务结束: {}'.format(local_now_display())) logger.info('\n任务结束: {}'.format(local_now_display()))
timedelta = round((time.time() - time_start), 2) timedelta = round((time.time() - time_start), 2)
logger.info('用时: {}'.format(timedelta)) logger.info('用时: {}'.format(timedelta))
@staticmethod
def create_secret_row(instance):
row = {
getattr(BaseUser, 'username').field.verbose_name: instance.username,
getattr(BaseUser, 'password').field.verbose_name: instance.password,
getattr(BaseUser, 'private_key').field.verbose_name: instance.private_key,
getattr(BaseUser, 'public_key').field.verbose_name: instance.public_key
}
return row

View File

@ -1,7 +1,7 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from celery import shared_task from celery import shared_task
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _, gettext_noop
from common.utils import get_logger from common.utils import get_logger
from orgs.utils import org_aware_func from orgs.utils import org_aware_func
@ -104,6 +104,6 @@ def test_accounts_connectivity_manual(accounts):
:param accounts: <AuthBook>对象 :param accounts: <AuthBook>对象
""" """
for account in accounts: for account in accounts:
task_name = _("Test account connectivity: {}").format(account) task_name = gettext_noop("Test account connectivity: ") + str(account)
test_account_connectivity_util(account, task_name) test_account_connectivity_util(account, task_name)
print(".\n") print(".\n")

View File

@ -2,7 +2,7 @@
from itertools import groupby from itertools import groupby
from collections import defaultdict from collections import defaultdict
from celery import shared_task from celery import shared_task
from django.utils.translation import ugettext as _ from django.utils.translation import gettext_noop
from common.utils import get_logger from common.utils import get_logger
from orgs.utils import org_aware_func from orgs.utils import org_aware_func
@ -46,7 +46,7 @@ def test_asset_connectivity_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
if task_name is None: if task_name is None:
task_name = _("Test assets connectivity") task_name = gettext_noop("Test assets connectivity. ")
hosts = clean_ansible_task_hosts(assets) hosts = clean_ansible_task_hosts(assets)
if not hosts: if not hosts:
@ -88,7 +88,7 @@ def test_asset_connectivity_util(assets, task_name=None):
@shared_task(queue="ansible") @shared_task(queue="ansible")
def test_asset_connectivity_manual(asset): def test_asset_connectivity_manual(asset):
task_name = _("Test assets connectivity: {}").format(asset) task_name = gettext_noop("Test assets connectivity: ") + str(asset)
summary = test_asset_connectivity_util([asset], task_name=task_name) summary = test_asset_connectivity_util([asset], task_name=task_name)
if summary.get('dark'): if summary.get('dark'):
@ -99,7 +99,7 @@ def test_asset_connectivity_manual(asset):
@shared_task(queue="ansible") @shared_task(queue="ansible")
def test_assets_connectivity_manual(assets): def test_assets_connectivity_manual(assets):
task_name = _("Test assets connectivity: {}").format([asset.hostname for asset in assets]) task_name = gettext_noop("Test assets connectivity: ") + str([asset.hostname for asset in assets])
summary = test_asset_connectivity_util(assets, task_name=task_name) summary = test_asset_connectivity_util(assets, task_name=task_name)
if summary.get('dark'): if summary.get('dark'):
@ -110,8 +110,7 @@ def test_assets_connectivity_manual(assets):
@shared_task(queue="ansible") @shared_task(queue="ansible")
def test_node_assets_connectivity_manual(node): def test_node_assets_connectivity_manual(node):
task_name = _("Test if the assets under the node are connectable: {}".format(node.name)) task_name = gettext_noop("Test if the assets under the node are connectable: ") + node.name
assets = node.get_all_assets() assets = node.get_all_assets()
result = test_asset_connectivity_util(assets, task_name=task_name) result = test_asset_connectivity_util(assets, task_name=task_name)
return result return result

View File

@ -4,7 +4,7 @@ import json
import re import re
from celery import shared_task from celery import shared_task
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _, gettext_noop
from common.utils import ( from common.utils import (
capacity_convert, sum_capacity, get_logger capacity_convert, sum_capacity, get_logger
@ -94,7 +94,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
if task_name is None: if task_name is None:
task_name = _("Update some assets hardware info") task_name = gettext_noop("Update some assets hardware info. ")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hosts = clean_ansible_task_hosts(assets) hosts = clean_ansible_task_hosts(assets)
if not hosts: if not hosts:
@ -111,13 +111,13 @@ def update_assets_hardware_info_util(assets, task_name=None):
@shared_task(queue="ansible") @shared_task(queue="ansible")
def update_asset_hardware_info_manual(asset): def update_asset_hardware_info_manual(asset):
task_name = _("Update asset hardware info: {}").format(asset.hostname) task_name = gettext_noop("Update asset hardware info: ") + str(asset.hostname)
update_assets_hardware_info_util([asset], task_name=task_name) update_assets_hardware_info_util([asset], task_name=task_name)
@shared_task(queue="ansible") @shared_task(queue="ansible")
def update_assets_hardware_info_manual(assets): def update_assets_hardware_info_manual(assets):
task_name = _("Update assets hardware info: {}").format([asset.hostname for asset in assets]) task_name = gettext_noop("Update assets hardware info: ") + str([asset.hostname for asset in assets])
update_assets_hardware_info_util(assets, task_name=task_name) update_assets_hardware_info_util(assets, task_name=task_name)
@ -134,7 +134,7 @@ def update_assets_hardware_info_period():
@shared_task(queue="ansible") @shared_task(queue="ansible")
def update_node_assets_hardware_info_manual(node): def update_node_assets_hardware_info_manual(node):
task_name = _("Update node asset hardware information: {}").format(node.name) task_name = gettext_noop("Update node asset hardware information: ") + str(node.name)
assets = node.get_all_assets() assets = node.get_all_assets()
result = update_assets_hardware_info_util(assets, task_name=task_name) result = update_assets_hardware_info_util(assets, task_name=task_name)
return result return result

View File

@ -4,7 +4,7 @@ import re
from collections import defaultdict from collections import defaultdict
from celery import shared_task from celery import shared_task
from django.utils.translation import ugettext as _ from django.utils.translation import gettext_noop
from django.utils import timezone from django.utils import timezone
from orgs.utils import tmp_to_org, org_aware_func from orgs.utils import tmp_to_org, org_aware_func
@ -108,7 +108,7 @@ def add_asset_users(assets, results):
def gather_asset_users(assets, task_name=None): def gather_asset_users(assets, task_name=None):
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
if task_name is None: if task_name is None:
task_name = _("Gather assets users") task_name = gettext_noop("Gather assets users")
assets = clean_ansible_task_hosts(assets) assets = clean_ansible_task_hosts(assets)
if not assets: if not assets:
return return

View File

@ -3,7 +3,7 @@
from itertools import groupby from itertools import groupby
from celery import shared_task from celery import shared_task
from common.db.utils import get_object_if_need, get_objects from common.db.utils import get_object_if_need, get_objects
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _, gettext_noop
from django.db.models import Empty, Q from django.db.models import Empty, Q
from common.utils import encrypt_password, get_logger from common.utils import encrypt_password, get_logger
@ -279,7 +279,7 @@ def push_system_user_to_assets_manual(system_user, username=None):
""" """
system_user = get_object_if_need(SystemUser, system_user) system_user = get_object_if_need(SystemUser, system_user)
assets = system_user.get_related_assets() assets = system_user.get_related_assets()
task_name = _("Push system users to assets: {}").format(system_user.name) task_name = gettext_noop("Push system users to assets: ") + system_user.name
return push_system_user_util(system_user, assets, task_name=task_name, username=username) return push_system_user_util(system_user, assets, task_name=task_name, username=username)
@ -291,7 +291,7 @@ def push_system_user_a_asset_manual(system_user, asset, username=None):
""" """
# if username is None: # if username is None:
# username = system_user.username # username = system_user.username
task_name = _("Push system users to asset: {}({}) => {}").format( task_name = gettext_noop("Push system users to asset: ") + "{}({}) => {}".format(
system_user.name, username, asset system_user.name, username, asset
) )
return push_system_user_util(system_user, [asset], task_name=task_name, username=username) return push_system_user_util(system_user, [asset], task_name=task_name, username=username)
@ -312,7 +312,7 @@ def push_system_user_to_assets(system_user_id, asset_ids, username=None):
""" """
system_user = SystemUser.objects.get(id=system_user_id) system_user = SystemUser.objects.get(id=system_user_id)
assets = get_objects(Asset, asset_ids) assets = get_objects(Asset, asset_ids)
task_name = _("Push system users to assets: {}").format(system_user.name) task_name = gettext_noop("Push system users to assets: ") + system_user.name
return push_system_user_util(system_user, assets, task_name, username=username) return push_system_user_util(system_user, assets, task_name, username=username)

View File

@ -3,7 +3,7 @@ from itertools import groupby
from collections import defaultdict from collections import defaultdict
from celery import shared_task from celery import shared_task
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _, gettext_noop
from assets.models import Asset from assets.models import Asset
from common.utils import get_logger from common.utils import get_logger
@ -115,7 +115,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
@shared_task(queue="ansible") @shared_task(queue="ansible")
@org_aware_func("system_user") @org_aware_func("system_user")
def test_system_user_connectivity_manual(system_user, asset_ids=None): def test_system_user_connectivity_manual(system_user, asset_ids=None):
task_name = _("Test system user connectivity: {}").format(system_user) task_name = gettext_noop("Test system user connectivity: ") + str(system_user)
if asset_ids: if asset_ids:
assets = Asset.objects.filter(id__in=asset_ids) assets = Asset.objects.filter(id__in=asset_ids)
else: else:
@ -126,7 +126,7 @@ def test_system_user_connectivity_manual(system_user, asset_ids=None):
@shared_task(queue="ansible") @shared_task(queue="ansible")
@org_aware_func("system_user") @org_aware_func("system_user")
def test_system_user_connectivity_a_asset(system_user, asset): def test_system_user_connectivity_a_asset(system_user, asset):
task_name = _("Test system user connectivity: {} => {}").format( task_name = gettext_noop("Test system user connectivity: ") + "{} => {}".format(
system_user, asset system_user, asset
) )
test_system_user_connectivity_util(system_user, [asset], task_name) test_system_user_connectivity_util(system_user, [asset], task_name)
@ -145,7 +145,7 @@ def test_system_user_connectivity_period():
return return
queryset_map = SystemUser.objects.all_group_by_org() queryset_map = SystemUser.objects.all_group_by_org()
for org, system_user in queryset_map.items(): for org, system_user in queryset_map.items():
task_name = _("Test system user connectivity period: {}").format(system_user) task_name = gettext_noop("Test system user connectivity period: ") + str(system_user)
with tmp_to_org(org): with tmp_to_org(org):
assets = system_user.get_related_assets() assets = system_user.get_related_assets()
test_system_user_connectivity_util(system_user, assets, task_name) test_system_user_connectivity_util(system_user, assets, task_name)

View File

@ -12,6 +12,7 @@ from rest_framework.response import Response
from common.permissions import IsValidUser, NeedMFAVerify from common.permissions import IsValidUser, NeedMFAVerify
from common.utils import get_logger from common.utils import get_logger
from common.exceptions import UnexpectError
from users.models.user import User from users.models.user import User
from ..serializers import OtpVerifySerializer from ..serializers import OtpVerifySerializer
from .. import serializers from .. import serializers
@ -35,30 +36,45 @@ class MFASendCodeApi(AuthMixin, CreateAPIView):
""" """
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
serializer_class = serializers.MFASelectTypeSerializer serializer_class = serializers.MFASelectTypeSerializer
username = ''
ip = ''
def get_user_from_db(self, username):
try:
user = get_object_or_404(User, username=username)
return user
except Exception as e:
self.incr_mfa_failed_time(username, self.ip)
raise e
def get_user_from_db(self, username):
"""避免暴力测试用户名"""
ip = self.get_request_ip()
self.check_mfa_is_block(username, ip)
try:
user = get_object_or_404(User, username=username)
return user
except Exception as e:
self.incr_mfa_failed_time(username, ip)
raise e
def perform_create(self, serializer): def perform_create(self, serializer):
username = serializer.validated_data.get('username', '') username = serializer.validated_data.get('username', '')
mfa_type = serializer.validated_data['type'] mfa_type = serializer.validated_data['type']
if not username: if not username:
user = self.get_user_from_session() user = self.get_user_from_session()
else: else:
user = get_object_or_404(User, username=username) user = self.get_user_from_db(username)
mfa_backend = user.get_active_mfa_backend_by_type(mfa_type) mfa_backend = user.get_active_mfa_backend_by_type(mfa_type)
if not mfa_backend or not mfa_backend.challenge_required: if not mfa_backend or not mfa_backend.challenge_required:
raise ValidationError('MFA type not support: {} {}'.format(mfa_type, mfa_backend)) error = _('Current user not support mfa type: {}').format(mfa_type)
mfa_backend.send_challenge() raise ValidationError({'error': error})
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
try: try:
self.perform_create(serializer) mfa_backend.send_challenge()
return Response(serializer.data, status=201)
except Exception as e: except Exception as e:
logger.exception(e) raise UnexpectError(str(e))
return Response({'error': str(e)}, status=400)
class MFAChallengeVerifyApi(AuthMixin, CreateAPIView): class MFAChallengeVerifyApi(AuthMixin, CreateAPIView):

View File

@ -76,11 +76,10 @@ class PrepareRequestMixin:
@staticmethod @staticmethod
def get_attribute_consuming_service(): def get_attribute_consuming_service():
attr_mapping = settings.SAML2_RENAME_ATTRIBUTES attr_mapping = settings.SAML2_RENAME_ATTRIBUTES
name_prefix = settings.SITE_URL
if attr_mapping and isinstance(attr_mapping, dict): if attr_mapping and isinstance(attr_mapping, dict):
attr_list = [ attr_list = [
{ {
"name": '{}/{}'.format(name_prefix, sp_key), "name": sp_key,
"friendlyName": idp_key, "isRequired": True "friendlyName": idp_key, "isRequired": True
} }
for idp_key, sp_key in attr_mapping.items() for idp_key, sp_key in attr_mapping.items()
@ -168,12 +167,10 @@ class PrepareRequestMixin:
def get_attributes(self, saml_instance): def get_attributes(self, saml_instance):
user_attrs = {} user_attrs = {}
real_key_index = len(settings.SITE_URL) + 1
attrs = saml_instance.get_attributes() attrs = saml_instance.get_attributes()
valid_attrs = ['username', 'name', 'email', 'comment', 'phone'] valid_attrs = ['username', 'name', 'email', 'comment', 'phone']
for attr, value in attrs.items(): for attr, value in attrs.items():
attr = attr[real_key_index:]
if attr not in valid_attrs: if attr not in valid_attrs:
continue continue
user_attrs[attr] = self.value_to_str(value) user_attrs[attr] = self.value_to_str(value)

View File

@ -335,6 +335,11 @@ class MFAMixin:
mfa_backends = User.get_user_mfa_backends(user) mfa_backends = User.get_user_mfa_backends(user)
return {'mfa_backends': mfa_backends} return {'mfa_backends': mfa_backends}
@staticmethod
def incr_mfa_failed_time(username, ip):
util = MFABlockUtils(username, ip)
util.incr_failed_count()
class AuthPostCheckMixin: class AuthPostCheckMixin:
@classmethod @classmethod
@ -450,7 +455,10 @@ class AuthMixin(CommonMixin, AuthPreCheckMixin, AuthACLMixin, MFAMixin, AuthPost
) )
if not user: if not user:
self.raise_credential_error(errors.reason_password_failed) self.raise_credential_error(errors.reason_password_failed)
elif user.is_expired:
self.request.session['auth_backend'] = getattr(user, 'backend', settings.AUTH_BACKEND_MODEL)
if user.is_expired:
self.raise_credential_error(errors.reason_user_expired) self.raise_credential_error(errors.reason_user_expired)
elif not user.is_active: elif not user.is_active:
self.raise_credential_error(errors.reason_user_inactive) self.raise_credential_error(errors.reason_user_inactive)

View File

@ -109,7 +109,7 @@
} }
.select-con { .select-con {
width: 30%; width: 35%;
} }
.mfa-div { .mfa-div {

View File

@ -44,6 +44,7 @@ def get_objects(model, pks):
return objs return objs
# 复制 django.db.close_old_connections, 因为它没有导出ide 提示有问题
def close_old_connections(): def close_old_connections():
for conn in connections.all(): for conn in connections.all():
conn.close_if_unusable_or_obsolete() conn.close_if_unusable_or_obsolete()

View File

@ -45,3 +45,9 @@ class MFAVerifyRequired(JMSException):
status_code = status.HTTP_400_BAD_REQUEST status_code = status.HTTP_400_BAD_REQUEST
default_code = 'mfa_verify_required' default_code = 'mfa_verify_required'
default_detail = _('This action require verify your MFA') default_detail = _('This action require verify your MFA')
class UnexpectError(JMSException):
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
default_code = 'unexpect_error'
default_detail = _('Unexpect error occur')

View File

@ -1,6 +1,6 @@
from django.core.cache import cache from django.core.cache import cache
from django.shortcuts import reverse from django.shortcuts import reverse, redirect
from django.shortcuts import redirect from django.utils.translation import gettext_noop
from .random import random_string from .random import random_string

View File

@ -2,6 +2,7 @@
# #
from functools import partial from functools import partial
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from datetime import datetime
from django.conf import settings from django.conf import settings
from common.local import thread_local from common.local import thread_local
@ -26,4 +27,18 @@ def has_valid_xpack_license():
return License.has_valid_license() return License.has_valid_license()
def get_xpack_license_info() -> dict:
if has_valid_xpack_license():
from xpack.plugins.license.models import License
info = License.get_license_detail()
corporation = info.get('corporation', '')
else:
current_year = datetime.now().year
corporation = f'Copyright - FIT2CLOUD 飞致云 © 2014-{current_year}'
info = {
'corporation': corporation
}
return info
current_request = LocalProxy(partial(_find, 'current_request')) current_request = LocalProxy(partial(_find, 'current_request'))

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:942e981be66e5d0c32efb59583a377503ee3dc285e2794da40c312694c4a9dc2 oid sha256:a08014e3eed6152aaff1e42758f20666f0e90e1ed4264a9d7cd44e34191d607e
size 96378 size 96692

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-12 20:51+0800\n" "POT-Creation-Date: 2022-01-17 19:06+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -123,14 +123,14 @@ msgid "Login acl"
msgstr "登录访问控制" msgstr "登录访问控制"
#: acls/models/login_asset_acl.py:21 #: acls/models/login_asset_acl.py:21
#: applications/serializers/application.py:108 #: applications/serializers/application.py:122
#: applications/serializers/application.py:139 #: applications/serializers/application.py:165
msgid "System User" msgid "System User"
msgstr "系统用户" msgstr "系统用户"
#: acls/models/login_asset_acl.py:22 #: acls/models/login_asset_acl.py:22
#: applications/serializers/attrs/application_category/remote_app.py:37 #: applications/serializers/attrs/application_category/remote_app.py:36
#: assets/models/asset.py:356 assets/models/authbook.py:18 #: assets/models/asset.py:356 assets/models/authbook.py:19
#: assets/models/backup.py:31 assets/models/cmd_filter.py:34 #: assets/models/backup.py:31 assets/models/cmd_filter.py:34
#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:264 #: assets/models/gathered_user.py:14 assets/serializers/system_user.py:264
#: audits/models.py:38 perms/models/asset_permission.py:24 #: audits/models.py:38 perms/models/asset_permission.py:24
@ -157,16 +157,12 @@ msgid "Format for comma-delimited string, with * indicating a match all. "
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17
#: acls/serializers/login_asset_acl.py:51 #: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: applications/serializers/attrs/application_type/chrome.py:20 #: assets/models/gathered_user.py:15 audits/models.py:105
#: applications/serializers/attrs/application_type/custom.py:21 #: authentication/forms.py:15 authentication/forms.py:17
#: applications/serializers/attrs/application_type/mysql_workbench.py:30
#: applications/serializers/attrs/application_type/vmware_client.py:26
#: assets/models/base.py:176 assets/models/gathered_user.py:15
#: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17
#: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:547 #: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:547
#: users/templates/users/_msg_user_created.html:12 #: users/templates/users/_msg_user_created.html:12
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: xpack/plugins/change_auth_plan/models/asset.py:34 #: xpack/plugins/change_auth_plan/models/asset.py:34
@ -185,7 +181,7 @@ msgstr ""
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
#: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33
#: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: applications/serializers/attrs/application_type/mysql_workbench.py:17
#: assets/models/asset.py:211 assets/models/domain.py:61 #: assets/models/asset.py:211 assets/models/domain.py:61
#: assets/serializers/account.py:12 #: assets/serializers/account.py:12
#: authentication/templates/authentication/_msg_oauth_bind.html:12 #: authentication/templates/authentication/_msg_oauth_bind.html:12
@ -252,9 +248,9 @@ msgstr "时段"
msgid "My applications" msgid "My applications"
msgstr "我的应用" msgstr "我的应用"
#: applications/const.py:8 applications/models/account.py:10 #: applications/const.py:8 applications/models/account.py:12
#: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_category/db.py:14
#: applications/serializers/attrs/application_type/mysql_workbench.py:26 #: applications/serializers/attrs/application_type/mysql_workbench.py:25
#: xpack/plugins/change_auth_plan/models/app.py:32 #: xpack/plugins/change_auth_plan/models/app.py:32
msgid "Database" msgid "Database"
msgstr "数据库" msgstr "数据库"
@ -267,7 +263,7 @@ msgstr "远程应用"
msgid "Custom" msgid "Custom"
msgstr "自定义" msgstr "自定义"
#: applications/models/account.py:11 assets/models/authbook.py:19 #: applications/models/account.py:15 assets/models/authbook.py:20
#: assets/models/cmd_filter.py:38 assets/models/user.py:302 audits/models.py:39 #: assets/models/cmd_filter.py:38 assets/models/user.py:302 audits/models.py:39
#: perms/models/application_permission.py:32 #: perms/models/application_permission.py:32
#: perms/models/asset_permission.py:26 templates/_nav.html:45 #: perms/models/asset_permission.py:26 templates/_nav.html:45
@ -284,12 +280,12 @@ msgstr "自定义"
msgid "System user" msgid "System user"
msgstr "系统用户" msgstr "系统用户"
#: applications/models/account.py:12 assets/models/authbook.py:20 #: applications/models/account.py:17 assets/models/authbook.py:21
#: settings/serializers/auth/cas.py:15 #: settings/serializers/auth/cas.py:15
msgid "Version" msgid "Version"
msgstr "版本" msgstr "版本"
#: applications/models/account.py:18 xpack/plugins/cloud/models.py:82 #: applications/models/account.py:23 xpack/plugins/cloud/models.py:82
#: xpack/plugins/cloud/serializers/task.py:66 #: xpack/plugins/cloud/serializers/task.py:66
msgid "Account" msgid "Account"
msgstr "账户" msgstr "账户"
@ -299,7 +295,7 @@ msgid "Applications"
msgstr "应用管理" msgstr "应用管理"
#: applications/models/application.py:204 #: applications/models/application.py:204
#: applications/serializers/application.py:88 assets/models/label.py:21 #: applications/serializers/application.py:99 assets/models/label.py:21
#: perms/models/application_permission.py:20 #: perms/models/application_permission.py:20
#: perms/serializers/application/user_permission.py:33 #: perms/serializers/application/user_permission.py:33
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22
@ -308,7 +304,7 @@ msgid "Category"
msgstr "类别" msgstr "类别"
#: applications/models/application.py:207 #: applications/models/application.py:207
#: applications/serializers/application.py:90 assets/models/backup.py:49 #: applications/serializers/application.py:101 assets/models/backup.py:49
#: assets/models/cmd_filter.py:76 assets/models/user.py:210 #: assets/models/cmd_filter.py:76 assets/models/user.py:210
#: perms/models/application_permission.py:23 #: perms/models/application_permission.py:23
#: perms/serializers/application/user_permission.py:34 #: perms/serializers/application/user_permission.py:34
@ -335,15 +331,15 @@ msgstr "属性"
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: applications/serializers/application.py:59 #: applications/serializers/application.py:70
#: applications/serializers/application.py:89 assets/serializers/label.py:13 #: applications/serializers/application.py:100 assets/serializers/label.py:13
#: perms/serializers/application/permission.py:18 #: perms/serializers/application/permission.py:18
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26
msgid "Category display" msgid "Category display"
msgstr "类别名称" msgstr "类别名称"
#: applications/serializers/application.py:60 #: applications/serializers/application.py:71
#: applications/serializers/application.py:91 #: applications/serializers/application.py:102
#: assets/serializers/system_user.py:27 audits/serializers.py:29 #: assets/serializers/system_user.py:27 audits/serializers.py:29
#: perms/serializers/application/permission.py:19 #: perms/serializers/application/permission.py:19
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33
@ -352,43 +348,62 @@ msgstr "类别名称"
msgid "Type display" msgid "Type display"
msgstr "类型名称" msgstr "类型名称"
#: applications/serializers/application.py:107 #: applications/serializers/application.py:103 assets/models/asset.py:231
#: applications/serializers/application.py:138 #: assets/models/base.py:181 assets/models/cluster.py:26
#: assets/models/domain.py:27 assets/models/gathered_user.py:19
#: assets/models/group.py:22 assets/models/label.py:25
#: assets/serializers/account.py:17 common/db/models.py:113
#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:29
#: orgs/models.py:26 orgs/models.py:435 perms/models/base.py:92
#: users/models/group.py:18 users/models/user.py:783
#: xpack/plugins/cloud/models.py:122
msgid "Date created"
msgstr "创建日期"
#: applications/serializers/application.py:104 assets/models/base.py:182
#: assets/models/gathered_user.py:20 assets/serializers/account.py:20
#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:39
#: orgs/models.py:436
msgid "Date updated"
msgstr "更新日期"
#: applications/serializers/application.py:121
#: applications/serializers/application.py:164
msgid "Application display" msgid "Application display"
msgstr "应用名称" msgstr "应用名称"
#: applications/serializers/attrs/application_category/cloud.py:9 #: applications/serializers/attrs/application_category/cloud.py:8
#: assets/models/cluster.py:40 #: assets/models/cluster.py:40
msgid "Cluster" msgid "Cluster"
msgstr "集群" msgstr "集群"
#: applications/serializers/attrs/application_category/db.py:11 #: applications/serializers/attrs/application_category/db.py:11
#: ops/models/adhoc.py:146 settings/serializers/auth/radius.py:14 #: ops/models/adhoc.py:157 settings/serializers/auth/radius.py:14
#: xpack/plugins/cloud/serializers/account_attrs.py:68 #: xpack/plugins/cloud/serializers/account_attrs.py:68
msgid "Host" msgid "Host"
msgstr "主机" msgstr "主机"
#: applications/serializers/attrs/application_category/db.py:12 #: applications/serializers/attrs/application_category/db.py:12
#: applications/serializers/attrs/application_type/mysql.py:11 #: applications/serializers/attrs/application_type/mysql.py:10
#: applications/serializers/attrs/application_type/mysql_workbench.py:22 #: applications/serializers/attrs/application_type/mysql_workbench.py:21
#: applications/serializers/attrs/application_type/oracle.py:11 #: applications/serializers/attrs/application_type/oracle.py:10
#: applications/serializers/attrs/application_type/pgsql.py:11 #: applications/serializers/attrs/application_type/pgsql.py:10
#: applications/serializers/attrs/application_type/redis.py:11 #: applications/serializers/attrs/application_type/redis.py:10
#: applications/serializers/attrs/application_type/sqlserver.py:11 #: applications/serializers/attrs/application_type/sqlserver.py:10
#: assets/models/asset.py:215 assets/models/domain.py:62 #: assets/models/asset.py:215 assets/models/domain.py:62
#: settings/serializers/auth/radius.py:15 #: settings/serializers/auth/radius.py:15
#: xpack/plugins/cloud/serializers/account_attrs.py:69 #: xpack/plugins/cloud/serializers/account_attrs.py:69
msgid "Port" msgid "Port"
msgstr "端口" msgstr "端口"
#: applications/serializers/attrs/application_category/remote_app.py:40 #: applications/serializers/attrs/application_category/remote_app.py:39
#: applications/serializers/attrs/application_type/chrome.py:14 #: applications/serializers/attrs/application_type/chrome.py:13
#: applications/serializers/attrs/application_type/mysql_workbench.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:13
#: applications/serializers/attrs/application_type/vmware_client.py:18 #: applications/serializers/attrs/application_type/vmware_client.py:17
msgid "Application path" msgid "Application path"
msgstr "应用路径" msgstr "应用路径"
#: applications/serializers/attrs/application_category/remote_app.py:45 #: applications/serializers/attrs/application_category/remote_app.py:44
#: assets/serializers/system_user.py:163 #: assets/serializers/system_user.py:163
#: xpack/plugins/change_auth_plan/serializers/asset.py:65 #: xpack/plugins/change_auth_plan/serializers/asset.py:65
#: xpack/plugins/change_auth_plan/serializers/asset.py:68 #: xpack/plugins/change_auth_plan/serializers/asset.py:68
@ -398,37 +413,56 @@ msgstr "应用路径"
msgid "This field is required." msgid "This field is required."
msgstr "该字段是必填项。" msgstr "该字段是必填项。"
#: applications/serializers/attrs/application_type/chrome.py:17 #: applications/serializers/attrs/application_type/chrome.py:16
#: applications/serializers/attrs/application_type/vmware_client.py:22 #: applications/serializers/attrs/application_type/vmware_client.py:21
msgid "Target URL" msgid "Target URL"
msgstr "目标URL" msgstr "目标URL"
#: applications/serializers/attrs/application_type/chrome.py:23 #: applications/serializers/attrs/application_type/chrome.py:19
#: applications/serializers/attrs/application_type/custom.py:25 msgid "Chrome username"
#: applications/serializers/attrs/application_type/mysql_workbench.py:34 msgstr "Chrome 用户名"
#: applications/serializers/attrs/application_type/vmware_client.py:30
#: assets/models/base.py:177 audits/signals_handler.py:65
#: authentication/forms.py:22
#: authentication/templates/authentication/login.html:151
#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_update.html:43
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/change_auth_plan/models/base.py:39
#: xpack/plugins/change_auth_plan/models/base.py:118
#: xpack/plugins/change_auth_plan/models/base.py:193
#: xpack/plugins/cloud/serializers/account_attrs.py:24
msgid "Password"
msgstr "密码"
#: applications/serializers/attrs/application_type/custom.py:13 #: applications/serializers/attrs/application_type/chrome.py:22
#: applications/serializers/attrs/application_type/chrome.py:29
msgid "Chrome password"
msgstr "Chrome 密码"
#: applications/serializers/attrs/application_type/custom.py:11
msgid "Operating parameter" msgid "Operating parameter"
msgstr "运行参数" msgstr "运行参数"
#: applications/serializers/attrs/application_type/custom.py:17 #: applications/serializers/attrs/application_type/custom.py:15
msgid "Target url" msgid "Target url"
msgstr "目标URL" msgstr "目标URL"
#: applications/serializers/attrs/application_type/custom.py:19
msgid "Custom Username"
msgstr "自定义用户名"
#: applications/serializers/attrs/application_type/custom.py:23
#: applications/serializers/attrs/application_type/custom.py:30
#: xpack/plugins/change_auth_plan/models/base.py:24
msgid "Custom password"
msgstr "自定义密码"
#: applications/serializers/attrs/application_type/mysql_workbench.py:29
msgid "Mysql workbench username"
msgstr "Mysql 工作台 用户名"
#: applications/serializers/attrs/application_type/mysql_workbench.py:33
#: applications/serializers/attrs/application_type/mysql_workbench.py:40
msgid "Mysql workbench password"
msgstr "Mysql 工作台 密码"
#: applications/serializers/attrs/application_type/vmware_client.py:25
msgid "Vmware username"
msgstr "Vmware 用户名"
#: applications/serializers/attrs/application_type/vmware_client.py:29
#: applications/serializers/attrs/application_type/vmware_client.py:36
msgid "Vmware password"
msgstr "Vmware 密码"
#: assets/api/domain.py:52 #: assets/api/domain.py:52
msgid "Number required" msgid "Number required"
msgstr "需要为数字" msgstr "需要为数字"
@ -453,7 +487,7 @@ msgstr "基础"
msgid "Charset" msgid "Charset"
msgstr "编码" msgstr "编码"
#: assets/models/asset.py:142 assets/serializers/asset.py:178 #: assets/models/asset.py:142 assets/serializers/asset.py:176
#: tickets/models/ticket.py:54 #: tickets/models/ticket.py:54
msgid "Meta" msgid "Meta"
msgstr "元数据" msgstr "元数据"
@ -463,7 +497,8 @@ msgid "Internal"
msgstr "内部的" msgstr "内部的"
#: assets/models/asset.py:163 assets/models/asset.py:217 #: assets/models/asset.py:163 assets/models/asset.py:217
#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:43 #: assets/serializers/account.py:14 assets/serializers/asset.py:63
#: perms/serializers/asset/user_permission.py:43
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
@ -523,8 +558,8 @@ msgstr "系统架构"
msgid "Hostname raw" msgid "Hostname raw"
msgstr "主机名原始" msgstr "主机名原始"
#: assets/models/asset.py:216 assets/serializers/asset.py:67 #: assets/models/asset.py:216 assets/serializers/account.py:15
#: perms/serializers/asset/user_permission.py:41 #: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41
#: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:42 #: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:42
msgid "Protocols" msgid "Protocols"
msgstr "协议组" msgstr "协议组"
@ -569,17 +604,7 @@ msgstr "标签管理"
msgid "Created by" msgid "Created by"
msgstr "创建者" msgstr "创建者"
#: assets/models/asset.py:231 assets/models/base.py:181 #: assets/models/authbook.py:27
#: assets/models/cluster.py:26 assets/models/domain.py:27
#: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 common/db/models.py:113 common/mixins/models.py:50
#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26
#: orgs/models.py:435 perms/models/base.py:92 users/models/group.py:18
#: users/models/user.py:783 xpack/plugins/cloud/models.py:122
msgid "Date created"
msgstr "创建日期"
#: assets/models/authbook.py:26
msgid "AuthBook" msgid "AuthBook"
msgstr "账号" msgstr "账号"
@ -621,7 +646,7 @@ msgstr "开始日期"
#: assets/models/backup.py:108 #: assets/models/backup.py:108
#: authentication/templates/authentication/_msg_oauth_bind.html:11 #: authentication/templates/authentication/_msg_oauth_bind.html:11
#: notifications/notifications.py:187 ops/models/adhoc.py:246 #: notifications/notifications.py:187 ops/models/adhoc.py:257
#: xpack/plugins/change_auth_plan/models/base.py:112 #: xpack/plugins/change_auth_plan/models/base.py:112
#: xpack/plugins/change_auth_plan/models/base.py:201 #: xpack/plugins/change_auth_plan/models/base.py:201
#: xpack/plugins/gathered_user/models.py:79 #: xpack/plugins/gathered_user/models.py:79
@ -646,7 +671,7 @@ msgid "Reason"
msgstr "原因" msgstr "原因"
#: assets/models/backup.py:121 audits/serializers.py:76 #: assets/models/backup.py:121 audits/serializers.py:76
#: audits/serializers.py:91 ops/models/adhoc.py:248 #: audits/serializers.py:91 ops/models/adhoc.py:259
#: terminal/serializers/session.py:35 #: terminal/serializers/session.py:35
#: xpack/plugins/change_auth_plan/models/base.py:199 #: xpack/plugins/change_auth_plan/models/base.py:199
msgid "Is success" msgid "Is success"
@ -678,6 +703,20 @@ msgstr "可连接性"
msgid "Date verified" msgid "Date verified"
msgstr "校验日期" msgstr "校验日期"
#: assets/models/base.py:177 audits/signals_handler.py:65
#: authentication/forms.py:22
#: authentication/templates/authentication/login.html:151
#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21
#: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_update.html:43
#: users/templates/users/user_password_verify.html:18
#: xpack/plugins/change_auth_plan/models/base.py:39
#: xpack/plugins/change_auth_plan/models/base.py:118
#: xpack/plugins/change_auth_plan/models/base.py:193
#: xpack/plugins/cloud/serializers/account_attrs.py:24
msgid "Password"
msgstr "密码"
#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:53 #: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206 #: xpack/plugins/change_auth_plan/models/asset.py:206
@ -690,12 +729,6 @@ msgstr "SSH密钥"
msgid "SSH public key" msgid "SSH public key"
msgstr "SSH公钥" msgstr "SSH公钥"
#: assets/models/base.py:182 assets/models/gathered_user.py:20
#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:39
#: orgs/models.py:436
msgid "Date updated"
msgstr "更新日期"
#: assets/models/cluster.py:20 #: assets/models/cluster.py:20
msgid "Bandwidth" msgid "Bandwidth"
msgstr "带宽" msgstr "带宽"
@ -965,39 +998,39 @@ msgstr ""
"{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" "{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设"
"置加密密码" "置加密密码"
#: assets/serializers/account.py:31 assets/serializers/account.py:52 #: assets/serializers/account.py:39 assets/serializers/account.py:67
msgid "System user display" msgid "System user display"
msgstr "系统用户名称" msgstr "系统用户名称"
#: assets/serializers/asset.py:22 #: assets/serializers/asset.py:20
msgid "Protocol format should {}/{}" msgid "Protocol format should {}/{}"
msgstr "协议格式 {}/{}" msgstr "协议格式 {}/{}"
#: assets/serializers/asset.py:39 #: assets/serializers/asset.py:37
msgid "Protocol duplicate: {}" msgid "Protocol duplicate: {}"
msgstr "协议重复: {}" msgstr "协议重复: {}"
#: assets/serializers/asset.py:68 #: assets/serializers/asset.py:66
msgid "Domain name" msgid "Domain name"
msgstr "网域名称" msgstr "网域名称"
#: assets/serializers/asset.py:70 #: assets/serializers/asset.py:68
msgid "Nodes name" msgid "Nodes name"
msgstr "节点名称" msgstr "节点名称"
#: assets/serializers/asset.py:73 #: assets/serializers/asset.py:71
msgid "Labels name" msgid "Labels name"
msgstr "标签名称" msgstr "标签名称"
#: assets/serializers/asset.py:107 #: assets/serializers/asset.py:105
msgid "Hardware info" msgid "Hardware info"
msgstr "硬件信息" msgstr "硬件信息"
#: assets/serializers/asset.py:108 #: assets/serializers/asset.py:106
msgid "Admin user display" msgid "Admin user display"
msgstr "特权用户名称" msgstr "特权用户名称"
#: assets/serializers/asset.py:109 #: assets/serializers/asset.py:107
msgid "CPU info" msgid "CPU info"
msgstr "CPU信息" msgstr "CPU信息"
@ -1136,20 +1169,20 @@ msgid "The asset {} system platform {} does not support run Ansible tasks"
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务" msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
#: assets/tasks/account_connectivity.py:107 #: assets/tasks/account_connectivity.py:107
msgid "Test account connectivity: {}" msgid "Test account connectivity: "
msgstr "测试账号可连接性: {}" msgstr "测试账号可连接性: "
#: assets/tasks/asset_connectivity.py:49 #: assets/tasks/asset_connectivity.py:49
msgid "Test assets connectivity" msgid "Test assets connectivity. "
msgstr "测试资产可连接性" msgstr "测试资产可连接性. "
#: assets/tasks/asset_connectivity.py:91 assets/tasks/asset_connectivity.py:102 #: assets/tasks/asset_connectivity.py:91 assets/tasks/asset_connectivity.py:102
msgid "Test assets connectivity: {}" msgid "Test assets connectivity: "
msgstr "测试资产可连接性: {}" msgstr "测试资产可连接性: "
#: assets/tasks/asset_connectivity.py:113 #: assets/tasks/asset_connectivity.py:113
msgid "Test if the assets under the node are connectable: {}" msgid "Test if the assets under the node are connectable: "
msgstr "测试节点下资产是否可连接: {}" msgstr "测试节点下资产是否可连接: "
#: assets/tasks/const.py:49 #: assets/tasks/const.py:49
msgid "Unreachable" msgid "Unreachable"
@ -1164,20 +1197,20 @@ msgid "Get asset info failed: {}"
msgstr "获取资产信息失败:{}" msgstr "获取资产信息失败:{}"
#: assets/tasks/gather_asset_hardware_info.py:97 #: assets/tasks/gather_asset_hardware_info.py:97
msgid "Update some assets hardware info" msgid "Update some assets hardware info. "
msgstr "更新资产硬件信息" msgstr "更新资产硬件信息. "
#: assets/tasks/gather_asset_hardware_info.py:114 #: assets/tasks/gather_asset_hardware_info.py:114
msgid "Update asset hardware info: {}" msgid "Update asset hardware info: "
msgstr "更新资产硬件信息: {}" msgstr "更新资产硬件信息: "
#: assets/tasks/gather_asset_hardware_info.py:120 #: assets/tasks/gather_asset_hardware_info.py:120
msgid "Update assets hardware info: {}" msgid "Update assets hardware info: "
msgstr "更新资产硬件信息: {}" msgstr "更新资产硬件信息: "
#: assets/tasks/gather_asset_hardware_info.py:137 #: assets/tasks/gather_asset_hardware_info.py:137
msgid "Update node asset hardware information: {}" msgid "Update node asset hardware information: "
msgstr "更新节点资产硬件信息: {}" msgstr "更新节点资产硬件信息: "
#: assets/tasks/gather_asset_users.py:111 #: assets/tasks/gather_asset_users.py:111
msgid "Gather assets users" msgid "Gather assets users"
@ -1202,12 +1235,12 @@ msgid "Hosts count: {}"
msgstr "主机数量: {}" msgstr "主机数量: {}"
#: assets/tasks/push_system_user.py:282 assets/tasks/push_system_user.py:315 #: assets/tasks/push_system_user.py:282 assets/tasks/push_system_user.py:315
msgid "Push system users to assets: {}" msgid "Push system users to assets: "
msgstr "推送系统用户到入资产: {}" msgstr "推送系统用户到入资产: "
#: assets/tasks/push_system_user.py:294 #: assets/tasks/push_system_user.py:294
msgid "Push system users to asset: {}({}) => {}" msgid "Push system users to asset: "
msgstr "推送系统用户到入资产: {}({}) => {}" msgstr "推送系统用户到入资产: "
#: assets/tasks/system_user_connectivity.py:56 #: assets/tasks/system_user_connectivity.py:56
msgid "Dynamic system user not support test" msgid "Dynamic system user not support test"
@ -1218,16 +1251,13 @@ msgid "Start test system user connectivity for platform: [{}]"
msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" msgstr "开始测试系统用户在该系统平台的可连接性: [{}]"
#: assets/tasks/system_user_connectivity.py:118 #: assets/tasks/system_user_connectivity.py:118
msgid "Test system user connectivity: {}"
msgstr "测试系统用户可连接性: {}"
#: assets/tasks/system_user_connectivity.py:129 #: assets/tasks/system_user_connectivity.py:129
msgid "Test system user connectivity: {} => {}" msgid "Test system user connectivity: "
msgstr "测试系统用户可连接性: {} => {}" msgstr "测试系统用户可连接性: "
#: assets/tasks/system_user_connectivity.py:148 #: assets/tasks/system_user_connectivity.py:148
msgid "Test system user connectivity period: {}" msgid "Test system user connectivity period: "
msgstr "定期测试系统用户可连接性: {}" msgstr "定期测试系统用户可连接性: "
#: assets/tasks/utils.py:17 #: assets/tasks/utils.py:17
msgid "Asset has been disabled, skipped: {}" msgid "Asset has been disabled, skipped: {}"
@ -1427,14 +1457,14 @@ msgid "Auth Token"
msgstr "认证令牌" msgstr "认证令牌"
#: audits/signals_handler.py:68 authentication/notifications.py:73 #: audits/signals_handler.py:68 authentication/notifications.py:73
#: authentication/views/dingtalk.py:160 authentication/views/feishu.py:148
#: authentication/views/login.py:164 authentication/views/wecom.py:158 #: authentication/views/login.py:164 authentication/views/wecom.py:158
#: notifications/backends/__init__.py:11 users/models/user.py:607 #: notifications/backends/__init__.py:11 users/models/user.py:607
msgid "WeCom" msgid "WeCom"
msgstr "企业微信" msgstr "企业微信"
#: audits/signals_handler.py:69 authentication/views/login.py:170 #: audits/signals_handler.py:69 authentication/views/dingtalk.py:160
#: notifications/backends/__init__.py:12 users/models/user.py:608 #: authentication/views/login.py:170 notifications/backends/__init__.py:12
#: users/models/user.py:608
msgid "DingTalk" msgid "DingTalk"
msgstr "钉钉" msgstr "钉钉"
@ -1625,7 +1655,11 @@ msgstr "{ApplicationPermission} 移除 {SystemUser}"
msgid "Invalid token" msgid "Invalid token"
msgstr "无效的令牌" msgstr "无效的令牌"
#: authentication/api/mfa.py:103 #: authentication/api/mfa.py:72
msgid "Current user not support mfa type: {}"
msgstr "当前用户不支持 MFA 类型: {}"
#: authentication/api/mfa.py:119
msgid "Code is invalid, {}" msgid "Code is invalid, {}"
msgstr "验证码无效: {}" msgstr "验证码无效: {}"
@ -1794,15 +1828,15 @@ msgstr "该 时间段 不被允许登录"
msgid "SSO auth closed" msgid "SSO auth closed"
msgstr "SSO 认证关闭了" msgstr "SSO 认证关闭了"
#: authentication/errors.py:300 authentication/mixins.py:359 #: authentication/errors.py:300 authentication/mixins.py:364
msgid "Your password is too simple, please change it for security" msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改" msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors.py:309 authentication/mixins.py:366 #: authentication/errors.py:309 authentication/mixins.py:371
msgid "You should to change your password before login" msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码" msgstr "登录完成前,请先修改密码"
#: authentication/errors.py:318 authentication/mixins.py:373 #: authentication/errors.py:318 authentication/mixins.py:378
msgid "Your password has expired, please reset before logging in" msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录" msgstr "您的密码已过期,先修改再登录"
@ -1898,7 +1932,7 @@ msgstr "清空手机号码禁用"
msgid "The MFA type ({}) is not enabled" msgid "The MFA type ({}) is not enabled"
msgstr "该 MFA ({}) 方式没有启用" msgstr "该 MFA ({}) 方式没有启用"
#: authentication/mixins.py:349 #: authentication/mixins.py:354
msgid "Please change your password" msgid "Please change your password"
msgstr "请修改密码" msgstr "请修改密码"
@ -2006,7 +2040,7 @@ msgstr "代码错误"
#: authentication/templates/authentication/_msg_reset_password.html:3 #: authentication/templates/authentication/_msg_reset_password.html:3
#: authentication/templates/authentication/_msg_rest_password_success.html:2 #: authentication/templates/authentication/_msg_rest_password_success.html:2
#: authentication/templates/authentication/_msg_rest_public_key_success.html:2 #: authentication/templates/authentication/_msg_rest_public_key_success.html:2
#: jumpserver/conf.py:293 #: jumpserver/conf.py:293 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3
#: users/templates/users/_msg_account_expire_reminder.html:4 #: users/templates/users/_msg_account_expire_reminder.html:4
@ -2206,6 +2240,11 @@ msgstr "飞书查询用户失败"
msgid "The FeiShu is already bound to another user" msgid "The FeiShu is already bound to another user"
msgstr "该飞书已经绑定其他用户" msgstr "该飞书已经绑定其他用户"
#: authentication/views/feishu.py:148 authentication/views/login.py:176
#: notifications/backends/__init__.py:14 users/models/user.py:609
msgid "FeiShu"
msgstr "飞书"
#: authentication/views/feishu.py:149 #: authentication/views/feishu.py:149
msgid "Binding FeiShu successfully" msgid "Binding FeiShu successfully"
msgstr "绑定 飞书 成功" msgstr "绑定 飞书 成功"
@ -2234,11 +2273,6 @@ msgstr "正在跳转到 {} 认证"
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:176 notifications/backends/__init__.py:14
#: users/models/user.py:609
msgid "FeiShu"
msgstr "飞书"
#: authentication/views/login.py:265 #: authentication/views/login.py:265
msgid "" msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n" "Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
@ -2350,6 +2384,10 @@ msgstr "被其他对象关联,不能删除"
msgid "This action require verify your MFA" msgid "This action require verify your MFA"
msgstr "这个操作需要验证 MFA" msgstr "这个操作需要验证 MFA"
#: common/exceptions.py:53
msgid "Unexpect error occur"
msgstr ""
#: common/fields/model.py:80 #: common/fields/model.py:80
msgid "Marshal dict data to char field" msgid "Marshal dict data to char field"
msgstr "编码 dict 为 char" msgstr "编码 dict 为 char"
@ -2558,56 +2596,56 @@ msgstr "单位: 时"
msgid "Callback" msgid "Callback"
msgstr "回调" msgstr "回调"
#: ops/models/adhoc.py:143 #: ops/models/adhoc.py:154
msgid "Tasks" msgid "Tasks"
msgstr "任务" msgstr "任务"
#: ops/models/adhoc.py:144 #: ops/models/adhoc.py:155
msgid "Pattern" msgid "Pattern"
msgstr "模式" msgstr "模式"
#: ops/models/adhoc.py:145 #: ops/models/adhoc.py:156
msgid "Options" msgid "Options"
msgstr "选项" msgstr "选项"
#: ops/models/adhoc.py:147 #: ops/models/adhoc.py:158
msgid "Run as admin" msgid "Run as admin"
msgstr "再次执行" msgstr "再次执行"
#: ops/models/adhoc.py:150 #: ops/models/adhoc.py:161
msgid "Become" msgid "Become"
msgstr "Become" msgstr "Become"
#: ops/models/adhoc.py:151 #: ops/models/adhoc.py:162
msgid "Create by" msgid "Create by"
msgstr "创建者" msgstr "创建者"
#: ops/models/adhoc.py:240 #: ops/models/adhoc.py:251
msgid "Task display" msgid "Task display"
msgstr "任务名称" msgstr "任务名称"
#: ops/models/adhoc.py:242 #: ops/models/adhoc.py:253
msgid "Host amount" msgid "Host amount"
msgstr "主机数量" msgstr "主机数量"
#: ops/models/adhoc.py:244 #: ops/models/adhoc.py:255
msgid "Start time" msgid "Start time"
msgstr "开始时间" msgstr "开始时间"
#: ops/models/adhoc.py:245 #: ops/models/adhoc.py:256
msgid "End time" msgid "End time"
msgstr "完成时间" msgstr "完成时间"
#: ops/models/adhoc.py:247 ops/models/command.py:28 #: ops/models/adhoc.py:258 ops/models/command.py:28
#: terminal/serializers/session.py:39 #: terminal/serializers/session.py:39
msgid "Is finished" msgid "Is finished"
msgstr "是否完成" msgstr "是否完成"
#: ops/models/adhoc.py:249 #: ops/models/adhoc.py:260
msgid "Adhoc raw result" msgid "Adhoc raw result"
msgstr "结果" msgstr "结果"
#: ops/models/adhoc.py:250 #: ops/models/adhoc.py:261
msgid "Adhoc result summary" msgid "Adhoc result summary"
msgstr "汇总" msgstr "汇总"
@ -2655,11 +2693,11 @@ msgstr "内存使用率超过 {max_threshold}%: => {value}"
msgid "CPU load more than {max_threshold}: => {value}" msgid "CPU load more than {max_threshold}: => {value}"
msgstr "CPU 使用率超过 {max_threshold}: => {value}" msgstr "CPU 使用率超过 {max_threshold}: => {value}"
#: ops/tasks.py:71 #: ops/tasks.py:72
msgid "Clean task history period" msgid "Clean task history period"
msgstr "定期清除任务历史" msgstr "定期清除任务历史"
#: ops/tasks.py:84 #: ops/tasks.py:85
msgid "Clean celery log period" msgid "Clean celery log period"
msgstr "定期清除Celery日志" msgstr "定期清除Celery日志"
@ -2819,7 +2857,7 @@ msgstr "用户组数量"
msgid "System users amount" msgid "System users amount"
msgstr "系统用户数量" msgstr "系统用户数量"
#: perms/serializers/application/permission.py:88 #: perms/serializers/application/permission.py:79
msgid "" msgid ""
"The application list contains applications that are different from the " "The application list contains applications that are different from the "
"permission type. ({})" "permission type. ({})"
@ -2895,7 +2933,7 @@ msgstr "获取 LDAP 用户为 None"
msgid "Imported {} users successfully (Organization: {})" msgid "Imported {} users successfully (Organization: {})"
msgstr "成功导入 {} 个用户 ( 组织: {} )" msgstr "成功导入 {} 个用户 ( 组织: {} )"
#: settings/models.py:196 users/templates/users/reset_password.html:29 #: settings/models.py:195 users/templates/users/reset_password.html:29
msgid "Setting" msgid "Setting"
msgstr "设置" msgstr "设置"
@ -4938,7 +4976,7 @@ msgstr "流程"
msgid "TicketFlow" msgid "TicketFlow"
msgstr "工单流程" msgstr "工单流程"
#: tickets/models/ticket.py:296 #: tickets/models/ticket.py:297
msgid "Please try again" msgid "Please try again"
msgstr "请再次尝试" msgstr "请再次尝试"
@ -5750,10 +5788,6 @@ msgstr "改密计划执行"
msgid "Change auth plan task" msgid "Change auth plan task"
msgstr "改密计划任务" msgstr "改密计划任务"
#: xpack/plugins/change_auth_plan/models/base.py:24
msgid "Custom password"
msgstr "自定义密码"
#: xpack/plugins/change_auth_plan/models/base.py:25 #: xpack/plugins/change_auth_plan/models/base.py:25
msgid "All assets use the same random password" msgid "All assets use the same random password"
msgstr "使用相同的随机密码" msgstr "使用相同的随机密码"
@ -6311,6 +6345,15 @@ msgstr "旗舰版"
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid "MFA type not support: {}"
#~ msgstr "MFA 类型不支持:{}"
#~ msgid "Push system users to asset: {}({}) => {}"
#~ msgstr "推送系统用户到入资产: {}({}) => {}"
#~ msgid "Test system user connectivity: {} => {}"
#~ msgstr "测试系统用户可连接性: {} => {}"
#~ msgid "Account backup plan execution" #~ msgid "Account backup plan execution"
#~ msgstr "改密计划执行" #~ msgstr "改密计划执行"

View File

@ -13,5 +13,6 @@ class OpsConfig(AppConfig):
from orgs.utils import set_current_org from orgs.utils import set_current_org
set_current_org(Organization.root()) set_current_org(Organization.root())
from .celery import signal_handler from .celery import signal_handler
from . import signals_handler
from . import notifications from . import notifications
super().ready() super().ready()

View File

@ -9,7 +9,7 @@ from celery import current_task
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _, gettext
from common.utils import get_logger, lazyproperty from common.utils import get_logger, lazyproperty
from common.fields.model import ( from common.fields.model import (
@ -58,6 +58,17 @@ class Task(PeriodTaskModelMixin, OrgModelMixin):
else: else:
return False return False
@lazyproperty
def display_name(self):
sps = ['. ', ': ']
spb = {str(sp in self.name): sp for sp in sps}
sp = spb.get('True')
if not sp:
return self.name
tpl, data = self.name.split(sp, 1)
return gettext(tpl + sp) + data
@property @property
def timedelta(self): def timedelta(self):
if self.latest_execution: if self.latest_execution:

View File

@ -56,7 +56,7 @@ class TaskSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = Task model = Task
fields_mini = ['id', 'name'] fields_mini = ['id', 'name', 'display_name']
fields_small = fields_mini + [ fields_small = fields_mini + [
'interval', 'crontab', 'interval', 'crontab',
'is_periodic', 'is_deleted', 'is_periodic', 'is_deleted',

View File

@ -0,0 +1,34 @@
from django.utils import translation
from django.core.cache import cache
from celery.signals import task_prerun, task_postrun, before_task_publish
from common.db.utils import close_old_connections
TASK_LANG_CACHE_KEY = 'TASK_LANG_{}'
TASK_LANG_CACHE_TTL = 1800
@before_task_publish.connect()
def before_task_publish(headers=None, **kwargs):
task_id = headers.get('id')
current_lang = translation.get_language()
key = TASK_LANG_CACHE_KEY.format(task_id)
cache.set(key, current_lang, 1800)
@task_prerun.connect()
def on_celery_task_pre_run(task_id='', **kwargs):
# 关闭之前的数据库连接
close_old_connections()
# 保存 Lang context
key = TASK_LANG_CACHE_KEY.format(task_id)
task_lang = cache.get(key)
if task_lang:
translation.activate(task_lang)
@task_postrun.connect()
def on_celery_task_post_run(**kwargs):
close_old_connections()

View File

@ -5,9 +5,10 @@ import time
from django.conf import settings from django.conf import settings
from celery import shared_task, subtask from celery import shared_task, subtask
from celery.exceptions import SoftTimeLimitExceeded from celery.exceptions import SoftTimeLimitExceeded
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _, gettext
from common.utils import get_logger, get_object_or_none, get_log_keep_day from common.utils import get_logger, get_object_or_none, get_log_keep_day
from orgs.utils import tmp_to_root_org, tmp_to_org from orgs.utils import tmp_to_root_org, tmp_to_org
@ -141,10 +142,10 @@ def hello(name, callback=None):
import time import time
count = User.objects.count() count = User.objects.count()
print("Hello {}".format(name)) print(gettext("Hello") + ': ' + name)
print("Count: ", count) print("Count: ", count)
time.sleep(1) time.sleep(1)
return count return gettext("Hello")
@shared_task @shared_task
@ -177,3 +178,4 @@ def add_m(x):
s.append(add.s(i)) s.append(add.s(i))
res = chain(*tuple(s))() res = chain(*tuple(s))()
return res return res

View File

@ -6,14 +6,14 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from perms.models import ApplicationPermission from perms.models import ApplicationPermission
from ..base import ActionsField from ..base import ActionsField, BasePermissionSerializer
__all__ = [ __all__ = [
'ApplicationPermissionSerializer' 'ApplicationPermissionSerializer'
] ]
class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer): class ApplicationPermissionSerializer(BasePermissionSerializer):
actions = ActionsField(required=False, allow_null=True, label=_("Actions")) actions = ActionsField(required=False, allow_null=True, label=_("Actions"))
category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display')) category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display'))
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
@ -46,15 +46,7 @@ class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer):
'applications_amount': {'label': _('Applications amount')}, 'applications_amount': {'label': _('Applications amount')},
} }
def __init__(self, *args, **kwargs): def _filter_actions_choices(self, choices):
super().__init__(*args, **kwargs)
self.set_actions_choices()
def set_actions_choices(self):
actions = self.fields.get('actions')
if not actions:
return
choices = actions._choices
if request := self.context.get('request'): if request := self.context.get('request'):
category = request.query_params.get('category') category = request.query_params.get('category')
else: else:
@ -62,8 +54,7 @@ class ApplicationPermissionSerializer(BulkOrgResourceModelSerializer):
exclude_choices = ApplicationPermission.get_exclude_actions_choices(category=category) exclude_choices = ApplicationPermission.get_exclude_actions_choices(category=category)
for choice in exclude_choices: for choice in exclude_choices:
choices.pop(choice, None) choices.pop(choice, None)
actions._choices = choices return choices
actions.default = list(choices.keys())
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):

View File

@ -9,12 +9,12 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from perms.models import AssetPermission, Action from perms.models import AssetPermission, Action
from assets.models import Asset, Node, SystemUser from assets.models import Asset, Node, SystemUser
from users.models import User, UserGroup from users.models import User, UserGroup
from ..base import ActionsField from ..base import ActionsField, BasePermissionSerializer
__all__ = ['AssetPermissionSerializer'] __all__ = ['AssetPermissionSerializer']
class AssetPermissionSerializer(BulkOrgResourceModelSerializer): class AssetPermissionSerializer(BasePermissionSerializer):
actions = ActionsField(required=False, allow_null=True, label=_("Actions")) actions = ActionsField(required=False, allow_null=True, label=_("Actions"))
is_valid = serializers.BooleanField(read_only=True, label=_("Is valid")) is_valid = serializers.BooleanField(read_only=True, label=_("Is valid"))
is_expired = serializers.BooleanField(read_only=True, label=_('Is expired')) is_expired = serializers.BooleanField(read_only=True, label=_('Is expired'))

View File

@ -1,7 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from perms.models import Action from perms.models import Action
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
__all__ = ['ActionsDisplayField', 'ActionsField'] __all__ = ['ActionsDisplayField', 'ActionsField', 'BasePermissionSerializer']
class ActionsField(serializers.MultipleChoiceField): class ActionsField(serializers.MultipleChoiceField):
@ -24,3 +25,21 @@ class ActionsDisplayField(ActionsField):
choices = dict(Action.CHOICES) choices = dict(Action.CHOICES)
return [choices.get(i) for i in values] return [choices.get(i) for i in values]
class BasePermissionSerializer(BulkOrgResourceModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_actions_field()
def set_actions_field(self):
actions = self.fields.get('actions')
if not actions:
return
choices = actions._choices
choices = self._filter_actions_choices(choices)
actions._choices = choices
actions.default = list(choices.keys())
def _filter_actions_choices(self, choices):
return choices

View File

@ -2,7 +2,7 @@ from rest_framework import generics
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from django.conf import settings from django.conf import settings
from jumpserver.utils import has_valid_xpack_license from jumpserver.utils import has_valid_xpack_license, get_xpack_license_info
from common.utils import get_logger from common.utils import get_logger
from .. import serializers from .. import serializers
from ..utils import get_interface_setting from ..utils import get_interface_setting
@ -40,6 +40,7 @@ class PublicSettingApi(generics.RetrieveAPIView):
"SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME, "SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME,
"SECURITY_LUNA_REMEMBER_AUTH": settings.SECURITY_LUNA_REMEMBER_AUTH, "SECURITY_LUNA_REMEMBER_AUTH": settings.SECURITY_LUNA_REMEMBER_AUTH,
"XPACK_LICENSE_IS_VALID": has_valid_xpack_license(), "XPACK_LICENSE_IS_VALID": has_valid_xpack_license(),
"XPACK_LICENSE_INFO": get_xpack_license_info(),
"LOGIN_TITLE": self.get_login_title(), "LOGIN_TITLE": self.get_login_title(),
"LOGO_URLS": self.get_logo_urls(), "LOGO_URLS": self.get_logo_urls(),
"TICKETS_ENABLED": settings.TICKETS_ENABLED, "TICKETS_ENABLED": settings.TICKETS_ENABLED,

View File

@ -79,7 +79,6 @@ class Setting(models.Model):
item.refresh_setting() item.refresh_setting()
def refresh_setting(self): def refresh_setting(self):
logger.debug(f"Refresh setting: {self.name}")
if hasattr(self.__class__, f'refresh_{self.name}'): if hasattr(self.__class__, f'refresh_{self.name}'):
getattr(self.__class__, f'refresh_{self.name}')() getattr(self.__class__, f'refresh_{self.name}')()
else: else:

View File

@ -121,11 +121,7 @@
url: url, url: url,
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
success: onSuccess, success: onSuccess
error: function (text, data) {
toastr.error(data.error)
},
flash_message: false
}) })
} }
</script> </script>

View File

@ -29,7 +29,10 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
search_fields = [ search_fields = [
'title', 'action', 'type', 'status', 'applicant_display' 'title', 'action', 'type', 'status', 'applicant_display'
] ]
ordering_fields = ('title', 'applicant_display', 'status', 'state', 'action_display', 'date_created') ordering_fields = (
'title', 'applicant_display', 'status', 'state', 'action_display',
'date_created', 'serial_num',
)
ordering = ('-date_created', ) ordering = ('-date_created', )
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):

View File

@ -38,7 +38,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='ticket', model_name='ticket',
name='serial_num', name='serial_num',
field=models.CharField(max_length=256, null=True, unique=True, verbose_name='Serial number'), field=models.CharField(max_length=128, null=True, unique=True, verbose_name='Serial number'),
), ),
migrations.RunPython(fill_ticket_serial_number), migrations.RunPython(fill_ticket_serial_number),
] ]

View File

@ -77,7 +77,7 @@ class Ticket(CommonModelMixin, OrgModelMixin):
'TicketFlow', related_name='tickets', on_delete=models.SET_NULL, null=True, 'TicketFlow', related_name='tickets', on_delete=models.SET_NULL, null=True,
verbose_name=_("TicketFlow") verbose_name=_("TicketFlow")
) )
serial_num = models.CharField(max_length=256, unique=True, null=True, verbose_name=_('Serial number')) serial_num = models.CharField(max_length=128, unique=True, null=True, verbose_name=_('Serial number'))
class Meta: class Meta:
ordering = ('-date_created',) ordering = ('-date_created',)
@ -195,7 +195,8 @@ class Ticket(CommonModelMixin, OrgModelMixin):
def update_current_step_state_and_assignee(self, processor, state): def update_current_step_state_and_assignee(self, processor, state):
if self.status_closed: if self.status_closed:
raise AlreadyClosed raise AlreadyClosed
self.state = state if state != TicketState.approved:
self.state = state
current_node = self.current_node current_node = self.current_node
current_node.update(state=state) current_node.update(state=state)
current_node.first().ticket_assignees.filter(assignee=processor).update(state=state) current_node.first().ticket_assignees.filter(assignee=processor).update(state=state)
@ -260,7 +261,7 @@ class Ticket(CommonModelMixin, OrgModelMixin):
date_created = as_current_tz(self.date_created) date_created = as_current_tz(self.date_created)
date_prefix = date_created.strftime('%Y%m%d') date_prefix = date_created.strftime('%Y%m%d')
ticket = Ticket.objects.select_for_update().filter( ticket = Ticket.all().select_for_update().filter(
serial_num__startswith=date_prefix serial_num__startswith=date_prefix
).order_by('-date_created').first() ).order_by('-date_created').first()

View File

@ -6,7 +6,7 @@
<div> <div>
<p> <p>
{{ content }} {{ content | safe }}
</p> </p>
<p> <p>
{% trans 'Username' %}: {{ user.username }} <br /> {% trans 'Username' %}: {{ user.username }} <br />

View File

@ -1 +1 @@
g++ make iputils-ping default-libmysqlclient-dev libpq-dev libffi-dev libldap2-dev libsasl2-dev sshpass pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl g++ make iputils-ping default-libmysqlclient-dev libpq-dev libffi-dev libldap2-dev libsasl2-dev sshpass pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl libaio-dev freetds-dev