From 9d4a828c53fa13b56caba317c1b6ba06d7908d37 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Aug 2022 19:27:08 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=B9=B3=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/platform.py | 9 +-- apps/assets/const.py | 80 +++++++++++-------- .../migrations/0100_auto_20220430_2126.py | 62 +++++++++++--- .../migrations/0105_auto_20220810_1449.py | 41 ++++++++++ apps/assets/models/platform.py | 44 +++++----- apps/assets/serializers/platform.py | 26 +++--- 6 files changed, 176 insertions(+), 86 deletions(-) create mode 100644 apps/assets/migrations/0105_auto_20220810_1449.py diff --git a/apps/assets/api/platform.py b/apps/assets/api/platform.py index 6bc1c4681..4098dc29a 100644 --- a/apps/assets/api/platform.py +++ b/apps/assets/api/platform.py @@ -21,7 +21,7 @@ class AssetPlatformViewSet(JMSModelViewSet): search_fields = ['name'] rbac_perms = { 'categories': 'assets.view_platform', - 'type_limits': 'assets-view_platform' + 'type_constraints': 'assets-view_platform' } @action(methods=['GET'], detail=False) @@ -30,14 +30,13 @@ class AssetPlatformViewSet(JMSModelViewSet): serializer = self.get_serializer(data, many=True) return Response(serializer.data) - @action(methods=['GET'], detail=False, url_path='type-limits') - def type_limits(self, request, *args, **kwargs): + @action(methods=['GET'], detail=False, url_path='type-constraints') + def type_constraints(self, request, *args, **kwargs): category = request.query_params.get('category') tp = request.query_params.get('type') - limits = AllTypes.get_type_limits(category, tp) + limits = AllTypes.get_constraints(category, tp) return Response(limits) - def check_object_permissions(self, request, obj): if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( diff --git a/apps/assets/const.py b/apps/assets/const.py index 4da356115..d5bf84727 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -12,8 +12,16 @@ __all__ = [ class PlatformMixin: @classmethod - def platform_limits(cls): - return {} + def platform_constraints(cls): + return { + 'has_domain': False, + 'has_su': False, + 'has_ping': False, + 'has_change_password': False, + 'has_verify_account': False, + 'has_create_account': False, + '_protocols': [] + } class Category(PlatformMixin, models.TextChoices): @@ -24,25 +32,30 @@ class Category(PlatformMixin, models.TextChoices): WEB = 'web', _("Web") @classmethod - def platform_limits(cls): + def platform_constraints(cls) -> dict: return { cls.HOST: { 'has_domain': True, - 'protocols_limit': ['ssh', 'rdp', 'vnc', 'telnet'] + 'has_ping': True, + 'has_verify_account': True, + 'has_change_password': True, + 'has_create_account': True, + '_protocols': ['ssh', 'telnet'] }, cls.NETWORK: { 'has_domain': True, - 'protocols_limit': ['ssh', 'telnet'] + '_protocols': ['ssh', 'telnet'] }, cls.DATABASE: { - 'has_domain': True + 'has_domain': True, }, cls.WEB: { 'has_domain': False, + '_protocols': [] }, cls.CLOUD: { 'has_domain': False, - 'protocol_limit': [] + '_protocols': [] } } @@ -57,18 +70,17 @@ class HostTypes(PlatformMixin, models.TextChoices): OTHER_HOST = 'other_host', _("Other host") @classmethod - def platform_limits(cls): - return {} - - @classmethod - def get_default_port(cls): - defaults = { - cls.LINUX: 22, - cls.WINDOWS: 3389, - cls.UNIX: 22, - cls.BSD: 22, - cls.MACOS: 22, - cls.MAINFRAME: 22, + def platform_constraints(cls): + return { + cls.LINUX: { + '_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] + }, + cls.WINDOWS: { + '_protocols': ['ssh', 'rdp', 'vnc'] + }, + cls.MACOS: { + '_protocols': ['ssh', 'vnc'] + } } @@ -89,11 +101,11 @@ class DatabaseTypes(PlatformMixin, models.TextChoices): REDIS = 'redis', 'Redis' @classmethod - def platform_limits(cls): + def platform_constraints(cls): meta = {} for name, label in cls.choices: meta[name] = { - 'protocols_limit': [name] + 'protocols': [name] } return meta @@ -114,23 +126,25 @@ class AllTypes(metaclass=IncludesTextChoicesMeta): ] @classmethod - def get_type_limits(cls, category, tp): - limits = Category.platform_limits().get(category, {}) + def get_constraints(cls, category, tp): + constraints = PlatformMixin.platform_constraints() + category_constraints = Category.platform_constraints().get(category) or {} + constraints.update(category_constraints) + types_cls = dict(cls.category_types()).get(category) if not types_cls: - return {} - types_limits = types_cls.platform_limits() or {} - type_limits = types_limits.get(tp, {}) - limits.update(type_limits) + return constraints + type_constraints = types_cls.platform_constraints().get(tp) or {} + constraints.update(type_constraints) - _protocols_limit = limits.get('protocols_limit', []) + _protocols = constraints.pop('_protocols', []) default_ports = Protocol.default_ports() - protocols_limit = [] - for p in _protocols_limit: + protocols = [] + for p in _protocols: port = default_ports.get(p, 0) - protocols_limit.append(f'{p}/{port}') - limits['protocols_limit'] = protocols_limit - return limits + protocols.append({'name': p, 'port': port}) + constraints['protocols'] = protocols + return constraints @classmethod def category_types(cls): diff --git a/apps/assets/migrations/0100_auto_20220430_2126.py b/apps/assets/migrations/0100_auto_20220430_2126.py index bb2fc3b82..9651a9e43 100644 --- a/apps/assets/migrations/0100_auto_20220430_2126.py +++ b/apps/assets/migrations/0100_auto_20220430_2126.py @@ -11,16 +11,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='platform', - name='admin_user_default', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.systemuser', verbose_name='Admin user default'), - ), - migrations.AddField( - model_name='platform', - name='admin_user_enabled', - field=models.BooleanField(default=True, verbose_name='Admin user enabled'), - ), migrations.AddField( model_name='platform', name='domain_default', @@ -34,13 +24,63 @@ class Migration(migrations.Migration): migrations.AddField( model_name='platform', name='protocols_default', - field=models.CharField(blank=True, default='', max_length=128, verbose_name='Protocols default'), + field=models.JSONField(blank=True, default=list, max_length=128, verbose_name='Protocols default'), ), migrations.AddField( model_name='platform', name='protocols_enabled', field=models.BooleanField(default=True, verbose_name='Protocols enabled'), ), + migrations.AddField( + model_name='platform', + name='change_password_enabled', + field=models.BooleanField(default=False, verbose_name='Change password enabled'), + ), + migrations.AddField( + model_name='platform', + name='change_password_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Change password method'), + ), + migrations.AddField( + model_name='platform', + name='create_account_enabled', + field=models.BooleanField(default=False, verbose_name='Create account enabled'), + ), + migrations.AddField( + model_name='platform', + name='create_account_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Create account method'), + ), + migrations.AddField( + model_name='platform', + name='ping_enabled', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='platform', + name='ping_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Ping method'), + ), + migrations.AddField( + model_name='platform', + name='su_enabled', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='platform', + name='su_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='SU method'), + ), + migrations.AddField( + model_name='platform', + name='verify_account_enabled', + field=models.BooleanField(default=False, verbose_name='Verify account enabled'), + ), + migrations.AddField( + model_name='platform', + name='verify_account_method', + field=models.TextField(blank=True, max_length=32, null=True, verbose_name='Verify account method'), + ), migrations.AlterField( model_name='asset', name='category', diff --git a/apps/assets/migrations/0105_auto_20220810_1449.py b/apps/assets/migrations/0105_auto_20220810_1449.py new file mode 100644 index 000000000..18b933338 --- /dev/null +++ b/apps/assets/migrations/0105_auto_20220810_1449.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.14 on 2022-08-10 06:49 + +import assets.models.platform +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0104_auto_20220803_1859'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('cloud', 'Clouding'), ('web', 'Web')], max_length=16, verbose_name='Category'), + ), + migrations.AlterField( + model_name='asset', + name='platform', + field=models.ForeignKey(default=assets.models.platform.Platform.default, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.platform', verbose_name='Platform'), + ), + migrations.AlterField( + model_name='asset', + name='type', + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('general', 'General'), ('k8s', 'Kubernetes')], max_length=128, verbose_name='Type'), + ), + migrations.AlterField( + model_name='platform', + name='category', + field=models.CharField(choices=[('host', 'Host'), ('network', 'NetworkDevice'), ('database', 'Database'), ('cloud', 'Clouding'), ('web', 'Web')], default='host', max_length=16, verbose_name='Category'), + ), + migrations.AlterField( + model_name='platform', + name='type', + field=models.CharField(choices=[('linux', 'Linux'), ('windows', 'Windows'), ('unix', 'Unix'), ('bsd', 'BSD'), ('macos', 'MacOS'), ('mainframe', 'Mainframe'), ('other_host', 'Other host'), ('switch', 'Switch'), ('router', 'Router'), ('firewall', 'Firewall'), ('other_network', 'Other device'), ('mysql', 'MySQL'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('oracle', 'Oracle'), ('sqlserver', 'SQLServer'), ('mongodb', 'MongoDB'), ('redis', 'Redis'), ('general', 'General'), ('k8s', 'Kubernetes')], default='Linux', max_length=32, verbose_name='Type'), + ), + ] diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index d50afd27e..36e1b0b51 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -9,6 +9,10 @@ __all__ = ['Platform'] class Platform(models.Model): + """ + 对资产提供 约束和默认值 + 对资产进行抽象 + """ CHARSET_CHOICES = ( ('utf8', 'UTF-8'), ('gbk', 'GBK'), @@ -26,27 +30,25 @@ class Platform(models.Model): verbose_name=_("Domain default") ) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) - protocols_default = models.CharField( - max_length=128, default='', blank=True, verbose_name=_("Protocols default") + protocols_default = models.JSONField( + max_length=128, default=list, blank=True, verbose_name=_("Protocols default") ) - admin_user_enabled = models.BooleanField(default=True, verbose_name=_("Admin user enabled")) - admin_user_default = models.ForeignKey( - 'assets.SystemUser', null=True, on_delete=models.SET_NULL, - verbose_name=_("Admin user default") - ) - - @classmethod - def get_type_meta(cls, category, tp): - meta = Category.platform_meta().get(category, {}) - types = dict(AllTypes.category_types()).get(category) - types_meta = types.platform_meta() or {} - type_meta = types_meta.get(tp, {}) - meta.update(type_meta) - return meta + # Accounts + # 这应该和账号有关 + su_enabled = models.BooleanField(default=False) + su_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("SU method")) + ping_enabled = models.BooleanField(default=False) + ping_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Ping method")) + verify_account_enabled = models.BooleanField(default=False, verbose_name=_("Verify account enabled")) + verify_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Verify account method")) + create_account_enabled = models.BooleanField(default=False, verbose_name=_("Create account enabled")) + create_account_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Create account method")) + change_password_enabled = models.BooleanField(default=False, verbose_name=_("Change password enabled")) + change_password_method = models.TextField(max_length=32, blank=True, null=True, verbose_name=_("Change password method")) @property - def type_limits(self): - return AllTypes.get_type_limits(self.category, self.type) + def type_constraints(self): + return AllTypes.get_constraints(self.category, self.type) @classmethod def default(cls): @@ -55,12 +57,6 @@ class Platform(models.Model): ) return linux.id - def is_windows(self): - return self.type.lower() in ('windows',) - - def is_unixlike(self): - return self.type.lower() in ("linux", "unix", "macos", "bsd") - def __str__(self): return self.name diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index b7277741d..999821abc 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -8,19 +8,15 @@ from .mixin import CategoryDisplayMixin __all__ = ['PlatformSerializer'] +class PlatformProtocolsSerializer(serializers.Serializer): + name = serializers.CharField(max_length=255, required=True) + port = serializers.IntegerField(max_value=65535, min_value=1, required=True) + + class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - protocols_default = serializers.ListField(label=_('Protocols'), required=False) - type_limits = serializers.ReadOnlyField(required=False, read_only=True) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # TODO 修复 drf SlugField RegexValidator bug,之后记得删除 - validators = self.fields['name'].validators - if isinstance(validators[-1], RegexValidator): - validators.pop() - # self.set_platform_meta() + protocols_default = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) + type_constraints = serializers.ReadOnlyField(required=False, read_only=True) class Meta: model = Platform @@ -29,12 +25,16 @@ class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): 'meta', 'comment', 'charset', 'category', 'category_display', 'type', 'type_display', - 'type_limits', + 'su_enabled', 'su_method', + 'ping_enabled', 'ping_method', + 'verify_account_enabled', 'verify_account_method', + 'create_account_enabled', 'create_account_method', + 'change_password_enabled', 'change_password_method', + 'type_constraints', ] fields_fk = [ 'domain_enabled', 'domain_default', 'protocols_enabled', 'protocols_default', - 'admin_user_enabled', 'admin_user_default', ] fields = fields_small + fields_fk read_only_fields = [