perf: merge v3

pull/8873/head
ibuler 2022-08-23 10:26:43 +08:00
commit 1e57a0eb1f
26 changed files with 166 additions and 55 deletions

View File

@ -7,5 +7,6 @@ from .node import *
from .domain import *
from .gathered_user import *
from .favorite_asset import *
from .account_template import *
from .account_backup import *
from .account_history import *

View File

@ -0,0 +1,12 @@
from orgs.mixins.api import OrgBulkModelViewSet
from ..models import AccountTemplate
from .. import serializers
class AccountTemplateViewSet(OrgBulkModelViewSet):
model = AccountTemplate
filterset_fields = ("username", 'name')
search_fields = ('username', 'name')
serializer_classes = {
'default': serializers.AccountTemplateSerializer
}

View File

@ -16,5 +16,5 @@ class GatheredUserViewSet(OrgModelViewSet):
serializer_class = GatheredUserSerializer
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id']
search_fields = ['username', 'asset__ip', 'asset__hostname']
filterset_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__name', 'asset_id']
search_fields = ['username', 'asset__ip', 'asset__name']

View File

@ -1,6 +1,6 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from common.db.models import IncludesTextChoicesMeta
from common.db.models import IncludesTextChoicesMeta, ChoicesMixin
from common.tree import TreeNode
@ -24,7 +24,7 @@ class PlatformMixin:
}
class Category(PlatformMixin, models.TextChoices):
class Category(PlatformMixin, ChoicesMixin, models.TextChoices):
HOST = 'host', _('Host')
NETWORK = 'network', _("NetworkDevice")
DATABASE = 'database', _("Database")
@ -60,7 +60,7 @@ class Category(PlatformMixin, models.TextChoices):
}
class HostTypes(PlatformMixin, models.TextChoices):
class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
LINUX = 'linux', 'Linux'
WINDOWS = 'windows', 'Windows'
UNIX = 'unix', 'Unix'
@ -84,14 +84,14 @@ class HostTypes(PlatformMixin, models.TextChoices):
}
class NetworkTypes(PlatformMixin, models.TextChoices):
class NetworkTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
SWITCH = 'switch', _("Switch")
ROUTER = 'router', _("Router")
FIREWALL = 'firewall', _("Firewall")
OTHER_NETWORK = 'other_network', _("Other device")
class DatabaseTypes(PlatformMixin, models.TextChoices):
class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
MYSQL = 'mysql', 'MySQL'
MARIADB = 'mariadb', 'MariaDB'
POSTGRESQL = 'postgresql', 'PostgreSQL'
@ -110,15 +110,15 @@ class DatabaseTypes(PlatformMixin, models.TextChoices):
return meta
class WebTypes(PlatformMixin, models.TextChoices):
class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
General = 'general', 'General'
class CloudTypes(PlatformMixin, models.TextChoices):
class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
K8S = 'k8s', 'Kubernetes'
class AllTypes(metaclass=IncludesTextChoicesMeta):
class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta):
choices: list
includes = [
HostTypes, NetworkTypes, DatabaseTypes,
@ -202,7 +202,7 @@ class AllTypes(metaclass=IncludesTextChoicesMeta):
return nodes
class Protocol(models.TextChoices):
class Protocol(ChoicesMixin, models.TextChoices):
ssh = 'ssh', 'SSH'
rdp = 'rdp', 'RDP'
telnet = 'telnet', 'Telnet'

View File

@ -18,6 +18,9 @@ def create_app_platform(apps, *args):
{'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'},
{'name': 'Redis', 'category': 'database', 'type': 'redis'},
{'name': 'Chrome', 'category': 'remote_app', 'type': 'chrome'},
{'name': 'MysqlWorkbench', 'category': 'remote_app', 'type': 'mysql_workbench'},
{'name': 'VmwareClient', 'category': 'remote_app', 'type': 'vmware_client'},
{'name': 'General', 'category': 'remote_app', 'type': 'general_remote_app'},
{'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'},
]

View File

@ -0,0 +1,40 @@
# Generated by Django 3.2.13 on 2022-08-19 07:23
import assets.models.base
import common.db.fields
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0110_auto_20220817_1716'),
]
operations = [
migrations.CreateModel(
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')),
('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')),
('public_key', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
('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')),
('token', common.db.fields.EncryptTextField(blank=True, null=True, verbose_name='Token')),
('privileged', models.BooleanField(default=False, verbose_name='Privileged account')),
],
options={
'verbose_name': 'Account template',
},
bases=(models.Model, assets.models.base.AuthMixin),
)
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.13 on 2022-08-19 07:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0111_auto_20220819_1523'),
]
operations = [
migrations.RemoveField(
model_name='platform',
name='protocols_default',
),
migrations.AddField(
model_name='platform',
name='protocols_enabled',
field=models.BooleanField(default=True, verbose_name='Protocols enabled'),
),
]

View File

@ -5,7 +5,7 @@ from simple_history.models import HistoricalRecords
from common.db import fields
from .base import BaseAccount, AbsConnectivity
__all__ = ['Account']
__all__ = ['Account', 'AccountTemplate']
class Account(BaseAccount, AbsConnectivity):
@ -27,3 +27,14 @@ class Account(BaseAccount, AbsConnectivity):
def __str__(self):
return '{}@{}'.format(self.username, self.asset.name)
class AccountTemplate(BaseAccount, AbsConnectivity):
token = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Token'))
privileged = models.BooleanField(verbose_name=_("Privileged account"), default=False)
class Meta:
verbose_name = _('Account template')
def __str__(self):
return '{}@{}'.format(self.username, self.name)

View File

@ -11,6 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from orgs.mixins.models import OrgManager, JMSOrgBaseModel
from ...const import Category
from ..platform import Platform
from ..base import AbsConnectivity

View File

@ -9,5 +9,6 @@ from .gathered_user import *
from .favorite_asset import *
from .account import *
from .account_history import *
from .account_template import *
from .backup import *
from .platform import *

View File

@ -6,7 +6,6 @@ from assets.models import Account
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import AuthSerializerMixin
from common.utils.encode import ssh_pubkey_gen
from common.drf.serializers import SecretReadableMixin
@ -33,17 +32,6 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
}
ref_name = 'AssetAccountSerializer'
def _validate_gen_key(self, attrs):
private_key = attrs.get('private_key')
if not private_key:
return attrs
password = attrs.get('passphrase')
username = attrs.get('username')
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
attrs['public_key'] = public_key
return attrs
def validate(self, attrs):
attrs = self._validate_gen_key(attrs)
return attrs

View File

@ -0,0 +1,22 @@
from assets.models import AccountTemplate
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import AuthSerializerMixin
from .account import AccountSerializer
class AccountTemplateSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
class Meta:
model = AccountTemplate
fields_mini = ['id', 'privileged', 'username', 'name']
fields_write_only = AccountSerializer.Meta.fields_write_only
fields_other = AccountSerializer.Meta.fields_other
fields = fields_mini + fields_write_only + fields_other
extra_kwargs = AccountSerializer.Meta.extra_kwargs
def validate(self, attrs):
print(attrs)
raise ValueError('test')
attrs = self._validate_gen_key(attrs)
return attrs

View File

@ -5,6 +5,7 @@ from .common import AssetSerializer
__all__ = [
'DeviceSerializer', 'HostSerializer', 'DatabaseSerializer',
'NetworkSerializer', 'CloudSerializer',
]

View File

@ -13,7 +13,8 @@ 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'))
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:
@ -74,6 +75,18 @@ class AuthSerializerMixin(serializers.ModelSerializer):
validated_data.pop(field, None)
validated_data.pop('passphrase', None)
@staticmethod
def _validate_gen_key(attrs):
private_key = attrs.get('private_key')
if not private_key:
return attrs
password = attrs.get('passphrase')
username = attrs.get('username')
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
attrs['public_key'] = public_key
return attrs
def create(self, validated_data):
self.clean_auth_fields(validated_data)
return super().create(validated_data)

View File

@ -14,6 +14,7 @@ router.register(r'assets', api.AssetViewSet, 'asset')
router.register(r'hosts', api.HostViewSet, 'host')
router.register(r'databases', api.DatabaseViewSet, 'database')
router.register(r'accounts', api.AccountViewSet, 'account')
router.register(r'account-templates', api.AccountTemplateViewSet, 'account-template')
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
router.register(r'accounts-history', api.AccountHistoryViewSet, 'account-history')
router.register(r'account-history-secrets', api.AccountHistorySecretsViewSet, 'account-history-secret')

View File

@ -107,11 +107,11 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
]
filterset_fields = [
'user__name', 'user__username', 'command',
'run_as__name', 'run_as__username', 'is_finished'
'account', 'is_finished'
]
search_fields = [
'command', 'user__name', 'user__username',
'run_as__name', 'run_as__username',
'account__username',
]
ordering = ['-date_created']
@ -121,7 +121,7 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
return queryset.model.objects.none()
if current_org.is_root():
return queryset
queryset = queryset.filter(run_as__org_id=current_org.org_id())
# queryset = queryset.filter(run_as__org_id=current_org.org_id())
return queryset
@ -131,7 +131,7 @@ class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet)
filterset_fields = [
'id', 'asset', 'commandexecution'
]
search_fields = ('asset__hostname', )
search_fields = ('asset__name', )
http_method_names = ['options', 'get']
rbac_perms = {
'GET': 'ops.view_commandexecution',
@ -142,7 +142,7 @@ class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet)
queryset = super().get_queryset()
queryset = queryset.annotate(
asset_display=Concat(
F('asset__hostname'), Value('('),
F('asset__name'), Value('('),
F('asset__ip'), Value(')')
)
)

View File

@ -88,24 +88,22 @@ class CommandExecutionSerializer(serializers.ModelSerializer):
model = CommandExecution
fields_mini = ['id']
fields_small = fields_mini + [
'run_as', 'command', 'is_finished', 'user',
'command', 'is_finished', 'user',
'date_start', 'result', 'is_success', 'org_id'
]
fields = fields_small + ['hosts', 'hosts_display', 'run_as_display', 'user_display']
fields = fields_small + ['hosts', 'hosts_display', 'user_display']
extra_kwargs = {
'result': {'label': _('Result')}, # model 上的方法,只能在这修改
'is_success': {'label': _('Is success')},
'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改
'run_as': {'label': _('Run as')},
'user': {'label': _('User')},
'run_as_display': {'label': _('Run as display')},
'user_display': {'label': _('User display')},
}
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('user', 'run_as', 'hosts')
queryset = queryset.prefetch_related('user', 'hosts')
return queryset

View File

@ -45,18 +45,12 @@ class ConnectionTokenMixin:
@staticmethod
def check_user_has_resource_permission(user, asset, application, system_user):
from perms.utils.asset import has_asset_system_permission
from perms.utils.application import has_application_system_permission
if asset and not has_asset_system_permission(user, asset, system_user):
error = f'User not has this asset and system user permission: ' \
f'user={user.id} system_user={system_user.id} asset={asset.id}'
raise PermissionDenied(error)
if application and not has_application_system_permission(user, application, system_user):
error = f'User not has this application and system user permission: ' \
f'user={user.id} system_user={system_user.id} application={application.id}'
raise PermissionDenied(error)
def get_smart_endpoint(self, protocol, asset=None, application=None):
if asset:
target_ip = asset.get_target_ip()
@ -204,8 +198,7 @@ class ConnectionTokenMixin:
class ConnectionTokenViewSet(ConnectionTokenMixin, RootOrgViewMixin, JMSModelViewSet):
filterset_fields = (
'type',
'user_display', 'system_user_display', 'application_display', 'asset_display'
'type', 'user_display', 'asset_display'
)
search_fields = filterset_fields
serializer_classes = {

View File

@ -10,7 +10,7 @@ def migrate_system_user_to_account(apps, schema_editor):
while True:
connection_tokens = connection_token_model.objects \
.prefetch_related('system_users')[count:bulk_size]
.prefetch_related('system_user')[count:bulk_size]
if not connection_tokens:
break
count += len(connection_tokens)

View File

@ -85,6 +85,14 @@ class BitOperationChoice:
return [(cls.NAME_MAP[i], j) for i, j in cls.DB_CHOICES]
class ChoicesMixin:
_value2label_map_: dict
@classmethod
def get_label(cls, value: (str, int)):
return cls._value2label_map_[value]
class BaseCreateUpdateModel(models.Model):
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
updated_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Updated by'))

View File

@ -25,7 +25,6 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet):
def check_hosts(self, serializer):
data = serializer.validated_data
assets = data["hosts"]
system_user = data["run_as"]
user = self.request.user
# TOdo:

View File

@ -47,10 +47,6 @@ class CommandExecution(OrgModelMixin):
inv = JMSInventory(self.allow_assets, run_as=username, system_user=self.run_as)
return inv
@lazyproperty
def run_as_display(self):
return str(self.run_as)
@lazyproperty
def user_display(self):
return str(self.user)

View File

@ -138,9 +138,8 @@ class CommandExecutionSerializer(serializers.ModelSerializer):
'command', 'result', 'log_url',
'is_finished', 'date_created', 'date_finished'
]
fields_fk = ['run_as']
fields_m2m = ['hosts']
fields = fields_small + fields_fk + fields_m2m
fields = fields_small + fields_m2m
read_only_fields = [
'result', 'is_finished', 'log_url', 'date_created',
'date_finished'

View File

@ -80,12 +80,12 @@ class AssetPermissionAssetRelationViewSet(RelationMixin):
filterset_fields = [
'id', 'asset', 'assetpermission',
]
search_fields = ["id", "asset__hostname", "asset__ip", "assetpermission__name"]
search_fields = ["id", "asset__name", "asset__ip", "assetpermission__name"]
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(asset_display=F('asset__hostname'))
.annotate(asset_display=F('asset__name'))
return queryset

View File

@ -12,7 +12,9 @@ from rest_framework.generics import (
)
from orgs.utils import tmp_to_root_org
from perms.utils.permission import get_asset_system_user_ids_with_actions_by_user, validate_permission
from perms.utils.permission import (
get_asset_system_user_ids_with_actions_by_user, validate_permission
)
from common.permissions import IsValidUser
from common.utils import get_logger, lazyproperty

View File

@ -25,7 +25,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
only_fields = [
"id", "name", "ip", "protocols", "os", 'domain',
"id", "name", "ip", "protocols", 'domain',
"platform", "comment", "org_id", "is_active"
]
fields = only_fields + ['org_name']