diff --git a/apps/terminal/api/component/endpoint.py b/apps/terminal/api/component/endpoint.py index 8f770d75f..d406b51f7 100644 --- a/apps/terminal/api/component/endpoint.py +++ b/apps/terminal/api/component/endpoint.py @@ -2,13 +2,11 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext_lazy as _ from rest_framework import status from rest_framework.decorators import action -from rest_framework.generics import ListAPIView from rest_framework.request import Request from rest_framework.response import Response from assets.models import Asset from common.drf.api import JMSBulkModelViewSet -from common.permissions import IsValidUser from common.permissions import IsValidUserOrConnectionToken from orgs.utils import tmp_to_root_org from terminal import serializers @@ -98,15 +96,3 @@ class EndpointRuleViewSet(JMSBulkModelViewSet): search_fields = filterset_fields serializer_class = serializers.EndpointRuleSerializer queryset = EndpointRule.objects.all() - - -class ConnectMethodListApi(ListAPIView): - permission_classes = (IsValidUser,) - - # serializer_class = serializers.ProtocolConnectMethodsSerializer - - def get_queryset(self): - protocol = self.request.query_params.get('protocol') - if not protocol: - return [] - return Protocol.objects.filter(name=protocol) diff --git a/apps/terminal/api/component/terminal.py b/apps/terminal/api/component/terminal.py index 3133db6eb..e3aa4afb3 100644 --- a/apps/terminal/api/component/terminal.py +++ b/apps/terminal/api/component/terminal.py @@ -1,26 +1,24 @@ # -*- coding: utf-8 -*- # import logging -import uuid -from django.core.cache import cache -from rest_framework import generics -from rest_framework.views import APIView, Response -from rest_framework import status from django.conf import settings from django.utils.translation import gettext_lazy as _ +from rest_framework import generics +from rest_framework import status +from rest_framework.views import APIView, Response -from common.exceptions import JMSException from common.drf.api import JMSBulkModelViewSet -from common.utils import get_object_or_none, get_request_ip +from common.exceptions import JMSException +from common.permissions import IsValidUser from common.permissions import WithBootstrapToken -from terminal.models import Terminal from terminal import serializers -from terminal import exceptions +from terminal.const import TerminalType +from terminal.models import Terminal __all__ = [ - 'TerminalViewSet', 'TerminalConfig', - 'TerminalRegistrationApi', + 'TerminalViewSet', 'TerminalConfig', + 'TerminalRegistrationApi', 'ConnectMethodListApi' ] logger = logging.getLogger(__file__) @@ -72,3 +70,22 @@ class TerminalRegistrationApi(generics.CreateAPIView): data = {"error": "service account registration disabled"} return Response(data=data, status=status.HTTP_400_BAD_REQUEST) return super().create(request, *args, **kwargs) + + +class ConnectMethodListApi(generics.ListAPIView): + serializer_class = serializers.ConnectMethodSerializer + permission_classes = [IsValidUser] + + def get_queryset(self): + user_agent = self.request.META['HTTP_USER_AGENT'].lower() + if 'macintosh' in user_agent: + os = 'macos' + elif 'windows' in user_agent: + os = 'windows' + else: + os = 'linux' + return TerminalType.get_protocols_connect_methods(os) + + def list(self, request, *args, **kwargs): + queryset = self.get_queryset() + return Response(queryset) diff --git a/apps/terminal/const.py b/apps/terminal/const.py index 1b39763e0..288975866 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from collections import defaultdict from django.db.models import TextChoices from django.utils.translation import ugettext_lazy as _ @@ -43,24 +44,12 @@ class ComponentLoad(TextChoices): return set(dict(cls.choices).keys()) -class TerminalType(TextChoices): - koko = 'koko', 'KoKo' - guacamole = 'guacamole', 'Guacamole' - omnidb = 'omnidb', 'OmniDB' - xrdp = 'xrdp', 'Xrdp' - lion = 'lion', 'Lion' - core = 'core', 'Core' - celery = 'celery', 'Celery' - magnus = 'magnus', 'Magnus' - razor = 'razor', 'Razor' - tinker = 'tinker', 'Tinker' - - @classmethod - def types(cls): - return set(dict(cls.choices).keys()) +class HttpMethod(TextChoices): + web_gui = 'web_gui', 'Web GUI' + web_cli = 'web_cli', 'Web CLI' -class NativeClient: +class NativeClient(TextChoices): # Koko ssh = 'ssh', 'ssh' putty = 'putty', 'PuTTY' @@ -71,15 +60,42 @@ class NativeClient: psql = 'psql', 'psql' sqlplus = 'sqlplus', 'sqlplus' redis = 'redis-cli', 'redis-cli' + mongodb = 'mongo', 'mongo' # Razor mstsc = 'mstsc', 'Remote Desktop' @classmethod - def get_native_clients(cls, p, os='windows'): + def get_native_clients(cls): clients = { - '' + Protocol.ssh: { + 'default': [cls.ssh], + 'windows': [cls.putty], + }, + Protocol.rdp: [cls.mstsc], + Protocol.mysql: [cls.mysql], + Protocol.oracle: [cls.sqlplus], + Protocol.postgresql: [cls.psql], + Protocol.redis: [cls.redis], + Protocol.mongodb: [cls.mongodb], } + return clients + + @classmethod + def get_native_methods(cls, os='windows'): + clients_map = cls.get_native_clients() + methods = defaultdict(list) + + for protocol, _clients in clients_map.items(): + if isinstance(_clients, dict): + _clients = _clients.get(os, _clients['default']) + for client in _clients: + methods[protocol].append({ + 'value': client.value, + 'label': client.label, + 'type': 'native', + }) + return methods @classmethod def get_launch_command(cls, name, os='windows'): @@ -104,33 +120,113 @@ class NativeClient: class RemoteAppMethod: @classmethod - def get_remote_app_methods(cls, protocol): + def get_remote_app_methods(cls): from .models import Applet - applets = Applet.objects.filter(protocol=protocol) - return applets + applets = Applet.objects.all() + methods = defaultdict(list) + for applet in applets: + for protocol in applet.protocols: + methods[protocol].append({ + 'value': applet.name, + 'label': applet.display_name, + 'icon': applet.icon, + 'type': 'remote_app', + }) + return methods -class ConnectMethod(TextChoices): - web_cli = 'web_cli', _('Web CLI') - web_gui = 'web_gui', _('Web GUI') - native_client = 'native_client', _('Native Client') - remote_app = 'remote_app', _('Remote App') +class TerminalType(TextChoices): + koko = 'koko', 'KoKo' + guacamole = 'guacamole', 'Guacamole' + omnidb = 'omnidb', 'OmniDB' + xrdp = 'xrdp', 'Xrdp' + lion = 'lion', 'Lion' + core = 'core', 'Core' + celery = 'celery', 'Celery' + magnus = 'magnus', 'Magnus' + razor = 'razor', 'Razor' + tinker = 'tinker', 'Tinker' @classmethod - def methods(cls): + def types(cls): + return set(dict(cls.choices).keys()) + + @classmethod + def protocols(cls): return { - Protocol.ssh: [cls.web_cli, cls.native_client], - Protocol.rdp: ([cls.web_gui], [cls.native_client]), - Protocol.vnc: [cls.web_gui], - Protocol.telnet: [cls.web_cli, cls.native_client], - - Protocol.mysql: [cls.web_cli, cls.web_gui, cls.native_client], - Protocol.sqlserver: [cls.web_cli, cls.web_gui], - Protocol.oracle: [cls.web_cli, cls.web_gui], - Protocol.postgresql: [cls.web_cli, cls.web_gui], - Protocol.redis: [cls.web_cli, cls.web_gui, cls.native_client], - Protocol.mongodb: [cls.web_cli, cls.web_gui], - - Protocol.k8s: [cls.web_cli], - Protocol.http: [], + cls.koko: { + 'http_method': HttpMethod.web_cli, + 'listen': [Protocol.ssh, Protocol.http], + 'support': [ + Protocol.ssh, Protocol.telnet, + Protocol.mysql, Protocol.postgresql, + Protocol.oracle, Protocol.sqlserver, + Protocol.mariadb, Protocol.redis, + Protocol.mongodb, + ], + 'match': 'm2m' + }, + cls.omnidb: { + 'http_method': HttpMethod.web_gui, + 'listen': [Protocol.http], + 'support': [ + Protocol.mysql, Protocol.postgresql, Protocol.oracle, + Protocol.sqlserver, Protocol.mariadb + ], + 'match': 'm2m' + }, + cls.lion: { + 'http_method': HttpMethod.web_gui, + 'listen': [Protocol.http], + 'support': [Protocol.rdp, Protocol.vnc], + 'match': 'm2m' + }, + cls.magnus: { + 'listen': [], + 'support': [ + Protocol.mysql, Protocol.postgresql, Protocol.oracle, + Protocol.mariadb + ], + 'match': 'map' + }, + cls.razor: { + 'listen': [Protocol.rdp], + 'support': [Protocol.rdp], + 'match': 'map' + } } + + @classmethod + def get_protocols_connect_methods(cls, os): + methods = defaultdict(list) + native_methods = NativeClient.get_native_methods(os) + remote_app_methods = RemoteAppMethod.get_remote_app_methods() + + for component, component_protocol in cls.protocols().items(): + component_methods = defaultdict(list) + support = component_protocol['support'] + + for protocol in support: + if component_protocol['match'] == 'map': + listen = [protocol] + else: + listen = component_protocol['listen'] + + for listen_protocol in listen: + if listen_protocol == Protocol.http: + web_protocol = component_protocol['http_method'] + component_methods[protocol.value].append({ + 'value': web_protocol.value, + 'label': web_protocol.label, + 'type': 'web', + }) + + # Native method + component_methods[protocol.value].extend(native_methods[listen_protocol]) + component_methods[protocol.value].extend(remote_app_methods[listen_protocol]) + + for protocol, _methods in component_methods.items(): + for method in _methods: + method['component'] = component.value + methods[protocol].extend(_methods) + return methods diff --git a/apps/terminal/models/component/terminal.py b/apps/terminal/models/component/terminal.py index 11a2a9a61..1d65ad32a 100644 --- a/apps/terminal/models/component/terminal.py +++ b/apps/terminal/models/component/terminal.py @@ -1,19 +1,16 @@ -import uuid import time +import uuid -from django.utils import timezone -from django.db import models -from django.core.cache import cache -from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, lazyproperty -from users.models import User from orgs.utils import tmp_to_root_org -from terminal.const import TerminalType as TypeChoices, ComponentLoad as StatusChoice +from terminal.const import TerminalType as TypeChoices +from users.models import User from ..session import Session - logger = get_logger(__file__) @@ -87,7 +84,8 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): remote_addr = models.CharField(max_length=128, blank=True, verbose_name=_('Remote Address')) command_storage = models.CharField(max_length=128, verbose_name=_("Command storage"), default='default') replay_storage = models.CharField(max_length=128, verbose_name=_("Replay storage"), default='default') - user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE) + user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, + on_delete=models.CASCADE) is_deleted = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -160,4 +158,3 @@ class Terminal(StorageMixin, TerminalStatusMixin, models.Model): permissions = ( ('view_terminalconfig', _('Can view terminal config')), ) - diff --git a/apps/terminal/serializers/terminal.py b/apps/terminal/serializers/terminal.py index f12990618..df32d89c2 100644 --- a/apps/terminal/serializers/terminal.py +++ b/apps/terminal/serializers/terminal.py @@ -136,4 +136,6 @@ class TerminalRegistrationSerializer(serializers.ModelSerializer): class ConnectMethodSerializer(serializers.Serializer): - name = serializers.CharField(max_length=128) + value = serializers.CharField(max_length=128) + label = serializers.CharField(max_length=128) + group = serializers.CharField(max_length=128) diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 3e39b55ce..fb7087850 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -31,7 +31,6 @@ router.register(r'applet-hosts', api.AppletHostViewSet, 'applet-host') router.register(r'applet-publications', api.AppletPublicationViewSet, 'applet-publication') router.register(r'applet-host-deployments', api.AppletHostDeploymentViewSet, 'applet-host-deployment') - urlpatterns = [ path('my-sessions/', api.MySessionAPIView.as_view(), name='my-session'), path('terminal-registrations/', api.TerminalRegistrationApi.as_view(), name='terminal-registration'), @@ -44,10 +43,13 @@ urlpatterns = [ path('tasks/kill-session-for-ticket/', api.KillSessionForTicketAPI.as_view(), name='kill-session-for-ticket'), path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'), path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"), - path('replay-storages//test-connective/', api.ReplayStorageTestConnectiveApi.as_view(), name='replay-storage-test-connective'), - path('command-storages//test-connective/', api.CommandStorageTestConnectiveApi.as_view(), name='command-storage-test-connective'), + path('replay-storages//test-connective/', api.ReplayStorageTestConnectiveApi.as_view(), + name='replay-storage-test-connective'), + path('command-storages//test-connective/', api.CommandStorageTestConnectiveApi.as_view(), + name='command-storage-test-connective'), # components path('components/metrics/', api.ComponentsMetricsAPIView.as_view(), name='components-metrics'), + path('components/connect-methods/', api.ConnectMethodListApi.as_view(), name='connect-methods'), ] old_version_urlpatterns = [ @@ -55,6 +57,3 @@ old_version_urlpatterns = [ ] urlpatterns += router.urls + old_version_urlpatterns - - -