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 import serializers
from accounts.filters import AccountFilterSet from accounts.filters import AccountFilterSet
from accounts.models import Account from accounts.models import Account
from accounts.mixins import AccountRecordViewLogMixin
from assets.models import Asset, Node from assets.models import Asset, Node
from common.api.mixin import ExtraFilterFieldsMixin from common.api.mixin import ExtraFilterFieldsMixin
from common.permissions import UserConfirmation, ConfirmType, IsValidUser from common.permissions import UserConfirmation, ConfirmType, IsValidUser
from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
@ -86,7 +86,7 @@ class AccountViewSet(OrgBulkModelViewSet):
return Response(status=HTTP_200_OK) return Response(status=HTTP_200_OK)
class AccountSecretsViewSet(RecordViewLogMixin, AccountViewSet): class AccountSecretsViewSet(AccountRecordViewLogMixin, AccountViewSet):
""" """
因为可能要导出所有账号所以单独建立了一个 viewset 因为可能要导出所有账号所以单独建立了一个 viewset
""" """
@ -115,7 +115,7 @@ class AssetAccountBulkCreateApi(CreateAPIView):
return Response(data=serializer.data, status=HTTP_200_OK) return Response(data=serializer.data, status=HTTP_200_OK)
class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, RecordViewLogMixin, ListAPIView): class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixin, ListAPIView):
model = Account.history.model model = Account.history.model
serializer_class = serializers.AccountHistorySerializer serializer_class = serializers.AccountHistorySerializer
http_method_names = ['get', 'options'] http_method_names = ['get', 'options']

View File

@ -4,10 +4,10 @@ from rest_framework.response import Response
from accounts import serializers from accounts import serializers
from accounts.models import AccountTemplate from accounts.models import AccountTemplate
from accounts.mixins import AccountRecordViewLogMixin
from assets.const import Protocol from assets.const import Protocol
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from common.permissions import UserConfirmation, ConfirmType from common.permissions import UserConfirmation, ConfirmType
from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
@ -55,7 +55,7 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
return Response(data=serializer.data) return Response(data=serializer.data)
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet): class AccountTemplateSecretsViewSet(AccountRecordViewLogMixin, AccountTemplateViewSet):
serializer_classes = { serializer_classes = {
'default': serializers.AccountTemplateSecretSerializer, '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") delete = "delete", _("Delete")
create = "create", _("Create") create = "create", _("Create")
# Activities action # Activities action
download = "download", _("Download")
connect = "connect", _("Connect") connect = "connect", _("Connect")
login = "login", _("Login") login = "login", _("Login")
change_auth = "change_password", _("Change password") change_auth = "change_password", _("Change password")

View File

@ -88,6 +88,7 @@ class AsyncApiMixin(InterceptMixin):
if not self.is_need_async(): if not self.is_need_async():
return handler(*args, **kwargs) return handler(*args, **kwargs)
resp = self.do_async(handler, *args, **kwargs) resp = self.do_async(handler, *args, **kwargs)
self.async_callback(*args, **kwargs)
return resp return resp
def is_need_refresh(self): def is_need_refresh(self):
@ -98,6 +99,9 @@ class AsyncApiMixin(InterceptMixin):
def is_need_async(self): def is_need_async(self):
return False return False
def async_callback(self, params):
pass
def do_async(self, handler, *args, **kwargs): def do_async(self, handler, *args, **kwargs):
data = self.get_cache_data() data = self.get_cache_data()
if not data: if not data:

View File

@ -2,16 +2,14 @@
# #
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
from django.http.response import JsonResponse from django.http.response import JsonResponse
from django.utils import translation from django.db.models import Model
from django.utils.translation import gettext_noop
from rest_framework import permissions from rest_framework import permissions
from rest_framework.request import Request 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.handler import create_or_update_operate_log
from audits.models import ActivityLog from audits.models import ActivityLog
from common.exceptions import UserConfirmRequired from common.exceptions import UserConfirmRequired
from common.utils import i18n_fmt
from orgs.utils import current_org from orgs.utils import current_org
__all__ = [ __all__ = [
@ -49,66 +47,19 @@ class PermissionsMixin(UserPassesTestMixin):
class RecordViewLogMixin: class RecordViewLogMixin:
ACTION = ActionChoices.view model: Model
@staticmethod def record_logs(self, ids, action, detail, model=None, **kwargs):
def _filter_params(params): model = model or self.model
new_params = {} resource_type = model._meta.verbose_name
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
create_or_update_operate_log( create_or_update_operate_log(
self.ACTION, resource_type, force=True, **kwargs action, resource_type, force=True, **kwargs
)
detail = i18n_fmt(
gettext_noop('User %s view/export secret'), self.request.user
) )
activities = [ activities = [
ActivityLog( ActivityLog(
resource_id=getattr(resource_id, 'pk', resource_id), resource_id=resource_id, type=ActivityChoices.operate_log,
type=ActivityChoices.operate_log, detail=detail, org_id=current_org.id, detail=detail, org_id=current_org.id,
) )
for resource_id in ids for resource_id in ids
] ]
ActivityLog.objects.bulk_create(activities) 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.http import FileResponse
from django.shortcuts import get_object_or_404, reverse from django.shortcuts import get_object_or_404, reverse
from django.utils.encoding import escape_uri_path 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 django_filters import rest_framework as filters
from rest_framework import generics from rest_framework import generics
from rest_framework import viewsets, views 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.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from audits.const import ActionChoices
from common.api import AsyncApiMixin from common.api import AsyncApiMixin
from common.const.http import GET from common.const.http import GET
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from common.drf.filters import DatetimeRangeFilterBackend from common.drf.filters import DatetimeRangeFilterBackend
from common.drf.renders import PassthroughRenderer from common.drf.renders import PassthroughRenderer
from common.storage.replay import ReplayStorageHandler 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.utils import get_logger, get_object_or_none
from common.views.mixins import RecordViewLogMixin
from orgs.mixins.api import OrgBulkModelViewSet 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 rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
@ -70,7 +72,7 @@ class SessionFilterSet(BaseFilterSet):
return queryset.filter(terminal__name=value) return queryset.filter(terminal__name=value)
class SessionViewSet(OrgBulkModelViewSet): class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
model = Session model = Session
serializer_classes = { serializer_classes = {
'default': serializers.SessionSerializer, 'default': serializers.SessionSerializer,
@ -132,6 +134,15 @@ class SessionViewSet(OrgBulkModelViewSet):
filename = escape_uri_path('{}.tar'.format(storage.obj.id)) filename = escape_uri_path('{}.tar'.format(storage.obj.id))
disposition = "attachment; filename*=UTF-8''{}".format(filename) disposition = "attachment; filename*=UTF-8''{}".format(filename)
response["Content-Disposition"] = disposition 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 return response
def get_queryset(self): def get_queryset(self):
@ -152,7 +163,7 @@ class SessionViewSet(OrgBulkModelViewSet):
return super().perform_create(serializer) return super().perform_create(serializer)
class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet): class SessionReplayViewSet(AsyncApiMixin, RecordViewLogMixin, viewsets.ViewSet):
serializer_class = serializers.ReplaySerializer serializer_class = serializers.ReplaySerializer
download_cache_key = "SESSION_REPLAY_DOWNLOAD_{}" download_cache_key = "SESSION_REPLAY_DOWNLOAD_{}"
session = None session = None
@ -215,6 +226,18 @@ class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet):
return False return False
return True 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): def retrieve(self, request, *args, **kwargs):
session_id = kwargs.get('pk') session_id = kwargs.get('pk')
session = get_object_or_404(Session, id=session_id) session = get_object_or_404(Session, id=session_id)