forked from shaba/openuds
* Small RDP fix (typo)
* Added secure ticket type * Reformated sevice view
This commit is contained in:
parent
50f3b79ee3
commit
58a70e368e
@ -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):
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user