mirror of https://github.com/jumpserver/jumpserver
Merge branch 'v3' of github.com:jumpserver/jumpserver into v3
commit
4a21f86cd1
|
@ -1,309 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
|
||||||
from rest_framework.generics import RetrieveAPIView, ListAPIView
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
|
||||||
from common.mixins.api import SuggestionMixin, RenderToJsonMixin
|
|
||||||
from users.models import User, UserGroup
|
|
||||||
from users.serializers import UserSerializer, UserGroupSerializer
|
|
||||||
from users.filters import UserFilter
|
|
||||||
from perms.models import AssetPermission
|
|
||||||
from perms.serializers import AssetPermissionSerializer
|
|
||||||
from perms.filters import AssetPermissionFilter
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
from orgs.mixins import generics
|
|
||||||
from assets.api import FilterAssetByNodeMixin
|
|
||||||
from ..models import Asset, Node, Platform, Gateway
|
|
||||||
from .. import serializers
|
|
||||||
from ..tasks import (
|
|
||||||
update_assets_hardware_info_manual, test_assets_connectivity_manual,
|
|
||||||
test_system_users_connectivity_a_asset, push_system_users_a_asset
|
|
||||||
)
|
|
||||||
from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
__all__ = [
|
|
||||||
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
|
||||||
'AssetGatewayListApi', 'AssetPlatformViewSet',
|
|
||||||
'AssetTaskCreateApi', 'AssetsTaskCreateApi',
|
|
||||||
'AssetPermUserListApi', 'AssetPermUserPermissionsListApi',
|
|
||||||
'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet):
|
|
||||||
"""
|
|
||||||
API endpoint that allows Asset to be viewed or edited.
|
|
||||||
"""
|
|
||||||
model = Asset
|
|
||||||
filterset_fields = {
|
|
||||||
'hostname': ['exact'],
|
|
||||||
'ip': ['exact'],
|
|
||||||
'system_users__id': ['exact'],
|
|
||||||
'platform__base': ['exact'],
|
|
||||||
'is_active': ['exact'],
|
|
||||||
'protocols': ['exact', 'icontains']
|
|
||||||
}
|
|
||||||
search_fields = ("hostname", "ip")
|
|
||||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
|
||||||
ordering = ('hostname', )
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AssetSerializer,
|
|
||||||
'suggestion': serializers.MiniAssetSerializer
|
|
||||||
}
|
|
||||||
rbac_perms = {
|
|
||||||
'match': 'assets.match_asset'
|
|
||||||
}
|
|
||||||
extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]
|
|
||||||
|
|
||||||
def set_assets_node(self, assets):
|
|
||||||
if not isinstance(assets, list):
|
|
||||||
assets = [assets]
|
|
||||||
node_id = self.request.query_params.get('node_id')
|
|
||||||
if not node_id:
|
|
||||||
return
|
|
||||||
node = get_object_or_none(Node, pk=node_id)
|
|
||||||
if not node:
|
|
||||||
return
|
|
||||||
node.assets.add(*assets)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
assets = serializer.save()
|
|
||||||
self.set_assets_node(assets)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPlatformRetrieveApi(RetrieveAPIView):
|
|
||||||
queryset = Platform.objects.all()
|
|
||||||
serializer_class = serializers.PlatformSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'retrieve': 'assets.view_gateway'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
asset_pk = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_pk)
|
|
||||||
return asset.platform
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPlatformViewSet(ModelViewSet, RenderToJsonMixin):
|
|
||||||
queryset = Platform.objects.all()
|
|
||||||
serializer_class = serializers.PlatformSerializer
|
|
||||||
filterset_fields = ['name', 'base']
|
|
||||||
search_fields = ['name']
|
|
||||||
|
|
||||||
def check_object_permissions(self, request, obj):
|
|
||||||
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
|
||||||
self.permission_denied(
|
|
||||||
request, message={"detail": "Internal platform"}
|
|
||||||
)
|
|
||||||
return super().check_object_permissions(request, obj)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsTaskMixin:
|
|
||||||
|
|
||||||
def perform_assets_task(self, serializer):
|
|
||||||
data = serializer.validated_data
|
|
||||||
action = data['action']
|
|
||||||
assets = data.get('assets', [])
|
|
||||||
if action == "refresh":
|
|
||||||
task = update_assets_hardware_info_manual.delay(assets)
|
|
||||||
else:
|
|
||||||
# action == 'test':
|
|
||||||
task = test_assets_connectivity_manual.delay(assets)
|
|
||||||
return task
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
task = self.perform_assets_task(serializer)
|
|
||||||
self.set_task_to_serializer_data(serializer, task)
|
|
||||||
|
|
||||||
def set_task_to_serializer_data(self, serializer, task):
|
|
||||||
data = getattr(serializer, '_data', {})
|
|
||||||
data["task"] = task.id
|
|
||||||
setattr(serializer, '_data', data)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
|
|
||||||
model = Asset
|
|
||||||
serializer_class = serializers.AssetTaskSerializer
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
pk = self.kwargs.get('pk')
|
|
||||||
request.data['asset'] = pk
|
|
||||||
request.data['assets'] = [pk]
|
|
||||||
return super().create(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def check_permissions(self, request):
|
|
||||||
action = request.data.get('action')
|
|
||||||
action_perm_require = {
|
|
||||||
'refresh': 'assets.refresh_assethardwareinfo',
|
|
||||||
'push_system_user': 'assets.push_assetsystemuser',
|
|
||||||
'test': 'assets.test_assetconnectivity',
|
|
||||||
'test_system_user': 'assets.test_assetconnectivity'
|
|
||||||
}
|
|
||||||
perm_required = action_perm_require.get(action)
|
|
||||||
has = self.request.user.has_perm(perm_required)
|
|
||||||
|
|
||||||
if not has:
|
|
||||||
self.permission_denied(request)
|
|
||||||
|
|
||||||
def perform_asset_task(self, serializer):
|
|
||||||
data = serializer.validated_data
|
|
||||||
action = data['action']
|
|
||||||
if action not in ['push_system_user', 'test_system_user']:
|
|
||||||
return
|
|
||||||
|
|
||||||
asset = data['asset']
|
|
||||||
system_users = data.get('system_users')
|
|
||||||
if not system_users:
|
|
||||||
system_users = asset.get_all_system_users()
|
|
||||||
if action == 'push_system_user':
|
|
||||||
task = push_system_users_a_asset.delay(system_users, asset=asset)
|
|
||||||
elif action == 'test_system_user':
|
|
||||||
task = test_system_users_connectivity_a_asset.delay(system_users, asset=asset)
|
|
||||||
else:
|
|
||||||
task = None
|
|
||||||
return task
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
task = self.perform_asset_task(serializer)
|
|
||||||
if not task:
|
|
||||||
task = self.perform_assets_task(serializer)
|
|
||||||
self.set_task_to_serializer_data(serializer, task)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
|
|
||||||
model = Asset
|
|
||||||
serializer_class = serializers.AssetsTaskSerializer
|
|
||||||
|
|
||||||
def check_permissions(self, request):
|
|
||||||
action = request.data.get('action')
|
|
||||||
action_perm_require = {
|
|
||||||
'refresh': 'assets.refresh_assethardwareinfo',
|
|
||||||
}
|
|
||||||
perm_required = action_perm_require.get(action)
|
|
||||||
has = self.request.user.has_perm(perm_required)
|
|
||||||
if not has:
|
|
||||||
self.permission_denied(request)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetGatewayListApi(generics.ListAPIView):
|
|
||||||
serializer_class = serializers.GatewayWithAuthSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'assets.view_gateway'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
asset_id = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
|
||||||
if not asset.domain:
|
|
||||||
return Gateway.objects.none()
|
|
||||||
queryset = asset.domain.gateways.filter(protocol='ssh')
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAssetPermUserOrUserGroupListApi(ListAPIView):
|
|
||||||
rbac_perms = {
|
|
||||||
'GET': 'perms.view_assetpermission'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
asset_id = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
|
||||||
return asset
|
|
||||||
|
|
||||||
def get_asset_related_perms(self):
|
|
||||||
asset = self.get_object()
|
|
||||||
nodes = asset.get_all_nodes(flat=True)
|
|
||||||
perms = AssetPermission.objects.filter(Q(assets=asset) | Q(nodes__in=nodes))
|
|
||||||
return perms
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserListApi(BaseAssetPermUserOrUserGroupListApi):
|
|
||||||
filterset_class = UserFilter
|
|
||||||
search_fields = ('username', 'email', 'name', 'id', 'source', 'role')
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
rbac_perms = {
|
|
||||||
'GET': 'perms.view_assetpermission'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
perms = self.get_asset_related_perms()
|
|
||||||
users = User.objects.filter(
|
|
||||||
Q(assetpermissions__in=perms) | Q(groups__assetpermissions__in=perms)
|
|
||||||
).distinct()
|
|
||||||
return users
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi):
|
|
||||||
serializer_class = UserGroupSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
perms = self.get_asset_related_perms()
|
|
||||||
user_groups = UserGroup.objects.filter(assetpermissions__in=perms).distinct()
|
|
||||||
return user_groups
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
|
|
||||||
model = AssetPermission
|
|
||||||
serializer_class = AssetPermissionSerializer
|
|
||||||
filterset_class = AssetPermissionFilter
|
|
||||||
search_fields = ('name',)
|
|
||||||
rbac_perms = {
|
|
||||||
'list': 'perms.view_assetpermission'
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
asset_id = self.kwargs.get('pk')
|
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
|
||||||
return asset
|
|
||||||
|
|
||||||
def filter_asset_related(self, queryset):
|
|
||||||
asset = self.get_object()
|
|
||||||
nodes = asset.get_all_nodes(flat=True)
|
|
||||||
perms = queryset.filter(Q(assets=asset) | Q(nodes__in=nodes))
|
|
||||||
return perms
|
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = self.filter_asset_related(queryset)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = self.filter_user_related(queryset)
|
|
||||||
queryset = queryset.distinct()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_user_related(self, queryset):
|
|
||||||
user = self.get_perm_user()
|
|
||||||
user_groups = user.groups.all()
|
|
||||||
perms = queryset.filter(Q(users=user) | Q(user_groups__in=user_groups))
|
|
||||||
return perms
|
|
||||||
|
|
||||||
def get_perm_user(self):
|
|
||||||
user_id = self.kwargs.get('perm_user_id')
|
|
||||||
user = get_object_or_404(User, pk=user_id)
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
|
||||||
def filter_queryset(self, queryset):
|
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
queryset = self.filter_user_group_related(queryset)
|
|
||||||
queryset = queryset.distinct()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
def filter_user_group_related(self, queryset):
|
|
||||||
user_group = self.get_perm_user_group()
|
|
||||||
perms = queryset.filter(user_groups=user_group)
|
|
||||||
return perms
|
|
||||||
|
|
||||||
def get_perm_user_group(self):
|
|
||||||
user_group_id = self.kwargs.get('perm_user_group_id')
|
|
||||||
user_group = get_object_or_404(UserGroup, pk=user_group_id)
|
|
||||||
return user_group
|
|
|
@ -26,10 +26,11 @@ __all__ = [
|
||||||
class AssetFilterSet(BaseFilterSet):
|
class AssetFilterSet(BaseFilterSet):
|
||||||
type = django_filters.CharFilter(field_name='platform__type', lookup_expr='exact')
|
type = django_filters.CharFilter(field_name='platform__type', lookup_expr='exact')
|
||||||
category = django_filters.CharFilter(field_name='platform__category', lookup_expr='exact')
|
category = django_filters.CharFilter(field_name='platform__category', lookup_expr='exact')
|
||||||
|
hostname = django_filters.CharFilter(field_name='name', lookup_expr='exact')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = ['name', 'ip', 'is_active', 'type', 'category']
|
fields = ['name', 'ip', 'is_active', 'type', 'category', 'hostname']
|
||||||
|
|
||||||
|
|
||||||
class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
||||||
|
@ -39,7 +40,7 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
||||||
model = Asset
|
model = Asset
|
||||||
filterset_class = AssetFilterSet
|
filterset_class = AssetFilterSet
|
||||||
search_fields = ("name", "ip")
|
search_fields = ("name", "ip")
|
||||||
ordering_fields = ("name", "ip", "port")
|
ordering_fields = ("name", "ip")
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
serializer_classes = (
|
serializer_classes = (
|
||||||
('default', serializers.AssetSerializer),
|
('default', serializers.AssetSerializer),
|
||||||
|
@ -58,21 +59,6 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet):
|
||||||
NodeFilterBackend
|
NodeFilterBackend
|
||||||
]
|
]
|
||||||
|
|
||||||
def set_assets_node(self, assets):
|
|
||||||
if not isinstance(assets, list):
|
|
||||||
assets = [assets]
|
|
||||||
node_id = self.request.query_params.get('node_id')
|
|
||||||
if not node_id:
|
|
||||||
return
|
|
||||||
node = get_object_or_none(Node, pk=node_id)
|
|
||||||
if not node:
|
|
||||||
return
|
|
||||||
node.assets.add(*assets)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
assets = serializer.save()
|
|
||||||
self.set_assets_node(assets)
|
|
||||||
|
|
||||||
@action(methods=['GET'], detail=True, url_path='platform')
|
@action(methods=['GET'], detail=True, url_path='platform')
|
||||||
def platform(self, *args, **kwargs):
|
def platform(self, *args, **kwargs):
|
||||||
asset = self.get_object()
|
asset = self.get_object()
|
||||||
|
|
|
@ -2,11 +2,11 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.drf.api import JMSModelViewSet
|
from common.drf.api import JMSModelViewSet
|
||||||
from common.drf.serializers import GroupedChoiceSerailizer
|
from common.drf.serializers import GroupedChoiceSerializer
|
||||||
from assets.models import Platform
|
from assets.models import Platform
|
||||||
from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer
|
from assets.serializers import PlatformSerializer, PlatformOpsMethodSerializer
|
||||||
from assets.const import AllTypes, Category
|
from assets.const import AllTypes
|
||||||
from assets.playbooks.platform import filter_platform_methods
|
from assets.playbooks import filter_platform_methods
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AssetPlatformViewSet']
|
__all__ = ['AssetPlatformViewSet']
|
||||||
|
@ -16,7 +16,7 @@ class AssetPlatformViewSet(JMSModelViewSet):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': PlatformSerializer,
|
'default': PlatformSerializer,
|
||||||
'categories': GroupedChoiceSerailizer
|
'categories': GroupedChoiceSerializer
|
||||||
}
|
}
|
||||||
filterset_fields = ['name', 'category', 'type']
|
filterset_fields = ['name', 'category', 'type']
|
||||||
search_fields = ['name']
|
search_fields = ['name']
|
||||||
|
|
|
@ -13,4 +13,4 @@ class AssetsConfig(AppConfig):
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
super().ready()
|
super().ready()
|
||||||
# from . import signal_handlers
|
from . import signal_handlers
|
||||||
|
|
|
@ -92,7 +92,7 @@ class HostTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
|
||||||
def platform_constraints(cls):
|
def platform_constraints(cls):
|
||||||
return {
|
return {
|
||||||
cls.LINUX: {
|
cls.LINUX: {
|
||||||
'_protocols': ['ssh', 'sftp', 'rdp', 'vnc', 'telnet']
|
'_protocols': ['ssh', 'rdp', 'vnc', 'telnet']
|
||||||
},
|
},
|
||||||
cls.WINDOWS: {
|
cls.WINDOWS: {
|
||||||
'_protocols': ['ssh', 'rdp', 'vnc'],
|
'_protocols': ['ssh', 'rdp', 'vnc'],
|
||||||
|
@ -131,7 +131,7 @@ class DatabaseTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
|
||||||
|
|
||||||
|
|
||||||
class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
|
class WebTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
|
||||||
General = 'general', 'General'
|
WEBSITE = 'website', _('General Website')
|
||||||
|
|
||||||
|
|
||||||
class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
|
class CloudTypes(PlatformMixin, ChoicesMixin, models.TextChoices):
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-09-08 11:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0110_auto_20220901_1542'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platform',
|
||||||
|
name='domain_enabled',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Domain enalbed'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='account',
|
||||||
|
name='asset',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to='assets.asset', verbose_name='Asset'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-09-09 11:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0111_auto_20220908_1958'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='web',
|
||||||
|
name='autofill',
|
||||||
|
field=models.CharField(default='basic', max_length=16),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='web',
|
||||||
|
name='password_selector',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=128),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='web',
|
||||||
|
name='submit_selector',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=128),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='web',
|
||||||
|
name='username_selector',
|
||||||
|
field=models.CharField(blank=True, default='', max_length=128),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='platform',
|
||||||
|
name='domain_enabled',
|
||||||
|
field=models.BooleanField(default=True, verbose_name='Domain enabled'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -10,7 +10,7 @@ __all__ = ['Account', 'AccountTemplate']
|
||||||
|
|
||||||
|
|
||||||
class Account(BaseAccount):
|
class Account(BaseAccount):
|
||||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
asset = models.ForeignKey('assets.Asset', related_name='accounts', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
||||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||||
history = HistoricalRecords()
|
history = HistoricalRecords()
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,7 @@ from .common import Asset
|
||||||
|
|
||||||
class Web(Asset):
|
class Web(Asset):
|
||||||
url = models.CharField(max_length=1024, verbose_name=_("url"))
|
url = models.CharField(max_length=1024, verbose_name=_("url"))
|
||||||
|
autofill = models.CharField(max_length=16, default='basic')
|
||||||
|
username_selector = models.CharField(max_length=128, blank=True, default='')
|
||||||
|
password_selector = models.CharField(max_length=128, blank=True, default='')
|
||||||
|
submit_selector = models.CharField(max_length=128, blank=True, default='')
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Platform(models.Model):
|
||||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||||
# 资产有关的
|
# 资产有关的
|
||||||
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
||||||
|
domain_enabled = models.BooleanField(default=True, verbose_name=_("Domain enabled"))
|
||||||
protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled"))
|
protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled"))
|
||||||
protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols"))
|
protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols"))
|
||||||
gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled"))
|
gather_facts_enabled = models.BooleanField(default=False, verbose_name=_("Gather facts enabled"))
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def check_platform_method(manifest, manifest_path):
|
||||||
|
required_keys = ['category', 'method', 'name', 'id', 'type']
|
||||||
|
less_key = set(required_keys) - set(manifest.keys())
|
||||||
|
if less_key:
|
||||||
|
raise ValueError("Manifest missing keys: {}, {}".format(less_key, manifest_path))
|
||||||
|
if not isinstance(manifest['type'], list):
|
||||||
|
raise ValueError("Manifest type must be a list: {}".format(manifest_path))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_platform_methods(methods):
|
||||||
|
ids = [m['id'] for m in methods]
|
||||||
|
for i, _id in enumerate(ids):
|
||||||
|
if _id in ids[i+1:]:
|
||||||
|
raise ValueError("Duplicate id: {}".format(_id))
|
||||||
|
|
||||||
|
|
||||||
|
def get_platform_methods():
|
||||||
|
methods = []
|
||||||
|
for root, dirs, files in os.walk(BASE_DIR, topdown=False):
|
||||||
|
for name in files:
|
||||||
|
path = os.path.join(root, name)
|
||||||
|
if not path.endswith('manifest.yml'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
manifest = yaml.safe_load(f)
|
||||||
|
check_platform_method(manifest, path)
|
||||||
|
manifest['dir'] = os.path.dirname(path)
|
||||||
|
methods.append(manifest)
|
||||||
|
|
||||||
|
check_platform_methods(methods)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
|
||||||
|
def filter_key(manifest, attr, value):
|
||||||
|
manifest_value = manifest.get(attr, '')
|
||||||
|
if isinstance(manifest_value, str):
|
||||||
|
manifest_value = [manifest_value]
|
||||||
|
return value in manifest_value or 'all' in manifest_value
|
||||||
|
|
||||||
|
|
||||||
|
def filter_platform_methods(category, tp, method):
|
||||||
|
methods = platform_ops_methods
|
||||||
|
if category:
|
||||||
|
methods = filter(partial(filter_key, attr='category', value=category), methods)
|
||||||
|
if tp:
|
||||||
|
methods = filter(partial(filter_key, attr='type', value=tp), methods)
|
||||||
|
if method:
|
||||||
|
methods = filter(lambda x: x['method'] == method, methods)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
|
||||||
|
platform_ops_methods = get_platform_methods()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(get_platform_methods())
|
|
@ -1,4 +1,3 @@
|
||||||
{% for account in accounts %}
|
|
||||||
- hosts: {{ account.asset.name }}
|
- hosts: {{ account.asset.name }}
|
||||||
vars:
|
vars:
|
||||||
account:
|
account:
|
||||||
|
@ -7,4 +6,3 @@
|
||||||
public_key: {{ account.public_key }}
|
public_key: {{ account.public_key }}
|
||||||
roles:
|
roles:
|
||||||
- change_password
|
- change_password
|
||||||
{% endfor %}
|
|
|
@ -1,69 +0,0 @@
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
|
|
||||||
def check_platform_method(manifest, manifest_path):
|
|
||||||
required_keys = ['category', 'method', 'name', 'id', 'type']
|
|
||||||
less_key = set(required_keys) - set(manifest.keys())
|
|
||||||
if less_key:
|
|
||||||
raise ValueError("Manifest missing keys: {}, {}".format(less_key, manifest_path))
|
|
||||||
if not isinstance(manifest['type'], list):
|
|
||||||
raise ValueError("Manifest type must be a list: {}".format(manifest_path))
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def check_platform_methods(methods):
|
|
||||||
ids = [m['id'] for m in methods]
|
|
||||||
for i, _id in enumerate(ids):
|
|
||||||
if _id in ids[i+1:]:
|
|
||||||
raise ValueError("Duplicate id: {}".format(_id))
|
|
||||||
|
|
||||||
|
|
||||||
def get_platform_methods():
|
|
||||||
methods = []
|
|
||||||
for root, dirs, files in os.walk(BASE_DIR, topdown=False):
|
|
||||||
for name in dirs:
|
|
||||||
path = os.path.join(root, name)
|
|
||||||
rel_path = path.replace(BASE_DIR, '.')
|
|
||||||
if len(rel_path.split('/')) != 3:
|
|
||||||
continue
|
|
||||||
|
|
||||||
manifest_path = os.path.join(path, 'manifest.yml')
|
|
||||||
if not os.path.exists(manifest_path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(manifest_path, 'r') as f:
|
|
||||||
manifest = yaml.safe_load(f)
|
|
||||||
check_platform_method(manifest, manifest_path)
|
|
||||||
methods.append(manifest)
|
|
||||||
|
|
||||||
check_platform_methods(methods)
|
|
||||||
return methods
|
|
||||||
|
|
||||||
|
|
||||||
def filter_key(manifest, attr, value):
|
|
||||||
manifest_value = manifest.get(attr, '')
|
|
||||||
if isinstance(manifest_value, str):
|
|
||||||
manifest_value = [manifest_value]
|
|
||||||
return value in manifest_value or 'all' in manifest_value
|
|
||||||
|
|
||||||
|
|
||||||
def filter_platform_methods(category, tp, method):
|
|
||||||
methods = platform_ops_methods
|
|
||||||
if category:
|
|
||||||
methods = filter(partial(filter_key, attr='category', value=category), methods)
|
|
||||||
if tp:
|
|
||||||
methods = filter(partial(filter_key, attr='type', value=tp), methods)
|
|
||||||
if method:
|
|
||||||
methods = filter(lambda x: x['method'] == method, methods)
|
|
||||||
return methods
|
|
||||||
|
|
||||||
|
|
||||||
platform_ops_methods = get_platform_methods()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print(get_platform_methods())
|
|
|
@ -1,9 +0,0 @@
|
||||||
id: change_password_example
|
|
||||||
name: Change password example
|
|
||||||
category: host
|
|
||||||
method: change_password
|
|
||||||
vars:
|
|
||||||
account:
|
|
||||||
username: test
|
|
||||||
password: teset123
|
|
||||||
public_key: test
|
|
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
"""
|
|
||||||
Will run with the args:
|
|
||||||
$0 $asset_json $account_json
|
|
||||||
"""
|
|
|
@ -19,6 +19,8 @@ class AccountFieldsSerializerMixin(serializers.ModelSerializer):
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'private_key': {'write_only': True},
|
'private_key': {'write_only': True},
|
||||||
'public_key': {'write_only': True},
|
'public_key': {'write_only': True},
|
||||||
|
'token': {'write_only': True},
|
||||||
|
'password': {'write_only': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def validate_name(self, value):
|
def validate_name(self, value):
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.db.models import F
|
||||||
from common.drf.serializers import JMSWritableNestedModelSerializer
|
from common.drf.serializers import JMSWritableNestedModelSerializer
|
||||||
from common.drf.fields import LabeledChoiceField, ObjectRelatedField
|
from common.drf.fields import LabeledChoiceField, ObjectRelatedField
|
||||||
from ..account import AccountSerializer
|
from ..account import AccountSerializer
|
||||||
from ...models import Asset, Node, Platform, Protocol, Label, Domain
|
from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account
|
||||||
from ...const import Category, AllTypes
|
from ...const import Category, AllTypes
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -49,6 +49,17 @@ class AssetPlatformSerializer(serializers.ModelSerializer):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AssetAccountSerializer(AccountSerializer):
|
||||||
|
add_org_fields = False
|
||||||
|
|
||||||
|
class Meta(AccountSerializer.Meta):
|
||||||
|
fields_mini = [
|
||||||
|
'id', 'name', 'username', 'privileged', 'version'
|
||||||
|
]
|
||||||
|
fields_write_only = ['password', 'private_key', 'public_key', 'passphrase', 'token']
|
||||||
|
fields = fields_mini + fields_write_only
|
||||||
|
|
||||||
|
|
||||||
class AssetSerializer(JMSWritableNestedModelSerializer):
|
class AssetSerializer(JMSWritableNestedModelSerializer):
|
||||||
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
|
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
|
||||||
type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type'))
|
type = LabeledChoiceField(choices=AllTypes.choices, read_only=True, label=_('Type'))
|
||||||
|
@ -56,22 +67,14 @@ class AssetSerializer(JMSWritableNestedModelSerializer):
|
||||||
platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform'))
|
platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform'))
|
||||||
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
|
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
|
||||||
labels = AssetLabelSerializer(many=True, required=False, label=_('Labels'))
|
labels = AssetLabelSerializer(many=True, required=False, label=_('Labels'))
|
||||||
accounts = AccountSerializer(many=True, required=False, label=_('Accounts'))
|
accounts = AssetAccountSerializer(many=True, required=False, label=_('Accounts'))
|
||||||
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
|
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'))
|
||||||
"""
|
|
||||||
资产的数据结构
|
|
||||||
"""
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields_mini = [
|
fields_mini = ['id', 'name', 'ip']
|
||||||
'id', 'name', 'ip',
|
fields_small = fields_mini + ['is_active', 'comment']
|
||||||
]
|
fields_fk = ['domain', 'platform', 'platform']
|
||||||
fields_small = fields_mini + [
|
|
||||||
'is_active', 'comment',
|
|
||||||
]
|
|
||||||
fields_fk = [
|
|
||||||
'domain', 'platform', 'platform',
|
|
||||||
]
|
|
||||||
fields_m2m = [
|
fields_m2m = [
|
||||||
'nodes', 'labels', 'accounts', 'protocols', 'nodes_display',
|
'nodes', 'labels', 'accounts', 'protocols', 'nodes_display',
|
||||||
]
|
]
|
||||||
|
@ -83,16 +86,8 @@ class AssetSerializer(JMSWritableNestedModelSerializer):
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'label': _("Name")},
|
'name': {'label': _("Name")},
|
||||||
'ip': {'label': _('IP/Host')},
|
'ip': {'label': _('IP/Host')},
|
||||||
'protocol': {'write_only': True},
|
|
||||||
'port': {'write_only': True},
|
|
||||||
'admin_user_display': {'label': _('Admin user display'), 'read_only': True},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
data = kwargs.get('data', {})
|
|
||||||
self.accounts_data = data.pop('accounts', [])
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_eager_loading(cls, queryset):
|
def setup_eager_loading(cls, queryset):
|
||||||
""" Perform necessary eager loading of data. """
|
""" Perform necessary eager loading of data. """
|
||||||
|
@ -115,6 +110,17 @@ class AssetSerializer(JMSWritableNestedModelSerializer):
|
||||||
nodes_to_set.append(node)
|
nodes_to_set.append(node)
|
||||||
instance.nodes.set(nodes_to_set)
|
instance.nodes.set(nodes_to_set)
|
||||||
|
|
||||||
|
def validate_nodes(self, nodes):
|
||||||
|
print("Nodes: ", nodes)
|
||||||
|
if nodes:
|
||||||
|
return nodes
|
||||||
|
request = self.context.get('request')
|
||||||
|
if not request:
|
||||||
|
return []
|
||||||
|
node_id = request.query_params.get('node_id')
|
||||||
|
if not node_id:
|
||||||
|
return []
|
||||||
|
|
||||||
@atomic
|
@atomic
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
nodes_display = validated_data.pop('nodes_display', '')
|
nodes_display = validated_data.pop('nodes_display', '')
|
||||||
|
|
|
@ -8,4 +8,7 @@ __all__ = ['WebSerializer']
|
||||||
class WebSerializer(AssetSerializer):
|
class WebSerializer(AssetSerializer):
|
||||||
class Meta(AssetSerializer.Meta):
|
class Meta(AssetSerializer.Meta):
|
||||||
model = Web
|
model = Web
|
||||||
fields = AssetSerializer.Meta.fields + ['url']
|
fields = AssetSerializer.Meta.fields + [
|
||||||
|
'url', 'autofill', 'username_selector',
|
||||||
|
'password_selector', 'submit_selector'
|
||||||
|
]
|
||||||
|
|
|
@ -17,13 +17,13 @@ class ProtocolSettingSerializer(serializers.Serializer):
|
||||||
('tls', 'TLS'),
|
('tls', 'TLS'),
|
||||||
('nla', 'NLA'),
|
('nla', 'NLA'),
|
||||||
]
|
]
|
||||||
# Common
|
|
||||||
required = serializers.BooleanField(required=True, initial=False, label=_("Required"))
|
|
||||||
# RDP
|
# RDP
|
||||||
console = serializers.BooleanField(required=False)
|
console = serializers.BooleanField(required=False)
|
||||||
security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any', required=False)
|
security = serializers.ChoiceField(choices=SECURITY_CHOICES, default='any')
|
||||||
|
|
||||||
# SFTP
|
# SFTP
|
||||||
sftp_home = serializers.CharField(default='/tmp', required=False)
|
sftp_enabled = serializers.BooleanField(default=True, label=_("SFTP enabled"))
|
||||||
|
sftp_home = serializers.CharField(default='/tmp', label=_("SFTP home"))
|
||||||
|
|
||||||
|
|
||||||
class PlatformProtocolsSerializer(serializers.ModelSerializer):
|
class PlatformProtocolsSerializer(serializers.ModelSerializer):
|
||||||
|
@ -38,7 +38,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer):
|
||||||
type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type"))
|
type = LabeledChoiceField(choices=AllTypes.choices, label=_("Type"))
|
||||||
category = LabeledChoiceField(choices=Category.choices, label=_("Category"))
|
category = LabeledChoiceField(choices=Category.choices, label=_("Category"))
|
||||||
protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False)
|
protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False)
|
||||||
type_constraints = serializers.ReadOnlyField(required=False, read_only=True)
|
|
||||||
su_method = LabeledChoiceField(
|
su_method = LabeledChoiceField(
|
||||||
choices=[('sudo', 'sudo su -'), ('su', 'su - ')],
|
choices=[('sudo', 'sudo su -'), ('su', 'su - ')],
|
||||||
label='切换方式', required=False, default='sudo'
|
label='切换方式', required=False, default='sudo'
|
||||||
|
@ -48,17 +47,17 @@ class PlatformSerializer(JMSWritableNestedModelSerializer):
|
||||||
model = Platform
|
model = Platform
|
||||||
fields_mini = ['id', 'name', 'internal']
|
fields_mini = ['id', 'name', 'internal']
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'category', 'type',
|
'category', 'type', 'charset',
|
||||||
]
|
]
|
||||||
fields = fields_small + [
|
fields = fields_small + [
|
||||||
'protocols_enabled', 'protocols',
|
'protocols_enabled', 'protocols', 'domain_enabled',
|
||||||
'gather_facts_enabled', 'gather_facts_method',
|
'gather_facts_enabled', 'gather_facts_method',
|
||||||
'su_enabled', 'su_method',
|
'su_enabled', 'su_method',
|
||||||
'gather_accounts_enabled', 'gather_accounts_method',
|
'gather_accounts_enabled', 'gather_accounts_method',
|
||||||
'create_account_enabled', 'create_account_method',
|
'create_account_enabled', 'create_account_method',
|
||||||
'verify_account_enabled', 'verify_account_method',
|
'verify_account_enabled', 'verify_account_method',
|
||||||
'change_password_enabled', 'change_password_method',
|
'change_password_enabled', 'change_password_method',
|
||||||
'type_constraints', 'comment', 'charset',
|
'comment',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'su_enabled': {'label': '启用切换账号'},
|
'su_enabled': {'label': '启用切换账号'},
|
||||||
|
|
|
@ -9,6 +9,8 @@ logger = get_logger(__name__)
|
||||||
|
|
||||||
@receiver(pre_save, sender=Account)
|
@receiver(pre_save, sender=Account)
|
||||||
def on_account_pre_create(sender, instance, **kwargs):
|
def on_account_pre_create(sender, instance, **kwargs):
|
||||||
|
# Todo: 是否只有更改密码的时候才有版本增加, bitwarden 只有再改密码的时候,
|
||||||
|
# 才会有版本,代表的是 password_version
|
||||||
# 升级版本号
|
# 升级版本号
|
||||||
instance.version += 1
|
instance.version += 1
|
||||||
# 即使在 root 组织也不怕
|
# 即使在 root 组织也不怕
|
||||||
|
|
|
@ -10,7 +10,7 @@ router = BulkRouter()
|
||||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||||
router.register(r'hosts', api.HostViewSet, 'host')
|
router.register(r'hosts', api.HostViewSet, 'host')
|
||||||
router.register(r'databases', api.DatabaseViewSet, 'database')
|
router.register(r'databases', api.DatabaseViewSet, 'database')
|
||||||
router.register(r'webs', api.WebViewSet, 'web')
|
router.register(r'web', api.WebViewSet, 'web')
|
||||||
router.register(r'clouds', api.CloudViewSet, 'cloud')
|
router.register(r'clouds', api.CloudViewSet, 'cloud')
|
||||||
router.register(r'networks', api.NetworkViewSet, 'network')
|
router.register(r'networks', api.NetworkViewSet, 'network')
|
||||||
router.register(r'accounts', api.AccountViewSet, 'account')
|
router.register(r'accounts', api.AccountViewSet, 'account')
|
||||||
|
|
|
@ -14,6 +14,7 @@ __all__ = [
|
||||||
'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer',
|
'MethodSerializer', 'EmptySerializer', 'BulkModelSerializer',
|
||||||
'AdaptedBulkListSerializer', 'CeleryTaskSerializer',
|
'AdaptedBulkListSerializer', 'CeleryTaskSerializer',
|
||||||
'SecretReadableMixin', 'JMSWritableNestedModelSerializer',
|
'SecretReadableMixin', 'JMSWritableNestedModelSerializer',
|
||||||
|
'GroupedChoiceSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ class ChoiceSerializer(serializers.Serializer):
|
||||||
value = serializers.CharField(label=_("Value"))
|
value = serializers.CharField(label=_("Value"))
|
||||||
|
|
||||||
|
|
||||||
class GroupedChoiceSerailizer(ChoiceSerializer):
|
class GroupedChoiceSerializer(ChoiceSerializer):
|
||||||
children = ChoiceSerializer(many=True, label=_("Children"))
|
children = ChoiceSerializer(many=True, label=_("Children"))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Generated by Django 3.2.14 on 2022-09-08 11:58
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('assets', '0111_auto_20220908_1958'),
|
||||||
|
('ops', '0022_auto_20220817_1346'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AutomationStrategy',
|
||||||
|
fields=[
|
||||||
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('is_periodic', models.BooleanField(default=False)),
|
||||||
|
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
|
||||||
|
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('accounts', models.JSONField(default=list, verbose_name='Accounts')),
|
||||||
|
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||||
|
('assets', models.ManyToManyField(blank=True, related_name='automation_strategy', to='assets.Asset', verbose_name='Assets')),
|
||||||
|
('nodes', models.ManyToManyField(blank=True, related_name='automation_strategy', to='assets.Node', verbose_name='Nodes')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Automation plan',
|
||||||
|
'unique_together': {('org_id', 'name')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AutomationStrategyExecution',
|
||||||
|
fields=[
|
||||||
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')),
|
||||||
|
('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')),
|
||||||
|
('snapshot', common.db.fields.EncryptJsonDictTextField(blank=True, default=dict, null=True, verbose_name='Automation snapshot')),
|
||||||
|
('trigger', models.CharField(choices=[('manual', 'Manual trigger'), ('timing', 'Timing trigger')], default='manual', max_length=128, verbose_name='Trigger mode')),
|
||||||
|
('strategy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='execution', to='ops.automationstrategy', verbose_name='Automation strategy')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Automation strategy execution',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CollectStrategy',
|
||||||
|
fields=[
|
||||||
|
('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Collect strategy',
|
||||||
|
},
|
||||||
|
bases=('ops.automationstrategy',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PushStrategy',
|
||||||
|
fields=[
|
||||||
|
('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Push strategy',
|
||||||
|
},
|
||||||
|
bases=('ops.automationstrategy',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VerifyStrategy',
|
||||||
|
fields=[
|
||||||
|
('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Verify strategy',
|
||||||
|
},
|
||||||
|
bases=('ops.automationstrategy',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AutomationStrategyTask',
|
||||||
|
fields=[
|
||||||
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
|
('is_success', models.BooleanField(default=False, verbose_name='Is success')),
|
||||||
|
('timedelta', models.FloatField(default=0.0, null=True, verbose_name='Time')),
|
||||||
|
('date_start', models.DateTimeField(auto_now_add=True, verbose_name='Date start')),
|
||||||
|
('reason', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Reason')),
|
||||||
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.account', verbose_name='Account')),
|
||||||
|
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')),
|
||||||
|
('execution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='task', to='ops.automationstrategyexecution', verbose_name='Automation strategy execution')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Automation strategy task',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ChangeAuthStrategy',
|
||||||
|
fields=[
|
||||||
|
('automationstrategy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='ops.automationstrategy')),
|
||||||
|
('is_password', models.BooleanField(default=True)),
|
||||||
|
('password_strategy', models.CharField(blank=True, choices=[('custom', 'Custom password'), ('random_one', 'All assets use the same random password'), ('random_all', 'All assets use different random password')], max_length=128, null=True, verbose_name='Password strategy')),
|
||||||
|
('password_rules', common.db.fields.JsonDictCharField(blank=True, max_length=2048, null=True, verbose_name='Password rules')),
|
||||||
|
('password', common.db.fields.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||||
|
('is_ssh_key', models.BooleanField(default=False)),
|
||||||
|
('ssh_key_strategy', models.CharField(blank=True, choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (The key generated by JumpServer) ')], max_length=128, null=True, verbose_name='SSH Key strategy')),
|
||||||
|
('private_key', common.db.fields.EncryptTextField(blank=True, max_length=4096, null=True, verbose_name='SSH private key')),
|
||||||
|
('public_key', common.db.fields.EncryptTextField(blank=True, max_length=4096, null=True, verbose_name='SSH public key')),
|
||||||
|
('recipients', models.ManyToManyField(blank=True, related_name='recipients_change_auth_strategy', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Change auth strategy',
|
||||||
|
},
|
||||||
|
bases=('ops.automationstrategy',),
|
||||||
|
),
|
||||||
|
]
|
|
@ -24,6 +24,7 @@ class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
org_id = serializers.ReadOnlyField(default=get_current_org_id_for_serializer, label=_("Organization"))
|
org_id = serializers.ReadOnlyField(default=get_current_org_id_for_serializer, label=_("Organization"))
|
||||||
org_name = serializers.ReadOnlyField(label=_("Org name"))
|
org_name = serializers.ReadOnlyField(label=_("Org name"))
|
||||||
|
add_org_fields = True
|
||||||
|
|
||||||
def get_validators(self):
|
def get_validators(self):
|
||||||
_validators = super().get_validators()
|
_validators = super().get_validators()
|
||||||
|
@ -38,7 +39,8 @@ class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer):
|
||||||
|
|
||||||
def get_field_names(self, declared_fields, info):
|
def get_field_names(self, declared_fields, info):
|
||||||
fields = super().get_field_names(declared_fields, info)
|
fields = super().get_field_names(declared_fields, info)
|
||||||
fields.extend(["org_id", "org_name"])
|
if self.add_org_fields:
|
||||||
|
fields.extend(["org_id", "org_name"])
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue