diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index dee95ed06..7877c5b90 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -14,7 +14,7 @@ from .. import serializers from ..tasks import ( update_asset_hardware_info_manual, test_asset_connectivity_manual ) -from ..filters import AssetByNodeFilterBackend, LabelFilterBackend +from ..filters import AssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend logger = get_logger(__file__) @@ -32,7 +32,7 @@ class AssetViewSet(OrgBulkModelViewSet): model = Asset filter_fields = ( "hostname", "ip", "systemuser__id", "admin_user__id", "platform__base", - "is_active" + "is_active", 'ip' ) search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") @@ -41,7 +41,7 @@ class AssetViewSet(OrgBulkModelViewSet): 'display': serializers.AssetDisplaySerializer, } permission_classes = (IsOrgAdminOrAppUser,) - extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend] + extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend] def set_assets_node(self, assets): if not isinstance(assets, list): diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 13d8f9e60..149ed12a8 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -import coreapi +from rest_framework.compat import coreapi, coreschema from rest_framework import filters from django.db.models import Q @@ -117,3 +117,23 @@ class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend): def perform_query(pattern, queryset): return queryset.filter(asset__nodes__key__regex=pattern).distinct() + +class IpInFilterBackend(filters.BaseFilterBackend): + def filter_queryset(self, request, queryset, view): + ips = request.query_params.get('ips') + if not ips: + return queryset + ip_list = [i.strip() for i in ips.split(',')] + queryset = queryset.filter(ip__in=ip_list) + return queryset + + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='ips', location='query', required=False, type='string', + schema=coreschema.String( + title='ips', + description='ip in filter' + ) + ) + ] diff --git a/apps/assets/migrations/0050_auto_20200702_1602.py b/apps/assets/migrations/0050_auto_20200702_1602.py new file mode 100644 index 000000000..5471003ae --- /dev/null +++ b/apps/assets/migrations/0050_auto_20200702_1602.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.10 on 2020-07-02 08:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0049_systemuser_sftp_root'), + ] + + operations = [ + migrations.AlterField( + model_name='domain', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterUniqueTogether( + name='domain', + unique_together={('org_id', 'name')}, + ), + ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 20c040f74..6b5e716e0 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -244,10 +244,6 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): def platform_base(self): return self.platform.base - @lazyproperty - def admin_user_display(self): - return self.admin_user.name - @lazyproperty def admin_user_username(self): """求可连接性时,直接用用户名去取,避免再查一次admin user diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 2f4cc82b3..296e4cd18 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -17,13 +17,14 @@ __all__ = ['Domain', 'Gateway'] class Domain(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) + name = models.CharField(max_length=128, verbose_name=_('Name')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) class Meta: verbose_name = _("Domain") + unique_together = [('org_id', 'name')] def __str__(self): return self.name diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 1bef55dd9..461e61e08 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -199,6 +199,20 @@ class FamilyMixin: ) return child + def get_or_create_child(self, value, _id=None): + """ + :return: Node, bool (created) + """ + children = self.get_children() + exist = children.filter(value=value).exists() + if exist: + child = children.filter(value=value).first() + created = False + else: + child = self.create_child(value, _id) + created = True + return child, created + def get_next_child_key(self): mark = self.child_mark self.child_mark += 1 diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index e36f41946..4100c439b 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -67,6 +67,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer): slug_field='name', queryset=Platform.objects.all(), label=_("Platform") ) protocols = ProtocolsField(label=_('Protocols'), required=False) + domain_display = serializers.ReadOnlyField(source='domain.name') + admin_user_display = serializers.ReadOnlyField(source='admin_user.name') + """ 资产的数据结构 """ @@ -82,7 +85,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'created_by', 'date_created', 'hardware_info', ] fields_fk = [ - 'admin_user', 'admin_user_display', 'domain', 'platform' + 'admin_user', 'admin_user_display', 'domain', 'domain_display', 'platform' ] fk_only_fields = { 'platform': ['name'] diff --git a/apps/assets/tasks/asset_user_connectivity.py b/apps/assets/tasks/asset_user_connectivity.py index 6d979d6b1..ab1c417cc 100644 --- a/apps/assets/tasks/asset_user_connectivity.py +++ b/apps/assets/tasks/asset_user_connectivity.py @@ -85,7 +85,7 @@ def test_asset_user_connectivity_util(asset_user, task_name): raw, summary = test_user_connectivity( task_name=task_name, asset=asset_user.asset, username=asset_user.username, password=asset_user.password, - private_key=asset_user.private_key + private_key=asset_user.private_key_file ) except Exception as e: logger.warn("Failed run adhoc {}, {}".format(task_name, e)) diff --git a/apps/assets/tasks/system_user_connectivity.py b/apps/assets/tasks/system_user_connectivity.py index 96ba2ec4d..42b6f2331 100644 --- a/apps/assets/tasks/system_user_connectivity.py +++ b/apps/assets/tasks/system_user_connectivity.py @@ -31,7 +31,10 @@ def test_system_user_connectivity_util(system_user, assets, task_name): """ from ops.utils import update_or_create_ansible_task - hosts = clean_ansible_task_hosts(assets, system_user=system_user) + # hosts = clean_ansible_task_hosts(assets, system_user=system_user) + # TODO: 这里不传递系统用户,因为clean_ansible_task_hosts会通过system_user来判断是否可以推送, + # 不符合测试可连接性逻辑, 后面需要优化此逻辑 + hosts = clean_ansible_task_hosts(assets) if not hosts: return {} platform_hosts_map = {} diff --git a/apps/audits/migrations/0009_auto_20200624_1654.py b/apps/audits/migrations/0009_auto_20200624_1654.py new file mode 100644 index 000000000..6630558cf --- /dev/null +++ b/apps/audits/migrations/0009_auto_20200624_1654.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-06-24 08:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0008_auto_20200508_2105'), + ] + + operations = [ + migrations.AlterField( + model_name='ftplog', + name='operate', + field=models.CharField(choices=[('Delete', 'Delete'), ('Upload', 'Upload'), ('Download', 'Download'), ('Rmdir', 'Rmdir'), ('Rename', 'Rename'), ('Mkdir', 'Mkdir'), ('Symlink', 'Symlink')], max_length=16, verbose_name='Operate'), + ), + ] diff --git a/apps/audits/models.py b/apps/audits/models.py index 406d0aa44..38a41554f 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -14,12 +14,30 @@ __all__ = [ class FTPLog(OrgModelMixin): + OPERATE_DELETE = 'Delete' + OPERATE_UPLOAD = 'Upload' + OPERATE_DOWNLOAD = 'Download' + OPERATE_RMDIR = 'Rmdir' + OPERATE_RENAME = 'Rename' + OPERATE_MKDIR = 'Mkdir' + OPERATE_SYMLINK = 'Symlink' + + OPERATE_CHOICES = ( + (OPERATE_DELETE, _('Delete')), + (OPERATE_UPLOAD, _('Upload')), + (OPERATE_DOWNLOAD, _('Download')), + (OPERATE_RMDIR, _('Rmdir')), + (OPERATE_RENAME, _('Rename')), + (OPERATE_MKDIR, _('Mkdir')), + (OPERATE_SYMLINK, _('Symlink')) + ) + id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=128, verbose_name=_('User')) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) asset = models.CharField(max_length=1024, verbose_name=_("Asset")) system_user = models.CharField(max_length=128, verbose_name=_("System user")) - operate = models.CharField(max_length=16, verbose_name=_("Operate")) + operate = models.CharField(max_length=16, verbose_name=_("Operate"), choices=OPERATE_CHOICES) filename = models.CharField(max_length=1024, verbose_name=_("Filename")) is_success = models.BooleanField(default=True, verbose_name=_("Success")) date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index d4213f243..23d562c8d 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -12,12 +12,13 @@ from . import models class FTPLogSerializer(serializers.ModelSerializer): + operate_display = serializers.ReadOnlyField(source='get_operate_display') class Meta: model = models.FTPLog fields = ( 'id', 'user', 'remote_addr', 'asset', 'system_user', - 'operate', 'filename', 'is_success', 'date_start' + 'operate', 'filename', 'is_success', 'date_start', 'operate_display' ) diff --git a/apps/authentication/errors.py b/apps/authentication/errors.py index d782a05fc..20ec0aedf 100644 --- a/apps/authentication/errors.py +++ b/apps/authentication/errors.py @@ -10,6 +10,7 @@ from users.utils import ( ) reason_password_failed = 'password_failed' +reason_password_decrypt_failed = 'password_decrypt_failed' reason_mfa_failed = 'mfa_failed' reason_mfa_unset = 'mfa_unset' reason_user_not_exist = 'user_not_exist' @@ -19,6 +20,7 @@ reason_user_inactive = 'user_inactive' reason_choices = { reason_password_failed: _('Username/password check failed'), + reason_password_decrypt_failed: _('Password decrypt failed'), reason_mfa_failed: _('MFA failed'), reason_mfa_unset: _('MFA unset'), reason_user_not_exist: _("Username does not exist"), diff --git a/apps/authentication/forms.py b/apps/authentication/forms.py index d14cde515..84e923a8e 100644 --- a/apps/authentication/forms.py +++ b/apps/authentication/forms.py @@ -10,7 +10,7 @@ class UserLoginForm(forms.Form): username = forms.CharField(label=_('Username'), max_length=100) password = forms.CharField( label=_('Password'), widget=forms.PasswordInput, - max_length=128, strip=False + max_length=1024, strip=False ) def confirm_login_allowed(self, user): diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 8812f582b..14978e426 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -7,7 +7,7 @@ {% endblock %} {% block content %} -