* Small RDP fix (typo)

* Added secure ticket type
* Reformated sevice view
This commit is contained in:
Adolfo Gómez García 2020-11-16 19:45:54 +01:00
parent 50f3b79ee3
commit 58a70e368e
4 changed files with 91 additions and 83 deletions

View File

@ -173,6 +173,9 @@ class CryptoManager:
return toDecode[4 : 4 + struct.unpack('>i', toDecode[:4])[0]]
def xor(self, s1: typing.Union[str, bytes], s2: typing.Union[str, bytes]) -> bytes:
if len(s2) == 0:
return b'' # Protect against division by cero
if isinstance(s1, str):
s1 = s1.encode('utf-8')
if isinstance(s2, str):
@ -202,12 +205,12 @@ class CryptoManager:
if isinstance(key, str):
key = key.encode()
if not cryptText:
if not cryptText or not key:
return ''
try:
return self.AESDecrypt(cryptText, key).decode('utf-8')
except Exception: # Error decoding stored crypted password, return empty one
except Exception: # Error decoding crypted element, return empty one
return ''
def loadPrivateKey(self, rsaKey: str):

View File

@ -45,6 +45,7 @@ logger = logging.getLogger(__name__)
ValidatorType = typing.Callable[[typing.Any], bool]
SECURED = '#SECURE#' # Just a "different" owner. If used anywhere, it's not important (will not fail), but
class TicketStore(UUIDModel):
"""
@ -99,9 +100,14 @@ class TicketStore(UUIDModel):
validity is in seconds
"""
validator = pickle.dumps(validatorFnc) if validatorFnc else None
data = pickle.dumps(data)
if secure:
pass
if not owner:
raise ValueError('Tried to use a secure ticket without owner')
data = cryptoManager().AESCrypt(data, owner.encode())
owner = SECURED # So data is REALLY encrypted
return TicketStore.objects.create(
stamp=getSqlDatetime(),
@ -111,40 +117,6 @@ class TicketStore(UUIDModel):
owner=owner,
).uuid
@staticmethod
def store(
uuid: str,
data: str,
validatorFnc: typing.Optional[ValidatorType] = None,
validity: int = DEFAULT_VALIDITY,
owner: typing.Optional[str] = None,
secure: bool = False,
) -> None:
"""
Stores an ticketstore. If one with this uuid already exists, replaces it. Else, creates a new one
validity is in seconds
"""
validator = pickle.dumps(validatorFnc) if validatorFnc else None
if secure: # TODO: maybe in the future? what will mean "secure?" :)
pass
try:
t = TicketStore.objects.get(uuid=uuid)
t.data = pickle.dumps(data)
t.stamp = getSqlDatetime()
t.validity = validity
t.owner = owner
t.save()
except TicketStore.DoesNotExist:
TicketStore.objects.create(
uuid=uuid,
stamp=getSqlDatetime(),
data=pickle.dumps(data),
validator=validator,
validity=validity,
)
@staticmethod
def get(
uuid: str,
@ -153,7 +125,13 @@ class TicketStore(UUIDModel):
secure: bool = False,
) -> typing.Any:
try:
t = TicketStore.objects.get(uuid=uuid, owner=owner)
dbOwner = owner
if secure:
if not owner:
raise ValueError('Tried to use a secure ticket without owner')
dbOwner = SECURED
t = TicketStore.objects.get(uuid=uuid, owner=dbOwner)
validity = datetime.timedelta(seconds=t.validity)
now = getSqlDatetime()
@ -161,8 +139,12 @@ class TicketStore(UUIDModel):
if t.stamp + validity < now:
raise TicketStore.InvalidTicket('Not valid anymore')
# if secure: TODO
data = pickle.loads(t.data)
data: bytes = t.data
if secure: # Owner has already been tested and it's not emtpy
data = cryptoManager().AESDecrypt(data, typing.cast(str, owner).encode())
data = pickle.loads(data)
# If has validator, execute it
if t.validator:
@ -173,7 +155,7 @@ class TicketStore(UUIDModel):
if invalidate is True:
t.stamp = now - validity - datetime.timedelta(seconds=1)
t.save()
t.save(update_fields=['stamp'])
return data
except TicketStore.DoesNotExist:
@ -207,16 +189,12 @@ class TicketStore(UUIDModel):
TicketStore.objects.filter(stamp__lt=cleanSince).delete()
def __str__(self) -> str:
if self.validator:
validator = pickle.loads(self.validator)
else:
validator = None
data = pickle.loads(self.data) if self.owner != SECURED else '{Secure Ticket}'
return 'Ticket id: {}, Secure: {}, Stamp: {}, Validity: {}, Validator: {}, Data: {}'.format(
return 'Ticket id: {}, Owner: {}, Stamp: {}, Validity: {}, Data: {}'.format(
self.uuid,
self.owner,
self.stamp,
self.validity,
validator,
pickle.loads(self.data),
data,
)

View File

@ -372,7 +372,7 @@ class BaseRDPTransport(transports.Transport):
user: 'models.User',
password: str,
) -> typing.Dict[str, str]:
return self.processUserPassword(userService, user, password)
return self.processUserPassword(typing.cast('models.UserService', userService), user, password)
def getScript(
self, scriptNameTemplate: str, osName: str, params: typing.Dict[str, typing.Any]

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -44,7 +44,11 @@ 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.services.exceptions import (
ServiceNotReadyError,
MaxServicesReachedError,
ServiceAccessDeniedByCalendar,
)
from uds.web.util import errors
from uds.web.util import services
@ -57,32 +61,37 @@ logger = logging.getLogger(__name__)
@webLoginRequired(admin=False)
def transportOwnLink(request: 'ExtendedHttpRequestWithUser', idService: str, idTransport: str):
def transportOwnLink(
request: 'ExtendedHttpRequestWithUser', idService: str, idTransport: str
):
response: typing.MutableMapping[str, typing.Any] = {}
# For type checkers to "be happy"
try:
res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport)
res = userServiceManager().getService(
request.user, request.os, request.ip, idService, idTransport
)
ip, userService, iads, trans, itrans = res # pylint: disable=unused-variable
# This returns a response object in fact
if itrans and ip:
response = {
'url': itrans.getLink(userService, trans, ip, request.os, request.user, webPassword(request), request)
'url': itrans.getLink(
userService,
trans,
ip,
request.os,
request.user,
webPassword(request),
request,
)
}
except ServiceNotReadyError as e:
response = {
'running': e.code * 25
}
response = {'running': e.code * 25}
except Exception as e:
logger.exception("Exception")
response = {
'error': str(e)
}
response = {'error': str(e)}
return HttpResponse(
content=json.dumps(response),
content_type='application/json'
)
return HttpResponse(content=json.dumps(response), content_type='application/json')
# Will never reach this
return errors.errorView(request, errors.UNKNOWN_ERROR)
@ -114,7 +123,9 @@ def serviceImage(request: 'ExtendedHttpRequest', idImage: str) -> HttpResponse:
@webLoginRequired(admin=False)
@never_cache
def userServiceEnabler(request: 'ExtendedHttpRequestWithUser', idService: str, idTransport: str) -> HttpResponse:
def userServiceEnabler(
request: 'ExtendedHttpRequestWithUser', 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 = ''
@ -123,7 +134,9 @@ def userServiceEnabler(request: 'ExtendedHttpRequestWithUser', idService: str, i
# If meta service, process and rebuild idService & idTransport
try:
res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport, doTest=False)
res = userServiceManager().getService(
request.user, request.os, request.ip, idService, idTransport, doTest=False
)
scrambler = cryptoManager().randomString(32)
password = cryptoManager().symCrypt(webPassword(request), scrambler)
@ -140,7 +153,7 @@ def userServiceEnabler(request: 'ExtendedHttpRequestWithUser', idService: str, i
'service': 'A' + userService.uuid,
'transport': trans.uuid,
'user': request.user.uuid,
'password': password
'password': password,
}
ticket = TicketStore.create(data)
@ -149,7 +162,9 @@ def userServiceEnabler(request: 'ExtendedHttpRequestWithUser', idService: str, i
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))
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)
@ -161,42 +176,54 @@ def userServiceEnabler(request: 'ExtendedHttpRequestWithUser', idService: str, i
error = str(e)
return HttpResponse(
json.dumps({
'url': str(url),
'error': str(error)
}),
content_type='application/json'
json.dumps({'url': str(url), 'error': str(error)}),
content_type='application/json',
)
def closer(request: 'ExtendedHttpRequest') -> HttpResponse:
return HttpResponse('<html><body onload="window.close()"></body></html>')
@webLoginRequired(admin=False)
@never_cache
def action(request: 'ExtendedHttpRequestWithUser', idService: str, actionString: str) -> HttpResponse:
userService = userServiceManager().locateUserService(request.user, idService, create=False)
def action(
request: 'ExtendedHttpRequestWithUser', idService: str, actionString: str
) -> HttpResponse:
userService = userServiceManager().locateUserService(
request.user, idService, create=False
)
response: typing.Any = None
rebuild: bool = False
if userService:
if actionString == 'release' and userService.deployed_service.allow_users_remove:
if (
actionString == 'release'
and userService.deployed_service.allow_users_remove
):
rebuild = True
log.doLog(
userService.deployed_service,
log.INFO,
"Removing User Service {} as requested by {} from {}".format(userService.friendly_name, request.user.pretty_name, request.ip),
log.WEB
"Removing User Service {} as requested by {} from {}".format(
userService.friendly_name, request.user.pretty_name, request.ip
),
log.WEB,
)
userServiceManager().requestLogoff(userService)
userService.release()
elif (actionString == 'reset'
and userService.deployed_service.allow_users_reset
and userService.deployed_service.service.getType().canReset):
elif (
actionString == 'reset'
and userService.deployed_service.allow_users_reset
and userService.deployed_service.service.getType().canReset
):
rebuild = True
log.doLog(
userService.deployed_service,
log.INFO,
"Reseting User Service {} as requested by {} from {}".format(userService.friendly_name, request.user.pretty_name, request.ip),
log.WEB
"Reseting User Service {} as requested by {} from {}".format(
userService.friendly_name, request.user.pretty_name, request.ip
),
log.WEB,
)
# userServiceManager().requestLogoff(userService)
userServiceManager().reset(userService)