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>
This commit is contained in:
fit2bot
2021-07-08 14:23:18 +08:00
committed by GitHub
parent a9f814a515
commit ec8dca90d6
72 changed files with 1524 additions and 2210 deletions

View File

@@ -4,9 +4,9 @@ from .asset import *
from .label import *
from .system_user import *
from .system_user_relation import *
from .accounts import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *
from .gathered_user import *
from .favorite_asset import *

View File

@@ -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()

View File

@@ -1,109 +1,28 @@
from django.db import transaction
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 ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
from ..models import SystemUser
from .. import serializers
from ..tasks import test_admin_user_connectivity_manual
logger = get_logger(__file__)
__all__ = [
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
'AdminUserAssetsListView',
]
__all__ = ['AdminUserViewSet']
# 兼容一下老的 api
class AdminUserViewSet(OrgBulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""
model = AdminUser
model = SystemUser
filterset_fields = ("name", "username")
search_fields = filterset_fields
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,)
serializer_classes = {
'default': serializers.AdminUserSerializer,
'retrieve': serializers.AdminUserDetailSerializer,
}
def get_queryset(self):
queryset = super().get_queryset()
queryset = super().get_queryset().filter(type=SystemUser.Type.admin)
queryset = queryset.annotate(assets_amount=Count('assets'))
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()

View File

@@ -33,8 +33,7 @@ class AssetViewSet(FilterAssetByNodeMixin, OrgBulkModelViewSet):
filterset_fields = {
'hostname': ['exact'],
'ip': ['exact'],
'systemuser__id': ['exact'],
'admin_user__id': ['exact'],
'system_users__id': ['exact'],
'platform__base': ['exact'],
'is_active': ['exact'],
'protocols': ['exact', 'icontains']
@@ -43,7 +42,7 @@ class AssetViewSet(FilterAssetByNodeMixin, OrgBulkModelViewSet):
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
serializer_classes = {
'default': serializers.AssetSerializer,
'display': serializers.AssetDisplaySerializer,
'single': serializers.AssetVerboseSerializer,
}
permission_classes = (IsOrgAdminOrAppUser,)
extra_filter_backends = [FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]

View File

@@ -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

View File

@@ -2,14 +2,12 @@
#
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 common.utils import reverse
from common.utils import lazyproperty
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 ..hands import IsOrgAdmin, IsAppUser
from ..models import CommandFilter, CommandFilterRule

View File

@@ -32,7 +32,8 @@ class SystemUserViewSet(OrgBulkModelViewSet):
filterset_fields = {
'name': ['exact'],
'username': ['exact'],
'protocol': ['exact', 'in']
'protocol': ['exact', 'in'],
'type': ['exact', 'in'],
}
search_fields = filterset_fields
serializer_class = serializers.SystemUserSerializer

View File

@@ -6,6 +6,7 @@ from django.db.models.signals import m2m_changed
from django.db.models.functions import Concat
from common.permissions import IsOrgAdmin
from common.utils import get_logger
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
from .. import models, serializers
@@ -15,6 +16,8 @@ __all__ = [
'SystemUserUserRelationViewSet',
]
logger = get_logger(__name__)
class RelationMixin:
def get_queryset(self):
@@ -24,8 +27,8 @@ class RelationMixin:
queryset = queryset.filter(systemuser__org_id=org_id)
queryset = queryset.annotate(systemuser_display=Concat(
F('systemuser__name'), Value('('), F('systemuser__username'),
Value(')')
F('systemuser__name'), Value('('),
F('systemuser__username'), Value(')')
))
return queryset
@@ -41,10 +44,11 @@ class RelationMixin:
system_users_objects_map[i.systemuser].append(_id)
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(
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):