diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index bcb3b81b1..8556be3bf 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -4,9 +4,38 @@ from rest_framework import serializers from rest_framework_bulk.serializers import BulkListSerializer from common.mixins import BulkSerializerMixin -from ..models import Asset +from ..models import Asset, Node from .system_user import AssetSystemUserSerializer +__all__ = [ + 'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer', +] + + +class NodeTMPSerializer(serializers.ModelSerializer): + parent = serializers.SerializerMethodField() + assets_amount = serializers.SerializerMethodField() + + class Meta: + model = Node + fields = ['id', 'key', 'value', 'parent', 'assets_amount', + 'is_asset'] + list_serializer_class = BulkListSerializer + + @staticmethod + def get_parent(obj): + return obj.parent.id + + @staticmethod + def get_assets_amount(obj): + return obj.get_all_assets().count() + + def get_fields(self): + fields = super().get_fields() + field = fields["key"] + field.required = False + return fields + class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): """ @@ -33,12 +62,13 @@ class AssetGrantedSerializer(serializers.ModelSerializer): """ system_users_granted = AssetSystemUserSerializer(many=True, read_only=True) system_users_join = serializers.SerializerMethodField() + nodes = NodeTMPSerializer(many=True, read_only=True) class Meta: model = Asset fields = ( "id", "hostname", "ip", "port", "system_users_granted", - "is_active", "system_users_join", "os", 'domain', + "is_active", "system_users_join", "os", 'domain', "nodes", "platform", "comment" ) diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index 736b06c7e..1c9c385e9 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -7,6 +7,12 @@ from ..models import Asset, Node from .asset import AssetGrantedSerializer +__all__ = [ + 'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer", + "NodeAssetsSerializer", +] + + class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): """ 授权资产组 diff --git a/apps/terminal/api.py b/apps/terminal/api.py index e625e17bc..a964e9729 100644 --- a/apps/terminal/api.py +++ b/apps/terminal/api.py @@ -182,6 +182,11 @@ class SessionViewSet(viewsets.ModelViewSet): self.queryset = terminal.session_set.all() return self.queryset + def perform_create(self, serializer): + if self.request.user.terminal: + serializer.validated_data["terminal"] = self.request.user.terminal + return super().perform_create(serializer) + class TaskViewSet(BulkModelViewSet): queryset = Task.objects.all() @@ -299,6 +304,57 @@ class SessionReplayViewSet(viewsets.ViewSet): return HttpResponseNotFound() +class SessionReplayV2ViewSet(viewsets.ViewSet): + serializer_class = ReplaySerializer + permission_classes = (IsSuperUserOrAppUser,) + session = None + + def gen_session_path(self): + date = self.session.date_start.strftime('%Y-%m-%d') + replay = { + "id": self.session.id, + # "width": 100, + # "heith": 100 + } + if self.session.protocol == "ssh": + replay['type'] = "json" + replay['path'] = os.path.join(date, str(self.session.id) + '.gz') + return replay + elif self.session.protocol == "rdp": + replay['type'] = "mp4" + replay['path'] = os.path.join(date, str(self.session.id) + '.mp4') + return replay + else: + return replay + + def retrieve(self, request, *args, **kwargs): + session_id = kwargs.get('pk') + self.session = get_object_or_404(Session, id=session_id) + replay = self.gen_session_path() + + if replay.get("path", "") == "": + return HttpResponseNotFound() + + if default_storage.exists(replay["path"]): + replay["src"] = default_storage.url(replay["path"]) + return Response(replay) + else: + configs = settings.TERMINAL_REPLAY_STORAGE.items() + if not configs: + return HttpResponseNotFound() + + for name, config in configs: + client = jms_storage.init(config) + + target_path = default_storage.base_location + '/' + replay["path"] + + if client and client.has_file(replay["path"]) and \ + client.download_file(replay["path"], target_path): + replay["src"] = default_storage.url(replay["path"]) + return Response(replay) + return HttpResponseNotFound() + + class TerminalConfig(APIView): permission_classes = (IsAppUser,) diff --git a/apps/terminal/backends/command/base.py b/apps/terminal/backends/command/base.py index 585930a5d..d95697ed0 100644 --- a/apps/terminal/backends/command/base.py +++ b/apps/terminal/backends/command/base.py @@ -21,7 +21,7 @@ class CommandBase(object): @abc.abstractmethod def count(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, - input=None, session=None): + user=None, asset=None, system_user=None, + input=None, session=None): pass diff --git a/apps/terminal/models.py b/apps/terminal/models.py index 5829fa2e1..3ae7066bb 100644 --- a/apps/terminal/models.py +++ b/apps/terminal/models.py @@ -117,6 +117,10 @@ class Session(models.Model): ('ST', 'SSH Terminal'), ('WT', 'Web Terminal'), ) + PROTOCOL_CHOICES = ( + ('ssh', 'ssh'), + ('rdp', 'rdp') + ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) user = models.CharField(max_length=128, verbose_name=_("User")) @@ -128,8 +132,9 @@ class Session(models.Model): has_replay = models.BooleanField(default=False, verbose_name=_("Replay")) has_command = models.BooleanField(default=False, verbose_name=_("Command")) terminal = models.ForeignKey(Terminal, null=True, on_delete=models.SET_NULL) + protocol = models.CharField(choices=PROTOCOL_CHOICES, default='ssh', max_length=8) date_last_active = models.DateTimeField(verbose_name=_("Date last active"), default=timezone.now) - date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True) + date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now) date_end = models.DateTimeField(verbose_name=_("Date end"), null=True) class Meta: diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html index c7f29f7b1..63a2c9209 100644 --- a/apps/terminal/templates/terminal/session_list.html +++ b/apps/terminal/templates/terminal/session_list.html @@ -72,7 +72,7 @@ {% trans 'Asset' %} {% trans 'System user' %} {% trans 'Remote addr' %} - {% trans 'Terminal' %} + {% trans 'Protocol' %} {% trans 'Command' %} {% trans 'Date start' %} {# {% trans 'Date last active' %}#} @@ -91,7 +91,7 @@ {{ session.asset }} {{ session.system_user }} {{ session.remote_addr|default:"" }} - {{ session.terminal.name }} + {{ session.protocol }} {{ session.id | get_session_command_amount }} {{ session.date_start }} diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 0d9da427d..fa8ff5434 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -23,8 +23,13 @@ urlpatterns = [ api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), name='session-replay'), url(r'^v1/tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'), - url(r'^v1/terminal/(?P[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(), name='terminal-access-key'), + url(r'^v1/terminal/(?P[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(), + name='terminal-access-key'), url(r'^v1/terminal/config', api.TerminalConfig.as_view(), name='terminal-config'), + # v2: get session's replay + url(r'^v2/sessions/(?P[0-9a-zA-Z\-]{36})/replay/$', + api.SessionReplayV2ViewSet.as_view({'get': 'retrieve'}), + name='session-replay-v2'), ] urlpatterns += router.urls diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index a77bea37f..0af0b5bfd 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -38,7 +38,7 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView): if self.system_user: filter_kwargs['system_user'] = self.system_user if self.command: - filter_kwargs['input__contains'] = self.command + filter_kwargs['input'] = self.command queryset = common_storage.filter(**filter_kwargs) return queryset