From dfcbdb0c3544fa31beb7a7ce6dc4c1c5801453be Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jul 2019 18:03:01 +0800 Subject: [PATCH] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9command=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=92=8C=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 8 +- apps/static/js/jumpserver.js | 17 ++- apps/terminal/api/__init__.py | 1 + apps/terminal/api/command.py | 100 ++++++++++++++++++ apps/terminal/api/session.py | 80 ++------------ .../templates/terminal/command_list.html | 32 +++--- apps/terminal/urls/api_urls.py | 1 + apps/terminal/urls/views_urls.py | 1 - apps/terminal/utils.py | 2 +- apps/terminal/views/command.py | 45 +------- apps/terminal/views/session.py | 12 +-- 11 files changed, 147 insertions(+), 152 deletions(-) create mode 100644 apps/terminal/api/command.py diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 10e2d2547..f13bb667a 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -44,8 +44,12 @@ app_view_patterns = [ if settings.XPACK_ENABLED: - app_view_patterns.append(path('xpack/', include('xpack.urls.view_urls', namespace='xpack'))) - api_v1.append(path('xpack/v1/', include('xpack.urls.api_urls', namespace='api-xpack'))) + app_view_patterns.append( + path('xpack/', include('xpack.urls.view_urls', namespace='xpack')) + ) + api_v1.append( + path('xpack/v1/', include('xpack.urls.api_urls', namespace='api-xpack')) + ) js_i18n_patterns = i18n_patterns( path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 3880b496e..1b8b9321e 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -467,14 +467,15 @@ jumpserver.initDataTable = function (options) { ]; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; var select = { - style: 'multi', - selector: 'td:first-child' - }; + style: 'multi', + selector: 'td:first-child' + }; var table = ele.DataTable({ pageLength: options.pageLength || 15, dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>', order: options.order || [], // select: options.select || 'multi', + searchDelay: 800, buttons: [], columnDefs: columnDefs, ajax: { @@ -574,6 +575,7 @@ jumpserver.initServerSideDataTable = function (options) { columnDefs: columnDefs, serverSide: true, processing: true, + searchDelay: 800, ajax: { url: options.ajax_url , error: function(jqXHR, textStatus, errorThrown) { @@ -1103,3 +1105,12 @@ function formatDateAsCN(d) { var date = new Date(d); return date.toISOString().replace("T", " ").replace(/\..*/, ""); } + +function getUrlParams(url) { + url = url.split("?"); + let params = ""; + if (url.length === 2){ + params = url[1]; + } + return params +} \ No newline at end of file diff --git a/apps/terminal/api/__init__.py b/apps/terminal/api/__init__.py index 21e78a2d7..d33905b78 100644 --- a/apps/terminal/api/__init__.py +++ b/apps/terminal/api/__init__.py @@ -2,4 +2,5 @@ # from .terminal import * from .session import * +from .command import * from .task import * diff --git a/apps/terminal/api/command.py b/apps/terminal/api/command.py new file mode 100644 index 000000000..58523eb99 --- /dev/null +++ b/apps/terminal/api/command.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +# +import time +from django.utils import timezone +from django.shortcuts import HttpResponse +from rest_framework.pagination import LimitOffsetPagination +from rest_framework import viewsets +from rest_framework import generics +from rest_framework.response import Response +from django.template import loader + + +from common.permissions import IsOrgAdminOrAppUser, IsAuditor +from common.utils import get_logger +from ..backends import ( + get_command_storage, get_multi_command_storage, + SessionCommandSerializer, +) + +logger = get_logger(__name__) +__all__ = ['CommandViewSet', 'CommandExportApi'] + + +class CommandQueryMixin: + command_store = get_command_storage() + pagination_class = LimitOffsetPagination + permission_classes = [IsOrgAdminOrAppUser | IsAuditor] + filter_fields = [ + "asset", "system_user", "user", "input", "session", + ] + default_days_ago = 5 + + def get_queryset(self): + date_from, date_to = self.get_date_range() + multi_command_storage = get_multi_command_storage() + queryset = multi_command_storage.filter(date_from=date_from, date_to=date_to) + return queryset + + def get_filter_fields(self): + fields = self.filter_fields + fields.extend(["date_from", "date_to"]) + return fields + + def get_date_range(self): + now = timezone.now() + days_ago = now - timezone.timedelta(days=self.default_days_ago) + default_start_st = days_ago.timestamp() + default_end_st = now.timestamp() + query_params = self.request.query_params + date_from_st = query_params.get("date_from") or default_start_st + date_to_st = query_params.get("date_to") or default_end_st + return float(date_from_st), float(date_to_st) + + +class CommandViewSet(CommandQueryMixin, viewsets.ModelViewSet): + """接受app发送来的command log, 格式如下 + { + "user": "admin", + "asset": "localhost", + "system_user": "web", + "session": "xxxxxx", + "input": "whoami", + "output": "d2hvbWFp", # base64.b64encode(s) + "timestamp": 1485238673.0 + } + + """ + command_store = get_command_storage() + serializer_class = SessionCommandSerializer + + def create(self, request, *args, **kwargs): + serializer = self.serializer_class(data=request.data, many=True) + if serializer.is_valid(): + ok = self.command_store.bulk_save(serializer.validated_data) + if ok: + return Response("ok", status=201) + else: + return Response("Save error", status=500) + else: + msg = "Command not valid: {}".format(serializer.errors) + logger.error(msg) + return Response({"msg": msg}, status=401) + + +class CommandExportApi(CommandQueryMixin, generics.ListAPIView): + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + + template = 'terminal/command_report.html' + context = { + 'queryset': queryset, + 'total_count': len(queryset), + 'now': time.time(), + } + content = loader.render_to_string(template, context, request) + content_type = 'application/octet-stream' + response = HttpResponse(content, content_type) + filename = 'command-report-{}.html'.format(int(time.time())) + response['Content-Disposition'] = 'attachment; filename="%s"' % filename + return response diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index f634f5628..deb005b77 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -1,13 +1,11 @@ # -*- coding: utf-8 -*- # -import logging import os from django.shortcuts import get_object_or_404 from django.core.files.storage import default_storage from django.http import HttpResponseNotFound from django.conf import settings -from django.utils import timezone from rest_framework.pagination import LimitOffsetPagination from rest_framework import viewsets from rest_framework.response import Response @@ -15,16 +13,15 @@ from rest_framework_bulk import BulkModelViewSet import jms_storage -from common.utils import is_uuid +from common.utils import is_uuid, get_logger from common.permissions import IsOrgAdminOrAppUser, IsAuditor from ..hands import SystemUser -from ..models import Terminal, Session +from ..models import Session from .. import serializers -from ..backends import get_command_storage, get_multi_command_storage, \ - SessionCommandSerializer -__all__ = ['SessionViewSet', 'SessionReplayViewSet', 'CommandViewSet'] -logger = logging.getLogger(__file__) + +__all__ = ['SessionViewSet', 'SessionReplayViewSet',] +logger = get_logger(__name__) class SessionViewSet(BulkModelViewSet): @@ -32,15 +29,7 @@ class SessionViewSet(BulkModelViewSet): serializer_class = serializers.SessionSerializer pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser | IsAuditor, ) - - def get_queryset(self): - queryset = super().get_queryset() - terminal_id = self.kwargs.get("terminal", None) - if terminal_id: - terminal = get_object_or_404(Terminal, id=terminal_id) - queryset = queryset.filter(terminal=terminal) - return queryset - return queryset + filter_fields = ["user", "asset", "system_user", "terminal"] def get_object(self): # 解决guacamole更新session时并发导致幽灵会话的问题 @@ -60,63 +49,6 @@ class SessionViewSet(BulkModelViewSet): return super().perform_create(serializer) -class CommandViewSet(viewsets.ModelViewSet): - """接受app发送来的command log, 格式如下 - { - "user": "admin", - "asset": "localhost", - "system_user": "web", - "session": "xxxxxx", - "input": "whoami", - "output": "d2hvbWFp", # base64.b64encode(s) - "timestamp": 1485238673.0 - } - - """ - command_store = get_command_storage() - serializer_class = SessionCommandSerializer - pagination_class = LimitOffsetPagination - permission_classes = [IsOrgAdminOrAppUser | IsAuditor] - filter_fields = [ - "asset", "system_user", "user", "input", "session", - ] - default_days_ago = 5 - - def get_queryset(self): - date_from, date_to = self.get_date_range() - multi_command_storage = get_multi_command_storage() - queryset = multi_command_storage.filter(date_from=date_from, date_to=date_to) - return queryset - - def get_filter_fields(self): - fields = self.filter_fields - fields.extend(["date_from", "date_to"]) - return fields - - def get_date_range(self): - now = timezone.now() - days_ago = now - timezone.timedelta(days=self.default_days_ago) - default_start_st = days_ago.timestamp() - default_end_st = now.timestamp() - query_params = self.request.query_params - date_from_st = query_params.get("date_from") or default_start_st - date_to_st = query_params.get("date_to") or default_end_st - return int(date_from_st), int(date_to_st) - - def create(self, request, *args, **kwargs): - serializer = self.serializer_class(data=request.data, many=True) - if serializer.is_valid(): - ok = self.command_store.bulk_save(serializer.validated_data) - if ok: - return Response("ok", status=201) - else: - return Response("Save error", status=500) - else: - msg = "Command not valid: {}".format(serializer.errors) - logger.error(msg) - return Response({"msg": msg}, status=401) - - class SessionReplayViewSet(viewsets.ViewSet): serializer_class = serializers.ReplaySerializer permission_classes = (IsOrgAdminOrAppUser,) diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html index eba0d6a60..addece387 100644 --- a/apps/terminal/templates/terminal/command_list.html +++ b/apps/terminal/templates/terminal/command_list.html @@ -80,7 +80,7 @@ $(document).ready(function () { table = initTable().on("init", function () { var dateFromRef = $("#date_from"); var dateToRef = $("#date_to"); - let options = { + var options = { format: "yyyy-mm-dd", todayBtn: "linked", keyboardNavigation: false, @@ -90,16 +90,15 @@ $(document).ready(function () { language: navigator.language || "en", }; dateFromRef.datepicker(options).on("changeDate", function () { - let date = new Date($(this).val() + ' 0:0:0'); - let url = table.ajax.url(); + var date = new Date($(this).val() + ' 0:0:0'); + var url = table.ajax.url(); url = setUrlParam(url, "date_from", date.getTime()/1000); table.ajax.url(url); table.ajax.reload(); - console.log("On change") }); dateToRef.datepicker(options).on("changeDate", function () { - let date = new Date($(this).val() + ' 23:59:59'); - let url = table.ajax.url(); + var date = new Date($(this).val() + ' 23:59:59'); + var url = table.ajax.url(); url = setUrlParam(url, "date_to", date.getTime()/1000); table.ajax.url(url); table.ajax.reload(); @@ -107,15 +106,11 @@ $(document).ready(function () { }); }) .on('click', '#btn_bulk_update', function(){ - var action = $('#slct_bulk_update').val(); - var param_action = '&action=' + action; - var local_params = window.location.search; - if(!local_params){ - param_action = '?action=' + action; - } - var params = local_params + param_action; - var pathname = window.location.pathname + 'export/'; - var url = pathname + params; + // var action = $('#slct_bulk_update').val(); + var params = getUrlParams(table.ajax.url()); + + var exportPath = "{% url 'api-terminal:command-export' %}"; + var url = exportPath + "?" + params; window.open(url); }).on("click", '#command_table_filter input', function (e) { e.preventDefault(); @@ -182,8 +177,13 @@ function format(d) { return output } +var commandListUrl = '{% url "api-terminal:command-list" %}'; +var dateFrom = "{{ date_from.timestamp }}"; +var dateTo = "{{ date_to.timestamp }}"; function initTable() { + commandListUrl = setUrlParam(commandListUrl, "date_from", dateFrom); + commandListUrl = setUrlParam(commandListUrl, "date_to", dateTo); var options = { ele: $('#command_table'), columnDefs: [ @@ -202,7 +202,7 @@ function initTable() { }}, ], toggle: true, - ajax_url: '{% url "api-terminal:command-list" %}', + ajax_url: commandListUrl, columns: [ {data: "id"}, {data: "input", orderable: false}, {data: "user", orderable: false}, {data: "asset"}, {data: "system_user"}, diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 54724c7ad..95ccc85e2 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -26,6 +26,7 @@ urlpatterns = [ path('terminal//access-key/', api.TerminalTokenApi.as_view(), name='terminal-access-key'), path('terminal/config/', api.TerminalConfig.as_view(), name='terminal-config'), + path('commands/export/', api.CommandExportApi.as_view(), name="command-export") # v2: get session's replay # path('v2/sessions//replay/', # api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}), diff --git a/apps/terminal/urls/views_urls.py b/apps/terminal/urls/views_urls.py index 3e144da85..61db49c45 100644 --- a/apps/terminal/urls/views_urls.py +++ b/apps/terminal/urls/views_urls.py @@ -25,6 +25,5 @@ urlpatterns = [ # Command view path('command/', views.CommandListView.as_view(), name='command-list'), - path('command/export/', views.CommandExportView.as_view(), name='command-export') ] diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index 38a5d24d2..d7dace0a9 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -9,7 +9,7 @@ from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY def get_session_asset_list(): - return Asset.objects.values_list() + return Asset.objects.values_list('hostname', flat=True) def get_session_user_list(): diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 170f5c1c8..4167b3d47 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -10,10 +10,9 @@ import time from common.mixins import DatetimeSearchMixin from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor -from ..models import Command from ..backends import get_multi_command_storage -__all__ = ['CommandListView', 'CommandExportView'] +__all__ = ['CommandListView'] common_storage = get_multi_command_storage() @@ -33,45 +32,3 @@ class CommandListView(DatetimeSearchMixin, PermissionsMixin, TemplateView): kwargs.update(context) return super().get_context_data(**kwargs) - -class CommandExportView(DatetimeSearchMixin, PermissionsMixin, View): - model = Command - command = user = asset = system_user = action = '' - date_from = date_to = None - permission_classes = [IsOrgAdmin | IsAuditor] - - def get(self, request, *args, **kwargs): - queryset = self.get_queryset() - template = 'terminal/command_report.html' - context = { - 'queryset': queryset, - 'total_count': len(queryset), - 'now': time.time(), - } - content = loader.render_to_string(template, context, request) - content_type = 'application/octet-stream' - response = HttpResponse(content, content_type) - filename = 'command-report-{}.html'.format(int(time.time())) - response['Content-Disposition'] = 'attachment; filename="%s"' % filename - return response - - def get_queryset(self): - self.get_date_range() - self.action = self.request.GET.get('action', '') - self.command = self.request.GET.get('command', '') - self.user = self.request.GET.get("user", '') - self.asset = self.request.GET.get('asset', '') - self.system_user = self.request.GET.get('system_user', '') - filter_kwargs = dict() - filter_kwargs['date_from'] = self.date_from - filter_kwargs['date_to'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if self.asset: - filter_kwargs['asset'] = self.asset - if self.system_user: - filter_kwargs['system_user'] = self.system_user - if self.command: - filter_kwargs['input'] = self.command - queryset = common_storage.filter(**filter_kwargs) - return queryset diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index cf4484c06..6903c90fd 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -38,21 +38,11 @@ class SessionListView(PermissionsMixin, DatetimeSearchMixin, ListView): filter_kwargs = dict() filter_kwargs['date_start__gt'] = self.date_from filter_kwargs['date_start__lt'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if self.asset: - filter_kwargs['asset'] = self.asset - if self.system_user: - filter_kwargs['system_user'] = self.system_user - if filter_kwargs: - self.queryset = self.queryset.filter(**filter_kwargs) return self.queryset def get_context_data(self, **kwargs): context = { - 'user_list': utils.get_session_user_list(), - 'asset_list': utils.get_session_asset_list(), - 'system_user_list': utils.get_session_system_user_list(), + 'asset_list': utils.get_session_asset_list()[:10], 'date_from': self.date_from, 'date_to': self.date_to, 'user': self.user,