mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-25 14:32:05 +00:00
perf: 修改表结构
This commit is contained in:
parent
984b8dfb28
commit
585ce6b46a
@ -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')
|
|
@ -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,),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -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')
|
|
@ -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
|
|
@ -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)
|
|
@ -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),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -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),
|
),
|
||||||
)
|
|
||||||
]
|
]
|
||||||
|
@ -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
|
|
||||||
|
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
|
|
||||||
|
@ -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')}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
|
|
||||||
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'
|
||||||
|
@ -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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 ''
|
||||||
|
@ -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',
|
||||||
|
@ -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)
|
||||||
|
@ -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',
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user