diff --git a/apps/applications/const.py b/apps/applications/const.py deleted file mode 100644 index 4e0d2fe50..000000000 --- a/apps/applications/const.py +++ /dev/null @@ -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') diff --git a/apps/applications/migrations/0010_appaccount_historicalappaccount.py b/apps/applications/migrations/0010_appaccount_historicalappaccount.py index f79fd2475..cd0bf88d1 100644 --- a/apps/applications/migrations/0010_appaccount_historicalappaccount.py +++ b/apps/applications/migrations/0010_appaccount_historicalappaccount.py @@ -71,6 +71,6 @@ class Migration(migrations.Migration): 'verbose_name': 'Account', 'unique_together': {('username', 'app', 'systemuser')}, }, - bases=(models.Model, assets.models.base.AuthMixin), + bases=(models.Model,), ), ] diff --git a/apps/applications/models/application.py b/apps/applications/models/application.py deleted file mode 100644 index cc98abaf8..000000000 --- a/apps/applications/models/application.py +++ /dev/null @@ -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') diff --git a/apps/applications/serializers/attrs/application_category/remote_app.py b/apps/applications/serializers/attrs/application_category/remote_app.py deleted file mode 100644 index 063af6daa..000000000 --- a/apps/applications/serializers/attrs/application_category/remote_app.py +++ /dev/null @@ -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 diff --git a/apps/applications/serializers/attrs/application_type/oracle.py b/apps/applications/serializers/attrs/application_type/oracle.py deleted file mode 100644 index fdc8016d2..000000000 --- a/apps/applications/serializers/attrs/application_type/oracle.py +++ /dev/null @@ -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) diff --git a/apps/assets/migrations/0099_auto_20220711_1409.py b/apps/assets/migrations/0099_auto_20220711_1409.py index 674b1c447..52c8750dc 100644 --- a/apps/assets/migrations/0099_auto_20220711_1409.py +++ b/apps/assets/migrations/0099_auto_20220711_1409.py @@ -1,6 +1,5 @@ # Generated by Django 3.2.12 on 2022-07-11 08:59 -import assets.models.base import common.db.fields from django.conf import settings from django.db import migrations, models @@ -21,10 +20,7 @@ class Migration(migrations.Migration): name='HistoricalAccount', fields=[ ('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)), - ('name', models.CharField(max_length=128, verbose_name='Name')), ('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')), ('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', fields=[ ('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)), - ('name', models.CharField(max_length=128, verbose_name='Name')), ('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')), ('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')], 'unique_together': {('username', 'asset')}, }, - bases=(models.Model, assets.models.base.AuthMixin, assets.models.protocol.ProtocolMixin), ), ] diff --git a/apps/assets/migrations/0106_auto_20220819_1523.py b/apps/assets/migrations/0106_auto_20220819_1523.py index 6ed0be81c..c77fdb09b 100644 --- a/apps/assets/migrations/0106_auto_20220819_1523.py +++ b/apps/assets/migrations/0106_auto_20220819_1523.py @@ -17,8 +17,6 @@ class Migration(migrations.Migration): name='AccountTemplate', fields=[ ('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)), ('name', models.CharField(max_length=128, verbose_name='Name')), ('username', models.CharField(blank=True, db_index=True, max_length=128, verbose_name='Username')), @@ -34,7 +32,7 @@ class Migration(migrations.Migration): ], options={ 'verbose_name': 'Account template', + 'unique_together': {('name', 'org_id')}, }, - bases=(models.Model, assets.models.base.AuthMixin), - ) + ), ] diff --git a/apps/assets/models/_authbook.py b/apps/assets/models/_authbook.py deleted file mode 100644 index a6b52927b..000000000 --- a/apps/assets/models/_authbook.py +++ /dev/null @@ -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 - diff --git a/apps/assets/models/_user.py b/apps/assets/models/_user.py index ab593b016..d7809a2b3 100644 --- a/apps/assets/models/_user.py +++ b/apps/assets/models/_user.py @@ -3,20 +3,21 @@ # import logging +import uuid +from common.db import fields 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 .base import BaseAccount -from .protocol import ProtocolMixin +from orgs.mixins.models import OrgModelMixin __all__ = ['SystemUser'] logger = logging.getLogger(__name__) -class SystemUser(BaseAccount, ProtocolMixin): +class SystemUser(OrgModelMixin): LOGIN_AUTO = 'auto' LOGIN_MANUAL = 'manual' LOGIN_MODE_CHOICES = ( @@ -28,6 +29,19 @@ class SystemUser(BaseAccount, ProtocolMixin): common = 'common', _('Common 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")) 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)]) @@ -37,7 +51,6 @@ class SystemUser(BaseAccount, ProtocolMixin): 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')) 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) system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True) ad_domain = models.CharField(default='', max_length=256) diff --git a/apps/assets/models/account.py b/apps/assets/models/account.py index fc76e86ee..6f122cfe2 100644 --- a/apps/assets/models/account.py +++ b/apps/assets/models/account.py @@ -2,13 +2,14 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords -from .base import BaseAccount, AbsConnectivity +from common.utils import lazyproperty + +from .base import BaseAccount __all__ = ['Account', 'AccountTemplate'] -class Account(BaseAccount, AbsConnectivity): - privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) +class Account(BaseAccount): asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) version = models.IntegerField(default=0, verbose_name=_('Version')) history = HistoricalRecords() @@ -23,15 +24,28 @@ class Account(BaseAccount, AbsConnectivity): ('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): return '{}@{}'.format(self.username, self.asset.name) -class AccountTemplate(BaseAccount, AbsConnectivity): - privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False) - +class AccountTemplate(BaseAccount): class Meta: verbose_name = _('Account template') + unique_together = ( + ('name', 'org_id'), + ) def __str__(self): - return '{}@{}'.format(self.username, self.name) + return self.username diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index 0a995d8cc..fad119a03 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -78,7 +78,6 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) - labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) info = models.JSONField(verbose_name='Info', default=dict, blank=True) diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 9cdac9e1f..ce7b99da3 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -13,11 +13,10 @@ from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django.db.models import QuerySet -from common.utils import random_string 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 orgs.mixins.models import OrgModelMixin @@ -55,11 +54,29 @@ class AbsConnectivity(models.Model): abstract = True -class AuthMixin: - private_key = '' - password = '' - public_key = '' - username = '' +class BaseAccount(OrgModelMixin): + 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')) + 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 def ssh_key_fingerprint(self): @@ -115,18 +132,11 @@ class AuthMixin: pass return None - def set_auth(self, password=None, private_key=None, public_key=None): + def set_auth(self, **kwargs): update_fields = [] - if password: - self.password = password - update_fields.append('password') - 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') - + for k, v in kwargs.items(): + setattr(self, k, v) + update_fields.append(k) if update_fields: self.save(update_fields=update_fields) @@ -141,6 +151,7 @@ class AuthMixin: self.password = '' self.private_key = '' self.public_key = '' + self.token = '' self.save() @staticmethod @@ -168,33 +179,6 @@ class AuthMixin: 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): """Push system user use it""" return { @@ -203,6 +187,7 @@ class BaseAccount(OrgModelMixin, AuthMixin): 'password': self.password, 'public_key': self.public_key, 'private_key': self.private_key_file, + 'token': self.token } class Meta: diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 72a5c748c..12da79df1 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -57,6 +57,7 @@ class Gateway(BaseAccount): class Protocol(models.TextChoices): ssh = 'ssh', 'SSH' + name = models.CharField(max_length=128, verbose_name='Name') ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) port = models.IntegerField(default=22, verbose_name=_('Port')) 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")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) token = None + privileged = None def __str__(self): return self.name diff --git a/apps/assets/models/protocol.py b/apps/assets/models/protocol.py index 22d330339..d42ee2754 100644 --- a/apps/assets/models/protocol.py +++ b/apps/assets/models/protocol.py @@ -5,67 +5,3 @@ from django.utils.translation import gettext_lazy as _ class Protocol(models.Model): name = models.CharField(max_length=32, verbose_name=_("Name")) 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 - diff --git a/apps/assets/serializers/account/account.py b/apps/assets/serializers/account/account.py index 0e73f6a1b..1c60e5d98 100644 --- a/apps/assets/serializers/account/account.py +++ b/apps/assets/serializers/account/account.py @@ -4,29 +4,58 @@ from rest_framework import serializers from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin -from assets.models import Account -from assets.serializers.base import AuthSerializerMixin -from .account_template import AccountTemplateSerializerMixin -from .common import BaseAccountSerializer +from assets.models import Account, AccountTemplate +from assets.serializers.base import AuthValidateMixin +from .common import AccountFieldsSerializerMixin -class AccountSerializer( - AccountTemplateSerializerMixin, - AuthSerializerMixin, - BulkOrgResourceModelSerializer -): +class AccountSerializerCreateMixin(serializers.ModelSerializer): + template = serializers.UUIDField( + required=False, allow_null=True, write_only=True, + 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")) asset_name = serializers.ReadOnlyField(label=_("Asset")) platform = serializers.ReadOnlyField(label=_("Platform")) - class Meta(BaseAccountSerializer.Meta): + class Meta(AccountFieldsSerializerMixin.Meta): model = Account - fields = BaseAccountSerializer.Meta.fields + ['account_template', ] - - def validate(self, attrs): - attrs = self._validate_gen_key(attrs) - attrs = super()._validate(attrs) - return attrs + fields = AccountFieldsSerializerMixin.Meta.fields \ + + ['template', 'push_to_asset'] @classmethod def setup_eager_loading(cls, queryset): @@ -47,7 +76,6 @@ class AccountSecretSerializer(SecretReadableMixin, AccountSerializer): 'password': {'write_only': False}, 'private_key': {'write_only': False}, 'public_key': {'write_only': False}, - 'systemuser_display': {'label': _('System user display')} } diff --git a/apps/assets/serializers/account/account_history.py b/apps/assets/serializers/account/account_history.py index 7fadc1a47..0a6c2042f 100644 --- a/apps/assets/serializers/account/account_history.py +++ b/apps/assets/serializers/account/account_history.py @@ -1,18 +1,17 @@ from assets.models import Account from common.drf.serializers import SecretReadableMixin -from .common import BaseAccountSerializer +from .common import AccountFieldsSerializerMixin from .account import AccountSerializer, AccountSecretSerializer class AccountHistorySerializer(AccountSerializer): - class Meta: model = Account.history.model - fields = BaseAccountSerializer.Meta.fields_mini + \ - BaseAccountSerializer.Meta.fields_write_only + \ - BaseAccountSerializer.Meta.fields_fk + \ - ['history_id', 'date_created', 'date_updated'] + fields = AccountFieldsSerializerMixin.Meta.fields_mini + \ + AccountFieldsSerializerMixin.Meta.fields_write_only + \ + AccountFieldsSerializerMixin.Meta.fields_fk + \ + ['history_id', 'date_created', 'date_updated'] read_only_fields = fields ref_name = 'AccountHistorySerializer' diff --git a/apps/assets/serializers/account/account_template.py b/apps/assets/serializers/account/account_template.py index 068239e74..5fe53c26c 100644 --- a/apps/assets/serializers/account/account_template.py +++ b/apps/assets/serializers/account/account_template.py @@ -3,16 +3,16 @@ from rest_framework import serializers from assets.models import AccountTemplate from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from assets.serializers.base import AuthSerializerMixin -from .common import BaseAccountSerializer +from assets.serializers.base import AuthValidateMixin +from .common import AccountFieldsSerializerMixin -class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +class AccountTemplateSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): class Meta: model = AccountTemplate - fields_mini = ['id', 'privileged', 'username', 'name'] - fields_write_only = BaseAccountSerializer.Meta.fields_write_only - fields_other = BaseAccountSerializer.Meta.fields_other + fields_mini = ['id', 'privileged', 'username'] + fields_write_only = AccountFieldsSerializerMixin.Meta.fields_write_only + fields_other = AccountFieldsSerializerMixin.Meta.fields_other fields = fields_mini + fields_write_only + fields_other extra_kwargs = { 'username': {'required': True}, @@ -35,39 +35,3 @@ class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSeriali return 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 - - - - diff --git a/apps/assets/serializers/account/common.py b/apps/assets/serializers/account/common.py index 3ad033e0e..1627bf8c3 100644 --- a/apps/assets/serializers/account/common.py +++ b/apps/assets/serializers/account/common.py @@ -2,25 +2,26 @@ # from rest_framework import serializers -__all__ = [ - 'BaseAccountSerializer', -] +__all__ = ['AccountFieldsSerializerMixin'] -class BaseAccountSerializer(serializers.ModelSerializer): +class AccountFieldsSerializerMixin(serializers.ModelSerializer): class Meta: fields_mini = [ - 'id', 'privileged', 'username', 'ip', 'asset_name', - 'platform', 'version' + 'id', 'name', 'username', 'privileged', 'ip', + 'asset_name', 'platform', 'version' ] 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_fk = ['asset'] fields = fields_small + fields_fk - ref_name = 'AssetAccountSerializer' extra_kwargs = { - 'username': {'required': True}, 'private_key': {'write_only': True}, 'public_key': {'write_only': True}, } + + def validate_name(self, value): + if not value: + return self.initial_data.get('username') + return '' diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 439d96483..8d41eee84 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -67,8 +67,7 @@ class AssetSerializer(JMSWritableNestedModelSerializer): 'domain', 'platform', 'platform', ] fields_m2m = [ - 'nodes', 'labels', 'accounts', 'protocols', - 'nodes_display', + 'nodes', 'labels', 'accounts', 'protocols', 'nodes_display', ] read_only_fields = [ 'category', 'type', 'connectivity', 'date_verified', diff --git a/apps/assets/serializers/base.py b/apps/assets/serializers/base.py index 9e4192fb3..e421b8f43 100644 --- a/apps/assets/serializers/base.py +++ b/apps/assets/serializers/base.py @@ -11,44 +11,20 @@ from assets.models import Type from .utils import validate_password_for_ansible -class AuthSerializer(serializers.ModelSerializer): - 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): +class AuthValidateMixin(serializers.Serializer): password = EncryptedField( - label=_('Password'), required=False, allow_blank=True, allow_null=True, max_length=1024, - validators=[validate_password_for_ansible] + label=_('Password'), required=False, allow_blank=True, allow_null=True, + max_length=1024, validators=[validate_password_for_ansible] ) 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( allow_blank=True, allow_null=True, required=False, max_length=512, write_only=True, label=_('Key password') ) - def validate_password(self, password): - return password - def validate_private_key(self, private_key): if not private_key: return @@ -64,9 +40,6 @@ class AuthSerializerMixin(serializers.ModelSerializer): private_key = string_io.getvalue() return private_key - def validate_public_key(self, public_key): - return public_key - @staticmethod def clean_auth_fields(validated_data): for field in ('password', 'private_key', 'public_key'): @@ -87,6 +60,10 @@ class AuthSerializerMixin(serializers.ModelSerializer): attrs['public_key'] = public_key return attrs + def validate(self, attrs): + attrs = self._validate_gen_key(attrs) + return super().validate(attrs) + def create(self, validated_data): self.clean_auth_fields(validated_data) return super().create(validated_data) @@ -97,9 +74,9 @@ class AuthSerializerMixin(serializers.ModelSerializer): class TypesField(serializers.MultipleChoiceField): - def __init__(self, *args, **kwargs): + def __init__(self, **kwargs): kwargs['choices'] = Type.CHOICES - super().__init__(*args, **kwargs) + super().__init__(**kwargs) def to_representation(self, value): return Type.value_to_choices(value) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index d67b624e4..82fd9433f 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -7,7 +7,7 @@ from common.validators import alphanumeric from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.drf.serializers import SecretReadableMixin from ..models import Domain, Gateway -from .base import AuthSerializerMixin +from .base import AuthValidateMixin class DomainSerializer(BulkOrgResourceModelSerializer): @@ -38,17 +38,17 @@ class DomainSerializer(BulkOrgResourceModelSerializer): return obj.gateway_set.all().count() -class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +class GatewaySerializer(AuthValidateMixin, BulkOrgResourceModelSerializer): is_connective = serializers.BooleanField(required=False, label=_('Connectivity')) class Meta: model = Gateway - fields_mini = ['id', 'name'] + fields_mini = ['id', 'username'] fields_write_only = [ 'password', 'private_key', 'public_key', 'passphrase' ] fields_small = fields_mini + fields_write_only + [ - 'username', 'ip', 'port', 'protocol', + 'ip', 'port', 'protocol', 'is_active', 'is_connective', 'date_created', 'date_updated', 'created_by', 'comment', diff --git a/apps/tickets/models/ticket/apply_application.py b/apps/tickets/models/ticket/apply_application.py index 378047db2..115d45a9f 100644 --- a/apps/tickets/models/ticket/apply_application.py +++ b/apps/tickets/models/ticket/apply_application.py @@ -2,7 +2,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from perms.models import Action -from applications.const import AppCategory, AppType from .general import Ticket __all__ = ['ApplyApplicationTicket'] @@ -12,10 +11,10 @@ class ApplyApplicationTicket(Ticket): apply_permission_name = models.CharField(max_length=128, verbose_name=_('Permission name')) # 申请信息 apply_category = models.CharField( - max_length=16, choices=AppCategory.choices, verbose_name=_('Category') + max_length=16, verbose_name=_('Category') ) apply_type = models.CharField( - max_length=16, choices=AppType.choices, verbose_name=_('Type') + max_length=16, verbose_name=_('Type') ) apply_applications = models.ManyToManyField( '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_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 def apply_actions_display(self): return Action.value_to_choices_display(self.apply_actions)