Commit 4efad4e5 authored by BaiJiangJie's avatar BaiJiangJie

[Feature] 会话管理: Session 页面展示;Command 待续

parent 1e49de79
......@@ -126,7 +126,7 @@
</tr>
<tr>
<td>{% trans 'Date joined' %}:</td>
<td><b>{{ asset.date_joined|date:"Y-m-j H:i:s" }}</b></td>
<td><b>{{ asset.date_created|date:"Y-m-j H:i:s" }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
......
......@@ -146,7 +146,7 @@ function activeNav() {
if (app === ''){
$('#index').addClass('active');
}
else if (app === 'xpack' && resource === 'cloud') {
else if ((app === 'xpack' && resource === 'cloud') || (app === 'terminal' && (resource === 'asset' || resource === 'database'))) {
var item = url_array[3];
$("#" + app).addClass('active');
$('#' + app + ' #' + resource).addClass('active');
......
......@@ -59,9 +59,22 @@
<i class="fa fa-rocket" style="width: 14px"></i> <span class="nav-label">{% trans 'Sessions' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li>
<li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li>
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li>
<li id="asset">
<a href="#"><span class="nav-label">{% trans 'Asset sessions' %}</span><span class="fa arrow"></span></a>
<ul class="nav nav-third-level">
<li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li>
<li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li>
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li>
</ul>
</li>
<li id="database">
<a href="#"><span class="nav-label">{% trans 'Database sessions' %}</span><span class="fa arrow"></span></a>
<ul class="nav nav-third-level">
<li id="session-online"><a href="{% url 'terminal:database-session-online-list' %}">{% trans 'Session online' %}</a></li>
<li id="session-offline"><a href="{% url 'terminal:database-session-offline-list' %}">{% trans 'Session offline' %}</a></li>
{# <li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li>#}
</ul>
</li>
<li>
<a href="{% url 'terminal:web-terminal' %}" target="_blank">
<span class="nav-label">{% trans 'Web terminal' %}</span>
......
......@@ -14,33 +14,19 @@ from common.permissions import IsOrgAdminOrAppUser, IsAuditor
from common.utils import get_logger
from ..backends import (
get_command_storage, get_multi_command_storage,
SessionCommandSerializer,
SessionCommandSerializer, DatabaseSessionCommandSerializer
)
logger = get_logger(__name__)
__all__ = ['CommandViewSet', 'CommandExportApi']
__all__ = ['CommandViewSet', 'CommandExportApi', 'DatabaseCommandViewSet']
class CommandQueryMixin:
class BaseCommandQueryMixin:
command_store = get_command_storage()
pagination_class = LimitOffsetPagination
permission_classes = [IsOrgAdminOrAppUser | IsAuditor]
filter_fields = [
"asset", "system_user", "user", "session",
]
default_days_ago = 5
def get_queryset(self):
date_from, date_to = self.get_date_range()
q = self.request.query_params
multi_command_storage = get_multi_command_storage()
queryset = multi_command_storage.filter(
date_from=date_from, date_to=date_to, input=q.get("input"),
user=q.get("user"), asset=q.get("asset"),
system_user=q.get("system_user")
)
return queryset
def filter_queryset(self, queryset):
return queryset
......@@ -60,21 +46,49 @@ class CommandQueryMixin:
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
}
class CommandQueryMixin(BaseCommandQueryMixin):
"""
Asset Command Query Mixin
"""
filter_fields = [
"user", "asset", "system_user", "session",
]
def get_queryset(self):
date_from, date_to = self.get_date_range()
q = self.request.query_params
multi_command_storage = get_multi_command_storage()
queryset = multi_command_storage.filter(
date_from=date_from, date_to=date_to, input=q.get("input"),
user=q.get("user"), asset=q.get("asset"),
system_user=q.get("system_user")
)
return queryset
class DatabaseCommandQueryMixin(BaseCommandQueryMixin):
"""
Database Command Query Mixin
"""
filter_fields = [
"user", "database", "db_host", "db_name", "db_user"
]
def get_queryset(self):
date_from, date_to = self.get_date_range()
q = self.request.query_params
multi_command_storage = get_multi_command_storage()
queryset = multi_command_storage.filter(
date_from=date_from, date_to=date_to, input=q.get("input"),
user=q.get("user"), database=q.get("database"),
db_host=q.get("db_host"), db_name=q.get("db_name"),
db_user=q.get("db_user")
)
return queryset
class BaseCommandViewSet(viewsets.ModelViewSet):
command_store = get_command_storage()
serializer_class = SessionCommandSerializer
def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, many=True)
......@@ -90,6 +104,31 @@ class CommandViewSet(CommandQueryMixin, viewsets.ModelViewSet):
return Response({"msg": msg}, status=401)
class CommandViewSet(CommandQueryMixin, BaseCommandViewSet):
"""
Asset Command ViewSet
接受app发送来的command log, 格式如下
{
"user": "admin",
"asset": "localhost",
"system_user": "web",
"session": "xxxxxx",
"input": "whoami",
"output": "d2hvbWFp", # base64.b64encode(s)
"timestamp": 1485238673.0
}
"""
serializer_class = SessionCommandSerializer
class DatabaseCommandViewSet(DatabaseCommandQueryMixin, BaseCommandViewSet):
"""
Database Command ViewSet
"""
serializer_class = DatabaseSessionCommandSerializer
class CommandExportApi(CommandQueryMixin, generics.ListAPIView):
serializer_class = SessionCommandSerializer
......@@ -108,3 +147,5 @@ class CommandExportApi(CommandQueryMixin, generics.ListAPIView):
filename = 'command-report-{}.html'.format(int(time.time()))
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
return response
......@@ -68,7 +68,8 @@ class DatabaseSessionViewSet(OrgBulkModelViewSet):
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser | IsAuditor, )
filter_fields = [
'user', 'database', 'db_host', 'db_name', 'db_user'
'user', 'database', 'db_host', 'db_name', 'db_user',
'is_finished', 'terminal', 'remote_addr'
]
date_range_filter_fields = [
('date_start', ('date_from', 'date_to'))
......
......@@ -6,7 +6,7 @@ from rest_framework_bulk import BulkModelViewSet
from common.utils import get_object_or_none
from common.permissions import IsOrgAdminOrAppUser
from ..models import Session, Task
from ..models import Session, Task, DatabaseSession
from .. import serializers
......@@ -28,6 +28,8 @@ class KillSessionAPI(APIView):
validated_session = []
for session_id in request.data:
session = get_object_or_none(Session, id=session_id)
if not session:
session = get_object_or_none(DatabaseSession, id=session_id)
if session and not session.is_finished:
validated_session.append(session_id)
self.model.objects.create(
......
from importlib import import_module
from django.conf import settings
from .command.serializers import SessionCommandSerializer
from .command.serializers import (
SessionCommandSerializer, DatabaseSessionCommandSerializer
)
from common import utils
......
......@@ -7,19 +7,14 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin
class AbstractSessionCommand(OrgModelMixin):
class BaseAbstractSessionCommand(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=64, db_index=True, verbose_name=_("User"))
asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset"))
system_user = models.CharField(max_length=64, db_index=True, verbose_name=_("System user"))
input = models.CharField(max_length=128, db_index=True, verbose_name=_("Input"))
output = models.CharField(max_length=1024, blank=True, verbose_name=_("Output"))
session = models.CharField(max_length=36, db_index=True, verbose_name=_("Session"))
timestamp = models.IntegerField(db_index=True)
class Meta:
abstract = True
@classmethod
def from_dict(cls, d):
self = cls()
......@@ -43,3 +38,27 @@ class AbstractSessionCommand(OrgModelMixin):
def __str__(self):
return self.input
class Meta:
abstract = True
class AbstractSessionCommand(BaseAbstractSessionCommand):
asset = models.CharField(max_length=128, db_index=True, verbose_name=_("Asset"))
system_user = models.CharField(max_length=64, db_index=True, verbose_name=_("System user"))
class Meta:
abstract = True
class AbstractDatabaseSessionCommand(BaseAbstractSessionCommand):
user_id = models.CharField(max_length=36, db_index=True, verbose_name=_("User ID"))
database = models.CharField(max_length=128, verbose_name=_('Database'))
database_id = models.CharField(max_length=36, db_index=True, verbose_name=_("Database ID"))
db_host = models.CharField(max_length=128, verbose_name=_('Database host'))
db_name = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Database name'))
db_user = models.CharField(max_length=32, blank=True, null=True, verbose_name=_('Database user'))
class Meta:
abstract = True
......@@ -2,16 +2,26 @@
from rest_framework import serializers
class SessionCommandSerializer(serializers.Serializer):
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
class BaseSessionCommandSerializer(serializers.Serializer):
id = serializers.UUIDField(read_only=True)
user = serializers.CharField(max_length=64)
asset = serializers.CharField(max_length=128)
system_user = serializers.CharField(max_length=64)
input = serializers.CharField(max_length=128)
output = serializers.CharField(max_length=1024, allow_blank=True)
session = serializers.CharField(max_length=36)
org_id = serializers.CharField(max_length=36, required=False, default='', allow_null=True, allow_blank=True)
timestamp = serializers.IntegerField()
class SessionCommandSerializer(BaseSessionCommandSerializer):
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
user = serializers.CharField(max_length=64)
asset = serializers.CharField(max_length=128)
system_user = serializers.CharField(max_length=64)
class DatabaseSessionCommandSerializer(BaseSessionCommandSerializer):
user_id = serializers.CharField(max_length=36)
database = serializers.CharField(max_length=128)
database_id = serializers.CharField(max_length=36)
db_host = serializers.CharField(max_length=128)
db_name = serializers.CharField(max_length=128)
db_user = serializers.CharField(max_length=32)
......@@ -71,6 +71,9 @@ class DatabaseSession(BaseSession):
db_name = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Database name'))
db_user = models.CharField(max_length=32, blank=True, null=True, verbose_name=_('Database user'))
def can_replay(self):
return self.has_replay
class Meta:
db_table = "terminal_database_session"
ordering = ["-date_start"]
......@@ -50,7 +50,7 @@ class DatabaseSessionSerializer(BulkOrgResourceModelSerializer):
fields = [
'id', 'user', 'database', 'db_host', 'db_name', 'db_user',
'login_from_display', 'remote_addr', 'is_finished', 'has_replay',
'date_start', 'date_end', 'terminal', 'command_amount'
'can_replay', 'date_start', 'date_end', 'terminal', 'command_amount'
]
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
......@@ -18,12 +18,18 @@ urlpatterns = [
path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'),
path('web-sftp/', views.WebSFTPView.as_view(), name='web-sftp'),
# Session view
path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'),
path('session-offline/', views.SessionOfflineListView.as_view(), name='session-offline-list'),
path('session/<uuid:pk>/', views.SessionDetailView.as_view(), name='session-detail'),
# Asset session view
path('asset/session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'),
path('asset/session-offline/', views.SessionOfflineListView.as_view(), name='session-offline-list'),
path('asset/session/<uuid:pk>/', views.SessionDetailView.as_view(), name='session-detail'),
# Databases session view
path('database/session-online/', views.DatabaseSessionOnlineListView.as_view(), name='database-session-online-list'),
path('database/session-offline/', views.DatabaseSessionOfflineListView.as_view(), name='database-session-offline-list'),
path('database/session/<uuid:pk>/', views.DatabaseSessionDetailView.as_view(), name='database-session-detail'),
# Command view
path('command/', views.CommandListView.as_view(), name='command-list'),
path('asset/command/', views.CommandListView.as_view(), name='command-list'),
# path('database/command/', views.DatabaseCommandListView.as_view(), name='database-command-list'),
]
# -*- coding: utf-8 -*-
#
from .terminal import *
from .session import *
from .command import *
from .asset_session import *
from .asset_command import *
from .database_session import *
......@@ -11,7 +11,7 @@ __all__ = ['CommandListView']
class CommandListView(PermissionsMixin, TemplateView):
template_name = "terminal/command_list.html"
template_name = "terminal/asset_command_list.html"
permission_classes = [IsOrgAdmin | IsAuditor]
default_days_ago = 5
......
......@@ -4,14 +4,11 @@
from django.views.generic import ListView, TemplateView
from django.views.generic.edit import SingleObjectMixin
from django.utils.translation import ugettext as _
from django.utils import timezone
from django.conf import settings
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal
from ..models import Session
from ..backends import get_multi_command_storage
from .. import utils
from .base_session import BaseSessionListView
__all__ = [
......@@ -20,21 +17,9 @@ __all__ = [
]
class SessionListView(PermissionsMixin, TemplateView):
class SessionListView(BaseSessionListView):
model = Session
template_name = 'terminal/session_list.html'
date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor]
default_days_ago = 5
def get_context_data(self, **kwargs):
now = timezone.now()
context = {
'date_from': now - timezone.timedelta(days=self.default_days_ago),
'date_to': now,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
template_name = 'terminal/asset_session_list.html'
class SessionOnlineListView(SessionListView):
......@@ -60,7 +45,7 @@ class SessionOfflineListView(SessionListView):
class SessionDetailView(SingleObjectMixin, PermissionsMixin, ListView):
template_name = 'terminal/session_detail.html'
template_name = 'terminal/asset_session_detail.html'
model = Session
object = None
permission_classes = [IsOrgAdmin | IsAuditor]
......
# coding: utf-8
#
from django.views.generic import TemplateView
from django.utils import timezone
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
__all__ = ['BaseSessionListView']
class BaseSessionListView(PermissionsMixin, TemplateView):
model = None
template_name = None
date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor]
default_days_ago = 5
def get_context_data(self, **kwargs):
now = timezone.now()
context = {
'date_from': now - timezone.timedelta(days=self.default_days_ago),
'date_to': now,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
# -*- coding: utf-8 -*-
#
from django.views.generic import ListView
from django.views.generic.edit import SingleObjectMixin
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
from ..backends import get_multi_command_storage
from django.utils.translation import ugettext as _
from ..models import DatabaseSession
from .base_session import BaseSessionListView
__all__ = [
'DatabaseSessionOnlineListView', 'DatabaseSessionOfflineListView',
'DatabaseSessionDetailView'
]
class DatabaseSessionListView(BaseSessionListView):
model = DatabaseSession
template_name = 'terminal/database_session_list.html'
class DatabaseSessionOnlineListView(DatabaseSessionListView):
def get_context_data(self, **kwargs):
context = {
'app': _('Sessions'),
'action': _('Session online list'),
'type': 'online',
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class DatabaseSessionOfflineListView(DatabaseSessionListView):
def get_context_data(self, **kwargs):
context = {
'app': _('Sessions'),
'action': _('Session offline'),
'type': 'offline',
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class DatabaseSessionDetailView(SingleObjectMixin, PermissionsMixin, ListView):
template_name = 'terminal/database_session_detail.html'
model = DatabaseSession
object = None
permission_classes = [IsOrgAdmin | IsAuditor]
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=self.model.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
command_store = get_multi_command_storage()
return command_store.filter(session=self.object.id)
def get_context_data(self, **kwargs):
context = {
'app': _('Sessions'),
'action': _('Session detail'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment