diff --git a/apps/applications/api.py b/apps/applications/api.py index 4933246b0..59696772c 100644 --- a/apps/applications/api.py +++ b/apps/applications/api.py @@ -1,22 +1,23 @@ # -*- coding: utf-8 -*- -# - +# from collections import OrderedDict import copy -from rest_framework import viewsets +from rest_framework import viewsets, serializers from rest_framework.views import APIView, Response from rest_framework.permissions import AllowAny from django.shortcuts import get_object_or_404 +from django.utils import timezone -from .models import Terminal, TerminalHeatbeat -from .serializers import TerminalSerializer, TerminalHeatbeatSerializer +from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask +from .serializers import TerminalSerializer, TerminalStatusSerializer, \ + TerminalSessionSerializer, TerminalTaskSerializer from .hands import IsSuperUserOrAppUser, IsAppUser, ProxyLog, \ IsSuperUserOrAppUserOrUserReadonly from common.utils import get_object_or_none class TerminalViewSet(viewsets.ModelViewSet): - queryset = Terminal.objects.all() + queryset = Terminal.objects.filter(is_deleted=False) serializer_class = TerminalSerializer permission_classes = (IsSuperUserOrAppUserOrUserReadonly,) @@ -37,7 +38,7 @@ class TerminalViewSet(viewsets.ModelViewSet): if serializer.is_valid(): terminal = serializer.save() - app_user, access_key = terminal.create_related_app_user() + app_user, access_key = terminal.create_app_user() data = OrderedDict() data['terminal'] = copy.deepcopy(serializer.data) data['user'] = app_user.to_json() @@ -51,44 +52,86 @@ class TerminalViewSet(viewsets.ModelViewSet): def get_permissions(self): if self.action == "create": self.permission_classes = (AllowAny,) - return super().get_permissions() -tasks = OrderedDict() -# tasks = {1: [{'name': 'kill_proxy', 'proxy_log_id': 23}]} - - -class TerminalHeatbeatViewSet(viewsets.ModelViewSet): - queryset = TerminalHeatbeat.objects.all() - serializer_class = TerminalHeatbeatSerializer - permission_classes = (IsAppUser,) +class TerminalStatusViewSet(viewsets.ModelViewSet): + queryset = TerminalStatus.objects.all() + serializer_class = TerminalStatusSerializer + permission_classes = (IsSuperUserOrAppUser,) + session_serializer_class = TerminalSessionSerializer def create(self, request, *args, **kwargs): - terminal = request.user.terminal - TerminalHeatbeat.objects.create(terminal=terminal) - task = tasks.get(terminal.name) - tasks[terminal.name] = [] - return Response({'msg': 'Success', - 'tasks': task}, - status=201) - - -class TerminateConnectionView(APIView): - def post(self, request, *args, **kwargs): - if isinstance(request.data, dict): - data = [request.data] - else: - data = request.data - for d in data: - proxy_log_id = d.get('proxy_log_id') - proxy_log = get_object_or_404(ProxyLog, id=proxy_log_id) - terminal_id = proxy_log.terminal - if terminal_id in tasks: - tasks[terminal_id].append({'name': 'kill_proxy', - 'proxy_log_id': proxy_log_id}) + sessions_active = [] + for session_data in request.data.get("sessions", []): + session_data["terminal"] = self.request.user.terminal.id + _id = session_data["id"] + session = get_object_or_none(TerminalSession, id=_id) + if session: + serializer = TerminalSessionSerializer(data=session_data, instance=session) else: - tasks[terminal_id] = [{'name': 'kill_proxy', - 'proxy_log_id': proxy_log_id}] + serializer = TerminalSessionSerializer(data=session_data) - return Response({'msg': 'get it'}) + if serializer.is_valid(): + serializer.save() + + if session_data["is_finished"]: + sessions_active.append(session_data["id"]) + + sessions_in_db_active = TerminalSession.objects.filter( + is_finished=False, terminal=self.request.user.terminal.id + ) + + for session in sessions_in_db_active: + if session.id not in sessions_active: + session.is_finished = True + session.date_end = timezone.now() + session.save() + + return super().create(request, *args, **kwargs) + + def get_queryset(self): + terminal_id = self.kwargs.get("terminal", None) + if terminal_id: + terminal = get_object_or_404(Terminal, id=terminal_id) + self.queryset = terminal.terminalstatus_set.all() + return self.queryset + + def perform_create(self, serializer): + serializer.validated_data["terminal"] = self.request.user.terminal + return super().perform_create(serializer) + + def get_permissions(self): + if self.action == "create": + self.permission_classes = (IsAppUser,) + return super().get_permissions() + + +class TerminalSessionViewSet(viewsets.ModelViewSet): + queryset = TerminalSession.objects.all() + serializers_class = TerminalSessionSerializer + permission_classes = (IsSuperUserOrAppUser,) + + def get_queryset(self): + terminal_id = self.kwargs.get("terminal", None) + if terminal_id: + terminal = get_object_or_404(Terminal, id=terminal_id) + self.queryset = terminal.terminalstatus_set.all() + return self.queryset + + +class TerminalTaskViewSet(viewsets.ModelViewSet): + queryset = TerminalTask.objects.all() + serializer_class = TerminalTaskSerializer + permission_classes = (IsSuperUserOrAppUser,) + + def get_queryset(self): + terminal_id = self.kwargs.get("terminal", None) + if terminal_id: + terminal = get_object_or_404(Terminal, id=terminal_id) + self.queryset = terminal.terminalstatus_set.all() + + if hasattr(self.request.user, "terminal"): + terminal = self.request.user.terminal + self.queryset = terminal.terminalstatus_set.all() + return self.queryset diff --git a/apps/applications/models.py b/apps/applications/models.py index 0d1cb1bc9..38e606498 100644 --- a/apps/applications/models.py +++ b/apps/applications/models.py @@ -13,6 +13,7 @@ class Terminal(models.Model): http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000) user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE) is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted') + is_deleted = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True) comment = models.TextField(blank=True, verbose_name=_('Comment')) @@ -28,7 +29,7 @@ class Terminal(models.Model): self.user.is_active = active self.user.save() - def create_related_app_user(self): + def create_app_user(self): user, access_key = User.create_app_user(name=self.name, comment=self.comment) self.user = user self.save() @@ -37,19 +38,71 @@ class Terminal(models.Model): def delete(self, using=None, keep_parents=False): if self.user: self.user.delete() - return super(Terminal, self).delete(using=using, keep_parents=keep_parents) + self.is_deleted = True + self.save() + return def __str__(self): - active = 'Active' if self.user and self.user.is_active else 'Disabled' - return '%s: %s' % (self.name, active) + status = "Active" + if not self.is_accepted: + status = "NotAccept" + elif self.is_deleted: + status = "Deleted" + elif not self.is_active: + status = "Disable" + return '%s: %s' % (self.name, status) class Meta: ordering = ('is_accepted',) -class TerminalHeatbeat(models.Model): - terminal = models.ForeignKey(Terminal, on_delete=models.CASCADE) +class TerminalStatus(models.Model): + session_online = models.IntegerField(verbose_name=_("Session Online"), default=0) + cpu_used = models.FloatField(verbose_name=_("CPU Usage")) + memory_used = models.FloatField(verbose_name=_("Memory Used")) + connections = models.IntegerField(verbose_name=_("Connections")) + threads = models.IntegerField(verbose_name=_("Threads")) + boot_time = models.FloatField(verbose_name=_("Boot Time")) + terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE) date_created = models.DateTimeField(auto_now_add=True) class Meta: - db_table = 'terminal_heatbeat' + db_table = 'terminal_status' + + # def __str__(self): + # return "<{} status>".format(self.terminal.name) + + +class TerminalSession(models.Model): + LOGIN_FROM_CHOICES = ( + ('ST', 'SSH Terminal'), + ('WT', 'Web Terminal'), + ) + + id = models.UUIDField(primary_key=True) + user = models.CharField(max_length=128, verbose_name=_("User")) + asset = models.CharField(max_length=1024, verbose_name=_("Asset")) + system_user = models.CharField(max_length=128, verbose_name=_("System User")) + login_from = models.CharField(max_length=2, choices=LOGIN_FROM_CHOICES, default="ST") + is_finished = models.BooleanField(default=False) + terminal = models.IntegerField(null=True, verbose_name=_("Terminal")) + date_start = models.DateTimeField(verbose_name=_("Date Start")) + date_end = models.DateTimeField(verbose_name=_("Date End"), null=True) + + class Meta: + db_table = "terminal_session" + + def __str__(self): + return "{0.id} of {0.user} to {0.asset}".format(self) + + +class TerminalTask(models.Model): + name = models.CharField(max_length=128, verbose_name=_("Name")) + args = models.CharField(max_length=1024, verbose_name=_("Task Args")) + terminal = models.ForeignKey(Terminal, null=True, on_delete=models.CASCADE) + is_finished = models.BooleanField(default=False) + date_created = models.DateTimeField(auto_now_add=True) + date_finished = models.DateTimeField(null=True) + + class Meta: + db_table = "terminal_task" diff --git a/apps/applications/serializers.py b/apps/applications/serializers.py index fcb43adb9..6118ac62e 100644 --- a/apps/applications/serializers.py +++ b/apps/applications/serializers.py @@ -4,7 +4,7 @@ from django.utils import timezone from rest_framework import serializers -from .models import Terminal, TerminalHeatbeat +from .models import Terminal, TerminalStatus, TerminalSession, TerminalTask from .hands import ProxyLog @@ -15,8 +15,7 @@ class TerminalSerializer(serializers.ModelSerializer): class Meta: model = Terminal fields = ['id', 'name', 'remote_addr', 'http_port', 'ssh_port', - 'comment', 'is_accepted', - 'session_connected', 'is_alive'] + 'comment', 'is_accepted', 'session_connected', 'is_alive'] @staticmethod def get_session_connected(obj): @@ -31,12 +30,22 @@ class TerminalSerializer(serializers.ModelSerializer): return False -class TerminalHeatbeatSerializer(serializers.ModelSerializer): - date_start = serializers.DateTimeField +class TerminalSessionSerializer(serializers.ModelSerializer): class Meta: - model = TerminalHeatbeat + model = TerminalSession + fields = '__all__' +class TerminalStatusSerializer(serializers.ModelSerializer): + + class Meta: + fields = '__all__' + model = TerminalStatus +class TerminalTaskSerializer(serializers.ModelSerializer): + + class Meta: + fields = '__all__' + model = TerminalTask diff --git a/apps/applications/tasks.py b/apps/applications/tasks.py index 69a80f243..8d85f7e4d 100644 --- a/apps/applications/tasks.py +++ b/apps/applications/tasks.py @@ -1,5 +1,10 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # +from celery import shared_task + +# Todo: 定期清理上报history +@shared_task +def clean_terminal_history(): + pass diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py index 7eb6e8b43..160a66853 100644 --- a/apps/applications/urls/api_urls.py +++ b/apps/applications/urls/api_urls.py @@ -10,12 +10,13 @@ from .. import api app_name = 'applications' router = routers.DefaultRouter() -router.register(r'v1/terminal/heatbeat', api.TerminalHeatbeatViewSet, 'terminal-heatbeat') -router.register(r'v1/terminal', api.TerminalViewSet, 'terminal') +router.register(r'v1/terminal/(?P[0-9]+)?/?status', api.TerminalStatusViewSet, 'terminal-status') +router.register(r'v1/terminal/(?P[0-9]+)?/?sessions', api.TerminalSessionViewSet, 'terminal-sessions') +router.register(r'v1/terminal$', api.TerminalViewSet, 'terminal') urlpatterns = [ - url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(), - name='terminate-connection') +# url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(), +# name='terminate-connection') ] urlpatterns += router.urls diff --git a/apps/applications/views.py b/apps/applications/views.py index 7cac6f575..f3ac66659 100644 --- a/apps/applications/views.py +++ b/apps/applications/views.py @@ -65,9 +65,6 @@ class TerminalModelAccept(AdminUserRequiredMixin, JSONResponseMixin, UpdateView) form_class = TerminalForm template_name = 'applications/terminal_modal_test.html' - def post(self, request, *args, **kwargs): - return super(TerminalModelAccept, self).post(request, *args, **kwargs) - def form_valid(self, form): terminal = form.save() terminal.is_accepted = True @@ -80,7 +77,6 @@ class TerminalModelAccept(AdminUserRequiredMixin, JSONResponseMixin, UpdateView) return self.render_json_response(data) def form_invalid(self, form): - print(form.data) data = { 'success': False, 'msg': str(form.errors), diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index b4c7769a2..ad4311cf9 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -199,8 +199,7 @@ class SystemUser(models.Model): return assets def get_assets(self): - assets = set(self.assets.all() - ) | self.get_assets_inherit_from_asset_groups() + assets = set(self.assets.all()) | self.get_assets_inherit_from_asset_groups() return list(assets) def _to_secret_json(self): diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 615220948..a68a8de74 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -187,3 +187,16 @@ class IDCSerializer(BulkSerializerMixin, serializers.ModelSerializer): fields.append('assets_amount') return fields + +class AssetGroupGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): + assets_granted = AssetGrantedSerializer(many=True, read_only=True) + assets_amount = serializers.SerializerMethodField() + + class Meta: + model = AssetGroup + list_serializer_class = BulkListSerializer + fields = '__all__' + + @staticmethod + def get_assets_amount(obj): + return len(obj.assets_granted) diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index f2b3035df..600daf2a0 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -7,7 +7,6 @@ import uuid import codecs import chardet from io import StringIO -from collections import defaultdict from django.conf import settings from django.utils.translation import ugettext_lazy as _ diff --git a/apps/common/celery.py b/apps/common/celery.py index 3094351e5..a30b5af7c 100644 --- a/apps/common/celery.py +++ b/apps/common/celery.py @@ -31,6 +31,11 @@ app.conf.update( 'task': 'assets.tasks.test_admin_user_connective_period', 'schedule': 60*60*60, 'args': (), + }, + 'clean_terminal_history': { + 'task': 'applications.tasks.clean_terminal_history', + 'schedule': 60*60*60, + 'args': (), } } ) diff --git a/apps/perms/api.py b/apps/perms/api.py index 7e4f618d1..1713f8623 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -12,7 +12,7 @@ from .utils import get_user_granted_assets, get_user_granted_asset_groups, \ get_user_group_granted_assets, get_user_group_granted_asset_groups from .models import AssetPermission from .hands import AssetGrantedSerializer, User, UserGroup, AssetGroup, Asset, \ - AssetGroup, AssetGroupSerializer, SystemUser + AssetGroup, AssetGroupGrantedSerializer, SystemUser from . import serializers from .utils import associate_system_users_and_assets @@ -135,30 +135,24 @@ class UserGrantedAssetsApi(ListAPIView): class UserGrantedAssetGroupsApi(ListAPIView): permission_classes = (IsSuperUserOrAppUser,) - serializer_class = AssetGroupSerializer + serializer_class = AssetGroupGrantedSerializer def get_queryset(self): user_id = self.kwargs.get('pk', '') - if not user_id: return [] user = get_object_or_404(User, id=user_id) - if user: - asset_groups = {} - for asset, system_ in get_user_granted_assets(user).items(): - for asset_group in asset.groups.all(): - if asset_group.id in asset_groups: - asset_groups[asset_group.id]['assets_amount'] += 1 - else: - asset_groups[asset_group.id] = { - 'id': asset_group.id, - 'name': asset_group.name, - 'comment': asset_group.comment, - 'assets_amount': 1 - } - asset_groups_json = asset_groups.values() + asset_groups = get_user_granted_asset_groups(user) + queryset = [] + for asset_group, assets_system_users in asset_groups.items(): + assets = [] + for asset, system_users in assets_system_users: + asset.system_users_granted = system_users + assets.append(asset) + asset_group.assets_granted = assets + queryset.append(asset_group) return queryset @@ -277,7 +271,7 @@ class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedAssetGroupsApi(ListAPIView): permission_classes = (IsSuperUser,) - serializer_class = AssetGroupSerializer + serializer_class = AssetGroupGrantedSerializer def get_queryset(self): user_group_id = self.kwargs.get('pk', '') diff --git a/apps/perms/hands.py b/apps/perms/hands.py index cfcae6685..0bcf07576 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -4,7 +4,7 @@ from users.utils import AdminUserRequiredMixin from users.models import User, UserGroup from assets.models import Asset, AssetGroup, SystemUser -from assets.serializers import AssetGrantedSerializer, AssetGroupSerializer +from assets.serializers import AssetGrantedSerializer, AssetGroupGrantedSerializer diff --git a/apps/perms/utils.py b/apps/perms/utils.py index 811c00916..f7b05f759 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -1,6 +1,7 @@ # coding: utf-8 from __future__ import absolute_import, unicode_literals +import collections from common.utils import setattr_bulk, get_logger from .tasks import push_users @@ -52,77 +53,77 @@ def get_user_group_granted_assets(user_group): return assets -def get_user_granted_asset_groups_direct(user): - """Return asset groups granted of the user direct nor inherit from user group - - :param user: Instance of :class: ``User`` - :return: {asset_group: {system_user1, }, - asset_group2: {system_user1, system_user2]} - """ - asset_groups = {} - asset_permissions_direct = user.asset_permissions.all() - - for asset_permission in asset_permissions_direct: - if not asset_permission.is_valid: - continue - for asset_group in asset_permission.asset_groups.all(): - if asset_group in asset_groups: - asset_groups[asset_group] |= set(asset_permission.system_users.all()) - else: - setattr(asset_group, 'inherited', False) - asset_groups[asset_group] = set(asset_permission.system_users.all()) - - return asset_groups +# def get_user_granted_asset_groups_direct(user): +# """Return asset groups granted of the user direct nor inherit from user group +# +# :param user: Instance of :class: ``User`` +# :return: {asset_group: {system_user1, }, +# asset_group2: {system_user1, system_user2]} +# """ +# asset_groups = {} +# asset_permissions_direct = user.asset_permissions.all() +# +# for asset_permission in asset_permissions_direct: +# if not asset_permission.is_valid: +# continue +# for asset_group in asset_permission.asset_groups.all(): +# if asset_group in asset_groups: +# asset_groups[asset_group] |= set(asset_permission.system_users.all()) +# else: +# setattr(asset_group, 'inherited', False) +# asset_groups[asset_group] = set(asset_permission.system_users.all()) +# +# return asset_groups -def get_user_granted_asset_groups_inherit_from_user_groups(user): - """Return asset groups granted of the user and inherit from user group - - :param user: Instance of :class: ``User`` - :return: {asset_group: {system_user1, }, - asset_group2: {system_user1, system_user2]} - """ - asset_groups = {} - user_groups = user.groups.all() - asset_permissions = set() - - # Get asset permission list of user groups for this user - for user_group in user_groups: - asset_permissions |= set(user_group.asset_permissions.all()) - - # Get asset groups granted from user groups - for asset_permission in asset_permissions: - if not asset_permission.is_valid: - continue - for asset_group in asset_permission.asset_groups.all(): - if asset_group in asset_groups: - asset_groups[asset_group] |= set(asset_permission.system_users.all()) - else: - setattr(asset_group, 'inherited', True) - asset_groups[asset_group] = set(asset_permission.system_users.all()) - - return asset_groups +# def get_user_granted_asset_groups_inherit_from_user_groups(user): +# """Return asset groups granted of the user and inherit from user group +# +# :param user: Instance of :class: ``User`` +# :return: {asset_group: {system_user1, }, +# asset_group2: {system_user1, system_user2]} +# """ +# asset_groups = {} +# user_groups = user.groups.all() +# asset_permissions = set() +# +# # Get asset permission list of user groups for this user +# for user_group in user_groups: +# asset_permissions |= set(user_group.asset_permissions.all()) +# +# # Get asset groups granted from user groups +# for asset_permission in asset_permissions: +# if not asset_permission.is_valid: +# continue +# for asset_group in asset_permission.asset_groups.all(): +# if asset_group in asset_groups: +# asset_groups[asset_group] |= set(asset_permission.system_users.all()) +# else: +# setattr(asset_group, 'inherited', True) +# asset_groups[asset_group] = set(asset_permission.system_users.all()) +# +# return asset_groups -def get_user_granted_asset_groups(user): - """Get user granted asset groups all, include direct and inherit from user group - - :param user: Instance of :class: ``User`` - :return: {asset1: {system_user1, system_user2}, asset2: {...}} - """ - - asset_groups_inherit_from_user_groups = \ - get_user_granted_asset_groups_inherit_from_user_groups(user) - asset_groups_direct = get_user_granted_asset_groups_direct(user) - asset_groups = asset_groups_inherit_from_user_groups - - # Merge direct granted and inherit from user group - for asset_group, system_users in asset_groups_direct.items(): - if asset_group in asset_groups: - asset_groups[asset_group] |= asset_groups_direct[asset_group] - else: - asset_groups[asset_group] = asset_groups_direct[asset_group] - return asset_groups +# def get_user_granted_asset_groups(user): +# """Get user granted asset groups all, include direct and inherit from user group +# +# :param user: Instance of :class: ``User`` +# :return: {asset_group1: {system_user1, system_user2}, asset_group2: {...}} +# """ +# +# asset_groups_inherit_from_user_groups = \ +# get_user_granted_asset_groups_inherit_from_user_groups(user) +# asset_groups_direct = get_user_granted_asset_groups_direct(user) +# asset_groups = asset_groups_inherit_from_user_groups +# +# # Merge direct granted and inherit from user group +# for asset_group, system_users in asset_groups_direct.items(): +# if asset_group in asset_groups: +# asset_groups[asset_group] |= asset_groups_direct[asset_group] +# else: +# asset_groups[asset_group] = asset_groups_direct[asset_group] +# return asset_groups def get_user_granted_assets_direct(user): @@ -191,8 +192,21 @@ def get_user_granted_assets(user): def get_user_granted_asset_groups(user): - pass + """Return asset groups with assets and system users, it's not the asset + group direct permed in rules. We get all asset and then get it asset group + :param user: Instance of :class: ``User`` + :return: {asset_group1: [asset1, asset2], asset_group2: []} + """ + asset_groups = collections.defaultdict(list) + ungroups = [AssetGroup(name="UnGrouped")] + for asset, system_users in get_user_granted_assets(user).items(): + groups = asset.groups.all() + if not groups: + groups = ungroups + for asset_group in groups: + asset_groups[asset_group].append((asset, system_users)) + return asset_groups def get_user_group_asset_permissions(user_group): diff --git a/apps/users/api.py b/apps/users/api.py index 2258ace71..aed67cc8c 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -153,16 +153,21 @@ class UserAuthApi(APIView): login_ip = request.data.get('remote_addr', None) user_agent = request.data.get('HTTP_USER_AGENT', '') + if not login_ip: + login_ip = request.META.get("REMOTE_ADDR") + user, msg = check_user_valid( username=username, password=password, - public_key=public_key) + public_key=public_key + ) if user: token = generate_token(request, user) write_login_log_async.delay( user.username, name=user.name, user_agent=user_agent, login_ip=login_ip, - login_type=login_type) + login_type=login_type + ) return Response({'token': token, 'user': user.to_json()}) else: return Response({'msg': msg}, status=401)