From 4a97872cddc4d14558b789f5701655c6a5e5ca94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Thu, 8 Jun 2017 11:02:34 +0200 Subject: [PATCH] Added usage info --- server/src/uds/REST/methods/providers.py | 7 +- server/src/uds/REST/methods/services_usage.py | 152 ++++++++++++++++++ server/src/uds/REST/methods/user_services.py | 2 +- server/src/uds/models/User.py | 6 +- server/src/uds/static/adm/img/icons/usage.png | Bin 0 -> 2156 bytes .../uds/static/adm/js/gui-d-services.coffee | 21 +++ .../templates/uds/admin/tmpl/providers.html | 7 + server/src/uds/transports/RDP/RDPFile.py | 4 +- 8 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 server/src/uds/REST/methods/services_usage.py create mode 100644 server/src/uds/static/adm/img/icons/usage.png diff --git a/server/src/uds/REST/methods/providers.py b/server/src/uds/REST/methods/providers.py index 0e87ab37..5011163e 100644 --- a/server/src/uds/REST/methods/providers.py +++ b/server/src/uds/REST/methods/providers.py @@ -40,6 +40,7 @@ from uds.core.util import permissions from uds.core.util.model import processUuid from .services import Services as DetailServices +from .services_usage import ServicesUsage from uds.REST import NotFound, RequestError from uds.REST.model import ModelHandler @@ -54,7 +55,11 @@ class Providers(ModelHandler): Providers REST handler ''' model = Provider - detail = {'services': DetailServices} + detail = { + 'services': DetailServices, + 'usage': ServicesUsage + } + custom_methods = [('allservices', False), ('service', False), ('maintenance', True)] save_fields = ['name', 'comments', 'tags'] diff --git a/server/src/uds/REST/methods/services_usage.py b/server/src/uds/REST/methods/services_usage.py new file mode 100644 index 00000000..db346e57 --- /dev/null +++ b/server/src/uds/REST/methods/services_usage.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2012 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' + +# pylint: disable=too-many-public-methods + +from __future__ import unicode_literals + +from django.utils.translation import ugettext as _ + +from uds.models import UserService +from uds.core.util.State import State +from uds.core.util.model import processUuid +from uds.core.util import log +from uds.REST.model import DetailHandler + + + +import logging + +logger = logging.getLogger(__name__) + +class ServicesUsage(DetailHandler): + ''' + Rest handler for Assigned Services, wich parent is Service + ''' + + @staticmethod + def itemToDict(item): + ''' + Converts an assigned/cached service db item to a dictionary for REST response + :param item: item to convert + :param is_cache: If item is from cache or not + ''' + props = item.getProperties() + + if item.user is None: + owner = '' + owner_info = { + 'auth_id': '', + 'user_id': '' + } + else: + owner = item.user.fullUsername + owner_info = { + 'auth_id': item.user.manager.uuid, + 'user_id': item.user.uuid + } + + + return { + 'id': item.uuid, + 'state_date': item.state_date, + 'creation_date': item.creation_date, + 'unique_id': item.unique_id, + 'friendly_name': item.friendly_name, + 'owner': owner, + 'owner_info': owner_info, + 'service': item.deployed_service.service.name, + 'service_id': item.deployed_service.service.uuid, + 'pool': item.deployed_service.name, + 'pool_id': item.deployed_service.uuid, + 'ip': props.get('ip', _('unknown')), + 'source_host': item.src_hostname, + 'source_ip': item.src_ip, + 'in_use': item.in_use + } + + def getItems(self, parent, item): + try: + if item is None: + userServicesQuery = UserService.objects.filter(deployed_service__service__provider=parent) + else: + userServicesQuery = UserService.objects.filter(deployed_service__service_uuid=processUuid(item)) + + return [ServicesUsage.itemToDict(k) for k in userServicesQuery.filter(state=State.USABLE).order_by('creation_date'). + prefetch_related('deployed_service').prefetch_related('deployed_service__service').prefetch_related('properties')] + + except Exception: + logger.exception('getItems') + self.invalidItemException() + + def getTitle(self, parent): + return _('Services Usage') + + def getFields(self, parent): + return [ + # {'creation_date': {'title': _('Creation date'), 'type': 'datetime'}}, + {'state_date': {'title': _('Access'), 'type': 'datetime'}}, + {'owner': {'title': _('Owner')}}, + {'service': {'title': _('Service')}}, + {'pool': {'title': _('Pool')}}, + {'unique_id': {'title': 'Unique ID'}}, + {'ip': {'title': _('IP')}}, + {'friendly_name': {'title': _('Friendly name')}}, + {'source_ip': {'title': _('Src Ip')}}, + {'source_host': {'title': _('Src Host')}}, + ] + + def getRowStyle(self, parent): + return {'field': 'state', 'prefix': 'row-state-'} + + def deleteItem(self, parent, item): # This is also used by CachedService, so we use "userServices" directly and is valid for both + try: + service = UserService.objects.get(uuid=processUuid(item)) + if service.deployed_service.service.provider.id != parent.id: + # Forged UUID service!!! + raise Exception('Access denied') + except Exception: + logger.exception('deleteItem') + self.invalidItemException() + + logger.debug('Deleting user service') + if service.state in (State.USABLE, State.REMOVING): + service.remove() + elif service.state == State.PREPARING: + service.cancel() + elif service.state == State.REMOVABLE: + self.invalidItemException(_('Item already being removed')) + else: + self.invalidItemException(_('Item is not removable')) + + return self.success() diff --git a/server/src/uds/REST/methods/user_services.py b/server/src/uds/REST/methods/user_services.py index 580ee254..159a6131 100644 --- a/server/src/uds/REST/methods/user_services.py +++ b/server/src/uds/REST/methods/user_services.py @@ -91,7 +91,7 @@ class AssignedService(DetailHandler): 'user_id': '' } else: - owner = item.user.manager.name + "-" + item.user.name + owner = item.user.fullUsername owner_info = { 'auth_id': item.user.manager.uuid, 'user_id': item.user.uuid diff --git a/server/src/uds/models/User.py b/server/src/uds/models/User.py index cf760a68..d8d6deb6 100644 --- a/server/src/uds/models/User.py +++ b/server/src/uds/models/User.py @@ -48,7 +48,7 @@ import logging logger = logging.getLogger(__name__) -__updated__ = '2015-04-30' +__updated__ = '2017-06-08' @python_2_unicode_compatible @@ -168,6 +168,10 @@ class User(UUIDModel): # This group matches yield g + @property + def fullUsername(self): + return self.manager.name + "\\" + self.name + def __str__(self): return u"User {0}(id:{1}) from auth {2}".format(self.name, self.id, self.manager.name) diff --git a/server/src/uds/static/adm/img/icons/usage.png b/server/src/uds/static/adm/img/icons/usage.png new file mode 100644 index 0000000000000000000000000000000000000000..d02fc69b5b3e4f56ee572e832046458006e9a36c GIT binary patch literal 2156 zcmV-y2$T1TP)27zYa?08YZRm=Pl@teNb{Wb0ku3uq7P|>)Ku+5!4*P+ zR-`luzQ|Bgd%cFm_O5U1oxRVjJ=2F>BOEBK%@W4c|CgE7&Ud~!|2gwN-#Ie_yV%7p zcCm|H?Bf3w!d4C*9v%)#l5|oKgr5OzHuGf`_)Jlh4-X$cyjphxhKGlPvMgT(UaqN9)E!c7>{{o6fb~1PJUL)a;k^q%)m6cdjmHJTx?9 zZ>FACNIUh)?MA>Z0lNg)Lj$&&1@703l5ViLsu^$0j%>s+78aJoo ztgWwOSr#cRODdD2P%L6pBS4X5+%6YEzn7q|frXVc%c~g%db^tLQ_WBH>$!08x@@ z*wX^{Iqwi1ymWAo-rim$hltw&idZQ(43|u>F+hKRKhdcvMn^|TEU$n7T`f&aMDOt9 z7x$wo^3KqJo3S|aODR%X_MY>v9C?*Uq?snCNTVVme52H|ELb%J9N+23=ksyp%4HHu zEBM?l8vS0TXOn!lyTktLfRb*ISYE}lEaHhp01kZrAWhB9ba@;ERS^Wpl`U3ECMzYA zT-gEvf~rWD$3b%>!hr*W0NhS2U|AOP%PC3^(GS~cfYaBAJJ>`@%Th870s%igJw2F~ zNhYt869g`dM@i+1o5lqjT>SLKUOa9W0KI*EM5m_EG>udyOUUnIX;q`6d5^s#Kz3Dx zZ8ehxpu49RK@gZ*Sm6(!-{8}$6Aw5KkjfSL^y&n+bL$%&DY|=lHrZNEXY8*5SI2Kr zDCy*jB>*Ck2ml?CFdbV=XOWhcN|q>8Y!ynn{WV}C&+Fwi0Nft;wr#oH?oGDzI%vSR zUJN~Cn1}$!rca+CKt3E*BxM2SE_%j5Gl-Iy%C_Lb7HX3&|v7qoV+HwKgLN0;&iemvd)S zgKO(1d-nDd^m}R96C$y^!j;RHIdEW*-o8ErLHM6xKQ%eY*yt#xY0}yhBIxs?8wS7o z<7eC$yUsgrzh(bw&>;#e#iwcB*H2e#7(uX@UrKTPtFMSoP0`)cL!_kzPgN80`2xxL z1kuxtBGG6R(cz$&t7Mlv@QaUs!>RY*p>J=ey<1{lua|bf?NmstXk_&@LVh11zwc2A zkbHWDfBgLl@wp}3K0kV)fL_dRBH`4pe#@!%er|69Hb{^p3AfXUmd&B%ixiAC%IhX( zb(t&*Ac+DVRmSgek#~v+FCRo68RzTyWz>zDV1oofsF3jMGraNIVOqi=`}G7LrtpSg zP%f8QTU+D!@#C1LSs_3akt7L4QBYMC_uei-p-|=Bglk_uNJ2IPULSej-0)?FUVqiT z5pYj^1p!r6QB@VwGzo{p4-g>Tx7KfOzfb8OK>}40j=lNT%YXa*Ctuhm3)Bm_&h_Di_`G?U0)0h4bj@#ie*`>udkCnKTI-}VPpC+tGaLJVu6r?b^XAPD2L=X?rPJwsBO@brj)Lj2v9XV8Zr9a%;Khp`t-YJ&z_yGdrz>60I(Nm2bzEe8x&L~kOLN} zd^dC(NZ0)qs3Hw4RGlsXZkrHL08-U+OK)ZHcAu{{RinYhv;UC70*Y0~7q{G)d4h=* iz-f~RC;{6_P5%YD*iJ1dJm3xh0000 providers: "providers-placeholder" provider_info: "provider-info-placeholder" services: "services-placeholder" + usage: "usage-placeholder" logs: "logs-placeholder" ) gui.setLinksEvents() @@ -194,6 +195,26 @@ gui.providers.link = (event) -> ) prevTables.push servicesTable prevTables.push logTable + + usageAPI = api.providers.detail(id, "usage", { permission: selected[0].permission }) + usage = new GuiElement(usageAPI, "usage-" + selected[0].type) + usageTable = usage.table( + icon: 'usage' + container: "usage-placeholder" + doNotLoadData: true + rowSelect: "multi" + + buttons: [ + "delete" + "xls" + ] + onDelete: gui.methods.del(usage, gettext("Delete user service"), gettext("User service deletion error"),) + scrollToTable: false + onLoad: (k) -> + gui.tools.unblockUI() + return + ) + return buttons: [ diff --git a/server/src/uds/templates/uds/admin/tmpl/providers.html b/server/src/uds/templates/uds/admin/tmpl/providers.html index a3316ebc..00978f6c 100644 --- a/server/src/uds/templates/uds/admin/tmpl/providers.html +++ b/server/src/uds/templates/uds/admin/tmpl/providers.html @@ -29,6 +29,7 @@
@@ -46,6 +47,12 @@
+
+
+
+
+
+
...
diff --git a/server/src/uds/transports/RDP/RDPFile.py b/server/src/uds/transports/RDP/RDPFile.py index 6c063e5d..e6d08fd2 100644 --- a/server/src/uds/transports/RDP/RDPFile.py +++ b/server/src/uds/transports/RDP/RDPFile.py @@ -40,7 +40,7 @@ from uds.core.util import OsDetector import six import os -__updated__ = '2017-06-05' +__updated__ = '2017-06-07' class RDPFile(object): @@ -249,6 +249,8 @@ class RDPFile(object): res += 'authentication level:i:0' + '\n' res += 'prompt for credentials:i:0' + '\n' res += 'negotiate security layer:i:1\n' + res += 'bandwidthautodetect:i:1\n' + res += 'connection type:i:7\n' res += 'videoplaybackmode:i:1\n' if self.smoothFonts is True: res += 'allow font smoothing:i:1\n'