mirror of https://github.com/jumpserver/jumpserver
refactor: 整合系统用户和管理用户 (#6236)
* perf: 整合系统用户和管理用户 * stash stash perf: 优化系统用户和资产的表结构 * perf: 添加信号 * perf: 添加算法 * perf: 去掉 asset user backends * perf: 整理系统用户api * perfF: 暂存一下 * stash * perf: 暂存一下 * perf: 暂存 * xxx * perf: ... * stash it * xxx * xxx * xxx * xxx * xxx * stash it * 修改Protocols * perf: 修改创建authbook信号 * perf: 添加auth info * .stash * perf: 基本完成 * perf: 修复完成 * perf: 修复更改的id * perf: 修复迁移过去数量不对的问题 * perf: 修改systemuser * fix: 修复批量编辑近期的问题 * fix: 修复authbook加载的问题 * xxx Co-authored-by: ibuler <ibuler@qq.com>pull/6401/head
parent
a9f814a515
commit
ec8dca90d6
|
@ -54,7 +54,7 @@ class LoginAssetACLSystemUsersSerializer(serializers.Serializer):
|
||||||
protocol_group = serializers.ListField(
|
protocol_group = serializers.ListField(
|
||||||
default=['*'], child=serializers.CharField(max_length=16), label=_('Protocol'),
|
default=['*'], child=serializers.CharField(max_length=16), label=_('Protocol'),
|
||||||
help_text=protocol_group_help_text.format(
|
help_text=protocol_group_help_text.format(
|
||||||
', '.join([SystemUser.PROTOCOL_SSH, SystemUser.PROTOCOL_TELNET])
|
', '.join([SystemUser.Protocol.ssh, SystemUser.Protocol.telnet])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ from .asset import *
|
||||||
from .label import *
|
from .label import *
|
||||||
from .system_user import *
|
from .system_user import *
|
||||||
from .system_user_relation import *
|
from .system_user_relation import *
|
||||||
|
from .accounts import *
|
||||||
from .node import *
|
from .node import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
from .cmd_filter import *
|
from .cmd_filter import *
|
||||||
from .asset_user import *
|
|
||||||
from .gathered_user import *
|
from .gathered_user import *
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
from django.db.models import F
|
||||||
|
from django.conf import settings
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
|
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, NeedMFAVerify
|
||||||
|
from ..tasks.account_connectivity import test_accounts_connectivity_manual
|
||||||
|
from ..models import AuthBook
|
||||||
|
from .. import serializers
|
||||||
|
|
||||||
|
__all__ = ['AccountViewSet', 'AccountSecretsViewSet']
|
||||||
|
|
||||||
|
|
||||||
|
class AccountViewSet(OrgBulkModelViewSet):
|
||||||
|
model = AuthBook
|
||||||
|
filterset_fields = ("username", "asset", "systemuser")
|
||||||
|
search_fields = filterset_fields
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.AccountSerializer,
|
||||||
|
'verify_account': serializers.AssetTaskSerializer
|
||||||
|
}
|
||||||
|
permission_classes = (IsOrgAdmin,)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()\
|
||||||
|
.annotate(ip=F('asset__ip'))\
|
||||||
|
.annotate(hostname=F('asset__hostname'))
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
@action(methods=['post'], detail=True, url_path='verify')
|
||||||
|
def verify_account(self, request, *args, **kwargs):
|
||||||
|
account = super().get_object()
|
||||||
|
task = test_accounts_connectivity_manual.delay([account])
|
||||||
|
return Response(data={'task': task.id})
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSecretsViewSet(AccountViewSet):
|
||||||
|
"""
|
||||||
|
因为可能要导出所有账号,所以单独建立了一个 viewset
|
||||||
|
"""
|
||||||
|
serializer_classes = {
|
||||||
|
'default': serializers.AccountSecretSerializer
|
||||||
|
}
|
||||||
|
permission_classes = (IsOrgAdmin, NeedMFAVerify)
|
||||||
|
http_method_names = ['get']
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
if not settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
||||||
|
self.permission_classes = [IsOrgAdminOrAppUser]
|
||||||
|
return super().get_permissions()
|
|
@ -1,109 +1,28 @@
|
||||||
|
|
||||||
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from rest_framework import status
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
|
||||||
from orgs.mixins import generics
|
|
||||||
|
|
||||||
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ..hands import IsOrgAdmin
|
from ..hands import IsOrgAdmin
|
||||||
from ..models import AdminUser, Asset
|
from ..models import SystemUser
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..tasks import test_admin_user_connectivity_manual
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = ['AdminUserViewSet']
|
||||||
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
|
|
||||||
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
|
|
||||||
'AdminUserAssetsListView',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
# 兼容一下老的 api
|
||||||
class AdminUserViewSet(OrgBulkModelViewSet):
|
class AdminUserViewSet(OrgBulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
Admin user api set, for add,delete,update,list,retrieve resource
|
Admin user api set, for add,delete,update,list,retrieve resource
|
||||||
"""
|
"""
|
||||||
model = AdminUser
|
model = SystemUser
|
||||||
filterset_fields = ("name", "username")
|
filterset_fields = ("name", "username")
|
||||||
search_fields = filterset_fields
|
search_fields = filterset_fields
|
||||||
serializer_class = serializers.AdminUserSerializer
|
serializer_class = serializers.AdminUserSerializer
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin,)
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AdminUserSerializer,
|
|
||||||
'retrieve': serializers.AdminUserDetailSerializer,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset().filter(type=SystemUser.Type.admin)
|
||||||
queryset = queryset.annotate(assets_amount=Count('assets'))
|
queryset = queryset.annotate(assets_amount=Count('assets'))
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
has_related_asset = instance.assets.exists()
|
|
||||||
if has_related_asset:
|
|
||||||
data = {'msg': _('Deleted failed, There are related assets')}
|
|
||||||
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
return super().destroy(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AdminUserAuthApi(generics.UpdateAPIView):
|
|
||||||
model = AdminUser
|
|
||||||
serializer_class = serializers.AdminUserAuthSerializer
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
|
|
||||||
|
|
||||||
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
|
||||||
model = AdminUser
|
|
||||||
serializer_class = serializers.ReplaceNodeAdminUserSerializer
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
|
||||||
admin_user = self.get_object()
|
|
||||||
serializer = self.serializer_class(data=request.data)
|
|
||||||
if serializer.is_valid():
|
|
||||||
nodes = serializer.validated_data['nodes']
|
|
||||||
assets = []
|
|
||||||
for node in nodes:
|
|
||||||
assets.extend([asset.id for asset in node.get_all_assets()])
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
Asset.objects.filter(id__in=assets).update(admin_user=admin_user)
|
|
||||||
|
|
||||||
return Response({"msg": "ok"})
|
|
||||||
else:
|
|
||||||
return Response({'error': serializer.errors}, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
|
||||||
"""
|
|
||||||
Test asset admin user assets_connectivity
|
|
||||||
"""
|
|
||||||
model = AdminUser
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
serializer_class = serializers.TaskIDSerializer
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
|
||||||
admin_user = self.get_object()
|
|
||||||
task = test_admin_user_connectivity_manual.delay(admin_user)
|
|
||||||
return Response({"task": task.id})
|
|
||||||
|
|
||||||
|
|
||||||
class AdminUserAssetsListView(generics.ListAPIView):
|
|
||||||
permission_classes = (IsOrgAdmin,)
|
|
||||||
serializer_class = serializers.AssetSimpleSerializer
|
|
||||||
filterset_fields = ("hostname", "ip")
|
|
||||||
search_fields = filterset_fields
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
pk = self.kwargs.get('pk')
|
|
||||||
return get_object_or_404(AdminUser, pk=pk)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
admin_user = self.get_object()
|
|
||||||
return admin_user.get_related_assets()
|
|
||||||
|
|
|
@ -33,8 +33,7 @@ class AssetViewSet(FilterAssetByNodeMixin, OrgBulkModelViewSet):
|
||||||
filterset_fields = {
|
filterset_fields = {
|
||||||
'hostname': ['exact'],
|
'hostname': ['exact'],
|
||||||
'ip': ['exact'],
|
'ip': ['exact'],
|
||||||
'systemuser__id': ['exact'],
|
'system_users__id': ['exact'],
|
||||||
'admin_user__id': ['exact'],
|
|
||||||
'platform__base': ['exact'],
|
'platform__base': ['exact'],
|
||||||
'is_active': ['exact'],
|
'is_active': ['exact'],
|
||||||
'protocols': ['exact', 'icontains']
|
'protocols': ['exact', 'icontains']
|
||||||
|
@ -43,7 +42,7 @@ class AssetViewSet(FilterAssetByNodeMixin, OrgBulkModelViewSet):
|
||||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.AssetSerializer,
|
'default': serializers.AssetSerializer,
|
||||||
'display': serializers.AssetDisplaySerializer,
|
'single': serializers.AssetVerboseSerializer,
|
||||||
}
|
}
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]
|
extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
import coreapi
|
|
||||||
from django.conf import settings
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import generics, filters
|
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
|
||||||
|
|
||||||
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
|
|
||||||
from common.utils import get_object_or_none, get_logger
|
|
||||||
from common.mixins import CommonApiMixin
|
|
||||||
from ..backends import AssetUserManager
|
|
||||||
from ..models import Node
|
|
||||||
from .. import serializers
|
|
||||||
from ..tasks import (
|
|
||||||
test_asset_users_connectivity_manual
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'AssetUserViewSet', 'AssetUserAuthInfoViewSet', 'AssetUserTaskCreateAPI',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserFilterBackend(filters.BaseFilterBackend):
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
|
||||||
kwargs = {}
|
|
||||||
for field in view.filterset_fields:
|
|
||||||
value = request.GET.get(field)
|
|
||||||
if not value:
|
|
||||||
continue
|
|
||||||
if field == "node_id":
|
|
||||||
value = get_object_or_none(Node, pk=value)
|
|
||||||
kwargs["node"] = value
|
|
||||||
continue
|
|
||||||
elif field == "asset_id":
|
|
||||||
field = "asset"
|
|
||||||
kwargs[field] = value
|
|
||||||
if kwargs:
|
|
||||||
queryset = queryset.filter(**kwargs)
|
|
||||||
logger.debug("Filter {}".format(kwargs))
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserSearchBackend(filters.BaseFilterBackend):
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
|
||||||
value = request.GET.get('search')
|
|
||||||
if not value:
|
|
||||||
return queryset
|
|
||||||
queryset = queryset.search(value)
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserLatestFilterBackend(filters.BaseFilterBackend):
|
|
||||||
def get_schema_fields(self, view):
|
|
||||||
return [
|
|
||||||
coreapi.Field(
|
|
||||||
name='latest', location='query', required=False,
|
|
||||||
type='string', example='1',
|
|
||||||
description='Only the latest version'
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
|
||||||
latest = request.GET.get('latest') == '1'
|
|
||||||
if latest:
|
|
||||||
queryset = queryset.distinct()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
|
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.AssetUserWriteSerializer,
|
|
||||||
'display': serializers.AssetUserReadSerializer,
|
|
||||||
'retrieve': serializers.AssetUserReadSerializer,
|
|
||||||
}
|
|
||||||
permission_classes = [IsOrgAdminOrAppUser]
|
|
||||||
filterset_fields = [
|
|
||||||
"id", "ip", "hostname", "username",
|
|
||||||
"asset_id", "node_id",
|
|
||||||
"prefer", "prefer_id",
|
|
||||||
]
|
|
||||||
search_fields = ["ip", "hostname", "username"]
|
|
||||||
filter_backends = [
|
|
||||||
AssetUserFilterBackend, AssetUserSearchBackend,
|
|
||||||
AssetUserLatestFilterBackend,
|
|
||||||
]
|
|
||||||
|
|
||||||
def allow_bulk_destroy(self, qs, filtered):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
pk = self.kwargs.get("pk")
|
|
||||||
if pk is None:
|
|
||||||
return
|
|
||||||
queryset = self.get_queryset()
|
|
||||||
obj = queryset.get(id=pk)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
|
||||||
manager = AssetUserManager()
|
|
||||||
manager.delete(instance)
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
manager = AssetUserManager()
|
|
||||||
queryset = manager.all()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserAuthInfoViewSet(AssetUserViewSet):
|
|
||||||
serializer_classes = {"default": serializers.AssetUserAuthInfoSerializer}
|
|
||||||
http_method_names = ['get', 'post']
|
|
||||||
permission_classes = [IsOrgAdminOrAppUser]
|
|
||||||
|
|
||||||
def get_permissions(self):
|
|
||||||
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
|
||||||
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
|
||||||
return super().get_permissions()
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserTaskCreateAPI(generics.CreateAPIView):
|
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
|
||||||
serializer_class = serializers.AssetUserTaskSerializer
|
|
||||||
filter_backends = AssetUserViewSet.filter_backends
|
|
||||||
filterset_fields = AssetUserViewSet.filterset_fields
|
|
||||||
|
|
||||||
def get_asset_users(self):
|
|
||||||
manager = AssetUserManager()
|
|
||||||
queryset = manager.all()
|
|
||||||
for cls in self.filter_backends:
|
|
||||||
queryset = cls().filter_queryset(self.request, queryset, self)
|
|
||||||
return list(queryset)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
asset_users = self.get_asset_users()
|
|
||||||
# action = serializer.validated_data["action"]
|
|
||||||
# only this
|
|
||||||
# if action == "test":
|
|
||||||
task = test_asset_users_connectivity_manual.delay(asset_users)
|
|
||||||
data = getattr(serializer, '_data', {})
|
|
||||||
data["task"] = task.id
|
|
||||||
setattr(serializer, '_data', data)
|
|
||||||
return task
|
|
||||||
|
|
||||||
def get_exception_handler(self):
|
|
||||||
def handler(e, context):
|
|
||||||
return Response({"error": str(e)}, status=400)
|
|
||||||
return handler
|
|
|
@ -2,14 +2,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.generics import CreateAPIView, RetrieveDestroyAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from common.utils import reverse
|
from common.utils import reverse
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from orgs.utils import tmp_to_root_org
|
|
||||||
from tickets.models import Ticket
|
|
||||||
from tickets.api import GenericTicketStatusRetrieveCloseAPI
|
from tickets.api import GenericTicketStatusRetrieveCloseAPI
|
||||||
from ..hands import IsOrgAdmin, IsAppUser
|
from ..hands import IsOrgAdmin, IsAppUser
|
||||||
from ..models import CommandFilter, CommandFilterRule
|
from ..models import CommandFilter, CommandFilterRule
|
||||||
|
|
|
@ -32,7 +32,8 @@ class SystemUserViewSet(OrgBulkModelViewSet):
|
||||||
filterset_fields = {
|
filterset_fields = {
|
||||||
'name': ['exact'],
|
'name': ['exact'],
|
||||||
'username': ['exact'],
|
'username': ['exact'],
|
||||||
'protocol': ['exact', 'in']
|
'protocol': ['exact', 'in'],
|
||||||
|
'type': ['exact', 'in'],
|
||||||
}
|
}
|
||||||
search_fields = filterset_fields
|
search_fields = filterset_fields
|
||||||
serializer_class = serializers.SystemUserSerializer
|
serializer_class = serializers.SystemUserSerializer
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.db.models.signals import m2m_changed
|
||||||
from django.db.models.functions import Concat
|
from django.db.models.functions import Concat
|
||||||
|
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
|
from common.utils import get_logger
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet
|
||||||
from orgs.utils import current_org
|
from orgs.utils import current_org
|
||||||
from .. import models, serializers
|
from .. import models, serializers
|
||||||
|
@ -15,6 +16,8 @@ __all__ = [
|
||||||
'SystemUserUserRelationViewSet',
|
'SystemUserUserRelationViewSet',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RelationMixin:
|
class RelationMixin:
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -24,8 +27,8 @@ class RelationMixin:
|
||||||
queryset = queryset.filter(systemuser__org_id=org_id)
|
queryset = queryset.filter(systemuser__org_id=org_id)
|
||||||
|
|
||||||
queryset = queryset.annotate(systemuser_display=Concat(
|
queryset = queryset.annotate(systemuser_display=Concat(
|
||||||
F('systemuser__name'), Value('('), F('systemuser__username'),
|
F('systemuser__name'), Value('('),
|
||||||
Value(')')
|
F('systemuser__username'), Value(')')
|
||||||
))
|
))
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -41,10 +44,11 @@ class RelationMixin:
|
||||||
system_users_objects_map[i.systemuser].append(_id)
|
system_users_objects_map[i.systemuser].append(_id)
|
||||||
|
|
||||||
sender = self.get_sender()
|
sender = self.get_sender()
|
||||||
for system_user, objects in system_users_objects_map.items():
|
for system_user, object_ids in system_users_objects_map.items():
|
||||||
|
logger.debug('System user relation changed, send m2m_changed signals')
|
||||||
m2m_changed.send(
|
m2m_changed.send(
|
||||||
sender=sender, instance=system_user, action='post_add',
|
sender=sender, instance=system_user, action='post_add',
|
||||||
reverse=False, model=model, pk_set=objects
|
reverse=False, model=model, pk_set=set(object_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_sender(self):
|
def get_sender(self):
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
from .manager import AssetUserManager
|
|
|
@ -1,48 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from abc import abstractmethod
|
|
||||||
|
|
||||||
from ..models import Asset
|
|
||||||
|
|
||||||
|
|
||||||
class BaseBackend:
|
|
||||||
@abstractmethod
|
|
||||||
def all(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def filter(self, username=None, hostname=None, ip=None, assets=None,
|
|
||||||
node=None, prefer_id=None, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def search(self, item):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_queryset(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def delete(self, union_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def qs_to_values(qs):
|
|
||||||
values = qs.values(
|
|
||||||
'hostname', 'ip', "asset_id",
|
|
||||||
'name', 'username', 'password', 'private_key', 'public_key',
|
|
||||||
'score', 'version',
|
|
||||||
"asset_username", "union_id",
|
|
||||||
'date_created', 'date_updated',
|
|
||||||
'org_id', 'backend', 'backend_display'
|
|
||||||
)
|
|
||||||
return values
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def make_assets_as_ids(assets):
|
|
||||||
if not assets:
|
|
||||||
return []
|
|
||||||
if isinstance(assets[0], Asset):
|
|
||||||
assets = [a.id for a in assets]
|
|
||||||
return assets
|
|
|
@ -1,332 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from functools import reduce
|
|
||||||
from django.db.models import F, CharField, Value, IntegerField, Q, Count
|
|
||||||
from django.db.models.functions import Concat
|
|
||||||
from rest_framework.exceptions import PermissionDenied
|
|
||||||
|
|
||||||
from common.utils import get_object_or_none
|
|
||||||
from orgs.utils import current_org
|
|
||||||
from ..models import AuthBook, SystemUser, Asset, AdminUser
|
|
||||||
from .base import BaseBackend
|
|
||||||
|
|
||||||
|
|
||||||
class DBBackend(BaseBackend):
|
|
||||||
union_id_length = 2
|
|
||||||
|
|
||||||
def __init__(self, queryset=None):
|
|
||||||
if queryset is None:
|
|
||||||
queryset = self.all()
|
|
||||||
self.queryset = queryset
|
|
||||||
|
|
||||||
def _clone(self):
|
|
||||||
return self.__class__(self.queryset)
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
return AuthBook.objects.none()
|
|
||||||
|
|
||||||
def count(self):
|
|
||||||
return self.queryset.count()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return self.queryset
|
|
||||||
|
|
||||||
def delete(self, union_id):
|
|
||||||
cleaned_union_id = union_id.split('_')
|
|
||||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
|
||||||
if not self._check_union_id(union_id, cleaned_union_id):
|
|
||||||
return
|
|
||||||
return self._perform_delete_by_union_id(cleaned_union_id)
|
|
||||||
|
|
||||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def filter(self, assets=None, node=None, prefer=None, prefer_id=None,
|
|
||||||
union_id=None, id__in=None, **kwargs):
|
|
||||||
clone = self._clone()
|
|
||||||
clone._filter_union_id(union_id)
|
|
||||||
clone._filter_prefer(prefer, prefer_id)
|
|
||||||
clone._filter_node(node)
|
|
||||||
clone._filter_assets(assets)
|
|
||||||
clone._filter_other(kwargs)
|
|
||||||
clone._filter_id_in(id__in)
|
|
||||||
return clone
|
|
||||||
|
|
||||||
def _filter_union_id(self, union_id):
|
|
||||||
if not union_id:
|
|
||||||
return
|
|
||||||
cleaned_union_id = union_id.split('_')
|
|
||||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
|
||||||
if not self._check_union_id(union_id, cleaned_union_id):
|
|
||||||
self.queryset = self.queryset.none()
|
|
||||||
return
|
|
||||||
return self._perform_filter_union_id(union_id, cleaned_union_id)
|
|
||||||
|
|
||||||
def _check_union_id(self, union_id, cleaned_union_id):
|
|
||||||
return union_id and len(cleaned_union_id) == self.union_id_length
|
|
||||||
|
|
||||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
|
||||||
self.queryset = self.queryset.filter(union_id=union_id)
|
|
||||||
|
|
||||||
def _filter_assets(self, assets):
|
|
||||||
asset_ids = self.make_assets_as_ids(assets)
|
|
||||||
if asset_ids:
|
|
||||||
self.queryset = self.queryset.filter(asset_id__in=asset_ids)
|
|
||||||
|
|
||||||
def _filter_node(self, node):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _filter_id_in(self, ids):
|
|
||||||
if ids and isinstance(ids, list):
|
|
||||||
self.queryset = self.queryset.filter(union_id__in=ids)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def clean_kwargs(kwargs):
|
|
||||||
return {k: v for k, v in kwargs.items() if v}
|
|
||||||
|
|
||||||
def _filter_other(self, kwargs):
|
|
||||||
kwargs = self.clean_kwargs(kwargs)
|
|
||||||
if kwargs:
|
|
||||||
self.queryset = self.queryset.filter(**kwargs)
|
|
||||||
|
|
||||||
def _filter_prefer(self, prefer, prefer_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def search(self, item):
|
|
||||||
qs = []
|
|
||||||
for i in ['hostname', 'ip', 'username']:
|
|
||||||
kwargs = {i + '__startswith': item}
|
|
||||||
qs.append(Q(**kwargs))
|
|
||||||
q = reduce(lambda x, y: x | y, qs)
|
|
||||||
clone = self._clone()
|
|
||||||
clone.queryset = clone.queryset.filter(q).distinct()
|
|
||||||
return clone
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserBackend(DBBackend):
|
|
||||||
model = SystemUser.assets.through
|
|
||||||
backend = 'system_user'
|
|
||||||
backend_display = _('System user')
|
|
||||||
prefer = backend
|
|
||||||
base_score = 0
|
|
||||||
union_id_length = 2
|
|
||||||
|
|
||||||
def _filter_prefer(self, prefer, prefer_id):
|
|
||||||
if prefer and prefer != self.prefer:
|
|
||||||
self.queryset = self.queryset.none()
|
|
||||||
|
|
||||||
if prefer_id:
|
|
||||||
self.queryset = self.queryset.filter(systemuser__id=prefer_id)
|
|
||||||
|
|
||||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
|
||||||
system_user_id, asset_id = union_id_cleaned
|
|
||||||
self.queryset = self.queryset.filter(
|
|
||||||
asset_id=asset_id, systemuser__id=system_user_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
|
||||||
system_user_id, asset_id = union_id_cleaned
|
|
||||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
|
||||||
asset = get_object_or_none(Asset, pk=asset_id)
|
|
||||||
if all((system_user, asset)):
|
|
||||||
system_user.assets.remove(asset)
|
|
||||||
|
|
||||||
def _filter_node(self, node):
|
|
||||||
if node:
|
|
||||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
|
||||||
|
|
||||||
def get_annotate(self):
|
|
||||||
kwargs = dict(
|
|
||||||
hostname=F("asset__hostname"),
|
|
||||||
ip=F("asset__ip"),
|
|
||||||
name=F("systemuser__name"),
|
|
||||||
username=F("systemuser__username"),
|
|
||||||
password=F("systemuser__password"),
|
|
||||||
private_key=F("systemuser__private_key"),
|
|
||||||
public_key=F("systemuser__public_key"),
|
|
||||||
score=F("systemuser__priority") + self.base_score,
|
|
||||||
version=Value(0, IntegerField()),
|
|
||||||
date_created=F("systemuser__date_created"),
|
|
||||||
date_updated=F("systemuser__date_updated"),
|
|
||||||
asset_username=Concat(F("asset__id"), Value("_"),
|
|
||||||
F("systemuser__username"),
|
|
||||||
output_field=CharField()),
|
|
||||||
union_id=Concat(F("systemuser_id"), Value("_"), F("asset_id"),
|
|
||||||
output_field=CharField()),
|
|
||||||
org_id=F("asset__org_id"),
|
|
||||||
backend=Value(self.backend, CharField()),
|
|
||||||
backend_display=Value(self.backend_display, CharField()),
|
|
||||||
)
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def get_filter(self):
|
|
||||||
return dict(
|
|
||||||
systemuser__username_same_with_user=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
kwargs = self.get_annotate()
|
|
||||||
filters = self.get_filter()
|
|
||||||
qs = self.model.objects.all().annotate(**kwargs)
|
|
||||||
if not current_org.is_root():
|
|
||||||
filters['org_id'] = current_org.org_id()
|
|
||||||
qs = qs.filter(**filters)
|
|
||||||
qs = self.qs_to_values(qs)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class DynamicSystemUserBackend(SystemUserBackend):
|
|
||||||
backend = 'system_user_dynamic'
|
|
||||||
backend_display = _('System user(Dynamic)')
|
|
||||||
prefer = 'system_user'
|
|
||||||
union_id_length = 3
|
|
||||||
|
|
||||||
def get_annotate(self):
|
|
||||||
kwargs = super().get_annotate()
|
|
||||||
kwargs.update(dict(
|
|
||||||
name=Concat(
|
|
||||||
F("systemuser__users__name"), Value('('), F("systemuser__name"), Value(')'),
|
|
||||||
output_field=CharField()
|
|
||||||
),
|
|
||||||
username=F("systemuser__users__username"),
|
|
||||||
asset_username=Concat(
|
|
||||||
F("asset__id"), Value("_"),
|
|
||||||
F("systemuser__users__username"),
|
|
||||||
output_field=CharField()
|
|
||||||
),
|
|
||||||
union_id=Concat(
|
|
||||||
F("systemuser_id"), Value("_"), F("asset_id"),
|
|
||||||
Value("_"), F("systemuser__users__id"),
|
|
||||||
output_field=CharField()
|
|
||||||
),
|
|
||||||
users_count=Count('systemuser__users'),
|
|
||||||
))
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
|
||||||
system_user_id, asset_id, user_id = union_id_cleaned
|
|
||||||
self.queryset = self.queryset.filter(
|
|
||||||
asset_id=asset_id, systemuser_id=system_user_id,
|
|
||||||
union_id=union_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
|
||||||
system_user_id, asset_id, user_id = union_id_cleaned
|
|
||||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
|
||||||
if not system_user:
|
|
||||||
return
|
|
||||||
system_user.users.remove(user_id)
|
|
||||||
if system_user.users.count() == 0:
|
|
||||||
system_user.assets.remove(asset_id)
|
|
||||||
|
|
||||||
def get_filter(self):
|
|
||||||
return dict(
|
|
||||||
users_count__gt=0,
|
|
||||||
systemuser__username_same_with_user=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AdminUserBackend(DBBackend):
|
|
||||||
model = Asset
|
|
||||||
backend = 'admin_user'
|
|
||||||
backend_display = _('Admin user')
|
|
||||||
prefer = backend
|
|
||||||
base_score = 200
|
|
||||||
|
|
||||||
def _filter_prefer(self, prefer, prefer_id):
|
|
||||||
if prefer and prefer != self.backend:
|
|
||||||
self.queryset = self.queryset.none()
|
|
||||||
if prefer_id:
|
|
||||||
self.queryset = self.queryset.filter(admin_user__id=prefer_id)
|
|
||||||
|
|
||||||
def _filter_node(self, node):
|
|
||||||
if node:
|
|
||||||
self.queryset = self.queryset.filter(nodes__id=node.id)
|
|
||||||
|
|
||||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
|
||||||
admin_user_id, asset_id = union_id_cleaned
|
|
||||||
self.queryset = self.queryset.filter(
|
|
||||||
id=asset_id, admin_user_id=admin_user_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
|
||||||
raise PermissionDenied(_("Could not remove asset admin user"))
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
qs = self.model.objects.all().annotate(
|
|
||||||
asset_id=F("id"),
|
|
||||||
name=F("admin_user__name"),
|
|
||||||
username=F("admin_user__username"),
|
|
||||||
password=F("admin_user__password"),
|
|
||||||
private_key=F("admin_user__private_key"),
|
|
||||||
public_key=F("admin_user__public_key"),
|
|
||||||
score=Value(self.base_score, IntegerField()),
|
|
||||||
version=Value(0, IntegerField()),
|
|
||||||
date_updated=F("admin_user__date_updated"),
|
|
||||||
asset_username=Concat(F("id"), Value("_"), F("admin_user__username"), output_field=CharField()),
|
|
||||||
union_id=Concat(F("admin_user_id"), Value("_"), F("id"), output_field=CharField()),
|
|
||||||
backend=Value(self.backend, CharField()),
|
|
||||||
backend_display=Value(self.backend_display, CharField()),
|
|
||||||
)
|
|
||||||
qs = self.qs_to_values(qs)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class AuthbookBackend(DBBackend):
|
|
||||||
model = AuthBook
|
|
||||||
backend = 'db'
|
|
||||||
backend_display = _('Database')
|
|
||||||
prefer = backend
|
|
||||||
base_score = 400
|
|
||||||
|
|
||||||
def _filter_node(self, node):
|
|
||||||
if node:
|
|
||||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
|
||||||
|
|
||||||
def _filter_prefer(self, prefer, prefer_id):
|
|
||||||
if not prefer or not prefer_id:
|
|
||||||
return
|
|
||||||
if prefer.lower() == "admin_user":
|
|
||||||
model = AdminUser
|
|
||||||
elif prefer.lower() == "system_user":
|
|
||||||
model = SystemUser
|
|
||||||
else:
|
|
||||||
self.queryset = self.queryset.none()
|
|
||||||
return
|
|
||||||
obj = get_object_or_none(model, pk=prefer_id)
|
|
||||||
if obj is None:
|
|
||||||
self.queryset = self.queryset.none()
|
|
||||||
return
|
|
||||||
username = obj.get_username()
|
|
||||||
if isinstance(username, str):
|
|
||||||
self.queryset = self.queryset.filter(username=username)
|
|
||||||
# dynamic system user return more username
|
|
||||||
else:
|
|
||||||
self.queryset = self.queryset.filter(username__in=username)
|
|
||||||
|
|
||||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
|
||||||
authbook_id, asset_id = union_id_cleaned
|
|
||||||
self.queryset = self.queryset.filter(
|
|
||||||
id=authbook_id, asset_id=asset_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
|
||||||
authbook_id, asset_id = union_id_cleaned
|
|
||||||
authbook = get_object_or_none(AuthBook, pk=authbook_id)
|
|
||||||
if authbook.is_latest:
|
|
||||||
raise PermissionDenied(_("Latest version could not be delete"))
|
|
||||||
AuthBook.objects.filter(id=authbook_id).delete()
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
qs = self.model.objects.all().annotate(
|
|
||||||
hostname=F("asset__hostname"),
|
|
||||||
ip=F("asset__ip"),
|
|
||||||
score=F('version') + self.base_score,
|
|
||||||
asset_username=Concat(F("asset__id"), Value("_"), F("username"), output_field=CharField()),
|
|
||||||
union_id=Concat(F("id"), Value("_"), F("asset_id"), output_field=CharField()),
|
|
||||||
backend=Value(self.backend, CharField()),
|
|
||||||
backend_display=Value(self.backend_display, CharField()),
|
|
||||||
)
|
|
||||||
qs = self.qs_to_values(qs)
|
|
||||||
return qs
|
|
|
@ -1,162 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from itertools import chain, groupby
|
|
||||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
|
||||||
|
|
||||||
from orgs.utils import current_org
|
|
||||||
from common.utils import get_logger, lazyproperty
|
|
||||||
from common.struct import QuerySetChain
|
|
||||||
|
|
||||||
from ..models import AssetUser, AuthBook
|
|
||||||
from .db import (
|
|
||||||
AuthbookBackend, SystemUserBackend, AdminUserBackend,
|
|
||||||
DynamicSystemUserBackend
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class NotSupportError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserQueryset:
|
|
||||||
ObjectDoesNotExist = ObjectDoesNotExist
|
|
||||||
MultipleObjectsReturned = MultipleObjectsReturned
|
|
||||||
|
|
||||||
def __init__(self, backends=()):
|
|
||||||
self.backends = backends
|
|
||||||
self._distinct_queryset = None
|
|
||||||
|
|
||||||
def backends_queryset(self):
|
|
||||||
return [b.get_queryset() for b in self.backends]
|
|
||||||
|
|
||||||
@lazyproperty
|
|
||||||
def backends_counts(self):
|
|
||||||
return [b.count() for b in self.backends]
|
|
||||||
|
|
||||||
def filter(self, hostname=None, ip=None, username=None,
|
|
||||||
assets=None, asset=None, node=None,
|
|
||||||
id=None, prefer_id=None, prefer=None, id__in=None):
|
|
||||||
if not assets and asset:
|
|
||||||
assets = [asset]
|
|
||||||
|
|
||||||
kwargs = dict(
|
|
||||||
hostname=hostname, ip=ip, username=username,
|
|
||||||
assets=assets, node=node, prefer=prefer, prefer_id=prefer_id,
|
|
||||||
id__in=id__in, union_id=id,
|
|
||||||
)
|
|
||||||
logger.debug("Filter: {}".format(kwargs))
|
|
||||||
backends = []
|
|
||||||
for backend in self.backends:
|
|
||||||
clone = backend.filter(**kwargs)
|
|
||||||
backends.append(clone)
|
|
||||||
return self._clone(backends)
|
|
||||||
|
|
||||||
def _clone(self, backends=None):
|
|
||||||
if backends is None:
|
|
||||||
backends = self.backends
|
|
||||||
return self.__class__(backends)
|
|
||||||
|
|
||||||
def search(self, item):
|
|
||||||
backends = []
|
|
||||||
for backend in self.backends:
|
|
||||||
new = backend.search(item)
|
|
||||||
backends.append(new)
|
|
||||||
return self._clone(backends)
|
|
||||||
|
|
||||||
def distinct(self):
|
|
||||||
logger.debug("Distinct asset user queryset")
|
|
||||||
queryset_chain = chain(*(backend.get_queryset() for backend in self.backends))
|
|
||||||
queryset_sorted = sorted(
|
|
||||||
queryset_chain,
|
|
||||||
key=lambda item: (item["asset_username"], item["score"]),
|
|
||||||
reverse=True,
|
|
||||||
)
|
|
||||||
results = groupby(queryset_sorted, key=lambda item: item["asset_username"])
|
|
||||||
final = [next(result[1]) for result in results]
|
|
||||||
self._distinct_queryset = final
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get(self, latest=False, **kwargs):
|
|
||||||
queryset = self.filter(**kwargs)
|
|
||||||
if latest:
|
|
||||||
queryset = queryset.distinct()
|
|
||||||
queryset = list(queryset)
|
|
||||||
count = len(queryset)
|
|
||||||
if count == 1:
|
|
||||||
data = queryset[0]
|
|
||||||
return data
|
|
||||||
elif count > 1:
|
|
||||||
msg = 'Should return 1 record, but get {}'.format(count)
|
|
||||||
raise MultipleObjectsReturned(msg)
|
|
||||||
else:
|
|
||||||
msg = 'No record found(org is {})'.format(current_org.name)
|
|
||||||
raise ObjectDoesNotExist(msg)
|
|
||||||
|
|
||||||
def get_latest(self, **kwargs):
|
|
||||||
return self.get(latest=True, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def to_asset_user(data):
|
|
||||||
obj = AssetUser()
|
|
||||||
for k, v in data.items():
|
|
||||||
setattr(obj, k, v)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
@property
|
|
||||||
def queryset(self):
|
|
||||||
if self._distinct_queryset is not None:
|
|
||||||
return self._distinct_queryset
|
|
||||||
return QuerySetChain(self.backends_queryset())
|
|
||||||
|
|
||||||
def count(self):
|
|
||||||
if self._distinct_queryset is not None:
|
|
||||||
return len(self._distinct_queryset)
|
|
||||||
else:
|
|
||||||
return sum(self.backends_counts)
|
|
||||||
|
|
||||||
def __getitem__(self, ndx):
|
|
||||||
return self.queryset.__getitem__(ndx)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
self._data = iter(self.queryset)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
return self.to_asset_user(next(self._data))
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserManager:
|
|
||||||
support_backends = (
|
|
||||||
('db', AuthbookBackend),
|
|
||||||
('system_user', SystemUserBackend),
|
|
||||||
('admin_user', AdminUserBackend),
|
|
||||||
('system_user_dynamic', DynamicSystemUserBackend),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.backends = [backend() for name, backend in self.support_backends]
|
|
||||||
self._queryset = AssetUserQueryset(self.backends)
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
return self._queryset
|
|
||||||
|
|
||||||
def delete(self, obj):
|
|
||||||
name_backends_map = dict(self.support_backends)
|
|
||||||
backend_name = obj.backend
|
|
||||||
backend_cls = name_backends_map.get(backend_name)
|
|
||||||
union_id = obj.union_id
|
|
||||||
if backend_cls:
|
|
||||||
backend_cls().delete(union_id)
|
|
||||||
else:
|
|
||||||
raise ObjectDoesNotExist("Not backend found")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create(**kwargs):
|
|
||||||
# 使用create方法创建AuthBook对象,解决并发创建问题(添加锁机制)
|
|
||||||
authbook = AuthBook.create(**kwargs)
|
|
||||||
return authbook
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
return getattr(self._queryset, item)
|
|
|
@ -1,7 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
# from django.conf import settings
|
|
||||||
|
|
||||||
# from .vault import VaultBackend
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Generated by Django 3.1.6 on 2021-06-04 16:46
|
||||||
|
|
||||||
|
from django.db import migrations, models, transaction
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_admin_user_to_system_user(apps, schema_editor):
|
||||||
|
admin_user_model = apps.get_model("assets", "AdminUser")
|
||||||
|
system_user_model = apps.get_model("assets", "SystemUser")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
admin_users = admin_user_model.objects.using(db_alias).all()
|
||||||
|
print()
|
||||||
|
for admin_user in admin_users:
|
||||||
|
kwargs = {}
|
||||||
|
for attr in [
|
||||||
|
'id', 'org_id', 'username', 'password', 'private_key', 'public_key',
|
||||||
|
'comment', 'date_created', 'date_updated', 'created_by',
|
||||||
|
]:
|
||||||
|
value = getattr(admin_user, attr)
|
||||||
|
kwargs[attr] = value
|
||||||
|
|
||||||
|
name = admin_user.name
|
||||||
|
exist = system_user_model.objects.using(db_alias).filter(
|
||||||
|
name=admin_user.name, org_id=admin_user.org_id
|
||||||
|
).exists()
|
||||||
|
if exist:
|
||||||
|
name = admin_user.name + '_' + str(admin_user.id)[:5]
|
||||||
|
kwargs.update({
|
||||||
|
'name': name,
|
||||||
|
'type': 'admin',
|
||||||
|
'protocol': 'ssh',
|
||||||
|
'auto_push': False,
|
||||||
|
})
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
s = system_user_model(**kwargs)
|
||||||
|
s.save()
|
||||||
|
print(" Migrate admin user to system user: {} => {}".format(admin_user.name, s.name))
|
||||||
|
assets = admin_user.assets.all()
|
||||||
|
s.assets.set(assets)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0070_auto_20210426_1515'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('common', 'Common user'), ('admin', 'Admin user')], default='common', max_length=16, verbose_name='Type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='login_mode',
|
||||||
|
field=models.CharField(choices=[('auto', 'Automatic managed'), ('manual', 'Manually input')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='protocol',
|
||||||
|
field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC'), ('mysql', 'MySQL'), ('oracle', 'Oracle'), ('mariadb', 'MariaDB'), ('postgresql', 'PostgreSQL'), ('k8s', 'K8S')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_admin_user_to_system_user)
|
||||||
|
]
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Generated by Django 3.1.6 on 2021-06-05 16:10
|
||||||
|
|
||||||
|
import common.fields.model
|
||||||
|
from django.conf import settings
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import simple_history.models
|
||||||
|
import uuid
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db import migrations, transaction
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_old_authbook_to_history(apps, schema_editor):
|
||||||
|
authbook_model = apps.get_model("assets", "AuthBook")
|
||||||
|
history_model = apps.get_model("assets", "HistoricalAuthBook")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
print()
|
||||||
|
while True:
|
||||||
|
authbooks = authbook_model.objects.using(db_alias).filter(is_latest=False)[:20]
|
||||||
|
if not authbooks:
|
||||||
|
break
|
||||||
|
historys = []
|
||||||
|
authbook_ids = []
|
||||||
|
# Todo: 或许能优化成更新那样
|
||||||
|
for authbook in authbooks:
|
||||||
|
authbook_ids.append(authbook.id)
|
||||||
|
history = history_model()
|
||||||
|
|
||||||
|
for attr in [
|
||||||
|
'id', 'username', 'password', 'private_key', 'public_key', 'version',
|
||||||
|
'comment', 'created_by', 'asset', 'date_created', 'date_updated'
|
||||||
|
]:
|
||||||
|
setattr(history, attr, getattr(authbook, attr))
|
||||||
|
history.history_type = '-'
|
||||||
|
history.history_date = timezone.now()
|
||||||
|
historys.append(history)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
print(" Migrate old auth book to history table: {} items".format(len(authbook_ids)))
|
||||||
|
history_model.objects.bulk_create(historys, ignore_conflicts=True)
|
||||||
|
authbook_model.objects.filter(id__in=authbook_ids).delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('assets', '0071_systemuser_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HistoricalAuthBook',
|
||||||
|
fields=[
|
||||||
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
|
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
|
||||||
|
('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||||
|
('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')),
|
||||||
|
('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')),
|
||||||
|
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||||
|
('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')),
|
||||||
|
('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')),
|
||||||
|
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||||
|
('version', models.IntegerField(default=1, verbose_name='Version')),
|
||||||
|
('is_latest', models.BooleanField(default=False, verbose_name='Latest version')),
|
||||||
|
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('history_date', models.DateTimeField()),
|
||||||
|
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||||
|
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||||
|
('asset', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.asset', verbose_name='Asset')),
|
||||||
|
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'historical AuthBook',
|
||||||
|
'ordering': ('-history_date', '-history_id'),
|
||||||
|
'get_latest_by': 'history_date',
|
||||||
|
},
|
||||||
|
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_old_authbook_to_history)
|
||||||
|
]
|
|
@ -0,0 +1,105 @@
|
||||||
|
# Generated by Django 3.1.6 on 2021-06-06 03:42
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db import migrations, models, transaction
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_system_assets_to_authbook(apps, schema_editor):
|
||||||
|
system_user_model = apps.get_model("assets", "SystemUser")
|
||||||
|
system_user_asset_model = system_user_model.assets.through
|
||||||
|
authbook_model = apps.get_model('assets', 'AuthBook')
|
||||||
|
history_model = apps.get_model("assets", "HistoricalAuthBook")
|
||||||
|
|
||||||
|
print()
|
||||||
|
system_users = system_user_model.objects.all()
|
||||||
|
for s in system_users:
|
||||||
|
while True:
|
||||||
|
systemuser_asset_relations = system_user_asset_model.objects.filter(systemuser=s)[:20]
|
||||||
|
if not systemuser_asset_relations:
|
||||||
|
break
|
||||||
|
authbooks = []
|
||||||
|
relations_ids = []
|
||||||
|
historys = []
|
||||||
|
for i in systemuser_asset_relations:
|
||||||
|
authbook = authbook_model(asset=i.asset, systemuser=i.systemuser, org_id=s.org_id)
|
||||||
|
authbooks.append(authbook)
|
||||||
|
relations_ids.append(i.id)
|
||||||
|
|
||||||
|
history = history_model(
|
||||||
|
asset=i.asset, systemuser=i.systemuser,
|
||||||
|
date_created=timezone.now(), date_updated=timezone.now(),
|
||||||
|
)
|
||||||
|
history.history_type = '-'
|
||||||
|
history.history_date = timezone.now()
|
||||||
|
historys.append(history)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
print(" Migrate system user assets relations: {} items".format(len(relations_ids)))
|
||||||
|
authbook_model.objects.bulk_create(authbooks, ignore_conflicts=True)
|
||||||
|
history_model.objects.bulk_create(historys)
|
||||||
|
system_user_asset_model.objects.filter(id__in=relations_ids).delete()
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_authbook_secret_to_system_user(apps, schema_editor):
|
||||||
|
authbook_model = apps.get_model('assets', 'AuthBook')
|
||||||
|
history_model = apps.get_model('assets', 'HistoricalAuthBook')
|
||||||
|
|
||||||
|
print()
|
||||||
|
authbooks_without_systemuser = authbook_model.objects.filter(systemuser__isnull=True)
|
||||||
|
for authbook in authbooks_without_systemuser:
|
||||||
|
matched = authbook_model.objects.filter(
|
||||||
|
asset=authbook.asset, systemuser__username=authbook.username
|
||||||
|
)
|
||||||
|
if not matched:
|
||||||
|
continue
|
||||||
|
historys = []
|
||||||
|
for i in matched:
|
||||||
|
history = history_model(
|
||||||
|
asset=i.asset, systemuser=i.systemuser,
|
||||||
|
date_created=timezone.now(), date_updated=timezone.now(),
|
||||||
|
version=authbook.version
|
||||||
|
)
|
||||||
|
history.history_type = '-'
|
||||||
|
history.history_date = timezone.now()
|
||||||
|
historys.append(history)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
print(" Migrate secret to system user assets account: {} items".format(len(historys)))
|
||||||
|
matched.update(password=authbook.password, private_key=authbook.private_key,
|
||||||
|
public_key=authbook.public_key, version=authbook.version)
|
||||||
|
history_model.objects.bulk_create(historys)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0072_historicalauthbook'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='authbook',
|
||||||
|
name='systemuser',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser', verbose_name='System user'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='historicalauthbook',
|
||||||
|
name='systemuser',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.systemuser', verbose_name='System user'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='authbook',
|
||||||
|
unique_together={('username', 'asset', 'systemuser')},
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_system_assets_to_authbook),
|
||||||
|
migrations.RunPython(migrate_authbook_secret_to_system_user),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='authbook',
|
||||||
|
name='is_latest',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalauthbook',
|
||||||
|
name='is_latest',
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 3.1.6 on 2021-06-06 03:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0073_auto_20210606_1142'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='assets',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='asset',
|
||||||
|
old_name='admin_user',
|
||||||
|
new_name='_admin_user',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='assets',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='system_users', through='assets.AuthBook', to='assets.Asset', verbose_name='Assets'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Generated by Django 3.1 on 2021-07-05 09:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0074_remove_systemuser_assets'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='connectivity',
|
||||||
|
field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='asset',
|
||||||
|
name='date_verified',
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='authbook',
|
||||||
|
name='connectivity',
|
||||||
|
field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='authbook',
|
||||||
|
name='date_verified',
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='historicalauthbook',
|
||||||
|
name='connectivity',
|
||||||
|
field=models.CharField(choices=[('unknown', 'Unknown'), ('ok', 'Ok'), ('failed', 'Failed')], default='unknown', max_length=16, verbose_name='Connectivity'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='historicalauthbook',
|
||||||
|
name='date_verified',
|
||||||
|
field=models.DateTimeField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='asset',
|
||||||
|
name='protocol',
|
||||||
|
field=models.CharField(choices=[('ssh', 'SSH'), ('rdp', 'RDP'), ('telnet', 'Telnet'), ('vnc', 'VNC')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gateway',
|
||||||
|
name='protocol',
|
||||||
|
field=models.CharField(choices=[('ssh', 'SSH')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,7 +2,6 @@ from .base import *
|
||||||
from .asset import *
|
from .asset import *
|
||||||
from .label import Label
|
from .label import Label
|
||||||
from .user import *
|
from .user import *
|
||||||
from .asset_user import *
|
|
||||||
from .cluster import *
|
from .cluster import *
|
||||||
from .group import *
|
from .group import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
|
|
|
@ -4,18 +4,20 @@
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from common.db.models import TextChoices
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from common.fields.model import JsonDictTextField
|
from common.fields.model import JsonDictTextField
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||||
from .base import ConnectivityMixin
|
|
||||||
from .utils import Connectivity
|
from .base import AbsConnectivity
|
||||||
|
|
||||||
__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet']
|
__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet']
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -57,16 +59,12 @@ class AssetQuerySet(models.QuerySet):
|
||||||
|
|
||||||
class ProtocolsMixin:
|
class ProtocolsMixin:
|
||||||
protocols = ''
|
protocols = ''
|
||||||
PROTOCOL_SSH = 'ssh'
|
|
||||||
PROTOCOL_RDP = 'rdp'
|
class Protocol(TextChoices):
|
||||||
PROTOCOL_TELNET = 'telnet'
|
ssh = 'ssh', 'SSH'
|
||||||
PROTOCOL_VNC = 'vnc'
|
rdp = 'rdp', 'RDP'
|
||||||
PROTOCOL_CHOICES = (
|
telnet = 'telnet', 'Telnet'
|
||||||
(PROTOCOL_SSH, 'ssh'),
|
vnc = 'vnc', 'VNC'
|
||||||
(PROTOCOL_RDP, 'rdp'),
|
|
||||||
(PROTOCOL_TELNET, 'telnet'),
|
|
||||||
(PROTOCOL_VNC, 'vnc'),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def protocols_as_list(self):
|
def protocols_as_list(self):
|
||||||
|
@ -167,7 +165,7 @@ class Platform(models.Model):
|
||||||
# ordering = ('name',)
|
# ordering = ('name',)
|
||||||
|
|
||||||
|
|
||||||
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
# Important
|
# Important
|
||||||
PLATFORM_CHOICES = (
|
PLATFORM_CHOICES = (
|
||||||
('Linux', 'Linux'),
|
('Linux', 'Linux'),
|
||||||
|
@ -182,8 +180,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||||
protocol = models.CharField(max_length=128, default=ProtocolsMixin.PROTOCOL_SSH,
|
protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh,
|
||||||
choices=ProtocolsMixin.PROTOCOL_CHOICES,
|
choices=ProtocolsMixin.Protocol.choices,
|
||||||
verbose_name=_('Protocol'))
|
verbose_name=_('Protocol'))
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
||||||
|
@ -193,7 +191,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||||
|
|
||||||
# Auth
|
# Auth
|
||||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
|
_admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
|
||||||
|
|
||||||
# Some information
|
# Some information
|
||||||
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
||||||
|
@ -223,11 +221,26 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
|
||||||
objects = AssetManager.from_queryset(AssetQuerySet)()
|
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||||
_connectivity = None
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{0.hostname}({0.ip})'.format(self)
|
return '{0.hostname}({0.ip})'.format(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def admin_user(self):
|
||||||
|
return self.system_users.filter(type='admin').first()
|
||||||
|
|
||||||
|
@admin_user.setter
|
||||||
|
def admin_user(self, system_user):
|
||||||
|
if not system_user:
|
||||||
|
return
|
||||||
|
if system_user.type != 'admin':
|
||||||
|
raise ValidationError('System user should be type admin')
|
||||||
|
system_user.assets.add(self)
|
||||||
|
|
||||||
|
def remove_admin_user(self):
|
||||||
|
from ..models import AuthBook
|
||||||
|
AuthBook.objects.filter(asset=self, systemuser__type='admin').delete()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
warning = ''
|
warning = ''
|
||||||
|
@ -276,23 +289,6 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@property
|
|
||||||
def connectivity(self):
|
|
||||||
if self._connectivity:
|
|
||||||
return self._connectivity
|
|
||||||
if not self.admin_user_username:
|
|
||||||
return Connectivity.unknown()
|
|
||||||
connectivity = ConnectivityMixin.get_asset_username_connectivity(
|
|
||||||
self, self.admin_user_username
|
|
||||||
)
|
|
||||||
return connectivity
|
|
||||||
|
|
||||||
@connectivity.setter
|
|
||||||
def connectivity(self, value):
|
|
||||||
if not self.admin_user:
|
|
||||||
return
|
|
||||||
self.admin_user.set_asset_connectivity(self, value)
|
|
||||||
|
|
||||||
def get_auth_info(self):
|
def get_auth_info(self):
|
||||||
if not self.admin_user:
|
if not self.admin_user:
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from .authbook import AuthBook
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUser(AuthBook):
|
|
||||||
hostname = ""
|
|
||||||
ip = ""
|
|
||||||
backend = ""
|
|
||||||
backend_display = ""
|
|
||||||
union_id = ""
|
|
||||||
asset_username = ""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
proxy = True
|
|
|
@ -1,92 +1,94 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.db import models, transaction
|
from django.db import models
|
||||||
from django.db.models import Max
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.exceptions import PermissionDenied
|
|
||||||
|
|
||||||
from orgs.mixins.models import OrgManager
|
from simple_history.models import HistoricalRecords
|
||||||
from .base import BaseUser
|
|
||||||
|
|
||||||
|
from .base import BaseUser, AbsConnectivity
|
||||||
|
|
||||||
__all__ = ['AuthBook']
|
__all__ = ['AuthBook']
|
||||||
|
|
||||||
|
|
||||||
class AuthBookQuerySet(models.QuerySet):
|
class AuthBook(BaseUser, AbsConnectivity):
|
||||||
def delete(self):
|
|
||||||
if self.count() > 1:
|
|
||||||
raise PermissionDenied(_("Bulk delete deny"))
|
|
||||||
return super().delete()
|
|
||||||
|
|
||||||
|
|
||||||
class AuthBookManager(OrgManager):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AuthBook(BaseUser):
|
|
||||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
||||||
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
|
systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user"))
|
||||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
|
auth_attrs = ['username', 'password', 'private_key', 'public_key']
|
||||||
backend = "db"
|
|
||||||
# 用于system user和admin_user的动态设置
|
|
||||||
_connectivity = None
|
|
||||||
CONN_CACHE_KEY = "ASSET_USER_CONN_{}"
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('AuthBook')
|
verbose_name = _('AuthBook')
|
||||||
|
unique_together = [('username', 'asset', 'systemuser')]
|
||||||
|
|
||||||
def get_related_assets(self):
|
def __init__(self, *args, **kwargs):
|
||||||
return [self.asset]
|
super().__init__(*args, **kwargs)
|
||||||
|
self.auth_snapshot = {}
|
||||||
|
self.load_auth()
|
||||||
|
|
||||||
def generate_id_with_asset(self, asset):
|
def get_or_systemuser_attr(self, attr):
|
||||||
return self.id
|
val = getattr(self, attr, None)
|
||||||
|
if val:
|
||||||
|
return val
|
||||||
|
if self.systemuser:
|
||||||
|
return getattr(self.systemuser, attr, '')
|
||||||
|
return ''
|
||||||
|
|
||||||
@classmethod
|
def load_auth(self):
|
||||||
def get_max_version(cls, username, asset):
|
for attr in self.auth_attrs:
|
||||||
version_max = cls.objects.filter(username=username, asset=asset) \
|
value = self.get_or_systemuser_attr(attr)
|
||||||
.aggregate(Max('version'))
|
self.auth_snapshot[attr] = [getattr(self, attr), value]
|
||||||
version_max = version_max['version__max'] or 0
|
setattr(self, attr, value)
|
||||||
return version_max
|
|
||||||
|
|
||||||
@classmethod
|
def unload_auth(self):
|
||||||
def create(cls, **kwargs):
|
if not self.systemuser:
|
||||||
"""
|
return
|
||||||
使用并发锁机制创建AuthBook对象, (主要针对并发创建 username, asset 相同的对象时)
|
|
||||||
并更新其他对象的 is_latest=False (其他对象: 与当前对象的 username, asset 相同)
|
for attr, values in self.auth_snapshot.items():
|
||||||
同时设置自己的 is_latest=True, version=max_version + 1
|
origin_value, loaded_value = values
|
||||||
"""
|
current_value = getattr(self, attr, '')
|
||||||
username = kwargs['username']
|
if current_value == loaded_value:
|
||||||
asset = kwargs.get('asset') or kwargs.get('asset_id')
|
setattr(self, attr, origin_value)
|
||||||
with transaction.atomic():
|
|
||||||
# 使用select_for_update限制并发创建相同的username、asset条目
|
def save(self, *args, **kwargs):
|
||||||
instances = cls.objects.select_for_update().filter(username=username, asset=asset)
|
self.unload_auth()
|
||||||
instances.filter(is_latest=True).update(is_latest=False)
|
instance = super().save(*args, **kwargs)
|
||||||
max_version = cls.get_max_version(username, asset)
|
self.load_auth()
|
||||||
kwargs.update({
|
return instance
|
||||||
'version': max_version + 1,
|
|
||||||
'is_latest': True
|
|
||||||
})
|
|
||||||
obj = cls.objects.create(**kwargs)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connectivity(self):
|
def username_display(self):
|
||||||
return self.get_asset_connectivity(self.asset)
|
return self.get_or_systemuser_attr('username') or '*'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def keyword(self):
|
def smart_name(self):
|
||||||
return '{}_#_{}'.format(self.username, str(self.asset.id))
|
username = self.username_display
|
||||||
|
|
||||||
@property
|
if self.asset:
|
||||||
def hostname(self):
|
asset = str(self.asset)
|
||||||
return self.asset.hostname
|
else:
|
||||||
|
asset = '*'
|
||||||
|
return '{}@{}'.format(username, asset)
|
||||||
|
|
||||||
@property
|
def sync_to_system_user_account(self):
|
||||||
def ip(self):
|
if self.systemuser:
|
||||||
return self.asset.ip
|
return
|
||||||
|
matched = AuthBook.objects.filter(
|
||||||
|
asset=self.asset, systemuser__username=self.username
|
||||||
|
)
|
||||||
|
if not matched:
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in matched:
|
||||||
|
i.password = self.password
|
||||||
|
i.private_key = self.private_key
|
||||||
|
i.public_key = self.public_key
|
||||||
|
i.comment = 'Update triggered by account {}'.format(self.id)
|
||||||
|
i.save(update_fields=['password', 'private_key', 'public_key'])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}@{}'.format(self.username, self.asset)
|
return self.smart_name
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,10 @@ from hashlib import md5
|
||||||
import sshpubkeys
|
import sshpubkeys
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from common.utils import random_string, signer
|
from common.utils import random_string, signer
|
||||||
from common.utils import (
|
from common.utils import (
|
||||||
|
@ -19,85 +21,39 @@ from common.utils.encode import ssh_pubkey_gen
|
||||||
from common.validators import alphanumeric
|
from common.validators import alphanumeric
|
||||||
from common import fields
|
from common import fields
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from .utils import Connectivity
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class ConnectivityMixin:
|
class Connectivity(models.TextChoices):
|
||||||
CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
|
unknown = 'unknown', _('Unknown')
|
||||||
CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
|
ok = 'ok', _('Ok')
|
||||||
ASSET_USER_CACHE_TIME = 3600 * 24
|
failed = 'failed', _('Failed')
|
||||||
id = ''
|
|
||||||
username = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def part_id(self):
|
|
||||||
i = '-'.join(str(self.id).split('-')[:3])
|
|
||||||
return i
|
|
||||||
|
|
||||||
def set_connectivity(self, summary):
|
class AbsConnectivity(models.Model):
|
||||||
unreachable = summary.get('dark', {}).keys()
|
connectivity = models.CharField(
|
||||||
reachable = summary.get('contacted', {}).keys()
|
choices=Connectivity.choices, default=Connectivity.unknown,
|
||||||
|
max_length=16, verbose_name=_('Connectivity')
|
||||||
|
)
|
||||||
|
date_verified = models.DateTimeField(null=True)
|
||||||
|
|
||||||
assets = self.get_related_assets()
|
def set_connectivity(self, val):
|
||||||
if not isinstance(assets, list):
|
self.connectivity = val
|
||||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
self.date_verified = timezone.now()
|
||||||
for asset in assets:
|
self.save(update_fields=['connectivity', 'date_verified'])
|
||||||
if asset.hostname in unreachable:
|
|
||||||
self.set_asset_connectivity(asset, Connectivity.unreachable())
|
|
||||||
elif asset.hostname in reachable:
|
|
||||||
self.set_asset_connectivity(asset, Connectivity.reachable())
|
|
||||||
else:
|
|
||||||
self.set_asset_connectivity(asset, Connectivity.unknown())
|
|
||||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
|
||||||
cache.delete(cache_key)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connectivity(self):
|
|
||||||
assets = self.get_related_assets()
|
|
||||||
if not isinstance(assets, list):
|
|
||||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
|
||||||
data = {
|
|
||||||
'unreachable': [],
|
|
||||||
'reachable': [],
|
|
||||||
'unknown': [],
|
|
||||||
}
|
|
||||||
for asset in assets:
|
|
||||||
connectivity = self.get_asset_connectivity(asset)
|
|
||||||
if connectivity.is_reachable():
|
|
||||||
data["reachable"].append(asset.hostname)
|
|
||||||
elif connectivity.is_unreachable():
|
|
||||||
data["unreachable"].append(asset.hostname)
|
|
||||||
else:
|
|
||||||
data["unknown"].append(asset.hostname)
|
|
||||||
return data
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connectivity_amount(self):
|
|
||||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
|
||||||
amount = cache.get(cache_key)
|
|
||||||
if not amount:
|
|
||||||
amount = {k: len(v) for k, v in self.connectivity.items()}
|
|
||||||
cache.set(cache_key, amount, self.ASSET_USER_CACHE_TIME)
|
|
||||||
return amount
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_asset_username_connectivity(cls, asset, username):
|
def bulk_set_connectivity(cls, queryset_or_id, connectivity):
|
||||||
key = cls.CONNECTIVITY_ASSET_CACHE_KEY.format(username, asset.id)
|
if not isinstance(queryset_or_id, QuerySet):
|
||||||
return Connectivity.get(key)
|
queryset = cls.objects.filter(id__in=queryset_or_id)
|
||||||
|
else:
|
||||||
|
queryset = queryset_or_id
|
||||||
|
queryset.update(connectivity=connectivity, date_verified=timezone.now())
|
||||||
|
|
||||||
def get_asset_connectivity(self, asset):
|
class Meta:
|
||||||
key = self.get_asset_connectivity_key(asset)
|
abstract = True
|
||||||
return Connectivity.get(key)
|
|
||||||
|
|
||||||
def get_asset_connectivity_key(self, asset):
|
|
||||||
return self.CONNECTIVITY_ASSET_CACHE_KEY.format(self.username, asset.id)
|
|
||||||
|
|
||||||
def set_asset_connectivity(self, asset, c):
|
|
||||||
key = self.get_asset_connectivity_key(asset)
|
|
||||||
Connectivity.set(key, c)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthMixin:
|
class AuthMixin:
|
||||||
|
@ -105,7 +61,6 @@ class AuthMixin:
|
||||||
password = ''
|
password = ''
|
||||||
public_key = ''
|
public_key = ''
|
||||||
username = ''
|
username = ''
|
||||||
_prefer = 'system_user'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ssh_key_fingerprint(self):
|
def ssh_key_fingerprint(self):
|
||||||
|
@ -173,38 +128,6 @@ class AuthMixin:
|
||||||
if update_fields:
|
if update_fields:
|
||||||
self.save(update_fields=update_fields)
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
def has_special_auth(self, asset=None, username=None):
|
|
||||||
from .authbook import AuthBook
|
|
||||||
if username is None:
|
|
||||||
username = self.username
|
|
||||||
queryset = AuthBook.objects.filter(username=username)
|
|
||||||
if asset:
|
|
||||||
queryset = queryset.filter(asset=asset)
|
|
||||||
return queryset.exists()
|
|
||||||
|
|
||||||
def get_asset_user(self, asset, username=None):
|
|
||||||
from ..backends import AssetUserManager
|
|
||||||
if username is None:
|
|
||||||
username = self.username
|
|
||||||
try:
|
|
||||||
manager = AssetUserManager()
|
|
||||||
other = manager.get_latest(
|
|
||||||
username=username, asset=asset,
|
|
||||||
prefer_id=self.id, prefer=self._prefer,
|
|
||||||
)
|
|
||||||
return other
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e, exc_info=True)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def load_asset_special_auth(self, asset=None, username=None):
|
|
||||||
if not asset:
|
|
||||||
return self
|
|
||||||
|
|
||||||
instance = self.get_asset_user(asset, username=username)
|
|
||||||
if instance:
|
|
||||||
self._merge_auth(instance)
|
|
||||||
|
|
||||||
def _merge_auth(self, other):
|
def _merge_auth(self, other):
|
||||||
if other.password:
|
if other.password:
|
||||||
self.password = other.password
|
self.password = other.password
|
||||||
|
@ -244,7 +167,7 @@ class AuthMixin:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseUser(OrgModelMixin, AuthMixin, ConnectivityMixin):
|
class BaseUser(OrgModelMixin, AuthMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
|
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
|
||||||
|
@ -259,8 +182,6 @@ class BaseUser(OrgModelMixin, AuthMixin, ConnectivityMixin):
|
||||||
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
|
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
|
||||||
ASSET_USER_CACHE_TIME = 600
|
ASSET_USER_CACHE_TIME = 600
|
||||||
|
|
||||||
_prefer = "system_user"
|
|
||||||
|
|
||||||
def get_related_assets(self):
|
def get_related_assets(self):
|
||||||
assets = self.assets.filter(org_id=self.org_id)
|
assets = self.assets.filter(org_id=self.org_id)
|
||||||
return assets
|
return assets
|
||||||
|
|
|
@ -7,6 +7,7 @@ import re
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import TextChoices
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils.strings import no_special_chars
|
from common.utils.strings import no_special_chars
|
||||||
|
@ -43,15 +44,12 @@ class Domain(OrgModelMixin):
|
||||||
|
|
||||||
|
|
||||||
class Gateway(BaseUser):
|
class Gateway(BaseUser):
|
||||||
PROTOCOL_SSH = 'ssh'
|
class Protocol(TextChoices):
|
||||||
PROTOCOL_RDP = 'rdp'
|
ssh = 'ssh', 'SSH'
|
||||||
PROTOCOL_CHOICES = (
|
|
||||||
(PROTOCOL_SSH, 'ssh'),
|
|
||||||
(PROTOCOL_RDP, 'rdp'),
|
|
||||||
)
|
|
||||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=PROTOCOL_SSH, verbose_name=_("Protocol"))
|
protocol = models.CharField(choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol"))
|
||||||
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
||||||
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
|
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||||
|
|
|
@ -10,15 +10,278 @@ from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
from common.utils import signer, get_object_or_none
|
from common.utils import signer, get_object_or_none
|
||||||
from common.exceptions import JMSException
|
from common.db.models import TextChoices
|
||||||
from .base import BaseUser
|
from .base import BaseUser
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
|
from .authbook import AuthBook
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AdminUser', 'SystemUser']
|
__all__ = ['AdminUser', 'SystemUser']
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolMixin:
|
||||||
|
protocol: str
|
||||||
|
|
||||||
|
class Protocol(TextChoices):
|
||||||
|
ssh = 'ssh', 'SSH'
|
||||||
|
rdp = 'rdp', 'RDP'
|
||||||
|
telnet = 'telnet', 'Telnet'
|
||||||
|
vnc = 'vnc', 'VNC'
|
||||||
|
mysql = 'mysql', 'MySQL'
|
||||||
|
oracle = 'oracle', 'Oracle'
|
||||||
|
mariadb = 'mariadb', 'MariaDB'
|
||||||
|
postgresql = 'postgresql', 'PostgreSQL'
|
||||||
|
k8s = 'k8s', 'K8S'
|
||||||
|
|
||||||
|
SUPPORT_PUSH_PROTOCOLS = [Protocol.ssh, Protocol.rdp]
|
||||||
|
|
||||||
|
ASSET_CATEGORY_PROTOCOLS = [
|
||||||
|
Protocol.ssh, Protocol.rdp, Protocol.telnet, Protocol.vnc
|
||||||
|
]
|
||||||
|
APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [
|
||||||
|
Protocol.rdp
|
||||||
|
]
|
||||||
|
APPLICATION_CATEGORY_DB_PROTOCOLS = [
|
||||||
|
Protocol.mysql, Protocol.oracle, Protocol.mariadb, Protocol.postgresql
|
||||||
|
]
|
||||||
|
APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [
|
||||||
|
Protocol.k8s
|
||||||
|
]
|
||||||
|
APPLICATION_CATEGORY_PROTOCOLS = [
|
||||||
|
*APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS,
|
||||||
|
*APPLICATION_CATEGORY_DB_PROTOCOLS,
|
||||||
|
*APPLICATION_CATEGORY_CLOUD_PROTOCOLS
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_protocol_support_push(self):
|
||||||
|
return self.protocol in self.SUPPORT_PUSH_PROTOCOLS
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_protocol_by_application_type(cls, app_type):
|
||||||
|
from applications.const import ApplicationTypeChoices
|
||||||
|
if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
|
||||||
|
protocol = app_type
|
||||||
|
elif app_type in ApplicationTypeChoices.remote_app_types():
|
||||||
|
protocol = cls.Protocol.rdp
|
||||||
|
else:
|
||||||
|
protocol = None
|
||||||
|
return protocol
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_perm_to_asset(self):
|
||||||
|
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
|
||||||
|
|
||||||
|
|
||||||
|
class AuthMixin:
|
||||||
|
username_same_with_user: bool
|
||||||
|
protocol: str
|
||||||
|
ASSET_CATEGORY_PROTOCOLS: list
|
||||||
|
login_mode: str
|
||||||
|
LOGIN_MANUAL: str
|
||||||
|
id: str
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
private_key: str
|
||||||
|
public_key: str
|
||||||
|
|
||||||
|
def set_temp_auth(self, asset_or_app_id, user_id, auth, ttl=300):
|
||||||
|
if not auth:
|
||||||
|
raise ValueError('Auth not set')
|
||||||
|
key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id)
|
||||||
|
logger.debug(f'Set system user temp auth: {key}')
|
||||||
|
cache.set(key, auth, ttl)
|
||||||
|
|
||||||
|
def get_temp_auth(self, asset_or_app_id, user_id):
|
||||||
|
key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id)
|
||||||
|
logger.debug(f'Get system user temp auth: {key}')
|
||||||
|
password = cache.get(key)
|
||||||
|
return password
|
||||||
|
|
||||||
|
def load_tmp_auth_if_has(self, asset_or_app_id, user):
|
||||||
|
if not asset_or_app_id or not user:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.login_mode != self.LOGIN_MANUAL:
|
||||||
|
return
|
||||||
|
|
||||||
|
auth = self.get_temp_auth(asset_or_app_id, user)
|
||||||
|
if not auth:
|
||||||
|
return
|
||||||
|
username = auth.get('username')
|
||||||
|
password = auth.get('password')
|
||||||
|
|
||||||
|
if username:
|
||||||
|
self.username = username
|
||||||
|
if password:
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
def load_app_more_auth(self, app_id=None, user_id=None):
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
if self.login_mode == self.LOGIN_MANUAL:
|
||||||
|
self.password = ''
|
||||||
|
self.private_key = ''
|
||||||
|
if not user_id:
|
||||||
|
return
|
||||||
|
user = get_object_or_none(User, pk=user_id)
|
||||||
|
if not user:
|
||||||
|
return
|
||||||
|
self.load_tmp_auth_if_has(app_id, user)
|
||||||
|
|
||||||
|
def load_asset_special_auth(self, asset, username=''):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
authbooks = list(AuthBook.objects.filter(asset=asset, systemuser=self))
|
||||||
|
if len(authbooks) == 0:
|
||||||
|
return None
|
||||||
|
elif len(authbooks) == 1:
|
||||||
|
authbook = authbooks[0]
|
||||||
|
else:
|
||||||
|
authbooks.sort(key=lambda x: 1 if x.username == username else 0, reverse=True)
|
||||||
|
authbook = authbooks[0]
|
||||||
|
self.password = authbook.password
|
||||||
|
self.private_key = authbook.private_key
|
||||||
|
self.public_key = authbook.public_key
|
||||||
|
|
||||||
|
def load_asset_more_auth(self, asset_id=None, username=None, user_id=None):
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
if self.login_mode == self.LOGIN_MANUAL:
|
||||||
|
self.password = ''
|
||||||
|
self.private_key = ''
|
||||||
|
|
||||||
|
asset = None
|
||||||
|
if asset_id:
|
||||||
|
asset = get_object_or_none(Asset, pk=asset_id)
|
||||||
|
# 没有资产就没有必要继续了
|
||||||
|
if not asset:
|
||||||
|
logger.debug('Asset not found, pass')
|
||||||
|
return
|
||||||
|
|
||||||
|
user = None
|
||||||
|
if user_id:
|
||||||
|
user = get_object_or_none(User, pk=user_id)
|
||||||
|
|
||||||
|
_username = self.username
|
||||||
|
if self.username_same_with_user:
|
||||||
|
if user and not username:
|
||||||
|
_username = user.username
|
||||||
|
else:
|
||||||
|
_username = username
|
||||||
|
self.username = _username
|
||||||
|
|
||||||
|
# 加载某个资产的特殊配置认证信息
|
||||||
|
self.load_asset_special_auth(asset, _username)
|
||||||
|
self.load_tmp_auth_if_has(asset_id, user)
|
||||||
|
|
||||||
|
|
||||||
|
class SystemUser(ProtocolMixin, AuthMixin, BaseUser):
|
||||||
|
LOGIN_AUTO = 'auto'
|
||||||
|
LOGIN_MANUAL = 'manual'
|
||||||
|
LOGIN_MODE_CHOICES = (
|
||||||
|
(LOGIN_AUTO, _('Automatic managed')),
|
||||||
|
(LOGIN_MANUAL, _('Manually input'))
|
||||||
|
)
|
||||||
|
|
||||||
|
class Type(TextChoices):
|
||||||
|
common = 'common', _('Common user')
|
||||||
|
admin = 'admin', _('Admin user')
|
||||||
|
|
||||||
|
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
|
||||||
|
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||||
|
assets = models.ManyToManyField(
|
||||||
|
'assets.Asset', blank=True, verbose_name=_("Assets"),
|
||||||
|
through='assets.AuthBook', through_fields=['systemuser', 'asset'],
|
||||||
|
related_name='system_users'
|
||||||
|
)
|
||||||
|
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
|
||||||
|
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
|
||||||
|
type = models.CharField(max_length=16, choices=Type.choices, default=Type.common, verbose_name=_('Type'))
|
||||||
|
priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||||
|
protocol = models.CharField(max_length=16, choices=ProtocolMixin.Protocol.choices, default='ssh', verbose_name=_('Protocol'))
|
||||||
|
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||||
|
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
||||||
|
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||||
|
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
|
||||||
|
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
||||||
|
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
|
||||||
|
token = models.TextField(default='', verbose_name=_('Token'))
|
||||||
|
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True)
|
||||||
|
system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True)
|
||||||
|
ad_domain = models.CharField(default='', max_length=256)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
username = self.username
|
||||||
|
if self.username_same_with_user:
|
||||||
|
username = 'dynamic'
|
||||||
|
return '{0.name}({1})'.format(self, username)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nodes_amount(self):
|
||||||
|
return self.nodes.all().count()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def login_mode_display(self):
|
||||||
|
return self.get_login_mode_display()
|
||||||
|
|
||||||
|
def is_need_push(self):
|
||||||
|
if self.auto_push and self.is_protocol_support_push:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_admin_user(self):
|
||||||
|
return self.type == self.Type.admin
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_need_cmd_filter(self):
|
||||||
|
return self.protocol not in [self.Protocol.rdp, self.Protocol.vnc]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_need_test_asset_connective(self):
|
||||||
|
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cmd_filter_rules(self):
|
||||||
|
from .cmd_filter import CommandFilterRule
|
||||||
|
rules = CommandFilterRule.objects.filter(
|
||||||
|
filter__in=self.cmd_filters.all()
|
||||||
|
).distinct()
|
||||||
|
return rules
|
||||||
|
|
||||||
|
def is_command_can_run(self, command):
|
||||||
|
for rule in self.cmd_filter_rules:
|
||||||
|
action, matched_cmd = rule.match(command)
|
||||||
|
if action == rule.ActionChoices.allow:
|
||||||
|
return True, None
|
||||||
|
elif action == rule.ActionChoices.deny:
|
||||||
|
return False, matched_cmd
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
def get_all_assets(self):
|
||||||
|
from assets.models import Node
|
||||||
|
nodes_keys = self.nodes.all().values_list('key', flat=True)
|
||||||
|
asset_ids = set(self.assets.all().values_list('id', flat=True))
|
||||||
|
nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys)
|
||||||
|
asset_ids.update(nodes_asset_ids)
|
||||||
|
assets = Asset.objects.filter(id__in=asset_ids)
|
||||||
|
return assets
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.username_same_with_user:
|
||||||
|
self.username = '*'
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
unique_together = [('name', 'org_id')]
|
||||||
|
verbose_name = _("System user")
|
||||||
|
|
||||||
|
|
||||||
|
# Todo: 准备废弃
|
||||||
class AdminUser(BaseUser):
|
class AdminUser(BaseUser):
|
||||||
"""
|
"""
|
||||||
A privileged user that ansible can use it to push system user and so on
|
A privileged user that ansible can use it to push system user and so on
|
||||||
|
@ -65,243 +328,3 @@ class AdminUser(BaseUser):
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
unique_together = [('name', 'org_id')]
|
unique_together = [('name', 'org_id')]
|
||||||
verbose_name = _("Admin user")
|
verbose_name = _("Admin user")
|
||||||
|
|
||||||
|
|
||||||
class SystemUser(BaseUser):
|
|
||||||
PROTOCOL_SSH = 'ssh'
|
|
||||||
PROTOCOL_RDP = 'rdp'
|
|
||||||
PROTOCOL_TELNET = 'telnet'
|
|
||||||
PROTOCOL_VNC = 'vnc'
|
|
||||||
PROTOCOL_MYSQL = 'mysql'
|
|
||||||
PROTOCOL_ORACLE = 'oracle'
|
|
||||||
PROTOCOL_MARIADB = 'mariadb'
|
|
||||||
PROTOCOL_POSTGRESQL = 'postgresql'
|
|
||||||
PROTOCOL_K8S = 'k8s'
|
|
||||||
PROTOCOL_CHOICES = (
|
|
||||||
(PROTOCOL_SSH, 'ssh'),
|
|
||||||
(PROTOCOL_RDP, 'rdp'),
|
|
||||||
(PROTOCOL_TELNET, 'telnet'),
|
|
||||||
(PROTOCOL_VNC, 'vnc'),
|
|
||||||
(PROTOCOL_MYSQL, 'mysql'),
|
|
||||||
(PROTOCOL_ORACLE, 'oracle'),
|
|
||||||
(PROTOCOL_MARIADB, 'mariadb'),
|
|
||||||
(PROTOCOL_POSTGRESQL, 'postgresql'),
|
|
||||||
(PROTOCOL_K8S, 'k8s'),
|
|
||||||
)
|
|
||||||
|
|
||||||
SUPPORT_PUSH_PROTOCOLS = [PROTOCOL_SSH, PROTOCOL_RDP]
|
|
||||||
|
|
||||||
ASSET_CATEGORY_PROTOCOLS = [
|
|
||||||
PROTOCOL_SSH, PROTOCOL_RDP, PROTOCOL_TELNET, PROTOCOL_VNC
|
|
||||||
]
|
|
||||||
APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS = [
|
|
||||||
PROTOCOL_RDP
|
|
||||||
]
|
|
||||||
APPLICATION_CATEGORY_DB_PROTOCOLS = [
|
|
||||||
PROTOCOL_MYSQL, PROTOCOL_ORACLE, PROTOCOL_MARIADB, PROTOCOL_POSTGRESQL
|
|
||||||
]
|
|
||||||
APPLICATION_CATEGORY_CLOUD_PROTOCOLS = [
|
|
||||||
PROTOCOL_K8S
|
|
||||||
]
|
|
||||||
APPLICATION_CATEGORY_PROTOCOLS = [
|
|
||||||
*APPLICATION_CATEGORY_REMOTE_APP_PROTOCOLS,
|
|
||||||
*APPLICATION_CATEGORY_DB_PROTOCOLS,
|
|
||||||
*APPLICATION_CATEGORY_CLOUD_PROTOCOLS
|
|
||||||
]
|
|
||||||
|
|
||||||
LOGIN_AUTO = 'auto'
|
|
||||||
LOGIN_MANUAL = 'manual'
|
|
||||||
LOGIN_MODE_CHOICES = (
|
|
||||||
(LOGIN_AUTO, _('Automatic login')),
|
|
||||||
(LOGIN_MANUAL, _('Manually login'))
|
|
||||||
)
|
|
||||||
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
|
|
||||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
|
||||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
|
||||||
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
|
|
||||||
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
|
|
||||||
priority = models.IntegerField(default=81, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)])
|
|
||||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
|
||||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
|
||||||
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
|
||||||
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
|
||||||
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
|
|
||||||
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
|
||||||
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
|
|
||||||
token = models.TextField(default='', verbose_name=_('Token'))
|
|
||||||
home = models.CharField(max_length=4096, default='', verbose_name=_('Home'), blank=True)
|
|
||||||
system_groups = models.CharField(default='', max_length=4096, verbose_name=_('System groups'), blank=True)
|
|
||||||
ad_domain = models.CharField(default='', max_length=256)
|
|
||||||
_prefer = 'system_user'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
username = self.username
|
|
||||||
if self.username_same_with_user:
|
|
||||||
username = 'dynamic'
|
|
||||||
return '{0.name}({1})'.format(self, username)
|
|
||||||
|
|
||||||
def get_username(self):
|
|
||||||
if self.username_same_with_user:
|
|
||||||
return list(self.users.values_list('username', flat=True))
|
|
||||||
else:
|
|
||||||
return self.username
|
|
||||||
|
|
||||||
@property
|
|
||||||
def nodes_amount(self):
|
|
||||||
return self.nodes.all().count()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def login_mode_display(self):
|
|
||||||
return self.get_login_mode_display()
|
|
||||||
|
|
||||||
def is_need_push(self):
|
|
||||||
if self.auto_push and self.is_protocol_support_push:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_protocol_support_push(self):
|
|
||||||
return self.protocol in self.SUPPORT_PUSH_PROTOCOLS
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_need_cmd_filter(self):
|
|
||||||
return self.protocol not in [self.PROTOCOL_RDP, self.PROTOCOL_VNC]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_need_test_asset_connective(self):
|
|
||||||
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
|
|
||||||
|
|
||||||
def has_special_auth(self, asset=None, username=None):
|
|
||||||
if username is None and self.username_same_with_user:
|
|
||||||
raise TypeError('System user is dynamic, username should be pass')
|
|
||||||
return super().has_special_auth(asset=asset, username=username)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def can_perm_to_asset(self):
|
|
||||||
return self.protocol in self.ASSET_CATEGORY_PROTOCOLS
|
|
||||||
|
|
||||||
def _merge_auth(self, other):
|
|
||||||
super()._merge_auth(other)
|
|
||||||
if self.username_same_with_user:
|
|
||||||
self.username = other.username
|
|
||||||
|
|
||||||
def set_temp_auth(self, asset_or_app_id, user_id, auth, ttl=300):
|
|
||||||
if not auth:
|
|
||||||
raise ValueError('Auth not set')
|
|
||||||
key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id)
|
|
||||||
logger.debug(f'Set system user temp auth: {key}')
|
|
||||||
cache.set(key, auth, ttl)
|
|
||||||
|
|
||||||
def get_temp_auth(self, asset_or_app_id, user_id):
|
|
||||||
key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id)
|
|
||||||
logger.debug(f'Get system user temp auth: {key}')
|
|
||||||
password = cache.get(key)
|
|
||||||
return password
|
|
||||||
|
|
||||||
def load_tmp_auth_if_has(self, asset_or_app_id, user):
|
|
||||||
if not asset_or_app_id or not user:
|
|
||||||
return
|
|
||||||
if self.login_mode != self.LOGIN_MANUAL:
|
|
||||||
pass
|
|
||||||
|
|
||||||
auth = self.get_temp_auth(asset_or_app_id, user)
|
|
||||||
if not auth:
|
|
||||||
return
|
|
||||||
username = auth.get('username')
|
|
||||||
password = auth.get('password')
|
|
||||||
|
|
||||||
if username:
|
|
||||||
self.username = username
|
|
||||||
if password:
|
|
||||||
self.password = password
|
|
||||||
|
|
||||||
def load_app_more_auth(self, app_id=None, user_id=None):
|
|
||||||
from users.models import User
|
|
||||||
|
|
||||||
if self.login_mode == self.LOGIN_MANUAL:
|
|
||||||
self.password = ''
|
|
||||||
self.private_key = ''
|
|
||||||
if not user_id:
|
|
||||||
return
|
|
||||||
user = get_object_or_none(User, pk=user_id)
|
|
||||||
if not user:
|
|
||||||
return
|
|
||||||
self.load_tmp_auth_if_has(app_id, user)
|
|
||||||
|
|
||||||
def load_asset_more_auth(self, asset_id=None, username=None, user_id=None):
|
|
||||||
from users.models import User
|
|
||||||
|
|
||||||
if self.login_mode == self.LOGIN_MANUAL:
|
|
||||||
self.password = ''
|
|
||||||
self.private_key = ''
|
|
||||||
|
|
||||||
asset = None
|
|
||||||
if asset_id:
|
|
||||||
asset = get_object_or_none(Asset, pk=asset_id)
|
|
||||||
# 没有资产就没有必要继续了
|
|
||||||
if not asset:
|
|
||||||
logger.debug('Asset not found, pass')
|
|
||||||
return
|
|
||||||
|
|
||||||
user = None
|
|
||||||
if user_id:
|
|
||||||
user = get_object_or_none(User, pk=user_id)
|
|
||||||
|
|
||||||
_username = self.username
|
|
||||||
if self.username_same_with_user:
|
|
||||||
if user and not username:
|
|
||||||
_username = user.username
|
|
||||||
else:
|
|
||||||
_username = username
|
|
||||||
|
|
||||||
# 加载某个资产的特殊配置认证信息
|
|
||||||
try:
|
|
||||||
self.load_asset_special_auth(asset, _username)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error('Load special auth Error: ', e)
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.load_tmp_auth_if_has(asset_id, user)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cmd_filter_rules(self):
|
|
||||||
from .cmd_filter import CommandFilterRule
|
|
||||||
rules = CommandFilterRule.objects.filter(
|
|
||||||
filter__in=self.cmd_filters.all()
|
|
||||||
).distinct()
|
|
||||||
return rules
|
|
||||||
|
|
||||||
def is_command_can_run(self, command):
|
|
||||||
for rule in self.cmd_filter_rules:
|
|
||||||
action, matched_cmd = rule.match(command)
|
|
||||||
if action == rule.ActionChoices.allow:
|
|
||||||
return True, None
|
|
||||||
elif action == rule.ActionChoices.deny:
|
|
||||||
return False, matched_cmd
|
|
||||||
return True, None
|
|
||||||
|
|
||||||
def get_all_assets(self):
|
|
||||||
from assets.models import Node
|
|
||||||
nodes_keys = self.nodes.all().values_list('key', flat=True)
|
|
||||||
asset_ids = set(self.assets.all().values_list('id', flat=True))
|
|
||||||
nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys)
|
|
||||||
asset_ids.update(nodes_asset_ids)
|
|
||||||
assets = Asset.objects.filter(id__in=asset_ids)
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_protocol_by_application_type(cls, app_type):
|
|
||||||
from applications.const import ApplicationTypeChoices
|
|
||||||
if app_type in cls.APPLICATION_CATEGORY_PROTOCOLS:
|
|
||||||
protocol = app_type
|
|
||||||
elif app_type in ApplicationTypeChoices.remote_app_types():
|
|
||||||
protocol = cls.PROTOCOL_RDP
|
|
||||||
else:
|
|
||||||
protocol = None
|
|
||||||
return protocol
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['name']
|
|
||||||
unique_together = [('name', 'org_id')]
|
|
||||||
verbose_name = _("System user")
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from common.utils import validate_ssh_private_key
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'init_model', 'generate_fake', 'private_key_validator', 'Connectivity',
|
'init_model', 'generate_fake', 'private_key_validator',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,74 +35,3 @@ def private_key_validator(value):
|
||||||
_('%(value)s is not an even number'),
|
_('%(value)s is not an even number'),
|
||||||
params={'value': value},
|
params={'value': value},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Connectivity:
|
|
||||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
|
||||||
CONNECTIVITY_CHOICES = (
|
|
||||||
(UNREACHABLE, _("Unreachable")),
|
|
||||||
(REACHABLE, _('Reachable')),
|
|
||||||
(UNKNOWN, _("Unknown")),
|
|
||||||
)
|
|
||||||
|
|
||||||
status = UNKNOWN
|
|
||||||
datetime = timezone.now()
|
|
||||||
|
|
||||||
def __init__(self, status, datetime):
|
|
||||||
self.status = status
|
|
||||||
self.datetime = datetime
|
|
||||||
|
|
||||||
def display(self):
|
|
||||||
return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.status)
|
|
||||||
|
|
||||||
def is_reachable(self):
|
|
||||||
return self.status == self.REACHABLE
|
|
||||||
|
|
||||||
def is_unreachable(self):
|
|
||||||
return self.status == self.UNREACHABLE
|
|
||||||
|
|
||||||
def is_unknown(self):
|
|
||||||
return self.status == self.UNKNOWN
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def unreachable(cls):
|
|
||||||
return cls(cls.UNREACHABLE, timezone.now())
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def reachable(cls):
|
|
||||||
return cls(cls.REACHABLE, timezone.now())
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def unknown(cls):
|
|
||||||
return cls(cls.UNKNOWN, timezone.now())
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set(cls, key, value, ttl=None):
|
|
||||||
cache.set(key, value, ttl)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, key):
|
|
||||||
value = cache.get(key, cls.unknown())
|
|
||||||
if not isinstance(value, cls):
|
|
||||||
value = cls.unknown()
|
|
||||||
return value
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_unreachable(cls, key, ttl=0):
|
|
||||||
cls.set(key, cls.unreachable(), ttl)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_reachable(cls, key, ttl=0):
|
|
||||||
cls.set(key, cls.reachable(), ttl)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.status == other.status
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self.status > other.status
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
return not self.__gt__(other)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.display()
|
|
||||||
|
|
|
@ -8,6 +8,6 @@ from .system_user import *
|
||||||
from .node import *
|
from .node import *
|
||||||
from .domain import *
|
from .domain import *
|
||||||
from .cmd_filter import *
|
from .cmd_filter import *
|
||||||
from .asset_user import *
|
|
||||||
from .gathered_user import *
|
from .gathered_user import *
|
||||||
from .favorite_asset import *
|
from .favorite_asset import *
|
||||||
|
from .account import *
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from assets.models import AuthBook
|
||||||
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
|
from .base import AuthSerializerMixin
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
|
ip = serializers.ReadOnlyField(label=_("IP"))
|
||||||
|
hostname = serializers.ReadOnlyField(label=_("Hostname"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AuthBook
|
||||||
|
fields_mini = ['id', 'username', 'ip', 'hostname', 'version']
|
||||||
|
fields_write_only = ['password', 'private_key', "public_key"]
|
||||||
|
fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment']
|
||||||
|
fields_small = fields_mini + fields_write_only + fields_other
|
||||||
|
fields_fk = ['asset', 'systemuser']
|
||||||
|
fields = fields_small + fields_fk
|
||||||
|
extra_kwargs = {
|
||||||
|
'username': {'required': True},
|
||||||
|
'password': {'write_only': True},
|
||||||
|
'private_key': {'write_only': True},
|
||||||
|
'public_key': {'write_only': True},
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_eager_loading(cls, queryset):
|
||||||
|
""" Perform necessary eager loading of data. """
|
||||||
|
queryset = queryset.prefetch_related('systemuser', 'asset')
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSecretSerializer(AccountSerializer):
|
||||||
|
class Meta(AccountSerializer.Meta):
|
||||||
|
extra_kwargs = {
|
||||||
|
'password': {'write_only': False},
|
||||||
|
'private_key': {'write_only': False},
|
||||||
|
'public_key': {'write_only': False},
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from ..models import Node, AdminUser
|
from ..models import SystemUser
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
from .base import AuthSerializer, AuthSerializerMixin
|
from .base import AuthSerializerMixin
|
||||||
|
|
||||||
|
|
||||||
class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
|
@ -15,7 +14,7 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdminUser
|
model = SystemUser
|
||||||
fields_mini = ['id', 'name', 'username']
|
fields_mini = ['id', 'name', 'username']
|
||||||
fields_write_only = ['password', 'private_key', 'public_key']
|
fields_write_only = ['password', 'private_key', 'public_key']
|
||||||
fields_small = fields_mini + fields_write_only + [
|
fields_small = fields_mini + fields_write_only + [
|
||||||
|
@ -34,39 +33,8 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
'assets_amount': {'label': _('Asset')},
|
'assets_amount': {'label': _('Asset')},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
class AdminUserDetailSerializer(AdminUserSerializer):
|
data = {k: v for k, v in validated_data.items()}
|
||||||
class Meta(AdminUserSerializer.Meta):
|
data['protocol'] = 'ssh'
|
||||||
fields = AdminUserSerializer.Meta.fields + ['ssh_key_fingerprint']
|
data['type'] = SystemUser.Type.admin
|
||||||
|
return super().create(data)
|
||||||
|
|
||||||
class AdminUserAuthSerializer(AuthSerializer):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AdminUser
|
|
||||||
fields = ['password', 'private_key']
|
|
||||||
|
|
||||||
|
|
||||||
class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
|
||||||
"""
|
|
||||||
管理用户更新关联到的集群
|
|
||||||
"""
|
|
||||||
nodes = serializers.PrimaryKeyRelatedField(
|
|
||||||
many=True, queryset=Node.objects
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AdminUser
|
|
||||||
fields = ['id', 'nodes']
|
|
||||||
|
|
||||||
|
|
||||||
class TaskIDSerializer(serializers.Serializer):
|
|
||||||
task = serializers.CharField(read_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserTaskSerializer(serializers.Serializer):
|
|
||||||
ACTION_CHOICES = (
|
|
||||||
('test', 'test'),
|
|
||||||
)
|
|
||||||
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
|
||||||
task = serializers.CharField(read_only=True)
|
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.db.models import F
|
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from ..models import Asset, Node, Platform
|
from ..models import Asset, Node, Platform, SystemUser
|
||||||
from .base import ConnectivitySerializer
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetSerializer', 'AssetSimpleSerializer',
|
'AssetSerializer', 'AssetSimpleSerializer', 'AssetVerboseSerializer',
|
||||||
'AssetDisplaySerializer',
|
|
||||||
'ProtocolsField', 'PlatformSerializer',
|
'ProtocolsField', 'PlatformSerializer',
|
||||||
'AssetDetailSerializer', 'AssetTaskSerializer',
|
'AssetTaskSerializer',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ProtocolField(serializers.RegexField):
|
class ProtocolField(serializers.RegexField):
|
||||||
protocols = '|'.join(dict(Asset.PROTOCOL_CHOICES).keys())
|
protocols = '|'.join(dict(Asset.Protocol.choices).keys())
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Protocol format should {}/{}'.format(protocols, '1-65535'))
|
'invalid': _('Protocol format should {}/{}'.format(protocols, '1-65535'))
|
||||||
}
|
}
|
||||||
|
@ -65,9 +62,11 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
platform = serializers.SlugRelatedField(
|
platform = serializers.SlugRelatedField(
|
||||||
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
|
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
|
||||||
)
|
)
|
||||||
|
admin_user = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=SystemUser.objects, label=_('Admin user'), write_only=True
|
||||||
|
)
|
||||||
protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22'])
|
protocols = ProtocolsField(label=_('Protocols'), required=False, default=['ssh/22'])
|
||||||
domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name'))
|
domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name'))
|
||||||
admin_user_display = serializers.ReadOnlyField(source='admin_user.name', label=_('Admin user name'))
|
|
||||||
nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes name'), required=False)
|
nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes name'), required=False)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -81,25 +80,18 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||||
'os', 'os_version', 'os_arch', 'hostname_raw', 'comment',
|
'os', 'os_version', 'os_arch', 'hostname_raw', 'comment',
|
||||||
'created_by', 'date_created', 'hardware_info',
|
'hardware_info', 'connectivity', 'date_verified'
|
||||||
]
|
]
|
||||||
fields_fk = [
|
fields_fk = [
|
||||||
'admin_user', 'admin_user_display', 'domain', 'domain_display', 'platform'
|
'domain', 'domain_display', 'platform', 'admin_user'
|
||||||
]
|
]
|
||||||
fk_only_fields = {
|
|
||||||
'platform': ['name']
|
|
||||||
}
|
|
||||||
fields_m2m = [
|
fields_m2m = [
|
||||||
'nodes', 'nodes_display', 'labels',
|
'nodes', 'nodes_display', 'labels',
|
||||||
]
|
]
|
||||||
annotates_fields = {
|
|
||||||
# 'admin_user_display': 'admin_user__name'
|
|
||||||
}
|
|
||||||
fields_as = list(annotates_fields.keys())
|
|
||||||
fields = fields_small + fields_fk + fields_m2m + fields_as
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'created_by', 'date_created',
|
'created_by', 'date_created',
|
||||||
] + fields_as
|
]
|
||||||
|
fields = fields_small + fields_fk + fields_m2m + read_only_fields
|
||||||
|
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'protocol': {'write_only': True},
|
'protocol': {'write_only': True},
|
||||||
|
@ -108,10 +100,19 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
'org_name': {'label': _('Org name')}
|
'org_name': {'label': _('Org name')}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_fields(self):
|
||||||
|
fields = super().get_fields()
|
||||||
|
|
||||||
|
admin_user_field = fields.get('admin_user')
|
||||||
|
# 因为 mixin 中对 fields 有处理,可能不需要返回 admin_user
|
||||||
|
if admin_user_field:
|
||||||
|
admin_user_field.queryset = SystemUser.objects.filter(type=SystemUser.Type.admin)
|
||||||
|
return fields
|
||||||
|
|
||||||
@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. """
|
||||||
queryset = queryset.prefetch_related('admin_user', 'domain', 'platform')
|
queryset = queryset.prefetch_related('domain', 'platform')
|
||||||
queryset = queryset.prefetch_related('nodes', 'labels')
|
queryset = queryset.prefetch_related('nodes', 'labels')
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -146,25 +147,26 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
self.compatible_with_old_protocol(validated_data)
|
self.compatible_with_old_protocol(validated_data)
|
||||||
nodes_display = validated_data.pop('nodes_display', '')
|
nodes_display = validated_data.pop('nodes_display', '')
|
||||||
|
admin_user = validated_data.pop('admin_user', '')
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
self.perform_nodes_display_create(instance, nodes_display)
|
self.perform_nodes_display_create(instance, nodes_display)
|
||||||
|
instance.admin_user = admin_user
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
nodes_display = validated_data.pop('nodes_display', '')
|
nodes_display = validated_data.pop('nodes_display', '')
|
||||||
self.compatible_with_old_protocol(validated_data)
|
self.compatible_with_old_protocol(validated_data)
|
||||||
|
admin_user = validated_data.pop('admin_user', '')
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
self.perform_nodes_display_create(instance, nodes_display)
|
self.perform_nodes_display_create(instance, nodes_display)
|
||||||
|
instance.admin_user = admin_user
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class AssetDisplaySerializer(AssetSerializer):
|
class AssetVerboseSerializer(AssetSerializer):
|
||||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
admin_user = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=SystemUser.objects, label=_('Admin user')
|
||||||
class Meta(AssetSerializer.Meta):
|
)
|
||||||
fields = AssetSerializer.Meta.fields + [
|
|
||||||
'connectivity',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class PlatformSerializer(serializers.ModelSerializer):
|
class PlatformSerializer(serializers.ModelSerializer):
|
||||||
|
@ -186,16 +188,11 @@ class PlatformSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AssetDetailSerializer(AssetSerializer):
|
|
||||||
platform = PlatformSerializer(read_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetSimpleSerializer(serializers.ModelSerializer):
|
class AssetSimpleSerializer(serializers.ModelSerializer):
|
||||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = ['id', 'hostname', 'ip', 'connectivity', 'port']
|
fields = ['id', 'hostname', 'ip', 'port', 'connectivity', 'date_verified']
|
||||||
|
|
||||||
|
|
||||||
class AssetTaskSerializer(serializers.Serializer):
|
class AssetTaskSerializer(serializers.Serializer):
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
|
||||||
from ..models import AuthBook, Asset
|
|
||||||
from ..backends import AssetUserManager
|
|
||||||
|
|
||||||
from .base import ConnectivitySerializer, AuthSerializerMixin
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'AssetUserWriteSerializer', 'AssetUserReadSerializer',
|
|
||||||
'AssetUserAuthInfoSerializer', 'AssetUserPushSerializer',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserWriteSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = AuthBook
|
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
fields_mini = ['id', 'username']
|
|
||||||
fields_write_only = ['password', 'private_key', "public_key"]
|
|
||||||
fields_small = fields_mini + fields_write_only + ['comment']
|
|
||||||
fields_fk = ['asset']
|
|
||||||
fields = fields_small + fields_fk
|
|
||||||
extra_kwargs = {
|
|
||||||
'username': {'required': True},
|
|
||||||
'password': {'write_only': True},
|
|
||||||
'private_key': {'write_only': True},
|
|
||||||
'public_key': {'write_only': True},
|
|
||||||
}
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
if not validated_data.get("name") and validated_data.get("username"):
|
|
||||||
validated_data["name"] = validated_data["username"]
|
|
||||||
instance = AssetUserManager.create(**validated_data)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserReadSerializer(AssetUserWriteSerializer):
|
|
||||||
id = serializers.CharField(read_only=True, source='union_id', label=_("ID"))
|
|
||||||
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
|
|
||||||
ip = serializers.CharField(read_only=True, label=_("IP"))
|
|
||||||
asset = serializers.CharField(source='asset_id', label=_('Asset'))
|
|
||||||
backend = serializers.CharField(read_only=True, label=_("Backend"))
|
|
||||||
backend_display = serializers.CharField(read_only=True, label=_("Source"))
|
|
||||||
|
|
||||||
class Meta(AssetUserWriteSerializer.Meta):
|
|
||||||
read_only_fields = (
|
|
||||||
'date_created', 'date_updated',
|
|
||||||
'created_by', 'version',
|
|
||||||
)
|
|
||||||
fields_mini = ['id', 'name', 'username']
|
|
||||||
fields_write_only = ['password', 'private_key', "public_key"]
|
|
||||||
fields_small = fields_mini + fields_write_only + [
|
|
||||||
'backend', 'backend_display', 'version',
|
|
||||||
'date_created', "date_updated",
|
|
||||||
'comment'
|
|
||||||
]
|
|
||||||
fields_fk = ['asset', 'hostname', 'ip']
|
|
||||||
fields = fields_small + fields_fk
|
|
||||||
extra_kwargs = {
|
|
||||||
'name': {'required': False},
|
|
||||||
'username': {'required': True},
|
|
||||||
'password': {'write_only': True},
|
|
||||||
'private_key': {'write_only': True},
|
|
||||||
'public_key': {'write_only': True},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserAuthInfoSerializer(AssetUserReadSerializer):
|
|
||||||
password = serializers.CharField(
|
|
||||||
max_length=256, allow_blank=True, allow_null=True,
|
|
||||||
required=False, label=_('Password')
|
|
||||||
)
|
|
||||||
public_key = serializers.CharField(
|
|
||||||
max_length=4096, allow_blank=True, allow_null=True,
|
|
||||||
required=False, label=_('Public key')
|
|
||||||
)
|
|
||||||
private_key = serializers.CharField(
|
|
||||||
max_length=4096, allow_blank=True, allow_null=True,
|
|
||||||
required=False, label=_('Private key')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetUserPushSerializer(serializers.Serializer):
|
|
||||||
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, label=_("Asset"))
|
|
||||||
username = serializers.CharField(max_length=1024)
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
pass
|
|
|
@ -5,7 +5,6 @@ from django.utils.translation import ugettext as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.utils import ssh_pubkey_gen, validate_ssh_private_key
|
from common.utils import ssh_pubkey_gen, validate_ssh_private_key
|
||||||
from ..models import AssetUser
|
|
||||||
|
|
||||||
|
|
||||||
class AuthSerializer(serializers.ModelSerializer):
|
class AuthSerializer(serializers.ModelSerializer):
|
||||||
|
@ -29,11 +28,6 @@ class AuthSerializer(serializers.ModelSerializer):
|
||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
class ConnectivitySerializer(serializers.Serializer):
|
|
||||||
status = serializers.IntegerField()
|
|
||||||
datetime = serializers.DateTimeField()
|
|
||||||
|
|
||||||
|
|
||||||
class AuthSerializerMixin:
|
class AuthSerializerMixin:
|
||||||
def validate_password(self, password):
|
def validate_password(self, password):
|
||||||
return password
|
return password
|
||||||
|
@ -64,15 +58,3 @@ class AuthSerializerMixin:
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
self.clean_auth_fields(validated_data)
|
self.clean_auth_fields(validated_data)
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class AuthInfoSerializer(serializers.ModelSerializer):
|
|
||||||
private_key = serializers.ReadOnlyField(source='get_private_key')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = AssetUser
|
|
||||||
fields = [
|
|
||||||
'username', 'password',
|
|
||||||
'private_key', 'public_key',
|
|
||||||
'date_updated',
|
|
||||||
]
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import re
|
import re
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from ..models import CommandFilter, CommandFilterRule
|
from ..models import CommandFilter, CommandFilterRule
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org
|
||||||
|
@ -15,7 +14,6 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CommandFilter
|
model = CommandFilter
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
fields_mini = ['id', 'name']
|
fields_mini = ['id', 'name']
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'org_id', 'org_name',
|
'org_id', 'org_name',
|
||||||
|
@ -48,7 +46,6 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
||||||
]
|
]
|
||||||
fields_fk = ['filter']
|
fields_fk = ['filter']
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from common.validators import NoSpecialChars
|
from common.validators import NoSpecialChars
|
||||||
from ..models import Domain, Gateway
|
from ..models import Domain, Gateway
|
||||||
|
@ -29,7 +28,6 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'assets': {'required': False, 'label': _('Assets')},
|
'assets': {'required': False, 'label': _('Assets')},
|
||||||
}
|
}
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_asset_count(obj):
|
def get_asset_count(obj):
|
||||||
|
@ -47,7 +45,6 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
|
||||||
class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Gateway
|
model = Gateway
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
fields_mini = ['id', 'name']
|
fields_mini = ['id', 'name']
|
||||||
fields_write_only = [
|
fields_write_only = [
|
||||||
'password', 'private_key', 'public_key',
|
'password', 'private_key', 'public_key',
|
||||||
|
@ -66,16 +63,6 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
'public_key': {"write_only": True},
|
'public_key': {"write_only": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.protocol_limit_to_ssh()
|
|
||||||
|
|
||||||
def protocol_limit_to_ssh(self):
|
|
||||||
protocol_field = self.fields['protocol']
|
|
||||||
choices = protocol_field.choices
|
|
||||||
choices.pop('rdp')
|
|
||||||
protocol_field._choices = choices
|
|
||||||
|
|
||||||
|
|
||||||
class GatewayWithAuthSerializer(GatewaySerializer):
|
class GatewayWithAuthSerializer(GatewaySerializer):
|
||||||
class Meta(GatewaySerializer.Meta):
|
class Meta(GatewaySerializer.Meta):
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from ..models import FavoriteAsset
|
from ..models import FavoriteAsset
|
||||||
|
|
||||||
|
@ -18,6 +17,5 @@ class FavoriteAssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
model = FavoriteAsset
|
model = FavoriteAsset
|
||||||
fields = ['user', 'asset']
|
fields = ['user', 'asset']
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
from ..models import Label
|
from ..models import Label
|
||||||
|
@ -30,7 +29,6 @@ class LabelSerializer(BulkOrgResourceModelSerializer):
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'assets': {'required': False}
|
'assets': {'required': False}
|
||||||
}
|
}
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_asset_count(obj):
|
def get_asset_count(obj):
|
||||||
|
|
|
@ -2,7 +2,6 @@ from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
|
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from common.mixins.serializers import BulkSerializerMixin
|
from common.mixins.serializers import BulkSerializerMixin
|
||||||
from common.utils import ssh_pubkey_gen
|
from common.utils import ssh_pubkey_gen
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
@ -23,15 +22,15 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
系统用户
|
系统用户
|
||||||
"""
|
"""
|
||||||
auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True)
|
auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True)
|
||||||
|
type_display = serializers.ReadOnlyField(source='get_type_display')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
fields_mini = ['id', 'name', 'username']
|
fields_mini = ['id', 'name', 'username']
|
||||||
fields_write_only = ['password', 'public_key', 'private_key']
|
fields_write_only = ['password', 'public_key', 'private_key']
|
||||||
fields_small = fields_mini + fields_write_only + [
|
fields_small = fields_mini + fields_write_only + [
|
||||||
'protocol', 'login_mode', 'login_mode_display', 'priority',
|
'type', 'type_display', 'protocol', 'login_mode', 'login_mode_display',
|
||||||
'sudo', 'shell', 'sftp_root', 'token',
|
'priority', 'sudo', 'shell', 'sftp_root', 'token',
|
||||||
'home', 'system_groups', 'ad_domain',
|
'home', 'system_groups', 'ad_domain',
|
||||||
'username_same_with_user', 'auto_push', 'auto_generate_key',
|
'username_same_with_user', 'auto_push', 'auto_generate_key',
|
||||||
'date_created', 'date_updated',
|
'date_created', 'date_updated',
|
||||||
|
@ -55,9 +54,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
login_mode = self.initial_data.get("login_mode")
|
login_mode = self.initial_data.get("login_mode")
|
||||||
protocol = self.initial_data.get("protocol")
|
protocol = self.initial_data.get("protocol")
|
||||||
|
|
||||||
if login_mode == SystemUser.LOGIN_MANUAL or \
|
if login_mode == SystemUser.LOGIN_MANUAL:
|
||||||
protocol in [SystemUser.PROTOCOL_TELNET,
|
value = False
|
||||||
SystemUser.PROTOCOL_VNC]:
|
elif protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS:
|
||||||
value = False
|
value = False
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
value = False
|
value = False
|
||||||
elif login_mode == SystemUser.LOGIN_MANUAL:
|
elif login_mode == SystemUser.LOGIN_MANUAL:
|
||||||
value = False
|
value = False
|
||||||
elif protocol in [SystemUser.PROTOCOL_TELNET, SystemUser.PROTOCOL_VNC]:
|
elif protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS:
|
||||||
value = False
|
value = False
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -80,7 +79,8 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
return username_same_with_user
|
return username_same_with_user
|
||||||
protocol = self.initial_data.get("protocol", "ssh")
|
protocol = self.initial_data.get("protocol", "ssh")
|
||||||
queryset = SystemUser.objects.filter(
|
queryset = SystemUser.objects.filter(
|
||||||
protocol=protocol, username_same_with_user=True
|
protocol=protocol,
|
||||||
|
username_same_with_user=True
|
||||||
)
|
)
|
||||||
if self.instance:
|
if self.instance:
|
||||||
queryset = queryset.exclude(id=self.instance.id)
|
queryset = queryset.exclude(id=self.instance.id)
|
||||||
|
@ -96,12 +96,14 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
login_mode = self.initial_data.get("login_mode")
|
login_mode = self.initial_data.get("login_mode")
|
||||||
protocol = self.initial_data.get("protocol")
|
protocol = self.initial_data.get("protocol")
|
||||||
username_same_with_user = self.initial_data.get("username_same_with_user")
|
username_same_with_user = self.initial_data.get("username_same_with_user")
|
||||||
if username_same_with_user:
|
|
||||||
return ''
|
|
||||||
if login_mode == SystemUser.LOGIN_AUTO and \
|
if login_mode == SystemUser.LOGIN_AUTO and \
|
||||||
protocol != SystemUser.PROTOCOL_VNC:
|
protocol != SystemUser.Protocol.vnc:
|
||||||
msg = _('* Automatic login mode must fill in the username.')
|
msg = _('* Automatic login mode must fill in the username.')
|
||||||
raise serializers.ValidationError(msg)
|
raise serializers.ValidationError(msg)
|
||||||
|
|
||||||
|
if username_same_with_user:
|
||||||
|
username = '*'
|
||||||
return username
|
return username
|
||||||
|
|
||||||
def validate_home(self, home):
|
def validate_home(self, home):
|
||||||
|
@ -118,28 +120,41 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
raise serializers.ValidationError(error)
|
raise serializers.ValidationError(error)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_admin_user(attrs):
|
||||||
|
tp = attrs.get('type')
|
||||||
|
if tp != SystemUser.Type.admin:
|
||||||
|
return attrs
|
||||||
|
attrs['protocol'] = SystemUser.Protocol.ssh
|
||||||
|
attrs['login_mode'] = SystemUser.LOGIN_AUTO
|
||||||
|
attrs['username_same_with_user'] = False
|
||||||
|
attrs['auto_push'] = False
|
||||||
|
return attrs
|
||||||
|
|
||||||
def validate_password(self, password):
|
def validate_password(self, password):
|
||||||
super().validate_password(password)
|
super().validate_password(password)
|
||||||
auto_gen_key = self.initial_data.get("auto_generate_key", False)
|
auto_gen_key = self.initial_data.get("auto_generate_key", False)
|
||||||
private_key = self.initial_data.get("private_key")
|
private_key = self.initial_data.get("private_key")
|
||||||
login_mode = self.initial_data.get("login_mode")
|
login_mode = self.initial_data.get("login_mode")
|
||||||
|
|
||||||
if not self.instance and not auto_gen_key and not password and \
|
if not self.instance and not auto_gen_key and not password and \
|
||||||
not private_key and login_mode == SystemUser.LOGIN_AUTO:
|
not private_key and login_mode == SystemUser.LOGIN_AUTO:
|
||||||
raise serializers.ValidationError(_("Password or private key required"))
|
raise serializers.ValidationError(_("Password or private key required"))
|
||||||
return password
|
return password
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate_gen_key(self, attrs):
|
||||||
username = attrs.get("username", "manual")
|
username = attrs.get("username", "manual")
|
||||||
auto_gen_key = attrs.pop("auto_generate_key", False)
|
auto_gen_key = attrs.pop("auto_generate_key", False)
|
||||||
protocol = attrs.get("protocol")
|
protocol = attrs.get("protocol")
|
||||||
|
|
||||||
if protocol not in [SystemUser.PROTOCOL_RDP, SystemUser.PROTOCOL_SSH]:
|
if protocol not in SystemUser.SUPPORT_PUSH_PROTOCOLS:
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
if auto_gen_key:
|
# 自动生成
|
||||||
|
if auto_gen_key and not self.instance:
|
||||||
password = SystemUser.gen_password()
|
password = SystemUser.gen_password()
|
||||||
attrs["password"] = password
|
attrs["password"] = password
|
||||||
if protocol == SystemUser.PROTOCOL_SSH:
|
if protocol == SystemUser.Protocol.ssh:
|
||||||
private_key, public_key = SystemUser.gen_key(username)
|
private_key, public_key = SystemUser.gen_key(username)
|
||||||
attrs["private_key"] = private_key
|
attrs["private_key"] = private_key
|
||||||
attrs["public_key"] = public_key
|
attrs["public_key"] = public_key
|
||||||
|
@ -147,11 +162,15 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
elif attrs.get("private_key", None):
|
elif attrs.get("private_key", None):
|
||||||
private_key = attrs["private_key"]
|
private_key = attrs["private_key"]
|
||||||
password = attrs.get("password")
|
password = attrs.get("password")
|
||||||
public_key = ssh_pubkey_gen(private_key, password=password,
|
public_key = ssh_pubkey_gen(private_key, password=password, username=username)
|
||||||
username=username)
|
|
||||||
attrs["public_key"] = public_key
|
attrs["public_key"] = public_key
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
attrs = self.validate_admin_user(attrs)
|
||||||
|
attrs = self.validate_gen_key(attrs)
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class SystemUserListSerializer(SystemUserSerializer):
|
class SystemUserListSerializer(SystemUserSerializer):
|
||||||
|
|
||||||
|
@ -222,24 +241,26 @@ class RelationMixin(BulkSerializerMixin, serializers.Serializer):
|
||||||
fields.extend(['systemuser', "systemuser_display"])
|
fields.extend(['systemuser', "systemuser_display"])
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
class Meta:
|
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
asset_display = serializers.ReadOnlyField()
|
asset_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = SystemUser.assets.through
|
model = SystemUser.assets.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', "asset", "asset_display",
|
"id", "asset", "asset_display",
|
||||||
|
'systemuser', 'systemuser_display'
|
||||||
]
|
]
|
||||||
|
use_model_bulk_create = True
|
||||||
|
model_bulk_create_kwargs = {
|
||||||
|
'ignore_conflicts': True
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
node_display = serializers.SerializerMethodField()
|
node_display = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = SystemUser.nodes.through
|
model = SystemUser.nodes.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'node', "node_display",
|
'id', 'node', "node_display",
|
||||||
|
@ -252,7 +273,7 @@ class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerialize
|
||||||
class SystemUserUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class SystemUserUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
user_display = serializers.ReadOnlyField()
|
user_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = SystemUser.users.through
|
model = SystemUser.users.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', "user", "user_display",
|
'id', "user", "user_display",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
from .common import *
|
from .asset import *
|
||||||
|
from .system_user import *
|
||||||
|
from .authbook import *
|
||||||
from .node_assets_amount import *
|
from .node_assets_amount import *
|
||||||
from .node_assets_mapping import *
|
from .node_assets_mapping import *
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.db.models.signals import (
|
||||||
|
post_save, m2m_changed, pre_delete, post_delete, pre_save
|
||||||
|
)
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from common.const.signals import POST_ADD, POST_REMOVE, PRE_REMOVE
|
||||||
|
from common.utils import get_logger
|
||||||
|
from common.decorator import on_transaction_commit
|
||||||
|
from assets.models import Asset, SystemUser, Node
|
||||||
|
from assets.tasks import (
|
||||||
|
update_assets_hardware_info_util,
|
||||||
|
test_asset_connectivity_util,
|
||||||
|
push_system_user_to_assets,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def update_asset_hardware_info_on_created(asset):
|
||||||
|
logger.debug("Update asset `{}` hardware info".format(asset))
|
||||||
|
update_assets_hardware_info_util.delay([asset])
|
||||||
|
|
||||||
|
|
||||||
|
def test_asset_conn_on_created(asset):
|
||||||
|
logger.debug("Test asset `{}` connectivity".format(asset))
|
||||||
|
test_asset_connectivity_util.delay([asset])
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=Node)
|
||||||
|
def on_node_pre_save(sender, instance: Node, **kwargs):
|
||||||
|
instance.parent_key = instance.compute_parent_key()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Asset)
|
||||||
|
@on_transaction_commit
|
||||||
|
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||||
|
"""
|
||||||
|
当资产创建时,更新硬件信息,更新可连接性
|
||||||
|
确保资产必须属于一个节点
|
||||||
|
"""
|
||||||
|
if created:
|
||||||
|
logger.info("Asset create signal recv: {}".format(instance))
|
||||||
|
|
||||||
|
# 获取资产硬件信息
|
||||||
|
update_asset_hardware_info_on_created(instance)
|
||||||
|
test_asset_conn_on_created(instance)
|
||||||
|
|
||||||
|
# 确保资产存在一个节点
|
||||||
|
has_node = instance.nodes.all().exists()
|
||||||
|
if not has_node:
|
||||||
|
instance.nodes.add(Node.org_root())
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
|
def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs):
|
||||||
|
"""
|
||||||
|
本操作共访问 4 次数据库
|
||||||
|
|
||||||
|
当资产的节点发生变化时,或者 当节点的资产关系发生变化时,
|
||||||
|
节点下新增的资产,添加到节点关联的系统用户中
|
||||||
|
"""
|
||||||
|
if action != POST_ADD:
|
||||||
|
return
|
||||||
|
logger.debug("Assets node add signal recv: {}".format(action))
|
||||||
|
if reverse:
|
||||||
|
nodes = [instance.key]
|
||||||
|
asset_ids = pk_set
|
||||||
|
else:
|
||||||
|
nodes = Node.objects.filter(pk__in=pk_set).values_list('key', flat=True)
|
||||||
|
asset_ids = [instance.id]
|
||||||
|
|
||||||
|
# 节点资产发生变化时,将资产关联到节点及祖先节点关联的系统用户, 只关注新增的
|
||||||
|
nodes_ancestors_keys = set()
|
||||||
|
for node in nodes:
|
||||||
|
nodes_ancestors_keys.update(Node.get_node_ancestor_keys(node, with_self=True))
|
||||||
|
|
||||||
|
# 查询所有祖先节点关联的系统用户,都是要跟资产建立关系的
|
||||||
|
system_user_ids = SystemUser.objects.filter(
|
||||||
|
nodes__key__in=nodes_ancestors_keys
|
||||||
|
).distinct().values_list('id', flat=True)
|
||||||
|
|
||||||
|
# 查询所有已存在的关系
|
||||||
|
m2m_model = SystemUser.assets.through
|
||||||
|
exist = set(m2m_model.objects.filter(
|
||||||
|
system_user_id__in=system_user_ids, asset_id__in=asset_ids
|
||||||
|
).values_list('system_user_id', 'asset_id'))
|
||||||
|
# TODO 优化
|
||||||
|
to_create = []
|
||||||
|
for system_user_id in system_user_ids:
|
||||||
|
asset_ids_to_push = []
|
||||||
|
for asset_id in asset_ids:
|
||||||
|
if (system_user_id, asset_id) in exist:
|
||||||
|
continue
|
||||||
|
asset_ids_to_push.append(asset_id)
|
||||||
|
to_create.append(m2m_model(
|
||||||
|
system_user_id=system_user_id,
|
||||||
|
asset_id=asset_id
|
||||||
|
))
|
||||||
|
if asset_ids_to_push:
|
||||||
|
push_system_user_to_assets.delay(system_user_id, asset_ids_to_push)
|
||||||
|
m2m_model.objects.bulk_create(to_create)
|
||||||
|
|
||||||
|
|
||||||
|
RELATED_NODE_IDS = '_related_node_ids'
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_delete, sender=Asset)
|
||||||
|
def on_asset_delete(instance: Asset, using, **kwargs):
|
||||||
|
node_ids = set(Node.objects.filter(
|
||||||
|
assets=instance
|
||||||
|
).distinct().values_list('id', flat=True))
|
||||||
|
setattr(instance, RELATED_NODE_IDS, node_ids)
|
||||||
|
m2m_changed.send(
|
||||||
|
sender=Asset.nodes.through, instance=instance, reverse=False,
|
||||||
|
model=Node, pk_set=node_ids, using=using, action=PRE_REMOVE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=Asset)
|
||||||
|
def on_asset_post_delete(instance: Asset, using, **kwargs):
|
||||||
|
node_ids = getattr(instance, RELATED_NODE_IDS, None)
|
||||||
|
if node_ids:
|
||||||
|
m2m_changed.send(
|
||||||
|
sender=Asset.nodes.through, instance=instance, reverse=False,
|
||||||
|
model=Node, pk_set=node_ids, using=using, action=POST_REMOVE
|
||||||
|
)
|
|
@ -0,0 +1,46 @@
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django.apps import apps
|
||||||
|
from simple_history.signals import pre_create_historical_record
|
||||||
|
from django.db.models.signals import post_save, pre_save
|
||||||
|
|
||||||
|
from common.utils import get_logger
|
||||||
|
from orgs.utils import tmp_to_root_org
|
||||||
|
from ..models import AuthBook, SystemUser
|
||||||
|
|
||||||
|
AuthBookHistory = apps.get_model('assets', 'HistoricalAuthBook')
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_create_historical_record, sender=AuthBookHistory)
|
||||||
|
def pre_create_historical_record_callback(sender, instance=None, history_instance=None, **kwargs):
|
||||||
|
attrs_to_copy = ['username', 'password', 'private_key']
|
||||||
|
|
||||||
|
for attr in attrs_to_copy:
|
||||||
|
if getattr(history_instance, attr):
|
||||||
|
continue
|
||||||
|
if not history_instance.systemuser:
|
||||||
|
continue
|
||||||
|
system_user_attr_value = getattr(history_instance.systemuser, attr)
|
||||||
|
if system_user_attr_value:
|
||||||
|
setattr(history_instance, attr, system_user_attr_value)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=AuthBook)
|
||||||
|
def on_authbook_post_create(sender, instance, **kwargs):
|
||||||
|
# 去掉这个资产的管理用户
|
||||||
|
if instance.systemuser and instance.systemuser.is_admin_user:
|
||||||
|
with tmp_to_root_org():
|
||||||
|
deleted_count, other = AuthBook.objects.filter(
|
||||||
|
asset=instance.asset,
|
||||||
|
systemuser__type=SystemUser.Type.admin
|
||||||
|
).exclude(id=instance.id).delete()
|
||||||
|
logger.debug('Remove asset old admin user: {}'.format(deleted_count))
|
||||||
|
|
||||||
|
if not instance.systemuser:
|
||||||
|
instance.sync_to_system_user_account()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=AuthBook)
|
||||||
|
def on_authbook_pre_create(sender, instance, **kwargs):
|
||||||
|
# 升级版本号
|
||||||
|
instance.version = instance.history.all().count() + 1
|
|
@ -1,223 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
from django.db.models.signals import (
|
|
||||||
post_save, m2m_changed, pre_delete, post_delete, pre_save
|
|
||||||
)
|
|
||||||
from django.dispatch import receiver
|
|
||||||
|
|
||||||
from common.exceptions import M2MReverseNotAllowed
|
|
||||||
from common.const.signals import POST_ADD, POST_REMOVE, PRE_REMOVE
|
|
||||||
from common.utils import get_logger
|
|
||||||
from common.decorator import on_transaction_commit
|
|
||||||
from assets.models import Asset, SystemUser, Node
|
|
||||||
from users.models import User
|
|
||||||
from assets.tasks import (
|
|
||||||
update_assets_hardware_info_util,
|
|
||||||
test_asset_connectivity_util,
|
|
||||||
push_system_user_to_assets_manual,
|
|
||||||
push_system_user_to_assets,
|
|
||||||
add_nodes_assets_to_system_users
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
def update_asset_hardware_info_on_created(asset):
|
|
||||||
logger.debug("Update asset `{}` hardware info".format(asset))
|
|
||||||
update_assets_hardware_info_util.delay([asset])
|
|
||||||
|
|
||||||
|
|
||||||
def test_asset_conn_on_created(asset):
|
|
||||||
logger.debug("Test asset `{}` connectivity".format(asset))
|
|
||||||
test_asset_connectivity_util.delay([asset])
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Node)
|
|
||||||
def on_node_pre_save(sender, instance: Node, **kwargs):
|
|
||||||
instance.parent_key = instance.compute_parent_key()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Asset)
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
|
||||||
"""
|
|
||||||
当资产创建时,更新硬件信息,更新可连接性
|
|
||||||
确保资产必须属于一个节点
|
|
||||||
"""
|
|
||||||
if created:
|
|
||||||
logger.info("Asset create signal recv: {}".format(instance))
|
|
||||||
|
|
||||||
# 获取资产硬件信息
|
|
||||||
update_asset_hardware_info_on_created(instance)
|
|
||||||
test_asset_conn_on_created(instance)
|
|
||||||
|
|
||||||
# 确保资产存在一个节点
|
|
||||||
has_node = instance.nodes.all().exists()
|
|
||||||
if not has_node:
|
|
||||||
instance.nodes.add(Node.org_root())
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=SystemUser, dispatch_uid="jms")
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_update(instance: SystemUser, created, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上,
|
|
||||||
其实应该当 用户名,密码,秘钥 sudo等更新时再推送,这里偷个懒,
|
|
||||||
这里直接取了 instance.assets 因为nodes和系统用户发生变化时,会自动将nodes下的资产
|
|
||||||
关联到上面
|
|
||||||
"""
|
|
||||||
if instance and not created:
|
|
||||||
logger.info("System user update signal recv: {}".format(instance))
|
|
||||||
assets = instance.assets.all().valid()
|
|
||||||
push_system_user_to_assets.delay(instance.id, [_asset.id for _asset in assets])
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.assets.through)
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_assets_change(instance, action, model, pk_set, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
logger.debug("System user assets change signal recv: {}".format(instance))
|
|
||||||
if model == Asset:
|
|
||||||
system_user_ids = [instance.id]
|
|
||||||
asset_ids = pk_set
|
|
||||||
else:
|
|
||||||
system_user_ids = pk_set
|
|
||||||
asset_ids = [instance.id]
|
|
||||||
for system_user_id in system_user_ids:
|
|
||||||
push_system_user_to_assets.delay(system_user_id, asset_ids)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.users.through)
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_users_change(sender, instance: SystemUser, action, model, pk_set, reverse, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和用户关系发生变化时,应该重新推送系统用户资产中
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
|
|
||||||
if reverse:
|
|
||||||
raise M2MReverseNotAllowed
|
|
||||||
|
|
||||||
if not instance.username_same_with_user:
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.debug("System user users change signal recv: {}".format(instance))
|
|
||||||
usernames = model.objects.filter(pk__in=pk_set).values_list('username', flat=True)
|
|
||||||
|
|
||||||
for username in usernames:
|
|
||||||
push_system_user_to_assets_manual.delay(instance, username)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
|
||||||
@on_transaction_commit
|
|
||||||
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
logger.info("System user nodes update signal recv: {}".format(instance))
|
|
||||||
|
|
||||||
queryset = model.objects.filter(pk__in=pk_set)
|
|
||||||
if model == Node:
|
|
||||||
nodes_keys = queryset.values_list('key', flat=True)
|
|
||||||
system_users = [instance]
|
|
||||||
else:
|
|
||||||
nodes_keys = [instance.key]
|
|
||||||
system_users = queryset
|
|
||||||
add_nodes_assets_to_system_users.delay(nodes_keys, system_users)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=SystemUser.groups.through)
|
|
||||||
def on_system_user_groups_change(instance, action, pk_set, reverse, **kwargs):
|
|
||||||
"""
|
|
||||||
当系统用户和用户组关系发生变化时,应该将组下用户关联到新的系统用户上
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
if reverse:
|
|
||||||
raise M2MReverseNotAllowed
|
|
||||||
logger.info("System user groups update signal recv: {}".format(instance))
|
|
||||||
|
|
||||||
users = User.objects.filter(groups__id__in=pk_set).distinct()
|
|
||||||
instance.users.add(*users)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
|
||||||
def on_asset_nodes_add(instance, action, reverse, pk_set, **kwargs):
|
|
||||||
"""
|
|
||||||
本操作共访问 4 次数据库
|
|
||||||
|
|
||||||
当资产的节点发生变化时,或者 当节点的资产关系发生变化时,
|
|
||||||
节点下新增的资产,添加到节点关联的系统用户中
|
|
||||||
"""
|
|
||||||
if action != POST_ADD:
|
|
||||||
return
|
|
||||||
logger.debug("Assets node add signal recv: {}".format(action))
|
|
||||||
if reverse:
|
|
||||||
nodes = [instance.key]
|
|
||||||
asset_ids = pk_set
|
|
||||||
else:
|
|
||||||
nodes = Node.objects.filter(pk__in=pk_set).values_list('key', flat=True)
|
|
||||||
asset_ids = [instance.id]
|
|
||||||
|
|
||||||
# 节点资产发生变化时,将资产关联到节点及祖先节点关联的系统用户, 只关注新增的
|
|
||||||
nodes_ancestors_keys = set()
|
|
||||||
for node in nodes:
|
|
||||||
nodes_ancestors_keys.update(Node.get_node_ancestor_keys(node, with_self=True))
|
|
||||||
|
|
||||||
# 查询所有祖先节点关联的系统用户,都是要跟资产建立关系的
|
|
||||||
system_user_ids = SystemUser.objects.filter(
|
|
||||||
nodes__key__in=nodes_ancestors_keys
|
|
||||||
).distinct().values_list('id', flat=True)
|
|
||||||
|
|
||||||
# 查询所有已存在的关系
|
|
||||||
m2m_model = SystemUser.assets.through
|
|
||||||
exist = set(m2m_model.objects.filter(
|
|
||||||
systemuser_id__in=system_user_ids, asset_id__in=asset_ids
|
|
||||||
).values_list('systemuser_id', 'asset_id'))
|
|
||||||
# TODO 优化
|
|
||||||
to_create = []
|
|
||||||
for system_user_id in system_user_ids:
|
|
||||||
asset_ids_to_push = []
|
|
||||||
for asset_id in asset_ids:
|
|
||||||
if (system_user_id, asset_id) in exist:
|
|
||||||
continue
|
|
||||||
asset_ids_to_push.append(asset_id)
|
|
||||||
to_create.append(m2m_model(
|
|
||||||
systemuser_id=system_user_id,
|
|
||||||
asset_id=asset_id
|
|
||||||
))
|
|
||||||
if asset_ids_to_push:
|
|
||||||
push_system_user_to_assets.delay(system_user_id, asset_ids_to_push)
|
|
||||||
m2m_model.objects.bulk_create(to_create)
|
|
||||||
|
|
||||||
|
|
||||||
RELATED_NODE_IDS = '_related_node_ids'
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Asset)
|
|
||||||
def on_asset_delete(instance: Asset, using, **kwargs):
|
|
||||||
node_ids = set(Node.objects.filter(
|
|
||||||
assets=instance
|
|
||||||
).distinct().values_list('id', flat=True))
|
|
||||||
setattr(instance, RELATED_NODE_IDS, node_ids)
|
|
||||||
m2m_changed.send(
|
|
||||||
sender=Asset.nodes.through, instance=instance, reverse=False,
|
|
||||||
model=Node, pk_set=node_ids, using=using, action=PRE_REMOVE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Asset)
|
|
||||||
def on_asset_post_delete(instance: Asset, using, **kwargs):
|
|
||||||
node_ids = getattr(instance, RELATED_NODE_IDS, None)
|
|
||||||
if node_ids:
|
|
||||||
m2m_changed.send(
|
|
||||||
sender=Asset.nodes.through, instance=instance, reverse=False,
|
|
||||||
model=Node, pk_set=node_ids, using=using, action=POST_REMOVE
|
|
||||||
)
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.db.models.signals import (
|
||||||
|
post_save, m2m_changed, pre_save, pre_delete, post_delete
|
||||||
|
)
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from common.exceptions import M2MReverseNotAllowed
|
||||||
|
from common.const.signals import POST_ADD
|
||||||
|
from common.utils import get_logger
|
||||||
|
from common.decorator import on_transaction_commit
|
||||||
|
from assets.models import Asset, SystemUser, Node, AuthBook
|
||||||
|
from users.models import User
|
||||||
|
from orgs.utils import get_current_org, tmp_to_root_org
|
||||||
|
from assets.tasks import (
|
||||||
|
push_system_user_to_assets_manual,
|
||||||
|
push_system_user_to_assets,
|
||||||
|
add_nodes_assets_to_system_users
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=SystemUser.assets.through)
|
||||||
|
@on_transaction_commit
|
||||||
|
def on_system_user_assets_change(instance, action, model, pk_set, **kwargs):
|
||||||
|
"""
|
||||||
|
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
|
||||||
|
"""
|
||||||
|
logger.debug("System user assets change signal recv: {}".format(instance))
|
||||||
|
|
||||||
|
if not instance:
|
||||||
|
logger.debug('No system user found')
|
||||||
|
return
|
||||||
|
|
||||||
|
if model == Asset:
|
||||||
|
system_user_ids = [instance.id]
|
||||||
|
asset_ids = pk_set
|
||||||
|
else:
|
||||||
|
system_user_ids = pk_set
|
||||||
|
asset_ids = [instance.id]
|
||||||
|
|
||||||
|
# 通过 through 创建的没有 org_id
|
||||||
|
current_org_id = get_current_org().id
|
||||||
|
with tmp_to_root_org():
|
||||||
|
authbooks = AuthBook.objects.filter(
|
||||||
|
asset_id__in=asset_ids,
|
||||||
|
systemuser_id__in=system_user_ids
|
||||||
|
)
|
||||||
|
authbooks.update(org_id=current_org_id)
|
||||||
|
|
||||||
|
save_action_mapper = {
|
||||||
|
'pre_add': pre_save,
|
||||||
|
'post_add': post_save,
|
||||||
|
'pre_remove': pre_delete,
|
||||||
|
'post_remove': post_delete
|
||||||
|
}
|
||||||
|
|
||||||
|
for ab in authbooks:
|
||||||
|
ab.org_id = current_org_id
|
||||||
|
|
||||||
|
post_action = save_action_mapper[action]
|
||||||
|
logger.debug('Send AuthBook post save signal: {} -> {}'.format(action, ab.id))
|
||||||
|
post_action.send(sender=AuthBook, instance=ab, created=True)
|
||||||
|
|
||||||
|
if action == 'post_add':
|
||||||
|
for system_user_id in system_user_ids:
|
||||||
|
push_system_user_to_assets.delay(system_user_id, asset_ids)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=SystemUser.users.through)
|
||||||
|
@on_transaction_commit
|
||||||
|
def on_system_user_users_change(sender, instance: SystemUser, action, model, pk_set, reverse, **kwargs):
|
||||||
|
"""
|
||||||
|
当系统用户和用户关系发生变化时,应该重新推送系统用户资产中
|
||||||
|
"""
|
||||||
|
if action != POST_ADD:
|
||||||
|
return
|
||||||
|
|
||||||
|
if reverse:
|
||||||
|
raise M2MReverseNotAllowed
|
||||||
|
|
||||||
|
if not instance.username_same_with_user:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug("System user users change signal recv: {}".format(instance))
|
||||||
|
usernames = model.objects.filter(pk__in=pk_set).values_list('username', flat=True)
|
||||||
|
|
||||||
|
for username in usernames:
|
||||||
|
push_system_user_to_assets_manual.delay(instance, username)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
||||||
|
@on_transaction_commit
|
||||||
|
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
|
||||||
|
"""
|
||||||
|
当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上
|
||||||
|
"""
|
||||||
|
if action != POST_ADD:
|
||||||
|
return
|
||||||
|
logger.info("System user nodes update signal recv: {}".format(instance))
|
||||||
|
|
||||||
|
queryset = model.objects.filter(pk__in=pk_set)
|
||||||
|
if model == Node:
|
||||||
|
nodes_keys = queryset.values_list('key', flat=True)
|
||||||
|
system_users = [instance]
|
||||||
|
else:
|
||||||
|
nodes_keys = [instance.key]
|
||||||
|
system_users = queryset
|
||||||
|
add_nodes_assets_to_system_users.delay(nodes_keys, system_users)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=SystemUser.groups.through)
|
||||||
|
def on_system_user_groups_change(instance, action, pk_set, reverse, **kwargs):
|
||||||
|
"""
|
||||||
|
当系统用户和用户组关系发生变化时,应该将组下用户关联到新的系统用户上
|
||||||
|
"""
|
||||||
|
if action != POST_ADD:
|
||||||
|
return
|
||||||
|
if reverse:
|
||||||
|
raise M2MReverseNotAllowed
|
||||||
|
logger.info("System user groups update signal recv: {}".format(instance))
|
||||||
|
|
||||||
|
users = User.objects.filter(groups__id__in=pk_set).distinct()
|
||||||
|
instance.users.add(*users)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=SystemUser, dispatch_uid="jms")
|
||||||
|
@on_transaction_commit
|
||||||
|
def on_system_user_update(instance: SystemUser, created, **kwargs):
|
||||||
|
"""
|
||||||
|
当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上,
|
||||||
|
其实应该当 用户名,密码,秘钥 sudo等更新时再推送,这里偷个懒,
|
||||||
|
这里直接取了 instance.assets 因为nodes和系统用户发生变化时,会自动将nodes下的资产
|
||||||
|
关联到上面
|
||||||
|
"""
|
||||||
|
if instance and not created:
|
||||||
|
logger.info("System user update signal recv: {}".format(instance))
|
||||||
|
assets = instance.assets.all().valid()
|
||||||
|
push_system_user_to_assets.delay(instance.id, [_asset.id for _asset in assets])
|
|
@ -2,9 +2,8 @@
|
||||||
#
|
#
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from .common import *
|
from .common import *
|
||||||
from .admin_user_connectivity import *
|
|
||||||
from .asset_connectivity import *
|
from .asset_connectivity import *
|
||||||
from .asset_user_connectivity import *
|
from .account_connectivity import *
|
||||||
from .gather_asset_users import *
|
from .gather_asset_users import *
|
||||||
from .gather_asset_hardware_info import *
|
from .gather_asset_hardware_info import *
|
||||||
from .push_system_user import *
|
from .push_system_user import *
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
from common.utils import get_logger
|
||||||
from orgs.utils import org_aware_func
|
from orgs.utils import org_aware_func
|
||||||
from ..models import Asset
|
from ..models import Connectivity
|
||||||
from . import const
|
from . import const
|
||||||
from .utils import check_asset_can_run_ansible
|
from .utils import check_asset_can_run_ansible
|
||||||
|
|
||||||
|
@ -14,13 +14,13 @@ logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'test_asset_user_connectivity_util', 'test_asset_users_connectivity_manual',
|
'test_account_connectivity_util', 'test_accounts_connectivity_manual',
|
||||||
'get_test_asset_user_connectivity_tasks', 'test_user_connectivity',
|
'get_test_account_connectivity_tasks', 'test_user_connectivity',
|
||||||
'run_adhoc',
|
'run_adhoc',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_test_asset_user_connectivity_tasks(asset):
|
def get_test_account_connectivity_tasks(asset):
|
||||||
if asset.is_unixlike():
|
if asset.is_unixlike():
|
||||||
tasks = const.PING_UNIXLIKE_TASKS
|
tasks = const.PING_UNIXLIKE_TASKS
|
||||||
elif asset.is_windows():
|
elif asset.is_windows():
|
||||||
|
@ -57,7 +57,7 @@ def test_user_connectivity(task_name, asset, username, password=None, private_ke
|
||||||
"""
|
"""
|
||||||
from ops.inventory import JMSCustomInventory
|
from ops.inventory import JMSCustomInventory
|
||||||
|
|
||||||
tasks = get_test_asset_user_connectivity_tasks(asset)
|
tasks = get_test_account_connectivity_tasks(asset)
|
||||||
if not tasks:
|
if not tasks:
|
||||||
logger.debug("No tasks ")
|
logger.debug("No tasks ")
|
||||||
return {}, {}
|
return {}, {}
|
||||||
|
@ -71,62 +71,37 @@ def test_user_connectivity(task_name, asset, username, password=None, private_ke
|
||||||
return raw, summary
|
return raw, summary
|
||||||
|
|
||||||
|
|
||||||
@org_aware_func("asset_user")
|
@org_aware_func("account")
|
||||||
def test_asset_user_connectivity_util(asset_user, task_name):
|
def test_account_connectivity_util(account, task_name):
|
||||||
"""
|
"""
|
||||||
:param asset_user: <AuthBook>对象
|
:param account: <AuthBook>对象
|
||||||
:param task_name:
|
:param task_name:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if not check_asset_can_run_ansible(asset_user.asset):
|
if not check_asset_can_run_ansible(account.asset):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raw, summary = test_user_connectivity(
|
raw, summary = test_user_connectivity(
|
||||||
task_name=task_name, asset=asset_user.asset,
|
task_name=task_name, asset=account.asset,
|
||||||
username=asset_user.username, password=asset_user.password,
|
username=account.username, password=account.password,
|
||||||
private_key=asset_user.private_key_file
|
private_key=account.private_key_file
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn("Failed run adhoc {}, {}".format(task_name, e))
|
logger.warn("Failed run adhoc {}, {}".format(task_name, e))
|
||||||
return
|
return
|
||||||
asset_user.set_connectivity(summary)
|
|
||||||
|
if summary.get('success'):
|
||||||
|
account.set_connectivity(Connectivity.ok)
|
||||||
|
else:
|
||||||
|
account.set_connectivity(Connectivity.failed)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
@shared_task(queue="ansible")
|
||||||
def test_asset_users_connectivity_manual(asset_users):
|
def test_accounts_connectivity_manual(accounts):
|
||||||
"""
|
"""
|
||||||
:param asset_users: <AuthBook>对象
|
:param accounts: <AuthBook>对象
|
||||||
"""
|
"""
|
||||||
for asset_user in asset_users:
|
for account in accounts:
|
||||||
task_name = _("Test asset user connectivity: {}").format(asset_user)
|
task_name = _("Test account connectivity: {}").format(account)
|
||||||
test_asset_user_connectivity_util(asset_user, task_name)
|
test_account_connectivity_util(account, task_name)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
def push_asset_user_util(asset_user):
|
|
||||||
"""
|
|
||||||
:param asset_user: <Asset user>对象
|
|
||||||
"""
|
|
||||||
from .push_system_user import push_system_user_util
|
|
||||||
if not asset_user.backend.startswith('system_user'):
|
|
||||||
logger.error("Asset user is not from system user")
|
|
||||||
return
|
|
||||||
union_id = asset_user.union_id
|
|
||||||
union_id_list = union_id.split('_')
|
|
||||||
if len(union_id_list) < 2:
|
|
||||||
logger.error("Asset user union id length less than 2")
|
|
||||||
return
|
|
||||||
system_user_id = union_id_list[0]
|
|
||||||
asset_id = union_id_list[1]
|
|
||||||
asset = get_object_or_none(Asset, pk=asset_id)
|
|
||||||
system_user = None
|
|
||||||
if not asset:
|
|
||||||
return
|
|
||||||
hosts = check_asset_can_run_ansible([asset])
|
|
||||||
if asset.is_unixlike:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
|
||||||
|
|
||||||
from celery import shared_task
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
from orgs.utils import tmp_to_root_org, org_aware_func
|
|
||||||
from common.utils import get_logger
|
|
||||||
from ops.celery.decorator import register_as_period_task
|
|
||||||
|
|
||||||
from ..models import AdminUser
|
|
||||||
from .utils import clean_ansible_task_hosts
|
|
||||||
from .asset_connectivity import test_asset_connectivity_util
|
|
||||||
from . import const
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
|
||||||
__all__ = [
|
|
||||||
'test_admin_user_connectivity_util', 'test_admin_user_connectivity_manual',
|
|
||||||
'test_admin_user_connectivity_period'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@org_aware_func("admin_user")
|
|
||||||
def test_admin_user_connectivity_util(admin_user, task_name):
|
|
||||||
"""
|
|
||||||
Test asset admin user can connect or not. Using ansible api do that
|
|
||||||
:param admin_user:
|
|
||||||
:param task_name:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
assets = admin_user.get_related_assets()
|
|
||||||
hosts = clean_ansible_task_hosts(assets)
|
|
||||||
if not hosts:
|
|
||||||
return {}
|
|
||||||
summary = test_asset_connectivity_util(hosts, task_name)
|
|
||||||
return summary
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
@register_as_period_task(interval=3600)
|
|
||||||
def test_admin_user_connectivity_period():
|
|
||||||
"""
|
|
||||||
A period task that update the ansible task period
|
|
||||||
"""
|
|
||||||
if not const.PERIOD_TASK_ENABLED:
|
|
||||||
logger.debug('Period task off, skip')
|
|
||||||
return
|
|
||||||
key = '_JMS_TEST_ADMIN_USER_CONNECTIVITY_PERIOD'
|
|
||||||
prev_execute_time = cache.get(key)
|
|
||||||
if prev_execute_time:
|
|
||||||
logger.debug("Test admin user connectivity, less than 40 minutes, skip")
|
|
||||||
return
|
|
||||||
cache.set(key, 1, 60*40)
|
|
||||||
with tmp_to_root_org():
|
|
||||||
admin_users = AdminUser.objects.all()
|
|
||||||
for admin_user in admin_users:
|
|
||||||
task_name = _("Test admin user connectivity period: {}").format(
|
|
||||||
admin_user.name
|
|
||||||
)
|
|
||||||
test_admin_user_connectivity_util(admin_user, task_name)
|
|
||||||
cache.set(key, 1, 60*40)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
|
||||||
def test_admin_user_connectivity_manual(admin_user):
|
|
||||||
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
|
|
||||||
test_admin_user_connectivity_util(admin_user, task_name)
|
|
||||||
return True
|
|
|
@ -6,7 +6,7 @@ from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from orgs.utils import org_aware_func
|
from orgs.utils import org_aware_func
|
||||||
from ..models.utils import Connectivity
|
from ..models import Asset, Connectivity, AuthBook
|
||||||
from . import const
|
from . import const
|
||||||
from .utils import clean_ansible_task_hosts, group_asset_by_platform
|
from .utils import clean_ansible_task_hosts, group_asset_by_platform
|
||||||
|
|
||||||
|
@ -18,6 +18,28 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def set_assets_accounts_connectivity(assets, results_summary):
|
||||||
|
asset_ids_ok = set()
|
||||||
|
asset_ids_failed = set()
|
||||||
|
|
||||||
|
asset_hostnames_ok = results_summary.get('contacted', {}).keys()
|
||||||
|
|
||||||
|
for asset in assets:
|
||||||
|
if asset.hostname in asset_hostnames_ok:
|
||||||
|
asset_ids_ok.add(asset.id)
|
||||||
|
else:
|
||||||
|
asset_ids_failed.add(asset.id)
|
||||||
|
|
||||||
|
Asset.bulk_set_connectivity(asset_ids_ok, Connectivity.ok)
|
||||||
|
Asset.bulk_set_connectivity(asset_ids_failed, Connectivity.failed)
|
||||||
|
|
||||||
|
accounts_ok = AuthBook.objects.filter(asset_id__in=asset_ids_ok, systemuser__type='admin')
|
||||||
|
accounts_failed = AuthBook.objects.filter(asset_id__in=asset_ids_failed, systemuser__type='admin')
|
||||||
|
|
||||||
|
AuthBook.bulk_set_connectivity(accounts_ok, Connectivity.ok)
|
||||||
|
AuthBook.bulk_set_connectivity(accounts_failed, Connectivity.failed)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(queue="ansible")
|
@shared_task(queue="ansible")
|
||||||
@org_aware_func("assets")
|
@org_aware_func("assets")
|
||||||
def test_asset_connectivity_util(assets, task_name=None):
|
def test_asset_connectivity_util(assets, task_name=None):
|
||||||
|
@ -60,14 +82,7 @@ def test_asset_connectivity_util(assets, task_name=None):
|
||||||
results_summary['contacted'].update(contacted)
|
results_summary['contacted'].update(contacted)
|
||||||
results_summary['dark'].update(dark)
|
results_summary['dark'].update(dark)
|
||||||
continue
|
continue
|
||||||
|
set_assets_accounts_connectivity(assets, results_summary)
|
||||||
for asset in assets:
|
|
||||||
if asset.hostname in results_summary.get('dark', {}).keys():
|
|
||||||
asset.connectivity = Connectivity.unreachable()
|
|
||||||
elif asset.hostname in results_summary.get('contacted', {}).keys():
|
|
||||||
asset.connectivity = Connectivity.reachable()
|
|
||||||
else:
|
|
||||||
asset.connectivity = Connectivity.unknown()
|
|
||||||
return results_summary
|
return results_summary
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,16 @@ app_name = 'assets'
|
||||||
|
|
||||||
router = BulkRouter()
|
router = BulkRouter()
|
||||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||||
|
router.register(r'accounts', api.AccountViewSet, 'account')
|
||||||
|
router.register(r'account-secrets', api.AccountSecretsViewSet, 'account-secret')
|
||||||
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
|
router.register(r'platforms', api.AssetPlatformViewSet, 'platform')
|
||||||
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
|
|
||||||
router.register(r'system-users', api.SystemUserViewSet, 'system-user')
|
router.register(r'system-users', api.SystemUserViewSet, 'system-user')
|
||||||
|
router.register(r'admin-users', api.AdminUserViewSet, 'admin-user')
|
||||||
router.register(r'labels', api.LabelViewSet, 'label')
|
router.register(r'labels', api.LabelViewSet, 'label')
|
||||||
router.register(r'nodes', api.NodeViewSet, 'node')
|
router.register(r'nodes', api.NodeViewSet, 'node')
|
||||||
router.register(r'domains', api.DomainViewSet, 'domain')
|
router.register(r'domains', api.DomainViewSet, 'domain')
|
||||||
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
||||||
router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
|
router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
|
||||||
router.register(r'asset-users', api.AssetUserViewSet, 'asset-user')
|
|
||||||
router.register(r'asset-user-auth-infos', api.AssetUserAuthInfoViewSet, 'asset-user-auth-info')
|
|
||||||
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
|
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
|
||||||
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
||||||
router.register(r'system-users-assets-relations', api.SystemUserAssetRelationViewSet, 'system-users-assets-relation')
|
router.register(r'system-users-assets-relations', api.SystemUserAssetRelationViewSet, 'system-users-assets-relation')
|
||||||
|
@ -37,13 +37,6 @@ urlpatterns = [
|
||||||
path('assets/<uuid:pk>/tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'),
|
path('assets/<uuid:pk>/tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'),
|
||||||
path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'),
|
path('assets/tasks/', api.AssetsTaskCreateApi.as_view(), name='assets-task-create'),
|
||||||
|
|
||||||
path('asset-users/tasks/', api.AssetUserTaskCreateAPI.as_view(), name='asset-user-task-create'),
|
|
||||||
|
|
||||||
path('admin-users/<uuid:pk>/nodes/', api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
|
||||||
path('admin-users/<uuid:pk>/auth/', api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
|
||||||
path('admin-users/<uuid:pk>/connective/', api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
|
||||||
path('admin-users/<uuid:pk>/assets/', api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
|
|
||||||
|
|
||||||
path('system-users/<uuid:pk>/auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
path('system-users/<uuid:pk>/auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||||
path('system-users/<uuid:pk>/assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
path('system-users/<uuid:pk>/assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||||
path('system-users/<uuid:pk>/assets/<uuid:asset_id>/auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
|
path('system-users/<uuid:pk>/assets/<uuid:asset_id>/auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
|
||||||
|
|
|
@ -5,7 +5,6 @@ from rest_framework import serializers
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from terminal.models import Session
|
from terminal.models import Session
|
||||||
from ops.models import CommandExecution
|
from ops.models import CommandExecution
|
||||||
from . import models
|
from . import models
|
||||||
|
@ -108,7 +107,6 @@ class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.M
|
||||||
commandexecution_display = serializers.ReadOnlyField()
|
commandexecution_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
model = CommandExecution.hosts.through
|
model = CommandExecution.hosts.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display'
|
'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display'
|
||||||
|
|
|
@ -15,7 +15,7 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from authentication.signals import post_auth_failed, post_auth_success
|
from authentication.signals import post_auth_failed, post_auth_success
|
||||||
from common.utils import get_logger, random_string
|
from common.utils import get_logger, random_string
|
||||||
from common.drf.api import SerializerMixin2
|
from common.drf.api import SerializerMixin
|
||||||
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
|
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
|
||||||
|
|
||||||
from orgs.mixins.api import RootOrgViewMixin
|
from orgs.mixins.api import RootOrgViewMixin
|
||||||
|
@ -29,7 +29,7 @@ logger = get_logger(__name__)
|
||||||
__all__ = ['UserConnectionTokenViewSet']
|
__all__ = ['UserConnectionTokenViewSet']
|
||||||
|
|
||||||
|
|
||||||
class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericViewSet):
|
class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin, GenericViewSet):
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': ConnectionTokenSerializer,
|
'default': ConnectionTokenSerializer,
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
#
|
#
|
||||||
import time
|
import time
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.conf import settings
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.permissions import IsValidUser
|
from common.permissions import IsValidUser, NeedMFAVerify
|
||||||
from ..serializers import OtpVerifySerializer
|
from ..serializers import OtpVerifySerializer
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from .. import errors
|
from .. import errors
|
||||||
|
@ -48,6 +49,9 @@ class UserOtpVerifyApi(CreateAPIView):
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsValidUser,)
|
||||||
serializer_class = OtpVerifySerializer
|
serializer_class = OtpVerifySerializer
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return Response({'code': 'valid', 'msg': 'verified'})
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
@ -58,3 +62,8 @@ class UserOtpVerifyApi(CreateAPIView):
|
||||||
return Response({"ok": "1"})
|
return Response({"ok": "1"})
|
||||||
else:
|
else:
|
||||||
return Response({"error": _("Code is invalid")}, status=400)
|
return Response({"error": _("Code is invalid")}, status=400)
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
if self.request.method.lower() == 'get' and settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
||||||
|
self.permission_classes = [NeedMFAVerify]
|
||||||
|
return super().get_permissions()
|
||||||
|
|
|
@ -2,12 +2,12 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
|
||||||
from ..mixins.api import (
|
from ..mixins.api import (
|
||||||
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin,
|
SerializerMixin, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin,
|
||||||
RelationMixin, AllowBulkDestoryMixin, RenderToJsonMixin,
|
RelationMixin, AllowBulkDestroyMixin, RenderToJsonMixin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CommonMixin(SerializerMixin2,
|
class CommonMixin(SerializerMixin,
|
||||||
QuerySetMixin,
|
QuerySetMixin,
|
||||||
ExtraFilterFieldsMixin,
|
ExtraFilterFieldsMixin,
|
||||||
PaginatedResponseMixin,
|
PaginatedResponseMixin,
|
||||||
|
@ -26,13 +26,13 @@ class JMSModelViewSet(CommonMixin,
|
||||||
|
|
||||||
|
|
||||||
class JMSBulkModelViewSet(CommonMixin,
|
class JMSBulkModelViewSet(CommonMixin,
|
||||||
AllowBulkDestoryMixin,
|
AllowBulkDestroyMixin,
|
||||||
BulkModelViewSet):
|
BulkModelViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JMSBulkRelationModelViewSet(CommonMixin,
|
class JMSBulkRelationModelViewSet(CommonMixin,
|
||||||
RelationMixin,
|
RelationMixin,
|
||||||
AllowBulkDestoryMixin,
|
AllowBulkDestroyMixin,
|
||||||
BulkModelViewSet):
|
BulkModelViewSet):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -23,7 +23,7 @@ from ..utils import lazyproperty
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'JSONResponseMixin', 'CommonApiMixin', 'AsyncApiMixin', 'RelationMixin',
|
'JSONResponseMixin', 'CommonApiMixin', 'AsyncApiMixin', 'RelationMixin',
|
||||||
'SerializerMixin2', 'QuerySetMixin', 'ExtraFilterFieldsMixin', 'RenderToJsonMixin',
|
'QuerySetMixin', 'ExtraFilterFieldsMixin', 'RenderToJsonMixin',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,21 +62,27 @@ class RenderToJsonMixin:
|
||||||
class SerializerMixin:
|
class SerializerMixin:
|
||||||
""" 根据用户请求动作的不同,获取不同的 `serializer_class `"""
|
""" 根据用户请求动作的不同,获取不同的 `serializer_class `"""
|
||||||
|
|
||||||
|
action: str
|
||||||
|
request: Request
|
||||||
|
|
||||||
serializer_classes = None
|
serializer_classes = None
|
||||||
|
single_actions = ['put', 'retrieve', 'patch']
|
||||||
|
|
||||||
def get_serializer_class_by_view_action(self):
|
def get_serializer_class_by_view_action(self):
|
||||||
if not hasattr(self, 'serializer_classes'):
|
if not hasattr(self, 'serializer_classes'):
|
||||||
return None
|
return None
|
||||||
if not isinstance(self.serializer_classes, dict):
|
if not isinstance(self.serializer_classes, dict):
|
||||||
return None
|
return None
|
||||||
action = self.request.query_params.get('action')
|
|
||||||
|
|
||||||
serializer_class = None
|
view_action = self.request.query_params.get('action') or self.action or 'list'
|
||||||
if action:
|
serializer_class = self.serializer_classes.get(view_action)
|
||||||
# metadata方法 使用 action 参数获取
|
|
||||||
serializer_class = self.serializer_classes.get(action)
|
|
||||||
if serializer_class is None:
|
if serializer_class is None:
|
||||||
serializer_class = self.serializer_classes.get(self.action)
|
view_method = self.request.method.lower()
|
||||||
|
serializer_class = self.serializer_classes.get(view_method)
|
||||||
|
|
||||||
|
if serializer_class is None and view_action in self.single_actions:
|
||||||
|
serializer_class = self.serializer_classes.get('single')
|
||||||
if serializer_class is None:
|
if serializer_class is None:
|
||||||
serializer_class = self.serializer_classes.get('display')
|
serializer_class = self.serializer_classes.get('display')
|
||||||
if serializer_class is None:
|
if serializer_class is None:
|
||||||
|
@ -301,36 +307,18 @@ class RelationMixin:
|
||||||
self.send_m2m_changed_signal(instance, 'post_remove')
|
self.send_m2m_changed_signal(instance, 'post_remove')
|
||||||
|
|
||||||
|
|
||||||
class SerializerMixin2:
|
|
||||||
serializer_classes = {}
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
|
||||||
if self.serializer_classes:
|
|
||||||
serializer_class = self.serializer_classes.get(
|
|
||||||
self.action, self.serializer_classes.get('default')
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(serializer_class, dict):
|
|
||||||
serializer_class = serializer_class.get(
|
|
||||||
self.request.method.lower, serializer_class.get('default')
|
|
||||||
)
|
|
||||||
|
|
||||||
assert serializer_class, '`serializer_classes` config error'
|
|
||||||
return serializer_class
|
|
||||||
return super().get_serializer_class()
|
|
||||||
|
|
||||||
|
|
||||||
class QuerySetMixin:
|
class QuerySetMixin:
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
serializer_class = self.get_serializer_class()
|
serializer_class = self.get_serializer_class()
|
||||||
|
|
||||||
if serializer_class and hasattr(serializer_class, 'setup_eager_loading'):
|
if serializer_class and hasattr(serializer_class, 'setup_eager_loading'):
|
||||||
queryset = serializer_class.setup_eager_loading(queryset)
|
queryset = serializer_class.setup_eager_loading(queryset)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AllowBulkDestoryMixin:
|
class AllowBulkDestroyMixin:
|
||||||
def allow_bulk_destroy(self, qs, filtered):
|
def allow_bulk_destroy(self, qs, filtered):
|
||||||
"""
|
"""
|
||||||
我们规定,批量删除的情况必须用 `id` 指定要删除的数据。
|
我们规定,批量删除的情况必须用 `id` 指定要删除的数据。
|
||||||
|
|
|
@ -67,6 +67,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.forms',
|
'django.forms',
|
||||||
|
'simple_history',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,6 +87,7 @@ MIDDLEWARE = [
|
||||||
'orgs.middleware.OrgMiddleware',
|
'orgs.middleware.OrgMiddleware',
|
||||||
'authentication.backends.oidc.middleware.OIDCRefreshIDTokenMiddleware',
|
'authentication.backends.oidc.middleware.OIDCRefreshIDTokenMiddleware',
|
||||||
'authentication.backends.cas.middleware.CASMiddleware',
|
'authentication.backends.cas.middleware.CASMiddleware',
|
||||||
|
'simple_history.middleware.HistoryRequestMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-06-25 17:12+0800\n"
|
"POT-Creation-Date: 2021-06-29 17:02+0800\n"
|
||||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||||
|
@ -19,7 +19,7 @@ msgstr ""
|
||||||
|
|
||||||
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
|
#: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
|
||||||
#: applications/models/application.py:11 assets/models/asset.py:142
|
#: applications/models/application.py:11 assets/models/asset.py:142
|
||||||
#: assets/models/base.py:249 assets/models/cluster.py:18
|
#: assets/models/base.py:216 assets/models/cluster.py:18
|
||||||
#: assets/models/cmd_filter.py:21 assets/models/domain.py:21
|
#: assets/models/cmd_filter.py:21 assets/models/domain.py:21
|
||||||
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
|
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
|
||||||
#: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29
|
#: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29
|
||||||
|
@ -40,7 +40,7 @@ msgid "Priority"
|
||||||
msgstr "优先级"
|
msgstr "优先级"
|
||||||
|
|
||||||
#: acls/models/base.py:28 assets/models/cmd_filter.py:54
|
#: acls/models/base.py:28 assets/models/cmd_filter.py:54
|
||||||
#: assets/models/user.py:123
|
#: assets/models/user.py:246
|
||||||
msgid "1-100, the lower the value will be match first"
|
msgid "1-100, the lower the value will be match first"
|
||||||
msgstr "优先级可选范围为 1-100 (数值越小越优先)"
|
msgstr "优先级可选范围为 1-100 (数值越小越优先)"
|
||||||
|
|
||||||
|
@ -54,16 +54,16 @@ msgstr "激活中"
|
||||||
# msgstr "创建日期"
|
# msgstr "创建日期"
|
||||||
#: acls/models/base.py:32 applications/models/application.py:24
|
#: acls/models/base.py:32 applications/models/application.py:24
|
||||||
#: assets/models/asset.py:147 assets/models/asset.py:223
|
#: assets/models/asset.py:147 assets/models/asset.py:223
|
||||||
#: assets/models/base.py:254 assets/models/cluster.py:29
|
#: assets/models/base.py:221 assets/models/cluster.py:29
|
||||||
#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:64
|
#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:64
|
||||||
#: assets/models/domain.py:22 assets/models/domain.py:56
|
#: assets/models/domain.py:22 assets/models/domain.py:56
|
||||||
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
|
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
|
||||||
#: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34
|
#: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34
|
||||||
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
|
#: terminal/models/storage.py:29 terminal/models/storage.py:96
|
||||||
#: tickets/models/ticket.py:73 users/models/group.py:16
|
#: terminal/models/terminal.py:114 tickets/models/ticket.py:73
|
||||||
#: users/models/user.py:583 xpack/plugins/change_auth_plan/models.py:77
|
#: users/models/group.py:16 users/models/user.py:583
|
||||||
#: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:98
|
#: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:35
|
||||||
#: xpack/plugins/gathered_user/models.py:26
|
#: xpack/plugins/cloud/models.py:98 xpack/plugins/gathered_user/models.py:26
|
||||||
msgid "Comment"
|
msgid "Comment"
|
||||||
msgstr "备注"
|
msgstr "备注"
|
||||||
|
|
||||||
|
@ -119,9 +119,8 @@ msgstr "系统用户"
|
||||||
|
|
||||||
#: acls/models/login_asset_acl.py:22
|
#: acls/models/login_asset_acl.py:22
|
||||||
#: applications/serializers/attrs/application_category/remote_app.py:33
|
#: applications/serializers/attrs/application_category/remote_app.py:33
|
||||||
#: assets/models/asset.py:355 assets/models/authbook.py:27
|
#: assets/models/asset.py:372 assets/models/authbook.py:17
|
||||||
#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:34
|
#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:33
|
||||||
#: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:91
|
|
||||||
#: assets/serializers/system_user.py:202 audits/models.py:38
|
#: assets/serializers/system_user.py:202 audits/models.py:38
|
||||||
#: perms/models/asset_permission.py:99 templates/index.html:82
|
#: perms/models/asset_permission.py:99 templates/index.html:82
|
||||||
#: terminal/backends/command/models.py:19
|
#: terminal/backends/command/models.py:19
|
||||||
|
@ -158,7 +157,7 @@ msgstr ""
|
||||||
#: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31
|
#: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31
|
||||||
#: applications/serializers/attrs/application_type/mysql_workbench.py:18
|
#: applications/serializers/attrs/application_type/mysql_workbench.py:18
|
||||||
#: assets/models/asset.py:183 assets/models/domain.py:52
|
#: assets/models/asset.py:183 assets/models/domain.py:52
|
||||||
#: assets/serializers/asset_user.py:47 settings/serializers/settings.py:113
|
#: settings/serializers/settings.py:113
|
||||||
#: users/templates/users/_granted_assets.html:26
|
#: users/templates/users/_granted_assets.html:26
|
||||||
#: users/templates/users/user_asset_permission.html:156
|
#: users/templates/users/user_asset_permission.html:156
|
||||||
msgid "IP"
|
msgid "IP"
|
||||||
|
@ -178,13 +177,13 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
|
||||||
#: applications/serializers/attrs/application_type/custom.py:21
|
#: applications/serializers/attrs/application_type/custom.py:21
|
||||||
#: applications/serializers/attrs/application_type/mysql_workbench.py:30
|
#: applications/serializers/attrs/application_type/mysql_workbench.py:30
|
||||||
#: applications/serializers/attrs/application_type/vmware_client.py:26
|
#: applications/serializers/attrs/application_type/vmware_client.py:26
|
||||||
#: assets/models/base.py:250 assets/models/gathered_user.py:15
|
#: assets/models/base.py:217 assets/models/gathered_user.py:15
|
||||||
#: audits/models.py:100 authentication/forms.py:15 authentication/forms.py:17
|
#: audits/models.py:100 authentication/forms.py:15 authentication/forms.py:17
|
||||||
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:548
|
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:548
|
||||||
#: users/templates/users/_select_user_modal.html:14
|
#: users/templates/users/_select_user_modal.html:14
|
||||||
#: xpack/plugins/change_auth_plan/models.py:47
|
#: xpack/plugins/change_auth_plan/models.py:47
|
||||||
#: xpack/plugins/change_auth_plan/models.py:278
|
#: xpack/plugins/change_auth_plan/models.py:278
|
||||||
#: xpack/plugins/cloud/serializers.py:65
|
#: xpack/plugins/cloud/serializers.py:51
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr "用户名"
|
msgstr "用户名"
|
||||||
|
|
||||||
|
@ -198,8 +197,7 @@ msgstr ""
|
||||||
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
|
"10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)"
|
||||||
|
|
||||||
#: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:184
|
#: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:184
|
||||||
#: assets/serializers/asset_user.py:46 assets/serializers/gathered_user.py:23
|
#: assets/serializers/gathered_user.py:23 settings/serializers/settings.py:112
|
||||||
#: settings/serializers/settings.py:112
|
|
||||||
#: users/templates/users/_granted_assets.html:25
|
#: users/templates/users/_granted_assets.html:25
|
||||||
#: users/templates/users/user_asset_permission.html:157
|
#: users/templates/users/user_asset_permission.html:157
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
|
@ -212,7 +210,7 @@ msgid ""
|
||||||
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}"
|
msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}"
|
||||||
|
|
||||||
#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:187
|
#: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:187
|
||||||
#: assets/models/domain.py:54 assets/models/user.py:124
|
#: assets/models/domain.py:54 assets/models/user.py:247
|
||||||
#: terminal/serializers/session.py:32 terminal/serializers/storage.py:69
|
#: terminal/serializers/session.py:32 terminal/serializers/storage.py:69
|
||||||
msgid "Protocol"
|
msgid "Protocol"
|
||||||
msgstr "协议"
|
msgstr "协议"
|
||||||
|
@ -254,7 +252,7 @@ msgid "Category"
|
||||||
msgstr "类别"
|
msgstr "类别"
|
||||||
|
|
||||||
#: applications/models/application.py:16 assets/models/cmd_filter.py:53
|
#: applications/models/application.py:16 assets/models/cmd_filter.py:53
|
||||||
#: perms/models/application_permission.py:23
|
#: assets/models/user.py:245 perms/models/application_permission.py:23
|
||||||
#: perms/serializers/application/permission.py:17
|
#: perms/serializers/application/permission.py:17
|
||||||
#: perms/serializers/application/user_permission.py:34
|
#: perms/serializers/application/user_permission.py:34
|
||||||
#: terminal/models/storage.py:47 terminal/models/storage.py:108
|
#: terminal/models/storage.py:47 terminal/models/storage.py:108
|
||||||
|
@ -296,7 +294,7 @@ msgstr "应用类型"
|
||||||
#: assets/serializers/system_user.py:49 assets/serializers/system_user.py:177
|
#: assets/serializers/system_user.py:49 assets/serializers/system_user.py:177
|
||||||
#: assets/serializers/system_user.py:203
|
#: assets/serializers/system_user.py:203
|
||||||
msgid "Login mode display"
|
msgid "Login mode display"
|
||||||
msgstr "登录模式(显示名称)"
|
msgstr "认证方式(显示名称)"
|
||||||
|
|
||||||
#: applications/serializers/attrs/application_category/cloud.py:9
|
#: applications/serializers/attrs/application_category/cloud.py:9
|
||||||
#: assets/models/cluster.py:40
|
#: assets/models/cluster.py:40
|
||||||
|
@ -304,7 +302,7 @@ msgid "Cluster"
|
||||||
msgstr "集群"
|
msgstr "集群"
|
||||||
|
|
||||||
#: applications/serializers/attrs/application_category/db.py:11
|
#: applications/serializers/attrs/application_category/db.py:11
|
||||||
#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:63
|
#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:49
|
||||||
msgid "Host"
|
msgid "Host"
|
||||||
msgstr "主机"
|
msgstr "主机"
|
||||||
|
|
||||||
|
@ -314,7 +312,7 @@ msgstr "主机"
|
||||||
#: applications/serializers/attrs/application_type/oracle.py:11
|
#: applications/serializers/attrs/application_type/oracle.py:11
|
||||||
#: applications/serializers/attrs/application_type/pgsql.py:11
|
#: applications/serializers/attrs/application_type/pgsql.py:11
|
||||||
#: assets/models/asset.py:188 assets/models/domain.py:53
|
#: assets/models/asset.py:188 assets/models/domain.py:53
|
||||||
#: xpack/plugins/cloud/serializers.py:64
|
#: xpack/plugins/cloud/serializers.py:50
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr "端口"
|
msgstr "端口"
|
||||||
|
|
||||||
|
@ -334,8 +332,8 @@ msgstr "目标URL"
|
||||||
#: applications/serializers/attrs/application_type/custom.py:25
|
#: applications/serializers/attrs/application_type/custom.py:25
|
||||||
#: applications/serializers/attrs/application_type/mysql_workbench.py:34
|
#: applications/serializers/attrs/application_type/mysql_workbench.py:34
|
||||||
#: applications/serializers/attrs/application_type/vmware_client.py:30
|
#: applications/serializers/attrs/application_type/vmware_client.py:30
|
||||||
#: assets/models/base.py:251 assets/serializers/asset_user.py:78
|
#: assets/models/base.py:218 audits/signals_handler.py:58
|
||||||
#: audits/signals_handler.py:58 authentication/forms.py:22
|
#: authentication/forms.py:22
|
||||||
#: authentication/templates/authentication/login.html:164
|
#: authentication/templates/authentication/login.html:164
|
||||||
#: settings/serializers/settings.py:94 users/forms/profile.py:21
|
#: settings/serializers/settings.py:94 users/forms/profile.py:21
|
||||||
#: users/templates/users/user_otp_check_password.html:13
|
#: users/templates/users/user_otp_check_password.html:13
|
||||||
|
@ -344,7 +342,7 @@ msgstr "目标URL"
|
||||||
#: xpack/plugins/change_auth_plan/models.py:68
|
#: xpack/plugins/change_auth_plan/models.py:68
|
||||||
#: xpack/plugins/change_auth_plan/models.py:190
|
#: xpack/plugins/change_auth_plan/models.py:190
|
||||||
#: xpack/plugins/change_auth_plan/models.py:285
|
#: xpack/plugins/change_auth_plan/models.py:285
|
||||||
#: xpack/plugins/cloud/serializers.py:67
|
#: xpack/plugins/cloud/serializers.py:53
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "密码"
|
msgstr "密码"
|
||||||
|
|
||||||
|
@ -356,10 +354,6 @@ msgstr "运行参数"
|
||||||
msgid "Target url"
|
msgid "Target url"
|
||||||
msgstr "目标URL"
|
msgstr "目标URL"
|
||||||
|
|
||||||
#: assets/api/admin_user.py:50
|
|
||||||
msgid "Deleted failed, There are related assets"
|
|
||||||
msgstr "删除失败,存在关联资产"
|
|
||||||
|
|
||||||
#: assets/api/domain.py:50
|
#: assets/api/domain.py:50
|
||||||
msgid "Number required"
|
msgid "Number required"
|
||||||
msgstr "需要为数字"
|
msgstr "需要为数字"
|
||||||
|
@ -376,38 +370,6 @@ msgstr "不能删除根节点 ({})"
|
||||||
msgid "Deletion failed and the node contains assets"
|
msgid "Deletion failed and the node contains assets"
|
||||||
msgstr "删除失败,节点包含资产"
|
msgstr "删除失败,节点包含资产"
|
||||||
|
|
||||||
#: assets/backends/db.py:110 assets/models/user.py:307 audits/models.py:39
|
|
||||||
#: perms/models/application_permission.py:31
|
|
||||||
#: perms/models/asset_permission.py:101 templates/_nav.html:45
|
|
||||||
#: terminal/backends/command/models.py:20
|
|
||||||
#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42
|
|
||||||
#: users/templates/users/_granted_assets.html:27
|
|
||||||
#: users/templates/users/user_asset_permission.html:42
|
|
||||||
#: users/templates/users/user_asset_permission.html:76
|
|
||||||
#: users/templates/users/user_asset_permission.html:159
|
|
||||||
#: users/templates/users/user_database_app_permission.html:40
|
|
||||||
#: users/templates/users/user_database_app_permission.html:67
|
|
||||||
msgid "System user"
|
|
||||||
msgstr "系统用户"
|
|
||||||
|
|
||||||
#: assets/backends/db.py:181
|
|
||||||
msgid "System user(Dynamic)"
|
|
||||||
msgstr "系统用户(动态)"
|
|
||||||
|
|
||||||
#: assets/backends/db.py:233 assets/models/asset.py:196
|
|
||||||
#: assets/models/cluster.py:19 assets/models/user.py:67 templates/_nav.html:44
|
|
||||||
#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:181
|
|
||||||
msgid "Admin user"
|
|
||||||
msgstr "管理用户"
|
|
||||||
|
|
||||||
#: assets/backends/db.py:254
|
|
||||||
msgid "Could not remove asset admin user"
|
|
||||||
msgstr "不能移除资产的管理用户账号"
|
|
||||||
|
|
||||||
#: assets/backends/db.py:318
|
|
||||||
msgid "Latest version could not be delete"
|
|
||||||
msgstr "最新版本的不能被删除"
|
|
||||||
|
|
||||||
#: assets/models/asset.py:143
|
#: assets/models/asset.py:143
|
||||||
msgid "Base"
|
msgid "Base"
|
||||||
msgstr "基础"
|
msgstr "基础"
|
||||||
|
@ -416,7 +378,7 @@ msgstr "基础"
|
||||||
msgid "Charset"
|
msgid "Charset"
|
||||||
msgstr "编码"
|
msgstr "编码"
|
||||||
|
|
||||||
#: assets/models/asset.py:145 assets/serializers/asset.py:171
|
#: assets/models/asset.py:145 assets/serializers/asset.py:180
|
||||||
#: tickets/models/ticket.py:40
|
#: tickets/models/ticket.py:40
|
||||||
msgid "Meta"
|
msgid "Meta"
|
||||||
msgstr "元数据"
|
msgstr "元数据"
|
||||||
|
@ -426,16 +388,16 @@ msgid "Internal"
|
||||||
msgstr "内部的"
|
msgstr "内部的"
|
||||||
|
|
||||||
#: assets/models/asset.py:166 assets/models/asset.py:190
|
#: assets/models/asset.py:166 assets/models/asset.py:190
|
||||||
#: assets/serializers/asset.py:66 perms/serializers/asset/user_permission.py:43
|
#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:43
|
||||||
msgid "Platform"
|
msgid "Platform"
|
||||||
msgstr "系统平台"
|
msgstr "系统平台"
|
||||||
|
|
||||||
#: assets/models/asset.py:189 assets/serializers/asset.py:68
|
#: assets/models/asset.py:189 assets/serializers/asset.py:70
|
||||||
#: perms/serializers/asset/user_permission.py:41
|
#: perms/serializers/asset/user_permission.py:41
|
||||||
msgid "Protocols"
|
msgid "Protocols"
|
||||||
msgstr "协议组"
|
msgstr "协议组"
|
||||||
|
|
||||||
#: assets/models/asset.py:192 assets/models/user.py:119
|
#: assets/models/asset.py:192 assets/models/user.py:237
|
||||||
#: perms/models/asset_permission.py:100
|
#: perms/models/asset_permission.py:100
|
||||||
#: xpack/plugins/change_auth_plan/models.py:56
|
#: xpack/plugins/change_auth_plan/models.py:56
|
||||||
#: xpack/plugins/gathered_user/models.py:24
|
#: xpack/plugins/gathered_user/models.py:24
|
||||||
|
@ -448,6 +410,13 @@ msgstr "节点"
|
||||||
msgid "Is active"
|
msgid "Is active"
|
||||||
msgstr "激活"
|
msgstr "激活"
|
||||||
|
|
||||||
|
#: assets/models/asset.py:196 assets/models/cluster.py:19
|
||||||
|
#: assets/models/user.py:234 assets/models/user.py:370
|
||||||
|
#: assets/serializers/asset.py:68 templates/_nav.html:44
|
||||||
|
#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:146
|
||||||
|
msgid "Admin user"
|
||||||
|
msgstr "管理用户"
|
||||||
|
|
||||||
#: assets/models/asset.py:199
|
#: assets/models/asset.py:199
|
||||||
msgid "Public IP"
|
msgid "Public IP"
|
||||||
msgstr "公网IP"
|
msgstr "公网IP"
|
||||||
|
@ -516,7 +485,7 @@ msgstr "主机名原始"
|
||||||
msgid "Labels"
|
msgid "Labels"
|
||||||
msgstr "标签管理"
|
msgstr "标签管理"
|
||||||
|
|
||||||
#: assets/models/asset.py:221 assets/models/base.py:257
|
#: assets/models/asset.py:221 assets/models/base.py:224
|
||||||
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
|
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
|
||||||
#: assets/models/cmd_filter.py:67 assets/models/group.py:21
|
#: assets/models/cmd_filter.py:67 assets/models/group.py:21
|
||||||
#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24
|
#: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24
|
||||||
|
@ -528,7 +497,7 @@ msgstr "创建者"
|
||||||
|
|
||||||
# msgid "Created by"
|
# msgid "Created by"
|
||||||
# msgstr "创建者"
|
# msgstr "创建者"
|
||||||
#: assets/models/asset.py:222 assets/models/base.py:255
|
#: assets/models/asset.py:222 assets/models/base.py:222
|
||||||
#: assets/models/cluster.py:26 assets/models/domain.py:24
|
#: assets/models/cluster.py:26 assets/models/domain.py:24
|
||||||
#: assets/models/gathered_user.py:19 assets/models/group.py:22
|
#: assets/models/gathered_user.py:19 assets/models/group.py:22
|
||||||
#: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50
|
#: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50
|
||||||
|
@ -538,35 +507,45 @@ msgstr "创建者"
|
||||||
msgid "Date created"
|
msgid "Date created"
|
||||||
msgstr "创建日期"
|
msgstr "创建日期"
|
||||||
|
|
||||||
#: assets/models/authbook.py:18
|
#: assets/models/authbook.py:18 assets/models/user.py:321 audits/models.py:39
|
||||||
msgid "Bulk delete deny"
|
#: perms/models/application_permission.py:31
|
||||||
msgstr "拒绝批量删除"
|
#: perms/models/asset_permission.py:101 templates/_nav.html:45
|
||||||
|
#: terminal/backends/command/models.py:20
|
||||||
|
#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42
|
||||||
|
#: users/templates/users/_granted_assets.html:27
|
||||||
|
#: users/templates/users/user_asset_permission.html:42
|
||||||
|
#: users/templates/users/user_asset_permission.html:76
|
||||||
|
#: users/templates/users/user_asset_permission.html:159
|
||||||
|
#: users/templates/users/user_database_app_permission.html:40
|
||||||
|
#: users/templates/users/user_database_app_permission.html:67
|
||||||
|
msgid "System user"
|
||||||
|
msgstr "系统用户"
|
||||||
|
|
||||||
#: assets/models/authbook.py:28
|
#: assets/models/authbook.py:20
|
||||||
msgid "Latest version"
|
|
||||||
msgstr "最新版本"
|
|
||||||
|
|
||||||
#: assets/models/authbook.py:29
|
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr "版本"
|
msgstr "版本"
|
||||||
|
|
||||||
|
#: assets/models/authbook.py:21
|
||||||
|
msgid "Latest version"
|
||||||
|
msgstr "最新版本"
|
||||||
|
|
||||||
#: assets/models/authbook.py:38
|
#: assets/models/authbook.py:38
|
||||||
msgid "AuthBook"
|
msgid "AuthBook"
|
||||||
msgstr ""
|
msgstr "账号"
|
||||||
|
|
||||||
#: assets/models/base.py:252 xpack/plugins/change_auth_plan/models.py:72
|
#: assets/models/base.py:219 xpack/plugins/change_auth_plan/models.py:72
|
||||||
#: xpack/plugins/change_auth_plan/models.py:197
|
#: xpack/plugins/change_auth_plan/models.py:197
|
||||||
#: xpack/plugins/change_auth_plan/models.py:292
|
#: xpack/plugins/change_auth_plan/models.py:292
|
||||||
msgid "SSH private key"
|
msgid "SSH private key"
|
||||||
msgstr "SSH密钥"
|
msgstr "SSH密钥"
|
||||||
|
|
||||||
#: assets/models/base.py:253 xpack/plugins/change_auth_plan/models.py:75
|
#: assets/models/base.py:220 xpack/plugins/change_auth_plan/models.py:75
|
||||||
#: xpack/plugins/change_auth_plan/models.py:193
|
#: xpack/plugins/change_auth_plan/models.py:193
|
||||||
#: xpack/plugins/change_auth_plan/models.py:288
|
#: xpack/plugins/change_auth_plan/models.py:288
|
||||||
msgid "SSH public key"
|
msgid "SSH public key"
|
||||||
msgstr "SSH公钥"
|
msgstr "SSH公钥"
|
||||||
|
|
||||||
#: assets/models/base.py:256 assets/models/gathered_user.py:20
|
#: assets/models/base.py:223 assets/models/gathered_user.py:20
|
||||||
#: common/db/models.py:73 common/mixins/models.py:51 ops/models/adhoc.py:39
|
#: common/db/models.py:73 common/mixins/models.py:51 ops/models/adhoc.py:39
|
||||||
#: orgs/models.py:421
|
#: orgs/models.py:421
|
||||||
msgid "Date updated"
|
msgid "Date updated"
|
||||||
|
@ -614,7 +593,7 @@ msgstr "系统"
|
||||||
msgid "Default Cluster"
|
msgid "Default Cluster"
|
||||||
msgstr "默认Cluster"
|
msgstr "默认Cluster"
|
||||||
|
|
||||||
#: assets/models/cmd_filter.py:33 assets/models/user.py:129
|
#: assets/models/cmd_filter.py:33 assets/models/user.py:252
|
||||||
msgid "Command filter"
|
msgid "Command filter"
|
||||||
msgstr "命令过滤器"
|
msgstr "命令过滤器"
|
||||||
|
|
||||||
|
@ -715,65 +694,65 @@ msgstr "ssh私钥"
|
||||||
#: users/templates/users/user_asset_permission.html:41
|
#: users/templates/users/user_asset_permission.html:41
|
||||||
#: users/templates/users/user_asset_permission.html:73
|
#: users/templates/users/user_asset_permission.html:73
|
||||||
#: users/templates/users/user_asset_permission.html:158
|
#: users/templates/users/user_asset_permission.html:158
|
||||||
#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:182
|
#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:147
|
||||||
msgid "Node"
|
msgid "Node"
|
||||||
msgstr "节点"
|
msgstr "节点"
|
||||||
|
|
||||||
#: assets/models/user.py:115
|
#: assets/models/user.py:227
|
||||||
msgid "Automatic login"
|
msgid "Automatic managed"
|
||||||
msgstr "自动登录"
|
msgstr "托管密码"
|
||||||
|
|
||||||
#: assets/models/user.py:116
|
#: assets/models/user.py:228
|
||||||
msgid "Manually login"
|
msgid "Manually input"
|
||||||
msgstr "手动登录"
|
msgstr "手动输入"
|
||||||
|
|
||||||
#: assets/models/user.py:118
|
#: assets/models/user.py:236
|
||||||
msgid "Username same with user"
|
msgid "Username same with user"
|
||||||
msgstr "用户名与用户相同"
|
msgstr "用户名与用户相同"
|
||||||
|
|
||||||
#: assets/models/user.py:120 assets/serializers/domain.py:30
|
#: assets/models/user.py:239 assets/serializers/domain.py:30
|
||||||
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:52
|
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:52
|
||||||
msgid "Assets"
|
msgid "Assets"
|
||||||
msgstr "资产"
|
msgstr "资产"
|
||||||
|
|
||||||
#: assets/models/user.py:121 templates/_nav.html:17
|
#: assets/models/user.py:243 templates/_nav.html:17
|
||||||
#: users/views/profile/password.py:43 users/views/profile/pubkey.py:37
|
#: users/views/profile/password.py:43 users/views/profile/pubkey.py:37
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "用户管理"
|
msgstr "用户管理"
|
||||||
|
|
||||||
#: assets/models/user.py:122
|
#: assets/models/user.py:244
|
||||||
msgid "User groups"
|
msgid "User groups"
|
||||||
msgstr "用户组"
|
msgstr "用户组"
|
||||||
|
|
||||||
#: assets/models/user.py:125
|
#: assets/models/user.py:248
|
||||||
msgid "Auto push"
|
msgid "Auto push"
|
||||||
msgstr "自动推送"
|
msgstr "自动推送"
|
||||||
|
|
||||||
#: assets/models/user.py:126
|
#: assets/models/user.py:249
|
||||||
msgid "Sudo"
|
msgid "Sudo"
|
||||||
msgstr "Sudo"
|
msgstr "Sudo"
|
||||||
|
|
||||||
#: assets/models/user.py:127
|
#: assets/models/user.py:250
|
||||||
msgid "Shell"
|
msgid "Shell"
|
||||||
msgstr "Shell"
|
msgstr "Shell"
|
||||||
|
|
||||||
#: assets/models/user.py:128
|
#: assets/models/user.py:251
|
||||||
msgid "Login mode"
|
msgid "Login mode"
|
||||||
msgstr "登录模式"
|
msgstr "认证方式"
|
||||||
|
|
||||||
#: assets/models/user.py:130
|
#: assets/models/user.py:253
|
||||||
msgid "SFTP Root"
|
msgid "SFTP Root"
|
||||||
msgstr "SFTP根路径"
|
msgstr "SFTP根路径"
|
||||||
|
|
||||||
#: assets/models/user.py:131 authentication/models.py:95
|
#: assets/models/user.py:254 authentication/models.py:95
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/models/user.py:132
|
#: assets/models/user.py:255
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "家目录"
|
msgstr "家目录"
|
||||||
|
|
||||||
#: assets/models/user.py:133
|
#: assets/models/user.py:256
|
||||||
msgid "System groups"
|
msgid "System groups"
|
||||||
msgstr "用户组"
|
msgstr "用户组"
|
||||||
|
|
||||||
|
@ -794,23 +773,19 @@ msgstr "可连接"
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr "未知"
|
msgstr "未知"
|
||||||
|
|
||||||
#: assets/serializers/asset.py:23
|
#: assets/serializers/asset.py:22
|
||||||
msgid "Protocol format should {}/{}"
|
msgid "Protocol format should {}/{}"
|
||||||
msgstr "协议格式 {}/{}"
|
msgstr "协议格式 {}/{}"
|
||||||
|
|
||||||
#: assets/serializers/asset.py:40
|
#: assets/serializers/asset.py:39
|
||||||
msgid "Protocol duplicate: {}"
|
msgid "Protocol duplicate: {}"
|
||||||
msgstr "协议重复: {}"
|
msgstr "协议重复: {}"
|
||||||
|
|
||||||
#: assets/serializers/asset.py:69
|
#: assets/serializers/asset.py:71
|
||||||
msgid "Domain name"
|
msgid "Domain name"
|
||||||
msgstr "网域名称"
|
msgstr "网域名称"
|
||||||
|
|
||||||
#: assets/serializers/asset.py:70
|
#: assets/serializers/asset.py:72 perms/serializers/asset/permission.py:49
|
||||||
msgid "Admin user name"
|
|
||||||
msgstr "管理用户名称"
|
|
||||||
|
|
||||||
#: assets/serializers/asset.py:71 perms/serializers/asset/permission.py:49
|
|
||||||
msgid "Nodes name"
|
msgid "Nodes name"
|
||||||
msgstr "节点名称"
|
msgstr "节点名称"
|
||||||
|
|
||||||
|
@ -822,33 +797,10 @@ msgstr "硬件信息"
|
||||||
msgid "Org name"
|
msgid "Org name"
|
||||||
msgstr "组织名称"
|
msgstr "组织名称"
|
||||||
|
|
||||||
#: assets/serializers/asset.py:162 assets/serializers/asset.py:194
|
#: assets/serializers/asset.py:171 assets/serializers/asset.py:203
|
||||||
msgid "Connectivity"
|
msgid "Connectivity"
|
||||||
msgstr "连接"
|
msgstr "连接"
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:45
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:30
|
|
||||||
#: users/serializers/group.py:37
|
|
||||||
msgid "ID"
|
|
||||||
msgstr "ID"
|
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:49
|
|
||||||
msgid "Backend"
|
|
||||||
msgstr "后端"
|
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:50 users/models/user.py:596
|
|
||||||
msgid "Source"
|
|
||||||
msgstr "来源"
|
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:82 users/forms/profile.py:160
|
|
||||||
#: users/models/user.py:580 users/templates/users/user_password_update.html:48
|
|
||||||
msgid "Public key"
|
|
||||||
msgstr "SSH公钥"
|
|
||||||
|
|
||||||
#: assets/serializers/asset_user.py:86 users/models/user.py:577
|
|
||||||
msgid "Private key"
|
|
||||||
msgstr "ssh私钥"
|
|
||||||
|
|
||||||
#: assets/serializers/base.py:47
|
#: assets/serializers/base.py:47
|
||||||
msgid "private key invalid"
|
msgid "private key invalid"
|
||||||
msgstr "密钥不合法"
|
msgstr "密钥不合法"
|
||||||
|
@ -1124,7 +1076,7 @@ msgstr "启用"
|
||||||
msgid "-"
|
msgid "-"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: audits/models.py:97 xpack/plugins/cloud/const.py:27
|
#: audits/models.py:97 xpack/plugins/cloud/const.py:25
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr "失败"
|
msgstr "失败"
|
||||||
|
|
||||||
|
@ -1455,6 +1407,11 @@ msgstr "使用api key签名请求头,每个请求的头部是不一样的"
|
||||||
msgid "docs"
|
msgid "docs"
|
||||||
msgstr "文档"
|
msgstr "文档"
|
||||||
|
|
||||||
|
#: authentication/templates/authentication/_access_key_modal.html:30
|
||||||
|
#: users/serializers/group.py:37
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:31
|
#: authentication/templates/authentication/_access_key_modal.html:31
|
||||||
msgid "Secret"
|
msgid "Secret"
|
||||||
msgstr "秘钥"
|
msgstr "秘钥"
|
||||||
|
@ -3927,6 +3884,11 @@ msgstr "不能和原来的密钥相同"
|
||||||
msgid "Not a valid ssh public key"
|
msgid "Not a valid ssh public key"
|
||||||
msgstr "SSH密钥不合法"
|
msgstr "SSH密钥不合法"
|
||||||
|
|
||||||
|
#: users/forms/profile.py:160 users/models/user.py:580
|
||||||
|
#: users/templates/users/user_password_update.html:48
|
||||||
|
msgid "Public key"
|
||||||
|
msgstr "SSH公钥"
|
||||||
|
|
||||||
#: users/models/user.py:174
|
#: users/models/user.py:174
|
||||||
msgid "System administrator"
|
msgid "System administrator"
|
||||||
msgstr "系统管理员"
|
msgstr "系统管理员"
|
||||||
|
@ -3951,6 +3913,14 @@ msgstr "头像"
|
||||||
msgid "Wechat"
|
msgid "Wechat"
|
||||||
msgstr "微信"
|
msgstr "微信"
|
||||||
|
|
||||||
|
#: users/models/user.py:577
|
||||||
|
msgid "Private key"
|
||||||
|
msgstr "ssh私钥"
|
||||||
|
|
||||||
|
#: users/models/user.py:596
|
||||||
|
msgid "Source"
|
||||||
|
msgstr "来源"
|
||||||
|
|
||||||
#: users/models/user.py:600
|
#: users/models/user.py:600
|
||||||
msgid "Date password last updated"
|
msgid "Date password last updated"
|
||||||
msgstr "最后更新密码日期"
|
msgstr "最后更新密码日期"
|
||||||
|
@ -4061,7 +4031,7 @@ msgid "Security token validation"
|
||||||
msgstr "安全令牌验证"
|
msgstr "安全令牌验证"
|
||||||
|
|
||||||
#: users/templates/users/_base_otp.html:14 xpack/plugins/cloud/models.py:78
|
#: users/templates/users/_base_otp.html:14 xpack/plugins/cloud/models.py:78
|
||||||
#: xpack/plugins/cloud/serializers.py:180
|
#: xpack/plugins/cloud/serializers.py:145
|
||||||
msgid "Account"
|
msgid "Account"
|
||||||
msgstr "账户"
|
msgstr "账户"
|
||||||
|
|
||||||
|
@ -4762,39 +4732,31 @@ msgstr ""
|
||||||
msgid "Nutanix"
|
msgid "Nutanix"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:17
|
#: xpack/plugins/cloud/const.py:20
|
||||||
msgid "Huawei Private Cloud"
|
|
||||||
msgstr "华为私有云"
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:18
|
|
||||||
msgid "Qingyun Private Cloud"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:22
|
|
||||||
msgid "Instance name"
|
msgid "Instance name"
|
||||||
msgstr "实例名称"
|
msgstr "实例名称"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:23
|
#: xpack/plugins/cloud/const.py:21
|
||||||
msgid "Instance name and Partial IP"
|
msgid "Instance name and Partial IP"
|
||||||
msgstr "实例名称和部分IP"
|
msgstr "实例名称和部分IP"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:28
|
#: xpack/plugins/cloud/const.py:26
|
||||||
msgid "Succeed"
|
msgid "Succeed"
|
||||||
msgstr "成功"
|
msgstr "成功"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:32
|
#: xpack/plugins/cloud/const.py:30
|
||||||
msgid "Unsync"
|
msgid "Unsync"
|
||||||
msgstr "未同步"
|
msgstr "未同步"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:33
|
#: xpack/plugins/cloud/const.py:31
|
||||||
msgid "New Sync"
|
msgid "New Sync"
|
||||||
msgstr "新同步"
|
msgstr "新同步"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:34
|
#: xpack/plugins/cloud/const.py:32
|
||||||
msgid "Synced"
|
msgid "Synced"
|
||||||
msgstr "已同步"
|
msgstr "已同步"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/const.py:35
|
#: xpack/plugins/cloud/const.py:33
|
||||||
msgid "Released"
|
msgid "Released"
|
||||||
msgstr "已释放"
|
msgstr "已释放"
|
||||||
|
|
||||||
|
@ -4810,7 +4772,7 @@ msgstr "云服务商"
|
||||||
msgid "Cloud account"
|
msgid "Cloud account"
|
||||||
msgstr "云账号"
|
msgstr "云账号"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:161
|
#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:126
|
||||||
msgid "Regions"
|
msgid "Regions"
|
||||||
msgstr "地域"
|
msgstr "地域"
|
||||||
|
|
||||||
|
@ -4818,7 +4780,7 @@ msgstr "地域"
|
||||||
msgid "Hostname strategy"
|
msgid "Hostname strategy"
|
||||||
msgstr "主机名策略"
|
msgstr "主机名策略"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:184
|
#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:149
|
||||||
msgid "Always update"
|
msgid "Always update"
|
||||||
msgstr "总是更新"
|
msgstr "总是更新"
|
||||||
|
|
||||||
|
@ -5010,28 +4972,20 @@ msgstr ""
|
||||||
msgid "Subscription ID"
|
msgid "Subscription ID"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers.py:49
|
#: xpack/plugins/cloud/serializers.py:124
|
||||||
msgid "This field is required"
|
|
||||||
msgstr "这个字段是必填项"
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers.py:83 xpack/plugins/cloud/serializers.py:87
|
|
||||||
msgid "API Endpoint"
|
|
||||||
msgstr "API 端点"
|
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers.py:159
|
|
||||||
msgid "History count"
|
msgid "History count"
|
||||||
msgstr "执行次数"
|
msgstr "执行次数"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers.py:160
|
#: xpack/plugins/cloud/serializers.py:125
|
||||||
msgid "Instance count"
|
msgid "Instance count"
|
||||||
msgstr "实例个数"
|
msgstr "实例个数"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/serializers.py:183
|
#: xpack/plugins/cloud/serializers.py:148
|
||||||
#: xpack/plugins/gathered_user/serializers.py:20
|
#: xpack/plugins/gathered_user/serializers.py:20
|
||||||
msgid "Periodic display"
|
msgid "Periodic display"
|
||||||
msgstr "定时执行"
|
msgstr "定时执行"
|
||||||
|
|
||||||
#: xpack/plugins/cloud/utils.py:65
|
#: xpack/plugins/cloud/utils.py:64
|
||||||
msgid "Account unavailable"
|
msgid "Account unavailable"
|
||||||
msgstr "账户无效"
|
msgstr "账户无效"
|
||||||
|
|
||||||
|
@ -5118,3 +5072,36 @@ msgstr "旗舰版"
|
||||||
#: xpack/plugins/license/models.py:77
|
#: xpack/plugins/license/models.py:77
|
||||||
msgid "Community edition"
|
msgid "Community edition"
|
||||||
msgstr "社区版"
|
msgstr "社区版"
|
||||||
|
|
||||||
|
#~ msgid "Deleted failed, There are related assets"
|
||||||
|
#~ msgstr "删除失败,存在关联资产"
|
||||||
|
|
||||||
|
#~ msgid "System user(Dynamic)"
|
||||||
|
#~ msgstr "系统用户(动态)"
|
||||||
|
|
||||||
|
#~ msgid "Could not remove asset admin user"
|
||||||
|
#~ msgstr "不能移除资产的管理用户账号"
|
||||||
|
|
||||||
|
#~ msgid "Latest version could not be delete"
|
||||||
|
#~ msgstr "最新版本的不能被删除"
|
||||||
|
|
||||||
|
#~ msgid "Bulk delete deny"
|
||||||
|
#~ msgstr "拒绝批量删除"
|
||||||
|
|
||||||
|
#~ msgid "Admin user name"
|
||||||
|
#~ msgstr "管理用户名称"
|
||||||
|
|
||||||
|
#~ msgid "Backend"
|
||||||
|
#~ msgstr "后端"
|
||||||
|
|
||||||
|
#~ msgid "Huawei Private Cloud"
|
||||||
|
#~ msgstr "华为私有云"
|
||||||
|
|
||||||
|
#~ msgid "This field is required"
|
||||||
|
#~ msgstr "这个字段是必填项"
|
||||||
|
|
||||||
|
#~ msgid "API Endpoint"
|
||||||
|
#~ msgstr "API 端点"
|
||||||
|
|
||||||
|
#~ msgid "Terminal command alert"
|
||||||
|
#~ msgstr "终端命令告警"
|
||||||
|
|
|
@ -31,7 +31,11 @@ class JMSBaseInventory(BaseInventory):
|
||||||
if run_as_admin:
|
if run_as_admin:
|
||||||
info.update(asset.get_auth_info())
|
info.update(asset.get_auth_info())
|
||||||
if asset.is_unixlike():
|
if asset.is_unixlike():
|
||||||
info["become"] = asset.admin_user.become_info
|
info["become"] = {
|
||||||
|
"method": 'sudo',
|
||||||
|
"user": 'root',
|
||||||
|
"pass": ''
|
||||||
|
}
|
||||||
if asset.is_windows():
|
if asset.is_windows():
|
||||||
info["vars"].update({
|
info["vars"].update({
|
||||||
"ansible_connection": "ssh",
|
"ansible_connection": "ssh",
|
||||||
|
@ -103,8 +107,6 @@ class JMSInventory(JMSBaseInventory):
|
||||||
super().__init__(host_list=host_list)
|
super().__init__(host_list=host_list)
|
||||||
|
|
||||||
def get_run_user_info(self, host):
|
def get_run_user_info(self, host):
|
||||||
from assets.backends import AssetUserManager
|
|
||||||
|
|
||||||
if not self.run_as and not self.system_user:
|
if not self.run_as and not self.system_user:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -112,17 +114,12 @@ class JMSInventory(JMSBaseInventory):
|
||||||
asset = self.assets.filter(id=asset_id).first()
|
asset = self.assets.filter(id=asset_id).first()
|
||||||
if not asset:
|
if not asset:
|
||||||
logger.error('Host not found: ', asset_id)
|
logger.error('Host not found: ', asset_id)
|
||||||
|
return {}
|
||||||
|
|
||||||
if self.system_user:
|
if self.system_user:
|
||||||
self.system_user.load_asset_special_auth(asset=asset, username=self.run_as)
|
self.system_user.load_asset_special_auth(asset=asset, username=self.run_as)
|
||||||
return self.system_user._to_secret_json()
|
return self.system_user._to_secret_json()
|
||||||
|
else:
|
||||||
try:
|
|
||||||
manager = AssetUserManager()
|
|
||||||
run_user = manager.get_latest(username=self.run_as, asset=asset, prefer='system_user')
|
|
||||||
return run_user._to_secret_json()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e, exc_info=True)
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -286,18 +286,12 @@ class AdHocExecution(OrgModelMixin):
|
||||||
raw = ''
|
raw = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
date_start_s = timezone.now().now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
print(_("{} Start task: {}").format(date_start_s, self.task.name))
|
|
||||||
raw, summary = self.start_runner()
|
raw, summary = self.start_runner()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e, exc_info=True)
|
logger.error(e, exc_info=True)
|
||||||
raw = {"dark": {"all": str(e)}, "contacted": []}
|
raw = {"dark": {"all": str(e)}, "contacted": []}
|
||||||
finally:
|
finally:
|
||||||
self.clean_up(summary, time_start)
|
self.clean_up(summary, time_start)
|
||||||
date_end = timezone.now().now()
|
|
||||||
date_end_s = date_end.strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
print(_("{} Task finish").format(date_end_s))
|
|
||||||
print('.\n\n.')
|
|
||||||
return raw, summary
|
return raw, summary
|
||||||
|
|
||||||
def clean_up(self, summary, time_start):
|
def clean_up(self, summary, time_start):
|
||||||
|
|
|
@ -9,10 +9,10 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import lazyproperty, settings
|
from common.utils import lazyproperty, settings
|
||||||
from common.const import choices
|
from common.const import choices
|
||||||
from common.db.models import ChoiceSet
|
from common.db.models import TextChoices
|
||||||
|
|
||||||
|
|
||||||
class ROLE(ChoiceSet):
|
class ROLE(TextChoices):
|
||||||
ADMIN = choices.ADMIN, _('Organization administrator')
|
ADMIN = choices.ADMIN, _('Organization administrator')
|
||||||
AUDITOR = choices.AUDITOR, _("Organization auditor")
|
AUDITOR = choices.AUDITOR, _("Organization auditor")
|
||||||
USER = choices.USER, _('User')
|
USER = choices.USER, _('User')
|
||||||
|
|
|
@ -4,7 +4,6 @@ from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from users.models.user import User
|
from users.models.user import User
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from common.drf.serializers import BulkModelSerializer
|
from common.drf.serializers import BulkModelSerializer
|
||||||
from common.db.models import concated_display as display
|
from common.db.models import concated_display as display
|
||||||
from .models import Organization, OrganizationMember, ROLE
|
from .models import Organization, OrganizationMember, ROLE
|
||||||
|
@ -35,7 +34,6 @@ class OrgSerializer(ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Organization
|
model = Organization
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
fields_mini = ['id', 'name']
|
fields_mini = ['id', 'name']
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'resource_statistics',
|
'resource_statistics',
|
||||||
|
|
|
@ -36,8 +36,8 @@ class OrgsMappingForMemoryPubSub(LazyObject):
|
||||||
orgs_mapping_for_memory_pub_sub = OrgsMappingForMemoryPubSub()
|
orgs_mapping_for_memory_pub_sub = OrgsMappingForMemoryPubSub()
|
||||||
|
|
||||||
|
|
||||||
def expire_orgs_mapping_for_memory():
|
def expire_orgs_mapping_for_memory(org_id):
|
||||||
orgs_mapping_for_memory_pub_sub.publish('expire_orgs_mapping')
|
orgs_mapping_for_memory_pub_sub.publish(str(org_id))
|
||||||
|
|
||||||
|
|
||||||
@receiver(django_ready)
|
@receiver(django_ready)
|
||||||
|
@ -54,7 +54,7 @@ def subscribe_orgs_mapping_expire(sender, **kwargs):
|
||||||
if message['data'] == b'error':
|
if message['data'] == b'error':
|
||||||
raise ValueError
|
raise ValueError
|
||||||
Organization.expire_orgs_mapping()
|
Organization.expire_orgs_mapping()
|
||||||
logger.debug('Expire orgs mapping')
|
logger.debug('Expire orgs mapping: ' + str(message['data']))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f'subscribe_orgs_mapping_expire: {e}')
|
logger.exception(f'subscribe_orgs_mapping_expire: {e}')
|
||||||
Organization.expire_orgs_mapping()
|
Organization.expire_orgs_mapping()
|
||||||
|
@ -65,10 +65,9 @@ def subscribe_orgs_mapping_expire(sender, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Organization)
|
@receiver(post_save, sender=Organization)
|
||||||
def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
|
def on_org_create_or_update(sender, instance, created=False, **kwargs):
|
||||||
# 必须放到最开始, 因为下面调用Node.save方法时会获取当前组织的org_id(即instance.org_id), 如果不过期会找不到
|
# 必须放到最开始, 因为下面调用Node.save方法时会获取当前组织的org_id(即instance.org_id), 如果不过期会找不到
|
||||||
expire_orgs_mapping_for_memory()
|
expire_orgs_mapping_for_memory(instance.id)
|
||||||
if instance:
|
|
||||||
old_org = get_current_org()
|
old_org = get_current_org()
|
||||||
set_current_org(instance)
|
set_current_org(instance)
|
||||||
node_root = Node.org_root()
|
node_root = Node.org_root()
|
||||||
|
@ -78,9 +77,9 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
|
||||||
set_current_org(old_org)
|
set_current_org(old_org)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Organization)
|
@receiver(pre_delete, sender=Organization)
|
||||||
def on_org_delete(sender, **kwargs):
|
def on_org_delete(sender, instance, **kwargs):
|
||||||
expire_orgs_mapping_for_memory()
|
expire_orgs_mapping_for_memory(instance.id)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Organization)
|
@receiver(pre_delete, sender=Organization)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from functools import reduce
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
|
||||||
from common.db.models import ChoiceSet
|
from common.db.models import TextChoices
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from common.db import models
|
from common.db import models
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
|
@ -165,7 +165,7 @@ class AssetPermission(BasePermission):
|
||||||
|
|
||||||
|
|
||||||
class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, models.JMSBaseModel):
|
class UserAssetGrantedTreeNodeRelation(OrgModelMixin, FamilyMixin, models.JMSBaseModel):
|
||||||
class NodeFrom(ChoiceSet):
|
class NodeFrom(TextChoices):
|
||||||
granted = 'granted', 'Direct node granted'
|
granted = 'granted', 'Direct node granted'
|
||||||
child = 'child', 'Have children node'
|
child = 'child', 'Have children node'
|
||||||
asset = 'asset', 'Direct asset granted'
|
asset = 'asset', 'Direct asset granted'
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from perms.models import ApplicationPermission
|
from perms.models import ApplicationPermission
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -24,14 +23,11 @@ class RelationMixin(BulkSerializerMixin, serializers.Serializer):
|
||||||
fields.extend(['applicationpermission', "applicationpermission_display"])
|
fields.extend(['applicationpermission', "applicationpermission_display"])
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
class Meta:
|
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class ApplicationPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
user_display = serializers.ReadOnlyField()
|
user_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = ApplicationPermission.users.through
|
model = ApplicationPermission.users.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user', 'user_display',
|
'id', 'user', 'user_display',
|
||||||
|
@ -41,7 +37,7 @@ class ApplicationPermissionUserRelationSerializer(RelationMixin, serializers.Mod
|
||||||
class ApplicationPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class ApplicationPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
usergroup_display = serializers.ReadOnlyField()
|
usergroup_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = ApplicationPermission.user_groups.through
|
model = ApplicationPermission.user_groups.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'usergroup', "usergroup_display",
|
'id', 'usergroup', "usergroup_display",
|
||||||
|
@ -51,7 +47,7 @@ class ApplicationPermissionUserGroupRelationSerializer(RelationMixin, serializer
|
||||||
class ApplicationPermissionApplicationRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class ApplicationPermissionApplicationRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
application_display = serializers.ReadOnlyField()
|
application_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = ApplicationPermission.applications.through
|
model = ApplicationPermission.applications.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', "application", "application_display",
|
'id', "application", "application_display",
|
||||||
|
@ -61,7 +57,7 @@ class ApplicationPermissionApplicationRelationSerializer(RelationMixin, serializ
|
||||||
class ApplicationPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class ApplicationPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
systemuser_display = serializers.ReadOnlyField()
|
systemuser_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = ApplicationPermission.system_users.through
|
model = ApplicationPermission.system_users.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'systemuser', 'systemuser_display'
|
'id', 'systemuser', 'systemuser_display'
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from assets.models import Asset, Node
|
from assets.models import Asset, Node
|
||||||
from perms.models import AssetPermission
|
from perms.models import AssetPermission
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
@ -37,14 +36,11 @@ class RelationMixin(BulkSerializerMixin, serializers.Serializer):
|
||||||
fields.extend(['assetpermission', "assetpermission_display"])
|
fields.extend(['assetpermission', "assetpermission_display"])
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
class Meta:
|
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class AssetPermissionUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
user_display = serializers.ReadOnlyField()
|
user_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = AssetPermission.users.through
|
model = AssetPermission.users.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'user', 'user_display',
|
'id', 'user', 'user_display',
|
||||||
|
@ -66,7 +62,7 @@ class AssetPermissionAllUserSerializer(serializers.Serializer):
|
||||||
class AssetPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class AssetPermissionUserGroupRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
usergroup_display = serializers.ReadOnlyField()
|
usergroup_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = AssetPermission.user_groups.through
|
model = AssetPermission.user_groups.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'usergroup', "usergroup_display",
|
'id', 'usergroup', "usergroup_display",
|
||||||
|
@ -76,7 +72,7 @@ class AssetPermissionUserGroupRelationSerializer(RelationMixin, serializers.Mode
|
||||||
class AssetPermissionAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class AssetPermissionAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
asset_display = serializers.ReadOnlyField()
|
asset_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = AssetPermission.assets.through
|
model = AssetPermission.assets.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', "asset", "asset_display",
|
'id', "asset", "asset_display",
|
||||||
|
@ -98,7 +94,7 @@ class AssetPermissionAllAssetSerializer(serializers.Serializer):
|
||||||
class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
node_display = serializers.CharField(source='node.full_value', read_only=True)
|
node_display = serializers.CharField(source='node.full_value', read_only=True)
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = AssetPermission.nodes.through
|
model = AssetPermission.nodes.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'node', "node_display",
|
'id', 'node', "node_display",
|
||||||
|
@ -108,7 +104,7 @@ class AssetPermissionNodeRelationSerializer(RelationMixin, serializers.ModelSeri
|
||||||
class AssetPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
class AssetPermissionSystemUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||||
systemuser_display = serializers.ReadOnlyField()
|
systemuser_display = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta(RelationMixin.Meta):
|
class Meta:
|
||||||
model = AssetPermission.system_users.through
|
model = AssetPermission.system_users.through
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'systemuser', 'systemuser_display'
|
'id', 'systemuser', 'systemuser_display'
|
||||||
|
|
|
@ -13,17 +13,17 @@ from django.core.cache import cache
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from orgs.mixins.models import OrgModelMixin
|
from orgs.mixins.models import OrgModelMixin
|
||||||
from common.db.models import ChoiceSet
|
from common.db.models import TextChoices
|
||||||
from ..backends import get_multi_command_storage
|
from ..backends import get_multi_command_storage
|
||||||
|
|
||||||
|
|
||||||
class Session(OrgModelMixin):
|
class Session(OrgModelMixin):
|
||||||
class LOGIN_FROM(ChoiceSet):
|
class LOGIN_FROM(TextChoices):
|
||||||
ST = 'ST', 'SSH Terminal'
|
ST = 'ST', 'SSH Terminal'
|
||||||
RT = 'RT', 'RDP Terminal'
|
RT = 'RT', 'RDP Terminal'
|
||||||
WT = 'WT', 'Web Terminal'
|
WT = 'WT', 'Web Terminal'
|
||||||
|
|
||||||
class PROTOCOL(ChoiceSet):
|
class PROTOCOL(TextChoices):
|
||||||
SSH = 'ssh', 'ssh'
|
SSH = 'ssh', 'ssh'
|
||||||
RDP = 'rdp', 'rdp'
|
RDP = 'rdp', 'rdp'
|
||||||
VNC = 'vnc', 'vnc'
|
VNC = 'vnc', 'vnc'
|
||||||
|
|
|
@ -2,7 +2,6 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from ..models import Session
|
from ..models import Session
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -16,7 +15,6 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Session
|
model = Session
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
fields_mini = ["id"]
|
fields_mini = ["id"]
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
"user", "asset", "system_user",
|
"user", "asset", "system_user",
|
||||||
|
|
|
@ -85,7 +85,6 @@ class TaskSerializer(BulkModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
model = Task
|
model = Task
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
ref_name = 'TerminalTaskSerializer'
|
ref_name = 'TerminalTaskSerializer'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ from orgs.models import OrganizationMember, Organization
|
||||||
from common.utils import date_expired_default, get_logger, lazyproperty, random_string
|
from common.utils import date_expired_default, get_logger, lazyproperty, random_string
|
||||||
from common import fields
|
from common import fields
|
||||||
from common.const import choices
|
from common.const import choices
|
||||||
from common.db.models import ChoiceSet
|
from common.db.models import TextChoices
|
||||||
from users.exceptions import MFANotEnabled
|
from users.exceptions import MFANotEnabled
|
||||||
from ..signals import post_user_change_password
|
from ..signals import post_user_change_password
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ class AuthMixin:
|
||||||
|
|
||||||
|
|
||||||
class RoleMixin:
|
class RoleMixin:
|
||||||
class ROLE(ChoiceSet):
|
class ROLE(TextChoices):
|
||||||
ADMIN = choices.ADMIN, _('System administrator')
|
ADMIN = choices.ADMIN, _('System administrator')
|
||||||
AUDITOR = choices.AUDITOR, _('System auditor')
|
AUDITOR = choices.AUDITOR, _('System auditor')
|
||||||
USER = choices.USER, _('User')
|
USER = choices.USER, _('User')
|
||||||
|
|
|
@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.drf.serializers import AdaptedBulkListSerializer
|
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from ..models import User, UserGroup
|
from ..models import User, UserGroup
|
||||||
|
@ -23,7 +22,6 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserGroup
|
model = UserGroup
|
||||||
list_serializer_class = AdaptedBulkListSerializer
|
|
||||||
fields_mini = ['id', 'name']
|
fields_mini = ['id', 'name']
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'comment', 'date_created', 'created_by'
|
'comment', 'date_created', 'created_by'
|
||||||
|
|
|
@ -113,3 +113,4 @@ termcolor==1.1.0
|
||||||
azure-identity==1.5.0
|
azure-identity==1.5.0
|
||||||
azure-mgmt-subscription==1.0.0
|
azure-mgmt-subscription==1.0.0
|
||||||
qingcloud-sdk==1.2.12
|
qingcloud-sdk==1.2.12
|
||||||
|
django-simple-history==3.0.0
|
|
@ -28,7 +28,7 @@ class AdminUsersGenerator(FakeDataGenerator):
|
||||||
class SystemUsersGenerator(FakeDataGenerator):
|
class SystemUsersGenerator(FakeDataGenerator):
|
||||||
def do_generate(self, batch, batch_size):
|
def do_generate(self, batch, batch_size):
|
||||||
system_users = []
|
system_users = []
|
||||||
protocols = list(dict(SystemUser.PROTOCOL_CHOICES).keys())
|
protocols = list(dict(SystemUser.Protocol.choices).keys())
|
||||||
for i in batch:
|
for i in batch:
|
||||||
username = forgery_py.internet.user_name(True)
|
username = forgery_py.internet.user_name(True)
|
||||||
protocol = random.choice(protocols)
|
protocol = random.choice(protocols)
|
||||||
|
|
Loading…
Reference in New Issue