mirror of https://github.com/jumpserver/jumpserver
perf: 记录会话活动日志 (#12523)
* perf: 更新会话生命周期日志 * perf: 优化错误原因 * perf: 增加错误类型 --------- Co-authored-by: Eric <xplzv@126.com>pull/12660/head
parent
2062778ab8
commit
58d30e7f85
|
@ -18,10 +18,11 @@ from rest_framework.response import Response
|
|||
|
||||
from audits.const import ActionChoices
|
||||
from common.api import AsyncApiMixin
|
||||
from common.const.http import GET
|
||||
from common.const.http import GET, POST
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.drf.filters import DatetimeRangeFilterBackend
|
||||
from common.drf.renders import PassthroughRenderer
|
||||
from common.permissions import IsServiceAccount
|
||||
from common.storage.replay import ReplayStorageHandler
|
||||
from common.utils import data_to_json, is_uuid, i18n_fmt
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
|
@ -33,6 +34,7 @@ from terminal import serializers
|
|||
from terminal.const import TerminalType
|
||||
from terminal.models import Session
|
||||
from terminal.permissions import IsSessionAssignee
|
||||
from terminal.session_lifecycle import lifecycle_events_map, reasons_map
|
||||
from terminal.utils import is_session_approver
|
||||
from users.models import User
|
||||
|
||||
|
@ -79,6 +81,7 @@ class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
|
|||
serializer_classes = {
|
||||
'default': serializers.SessionSerializer,
|
||||
'display': serializers.SessionDisplaySerializer,
|
||||
'lifecycle_log': serializers.SessionLifecycleLogSerializer,
|
||||
}
|
||||
search_fields = [
|
||||
"user", "asset", "account", "remote_addr",
|
||||
|
@ -168,6 +171,23 @@ class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
|
|||
count = queryset.count()
|
||||
return Response({'count': count})
|
||||
|
||||
@action(methods=[POST], detail=True, permission_classes=[IsServiceAccount], url_path='lifecycle_log',
|
||||
url_name='lifecycle_log')
|
||||
def lifecycle_log(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
validated_data = serializer.validated_data
|
||||
event = validated_data.pop('event', None)
|
||||
event_class = lifecycle_events_map.get(event, None)
|
||||
if not event_class:
|
||||
return Response({'msg': f'event_name {event} invalid'}, status=400)
|
||||
session = self.get_object()
|
||||
reason = validated_data.pop('reason', None)
|
||||
reason = reasons_map.get(reason, reason)
|
||||
event_obj = event_class(session, reason, **validated_data)
|
||||
activity_log = event_obj.create_activity_log()
|
||||
return Response({'msg': 'ok', 'id': activity_log.id})
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset() \
|
||||
.prefetch_related('terminal') \
|
||||
|
|
|
@ -4,6 +4,7 @@ from rest_framework import serializers
|
|||
from common.serializers.fields import LabeledChoiceField
|
||||
from common.utils import pretty_string
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from terminal.session_lifecycle import lifecycle_events_map
|
||||
from .terminal import TerminalSmallSerializer
|
||||
from ..const import SessionType, SessionErrorReason
|
||||
from ..models import Session
|
||||
|
@ -11,6 +12,7 @@ from ..models import Session
|
|||
__all__ = [
|
||||
'SessionSerializer', 'SessionDisplaySerializer',
|
||||
'ReplaySerializer', 'SessionJoinValidateSerializer',
|
||||
'SessionLifecycleLogSerializer'
|
||||
]
|
||||
|
||||
|
||||
|
@ -77,3 +79,9 @@ class ReplaySerializer(serializers.Serializer):
|
|||
class SessionJoinValidateSerializer(serializers.Serializer):
|
||||
user_id = serializers.UUIDField()
|
||||
session_id = serializers.UUIDField()
|
||||
|
||||
|
||||
class SessionLifecycleLogSerializer(serializers.Serializer):
|
||||
event = serializers.ChoiceField(choices=list(lifecycle_events_map.keys()))
|
||||
reason = serializers.CharField(required=False)
|
||||
user = serializers.CharField(required=False)
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
from django.utils.translation import gettext_noop
|
||||
|
||||
from audits.const import ActivityChoices
|
||||
from audits.models import ActivityLog
|
||||
from common.utils import i18n_fmt
|
||||
from terminal.models import Session
|
||||
|
||||
|
||||
class SessionLifecycleEventBase(object):
|
||||
|
||||
def __init__(self, session: Session, reason, *args, **kwargs):
|
||||
self.session = session
|
||||
self.reason = reason
|
||||
|
||||
def detail(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def create_activity_log(self):
|
||||
log_obj = ActivityLog.objects.create(
|
||||
resource_id=self.session.id,
|
||||
type=ActivityChoices.session_log,
|
||||
detail=self.detail(),
|
||||
org_id=self.session.org_id
|
||||
)
|
||||
return log_obj
|
||||
|
||||
|
||||
class AssetConnectSuccess(SessionLifecycleEventBase):
|
||||
name = "asset_connect_success"
|
||||
i18n_text = gettext_noop("Connect to asset %s success")
|
||||
|
||||
def detail(self):
|
||||
return i18n_fmt(self.i18n_text, self.session.asset)
|
||||
|
||||
|
||||
class AssetConnectFinished(SessionLifecycleEventBase):
|
||||
name = "asset_connect_finished"
|
||||
i18n_text = gettext_noop("Connect to asset %s finished: %s")
|
||||
|
||||
def detail(self):
|
||||
asset = self.session.asset
|
||||
reason = self.reason
|
||||
return i18n_fmt(self.i18n_text, asset, reason)
|
||||
|
||||
|
||||
class UserCreateShareLink(SessionLifecycleEventBase):
|
||||
name = "create_share_link"
|
||||
i18n_text = gettext_noop("User %s create share link")
|
||||
|
||||
def detail(self):
|
||||
user = self.session.user
|
||||
return i18n_fmt(self.i18n_text, user)
|
||||
|
||||
|
||||
class UserJoinSession(SessionLifecycleEventBase):
|
||||
name = "user_join_session"
|
||||
i18n_text = gettext_noop("User %s join session")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = kwargs.get("user")
|
||||
|
||||
def detail(self):
|
||||
return i18n_fmt(self.i18n_text, self.user)
|
||||
|
||||
|
||||
class UserLeaveSession(SessionLifecycleEventBase):
|
||||
name = "user_leave_session"
|
||||
i18n_text = gettext_noop("User %s leave session")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = kwargs.get("user")
|
||||
|
||||
def detail(self):
|
||||
return i18n_fmt(self.i18n_text, self.user)
|
||||
|
||||
|
||||
class AdminJoinMonitor(SessionLifecycleEventBase):
|
||||
name = "admin_join_monitor"
|
||||
i18n_text = gettext_noop("User %s join to monitor session")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = kwargs.get("user")
|
||||
|
||||
def detail(self):
|
||||
return i18n_fmt(self.i18n_text, self.user)
|
||||
|
||||
|
||||
class AdminExitMonitor(SessionLifecycleEventBase):
|
||||
name = "admin_exit_monitor"
|
||||
i18n_text = gettext_noop("User %s exit to monitor session")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = kwargs.get("user")
|
||||
|
||||
def detail(self):
|
||||
return i18n_fmt(self.i18n_text, self.user)
|
||||
|
||||
|
||||
class ReplayConvertStart(SessionLifecycleEventBase):
|
||||
name = "replay_convert_start"
|
||||
i18n_text = gettext_noop("Replay start to convert")
|
||||
|
||||
def detail(self):
|
||||
return self.i18n_text
|
||||
|
||||
|
||||
class ReplayConvertSuccess(SessionLifecycleEventBase):
|
||||
name = "replay_convert_success"
|
||||
i18n_text = gettext_noop("Replay successfully converted to MP4 format")
|
||||
|
||||
def detail(self):
|
||||
return self.i18n_text
|
||||
|
||||
|
||||
class ReplayConvertFailure(SessionLifecycleEventBase):
|
||||
name = "replay_convert_failure"
|
||||
i18n_text = gettext_noop("Replay failed to convert to MP4 format: %s")
|
||||
|
||||
def detail(self):
|
||||
return i18n_fmt(self.i18n_text, self.reason)
|
||||
|
||||
|
||||
class ReplayUploadStart(SessionLifecycleEventBase):
|
||||
name = "replay_upload_start"
|
||||
i18n_text = gettext_noop("Replay start to upload")
|
||||
|
||||
def detail(self):
|
||||
return self.i18n_text
|
||||
|
||||
|
||||
class ReplayUploadSuccess(SessionLifecycleEventBase):
|
||||
name = "replay_upload_success"
|
||||
i18n_text = gettext_noop("Replay successfully uploaded")
|
||||
|
||||
def detail(self):
|
||||
return self.i18n_text
|
||||
|
||||
|
||||
class ReplayUploadFailure(SessionLifecycleEventBase):
|
||||
name = "replay_upload_failure"
|
||||
i18n_text = gettext_noop("Replay failed to upload: %s")
|
||||
|
||||
def detail(self):
|
||||
return i18n_fmt(self.i18n_text, self.reason)
|
||||
|
||||
|
||||
reasons_map = {
|
||||
'connect_failed': gettext_noop('connect failed'),
|
||||
'connect_disconnect': gettext_noop('connection disconnect'),
|
||||
'user_close': gettext_noop('user closed'),
|
||||
'idle_disconnect': gettext_noop('idle disconnect'),
|
||||
'admin_terminate': gettext_noop('admin terminated'),
|
||||
'max_session_timeout': gettext_noop('maximum session time has been reached'),
|
||||
'permission_expired': gettext_noop('permission has expired'),
|
||||
'null_storage': gettext_noop('storage is null'),
|
||||
}
|
||||
|
||||
lifecycle_events_map = {
|
||||
AssetConnectSuccess.name: AssetConnectSuccess,
|
||||
AssetConnectFinished.name: AssetConnectFinished,
|
||||
UserCreateShareLink.name: UserCreateShareLink,
|
||||
UserJoinSession.name: UserJoinSession,
|
||||
UserLeaveSession.name: UserLeaveSession,
|
||||
AdminJoinMonitor.name: AdminJoinMonitor,
|
||||
AdminExitMonitor.name: AdminExitMonitor,
|
||||
ReplayConvertStart.name: ReplayConvertStart,
|
||||
ReplayConvertSuccess.name: ReplayConvertSuccess,
|
||||
ReplayConvertFailure.name: ReplayConvertFailure,
|
||||
ReplayUploadStart.name: ReplayUploadStart,
|
||||
ReplayUploadSuccess.name: ReplayUploadSuccess,
|
||||
ReplayUploadFailure.name: ReplayUploadFailure,
|
||||
}
|
|
@ -3,6 +3,7 @@ from django.dispatch import receiver
|
|||
|
||||
from terminal.models import SessionSharing
|
||||
from terminal.notifications import SessionSharingMessage
|
||||
from terminal.session_lifecycle import UserCreateShareLink
|
||||
|
||||
|
||||
@receiver(post_save, sender=SessionSharing)
|
||||
|
@ -11,3 +12,7 @@ def on_session_sharing_created(sender, instance: SessionSharing, created, **kwar
|
|||
return
|
||||
for user in instance.users_queryset:
|
||||
SessionSharingMessage(user, instance).publish_async()
|
||||
|
||||
# 创建会话分享活动日志
|
||||
session = instance.session
|
||||
UserCreateShareLink(session, None).create_activity_log()
|
||||
|
|
Loading…
Reference in New Issue