mirror of https://github.com/jumpserver/jumpserver
feat: 添加命令复核逻辑; 添加命令复核工单;
parent
e9b174f342
commit
50918a3dd2
|
@ -5,7 +5,7 @@ from rest_framework.generics import CreateAPIView, RetrieveDestroyAPIView
|
|||
from common.permissions import IsAppUser
|
||||
from common.utils import reverse, lazyproperty
|
||||
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 .. import serializers
|
||||
|
||||
|
@ -48,7 +48,7 @@ class LoginAssetCheckAPI(CreateAPIView):
|
|||
org_id=self.serializer.org.id
|
||||
)
|
||||
confirm_status_url = reverse(
|
||||
view_name='acls:login-asset-confirm-status',
|
||||
view_name='api-acls:login-asset-confirm-status',
|
||||
kwargs={'pk': str(ticket.id)}
|
||||
)
|
||||
ticket_detail_url = reverse(
|
||||
|
@ -72,34 +72,6 @@ class LoginAssetCheckAPI(CreateAPIView):
|
|||
return serializer
|
||||
|
||||
|
||||
class LoginAssetConfirmStatusAPI(RetrieveDestroyAPIView):
|
||||
permission_classes = (IsAppUser, )
|
||||
class LoginAssetConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI):
|
||||
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'])
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
# -*- 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 common.utils import reverse
|
||||
from common.utils import lazyproperty
|
||||
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 .. import serializers
|
||||
|
||||
|
||||
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
|
||||
__all__ = [
|
||||
'CommandFilterViewSet', 'CommandFilterRuleViewSet', 'CommandConfirmAPI',
|
||||
'CommandConfirmStatusAPI'
|
||||
]
|
||||
|
||||
|
||||
class CommandFilterViewSet(OrgBulkModelViewSet):
|
||||
|
@ -35,3 +45,52 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet):
|
|||
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
|
||||
|
||||
|
|
|
@ -103,3 +103,24 @@ class CommandFilterRule(OrgModelMixin):
|
|||
|
||||
def __str__(self):
|
||||
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
|
||||
|
|
|
@ -6,6 +6,9 @@ from rest_framework import serializers
|
|||
from common.drf.serializers import AdaptedBulkListSerializer
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
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):
|
||||
|
@ -50,3 +53,35 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
|||
# msg = _("Content should not be contain: {}").format(invalid_char)
|
||||
# raise serializers.ValidationError(msg)
|
||||
# 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
|
||||
|
|
|
@ -63,6 +63,9 @@ urlpatterns = [
|
|||
|
||||
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 = [
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.core.files.storage import default_storage
|
|||
from django.core.cache import cache
|
||||
|
||||
from assets.models import Asset
|
||||
from users.models import User
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.db.models import ChoiceSet
|
||||
from ..backends import get_multi_command_storage
|
||||
|
@ -79,6 +80,10 @@ class Session(OrgModelMixin):
|
|||
def asset_obj(self):
|
||||
return Asset.objects.get(id=self.asset_id)
|
||||
|
||||
@property
|
||||
def user_obj(self):
|
||||
return User.objects.get(id=self.user_id)
|
||||
|
||||
@property
|
||||
def _date_start_first_has_replay_rdp_session(self):
|
||||
if self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None:
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
from .ticket import *
|
||||
from .assignee import *
|
||||
from .comment import *
|
||||
from .common import *
|
||||
|
|
|
@ -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'])
|
|
@ -10,6 +10,7 @@ class TicketTypeChoices(TextChoices):
|
|||
apply_asset = 'apply_asset', _('Apply for asset')
|
||||
apply_application = 'apply_application', _('Apply for application')
|
||||
login_asset_confirm = 'login_asset_confirm', _('Login asset confirm')
|
||||
command_confirm = 'command_confirm', _('Command confirm')
|
||||
|
||||
|
||||
class TicketActionChoices(TextChoices):
|
||||
|
|
|
@ -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
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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
|
Loading…
Reference in New Issue