feat: 添加命令复核逻辑; 添加命令复核工单;

pull/6054/head
Bai 2021-04-26 17:56:06 +08:00 committed by Jiangjie.Bai
parent e9b174f342
commit 50918a3dd2
12 changed files with 248 additions and 34 deletions

View File

@ -5,7 +5,7 @@ from rest_framework.generics import CreateAPIView, RetrieveDestroyAPIView
from common.permissions import IsAppUser from common.permissions import IsAppUser
from common.utils import reverse, lazyproperty from common.utils import reverse, lazyproperty
from orgs.utils import tmp_to_org, tmp_to_root_org from orgs.utils import tmp_to_org, tmp_to_root_org
from tickets.models import Ticket from tickets.api import GenericTicketStatusRetrieveCloseAPI
from ..models import LoginAssetACL from ..models import LoginAssetACL
from .. import serializers from .. import serializers
@ -48,7 +48,7 @@ class LoginAssetCheckAPI(CreateAPIView):
org_id=self.serializer.org.id org_id=self.serializer.org.id
) )
confirm_status_url = reverse( confirm_status_url = reverse(
view_name='acls:login-asset-confirm-status', view_name='api-acls:login-asset-confirm-status',
kwargs={'pk': str(ticket.id)} kwargs={'pk': str(ticket.id)}
) )
ticket_detail_url = reverse( ticket_detail_url = reverse(
@ -72,34 +72,6 @@ class LoginAssetCheckAPI(CreateAPIView):
return serializer return serializer
class LoginAssetConfirmStatusAPI(RetrieveDestroyAPIView): class LoginAssetConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI):
permission_classes = (IsAppUser, ) pass
def retrieve(self, request, *args, **kwargs):
if self.ticket.action_open:
status = 'await'
elif self.ticket.action_approve:
status = 'approve'
else:
status = 'reject'
data = {
'status': status,
'action': self.ticket.action,
'processor': self.ticket.processor_display
}
return Response(data=data, status=200)
def destroy(self, request, *args, **kwargs):
if self.ticket.status_open:
self.ticket.close(processor=self.ticket.applicant)
data = {
'action': self.ticket.action,
'status': self.ticket.status,
'processor': self.ticket.processor_display
}
return Response(data=data, status=200)
@lazyproperty
def ticket(self):
with tmp_to_root_org():
return get_object_or_404(Ticket, pk=self.kwargs['pk'])

View File

@ -1,15 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView, RetrieveDestroyAPIView
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 lazyproperty
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from ..hands import IsOrgAdmin 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 from ..models import CommandFilter, CommandFilterRule
from .. import serializers from .. import serializers
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet'] __all__ = [
'CommandFilterViewSet', 'CommandFilterRuleViewSet', 'CommandConfirmAPI',
'CommandConfirmStatusAPI'
]
class CommandFilterViewSet(OrgBulkModelViewSet): class CommandFilterViewSet(OrgBulkModelViewSet):
@ -35,3 +45,52 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet):
return cmd_filter.rules.all() return cmd_filter.rules.all()
class CommandConfirmAPI(CreateAPIView):
permission_classes = ()
# permission_classes = (IsAppUser, )
serializer_class = serializers.CommandConfirmSerializer
def create(self, request, *args, **kwargs):
ticket = self.create_command_confirm_ticket()
response_data = self.get_response_data(ticket)
return Response(data=response_data, status=200)
def create_command_confirm_ticket(self):
ticket = self.serializer.cmd_filter_rule.create_command_confirm_ticket(
run_command=self.serializer.data.get('run_command'),
session=self.serializer.session,
cmd_filter_rule=self.serializer.cmd_filter_rule,
org_id=self.serializer.org.id
)
return ticket
@staticmethod
def get_response_data(ticket):
confirm_status_url = reverse(
view_name='api-assets:command-confirm-status',
kwargs={'pk': str(ticket.id)}
)
ticket_detail_url = reverse(
view_name='api-tickets:ticket-detail',
kwargs={'pk': str(ticket.id)},
external=True, api_to_ui=True
)
ticket_detail_url = '{url}?type={type}'.format(url=ticket_detail_url, type=ticket.type)
return {
'check_confirm_status': {'method': 'GET', 'url': confirm_status_url},
'close_confirm': {'method': 'DELETE', 'url': confirm_status_url},
'ticket_detail_url': ticket_detail_url,
'reviewers': [str(user) for user in ticket.assignees.all()]
}
@lazyproperty
def serializer(self):
serializer = self.get_serializer(data=self.request.data)
serializer.is_valid(raise_exception=True)
return serializer
class CommandConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI):
permission_classes = ()
pass

View File

@ -103,3 +103,24 @@ class CommandFilterRule(OrgModelMixin):
def __str__(self): def __str__(self):
return '{} % {}'.format(self.type, self.content) return '{} % {}'.format(self.type, self.content)
def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id):
from tickets.const import TicketTypeChoices
from tickets.models import Ticket
data = {
'title': _('Command confirm') + ' ({})'.format(session.user),
'type': TicketTypeChoices.command_confirm,
'meta': {
'apply_run_user': session.user,
'apply_run_asset': session.asset,
'apply_run_system_user': session.system_user,
'apply_run_command': run_command,
'apply_from_session': str(session.id),
'apply_from_cmd_filter_rule': str(cmd_filter_rule.id),
},
'org_id': org_id,
}
ticket = Ticket.objects.create(**data)
ticket.assignees.set(self.reviewers.all())
ticket.open(applicant=session.user_obj)
return ticket

View File

@ -6,6 +6,9 @@ from rest_framework import serializers
from common.drf.serializers import AdaptedBulkListSerializer from common.drf.serializers import AdaptedBulkListSerializer
from ..models import CommandFilter, CommandFilterRule, SystemUser from ..models import CommandFilter, CommandFilterRule, SystemUser
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from orgs.utils import tmp_to_root_org
from common.utils import get_object_or_none, lazyproperty
from terminal.models import Session
class CommandFilterSerializer(BulkOrgResourceModelSerializer): class CommandFilterSerializer(BulkOrgResourceModelSerializer):
@ -50,3 +53,35 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
# msg = _("Content should not be contain: {}").format(invalid_char) # msg = _("Content should not be contain: {}").format(invalid_char)
# raise serializers.ValidationError(msg) # raise serializers.ValidationError(msg)
# return content # return content
class CommandConfirmSerializer(serializers.Serializer):
session_id = serializers.UUIDField(required=True, allow_null=False)
cmd_filter_rule_id = serializers.UUIDField(required=True, allow_null=False)
run_command = serializers.CharField(required=True, allow_null=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.session = None
self.cmd_filter_rule = None
def validate_session_id(self, session_id):
self.session = self.validate_object_exist(Session, session_id)
return session_id
def validate_cmd_filter_rule_id(self, cmd_filter_rule_id):
self.cmd_filter_rule = self.validate_object_exist(CommandFilterRule, cmd_filter_rule_id)
return cmd_filter_rule_id
@staticmethod
def validate_object_exist(model, field_id):
with tmp_to_root_org():
obj = get_object_or_none(model, id=field_id)
if not obj:
error = '{} Model object does not exist'.format(model.__name__)
raise serializers.ValidationError(error)
return obj
@lazyproperty
def org(self):
return self.session.org

View File

@ -63,6 +63,9 @@ urlpatterns = [
path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
path('cmd-filters/command-confirm/', api.CommandConfirmAPI.as_view(), name='command-confirm'),
path('cmd-filters/command-confirm/<uuid:pk>/status/', api.CommandConfirmStatusAPI.as_view(), name='command-confirm-status')
] ]
old_version_urlpatterns = [ old_version_urlpatterns = [

View File

@ -11,6 +11,7 @@ from django.core.files.storage import default_storage
from django.core.cache import cache from django.core.cache import cache
from assets.models import Asset from assets.models import Asset
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 ChoiceSet
from ..backends import get_multi_command_storage from ..backends import get_multi_command_storage
@ -79,6 +80,10 @@ class Session(OrgModelMixin):
def asset_obj(self): def asset_obj(self):
return Asset.objects.get(id=self.asset_id) return Asset.objects.get(id=self.asset_id)
@property
def user_obj(self):
return User.objects.get(id=self.user_id)
@property @property
def _date_start_first_has_replay_rdp_session(self): def _date_start_first_has_replay_rdp_session(self):
if self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None: if self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None:

View File

@ -3,3 +3,4 @@
from .ticket import * from .ticket import *
from .assignee import * from .assignee import *
from .comment import * from .comment import *
from .common import *

View File

@ -0,0 +1,44 @@
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework.generics import RetrieveDestroyAPIView
from common.permissions import IsAppUser
from common.utils import lazyproperty
from orgs.utils import tmp_to_root_org
from ..models import Ticket
__all__ = ['GenericTicketStatusRetrieveCloseAPI']
class GenericTicketStatusRetrieveCloseAPI(RetrieveDestroyAPIView):
permission_classes = (IsAppUser, )
def retrieve(self, request, *args, **kwargs):
if self.ticket.action_open:
status = 'await'
elif self.ticket.action_approve:
status = 'approve'
else:
status = 'reject'
data = {
'status': status,
'action': self.ticket.action,
'processor': self.ticket.processor_display
}
return Response(data=data, status=200)
def destroy(self, request, *args, **kwargs):
if self.ticket.status_open:
self.ticket.close(processor=self.ticket.applicant)
data = {
'action': self.ticket.action,
'status': self.ticket.status,
'processor': self.ticket.processor_display
}
return Response(data=data, status=200)
@lazyproperty
def ticket(self):
with tmp_to_root_org():
return get_object_or_404(Ticket, pk=self.kwargs['pk'])

View File

@ -10,6 +10,7 @@ class TicketTypeChoices(TextChoices):
apply_asset = 'apply_asset', _('Apply for asset') apply_asset = 'apply_asset', _('Apply for asset')
apply_application = 'apply_application', _('Apply for application') apply_application = 'apply_application', _('Apply for application')
login_asset_confirm = 'login_asset_confirm', _('Login asset confirm') login_asset_confirm = 'login_asset_confirm', _('Login asset confirm')
command_confirm = 'command_confirm', _('Command confirm')
class TicketActionChoices(TextChoices): class TicketActionChoices(TextChoices):

View File

@ -0,0 +1,30 @@
from django.utils.translation import ugettext as _
from .base import BaseHandler
class Handler(BaseHandler):
# body
def _construct_meta_body_of_open(self):
apply_run_user = self.ticket.meta.get('apply_run_user')
apply_run_asset = self.ticket.meta.get('apply_run_asset')
apply_run_system_user = self.ticket.meta.get('apply_run_system_user')
apply_run_command = self.ticket.meta.get('apply_run_command')
apply_from_session = self.ticket.meta.get('apply_from_session')
apply_from_cmd_filter_rule = self.ticket.meta.get('apply_from_cmd_filter_rule')
applied_body = '''{}: {},
{}: {},
{}: {},
{}: {},
{}: {},
{}: {},
'''.format(
_("Applied run user"), apply_run_user,
_("Applied run asset"), apply_run_asset,
_("Applied run system user"), apply_run_system_user,
_("Applied run command"), apply_run_command,
_("Applied from session"), apply_from_session,
_("Applied from command filter rules"), apply_from_cmd_filter_rule,
)
return applied_body

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2021-04-26 09:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tickets', '0008_auto_20210311_1113'),
]
operations = [
migrations.AlterField(
model_name='ticket',
name='type',
field=models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('apply_asset', 'Apply for asset'), ('apply_application', 'Apply for application'), ('login_asset_confirm', 'Login asset confirm'), ('command_confirm', 'Command confirm')], default='general', max_length=64, verbose_name='Type'),
),
]

View File

@ -0,0 +1,25 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
__all__ = [
'ApplySerializer', 'CommandConfirmSerializer',
]
class ApplySerializer(serializers.Serializer):
# 申请信息
apply_run_user = serializers.CharField(required=True, label=_('Run user'))
apply_run_asset = serializers.CharField(required=True, label=_('Run asset'))
apply_run_system_user = serializers.CharField(
required=True, max_length=64, label=_('Run system user')
)
apply_run_command = serializers.CharField(required=True, label=_('Run command'))
apply_from_session = serializers.UUIDField(required=False, label=_('From session'))
apply_from_cmd_filter_rule = serializers.UUIDField(
required=False, label=_('From cmd filter rule')
)
class CommandConfirmSerializer(ApplySerializer):
pass