diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 692dac8c8..f5773179c 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -3,42 +3,18 @@ from rest_framework.generics import ListAPIView, get_object_or_404 from common.permissions import IsValidUser from common.utils import get_logger, lazyproperty -from assets.serializers import AccountSerializer -from perms.hands import User, Asset, Account from perms import serializers +from perms.hands import User, Asset from perms.utils import PermAccountUtil -from .mixin import RoleAdminMixin, RoleUserMixin logger = get_logger(__name__) - __all__ = [ - 'UserAllGrantedAccountsApi', - 'MyAllGrantedAccountsApi', 'UserGrantedAssetAccountsApi', 'MyGrantedAssetAccountsApi', - 'UserGrantedAssetSpecialAccountsApi', - 'MyGrantedAssetSpecialAccountsApi', ] -class UserAllGrantedAccountsApi(RoleAdminMixin, ListAPIView): - """ 授权给用户的所有账号列表 """ - serializer_class = AccountSerializer - filterset_fields = ("name", "username", "privileged", "version") - search_fields = filterset_fields - - def get_queryset(self): - util = PermAccountUtil() - accounts = util.get_perm_accounts_for_user(self.user) - return accounts - - -class MyAllGrantedAccountsApi(RoleUserMixin, UserAllGrantedAccountsApi): - """ 授权给我的所有账号列表 """ - pass - - class UserGrantedAssetAccountsApi(ListAPIView): serializer_class = serializers.AccountsGrantedSerializer @@ -55,9 +31,8 @@ class UserGrantedAssetAccountsApi(ListAPIView): return asset def get_queryset(self): - accounts = PermAccountUtil().get_perm_accounts_for_user_asset( - self.user, self.asset, with_actions=True - ) + util = PermAccountUtil() + accounts = util.get_permed_accounts_for_user(self.user, self.asset) return accounts @@ -67,29 +42,3 @@ class MyGrantedAssetAccountsApi(UserGrantedAssetAccountsApi): @lazyproperty def user(self): return self.request.user - - -class UserGrantedAssetSpecialAccountsApi(ListAPIView): - serializer_class = serializers.AccountsGrantedSerializer - - @lazyproperty - def user(self): - return self.request.user - - def get_queryset(self): - # 构造默认包含的账号,如: @INPUT @USER - accounts = [ - Account.get_manual_account(), - Account.get_user_account(self.user.username) - ] - for account in accounts: - account.actions = Action.ALL - return accounts - - -class MyGrantedAssetSpecialAccountsApi(UserGrantedAssetSpecialAccountsApi): - permission_classes = (IsValidUser,) - - @lazyproperty - def user(self): - return self.request.user diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index 09eb97428..6cee0e793 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers -from common.drf.fields import ObjectRelatedField, LabeledChoiceField -from assets.models import Node, Asset, Platform, Account from assets.const import Category, AllTypes +from assets.models import Node, Asset, Platform, Account +from common.drf.fields import ObjectRelatedField, LabeledChoiceField from perms.serializers.permission import ActionChoicesField __all__ = [ @@ -49,13 +49,9 @@ class ActionsSerializer(serializers.Serializer): class AccountsGrantedSerializer(serializers.ModelSerializer): - """ 授权的账号序列类 """ - - # Todo: 添加前端登录逻辑中需要的一些字段,比如:是否需要手动输入密码 - # need_manual = serializers.BooleanField(label=_('Need manual input')) actions = ActionChoicesField(read_only=True) class Meta: model = Account - fields = ['id', 'name', 'username', 'actions'] + fields = ['id', 'name', 'username', 'secret_type', 'has_secret', 'actions'] read_only_fields = fields diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index 99605372d..f402372df 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -7,10 +7,14 @@ from .. import api router = BulkRouter() router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') -router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, 'asset-permissions-users-relation') -router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, 'asset-permissions-user-groups-relation') -router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, 'asset-permissions-assets-relation') -router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, 'asset-permissions-nodes-relation') +router.register('asset-permissions-users-relations', api.AssetPermissionUserRelationViewSet, + 'asset-permissions-users-relation') +router.register('asset-permissions-user-groups-relations', api.AssetPermissionUserGroupRelationViewSet, + 'asset-permissions-user-groups-relation') +router.register('asset-permissions-assets-relations', api.AssetPermissionAssetRelationViewSet, + 'asset-permissions-assets-relation') +router.register('asset-permissions-nodes-relations', api.AssetPermissionNodeRelationViewSet, + 'asset-permissions-nodes-relation') user_permission_urlpatterns = [ # 以 serializer 格式返回 @@ -34,18 +38,22 @@ user_permission_urlpatterns = [ path('/nodes/children/', api.UserGrantedNodeChildrenForAdminApi.as_view(), name='user-nodes-children'), path('nodes/children/', api.MyGrantedNodeChildrenApi.as_view(), name='my-nodes-children'), # 以 Tree Node 的数据格式返回 - path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), name='user-nodes-children-as-tree'), + path('/nodes/children/tree/', api.UserGrantedNodeChildrenAsTreeForAdminApi.as_view(), + name='user-nodes-children-as-tree'), # 部分调用位置 # - 普通用户 -> 我的资产 -> 展开节点 时调用 path('nodes/children/tree/', api.MyGrantedNodeChildrenAsTreeApi.as_view(), name='my-nodes-children-as-tree'), # 此接口会返回整棵树 # 普通用户 -> 命令执行 -> 左侧树 - path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-with-assets-as-tree'), + path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), + name='my-nodes-with-assets-as-tree'), # 主要用于 luna 页面,带资产的节点树 - path('/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='user-nodes-children-with-assets-as-tree'), - path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), name='my-nodes-children-with-assets-as-tree'), + path('/nodes/children-with-assets/tree/', api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='user-nodes-children-with-assets-as-tree'), + path('nodes/children-with-assets/tree/', api.MyGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='my-nodes-children-with-assets-as-tree'), # 查询授权树上某个节点的所有资产 path('/nodes//assets/', api.UserGrantedNodeAssetsApi.as_view(), name='user-node-assets'), @@ -59,16 +67,10 @@ user_permission_urlpatterns = [ path('/nodes/favorite/assets/', api.UserFavoriteGrantedAssetsApi.as_view(), name='user-ungrouped-assets'), path('nodes/favorite/assets/', api.MyFavoriteGrantedAssetsApi.as_view(), name='my-ungrouped-assets'), - # 获取授权给用户的所有账号 - path('/accounts/', api.UserAllGrantedAccountsApi.as_view(), name='user-accounts'), - path('accounts/', api.MyAllGrantedAccountsApi.as_view(), name='my-accounts'), - # 获取授权给用户某个资产的所有账号 - path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), name='user-asset-accounts'), + path('/assets//accounts/', api.UserGrantedAssetAccountsApi.as_view(), + name='user-asset-accounts'), path('assets//accounts/', api.MyGrantedAssetAccountsApi.as_view(), name='my-asset-accounts'), - # 用户登录资产的特殊账号, @INPUT, @USER 等 - path('/assets/special-accounts/', api.UserGrantedAssetSpecialAccountsApi.as_view(), name='user-special-accounts'), - path('assets/special-accounts/', api.MyGrantedAssetSpecialAccountsApi.as_view(), name='my-special-accounts'), ] user_group_permission_urlpatterns = [ @@ -76,11 +78,14 @@ user_group_permission_urlpatterns = [ path('/assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), path('/nodes/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes'), path('/nodes/children/', api.UserGroupGrantedNodesApi.as_view(), name='user-group-nodes-children'), - path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), name='user-group-nodes-children-as-tree'), - path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), name='user-group-node-assets'), + path('/nodes/children/tree/', api.UserGroupGrantedNodeChildrenAsTreeApi.as_view(), + name='user-group-nodes-children-as-tree'), + path('/nodes//assets/', api.UserGroupGrantedNodeAssetsApi.as_view(), + name='user-group-node-assets'), # 获取所有和资产-用户组关联的账号列表 - path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), name='user-group-asset-accounts'), + path('/assets//accounts/', api.UserGroupGrantedAssetAccountsApi.as_view(), + name='user-group-asset-accounts'), ] permission_urlpatterns = [ diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 12b1cab1b..6216f49be 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -2,22 +2,20 @@ # from rest_framework import viewsets from rest_framework.decorators import action -from rest_framework.response import Response from rest_framework.exceptions import MethodNotAllowed +from rest_framework.response import Response from common.const.http import POST, PUT, PATCH from common.mixins.api import CommonApiMixin from orgs.utils import tmp_to_root_org - from rbac.permissions import RBACPermission - -from tickets import serializers from tickets import filters -from tickets.permissions.ticket import IsAssignee, IsApplicant +from tickets import serializers from tickets.models import ( Ticket, ApplyAssetTicket, ApplyLoginTicket, ApplyLoginAssetTicket, ApplyCommandTicket ) +from tickets.permissions.ticket import IsAssignee, IsApplicant __all__ = [ 'TicketViewSet', 'ApplyAssetTicketViewSet', @@ -27,10 +25,8 @@ __all__ = [ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): - serializer_class = serializers.TicketDisplaySerializer + serializer_class = serializers.TicketSerializer serializer_classes = { - 'list': serializers.TicketListSerializer, - 'open': serializers.TicketApplySerializer, 'approve': serializers.TicketApproveSerializer } model = Ticket @@ -40,8 +36,8 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): 'title', 'type', 'status' ] ordering_fields = ( - 'title', 'status', 'state', - 'action_display', 'date_created', 'serial_num', + 'title', 'status', 'state', 'action_display', + 'date_created', 'serial_num', ) ordering = ('-date_created',) rbac_perms = { @@ -98,11 +94,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): class ApplyAssetTicketViewSet(TicketViewSet): - serializer_class = serializers.ApplyAssetDisplaySerializer - serializer_classes = { - 'open': serializers.ApplyAssetSerializer, - 'approve': serializers.ApproveAssetSerializer - } + serializer_class = serializers.ApplyAssetSerializer model = ApplyAssetTicket filterset_class = filters.ApplyAssetTicketFilter diff --git a/apps/tickets/models/ticket/apply_asset.py b/apps/tickets/models/ticket/apply_asset.py index d5f11ee36..1e46cc130 100644 --- a/apps/tickets/models/ticket/apply_asset.py +++ b/apps/tickets/models/ticket/apply_asset.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from perms.const import ActionChoices from .general import Ticket __all__ = ['ApplyAssetTicket'] @@ -14,13 +15,6 @@ class ApplyAssetTicket(Ticket): # 申请信息 apply_assets = models.ManyToManyField('assets.Asset', verbose_name=_('Apply assets')) apply_accounts = models.JSONField(default=list, verbose_name=_('Apply accounts')) - apply_actions = models.IntegerField(default=1, verbose_name=_('Actions')) + apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.all()) apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True) apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True) - - @property - def apply_actions_display(self): - return 'Todo' - - def get_apply_actions_display(self): - return ', '.join(self.apply_actions_display) diff --git a/apps/tickets/serializers/ticket/apply_asset.py b/apps/tickets/serializers/ticket/apply_asset.py index 26a1fe434..69b4371f4 100644 --- a/apps/tickets/serializers/ticket/apply_asset.py +++ b/apps/tickets/serializers/ticket/apply_asset.py @@ -1,40 +1,40 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from perms.serializers.permission import ActionChoicesField -from perms.models import AssetPermission -from orgs.utils import tmp_to_org from assets.models import Asset, Node - +from common.drf.fields import ObjectRelatedField +from perms.models import AssetPermission +from perms.serializers.permission import ActionChoicesField from tickets.models import ApplyAssetTicket +from .common import BaseApplyAssetSerializer from .ticket import TicketApplySerializer -from .common import BaseApplyAssetApplicationSerializer -__all__ = ['ApplyAssetSerializer', 'ApplyAssetDisplaySerializer', 'ApproveAssetSerializer'] +__all__ = ['ApplyAssetSerializer'] asset_or_node_help_text = _("Select at least one asset or node") -class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySerializer): - apply_actions = ActionChoicesField(required=True, allow_empty=False) +class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer): + apply_assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Apply assets')) + apply_nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False, label=_('Apply nodes')) + apply_actions = ActionChoicesField(required=False, allow_null=True, label=_("Apply actions")) permission_model = AssetPermission - class Meta: + class Meta(TicketApplySerializer.Meta): model = ApplyAssetTicket + fields_mini = ['id', 'title'] writeable_fields = [ - 'id', 'title', 'type', 'apply_nodes', 'apply_assets', + 'id', 'title', 'apply_nodes', 'apply_assets', 'apply_accounts', 'apply_actions', 'org_id', 'comment', 'apply_date_start', 'apply_date_expired' ] - fields = TicketApplySerializer.Meta.fields + writeable_fields + [ - 'apply_permission_name', 'apply_actions_display' - ] + fields = TicketApplySerializer.Meta.fields + writeable_fields + ['apply_permission_name', ] read_only_fields = list(set(fields) - set(writeable_fields)) ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs extra_kwargs = { - 'apply_nodes': {'required': False, 'allow_empty': True}, - 'apply_assets': {'required': False, 'allow_empty': True}, - 'apply_accounts': {'required': False, 'allow_empty': True}, + 'apply_nodes': {'required': False}, + 'apply_assets': {'required': False}, + 'apply_accounts': {'required': False}, } extra_kwargs.update(ticket_extra_kwargs) @@ -45,9 +45,11 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria return self.filter_many_to_many_field(Asset, assets) def validate(self, attrs): + attrs['type'] = 'apply_asset' attrs = super().validate(attrs) if self.is_final_approval and ( - not attrs.get('apply_nodes') and not attrs.get('apply_assets') + not attrs.get('apply_nodes') + and not attrs.get('apply_assets') ): raise serializers.ValidationError({ 'apply_nodes': asset_or_node_help_text, @@ -56,29 +58,7 @@ class ApplyAssetSerializer(BaseApplyAssetApplicationSerializer, TicketApplySeria return attrs - -class ApproveAssetSerializer(ApplyAssetSerializer): - class Meta(ApplyAssetSerializer.Meta): - read_only_fields = ApplyAssetSerializer.Meta.read_only_fields + [ - 'title', 'type' - ] - - -class ApplyAssetDisplaySerializer(ApplyAssetSerializer): - apply_nodes = serializers.SerializerMethodField() - apply_assets = serializers.SerializerMethodField() - - class Meta: - model = ApplyAssetSerializer.Meta.model - fields = ApplyAssetSerializer.Meta.fields - read_only_fields = fields - - @staticmethod - def get_apply_nodes(instance): - with tmp_to_org(instance.org_id): - return instance.apply_nodes.values_list('id', flat=True) - - @staticmethod - def get_apply_assets(instance): - with tmp_to_org(instance.org_id): - return instance.apply_assets.values_list('id', flat=True) + @classmethod + def setup_eager_loading(cls, queryset): + queryset = queryset.prefetch_related('apply_nodes', 'apply_assets') + return queryset diff --git a/apps/tickets/serializers/ticket/common.py b/apps/tickets/serializers/ticket/common.py index f38f97a43..7cbaea697 100644 --- a/apps/tickets/serializers/ticket/common.py +++ b/apps/tickets/serializers/ticket/common.py @@ -1,12 +1,12 @@ -from django.db.transaction import atomic from django.db.models import Model +from django.db.transaction import atomic from django.utils.translation import ugettext as _ from rest_framework import serializers from orgs.utils import tmp_to_org from tickets.models import Ticket -__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetApplicationSerializer'] +__all__ = ['DefaultPermissionName', 'get_default_permission_name', 'BaseApplyAssetSerializer'] def get_default_permission_name(ticket): @@ -34,7 +34,7 @@ class DefaultPermissionName(object): return self.default -class BaseApplyAssetApplicationSerializer(serializers.Serializer): +class BaseApplyAssetSerializer(serializers.Serializer): permission_model: Model @property diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index 461e61870..f201c2edc 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -3,31 +3,35 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from orgs.models import Organization +from common.drf.fields import LabeledChoiceField from orgs.mixins.serializers import OrgResourceModelSerializerMixin +from orgs.models import Organization +from tickets.const import TicketType, TicketStatus, TicketState from tickets.models import Ticket, TicketFlow -from tickets.const import TicketType __all__ = [ - 'TicketDisplaySerializer', 'TicketApplySerializer', 'TicketListSerializer', 'TicketApproveSerializer' + 'TicketApplySerializer', 'TicketApproveSerializer', 'TicketSerializer', ] class TicketSerializer(OrgResourceModelSerializerMixin): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) - status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status display')) + type = LabeledChoiceField(choices=TicketType.choices, read_only=True, label=_('Type')) + status = LabeledChoiceField(choices=TicketStatus.choices, read_only=True, label=_('Status')) + state = LabeledChoiceField(choices=TicketState.choices, read_only=True, label=_("State")) class Meta: model = Ticket fields_mini = ['id', 'title'] fields_small = fields_mini + [ - 'type', 'type_display', 'status', 'status_display', - 'state', 'approval_step', 'rel_snapshot', 'comment', + 'type', 'status', 'state', 'approval_step', 'comment', 'date_created', 'date_updated', 'org_id', 'rel_snapshot', 'process_map', 'org_name', 'serial_num' ] fields_fk = ['applicant', ] fields = fields_small + fields_fk + extra_kwargs = { + 'type': {'required': True} + } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -41,43 +45,20 @@ class TicketSerializer(OrgResourceModelSerializerMixin): choices.pop(TicketType.general, None) tp._choices = choices - -class TicketListSerializer(TicketSerializer): - class Meta: - model = Ticket - fields = [ - 'id', 'title', 'serial_num', 'type', 'type_display', 'status', - 'state', 'rel_snapshot', 'date_created', 'rel_snapshot' - ] - read_only_fields = fields - - -class TicketDisplaySerializer(TicketSerializer): - class Meta: - model = Ticket - fields = TicketSerializer.Meta.fields - read_only_fields = fields + @classmethod + def setup_eager_loading(cls, queryset): + queryset = queryset.prefetch_related('ticket_steps') + return queryset class TicketApproveSerializer(TicketSerializer): - class Meta: - model = Ticket + class Meta(TicketSerializer.Meta): fields = TicketSerializer.Meta.fields read_only_fields = fields class TicketApplySerializer(TicketSerializer): - org_id = serializers.CharField( - required=True, max_length=36, - allow_blank=True, label=_("Organization") - ) - - class Meta: - model = Ticket - fields = TicketSerializer.Meta.fields - extra_kwargs = { - 'type': {'required': True} - } + org_id = serializers.CharField(required=True, max_length=36, allow_blank=True, label=_("Organization")) @staticmethod def validate_org_id(org_id): @@ -91,10 +72,13 @@ class TicketApplySerializer(TicketSerializer): if self.instance: return attrs + print("Attrs: ", attrs) + ticket_type = attrs.get('type') org_id = attrs.get('org_id') - flow = TicketFlow.get_org_related_flows(org_id=org_id)\ + flow = TicketFlow.get_org_related_flows(org_id=org_id) \ .filter(type=ticket_type).first() + if flow: attrs['flow'] = flow else: