diff --git a/apps/assets/migrations/0028_protocol.py b/apps/assets/migrations/0028_protocol.py index 230c43773..82b311c11 100644 --- a/apps/assets/migrations/0028_protocol.py +++ b/apps/assets/migrations/0028_protocol.py @@ -1,12 +1,12 @@ # Generated by Django 2.1.7 on 2019-05-22 02:58 +import uuid + import django.core.validators from django.db import migrations, models -import uuid class Migration(migrations.Migration): - dependencies = [ ('assets', '0027_auto_20190521_1703'), ] @@ -16,8 +16,12 @@ class Migration(migrations.Migration): name='Protocol', fields=[ ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), - ('name', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Name')), - ('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(1)], verbose_name='Port')), + ('name', + models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], + default='ssh', max_length=16, verbose_name='Name')), + ('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), + django.core.validators.MinValueValidator(0)], + verbose_name='Port')), ], ), migrations.AddField( diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 88a6cfef2..ba642dd96 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -28,7 +28,7 @@ __all__ = [ class AssetProtocolsSerializer(serializers.ModelSerializer): - port = serializers.IntegerField(required=False, allow_null=True, max_value=65535, min_value=1) + port = serializers.IntegerField(required=False, allow_null=True, max_value=65535, min_value=0) def to_file_representation(self, data): return '{name}/{port}'.format(**data) @@ -259,8 +259,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali protocols_data_map = {p['name']: p for p in protocols_data} for p in protocols_data: port = p.get('port', 0) - if port < 1 or port > 65535: - error = p.get('name') + ': ' + _("port out of range (1-65535)") + if port < 0 or port > 65535: + error = p.get('name') + ': ' + _("port out of range (0-65535)") raise serializers.ValidationError(error) protocols_required, protocols_default = self._get_protocols_required_default() diff --git a/apps/assets/serializers/asset/custom.py b/apps/assets/serializers/asset/custom.py index d857aaa8b..6487517f6 100644 --- a/apps/assets/serializers/asset/custom.py +++ b/apps/assets/serializers/asset/custom.py @@ -1,22 +1,32 @@ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers from assets.models import Custom, Platform, Asset from common.const import UUID_PATTERN -from common.serializers import MethodSerializer, create_serializer_class -from common.serializers.common import DictSerializer +from common.serializers import create_serializer_class +from common.serializers.common import DictSerializer, MethodSerializer from .common import AssetSerializer __all__ = ['CustomSerializer'] +class CustomInfoSerializer(serializers.Serializer): + name = serializers.CharField(required=False) + + class CustomSerializer(AssetSerializer): - custom_info = MethodSerializer(label=_('Custom info')) + custom_info = MethodSerializer(label=_('Custom info'), required=False, allow_null=True) class Meta(AssetSerializer.Meta): model = Custom fields = AssetSerializer.Meta.fields + ['custom_info'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if hasattr(self, 'initial_data') and not self.initial_data.get('custom_info'): + self.initial_data['custom_info'] = {} + def get_custom_info_serializer(self): request = self.context.get('request') default_field = DictSerializer() diff --git a/apps/common/serializers/common.py b/apps/common/serializers/common.py index cd91f269c..67233b174 100644 --- a/apps/common/serializers/common.py +++ b/apps/common/serializers/common.py @@ -14,7 +14,7 @@ __all__ = [ 'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskExecutionSerializer', 'WritableNestedModelSerializer', 'GroupedChoiceSerializer', - 'FileSerializer' + 'FileSerializer', 'DictSerializer' ] diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index b420b3bf0..3101273a4 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -10,6 +10,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from rest_framework.serializers import ValidationError +from assets.models import Platform from common.db.models import JMSBaseModel from common.utils import lazyproperty, get_logger from common.utils.yml import yaml_load_with_i18n @@ -91,8 +92,7 @@ class Applet(JMSBaseModel): raise ValidationError({'error': 'Missing name in manifest.yml'}) return manifest - @classmethod - def load_platform_if_need(cls, d): + def load_platform_if_need(self, d): from assets.serializers import PlatformSerializer from assets.const import CustomTypes @@ -109,16 +109,21 @@ class Applet(JMSBaseModel): try: tp = data['type'] + platform_name = data['name'] except KeyError: raise ValidationError({'error': _('Missing type in platform.yml')}) if not data.get('automation'): data['automation'] = CustomTypes._get_automation_constrains()['*'] - s = PlatformSerializer(data=data) + created_by = 'Applet:{}'.format(self.name) + instance = Platform.objects.filter(name=platform_name, created_by=created_by).first() + s = PlatformSerializer(data=data, instance=instance) s.add_type_choices(tp, tp) s.is_valid(raise_exception=True) - s.save() + p = s.save() + p.created_by = created_by + p.save(update_fields=['created_by']) @classmethod def install_from_dir(cls, path, builtin=True): @@ -129,9 +134,8 @@ class Applet(JMSBaseModel): instance = cls.objects.filter(name=name).first() serializer = AppletSerializer(instance=instance, data=manifest) serializer.is_valid() - serializer.save(builtin=builtin) - - cls.load_platform_if_need(path) + instance = serializer.save(builtin=builtin) + instance.load_platform_if_need(path) pkg_path = default_storage.path('applets/{}'.format(name)) if os.path.exists(pkg_path): @@ -157,6 +161,11 @@ class Applet(JMSBaseModel): cache.set(prefer_key, host.id, timeout=None) return host + def get_related_platform(self): + created_by = 'Applet:{}'.format(self.name) + platform = Platform.objects.filter(created_by=created_by).first() + return platform + @staticmethod def random_select_prefer_account(user, host, accounts): msg = 'Applet host remain public accounts: {}: {}'.format(host.name, len(accounts)) @@ -197,7 +206,8 @@ class Applet(JMSBaseModel): if private_account and private_account.username not in accounts_username_used: account = private_account else: - accounts = accounts.exclude(username__in=accounts_username_used).filter(username__startswith='jms_') + accounts = accounts.exclude(username__in=accounts_username_used) \ + .filter(username__startswith='jms_') account = self.random_select_prefer_account(user, host, accounts) if not account: return @@ -212,6 +222,12 @@ class Applet(JMSBaseModel): 'ttl': ttl } + def delete(self, using=None, keep_parents=False): + platform = self.get_related_platform() + if platform and platform.assets.count() == 0: + platform.delete() + return super().delete(using, keep_parents) + class AppletPublication(JMSBaseModel): applet = models.ForeignKey('Applet', on_delete=models.CASCADE, related_name='publications',