perf: 修改表结构

This commit is contained in:
ibuler 2022-09-06 19:57:03 +08:00
parent 984b8dfb28
commit 585ce6b46a
22 changed files with 159 additions and 886 deletions

View File

@ -1,91 +0,0 @@
# coding: utf-8
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
class AppCategory(models.TextChoices):
db = 'db', _('Database')
remote_app = 'remote_app', _('Remote app')
cloud = 'cloud', 'Cloud'
@classmethod
def get_label(cls, category):
return dict(cls.choices).get(category, '')
@classmethod
def is_xpack(cls, category):
return category in ['remote_app']
class AppType(models.TextChoices):
# db category
mysql = 'mysql', 'MySQL'
mariadb = 'mariadb', 'MariaDB'
oracle = 'oracle', 'Oracle'
pgsql = 'postgresql', 'PostgreSQL'
sqlserver = 'sqlserver', 'SQLServer'
redis = 'redis', 'Redis'
mongodb = 'mongodb', 'MongoDB'
# remote-app category
chrome = 'chrome', 'Chrome'
mysql_workbench = 'mysql_workbench', 'MySQL Workbench'
vmware_client = 'vmware_client', 'vSphere Client'
custom = 'custom', _('Custom')
# cloud category
k8s = 'k8s', 'Kubernetes'
@classmethod
def category_types_mapper(cls):
return {
AppCategory.db: [
cls.mysql, cls.mariadb, cls.oracle, cls.pgsql,
cls.sqlserver, cls.redis, cls.mongodb
],
AppCategory.remote_app: [
cls.chrome, cls.mysql_workbench,
cls.vmware_client, cls.custom
],
AppCategory.cloud: [cls.k8s]
}
@classmethod
def type_category_mapper(cls):
mapper = {}
for category, tps in cls.category_types_mapper().items():
for tp in tps:
mapper[tp] = category
return mapper
@classmethod
def get_label(cls, tp):
return dict(cls.choices).get(tp, '')
@classmethod
def db_types(cls):
return [tp.value for tp in cls.category_types_mapper()[AppCategory.db]]
@classmethod
def remote_app_types(cls):
return [tp.value for tp in cls.category_types_mapper()[AppCategory.remote_app]]
@classmethod
def cloud_types(cls):
return [tp.value for tp in cls.category_types_mapper()[AppCategory.cloud]]
@classmethod
def is_xpack(cls, tp):
tp_category_mapper = cls.type_category_mapper()
category = tp_category_mapper[tp]
if AppCategory.is_xpack(category):
return True
return tp in ['oracle', 'postgresql', 'sqlserver']
class OracleVersion(models.TextChoices):
version_11g = '11g', '11g'
version_12c = '12c', '12c'
version_other = 'other', _('Other')

View File

@ -71,6 +71,6 @@ class Migration(migrations.Migration):
'verbose_name': 'Account', 'verbose_name': 'Account',
'unique_together': {('username', 'app', 'systemuser')}, 'unique_together': {('username', 'app', 'systemuser')},
}, },
bases=(models.Model, assets.models.base.AuthMixin), bases=(models.Model,),
), ),
] ]

View File

@ -1,320 +0,0 @@
from collections import defaultdict
from urllib.parse import urlencode, parse_qsl
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
from common.tree import TreeNode
from common.utils import is_uuid
from assets.models import Asset, SystemUser
from ..const import OracleVersion
from ..utils import KubernetesTree
from .. import const
class ApplicationTreeNodeMixin:
id: str
name: str
type: str
category: str
attrs: dict
@staticmethod
def create_tree_id(pid, type, v):
i = dict(parse_qsl(pid))
i[type] = v
tree_id = urlencode(i)
return tree_id
@classmethod
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
show_empty=True, show_count=True):
count = counts.get(c.value, 0)
if count == 0 and not show_empty:
return None
label = c.label
if count is not None and show_count:
label = '{} ({})'.format(label, count)
data = {
'id': id_,
'name': label,
'title': label,
'pId': pid,
'isParent': bool(count),
'open': opened,
'iconSkin': '',
'meta': {
'type': tp,
'data': {
'name': c.name,
'value': c.value
}
}
}
return TreeNode(**data)
@classmethod
def create_root_tree_node(cls, queryset, show_count=True):
count = queryset.count() if show_count else None
root_id = 'applications'
root_name = _('Applications')
if count is not None and show_count:
root_name = '{} ({})'.format(root_name, count)
node = TreeNode(**{
'id': root_id,
'name': root_name,
'title': root_name,
'pId': '',
'isParent': True,
'open': True,
'iconSkin': '',
'meta': {
'type': 'applications_root',
}
})
return node
@classmethod
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
nodes = []
categories = const.AppType.category_types_mapper().keys()
for category in categories:
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
continue
i = cls.create_tree_id(pid, 'category', category.value)
node = cls.create_choice_node(
category, i, pid=pid, tp='category',
counts=counts, opened=False, show_empty=show_empty,
show_count=show_count
)
if not node:
continue
nodes.append(node)
return nodes
@classmethod
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
nodes = []
temp_pid = pid
type_category_mapper = const.AppType.type_category_mapper()
types = const.AppType.type_category_mapper().keys()
for tp in types:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
continue
category = type_category_mapper.get(tp)
pid = cls.create_tree_id(pid, 'category', category.value)
i = cls.create_tree_id(pid, 'type', tp.value)
node = cls.create_choice_node(
tp, i, pid, tp='type', counts=counts, opened=False,
show_empty=show_empty, show_count=show_count
)
pid = temp_pid
if not node:
continue
nodes.append(node)
return nodes
@staticmethod
def get_tree_node_counts(queryset):
counts = defaultdict(int)
values = queryset.values_list('type', 'category')
for i in values:
tp = i[0]
category = i[1]
counts[tp] += 1
counts[category] += 1
return counts
@classmethod
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
counts = cls.get_tree_node_counts(queryset)
tree_nodes = []
# 类别的节点
tree_nodes += cls.create_category_tree_nodes(
pid, counts, show_empty=show_empty,
show_count=show_count
)
# 类型的节点
tree_nodes += cls.create_types_tree_nodes(
pid, counts, show_empty=show_empty,
show_count=show_count
)
return tree_nodes
@classmethod
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
tree_nodes = []
# 根节点有可能是组织名称
if root_node is None:
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
tree_nodes.append(root_node)
tree_nodes += cls.create_category_type_tree_nodes(
queryset, root_node.id, show_empty=show_empty, show_count=show_count
)
# 应用的节点
for app in queryset:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
continue
node = app.as_tree_node(root_node.id)
tree_nodes.append(node)
return tree_nodes
def create_app_tree_pid(self, root_id):
pid = self.create_tree_id(root_id, 'category', self.category)
pid = self.create_tree_id(pid, 'type', self.type)
return pid
def as_tree_node(self, pid, k8s_as_tree=False):
if self.type == const.AppType.k8s and k8s_as_tree:
node = KubernetesTree(pid).as_tree_node(self)
else:
node = self._as_tree_node(pid)
return node
def _attrs_to_tree(self):
if self.category == const.AppCategory.db:
return self.attrs
return {}
def _as_tree_node(self, pid):
icon_skin_category_mapper = {
'remote_app': 'chrome',
'db': 'database',
'cloud': 'cloud'
}
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
pid = self.create_app_tree_pid(pid)
node = TreeNode(**{
'id': str(self.id),
'name': self.name,
'title': self.name,
'pId': pid,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'application',
'data': {
'category': self.category,
'type': self.type,
'attrs': self._attrs_to_tree()
}
}
})
return node
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
APP_TYPE = const.AppType
name = models.CharField(max_length=128, verbose_name=_('Name'))
category = models.CharField(
max_length=16, choices=const.AppCategory.choices, verbose_name=_('Category')
)
type = models.CharField(
max_length=16, choices=const.AppType.choices, verbose_name=_('Type')
)
domain = models.ForeignKey(
'assets.Domain', null=True, blank=True, related_name='applications',
on_delete=models.SET_NULL, verbose_name=_("Domain"),
)
attrs = models.JSONField(default=dict, verbose_name=_('Attrs'))
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
class Meta:
verbose_name = _('Application')
unique_together = [('org_id', 'name')]
ordering = ('name',)
permissions = [
('match_application', _('Can match application')),
]
def __str__(self):
category_display = self.get_category_display()
type_display = self.get_type_display()
return f'{self.name}({type_display})[{category_display}]'
@property
def category_remote_app(self):
return self.category == const.AppCategory.remote_app.value
@property
def category_cloud(self):
return self.category == const.AppCategory.cloud.value
@property
def category_db(self):
return self.category == const.AppCategory.db.value
def is_type(self, tp):
return self.type == tp
def get_rdp_remote_app_setting(self):
from applications.serializers.attrs import get_serializer_class_by_application_type
if not self.category_remote_app:
raise ValueError(f"Not a remote app application: {self.name}")
serializer_class = get_serializer_class_by_application_type(self.type)
fields = serializer_class().get_fields()
parameters = [self.type]
for field_name in list(fields.keys()):
if field_name in ['asset']:
continue
value = self.attrs.get(field_name)
if not value:
continue
if field_name == 'path':
value = '\"%s\"' % value
parameters.append(str(value))
parameters = ' '.join(parameters)
return {
'program': '||jmservisor',
'working_directory': '',
'parameters': parameters
}
def get_remote_app_asset(self, raise_exception=True):
asset_id = self.attrs.get('asset')
if is_uuid(asset_id):
return Asset.objects.filter(id=asset_id).first()
if raise_exception:
raise ValueError("Remote App not has asset attr")
def get_target_ip(self):
target_ip = ''
if self.category_remote_app:
asset = self.get_remote_app_asset()
target_ip = asset.ip if asset else target_ip
elif self.category_cloud:
target_ip = self.attrs.get('cluster')
elif self.category_db:
target_ip = self.attrs.get('host')
return target_ip
def get_target_protocol_for_oracle(self):
""" Oracle 类型需要单独处理,因为要携带版本号 """
if not self.is_type(self.APP_TYPE.oracle):
return
version = self.attrs.get('version', OracleVersion.version_12c)
if version == OracleVersion.version_other:
return
return 'oracle_%s' % version
class ApplicationUser(SystemUser):
class Meta:
proxy = True
verbose_name = _('Application user')

View File

@ -1,60 +0,0 @@
# coding: utf-8
#
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from common.utils import get_logger, is_uuid, get_object_or_none
from assets.models import Asset
logger = get_logger(__file__)
__all__ = ['RemoteAppSerializer']
class ExistAssetPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def to_internal_value(self, data):
instance = super().to_internal_value(data)
return str(instance.id)
def to_representation(self, _id):
# _id 是 instance.id
if self.pk_field is not None:
return self.pk_field.to_representation(_id)
# 解决删除资产后远程应用更新页面会显示资产ID的问题
asset = get_object_or_none(Asset, id=_id)
if not asset:
return None
return _id
class RemoteAppSerializer(serializers.Serializer):
asset_info = serializers.SerializerMethodField(label=_('Asset Info'))
asset = ExistAssetPrimaryKeyRelatedField(
queryset=Asset.objects, required=True, label=_("Asset"), allow_null=True
)
path = serializers.CharField(
max_length=128, label=_('Application path'), allow_null=True
)
def validate_asset(self, asset):
if not asset:
raise serializers.ValidationError(_('This field is required.'))
return asset
@staticmethod
def get_asset_info(obj):
asset_id = obj.get('asset')
if not asset_id or not is_uuid(asset_id):
return {}
try:
asset = Asset.objects.get(id=str(asset_id))
except ObjectDoesNotExist as e:
logger.error(e)
return {}
if not asset:
return {}
asset_info = {'id': str(asset.id), 'hostname': asset.hostname}
return asset_info

View File

@ -1,16 +0,0 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from ..application_category import DBSerializer
from applications.const import OracleVersion
__all__ = ['OracleSerializer']
class OracleSerializer(DBSerializer):
version = serializers.ChoiceField(
choices=OracleVersion.choices, default=OracleVersion.version_12c,
allow_null=True, label=_('Version'),
help_text=_('Magnus currently supports only 11g and 12c connections')
)
port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True)

View File

@ -1,6 +1,5 @@
# Generated by Django 3.2.12 on 2022-07-11 08:59 # Generated by Django 3.2.12 on 2022-07-11 08:59
import assets.models.base
import common.db.fields import common.db.fields
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -21,10 +20,7 @@ class Migration(migrations.Migration):
name='HistoricalAccount', name='HistoricalAccount',
fields=[ fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4)), ('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
@ -55,10 +51,7 @@ class Migration(migrations.Migration):
name='Account', name='Account',
fields=[ fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), ('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), ('private_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
@ -77,6 +70,5 @@ class Migration(migrations.Migration):
'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')], 'permissions': [('view_accountsecret', 'Can view asset account secret'), ('change_accountsecret', 'Can change asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret')],
'unique_together': {('username', 'asset')}, 'unique_together': {('username', 'asset')},
}, },
bases=(models.Model, assets.models.base.AuthMixin, assets.models.protocol.ProtocolMixin),
), ),
] ]

View File

@ -17,8 +17,6 @@ class Migration(migrations.Migration):
name='AccountTemplate', name='AccountTemplate',
fields=[ fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('connectivity', models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity')),
('date_verified', models.DateTimeField(null=True, verbose_name='Date verified')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')), ('name', models.CharField(max_length=128, verbose_name='Name')),
('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')),
@ -34,7 +32,7 @@ class Migration(migrations.Migration):
], ],
options={ options={
'verbose_name': 'Account template', 'verbose_name': 'Account template',
'unique_together': {('name', 'org_id')},
}, },
bases=(models.Model, assets.models.base.AuthMixin), ),
)
] ]

View File

@ -1,138 +0,0 @@
# -*- coding: utf-8 -*-
#
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
from common.utils import lazyproperty, get_logger
from .base import BaseUser, AbsConnectivity
logger = get_logger(__name__)
__all__ = ['AuthBook']
class AuthBook(BaseUser, AbsConnectivity):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
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()
auth_attrs = ['username', 'password', 'private_key', 'public_key']
class Meta:
verbose_name = _('AuthBook')
unique_together = [('username', 'asset', 'systemuser')]
permissions = [
('test_authbook', _('Can test asset account connectivity')),
('view_assetaccountsecret', _('Can view asset account secret')),
('change_assetaccountsecret', _('Can change asset account secret')),
('view_assethistoryaccount', _('Can view asset history account')),
('view_assethistoryaccountsecret', _('Can view asset history account secret')),
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.auth_snapshot = {}
def get_or_systemuser_attr(self, attr):
val = getattr(self, attr, None)
if val:
return val
if self.systemuser:
return getattr(self.systemuser, attr, '')
return ''
def load_auth(self):
for attr in self.auth_attrs:
value = self.get_or_systemuser_attr(attr)
self.auth_snapshot[attr] = [getattr(self, attr), value]
setattr(self, attr, value)
def unload_auth(self):
if not self.systemuser:
return
for attr, values in self.auth_snapshot.items():
origin_value, loaded_value = values
current_value = getattr(self, attr, '')
if current_value == loaded_value:
setattr(self, attr, origin_value)
def save(self, *args, **kwargs):
self.unload_auth()
instance = super().save(*args, **kwargs)
self.load_auth()
return instance
@property
def username_display(self):
return self.get_or_systemuser_attr('username') or '*'
@lazyproperty
def systemuser_display(self):
if not self.systemuser:
return ''
return str(self.systemuser)
@property
def smart_name(self):
username = self.username_display
if self.asset:
asset = str(self.asset)
else:
asset = '*'
return '{}@{}'.format(username, asset)
def sync_to_system_user_account(self):
if self.systemuser:
return
matched = AuthBook.objects.filter(
asset=self.asset, systemuser__username=self.username
)
if not matched:
return
for i in matched:
i.password = self.password
i.private_key = self.private_key
i.public_key = self.public_key
i.comment = 'Update triggered by account {}'.format(self.id)
# 不触发post_save信号
self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key'])
def remove_asset_admin_user_if_need(self):
if not self.asset or not self.systemuser:
return
if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser:
return
self.asset.admin_user = None
self.asset.save()
logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser))
def update_asset_admin_user_if_need(self):
if not self.asset or not self.systemuser:
return
if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser:
return
self.asset.admin_user = self.systemuser
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

@ -3,20 +3,21 @@
# #
import logging import logging
import uuid
from common.db import fields
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from .base import BaseAccount from orgs.mixins.models import OrgModelMixin
from .protocol import ProtocolMixin
__all__ = ['SystemUser'] __all__ = ['SystemUser']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SystemUser(BaseAccount, ProtocolMixin): class SystemUser(OrgModelMixin):
LOGIN_AUTO = 'auto' LOGIN_AUTO = 'auto'
LOGIN_MANUAL = 'manual' LOGIN_MANUAL = 'manual'
LOGIN_MODE_CHOICES = ( LOGIN_MODE_CHOICES = (
@ -28,6 +29,19 @@ class SystemUser(BaseAccount, ProtocolMixin):
common = 'common', _('Common user') common = 'common', _('Common user')
admin = 'admin', _('Admin user') admin = 'admin', _('Admin user')
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
token = models.TextField(default='', verbose_name=_('Token'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user")) username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type')) type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)])
@ -37,7 +51,6 @@ class SystemUser(BaseAccount, ProtocolMixin):
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell')) shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root")) sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
token = models.TextField(default='', verbose_name=_('Token'))
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True) home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True)
system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True) system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True)
ad_domain = models.CharField(default='', max_length=256) ad_domain = models.CharField(default='', max_length=256)

View File

@ -2,13 +2,14 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from .base import BaseAccount, AbsConnectivity from common.utils import lazyproperty
from .base import BaseAccount
__all__ = ['Account', 'AccountTemplate'] __all__ = ['Account', 'AccountTemplate']
class Account(BaseAccount, AbsConnectivity): class Account(BaseAccount):
privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False)
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
version = models.IntegerField(default=0, verbose_name=_('Version')) version = models.IntegerField(default=0, verbose_name=_('Version'))
history = HistoricalRecords() history = HistoricalRecords()
@ -23,15 +24,28 @@ class Account(BaseAccount, AbsConnectivity):
('view_historyaccountsecret', _('Can view asset history account secret')), ('view_historyaccountsecret', _('Can view asset history account secret')),
] ]
@property
def name(self):
return "{}({})_{}".format(self.asset_name, self.ip, self.username)
@lazyproperty
def ip(self):
return self.asset.ip
@lazyproperty
def asset_name(self):
return self.asset.name
def __str__(self): def __str__(self):
return '{}@{}'.format(self.username, self.asset.name) return '{}@{}'.format(self.username, self.asset.name)
class AccountTemplate(BaseAccount, AbsConnectivity): class AccountTemplate(BaseAccount):
privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False)
class Meta: class Meta:
verbose_name = _('Account template') verbose_name = _('Account template')
unique_together = (
('name', 'org_id'),
)
def __str__(self): def __str__(self):
return '{}@{}'.format(self.username, self.name) return self.username

View File

@ -78,7 +78,6 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel):
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets',
verbose_name=_("Nodes")) verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
info = models.JSONField(verbose_name='Info', default=dict, blank=True) info = models.JSONField(verbose_name='Info', default=dict, blank=True)

View File

@ -13,11 +13,10 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from django.db.models import QuerySet from django.db.models import QuerySet
from common.utils import random_string
from common.utils import ( from common.utils import (
ssh_key_string_to_obj, ssh_key_gen, get_logger ssh_key_string_to_obj, ssh_key_gen, get_logger,
random_string, ssh_pubkey_gen,
) )
from common.utils.encode import ssh_pubkey_gen
from common.db import fields from common.db import fields
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
@ -55,11 +54,29 @@ class AbsConnectivity(models.Model):
abstract = True abstract = True
class AuthMixin: class BaseAccount(OrgModelMixin):
private_key = '' id = models.UUIDField(default=uuid.uuid4, primary_key=True)
password = '' name = models.CharField(max_length=128, verbose_name=_("Name"))
public_key = '' username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
username = '' password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token'))
privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False)
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
ASSET_USER_CACHE_TIME = 600
APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT"
APP_USER_CACHE_TIME = 600
def expire_assets_amount(self):
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cache.delete(cache_key)
@property @property
def ssh_key_fingerprint(self): def ssh_key_fingerprint(self):
@ -115,18 +132,11 @@ class AuthMixin:
pass pass
return None return None
def set_auth(self, password=None, private_key=None, public_key=None): def set_auth(self, **kwargs):
update_fields = [] update_fields = []
if password: for k, v in kwargs.items():
self.password = password setattr(self, k, v)
update_fields.append('password') update_fields.append(k)
if private_key:
self.private_key = private_key
update_fields.append('private_key')
if public_key:
self.public_key = public_key
update_fields.append('public_key')
if update_fields: if update_fields:
self.save(update_fields=update_fields) self.save(update_fields=update_fields)
@ -141,6 +151,7 @@ class AuthMixin:
self.password = '' self.password = ''
self.private_key = '' self.private_key = ''
self.public_key = '' self.public_key = ''
self.token = ''
self.save() self.save()
@staticmethod @staticmethod
@ -168,33 +179,6 @@ class AuthMixin:
public_key=_public_key public_key=_public_key
) )
class BaseAccount(OrgModelMixin, AuthMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
ASSET_USER_CACHE_TIME = 600
APPS_AMOUNT_CACHE_KEY = "APP_USER_{}_APPS_AMOUNT"
APP_USER_CACHE_TIME = 600
def get_username(self):
return self.username
def expire_assets_amount(self):
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cache.delete(cache_key)
def _to_secret_json(self): def _to_secret_json(self):
"""Push system user use it""" """Push system user use it"""
return { return {
@ -203,6 +187,7 @@ class BaseAccount(OrgModelMixin, AuthMixin):
'password': self.password, 'password': self.password,
'public_key': self.public_key, 'public_key': self.public_key,
'private_key': self.private_key_file, 'private_key': self.private_key_file,
'token': self.token
} }
class Meta: class Meta:

View File

@ -57,6 +57,7 @@ class Gateway(BaseAccount):
class Protocol(models.TextChoices): class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH' ssh = 'ssh', 'SSH'
name = models.CharField(max_length=128, verbose_name='Name')
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
port = models.IntegerField(default=22, verbose_name=_('Port')) port = models.IntegerField(default=22, verbose_name=_('Port'))
protocol = models.CharField(choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")) protocol = models.CharField(choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol"))
@ -64,6 +65,7 @@ class Gateway(BaseAccount):
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment")) comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
token = None token = None
privileged = None
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -5,67 +5,3 @@ from django.utils.translation import gettext_lazy as _
class Protocol(models.Model): class Protocol(models.Model):
name = models.CharField(max_length=32, verbose_name=_("Name")) name = models.CharField(max_length=32, verbose_name=_("Name"))
port = models.IntegerField(verbose_name=_("Port")) port = models.IntegerField(verbose_name=_("Port"))
class ProtocolMixin:
protocol: str
class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH'
rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet'
vnc = 'vnc', 'VNC'
mysql = 'mysql', 'MySQL'
oracle = 'oracle', 'Oracle'
mariadb = 'mariadb', 'MariaDB'
postgresql = 'postgresql', 'PostgreSQL'
sqlserver = 'sqlserver', 'SQLServer'
redis = 'redis', 'Redis'
mongodb = 'mongodb', 'MongoDB'
k8s = 'k8s', 'K8S'
SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp]
ASSET_CATEGORY_PROTOCOLS = [
Protocol.ssh, Protocol.rdp, Protocol.telnet, Protocol.vnc
]
APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [
Protocol.rdp
]
APPLICATION_CATEGORY_DB_PROTOCOLS = [
Protocol.mysql, Protocol.mariadb, Protocol.oracle,
Protocol.postgresql, Protocol.sqlserver,
Protocol.redis, Protocol.mongodb
]
APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [
Protocol.k8s
]
APPLICATION_CATEGORY_PROTOCOLS = [
*APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS,
*APPLICATION_CATEGORY_DB_PROTOCOLS,
*APPLICATION_CATEGORY_CLOUD_PROTOCOLS
]
@property
def is_protocol_support_push(self):
return self.protocol in self.SUPPORT_PUSH_PROTOCOLS
@classmethod
def get_protocol_by_application_type(cls, app_type):
from applications.const import AppType
if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
protocol = app_type
elif app_type in AppType.remote_app_types():
protocol = cls.Protocol.rdp
else:
protocol = None
return protocol
@property
def can_perm_to_asset(self):
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
@property
def is_asset_protocol(self):
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS

View File

@ -4,29 +4,58 @@ from rest_framework import serializers
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from assets.models import Account from assets.models import Account, AccountTemplate
from assets.serializers.base import AuthSerializerMixin from assets.serializers.base import AuthValidateMixin
from .account_template import AccountTemplateSerializerMixin from .common import AccountFieldsSerializerMixin
from .common import BaseAccountSerializer
class AccountSerializer( class AccountSerializerCreateMixin(serializers.ModelSerializer):
AccountTemplateSerializerMixin, template = serializers.UUIDField(
AuthSerializerMixin, required=False, allow_null=True, write_only=True,
BulkOrgResourceModelSerializer label=_('Account template')
): )
push_to_asset = serializers.BooleanField(default=False, label=_("Push to asset"), write_only=True)
@staticmethod
def validate_template(value):
AccountTemplate.objects.get_or_create()
model = AccountTemplate
try:
return model.objects.get(id=value)
except AccountTemplate.DoesNotExist:
raise serializers.ValidationError(_('Account template not found'))
@staticmethod
def replace_attrs(account_template: AccountTemplate, attrs: dict):
exclude_fields = [
'_state', 'org_id', 'date_verified', 'id',
'date_created', 'date_updated', 'created_by'
]
template_attrs = {k: v for k, v in account_template.__dict__.items() if k not in exclude_fields}
for k, v in template_attrs.items():
attrs.setdefault(k, v)
def validate(self, attrs):
account_template = attrs.pop('template', None)
if account_template:
self.replace_attrs(account_template, attrs)
push_to_asset = attrs.pop('push_to_asset', False)
return super().validate(attrs)
class AccountSerializer(AuthValidateMixin,
AccountSerializerCreateMixin,
AccountFieldsSerializerMixin,
BulkOrgResourceModelSerializer):
name = serializers.CharField(max_length=128, read_only=True, label=_("Name"))
ip = serializers.ReadOnlyField(label=_("IP")) ip = serializers.ReadOnlyField(label=_("IP"))
asset_name = serializers.ReadOnlyField(label=_("Asset")) asset_name = serializers.ReadOnlyField(label=_("Asset"))
platform = serializers.ReadOnlyField(label=_("Platform")) platform = serializers.ReadOnlyField(label=_("Platform"))
class Meta(BaseAccountSerializer.Meta): class Meta(AccountFieldsSerializerMixin.Meta):
model = Account model = Account
fields = BaseAccountSerializer.Meta.fields + ['account_template', ] fields = AccountFieldsSerializerMixin.Meta.fields \
+ ['template', 'push_to_asset']
def validate(self, attrs):
attrs = self._validate_gen_key(attrs)
attrs = super()._validate(attrs)
return attrs
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
@ -47,7 +76,6 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
'password': {'write_only': False}, 'password': {'write_only': False},
'private_key': {'write_only': False}, 'private_key': {'write_only': False},
'public_key': {'write_only': False}, 'public_key': {'write_only': False},
'systemuser_display': {'label': _('System user display')}
} }

View File

@ -1,18 +1,17 @@
from assets.models import Account from assets.models import Account
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from .common import BaseAccountSerializer from .common import AccountFieldsSerializerMixin
from .account import AccountSerializer, AccountSecretSerializer from .account import AccountSerializer, AccountSecretSerializer
class AccountHistorySerializer(AccountSerializer): class AccountHistorySerializer(AccountSerializer):
class Meta: class Meta:
model = Account.history.model model = Account.history.model
fields = BaseAccountSerializer.Meta.fields_mini + \ fields = AccountFieldsSerializerMixin.Meta.fields_mini + \
BaseAccountSerializer.Meta.fields_write_only + \ AccountFieldsSerializerMixin.Meta.fields_write_only + \
BaseAccountSerializer.Meta.fields_fk + \ AccountFieldsSerializerMixin.Meta.fields_fk + \
['history_id', 'date_created', 'date_updated'] ['history_id', 'date_created', 'date_updated']
read_only_fields = fields read_only_fields = fields
ref_name = 'AccountHistorySerializer' ref_name = 'AccountHistorySerializer'

View File

@ -3,16 +3,16 @@ from rest_framework import serializers
from assets.models import AccountTemplate from assets.models import AccountTemplate
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from assets.serializers.base import AuthSerializerMixin from assets.serializers.base import AuthValidateMixin
from .common import BaseAccountSerializer from .common import AccountFieldsSerializerMixin
class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AccountTemplateSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = AccountTemplate model = AccountTemplate
fields_mini = ['id', 'privileged', 'username', 'name'] fields_mini = ['id', 'privileged', 'username']
fields_write_only = BaseAccountSerializer.Meta.fields_write_only fields_write_only = AccountFieldsSerializerMixin.Meta.fields_write_only
fields_other = BaseAccountSerializer.Meta.fields_other fields_other = AccountFieldsSerializerMixin.Meta.fields_other
fields = fields_mini + fields_write_only + fields_other fields = fields_mini + fields_write_only + fields_other
extra_kwargs = { extra_kwargs = {
'username': {'required': True}, 'username': {'required': True},
@ -35,39 +35,3 @@ class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSeriali
return return
raise serializers.ValidationError(required_field_dict) raise serializers.ValidationError(required_field_dict)
class AccountTemplateSerializerMixin(serializers.ModelSerializer):
account_template = serializers.UUIDField(
required=False, allow_null=True, write_only=True,
label=_('Account template')
)
@staticmethod
def validate_account_template(value):
AccountTemplate.objects.get_or_create()
model = AccountTemplate
try:
return model.objects.get(id=value)
except AccountTemplate.DoesNotExist:
raise serializers.ValidationError(_('Account template not found'))
@staticmethod
def replace_attrs(account_template: AccountTemplate, attrs: dict):
exclude_fields = [
'_state', 'org_id', 'date_verified', 'id', 'date_created', 'date_updated', 'created_by'
]
template_attrs = {k: v for k, v in account_template.__dict__.items() if k not in exclude_fields}
for k, v in template_attrs.items():
attrs.setdefault(k, v)
def _validate(self, attrs):
account_template = attrs.pop('account_template', None)
if account_template:
self.replace_attrs(account_template, attrs)
else:
AccountTemplateSerializer.validate_required(attrs)
return attrs

View File

@ -2,25 +2,26 @@
# #
from rest_framework import serializers from rest_framework import serializers
__all__ = [ __all__ = ['AccountFieldsSerializerMixin']
'BaseAccountSerializer',
]
class BaseAccountSerializer(serializers.ModelSerializer): class AccountFieldsSerializerMixin(serializers.ModelSerializer):
class Meta: class Meta:
fields_mini = [ fields_mini = [
'id', 'privileged', 'username', 'ip', 'asset_name', 'id', 'name', 'username', 'privileged', 'ip',
'platform', 'version' 'asset_name', 'platform', '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', 'comment']
fields_small = fields_mini + fields_write_only + fields_other fields_small = fields_mini + fields_write_only + fields_other
fields_fk = ['asset'] fields_fk = ['asset']
fields = fields_small + fields_fk fields = fields_small + fields_fk
ref_name = 'AssetAccountSerializer'
extra_kwargs = { extra_kwargs = {
'username': {'required': True},
'private_key': {'write_only': True}, 'private_key': {'write_only': True},
'public_key': {'write_only': True}, 'public_key': {'write_only': True},
} }
def validate_name(self, value):
if not value:
return self.initial_data.get('username')
return ''

View File

@ -67,8 +67,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer):
'domain', 'platform', 'platform', 'domain', 'platform', 'platform',
] ]
fields_m2m = [ fields_m2m = [
'nodes', 'labels', 'accounts', 'protocols', 'nodes', 'labels', 'accounts', 'protocols', 'nodes_display',
'nodes_display',
] ]
read_only_fields = [ read_only_fields = [
'category', 'type', 'connectivity', 'date_verified', 'category', 'type', 'connectivity', 'date_verified',

View File

@ -11,44 +11,20 @@ from assets.models import Type
from .utils import validate_password_for_ansible from .utils import validate_password_for_ansible
class AuthSerializer(serializers.ModelSerializer): class AuthValidateMixin(serializers.Serializer):
password = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password'))
private_key = EncryptedField(required=False, allow_blank=True, allow_null=True, max_length=16384,
label=_('Private key'))
def gen_keys(self, private_key=None, password=None):
if private_key is None:
return None, None
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
return private_key, public_key
def save(self, **kwargs):
password = self.validated_data.pop('password', None) or None
private_key = self.validated_data.pop('private_key', None) or None
self.instance = super().save(**kwargs)
if password or private_key:
private_key, public_key = self.gen_keys(private_key, password)
self.instance.set_auth(password=password, private_key=private_key,
public_key=public_key)
return self.instance
class AuthSerializerMixin(serializers.ModelSerializer):
password = EncryptedField( password = EncryptedField(
label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, label=_('Password'), required=False, allow_blank=True, allow_null=True,
validators=[validate_password_for_ansible] max_length=1024, validators=[validate_password_for_ansible]
) )
private_key = EncryptedField( private_key = EncryptedField(
label=_('SSH private key'), required=False, allow_blank=True, allow_null=True, max_length=16384 label=_('SSH private key'), required=False, allow_blank=True,
allow_null=True, max_length=16384
) )
passphrase = serializers.CharField( passphrase = serializers.CharField(
allow_blank=True, allow_null=True, required=False, max_length=512, allow_blank=True, allow_null=True, required=False, max_length=512,
write_only=True, label=_('Key password') write_only=True, label=_('Key password')
) )
def validate_password(self, password):
return password
def validate_private_key(self, private_key): def validate_private_key(self, private_key):
if not private_key: if not private_key:
return return
@ -64,9 +40,6 @@ class AuthSerializerMixin(serializers.ModelSerializer):
private_key = string_io.getvalue() private_key = string_io.getvalue()
return private_key return private_key
def validate_public_key(self, public_key):
return public_key
@staticmethod @staticmethod
def clean_auth_fields(validated_data): def clean_auth_fields(validated_data):
for field in ('password', 'private_key', 'public_key'): for field in ('password', 'private_key', 'public_key'):
@ -87,6 +60,10 @@ class AuthSerializerMixin(serializers.ModelSerializer):
attrs['public_key'] = public_key attrs['public_key'] = public_key
return attrs return attrs
def validate(self, attrs):
attrs = self._validate_gen_key(attrs)
return super().validate(attrs)
def create(self, validated_data): def create(self, validated_data):
self.clean_auth_fields(validated_data) self.clean_auth_fields(validated_data)
return super().create(validated_data) return super().create(validated_data)
@ -97,9 +74,9 @@ class AuthSerializerMixin(serializers.ModelSerializer):
class TypesField(serializers.MultipleChoiceField): class TypesField(serializers.MultipleChoiceField):
def __init__(self, *args, **kwargs): def __init__(self, **kwargs):
kwargs['choices'] = Type.CHOICES kwargs['choices'] = Type.CHOICES
super().__init__(*args, **kwargs) super().__init__(**kwargs)
def to_representation(self, value): def to_representation(self, value):
return Type.value_to_choices(value) return Type.value_to_choices(value)

View File

@ -7,7 +7,7 @@ from common.validators import alphanumeric
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import SecretReadableMixin from common.drf.serializers import SecretReadableMixin
from ..models import Domain, Gateway from ..models import Domain, Gateway
from .base import AuthSerializerMixin from .base import AuthValidateMixin
class DomainSerializer(BulkOrgResourceModelSerializer): class DomainSerializer(BulkOrgResourceModelSerializer):
@ -38,17 +38,17 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
return obj.gateway_set.all().count() return obj.gateway_set.all().count()
class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class GatewaySerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) is_connective = serializers.BooleanField(required=False, label=_('Connectivity'))
class Meta: class Meta:
model = Gateway model = Gateway
fields_mini = ['id', 'name'] fields_mini = ['id', 'username']
fields_write_only = [ fields_write_only = [
'password', 'private_key', 'public_key', 'passphrase' 'password', 'private_key', 'public_key', 'passphrase'
] ]
fields_small = fields_mini + fields_write_only + [ fields_small = fields_mini + fields_write_only + [
'username', 'ip', 'port', 'protocol', 'ip', 'port', 'protocol',
'is_active', 'is_connective', 'is_active', 'is_connective',
'date_created', 'date_updated', 'date_created', 'date_updated',
'created_by', 'comment', 'created_by', 'comment',

View File

@ -2,7 +2,6 @@ from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from perms.models import Action from perms.models import Action
from applications.const import AppCategory, AppType
from .general import Ticket from .general import Ticket
__all__ = ['ApplyApplicationTicket'] __all__ = ['ApplyApplicationTicket']
@ -12,10 +11,10 @@ class ApplyApplicationTicket(Ticket):
apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name')) apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name'))
# 申请信息 # 申请信息
apply_category = models.CharField( apply_category = models.CharField(
max_length=16, choices=AppCategory.choices, verbose_name=_('Category') max_length=16, verbose_name=_('Category')
) )
apply_type = models.CharField( apply_type = models.CharField(
max_length=16, choices=AppType.choices, verbose_name=_('Type') max_length=16, verbose_name=_('Type')
) )
apply_applications = models.ManyToManyField( apply_applications = models.ManyToManyField(
'applications.Application', verbose_name=_('Apply applications'), 'applications.Application', verbose_name=_('Apply applications'),
@ -29,14 +28,6 @@ class ApplyApplicationTicket(Ticket):
apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True)
apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True)
@property
def apply_category_display(self):
return AppCategory.get_label(self.apply_category)
@property
def apply_type_display(self):
return AppType.get_label(self.apply_type)
@property @property
def apply_actions_display(self): def apply_actions_display(self):
return Action.value_to_choices_display(self.apply_actions) return Action.value_to_choices_display(self.apply_actions)