Fixed connection client working

This commit is contained in:
Adolfo Gómez García 2021-05-04 12:32:56 +02:00
parent 9a7afe7839
commit e920628395
5 changed files with 143 additions and 80 deletions

View File

@ -243,6 +243,7 @@ class Handler:
session.save() session.save()
self._authToken = session.session_key self._authToken = session.session_key
self._session = session self._session = session
return self._authToken return self._authToken
def cleanAuthToken(self) -> None: def cleanAuthToken(self) -> None:

View File

@ -39,7 +39,7 @@ from uds.REST import RequestError
from uds.core.managers import userServiceManager from uds.core.managers import userServiceManager
from uds.core.managers import cryptoManager from uds.core.managers import cryptoManager
from uds.core.services.exceptions import ServiceNotReadyError from uds.core.services.exceptions import ServiceNotReadyError
from uds.web.util import errors from uds.web.util import errors, services
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -50,7 +50,8 @@ class Connection(Handler):
""" """
Processes actor requests Processes actor requests
""" """
authenticated = True # Actor requests are not authenticated
authenticated = True # Connection requests are authenticated
needs_admin = False needs_admin = False
needs_staff = False needs_staff = False
@ -59,7 +60,7 @@ class Connection(Handler):
result: typing.Any = None, result: typing.Any = None,
error: typing.Optional[typing.Union[str, int]] = None, error: typing.Optional[typing.Union[str, int]] = None,
errorCode: int = 0, errorCode: int = 0,
retryable: bool = False retryable: bool = False,
) -> typing.Dict[str, typing.Any]: ) -> typing.Dict[str, typing.Any]:
""" """
Helper method to create a "result" set for connection response Helper method to create a "result" set for connection response
@ -83,33 +84,44 @@ class Connection(Handler):
def serviceList(self): 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 # 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 # 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): def connection(self, doNotCheck: bool = False):
idService = self._args[0] idService = self._args[0]
idTransport = self._args[1] idTransport = self._args[1]
try: 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 = { ci = {
'username': '', 'username': '',
'password': '', 'password': '',
'domain': '', 'domain': '',
'protocol': 'unknown', 'protocol': 'unknown',
'ip': ip 'ip': ip,
} }
if itrans: # only will be available id doNotCheck is False if itrans: # only will be available id doNotCheck is False
ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN')) ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN'))
return Connection.result(result=ci) return Connection.result(result=ci)
except ServiceNotReadyError as e: except ServiceNotReadyError as e:
# Refresh ticket and make this retrayable # 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: except Exception as e:
logger.exception("Exception") logger.exception("Exception")
return Connection.result(error=str(e)) return Connection.result(error=str(e))
@ -122,23 +134,59 @@ class Connection(Handler):
hostname = self._args[3] hostname = self._args[3]
try: 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) 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) 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) return Connection.result(result=transportScript)
except ServiceNotReadyError as e: except ServiceNotReadyError as e:
# Refresh ticket and make this retrayable # 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: except Exception as e:
logger.exception("Exception") logger.exception("Exception")
return Connection.result(error=str(e)) 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): def get(self):
""" """
Processes get requests Processes get requests
@ -157,9 +205,12 @@ class Connection(Handler):
return self.connection() return self.connection()
if len(self._args) == 3: if len(self._args) == 3:
# /connection/idService/idTransport/skipChecking # /connection/idService/idTransport/skipcheck
if self._args[2] == 'skipChecking': if self._args[2] == 'skipcheck':
return self.connection(True) return self.connection(True)
# /connection/idService/idTransport/udslink
elif self._args[2] == 'udslink':
return self.getUdsLink()
if len(self._args) == 4: if len(self._args) == 4:
# /connection/idService/idTransport/scrambler/hostname # /connection/idService/idTransport/scrambler/hostname

View File

@ -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 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. so we can provide it to remote sessions.
""" """
if hasattr(request, 'session'):
return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string 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: def webLogout(request: HttpRequest, exit_url: typing.Optional[str] = None) -> HttpResponse:

View File

@ -28,6 +28,7 @@
''' '''
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' '''
import json
import logging import logging
import typing import typing
@ -35,15 +36,19 @@ from django.utils.translation import ugettext
from django.utils import formats from django.utils import formats
from django.urls import reverse 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.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.util import html
from uds.core.managers import userServiceManager from uds.core.managers import userServiceManager, cryptoManager
# Not imported at runtime, just for type checking # Not imported at runtime, just for type checking
if typing.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__) logger = logging.getLogger(__name__)
@ -242,3 +247,54 @@ def getServicesData(request: 'HttpRequest') -> typing.Dict[str, typing.Any]: #
'transports': validTrans, 'transports': validTrans,
'autorun': autorun '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)
}

View File

@ -33,18 +33,16 @@ import logging
import typing import typing
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.urls import reverse
from django.http import HttpResponse from django.http import HttpResponse
from django.views.decorators.cache import cache_page, never_cache from django.views.decorators.cache import cache_page, never_cache
from uds.core.auths.auth import webLoginRequired, webPassword from uds.core.auths.auth import webLoginRequired, webPassword
from uds.core.managers import userServiceManager, cryptoManager from uds.core.managers import userServiceManager
from uds.models import TicketStore
from uds.core.ui.images import DEFAULT_IMAGE from uds.core.ui.images import DEFAULT_IMAGE
from uds.core.util.model import processUuid from uds.core.util.model import processUuid
from uds.models import Transport, Image from uds.models import Transport, Image
from uds.core.util import html, log from uds.core.util import log
from uds.core.services.exceptions import ServiceNotReadyError, MaxServicesReachedError, ServiceAccessDeniedByCalendar from uds.core.services.exceptions import ServiceNotReadyError
from uds.web.util import errors from uds.web.util import errors
from uds.web.util import services from uds.web.util import services
@ -113,59 +111,12 @@ def serviceImage(request: 'HttpRequest', idImage: str) -> HttpResponse:
@webLoginRequired(admin=False) @webLoginRequired(admin=False)
@never_cache @never_cache
def userServiceEnabler(request: 'HttpRequest', idService: str, idTransport: str) -> HttpResponse: 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( return HttpResponse(
json.dumps({ json.dumps(services.enableService(request, idService=idService, idTransport=idTransport)),
'url': str(url),
'error': str(error)
}),
content_type='application/json' content_type='application/json'
) )
def closer(request: 'HttpRequest') -> HttpResponse: def closer(request: 'HttpRequest') -> HttpResponse:
return HttpResponse('<html><body onload="window.close()"></body></html>') return HttpResponse('<html><body onload="window.close()"></body></html>')