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,)
def get_queryset(self):
queryset = Account.objects.all() \
.annotate(type=F('app__type')) \
.annotate(app_display=F('app__name')) \
.annotate(systemuser_display=F('systemuser__name')) \
.annotate(category=F('app__category'))
queryset = Account.get_queryset()
return queryset

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
<div>
<p>
{{ content }}
{{ content | safe }}
</p>
<p>
{% 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