From e92062839521bf0cd19aaf4f6f136148e1d9b7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Tue, 4 May 2021 12:32:56 +0200 Subject: [PATCH] Fixed connection client working --- server/src/uds/REST/handlers.py | 1 + server/src/uds/REST/methods/connection.py | 95 +++++++++++++++++------ server/src/uds/core/auths/auth.py | 6 +- server/src/uds/web/util/services.py | 62 ++++++++++++++- server/src/uds/web/views/service.py | 59 ++------------ 5 files changed, 143 insertions(+), 80 deletions(-) diff --git a/server/src/uds/REST/handlers.py b/server/src/uds/REST/handlers.py index c48d15e49..2a3df4869 100644 --- a/server/src/uds/REST/handlers.py +++ b/server/src/uds/REST/handlers.py @@ -243,6 +243,7 @@ class Handler: session.save() self._authToken = session.session_key self._session = session + return self._authToken def cleanAuthToken(self) -> None: diff --git a/server/src/uds/REST/methods/connection.py b/server/src/uds/REST/methods/connection.py index d7c58fb8d..62720dd08 100644 --- a/server/src/uds/REST/methods/connection.py +++ b/server/src/uds/REST/methods/connection.py @@ -39,7 +39,7 @@ from uds.REST import RequestError from uds.core.managers import userServiceManager from uds.core.managers import cryptoManager from uds.core.services.exceptions import ServiceNotReadyError -from uds.web.util import errors +from uds.web.util import errors, services logger = logging.getLogger(__name__) @@ -50,17 +50,18 @@ class Connection(Handler): """ Processes actor requests """ - authenticated = True # Actor requests are not authenticated + + authenticated = True # Connection requests are authenticated needs_admin = False needs_staff = False @staticmethod def result( - result: typing.Any = None, - error: typing.Optional[typing.Union[str, int]] = None, - errorCode: int = 0, - retryable: bool = False - ) -> typing.Dict[str, typing.Any]: + result: typing.Any = None, + error: typing.Optional[typing.Union[str, int]] = None, + errorCode: int = 0, + retryable: bool = False, + ) -> typing.Dict[str, typing.Any]: """ Helper method to create a "result" set for connection response :param result: Result value to return (can be None, in which case it is converted to empty string '') @@ -83,33 +84,44 @@ class Connection(Handler): def serviceList(self): # We look for services for this authenticator groups. User is logged in in just 1 authenticator, so his groups must coincide with those assigned to ds - from uds.web.util.services import getServicesData - # Ensure user is present on request, used by web views methods - self._request.user = self._user + self._request.user = self._user # type: ignore - return Connection.result(result=getServicesData(self._request)) + return Connection.result(result=services.getServicesData(self._request)) def connection(self, doNotCheck: bool = False): idService = self._args[0] idTransport = self._args[1] try: - ip, userService, iads, trans, itrans = userServiceManager().getService( # pylint: disable=unused-variable - self._user, self._request.os, self._request.ip, idService, idTransport, not doNotCheck + ( + ip, + userService, + iads, + trans, + itrans, + ) = userServiceManager().getService( # pylint: disable=unused-variable + self._user, + self._request.os, # type: ignore + self._request.ip, # type: ignore + idService, + idTransport, + not doNotCheck, ) ci = { 'username': '', 'password': '', 'domain': '', 'protocol': 'unknown', - 'ip': ip + 'ip': ip, } if itrans: # only will be available id doNotCheck is False ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN')) return Connection.result(result=ci) except ServiceNotReadyError as e: # Refresh ticket and make this retrayable - return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True) + return Connection.result( + error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True + ) except Exception as e: logger.exception("Exception") return Connection.result(error=str(e)) @@ -122,23 +134,59 @@ class Connection(Handler): hostname = self._args[3] try: - res = userServiceManager().getService(self._user, self._request.os, self._request.ip, idService, idTransport) + res = userServiceManager().getService( + self._user, self._request.os, self._request.ip, idService, idTransport # type: ignore + ) logger.debug('Res: %s', res) - ip, userService, userServiceInstance, transport, transportInstance = res # pylint: disable=unused-variable + ( + ip, + userService, + userServiceInstance, + transport, + transportInstance, + ) = res # pylint: disable=unused-variable password = cryptoManager().symDecrpyt(self.getValue('password'), scrambler) - userService.setConnectionSource(self._request.ip, hostname) # Store where we are accessing from so we can notify Service + userService.setConnectionSource( + self._request.ip, hostname # type: ignore + ) # Store where we are accessing from so we can notify Service - transportScript = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._user, password, self._request) + if not ip: + raise ServiceNotReadyError() + + transportScript = transportInstance.getEncodedTransportScript( + userService, + transport, + ip, + self._request.os, # type: ignore + self._user, + password, + self._request, + ) return Connection.result(result=transportScript) except ServiceNotReadyError as e: # Refresh ticket and make this retrayable - return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True) + return Connection.result( + error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True + ) except Exception as e: logger.exception("Exception") return Connection.result(error=str(e)) + def getTicketContent(self): + return {} # TODO: use this for something? + + def getUdsLink(self): + # Returns the UDS link for the user & transport + self._request.user = self._user # type: ignore + self._request._cryptedpass = self._session['REST']['password'] # type: ignore + self._request._scrambler = self._request.META['HTTP_SCRAMBLER'] # type: ignore + linkInfo = services.enableService(self._request, idService=self._args[0], idTransport=self._args[1]) + if linkInfo['error']: + return Connection.result(error=linkInfo['error']) + return Connection.result(result=linkInfo['url']) + def get(self): """ Processes get requests @@ -157,9 +205,12 @@ class Connection(Handler): return self.connection() if len(self._args) == 3: - # /connection/idService/idTransport/skipChecking - if self._args[2] == 'skipChecking': + # /connection/idService/idTransport/skipcheck + if self._args[2] == 'skipcheck': return self.connection(True) + # /connection/idService/idTransport/udslink + elif self._args[2] == 'udslink': + return self.getUdsLink() if len(self._args) == 4: # /connection/idService/idTransport/scrambler/hostname diff --git a/server/src/uds/core/auths/auth.py b/server/src/uds/core/auths/auth.py index 309f47507..3e4dc940f 100644 --- a/server/src/uds/core/auths/auth.py +++ b/server/src/uds/core/auths/auth.py @@ -311,7 +311,11 @@ def webPassword(request: HttpRequest) -> str: session (db) and client browser cookies. This method uses this two values to recompose the user password so we can provide it to remote sessions. """ - return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string + if hasattr(request, 'session'): + return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string + else: # No session, get from _session instead, this is an "client" REST request + return cryptoManager().symDecrpyt(request._cryptedpass, request._scrambler) # type: ignore + def webLogout(request: HttpRequest, exit_url: typing.Optional[str] = None) -> HttpResponse: diff --git a/server/src/uds/web/util/services.py b/server/src/uds/web/util/services.py index b5358fef1..3fb640b11 100644 --- a/server/src/uds/web/util/services.py +++ b/server/src/uds/web/util/services.py @@ -28,6 +28,7 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' +import json import logging import typing @@ -35,15 +36,19 @@ from django.utils.translation import ugettext from django.utils import formats from django.urls import reverse -from uds.models import ServicePool, Transport, Network, ServicePoolGroup, MetaPool, getSqlDatetime + +from uds.models import ServicePool, Transport, Network, ServicePoolGroup, MetaPool, getSqlDatetime, TicketStore from uds.core.util.config import GlobalConfig +from uds.core.services.exceptions import ServiceNotReadyError, MaxServicesReachedError, ServiceAccessDeniedByCalendar +from uds.core.auths.auth import webPassword +from uds.web.util import errors from uds.core.util import html -from uds.core.managers import userServiceManager +from uds.core.managers import userServiceManager, cryptoManager # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: - from django.http import HttpRequest # pylint: disable=ungrouped-imports + from django.http import HttpRequest, HttpResponse logger = logging.getLogger(__name__) @@ -242,3 +247,54 @@ def getServicesData(request: 'HttpRequest') -> typing.Dict[str, typing.Any]: # 'transports': validTrans, 'autorun': autorun } + +def enableService(request: 'HttpRequest', idService: str, idTransport: str) -> typing.Mapping[str, typing.Any]: + # Maybe we could even protect this even more by limiting referer to own server /? (just a meditation..) + logger.debug('idService: %s, idTransport: %s', idService, idTransport) + url = '' + error = ugettext('Service not ready. Please, try again in a while.') + + # If meta service, process and rebuild idService & idTransport + + try: + res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport, doTest=False) + scrambler = cryptoManager().randomString(32) + password = cryptoManager().symCrypt(webPassword(request), scrambler) + + userService, trans = res[1], res[3] + + typeTrans = trans.getType() + + error = '' # No error + + if typeTrans.ownLink: + url = reverse('TransportOwnLink', args=('A' + userService.uuid, trans.uuid)) + else: + data = { + 'service': 'A' + userService.uuid, + 'transport': trans.uuid, + 'user': request.user.uuid, + 'password': password + } + + ticket = TicketStore.create(data) + url = html.udsLink(request, ticket, scrambler) + except ServiceNotReadyError as e: + logger.debug('Service not ready') + # Not ready, show message and return to this page in a while + # error += ' (code {0:04X})'.format(e.code) + error = ugettext('Your service is being created, please, wait for a few seconds while we complete it.)') + '({}%)'.format(int(e.code * 25)) + except MaxServicesReachedError: + logger.info('Number of service reached MAX for service pool "%s"', idService) + error = errors.errorString(errors.MAX_SERVICES_REACHED) + except ServiceAccessDeniedByCalendar: + logger.info('Access tried to a calendar limited access pool "%s"', idService) + error = errors.errorString(errors.SERVICE_CALENDAR_DENIED) + except Exception as e: + logger.exception('Error') + error = str(e) + + return { + 'url': str(url), + 'error': str(error) + } diff --git a/server/src/uds/web/views/service.py b/server/src/uds/web/views/service.py index 1d0969496..b52cd7eee 100644 --- a/server/src/uds/web/views/service.py +++ b/server/src/uds/web/views/service.py @@ -33,18 +33,16 @@ import logging import typing from django.utils.translation import ugettext as _ -from django.urls import reverse from django.http import HttpResponse from django.views.decorators.cache import cache_page, never_cache from uds.core.auths.auth import webLoginRequired, webPassword -from uds.core.managers import userServiceManager, cryptoManager -from uds.models import TicketStore +from uds.core.managers import userServiceManager from uds.core.ui.images import DEFAULT_IMAGE from uds.core.util.model import processUuid from uds.models import Transport, Image -from uds.core.util import html, log -from uds.core.services.exceptions import ServiceNotReadyError, MaxServicesReachedError, ServiceAccessDeniedByCalendar +from uds.core.util import log +from uds.core.services.exceptions import ServiceNotReadyError from uds.web.util import errors from uds.web.util import services @@ -113,58 +111,11 @@ def serviceImage(request: 'HttpRequest', idImage: str) -> HttpResponse: @webLoginRequired(admin=False) @never_cache def userServiceEnabler(request: 'HttpRequest', idService: str, idTransport: str) -> HttpResponse: - # Maybe we could even protect this even more by limiting referer to own server /? (just a meditation..) - logger.debug('idService: %s, idTransport: %s', idService, idTransport) - url = '' - error = _('Service not ready. Please, try again in a while.') - - # If meta service, process and rebuild idService & idTransport - - try: - res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport, doTest=False) - scrambler = cryptoManager().randomString(32) - password = cryptoManager().symCrypt(webPassword(request), scrambler) - - userService, trans = res[1], res[3] - - typeTrans = trans.getType() - - error = '' # No error - - if typeTrans.ownLink: - url = reverse('TransportOwnLink', args=('A' + userService.uuid, trans.uuid)) - else: - data = { - 'service': 'A' + userService.uuid, - 'transport': trans.uuid, - 'user': request.user.uuid, - 'password': password - } - - ticket = TicketStore.create(data) - url = html.udsLink(request, ticket, scrambler) - except ServiceNotReadyError as e: - logger.debug('Service not ready') - # Not ready, show message and return to this page in a while - # error += ' (code {0:04X})'.format(e.code) - error = _('Your service is being created, please, wait for a few seconds while we complete it.)') + '({}%)'.format(int(e.code * 25)) - except MaxServicesReachedError: - logger.info('Number of service reached MAX for service pool "%s"', idService) - error = errors.errorString(errors.MAX_SERVICES_REACHED) - except ServiceAccessDeniedByCalendar: - logger.info('Access tried to a calendar limited access pool "%s"', idService) - error = errors.errorString(errors.SERVICE_CALENDAR_DENIED) - except Exception as e: - logger.exception('Error') - error = str(e) - return HttpResponse( - json.dumps({ - 'url': str(url), - 'error': str(error) - }), + json.dumps(services.enableService(request, idService=idService, idTransport=idTransport)), content_type='application/json' ) + def closer(request: 'HttpRequest') -> HttpResponse: return HttpResponse('')