mirror of https://github.com/jumpserver/jumpserver
feat: 查看/下载录像被记录在活动日志中
parent
859268f7f3
commit
d4469aeaf7
|
@ -7,10 +7,10 @@ from rest_framework.status import HTTP_200_OK
|
|||
from accounts import serializers
|
||||
from accounts.filters import AccountFilterSet
|
||||
from accounts.models import Account
|
||||
from accounts.mixins import AccountRecordViewLogMixin
|
||||
from assets.models import Asset, Node
|
||||
from common.api.mixin import ExtraFilterFieldsMixin
|
||||
from common.permissions import UserConfirmation, ConfirmType, IsValidUser
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from rbac.permissions import RBACPermission
|
||||
|
||||
|
@ -86,7 +86,7 @@ class AccountViewSet(OrgBulkModelViewSet):
|
|||
return Response(status=HTTP_200_OK)
|
||||
|
||||
|
||||
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet):
|
||||
class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
|
||||
"""
|
||||
因为可能要导出所有账号,所以单独建立了一个 viewset
|
||||
"""
|
||||
|
@ -115,7 +115,7 @@ class AssetAccountBulkCreateApi(CreateAPIView):
|
|||
return Response(data=serializer.data, status=HTTP_200_OK)
|
||||
|
||||
|
||||
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, ListAPIView):
|
||||
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixin, ListAPIView):
|
||||
model = Account.history.model
|
||||
serializer_class = serializers.AccountHistorySerializer
|
||||
http_method_names = ['get', 'options']
|
||||
|
|
|
@ -4,10 +4,10 @@ from rest_framework.response import Response
|
|||
|
||||
from accounts import serializers
|
||||
from accounts.models import AccountTemplate
|
||||
from accounts.mixins import AccountRecordViewLogMixin
|
||||
from assets.const import Protocol
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.permissions import UserConfirmation, ConfirmType
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from rbac.permissions import RBACPermission
|
||||
|
||||
|
@ -55,7 +55,7 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
|
|||
return Response(data=serializer.data)
|
||||
|
||||
|
||||
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):
|
||||
class AccountTemplateSecretsViewSet(AccountRecordViewLogMixin, AccountTemplateViewSet):
|
||||
serializer_classes = {
|
||||
'default': serializers.AccountTemplateSecretSerializer,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from django.utils import translation
|
||||
from django.utils.translation import gettext_noop
|
||||
|
||||
from audits.const import ActionChoices
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from common.utils import i18n_fmt
|
||||
|
||||
|
||||
class AccountRecordViewLogMixin(RecordViewLogMixin):
|
||||
get_object: callable
|
||||
get_queryset: callable
|
||||
|
||||
@staticmethod
|
||||
def _filter_params(params):
|
||||
new_params = {}
|
||||
need_pop_params = ('format', 'order')
|
||||
for key, value in params.items():
|
||||
if key in need_pop_params:
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
value = list(filter(None, value))
|
||||
if value:
|
||||
new_params[key] = value
|
||||
return new_params
|
||||
|
||||
def get_resource_display(self, request):
|
||||
query_params = dict(request.query_params)
|
||||
params = self._filter_params(query_params)
|
||||
|
||||
spm_filter = params.pop("spm", None)
|
||||
|
||||
if not params and not spm_filter:
|
||||
display_message = gettext_noop("Export all")
|
||||
elif spm_filter:
|
||||
display_message = gettext_noop("Export only selected items")
|
||||
else:
|
||||
query = ",".join(
|
||||
["%s=%s" % (key, value) for key, value in params.items()]
|
||||
)
|
||||
display_message = i18n_fmt(gettext_noop("Export filtered: %s"), query)
|
||||
return display_message
|
||||
|
||||
@property
|
||||
def detail_msg(self):
|
||||
return i18n_fmt(
|
||||
gettext_noop('User %s view/export secret'), self.request.user
|
||||
)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
list_func = getattr(super(), 'list')
|
||||
if not callable(list_func):
|
||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
response = list_func(request, *args, **kwargs)
|
||||
with translation.override('en'):
|
||||
resource_display = self.get_resource_display(request)
|
||||
ids = [q.id for q in self.get_queryset()]
|
||||
self.record_logs(
|
||||
ids, ActionChoices.view, self.detail_msg, resource_display=resource_display
|
||||
)
|
||||
return response
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
retrieve_func = getattr(super(), 'retrieve')
|
||||
if not callable(retrieve_func):
|
||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
response = retrieve_func(request, *args, **kwargs)
|
||||
with translation.override('en'):
|
||||
resource = self.get_object()
|
||||
self.record_logs(
|
||||
[resource.id], ActionChoices.view, self.detail_msg, resource=resource
|
||||
)
|
||||
return response
|
||||
|
|
@ -25,6 +25,7 @@ class ActionChoices(TextChoices):
|
|||
delete = "delete", _("Delete")
|
||||
create = "create", _("Create")
|
||||
# Activities action
|
||||
download = "download", _("Download")
|
||||
connect = "connect", _("Connect")
|
||||
login = "login", _("Login")
|
||||
change_auth = "change_password", _("Change password")
|
||||
|
|
|
@ -88,6 +88,7 @@ class AsyncApiMixin(InterceptMixin):
|
|||
if not self.is_need_async():
|
||||
return handler(*args, **kwargs)
|
||||
resp = self.do_async(handler, *args, **kwargs)
|
||||
self.async_callback(*args, **kwargs)
|
||||
return resp
|
||||
|
||||
def is_need_refresh(self):
|
||||
|
@ -98,6 +99,9 @@ class AsyncApiMixin(InterceptMixin):
|
|||
def is_need_async(self):
|
||||
return False
|
||||
|
||||
def async_callback(self, params):
|
||||
pass
|
||||
|
||||
def do_async(self, handler, *args, **kwargs):
|
||||
data = self.get_cache_data()
|
||||
if not data:
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
#
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.http.response import JsonResponse
|
||||
from django.utils import translation
|
||||
from django.utils.translation import gettext_noop
|
||||
from django.db.models import Model
|
||||
from rest_framework import permissions
|
||||
from rest_framework.request import Request
|
||||
|
||||
from audits.const import ActionChoices, ActivityChoices
|
||||
from audits.const import ActivityChoices
|
||||
from audits.handler import create_or_update_operate_log
|
||||
from audits.models import ActivityLog
|
||||
from common.exceptions import UserConfirmRequired
|
||||
from common.utils import i18n_fmt
|
||||
from orgs.utils import current_org
|
||||
|
||||
__all__ = [
|
||||
|
@ -49,66 +47,19 @@ class PermissionsMixin(UserPassesTestMixin):
|
|||
|
||||
|
||||
class RecordViewLogMixin:
|
||||
ACTION = ActionChoices.view
|
||||
model: Model
|
||||
|
||||
@staticmethod
|
||||
def _filter_params(params):
|
||||
new_params = {}
|
||||
need_pop_params = ('format', 'order')
|
||||
for key, value in params.items():
|
||||
if key in need_pop_params:
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
value = list(filter(None, value))
|
||||
if value:
|
||||
new_params[key] = value
|
||||
return new_params
|
||||
|
||||
def get_resource_display(self, request):
|
||||
query_params = dict(request.query_params)
|
||||
params = self._filter_params(query_params)
|
||||
|
||||
spm_filter = params.pop("spm", None)
|
||||
|
||||
if not params and not spm_filter:
|
||||
display_message = gettext_noop("Export all")
|
||||
elif spm_filter:
|
||||
display_message = gettext_noop("Export only selected items")
|
||||
else:
|
||||
query = ",".join(
|
||||
["%s=%s" % (key, value) for key, value in params.items()]
|
||||
)
|
||||
display_message = i18n_fmt(gettext_noop("Export filtered: %s"), query)
|
||||
return display_message
|
||||
|
||||
def record_logs(self, ids, **kwargs):
|
||||
resource_type = self.model._meta.verbose_name
|
||||
def record_logs(self, ids, action, detail, model=None, **kwargs):
|
||||
model = model or self.model
|
||||
resource_type = model._meta.verbose_name
|
||||
create_or_update_operate_log(
|
||||
self.ACTION, resource_type, force=True, **kwargs
|
||||
)
|
||||
detail = i18n_fmt(
|
||||
gettext_noop('User %s view/export secret'), self.request.user
|
||||
action, resource_type, force=True, **kwargs
|
||||
)
|
||||
activities = [
|
||||
ActivityLog(
|
||||
resource_id=getattr(resource_id, 'pk', resource_id),
|
||||
type=ActivityChoices.operate_log, detail=detail, org_id=current_org.id,
|
||||
resource_id=resource_id, type=ActivityChoices.operate_log,
|
||||
detail=detail, org_id=current_org.id,
|
||||
)
|
||||
for resource_id in ids
|
||||
]
|
||||
ActivityLog.objects.bulk_create(activities)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
response = super().list(request, *args, **kwargs)
|
||||
with translation.override('en'):
|
||||
resource_display = self.get_resource_display(request)
|
||||
ids = [q.id for q in self.get_queryset()]
|
||||
self.record_logs(ids, resource_display=resource_display)
|
||||
return response
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
response = super().retrieve(request, *args, **kwargs)
|
||||
with translation.override('en'):
|
||||
resource = self.get_object()
|
||||
self.record_logs([resource.id], resource=resource)
|
||||
return response
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.db.models import F
|
|||
from django.http import FileResponse
|
||||
from django.shortcuts import get_object_or_404, reverse
|
||||
from django.utils.encoding import escape_uri_path
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_noop, gettext as _
|
||||
from django_filters import rest_framework as filters
|
||||
from rest_framework import generics
|
||||
from rest_framework import viewsets, views
|
||||
|
@ -16,14 +16,16 @@ from rest_framework.decorators import action
|
|||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from audits.const import ActionChoices
|
||||
from common.api import AsyncApiMixin
|
||||
from common.const.http import GET
|
||||
from common.drf.filters import BaseFilterSet
|
||||
from common.drf.filters import DatetimeRangeFilterBackend
|
||||
from common.drf.renders import PassthroughRenderer
|
||||
from common.storage.replay import ReplayStorageHandler
|
||||
from common.utils import data_to_json, is_uuid
|
||||
from common.utils import data_to_json, is_uuid, i18n_fmt
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.views.mixins import RecordViewLogMixin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.utils import tmp_to_root_org, tmp_to_org
|
||||
from rbac.permissions import RBACPermission
|
||||
|
@ -70,7 +72,7 @@ class SessionFilterSet(BaseFilterSet):
|
|||
return queryset.filter(terminal__name=value)
|
||||
|
||||
|
||||
class SessionViewSet(OrgBulkModelViewSet):
|
||||
class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
|
||||
model = Session
|
||||
serializer_classes = {
|
||||
'default': serializers.SessionSerializer,
|
||||
|
@ -132,6 +134,15 @@ class SessionViewSet(OrgBulkModelViewSet):
|
|||
filename = escape_uri_path('{}.tar'.format(storage.obj.id))
|
||||
disposition = "attachment; filename*=UTF-8''{}".format(filename)
|
||||
response["Content-Disposition"] = disposition
|
||||
|
||||
detail = i18n_fmt(
|
||||
gettext_noop(f'User %s %s session %s replay'), self.request.user,
|
||||
gettext_noop(ActionChoices.download), str(storage.obj)
|
||||
)
|
||||
self.record_logs(
|
||||
[storage.obj.asset_id], ActionChoices.download, detail,
|
||||
model=Session, resource_display=str(storage.obj)
|
||||
)
|
||||
return response
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -152,7 +163,7 @@ class SessionViewSet(OrgBulkModelViewSet):
|
|||
return super().perform_create(serializer)
|
||||
|
||||
|
||||
class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet):
|
||||
class SessionReplayViewSet(AsyncApiMixin, RecordViewLogMixin, viewsets.ViewSet):
|
||||
serializer_class = serializers.ReplaySerializer
|
||||
download_cache_key = "SESSION_REPLAY_DOWNLOAD_{}"
|
||||
session = None
|
||||
|
@ -215,6 +226,18 @@ class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet):
|
|||
return False
|
||||
return True
|
||||
|
||||
def async_callback(self, *args, **kwargs):
|
||||
session_id = kwargs.get('pk')
|
||||
session = get_object_or_404(Session, id=session_id)
|
||||
detail = i18n_fmt(
|
||||
gettext_noop(f'User %s %s session %s replay'), self.request.user,
|
||||
gettext_noop(ActionChoices.view), str(session)
|
||||
)
|
||||
self.record_logs(
|
||||
[session.asset_id], ActionChoices.download, detail,
|
||||
model=Session, resource_display=str(session)
|
||||
)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
session_id = kwargs.get('pk')
|
||||
session = get_object_or_404(Session, id=session_id)
|
||||
|
|
Loading…
Reference in New Issue