feat: 查看/下载录像被记录在活动日志中

pull/11452/head
jiangweidong 2023-08-29 14:21:06 +08:00
parent 859268f7f3
commit d4469aeaf7
7 changed files with 121 additions and 67 deletions

View File

@ -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']

View File

@ -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,
}

75
apps/accounts/mixins.py Normal file
View File

@ -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

View File

@ -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")

View File

@ -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:

View File

@ -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

View File

@ -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)