mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-01 11:25:22 +00:00
perf: Export resources to add operation logs
This commit is contained in:
parent
e8e0ea920b
commit
1f60e328b6
@ -1,65 +1,15 @@
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from django.db.models import Model
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.translation import gettext_noop
|
|
||||||
|
|
||||||
from audits.const import ActionChoices
|
from audits.const import ActionChoices
|
||||||
from common.views.mixins import RecordViewLogMixin
|
from audits.handler import create_or_update_operate_log
|
||||||
from common.utils import i18n_fmt
|
|
||||||
|
|
||||||
|
|
||||||
class AccountRecordViewLogMixin(RecordViewLogMixin):
|
class AccountRecordViewLogMixin(object):
|
||||||
get_object: callable
|
get_object: callable
|
||||||
get_queryset: callable
|
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
|
|
||||||
|
|
||||||
@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):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
retrieve_func = getattr(super(), 'retrieve')
|
retrieve_func = getattr(super(), 'retrieve')
|
||||||
@ -67,9 +17,9 @@ class AccountRecordViewLogMixin(RecordViewLogMixin):
|
|||||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
response = retrieve_func(request, *args, **kwargs)
|
response = retrieve_func(request, *args, **kwargs)
|
||||||
with translation.override('en'):
|
with translation.override('en'):
|
||||||
resource = self.get_object()
|
create_or_update_operate_log(
|
||||||
self.record_logs(
|
ActionChoices.view, self.model._meta.verbose_name,
|
||||||
[resource.id], ActionChoices.view, self.detail_msg, resource=resource
|
force=True, resource=self.get_object(),
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ class ActionChoices(TextChoices):
|
|||||||
update = "update", _("Update")
|
update = "update", _("Update")
|
||||||
delete = "delete", _("Delete")
|
delete = "delete", _("Delete")
|
||||||
create = "create", _("Create")
|
create = "create", _("Create")
|
||||||
|
export = "export", _("Export")
|
||||||
# Activities action
|
# Activities action
|
||||||
download = "download", _("Download")
|
download = "download", _("Download")
|
||||||
connect = "connect", _("Connect")
|
connect = "connect", _("Connect")
|
||||||
|
@ -6,12 +6,16 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Value, CharField
|
from django.db.models import F, Value, CharField
|
||||||
from django.db.models.functions import Concat
|
from django.db.models.functions import Concat
|
||||||
|
from django.utils import translation
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from common.db.fields import RelatedManager
|
from common.db.fields import RelatedManager
|
||||||
from common.utils import validate_ip, get_ip_city, get_logger
|
from common.utils import validate_ip, get_ip_city, get_logger
|
||||||
from common.utils.timezone import as_current_tz
|
from common.utils.timezone import as_current_tz
|
||||||
from .const import DEFAULT_CITY
|
from .const import DEFAULT_CITY, ActivityChoices as LogChoice
|
||||||
|
from .handler import create_or_update_operate_log
|
||||||
|
from .models import ActivityLog
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -140,3 +144,15 @@ def construct_userlogin_usernames(user_queryset):
|
|||||||
).values_list("usernames_combined_field", flat=True)
|
).values_list("usernames_combined_field", flat=True)
|
||||||
usernames = list(chain(usernames_original, usernames_combined))
|
usernames = list(chain(usernames_original, usernames_combined))
|
||||||
return usernames
|
return usernames
|
||||||
|
|
||||||
|
|
||||||
|
def record_operate_log_and_activity_log(ids, action, detail, model, **kwargs):
|
||||||
|
from orgs.utils import current_org
|
||||||
|
|
||||||
|
org_id = current_org.id
|
||||||
|
with translation.override('en'):
|
||||||
|
resource_type = model._meta.verbose_name
|
||||||
|
create_or_update_operate_log(action, resource_type, force=True, **kwargs)
|
||||||
|
base_data = {'type': LogChoice.operate_log, 'detail': detail, 'org_id': org_id}
|
||||||
|
activities = [ActivityLog(resource_id=r_id, **base_data) for r_id in ids]
|
||||||
|
ActivityLog.objects.bulk_create(activities)
|
||||||
|
@ -13,11 +13,13 @@ from rest_framework.utils import encoders, json
|
|||||||
|
|
||||||
from common.serializers import fields as common_fields
|
from common.serializers import fields as common_fields
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from .mixins import LogMixin
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class BaseFileRenderer(BaseRenderer):
|
class BaseFileRenderer(LogMixin, BaseRenderer):
|
||||||
# 渲染模板标识, 导入、导出、更新模板: ['import', 'update', 'export']
|
# 渲染模板标识, 导入、导出、更新模板: ['import', 'update', 'export']
|
||||||
template = 'export'
|
template = 'export'
|
||||||
serializer = None
|
serializer = None
|
||||||
@ -256,6 +258,8 @@ class BaseFileRenderer(BaseRenderer):
|
|||||||
logger.debug(e, exc_info=True)
|
logger.debug(e, exc_info=True)
|
||||||
value = 'Render error! ({})'.format(self.media_type).encode('utf-8')
|
value = 'Render error! ({})'.format(self.media_type).encode('utf-8')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
self.record_logs(request, view, data)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def compress_into_zip_file(self, value, request, response):
|
def compress_into_zip_file(self, value, request, response):
|
||||||
|
69
apps/common/drf/renders/mixins.py
Normal file
69
apps/common/drf/renders/mixins.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from django.utils.translation import gettext_noop
|
||||||
|
|
||||||
|
from audits.const import ActionChoices
|
||||||
|
from audits.utils import record_operate_log_and_activity_log
|
||||||
|
from common.utils import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class LogMixin(object):
|
||||||
|
@staticmethod
|
||||||
|
def _clean_params(query_params):
|
||||||
|
clean_params = {}
|
||||||
|
ignore_params = ('format', 'order')
|
||||||
|
for key, value in dict(query_params).items():
|
||||||
|
if key in ignore_params:
|
||||||
|
continue
|
||||||
|
if isinstance(value, list):
|
||||||
|
value = list(filter(None, value))
|
||||||
|
if value:
|
||||||
|
clean_params[key] = value
|
||||||
|
return clean_params
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_model(view):
|
||||||
|
model = getattr(view, 'model', None)
|
||||||
|
if not model:
|
||||||
|
serializer = view.get_serializer()
|
||||||
|
if serializer:
|
||||||
|
model = serializer.Meta.model
|
||||||
|
return model
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _build_after(params, data):
|
||||||
|
base = {
|
||||||
|
gettext_noop('Resource count'): {'value': len(data)}
|
||||||
|
}
|
||||||
|
extra = {key: {'value': value} for key, value in params.items()}
|
||||||
|
return {**extra, **base}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_resource_display(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:
|
||||||
|
display_message = gettext_noop("Export filtered")
|
||||||
|
return display_message
|
||||||
|
|
||||||
|
def record_logs(self, request, view, data):
|
||||||
|
activity_ids, activity_detail = [], ''
|
||||||
|
model = self._get_model(view)
|
||||||
|
if not model:
|
||||||
|
logger.warning('Model is not defined in view: %s' % view)
|
||||||
|
return
|
||||||
|
|
||||||
|
params = self._clean_params(request.query_params)
|
||||||
|
resource_display = self.get_resource_display(params)
|
||||||
|
after = self._build_after(params, data)
|
||||||
|
if hasattr(view, 'get_activity_detail_msg'):
|
||||||
|
activity_detail = view.get_activity_detail_msg()
|
||||||
|
activity_ids = [d['id'] for d in data if 'id' in d]
|
||||||
|
record_operate_log_and_activity_log(
|
||||||
|
activity_ids, ActionChoices.export, activity_detail,
|
||||||
|
model, resource_display=resource_display, after=after
|
||||||
|
)
|
@ -2,20 +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.db.models import Model
|
|
||||||
from django.utils import translation
|
|
||||||
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 ActivityChoices
|
|
||||||
from audits.handler import create_or_update_operate_log
|
|
||||||
from audits.models import ActivityLog
|
|
||||||
from common.exceptions import UserConfirmRequired
|
from common.exceptions import UserConfirmRequired
|
||||||
from orgs.utils import current_org
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"PermissionsMixin",
|
"PermissionsMixin",
|
||||||
"RecordViewLogMixin",
|
|
||||||
"UserConfirmRequiredExceptionMixin",
|
"UserConfirmRequiredExceptionMixin",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -45,23 +39,3 @@ class PermissionsMixin(UserPassesTestMixin):
|
|||||||
if not permission_class().has_permission(self.request, self):
|
if not permission_class().has_permission(self.request, self):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class RecordViewLogMixin:
|
|
||||||
model: Model
|
|
||||||
|
|
||||||
def record_logs(self, ids, action, detail, model=None, **kwargs):
|
|
||||||
with translation.override('en'):
|
|
||||||
model = model or self.model
|
|
||||||
resource_type = model._meta.verbose_name
|
|
||||||
create_or_update_operate_log(
|
|
||||||
action, resource_type, force=True, **kwargs
|
|
||||||
)
|
|
||||||
activities = [
|
|
||||||
ActivityLog(
|
|
||||||
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)
|
|
||||||
|
@ -18,6 +18,7 @@ from rest_framework.permissions import IsAuthenticated
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from audits.const import ActionChoices
|
from audits.const import ActionChoices
|
||||||
|
from audits.utils import record_operate_log_and_activity_log
|
||||||
from common.api import AsyncApiMixin
|
from common.api import AsyncApiMixin
|
||||||
from common.const.http import GET, POST
|
from common.const.http import GET, POST
|
||||||
from common.drf.filters import BaseFilterSet
|
from common.drf.filters import BaseFilterSet
|
||||||
@ -27,7 +28,6 @@ from common.permissions import IsServiceAccount
|
|||||||
from common.storage.replay import ReplayStorageHandler, SessionPartReplayStorageHandler
|
from common.storage.replay import ReplayStorageHandler, SessionPartReplayStorageHandler
|
||||||
from common.utils import data_to_json, is_uuid, i18n_fmt
|
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
|
||||||
@ -77,7 +77,7 @@ class SessionFilterSet(BaseFilterSet):
|
|||||||
return queryset.filter(terminal__name=value)
|
return queryset.filter(terminal__name=value)
|
||||||
|
|
||||||
|
|
||||||
class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
|
class SessionViewSet(OrgBulkModelViewSet):
|
||||||
model = Session
|
model = Session
|
||||||
serializer_classes = {
|
serializer_classes = {
|
||||||
'default': serializers.SessionSerializer,
|
'default': serializers.SessionSerializer,
|
||||||
@ -153,7 +153,7 @@ class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
|
|||||||
detail = i18n_fmt(
|
detail = i18n_fmt(
|
||||||
REPLAY_OP, self.request.user, _('Download'), str(session)
|
REPLAY_OP, self.request.user, _('Download'), str(session)
|
||||||
)
|
)
|
||||||
self.record_logs(
|
record_operate_log_and_activity_log(
|
||||||
[session.asset_id], ActionChoices.download, detail,
|
[session.asset_id], ActionChoices.download, detail,
|
||||||
model=Session, resource_display=str(session)
|
model=Session, resource_display=str(session)
|
||||||
)
|
)
|
||||||
@ -211,7 +211,7 @@ class SessionViewSet(RecordViewLogMixin, OrgBulkModelViewSet):
|
|||||||
return super().perform_create(serializer)
|
return super().perform_create(serializer)
|
||||||
|
|
||||||
|
|
||||||
class SessionReplayViewSet(AsyncApiMixin, RecordViewLogMixin, viewsets.ViewSet):
|
class SessionReplayViewSet(AsyncApiMixin, 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
|
||||||
@ -283,7 +283,7 @@ class SessionReplayViewSet(AsyncApiMixin, RecordViewLogMixin, viewsets.ViewSet):
|
|||||||
detail = i18n_fmt(
|
detail = i18n_fmt(
|
||||||
REPLAY_OP, self.request.user, _('View'), str(session)
|
REPLAY_OP, self.request.user, _('View'), str(session)
|
||||||
)
|
)
|
||||||
self.record_logs(
|
record_operate_log_and_activity_log(
|
||||||
[session.asset_id], ActionChoices.download, detail,
|
[session.asset_id], ActionChoices.download, detail,
|
||||||
model=Session, resource_display=str(session)
|
model=Session, resource_display=str(session)
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user