feat: 资产登录工单页面增加监控与中断

pull/7467/head
xinwen 2021-12-29 20:07:56 +08:00 committed by Jiangjie.Bai
parent 100bfe0304
commit bd84edea62
15 changed files with 168 additions and 20 deletions

View File

@ -60,7 +60,8 @@ class LoginAssetCheckAPI(CreateAPIView):
'check_confirm_status': {'method': 'GET', 'url': confirm_status_url}, 'check_confirm_status': {'method': 'GET', 'url': confirm_status_url},
'close_confirm': {'method': 'DELETE', 'url': confirm_status_url}, 'close_confirm': {'method': 'DELETE', 'url': confirm_status_url},
'ticket_detail_url': ticket_detail_url, 'ticket_detail_url': ticket_detail_url,
'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees] 'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees],
'ticket_id': str(ticket.id)
} }
return data return data

View File

@ -6,7 +6,7 @@ import tarfile
from django.shortcuts import get_object_or_404, reverse from django.shortcuts import get_object_or_404, reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.encoding import escape_uri_path from django.utils.encoding import escape_uri_path
from django.http import FileResponse, HttpResponse from django.http import FileResponse
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from rest_framework import viewsets, views from rest_framework import viewsets, views
from rest_framework.response import Response from rest_framework.response import Response
@ -15,7 +15,7 @@ from rest_framework.decorators import action
from common.utils import model_to_json from common.utils import model_to_json
from .. import utils from .. import utils
from common.const.http import GET from common.const.http import GET
from common.utils import is_uuid, get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.mixins.api import AsyncApiMixin from common.mixins.api import AsyncApiMixin
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsAppUser from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsAppUser
from common.drf.filters import DatetimeRangeFilter from common.drf.filters import DatetimeRangeFilter
@ -24,13 +24,14 @@ from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import tmp_to_root_org, tmp_to_org from orgs.utils import tmp_to_root_org, tmp_to_org
from users.models import User from users.models import User
from ..utils import find_session_replay_local, download_session_replay from ..utils import find_session_replay_local, download_session_replay
from ..hands import SystemUser
from ..models import Session from ..models import Session
from .. import serializers from .. import serializers
from terminal.utils import is_session_approver
__all__ = [ __all__ = [
'SessionViewSet', 'SessionReplayViewSet', 'SessionJoinValidateAPI' 'SessionViewSet', 'SessionReplayViewSet', 'SessionJoinValidateAPI'
] ]
logger = get_logger(__name__) logger = get_logger(__name__)
@ -197,6 +198,9 @@ class SessionJoinValidateAPI(views.APIView):
msg = _('User does not exist: {}'.format(user_id)) msg = _('User does not exist: {}'.format(user_id))
return Response({'ok': False, 'msg': msg}, status=401) return Response({'ok': False, 'msg': msg}, status=401)
with tmp_to_org(session.org): with tmp_to_org(session.org):
if is_session_approver(session_id, user_id):
return Response({'ok': True, 'msg': ''}, status=200)
if not user.admin_or_audit_orgs: if not user.admin_or_audit_orgs:
msg = _('User does not have permission') msg = _('User does not have permission')
return Response({'ok': False, 'msg': msg}, status=401) return Response({'ok': False, 'msg': msg}, status=401)

View File

@ -3,14 +3,18 @@
import logging import logging
from rest_framework.views import APIView, Response from rest_framework.views import APIView, Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from common.utils import get_object_or_none from common.utils import get_object_or_none
from common.permissions import IsOrgAdminOrAppUser from common.permissions import IsOrgAdminOrAppUser
from ..models import Session, Task from ..models import Session, Task
from .. import serializers from .. import serializers
from terminal.utils import is_session_approver
from orgs.utils import tmp_to_root_org
__all__ = ['TaskViewSet', 'KillSessionAPI'] __all__ = ['TaskViewSet', 'KillSessionAPI', 'KillSessionForTicketAPI']
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
@ -21,20 +25,45 @@ class TaskViewSet(BulkModelViewSet):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def kill_sessions(session_ids, user):
validated_session = []
for session_id in session_ids:
session = get_object_or_none(Session, id=session_id)
if session and not session.is_finished:
validated_session.append(session_id)
Task.objects.create(
name="kill_session", args=session.id, terminal=session.terminal,
kwargs={
'terminated_by': str(user)
}
)
return validated_session
class KillSessionAPI(APIView): class KillSessionAPI(APIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
model = Task
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
validated_session = [] session_ids = request.data
for session_id in request.data: user = request.user
session = get_object_or_none(Session, id=session_id) validated_session = kill_sessions(session_ids, user)
if session and not session.is_finished:
validated_session.append(session_id)
self.model.objects.create(
name="kill_session", args=session.id, terminal=session.terminal,
kwargs={
'terminated_by': str(request.user)
}
)
return Response({"ok": validated_session}) return Response({"ok": validated_session})
class KillSessionForTicketAPI(APIView):
permission_classes = (IsAuthenticated, )
def post(self, request, *args, **kwargs):
session_ids = request.data
user_id = request.user.id
for session_id in session_ids:
if not is_session_approver(session_id, user_id):
return Response({}, status=status.HTTP_403_FORBIDDEN)
with tmp_to_root_org():
validated_session = kill_sessions(session_ids, request.user)
return Response({"ok": validated_session})

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.13 on 2021-12-29 08:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('terminal', '0041_auto_20211105_1605'),
]
operations = [
migrations.AlterField(
model_name='session',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('vnc', 'vnc'), ('telnet', 'telnet'), ('mysql', 'mysql'), ('redis', 'redis'), ('oracle', 'oracle'), ('mariadb', 'mariadb'), ('sqlserver', 'sqlserver'), ('postgresql', 'postgresql'), ('k8s', 'kubernetes')], db_index=True, default='ssh', max_length=16),
),
]

View File

@ -30,6 +30,7 @@ urlpatterns = [
api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
name='session-replay'), name='session-replay'),
path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'), path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'),
path('tasks/kill-session-for-ticket/', api.KillSessionForTicketAPI.as_view(), name='kill-session-for-ticket'),
path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'), path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'),
path('commands/export/', api.CommandExportApi.as_view(), name="command-export"), path('commands/export/', api.CommandExportApi.as_view(), name="command-export"),
path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"), path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"),

View File

@ -11,6 +11,9 @@ import jms_storage
from common.utils import get_logger from common.utils import get_logger
from . import const from . import const
from .models import ReplayStorage from .models import ReplayStorage
from tickets.models import TicketSession, TicketStep, TicketAssignee
from tickets.const import ProcessStatus
logger = get_logger(__name__) logger = get_logger(__name__)
@ -247,3 +250,11 @@ class ComponentsPrometheusMetricsUtil(TypedComponentsStatusMetricsUtil):
prometheus_metrics.append('\n') prometheus_metrics.append('\n')
prometheus_metrics_text = '\n'.join(prometheus_metrics) prometheus_metrics_text = '\n'.join(prometheus_metrics)
return prometheus_metrics_text return prometheus_metrics_text
def is_session_approver(session_id, user_id):
ticket = TicketSession.get_ticket_by_session_id(session_id)
if not ticket:
return False
ok = ticket.has_all_assignee(user_id)
return ok

View File

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

View File

@ -0,0 +1,30 @@
from rest_framework.mixins import CreateModelMixin
from rest_framework import views
from rest_framework.response import Response
from rest_framework import status
from common.drf.api import JMSGenericViewSet
from common.permissions import IsOrgAdminOrAppUser
from tickets.models import TicketSession
from tickets.serializers import TicketSessionRelationSerializer
from terminal.serializers import SessionSerializer
from orgs.utils import tmp_to_root_org
class TicketSessionRelationViewSet(CreateModelMixin, JMSGenericViewSet):
queryset = TicketSession
serializer_class = TicketSessionRelationSerializer
permission_classes = (IsOrgAdminOrAppUser, )
class TicketSessionApi(views.APIView):
def get(self, request, *args, **kwargs):
with tmp_to_root_org():
ticketsession = TicketSession.objects.filter(ticket=self.kwargs['ticket_id']).first()
if not ticketsession:
return Response(status=status.HTTP_404_NOT_FOUND)
session = ticketsession.session
serializer = SessionSerializer(session)
return Response(serializer.data)

View File

@ -0,0 +1,23 @@
# Generated by Django 3.1.13 on 2021-12-29 08:19
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('terminal', '0042_auto_20211229_1619'),
('tickets', '0011_remove_approvalrule_assignees_display'),
]
operations = [
migrations.CreateModel(
name='TicketSession',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('session', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_relation', to='terminal.session')),
('ticket', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='session_relation', to='tickets.ticket')),
],
),
]

View File

@ -3,4 +3,4 @@
from .ticket import * from .ticket import *
from .comment import * from .comment import *
from .flow import * from .flow import *
from .relation import *

View File

@ -0,0 +1,14 @@
from django.db import models
from django.db.models import Model
class TicketSession(Model):
ticket = models.ForeignKey('tickets.Ticket', related_name='session_relation', on_delete=models.CASCADE, db_constraint=False)
session = models.ForeignKey('terminal.Session', related_name='ticket_relation', on_delete=models.CASCADE, db_constraint=False)
@classmethod
def get_ticket_by_session_id(cls, session_id):
relation = cls.objects.filter(session=session_id).first()
if relation:
return relation.ticket
return None

View File

@ -13,7 +13,7 @@ from tickets.signals import post_change_ticket_action
from tickets.handler import get_ticket_handler from tickets.handler import get_ticket_handler
from tickets.errors import AlreadyClosed from tickets.errors import AlreadyClosed
__all__ = ['Ticket'] __all__ = ['Ticket', 'TicketStep', 'TicketAssignee']
class TicketStep(CommonModelMixin): class TicketStep(CommonModelMixin):

View File

@ -2,3 +2,4 @@
# #
from .ticket import * from .ticket import *
from .comment import * from .comment import *
from .relation import *

View File

@ -0,0 +1,10 @@
from rest_framework import serializers
from tickets.models import TicketSession
class TicketSessionRelationSerializer(serializers.ModelSerializer):
class Meta:
model = TicketSession
fields = ['ticket', 'session']

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.urls import path
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from .. import api from .. import api
@ -10,6 +12,9 @@ router = BulkRouter()
router.register('tickets', api.TicketViewSet, 'ticket') router.register('tickets', api.TicketViewSet, 'ticket')
router.register('flows', api.TicketFlowViewSet, 'flows') router.register('flows', api.TicketFlowViewSet, 'flows')
router.register('comments', api.CommentViewSet, 'comment') router.register('comments', api.CommentViewSet, 'comment')
router.register('ticket-session-relation', api.TicketSessionRelationViewSet, 'ticket-session-relation')
urlpatterns = [] urlpatterns = [
path('tickets/<uuid:ticket_id>/session/', api.TicketSessionApi.as_view(), name='ticket-sesion'),
]
urlpatterns += router.urls urlpatterns += router.urls