forked from shaba/openuds
* Fixed comms to not "annoy" with warnings
* Added generateUuid to allow pass in an object * renamed "is_meta" to more convenient "is_owned_by_meta * Fixed 2.x actor rest api small bug (not used that part right now anyway, but for future)
This commit is contained in:
parent
637519a162
commit
52f524eb61
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2020 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -49,17 +49,19 @@ from uds.models import UserService
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Actor key, configurable in Security Section of administration interface
|
||||
actorKey = config.Config.section(config.SECURITY_SECTION).value('Master Key',
|
||||
cryptoManager().uuid(datetime.datetime.now()).replace('-', ''),
|
||||
type=config.Config.TEXT_FIELD)
|
||||
actorKey = config.Config.section(config.SECURITY_SECTION).value(
|
||||
'Master Key',
|
||||
cryptoManager().uuid(datetime.datetime.now()).replace('-', ''),
|
||||
type=config.Config.TEXT_FIELD,
|
||||
)
|
||||
|
||||
actorKey.get()
|
||||
|
||||
# Error codes:
|
||||
ERR_INVALID_KEY = 1
|
||||
ERR_HOST_NOT_MANAGED = 2
|
||||
ERR_USER_SERVICE_NOT_FOUND = 3
|
||||
ERR_OSMANAGER_ERROR = 4
|
||||
ERR_INVALID_KEY = '1'
|
||||
ERR_HOST_NOT_MANAGED = '2'
|
||||
ERR_USER_SERVICE_NOT_FOUND = '3'
|
||||
ERR_OSMANAGER_ERROR = '4'
|
||||
|
||||
# Constants for tickets
|
||||
OWNER = 'ACTOR'
|
||||
@ -71,6 +73,7 @@ class Actor(Handler):
|
||||
"""
|
||||
Processes actor requests
|
||||
"""
|
||||
|
||||
authenticated = False # Actor requests are not authenticated
|
||||
|
||||
@staticmethod
|
||||
@ -104,7 +107,7 @@ class Actor(Handler):
|
||||
return Actor.result(_('Invalid key'), error=ERR_INVALID_KEY)
|
||||
return None
|
||||
|
||||
def getUserServiceByIds(self) -> UserService:
|
||||
def getUserServiceByIds(self) -> typing.Optional[UserService]:
|
||||
"""
|
||||
This will get the client from the IDs passed from parameters
|
||||
"""
|
||||
@ -115,7 +118,9 @@ class Actor(Handler):
|
||||
except Exception:
|
||||
raise RequestError('Invalid request: (no id found)')
|
||||
|
||||
services = UserService.objects.filter(unique_id__in=clientIds, state__in=[State.USABLE, State.PREPARING])
|
||||
services = UserService.objects.filter(
|
||||
unique_id__in=clientIds, state__in=[State.USABLE, State.PREPARING]
|
||||
)
|
||||
|
||||
return services[0] if services else None
|
||||
|
||||
@ -176,11 +181,7 @@ class Actor(Handler):
|
||||
maxIdle = service.deployed_service.osmanager.getInstance().maxIdle()
|
||||
logger.debug('Max idle: %s', maxIdle)
|
||||
return Actor.result(
|
||||
(
|
||||
service.uuid,
|
||||
service.unique_id,
|
||||
0 if maxIdle is None else maxIdle
|
||||
)
|
||||
(service.uuid, service.unique_id, 0 if maxIdle is None else maxIdle)
|
||||
)
|
||||
raise RequestError('Invalid request')
|
||||
|
||||
@ -202,7 +203,9 @@ class Actor(Handler):
|
||||
try:
|
||||
service: UserService = UserService.objects.get(uuid=processUuid(uuid))
|
||||
except Exception:
|
||||
return Actor.result(_('User service not found'), error=ERR_USER_SERVICE_NOT_FOUND)
|
||||
return Actor.result(
|
||||
_('User service not found'), error=ERR_USER_SERVICE_NOT_FOUND
|
||||
)
|
||||
|
||||
if message == 'notifyComms':
|
||||
logger.debug('Setting comms url to %s', data)
|
||||
@ -210,7 +213,7 @@ class Actor(Handler):
|
||||
return Actor.result('ok')
|
||||
if message == 'ssoAvailable':
|
||||
logger.debug('Setting that SSO is available')
|
||||
service.setProperty('sso_available', 1)
|
||||
service.setProperty('sso_available', '1')
|
||||
return Actor.result('ok')
|
||||
if message == 'version':
|
||||
version = self._params.get('version', 'unknown')
|
||||
@ -220,7 +223,9 @@ class Actor(Handler):
|
||||
# "Cook" some messages, common to all clients, such as "log"
|
||||
if message == 'log':
|
||||
logger.debug(self._params)
|
||||
data = '\t'.join((self._params.get('message'), str(self._params.get('level', 10000))))
|
||||
data = '\t'.join(
|
||||
(self._params.get('message'), str(self._params.get('level', 10000)))
|
||||
)
|
||||
|
||||
osmanager = service.getInstance().osmanager()
|
||||
|
||||
@ -234,7 +239,11 @@ class Actor(Handler):
|
||||
# Mark for removal...
|
||||
service.release() # Release for removal
|
||||
return 'ok'
|
||||
raise Exception('Unknown message {} for an user service without os manager'.format(message))
|
||||
raise Exception(
|
||||
'Unknown message {} for an user service without os manager'.format(
|
||||
message
|
||||
)
|
||||
)
|
||||
res = osmanager.process(service, message, data, options={'scramble': False})
|
||||
if not res:
|
||||
raise Exception('Old Actors not supported by this os Manager!')
|
||||
|
@ -1,3 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2019-2020 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.
|
||||
"""
|
||||
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
@ -14,18 +44,21 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
TIMEOUT = 2
|
||||
|
||||
|
||||
class NoActorComms(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OldActorVersion(NoActorComms):
|
||||
pass
|
||||
|
||||
|
||||
def _requestActor(
|
||||
userService: 'UserService',
|
||||
method: str,
|
||||
data: typing.Optional[typing.MutableMapping[str, typing.Any]] = None,
|
||||
minVersion: typing.Optional[str] = None
|
||||
) -> typing.Any:
|
||||
userService: 'UserService',
|
||||
method: str,
|
||||
data: typing.Optional[typing.MutableMapping[str, typing.Any]] = None,
|
||||
minVersion: typing.Optional[str] = None,
|
||||
) -> typing.Any:
|
||||
"""
|
||||
Makes a request to actor using "method"
|
||||
if data is None, request is done using GET, else POST
|
||||
@ -34,14 +67,20 @@ def _requestActor(
|
||||
"""
|
||||
url = userService.getCommsUrl()
|
||||
if not url:
|
||||
logger.warning('No notification is made because agent does not supports notifications: %s', userService.friendly_name)
|
||||
raise NoActorComms('No notification urls for {}'.format(userService.friendly_name))
|
||||
# logger.warning('No notification is made because agent does not supports notifications: %s', userService.friendly_name)
|
||||
raise NoActorComms(
|
||||
'No notification urls for {}'.format(userService.friendly_name)
|
||||
)
|
||||
|
||||
minVersion = minVersion or '2.0.0'
|
||||
version = userService.getProperty('actor_version') or '0.0.0'
|
||||
if '-' in version or version < minVersion:
|
||||
logger.warning('Pool %s has old actors (%s)', userService.deployed_service.name, version)
|
||||
raise OldActorVersion('Old actor version {} for {}'.format(version, userService.friendly_name))
|
||||
logger.warning(
|
||||
'Pool %s has old actors (%s)', userService.deployed_service.name, version
|
||||
)
|
||||
raise OldActorVersion(
|
||||
'Old actor version {} for {}'.format(version, userService.friendly_name)
|
||||
)
|
||||
|
||||
url += '/' + method
|
||||
|
||||
@ -68,7 +107,7 @@ def _requestActor(
|
||||
data=json.dumps(data),
|
||||
headers={'content-type': 'application/json'},
|
||||
verify=verify,
|
||||
timeout=TIMEOUT
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
if verify:
|
||||
try:
|
||||
@ -81,34 +120,53 @@ def _requestActor(
|
||||
js = js['result']
|
||||
logger.debug('Requested %s to actor. Url=%s', method, url)
|
||||
except Exception as e:
|
||||
logger.warning('Request %s failed: %s. Check connection on destination machine: %s', method, e, url)
|
||||
logger.warning(
|
||||
'Request %s failed: %s. Check connection on destination machine: %s',
|
||||
method,
|
||||
e,
|
||||
url,
|
||||
)
|
||||
js = None
|
||||
|
||||
return js
|
||||
|
||||
|
||||
def notifyPreconnect(userService: 'UserService', userName: str, protocol: str) -> None:
|
||||
'''
|
||||
"""
|
||||
Notifies a preconnect to an user service
|
||||
'''
|
||||
"""
|
||||
ip, hostname = userService.getConnectionSource()
|
||||
try:
|
||||
_requestActor(userService, 'preConnect', {'user': userName, 'protocol': protocol, 'ip': ip, 'hostname': hostname})
|
||||
_requestActor(
|
||||
userService,
|
||||
'preConnect',
|
||||
{'user': userName, 'protocol': protocol, 'ip': ip, 'hostname': hostname},
|
||||
)
|
||||
except NoActorComms:
|
||||
pass # If no preconnect, warning will appear on UDS log
|
||||
|
||||
def checkUuid(userService: 'UserService') -> bool:
|
||||
'''
|
||||
|
||||
def checkUuid(userService: 'UserService') -> bool:
|
||||
"""
|
||||
Checks if the uuid of the service is the same of our known uuid on DB
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
uuid = _requestActor(userService, 'uuid')
|
||||
if uuid and uuid != userService.uuid: # Empty UUID means "no check this, fixed pool machine"
|
||||
logger.info('Machine %s do not have expected uuid %s, instead has %s', userService.friendly_name, userService.uuid, uuid)
|
||||
if (
|
||||
uuid and uuid != userService.uuid
|
||||
): # Empty UUID means "no check this, fixed pool machine"
|
||||
logger.info(
|
||||
'Machine %s do not have expected uuid %s, instead has %s',
|
||||
userService.friendly_name,
|
||||
userService.uuid,
|
||||
uuid,
|
||||
)
|
||||
return False
|
||||
except NoActorComms:
|
||||
pass
|
||||
|
||||
return True # Actor does not supports checking
|
||||
return True # Actor does not supports checking
|
||||
|
||||
|
||||
def requestScreenshot(userService: 'UserService') -> bytes:
|
||||
"""
|
||||
@ -116,24 +174,28 @@ def requestScreenshot(userService: 'UserService') -> bytes:
|
||||
"""
|
||||
emptyPng = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
|
||||
try:
|
||||
png = _requestActor(userService, 'screenshot', minVersion='3.0.0') # First valid version with screenshot is 3.0
|
||||
png = _requestActor(
|
||||
userService, 'screenshot', minVersion='3.0.0'
|
||||
) # First valid version with screenshot is 3.0
|
||||
except NoActorComms:
|
||||
png = None
|
||||
|
||||
return base64.b64decode(png or emptyPng)
|
||||
|
||||
|
||||
def sendScript(userService: 'UserService', script: str, forUser: bool = False) -> None:
|
||||
"""
|
||||
If allowed, send script to user service
|
||||
"""
|
||||
try:
|
||||
data: typing.MutableMapping[str, typing.Any] = {'script': script }
|
||||
data: typing.MutableMapping[str, typing.Any] = {'script': script}
|
||||
if forUser:
|
||||
data['user'] = forUser
|
||||
_requestActor(userService, 'script', data=data)
|
||||
except NoActorComms:
|
||||
pass
|
||||
|
||||
|
||||
def requestLogoff(userService: 'UserService') -> None:
|
||||
"""
|
||||
Ask client to logoff user
|
||||
@ -143,11 +205,12 @@ def requestLogoff(userService: 'UserService') -> None:
|
||||
except NoActorComms:
|
||||
pass
|
||||
|
||||
|
||||
def sendMessage(userService: 'UserService', message: str) -> None:
|
||||
"""
|
||||
Sends an screen message to client
|
||||
"""
|
||||
try:
|
||||
_requestActor(userService, 'message', data={'message':message})
|
||||
_requestActor(userService, 'message', data={'message': message})
|
||||
except NoActorComms:
|
||||
pass
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2020 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -31,14 +31,14 @@ import typing
|
||||
from uds.core.managers import cryptoManager
|
||||
|
||||
|
||||
def generateUuid() -> str:
|
||||
def generateUuid(obj: typing.Any = None) -> str:
|
||||
"""
|
||||
Generates a ramdom uuid for models default
|
||||
"""
|
||||
return cryptoManager().uuid().lower()
|
||||
return cryptoManager().uuid(obj=obj).lower()
|
||||
|
||||
|
||||
def processUuid(uuid: typing.Union[str, bytes]) -> str:
|
||||
def processUuid(uuid: str) -> str:
|
||||
if isinstance(uuid, bytes):
|
||||
uuid = uuid.decode('utf8')
|
||||
uuid = uuid.decode('utf8') # type: ignore
|
||||
return uuid.lower()
|
||||
|
@ -93,18 +93,17 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
|
||||
logger.debug('Checking meta pools: %s', availMetaPools)
|
||||
services = []
|
||||
meta: MetaPool
|
||||
# Preload all assigned user services for this user
|
||||
|
||||
# Add meta pools data first
|
||||
for meta in availMetaPools:
|
||||
# Check that we have access to at least one transport on some of its children
|
||||
hasUsablePools = False
|
||||
in_use = meta.number_in_use > 0 # False
|
||||
for pool in meta.pools.all():
|
||||
in_use = typing.cast(typing.Any, meta).number_in_use > 0 # Override, because we used hear annotations
|
||||
for member in meta.members.all():
|
||||
# if pool.isInMaintenance():
|
||||
# continue
|
||||
for t in pool.transports.all():
|
||||
for t in member.pool.transports.all():
|
||||
typeTrans = t.getType()
|
||||
if t.getType() and t.validForIp(request.ip) and typeTrans.supportsOs(os['OS']) and t.validForOs(os['OS']):
|
||||
hasUsablePools = True
|
||||
@ -148,13 +147,12 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
})
|
||||
|
||||
# Now generic user service
|
||||
svr: ServicePool
|
||||
for svr in availServicePools:
|
||||
# Skip pools that are part of meta pools
|
||||
if svr.is_meta:
|
||||
if svr.owned_by_meta:
|
||||
continue
|
||||
|
||||
use = str(svr.usage(svr.usage_count)) + '%'
|
||||
use = str(svr.usage(typing.cast(typing.Any, svr).usage_count)) + '%'
|
||||
|
||||
trans = []
|
||||
for t in sorted(svr.transports.all(), key=lambda x: x.priority): # In memory sort, allows reuse prefetched and not too big array
|
||||
@ -186,7 +184,7 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
imageId = 'x'
|
||||
|
||||
# Locate if user service has any already assigned user service for this. Use "pre cached" number of assignations in this pool to optimize
|
||||
in_use = svr.number_in_use > 0
|
||||
in_use = typing.cast(typing.Any, svr).number_in_use > 0
|
||||
# if svr.number_in_use: # Anotated value got from getDeployedServicesForGroups(...). If 0, no assignation for this user
|
||||
# ads = userServiceManager().getExistingAssignationForUser(svr, request.user)
|
||||
# if ads:
|
||||
@ -195,15 +193,17 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
group = svr.servicesPoolGroup.as_dict if svr.servicesPoolGroup else ServicePoolGroup.default().as_dict
|
||||
|
||||
# Only add toBeReplaced info in case we allow it. This will generate some "overload" on the services
|
||||
toBeReplaced = svr.toBeReplaced(request.user) if svr.pubs_active > 0 and GlobalConfig.NOTIFY_REMOVAL_BY_PUB.getBool(False) else None
|
||||
toBeReplaced = svr.toBeReplaced(request.user) if typing.cast(
|
||||
typing.Any, svr).pubs_active > 0 and GlobalConfig.NOTIFY_REMOVAL_BY_PUB.getBool(False) else None
|
||||
# tbr = False
|
||||
if toBeReplaced:
|
||||
toBeReplaced = formats.date_format(toBeReplaced, "SHORT_DATETIME_FORMAT")
|
||||
toBeReplacedTxt = ugettext('This service is about to be replaced by a new version. Please, close the session before {} and save all your work to avoid loosing it.').format(toBeReplaced)
|
||||
toBeReplacedTxt = ugettext(
|
||||
'This service is about to be replaced by a new version. Please, close the session before {} and save all your work to avoid loosing it.').format(toBeReplaced)
|
||||
else:
|
||||
toBeReplacedTxt = ''
|
||||
|
||||
datator = lambda x: x.replace('{use}', use).replace('{total}', str(svr.max_srvs))
|
||||
def datator(x): return x.replace('{use}', use).replace('{total}', str(svr.max_srvs))
|
||||
|
||||
services.append({
|
||||
'id': 'F' + svr.uuid,
|
||||
|
Loading…
x
Reference in New Issue
Block a user