1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-20 06:50:23 +03:00

Formating and type fixing

This commit is contained in:
Adolfo Gómez García 2021-08-24 12:15:10 +02:00
parent 3934f2b88d
commit e485374836
58 changed files with 1180 additions and 590 deletions

View File

@ -155,6 +155,7 @@ def webLoginRequired(
return decorator
# Helper for checking if requests is from trusted source
def isTrustedSource(ip: str) -> bool:
return net.ipInNetwork(ip, GlobalConfig.TRUSTED_SOURCES.get(True))
@ -224,7 +225,7 @@ def __registerUser(
# And add an login event
events.addEvent(
authenticator, events.ET_LOGIN, username=username, srcip=request.ip
)
)
events.addEvent(
authenticator,
events.ET_PLATFORM,
@ -345,7 +346,10 @@ def authInfoUrl(authenticator: typing.Union[str, bytes, Authenticator]) -> str:
def webLogin(
request: 'ExtendedHttpRequest', response: typing.Optional[HttpResponse], user: User, password: str
request: 'ExtendedHttpRequest',
response: typing.Optional[HttpResponse],
user: User,
password: str,
) -> bool:
"""
Helper function to, once the user is authenticated, store the information at the user session.
@ -393,7 +397,9 @@ def webPassword(request: HttpRequest) -> str:
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
@ -428,7 +434,6 @@ def webLogout(
# Try to delete session
request.session.flush()
response = HttpResponseRedirect(request.build_absolute_uri(exit_url))
if authenticator:
@ -437,7 +442,10 @@ def webLogout(
def authLogLogin(
request: 'ExtendedHttpRequest', authenticator: Authenticator, userName: str, logStr: str = ''
request: 'ExtendedHttpRequest',
authenticator: Authenticator,
userName: str,
logStr: str = '',
) -> None:
"""
Logs authentication

View File

@ -39,7 +39,10 @@ from uds.core import Module
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse # pylint: disable=ungrouped-imports
from django.http import (
HttpRequest,
HttpResponse,
) # pylint: disable=ungrouped-imports
from uds.core.environment import Environment
from uds import models
from .groups_manager import GroupsManager
@ -161,7 +164,12 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
_dbAuth: 'models.Authenticator'
def __init__(self, dbAuth: 'models.Authenticator', environment: 'Environment', values: typing.Optional[typing.Dict[str, str]]):
def __init__(
self,
dbAuth: 'models.Authenticator',
environment: 'Environment',
values: typing.Optional[typing.Dict[str, str]],
):
"""
Instantiathes the authenticator.
@param dbAuth: Database object for the authenticator
@ -202,13 +210,17 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
user param is a database user object
"""
from uds.core.auths.groups_manager import GroupsManager # pylint: disable=redefined-outer-name
from uds.core.auths.groups_manager import (
GroupsManager,
) # pylint: disable=redefined-outer-name
if self.isExternalSource:
groupsManager = GroupsManager(self._dbAuth)
self.getGroups(user.name, groupsManager)
# cast for typechecking. user.groups is a "simmmilar to a QuerySet", but it's not a QuerySet, so "set" is not there
typing.cast(typing.Any, user.groups).set([g.dbGroup() for g in groupsManager.getValidGroups()])
typing.cast(typing.Any, user.groups).set(
[g.dbGroup() for g in groupsManager.getValidGroups()]
)
def callbackUrl(self) -> str:
"""
@ -218,6 +230,7 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
we need to use callback for authentication
"""
from .auth import authCallbackUrl
return authCallbackUrl(self.dbAuthenticator())
def infoUrl(self) -> str:
@ -225,6 +238,7 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
Helper method to return info url for this authenticator
"""
from .auth import authInfoUrl
return authInfoUrl(self.dbAuthenticator())
@classmethod
@ -276,7 +290,9 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
"""
return []
def authenticate(self, username: str, credentials: str, groupsManager: 'GroupsManager') -> bool:
def authenticate(
self, username: str, credentials: str, groupsManager: 'GroupsManager'
) -> bool:
"""
This method must be overriden, and is responsible for authenticating
users.
@ -332,7 +348,9 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
"""
return username
def internalAuthenticate(self, username: str, credentials: str, groupsManager: 'GroupsManager') -> bool:
def internalAuthenticate(
self, username: str, credentials: str, groupsManager: 'GroupsManager'
) -> bool:
"""
This method is provided so "plugins" (For example, a custom dispatcher), can test
the username/credentials in an alternative way.
@ -396,7 +414,9 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
"""
return None
def webLogoutHook(self, username: str, request: 'HttpRequest', response: 'HttpResponse') -> None:
def webLogoutHook(
self, username: str, request: 'HttpRequest', response: 'HttpResponse'
) -> None:
'''
Invoked on web logout of an user
Args:
@ -455,7 +475,9 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
"""
return None
def authCallback(self, parameters: typing.Dict[str, typing.Any], gm: 'GroupsManager') -> typing.Optional[str]:
def authCallback(
self, parameters: typing.Dict[str, typing.Any], gm: 'GroupsManager'
) -> typing.Optional[str]:
"""
There is a view inside UDS, an url, that will redirect the petition
to this callback.
@ -491,7 +513,9 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
"""
return None
def getInfo(self, parameters: typing.Mapping[str, str]) -> typing.Optional[typing.Tuple[str, typing.Optional[str]]]:
def getInfo(
self, parameters: typing.Mapping[str, str]
) -> typing.Optional[typing.Tuple[str, typing.Optional[str]]]:
"""
This method is invoked whenever the authinfo url is invoked, with the name of the authenticator
If this is implemented, information returned by this will be shown via web.

View File

@ -36,6 +36,7 @@ import typing
if typing.TYPE_CHECKING:
from .authenticator import Authenticator
class AuthsFactory:
"""
This class holds the register of all known authentication modules
@ -43,6 +44,7 @@ class AuthsFactory:
It provides a way to register and recover Authentication providers.
"""
_factory: typing.Optional['AuthsFactory'] = None
_auths: typing.Dict[str, typing.Type['Authenticator']] = {}

View File

@ -31,6 +31,7 @@
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
"""
class AuthenticatorException(Exception):
"""
Generic authentication exception

View File

@ -62,6 +62,7 @@ class GroupsManager:
Managed groups names are compared using case insensitive comparison.
"""
_groups: typing.Dict[str, dict]
def __init__(self, dbAuthenticator: 'DBAuthenticator'):
@ -71,11 +72,18 @@ class GroupsManager:
to which this groupsManager will be associated
"""
self._dbAuthenticator = dbAuthenticator
self._groups = {} # We just get active groups, inactive aren't visible to this class
self._groups = (
{}
) # We just get active groups, inactive aren't visible to this class
for g in dbAuthenticator.groups.filter(state=State.ACTIVE, is_meta=False):
name = g.name.lower()
isPattern = name.find('pat:') == 0 # Is a pattern?
self._groups[name] = {'name': g.name, 'group': Group(g), 'valid': False, 'pattern': isPattern}
self._groups[name] = {
'name': g.name,
'group': Group(g),
'valid': False,
'pattern': isPattern,
}
def checkAllGroups(self, groupName: str):
"""
@ -119,11 +127,15 @@ class GroupsManager:
yield g['group']
# Now, get metagroups and also return them
for g2 in DBGroup.objects.filter(manager__id=self._dbAuthenticator.id, is_meta=True): # @UndefinedVariable
for g2 in DBGroup.objects.filter(
manager__id=self._dbAuthenticator.id, is_meta=True
): # @UndefinedVariable
gn = g2.groups.filter(id__in=lst, state=State.ACTIVE).count()
if g2.meta_if_any and gn > 0:
gn = g2.groups.count()
if gn == g2.groups.count(): # If a meta group is empty, all users belongs to it. we can use gn != 0 to check that if it is empty, is not valid
if (
gn == g2.groups.count()
): # If a meta group is empty, all users belongs to it. we can use gn != 0 to check that if it is empty, is not valid
# This group matches
yield Group(g2)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -40,9 +40,11 @@ from .delayed_task import DelayedTask
if typing.TYPE_CHECKING:
from .jobs_factory import JobsFactory
def factory() -> 'JobsFactory':
"""
Returns a singleton to a jobs factory
"""
from .jobs_factory import JobsFactory # pylint: disable=redefined-outer-name
return JobsFactory.factory()

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2020 Virtual Cable S.L.U.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -10,7 +10,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -54,13 +54,14 @@ class DelayedTaskThread(threading.Thread):
"""
Class responsible of executing a delayed task in its own thread
"""
_taskInstance: DelayedTask
def __init__(self, taskInstance: DelayedTask) -> None:
super().__init__()
self._taskInstance = taskInstance
def run(self):
def run(self) -> None:
try:
self._taskInstance.execute()
except Exception as e:
@ -73,8 +74,8 @@ class DelayedTaskRunner:
"""
Delayed task runner class
"""
# How often tasks are checked
granularity: int = 2
granularity: int = 2 # we check for delayed tasks every "granularity" seconds
# to keep singleton DelayedTaskRunner
_runner: typing.ClassVar[typing.Optional['DelayedTaskRunner']] = None
@ -106,14 +107,22 @@ class DelayedTaskRunner:
def executeOneDelayedTask(self) -> None:
now = getSqlDatetime()
filt = Q(execution_time__lt=now) | Q(insert_date__gt=now + timedelta(seconds=30))
filt = Q(execution_time__lt=now) | Q(
insert_date__gt=now + timedelta(seconds=30)
)
# If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
try:
with transaction.atomic(): # Encloses
# Throws exception if no delayed task is avilable
task = DBDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0] # @UndefinedVariable
task = (
DBDelayedTask.objects.select_for_update()
.filter(filt)
.order_by('execution_time')[0]
) # @UndefinedVariable
if task.insert_date > now + timedelta(seconds=30):
logger.warning('Executed %s due to insert_date being in the future!', task.type)
logger.warning(
'Executed %s due to insert_date being in the future!', task.type
)
taskInstanceDump = codecs.decode(task.instance.encode(), 'base64')
task.delete()
taskInstance = pickle.loads(taskInstanceDump)
@ -140,10 +149,21 @@ class DelayedTaskRunner:
instanceDump = codecs.encode(pickle.dumps(instance), 'base64').decode()
typeName = str(cls.__module__ + '.' + cls.__name__)
logger.debug('Inserting delayed task %s with %s bytes (%s)', typeName, len(instanceDump), exec_time)
logger.debug(
'Inserting delayed task %s with %s bytes (%s)',
typeName,
len(instanceDump),
exec_time,
)
DBDelayedTask.objects.create(type=typeName, instance=instanceDump, # @UndefinedVariable
insert_date=now, execution_delay=delay, execution_time=exec_time, tag=tag)
DBDelayedTask.objects.create(
type=typeName,
instance=instanceDump, # @UndefinedVariable
insert_date=now,
execution_delay=delay,
execution_time=exec_time,
tag=tag,
)
def insert(self, instance: DelayedTask, delay: int, tag: str = '') -> bool:
retries = 3
@ -161,14 +181,18 @@ class DelayedTaskRunner:
time.sleep(1) # Wait a bit before next try...
# If retries == 0, this is a big error
if retries == 0:
logger.error("Could not insert delayed task!!!! %s %s %s", instance, delay, tag)
logger.error(
"Could not insert delayed task!!!! %s %s %s", instance, delay, tag
)
return False
return True
def remove(self, tag: str) -> None:
try:
with transaction.atomic():
DBDelayedTask.objects.select_for_update().filter(tag=tag).delete() # @UndefinedVariable
DBDelayedTask.objects.select_for_update().filter(
tag=tag
).delete() # @UndefinedVariable
except Exception as e:
logger.exception('Exception removing a delayed task %s: %s', e.__class__, e)

View File

@ -80,16 +80,30 @@ class JobsFactory:
# We use database server datetime
now = getSqlDatetime()
next_ = now
job = Scheduler.objects.create(name=name, frecuency=type_.frecuency, last_execution=now, next_execution=next_, state=State.FOR_EXECUTE)
job = Scheduler.objects.create(
name=name,
frecuency=type_.frecuency,
last_execution=now,
next_execution=next_,
state=State.FOR_EXECUTE,
)
except Exception: # already exists
logger.debug('Already added %s', name)
job = Scheduler.objects.get(name=name)
job.frecuency = type_.frecuency
if job.next_execution > job.last_execution + datetime.timedelta(seconds=type_.frecuency):
job.next_execution = job.last_execution + datetime.timedelta(seconds=type_.frecuency)
if job.next_execution > job.last_execution + datetime.timedelta(
seconds=type_.frecuency
):
job.next_execution = job.last_execution + datetime.timedelta(
seconds=type_.frecuency
)
job.save()
except Exception as e:
logger.debug('Exception at ensureJobsInDatabase in JobsFactory: %s, %s', e.__class__, e)
logger.debug(
'Exception at ensureJobsInDatabase in JobsFactory: %s, %s',
e.__class__,
e,
)
def lookup(self, typeName: str) -> typing.Optional[typing.Type['Job']]:
return self._jobs.get(typeName, None)

View File

@ -43,30 +43,38 @@ if typing.TYPE_CHECKING:
from .user_service import UserServiceManager
from .publication import PublicationManager
def cryptoManager() -> 'CryptoManager':
from .crypto import CryptoManager # pylint: disable=redefined-outer-name
return CryptoManager.manager()
def taskManager() -> typing.Type['TaskManager']:
from .task import TaskManager # pylint: disable=redefined-outer-name
return TaskManager
def downloadsManager() -> 'DownloadsManager':
from .downloads import DownloadsManager # pylint: disable=redefined-outer-name
return DownloadsManager.manager()
def logManager() -> 'LogManager':
from .log import LogManager # pylint: disable=redefined-outer-name
return LogManager.manager()
def userServiceManager() -> 'UserServiceManager':
from .user_service import UserServiceManager # pylint: disable=redefined-outer-name
return UserServiceManager.manager()
def publicationManager() -> 'PublicationManager':
from .publication import PublicationManager # pylint: disable=redefined-outer-name
return PublicationManager.manager()

View File

@ -55,6 +55,7 @@ class DownloadsManager:
os.path.join(os.path.dirname(sys.modules[__package__].__file__), 'files/test.exe'),
'application/x-msdos-program')
"""
_manager: typing.Optional['DownloadsManager'] = None
_downloadables: typing.Dict[str, typing.Dict[str, str]] = {}
@ -67,7 +68,9 @@ class DownloadsManager:
DownloadsManager._manager = DownloadsManager()
return DownloadsManager._manager
def registerDownloadable(self, name: str, comment: str, path: str, mime: str = 'application/octet-stream'):
def registerDownloadable(
self, name: str, comment: str, path: str, mime: str = 'application/octet-stream'
):
"""
Registers a downloadable file.
@param name: name shown
@ -75,16 +78,28 @@ class DownloadsManager:
@params zip: If download as zip
"""
_id = cryptoManager().uuid(name)
self._downloadables[_id] = {'name': name, 'comment': comment, 'path': path, 'mime': mime}
self._downloadables[_id] = {
'name': name,
'comment': comment,
'path': path,
'mime': mime,
}
def getDownloadables(self) -> typing.Dict[str, typing.Dict[str, str]]:
return self._downloadables
def send(self, request, _id) -> HttpResponse:
if _id not in self._downloadables:
logger.error('Downloadable id %s not found in %s!!!', _id, self._downloadables)
logger.error(
'Downloadable id %s not found in %s!!!', _id, self._downloadables
)
raise Http404
return self._send_file(request, self._downloadables[_id]['name'], self._downloadables[_id]['path'], self._downloadables[_id]['mime'])
return self._send_file(
request,
self._downloadables[_id]['name'],
self._downloadables[_id]['path'],
self._downloadables[_id]['mime'],
)
def _send_file(self, _, name, filename, mime) -> HttpResponse:
"""

View File

@ -45,7 +45,19 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
OT_USERSERVICE, OT_PUBLICATION, OT_DEPLOYED_SERVICE, OT_SERVICE, OT_PROVIDER, OT_USER, OT_GROUP, OT_AUTHENTICATOR, OT_METAPOOL = range(9) # @UndefinedVariable
(
OT_USERSERVICE,
OT_PUBLICATION,
OT_DEPLOYED_SERVICE,
OT_SERVICE,
OT_PROVIDER,
OT_USER,
OT_GROUP,
OT_AUTHENTICATOR,
OT_METAPOOL,
) = range(
9
) # @UndefinedVariable
# Dict for translations
transDict: typing.Dict[typing.Type['Model'], int] = {
@ -65,6 +77,7 @@ class LogManager:
"""
Manager for logging (at database) events
"""
_manager: typing.Optional['LogManager'] = None
def __init__(self):
@ -76,7 +89,15 @@ class LogManager:
LogManager._manager = LogManager()
return LogManager._manager
def __log(self, owner_type: int, owner_id: int, level: int, message: str, source: str, avoidDuplicates: bool):
def __log(
self,
owner_type: int,
owner_id: int,
level: int,
message: str,
source: str,
avoidDuplicates: bool,
):
"""
Logs a message associated to owner
"""
@ -86,12 +107,16 @@ class LogManager:
qs = models.Log.objects.filter(owner_id=owner_id, owner_type=owner_type)
# First, ensure we do not have more than requested logs, and we can put one more log item
if qs.count() >= GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt():
for i in qs.order_by('-created',)[GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt() - 1:]:
for i in qs.order_by(
'-created',
)[GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt() - 1 :]:
i.delete()
if avoidDuplicates:
try:
lg = models.Log.objects.filter(owner_id=owner_id, owner_type=owner_type, level=level, source=source).order_by('-created', '-id')[0]
lg = models.Log.objects.filter(
owner_id=owner_id, owner_type=owner_type, level=level, source=source
).order_by('-created', '-id')[0]
if lg.data == message:
# Do not log again, already logged
return
@ -100,17 +125,29 @@ class LogManager:
# now, we add new log
try:
models.Log.objects.create(owner_type=owner_type, owner_id=owner_id, created=models.getSqlDatetime(), source=source, level=level, data=message)
models.Log.objects.create(
owner_type=owner_type,
owner_id=owner_id,
created=models.getSqlDatetime(),
source=source,
level=level,
data=message,
)
except Exception:
# Some objects will not get logged, such as System administrator objects, but this is fine
pass
def __getLogs(self, owner_type: int, owner_id: int, limit: int) -> typing.List[typing.Dict]:
def __getLogs(
self, owner_type: int, owner_id: int, limit: int
) -> typing.List[typing.Dict]:
"""
Get all logs associated with an user service, ordered by date
"""
qs = models.Log.objects.filter(owner_id=owner_id, owner_type=owner_type)
return [{'date': x.created, 'level': x.level, 'source': x.source, 'message': x.data} for x in reversed(qs.order_by('-created', '-id')[:limit])]
return [
{'date': x.created, 'level': x.level, 'source': x.source, 'message': x.data}
for x in reversed(qs.order_by('-created', '-id')[:limit])
]
def __clearLogs(self, owner_type: int, owner_id: int):
"""
@ -118,7 +155,14 @@ class LogManager:
"""
models.Log.objects.filter(owner_id=owner_id, owner_type=owner_type).delete()
def doLog(self, wichObject: 'Model', level: int, message: str, source: str, avoidDuplicates: bool = True):
def doLog(
self,
wichObject: 'Model',
level: int,
message: str,
source: str,
avoidDuplicates: bool = True,
):
"""
Do the logging for the requested object.
@ -129,7 +173,9 @@ class LogManager:
if owner_type is not None:
self.__log(owner_type, wichObject.id, level, message, source, avoidDuplicates) # type: ignore
else:
logger.debug('Requested doLog for a type of object not covered: %s', wichObject)
logger.debug(
'Requested doLog for a type of object not covered: %s', wichObject
)
def getLogs(self, wichObject: 'Model', limit: int) -> typing.List[typing.Dict]:
"""
@ -142,7 +188,9 @@ class LogManager:
if owner_type is not None: # 0 is valid owner type
return self.__getLogs(owner_type, wichObject.id, limit) # type: ignore
logger.debug('Requested getLogs for a type of object not covered: %s', wichObject)
logger.debug(
'Requested getLogs for a type of object not covered: %s', wichObject
)
return []
def clearLogs(self, wichObject: 'Model'):
@ -156,4 +204,6 @@ class LogManager:
if owner_type:
self.__clearLogs(owner_type, wichObject.id) # type: ignore
else:
logger.debug('Requested clearLogs for a type of object not covered: %s', wichObject)
logger.debug(
'Requested clearLogs for a type of object not covered: %s', wichObject
)

View File

@ -64,13 +64,19 @@ class PublicationOldMachinesCleaner(DelayedTask):
def run(self):
try:
servicePoolPub: ServicePoolPublication = ServicePoolPublication.objects.get(pk=self._id)
servicePoolPub: ServicePoolPublication = ServicePoolPublication.objects.get(
pk=self._id
)
if servicePoolPub.state != State.REMOVABLE:
logger.info('Already removed')
now = getSqlDatetime()
activePub: typing.Optional[ServicePoolPublication] = servicePoolPub.deployed_service.activePublication()
servicePoolPub.deployed_service.userServices.filter(in_use=True).update(in_use=False, state_date=now)
activePub: typing.Optional[
ServicePoolPublication
] = servicePoolPub.deployed_service.activePublication()
servicePoolPub.deployed_service.userServices.filter(in_use=True).update(
in_use=False, state_date=now
)
servicePoolPub.deployed_service.markOldUserServicesAsRemovables(activePub)
except Exception:
pass
@ -92,8 +98,12 @@ class PublicationLauncher(DelayedTask):
try:
now = getSqlDatetime()
with transaction.atomic():
servicePoolPub = ServicePoolPublication.objects.select_for_update().get(pk=self._publicationId)
if servicePoolPub.state != State.LAUNCHING: # If not preparing (may has been canceled by user) just return
servicePoolPub = ServicePoolPublication.objects.select_for_update().get(
pk=self._publicationId
)
if (
servicePoolPub.state != State.LAUNCHING
): # If not preparing (may has been canceled by user) just return
return
servicePoolPub.state = State.PREPARING
servicePoolPub.save()
@ -101,7 +111,15 @@ class PublicationLauncher(DelayedTask):
state = pi.publish()
servicePool: ServicePool = servicePoolPub.deployed_service
servicePool.current_pub_revision += 1
servicePool.storeValue('toBeReplacedIn', pickle.dumps(now + datetime.timedelta(hours=GlobalConfig.SESSION_EXPIRE_TIME.getInt(True))))
servicePool.storeValue(
'toBeReplacedIn',
pickle.dumps(
now
+ datetime.timedelta(
hours=GlobalConfig.SESSION_EXPIRE_TIME.getInt(True)
)
),
)
servicePool.save()
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pi, state)
except ServicePoolPublication.DoesNotExist: # Deployed service publication has been removed from database, this is ok, just ignore it
@ -128,7 +146,11 @@ class PublicationFinishChecker(DelayedTask):
self._state = publication.state
@staticmethod
def checkAndUpdateState(publication: ServicePoolPublication, publicationInstance: 'services.Publication', state: str) -> None:
def checkAndUpdateState(
publication: ServicePoolPublication,
publicationInstance: 'services.Publication',
state: str,
) -> None:
"""
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
Return True if it has to continue checking, False if finished
@ -140,20 +162,33 @@ class PublicationFinishChecker(DelayedTask):
# Now we mark, if it exists, the previous usable publication as "Removable"
if State.isPreparing(prevState):
old: ServicePoolPublication
for old in publication.deployed_service.publications.filter(state=State.USABLE):
for old in publication.deployed_service.publications.filter(
state=State.USABLE
):
old.setState(State.REMOVABLE)
osm = publication.deployed_service.osmanager
# If os manager says "machine is persistent", do not tray to delete "previous version" assigned machines
doPublicationCleanup = True if osm is None else not osm.getInstance().isPersistent()
doPublicationCleanup = (
True
if osm is None
else not osm.getInstance().isPersistent()
)
if doPublicationCleanup:
pc = PublicationOldMachinesCleaner(old.id)
pc.register(GlobalConfig.SESSION_EXPIRE_TIME.getInt(True) * 3600, 'pclean-' + str(old.id), True)
publication.deployed_service.markOldUserServicesAsRemovables(publication)
pc.register(
GlobalConfig.SESSION_EXPIRE_TIME.getInt(True) * 3600,
'pclean-' + str(old.id),
True,
)
publication.deployed_service.markOldUserServicesAsRemovables(
publication
)
else: # Remove only cache services, not assigned
publication.deployed_service.markOldUserServicesAsRemovables(publication, True)
publication.deployed_service.markOldUserServicesAsRemovables(
publication, True
)
publication.setState(State.USABLE)
elif State.isRemoving(prevState):
@ -178,36 +213,53 @@ class PublicationFinishChecker(DelayedTask):
PublicationFinishChecker.checkLater(publication, publicationInstance)
@staticmethod
def checkLater(publication: ServicePoolPublication, publicationInstance: 'services.Publication'):
def checkLater(
publication: ServicePoolPublication, publicationInstance: 'services.Publication'
):
"""
Inserts a task in the delayedTaskRunner so we can check the state of this publication
@param dps: Database object for ServicePoolPublication
@param pi: Instance of Publication manager for the object
"""
DelayedTaskRunner.runner().insert(PublicationFinishChecker(publication), publicationInstance.suggestedTime, PUBTAG + str(publication.id))
DelayedTaskRunner.runner().insert(
PublicationFinishChecker(publication),
publicationInstance.suggestedTime,
PUBTAG + str(publication.id),
)
def run(self):
logger.debug('Checking publication finished %s', self._publishId)
try:
publication: ServicePoolPublication = ServicePoolPublication.objects.get(pk=self._publishId)
publication: ServicePoolPublication = ServicePoolPublication.objects.get(
pk=self._publishId
)
if publication.state != self._state:
logger.debug('Task overrided by another task (state of item changed)')
else:
publicationInstance = publication.getInstance()
logger.debug("publication instance class: %s", publicationInstance.__class__)
logger.debug(
"publication instance class: %s", publicationInstance.__class__
)
try:
state = publicationInstance.checkState()
except Exception:
state = State.ERROR
PublicationFinishChecker.checkAndUpdateState(publication, publicationInstance, state)
PublicationFinishChecker.checkAndUpdateState(
publication, publicationInstance, state
)
except Exception as e:
logger.debug('Deployed service not found (erased from database) %s : %s', e.__class__, e)
logger.debug(
'Deployed service not found (erased from database) %s : %s',
e.__class__,
e,
)
class PublicationManager:
"""
Manager responsible of controlling publications
"""
_manager: typing.Optional['PublicationManager'] = None
def __init__(self):
@ -222,26 +274,43 @@ class PublicationManager:
PublicationManager._manager = PublicationManager()
return PublicationManager._manager
def publish(self, servicePool: ServicePool, changeLog: typing.Optional[str] = None): # pylint: disable=no-self-use
def publish(
self, servicePool: ServicePool, changeLog: typing.Optional[str] = None
): # pylint: disable=no-self-use
"""
Initiates the publication of a service pool, or raises an exception if this cannot be done
:param servicePool: Service pool object (db object)
:param changeLog: if not None, store change log string on "change log" table
"""
if servicePool.publications.filter(state__in=State.PUBLISH_STATES).count() > 0:
raise PublishException(_('Already publishing. Wait for previous publication to finish and try again'))
raise PublishException(
_(
'Already publishing. Wait for previous publication to finish and try again'
)
)
if servicePool.isInMaintenance():
raise PublishException(_('Service is in maintenance mode and new publications are not allowed'))
raise PublishException(
_('Service is in maintenance mode and new publications are not allowed')
)
publication: typing.Optional[ServicePoolPublication] = None
try:
now = getSqlDatetime()
publication = servicePool.publications.create(state=State.LAUNCHING, state_date=now, publish_date=now, revision=servicePool.current_pub_revision)
publication = servicePool.publications.create(
state=State.LAUNCHING,
state_date=now,
publish_date=now,
revision=servicePool.current_pub_revision,
)
if changeLog:
servicePool.changelog.create(revision=servicePool.current_pub_revision, log=changeLog, stamp=now)
servicePool.changelog.create(
revision=servicePool.current_pub_revision, log=changeLog, stamp=now
)
if publication:
DelayedTaskRunner.runner().insert(PublicationLauncher(publication), 4, PUBTAG + str(publication.id))
DelayedTaskRunner.runner().insert(
PublicationLauncher(publication), 4, PUBTAG + str(publication.id)
)
except Exception as e:
logger.debug('Caught exception at publish: %s', e)
if publication is not None:
@ -251,17 +320,26 @@ class PublicationManager:
logger.info('Could not delete %s', publication)
raise PublishException(str(e))
def cancel(self, publication: ServicePoolPublication): # pylint: disable=no-self-use
def cancel(
self, publication: ServicePoolPublication
): # pylint: disable=no-self-use
"""
Invoked to cancel a publication.
Double invokation (i.e. invokation over a "cancelling" item) will lead to a "forced" cancellation (unclean)
:param servicePoolPub: Service pool publication (db object for a publication)
"""
publication = ServicePoolPublication.objects.get(pk=publication.id) # Reloads publication from db
publication = ServicePoolPublication.objects.get(
pk=publication.id
) # Reloads publication from db
if publication.state not in State.PUBLISH_STATES:
if publication.state == State.CANCELING: # Double cancel
logger.info('Double cancel invoked for a publication')
log.doLog(publication.deployed_service, log.WARN, 'Forced cancel on publication, you must check uncleaned resources manually', log.ADMIN)
log.doLog(
publication.deployed_service,
log.WARN,
'Forced cancel on publication, you must check uncleaned resources manually',
log.ADMIN,
)
publication.setState(State.CANCELED)
publication.save()
return publication
@ -277,25 +355,35 @@ class PublicationManager:
pubInstance = publication.getInstance()
state = pubInstance.cancel()
publication.setState(State.CANCELING)
PublicationFinishChecker.checkAndUpdateState(publication, pubInstance, state)
PublicationFinishChecker.checkAndUpdateState(
publication, pubInstance, state
)
return publication
except Exception as e:
raise PublishException(str(e))
def unpublish(self, servicePoolPub: ServicePoolPublication): # pylint: disable=no-self-use
def unpublish(
self, servicePoolPub: ServicePoolPublication
): # pylint: disable=no-self-use
"""
Unpublishes an active (usable) or removable publication
:param servicePoolPub: Publication to unpublish
"""
if State.isUsable(servicePoolPub.state) is False and State.isRemovable(servicePoolPub.state) is False:
raise PublishException(_('Can\'t unpublish non usable publication')
)
if (
State.isUsable(servicePoolPub.state) is False
and State.isRemovable(servicePoolPub.state) is False
):
raise PublishException(_('Can\'t unpublish non usable publication'))
if servicePoolPub.userServices.exclude(state__in=State.INFO_STATES).count() > 0:
raise PublishException(_('Can\'t unpublish publications with services in process'))
raise PublishException(
_('Can\'t unpublish publications with services in process')
)
try:
pubInstance = servicePoolPub.getInstance()
state = pubInstance.destroy()
servicePoolPub.setState(State.REMOVING)
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pubInstance, state)
PublicationFinishChecker.checkAndUpdateState(
servicePoolPub, pubInstance, state
)
except Exception as e:
raise PublishException(str(e))

View File

@ -52,6 +52,7 @@ REVERSE_FLDS_EQUIV: typing.Mapping[str, str] = {
i: fld for fld, aliases in FLDS_EQUIV.items() for i in aliases
}
class StatsManager:
"""
Manager for statistics, so we can provide usefull info about platform usage
@ -60,6 +61,7 @@ class StatsManager:
that has counters (such as how many users is at a time active at platform, how many services
are assigned, are in use, in cache, etc...
"""
_manager: typing.Optional['StatsManager'] = None
def __init__(self):
@ -72,11 +74,23 @@ class StatsManager:
return StatsManager._manager
def __doCleanup(self, model):
minTime = time.mktime((getSqlDatetime() - datetime.timedelta(days=GlobalConfig.STATS_DURATION.getInt())).timetuple())
minTime = time.mktime(
(
getSqlDatetime()
- datetime.timedelta(days=GlobalConfig.STATS_DURATION.getInt())
).timetuple()
)
model.objects.filter(stamp__lt=minTime).delete()
# Counter stats
def addCounter(self, owner_type: int, owner_id: int, counterType: int, counterValue: int, stamp: typing.Optional[datetime.datetime] = None) -> bool:
def addCounter(
self,
owner_type: int,
owner_id: int,
counterType: int,
counterValue: int,
stamp: typing.Optional[datetime.datetime] = None,
) -> bool:
"""
Adds a new counter stats to database.
@ -97,13 +111,23 @@ class StatsManager:
stamp = typing.cast(datetime.datetime, getSqlDatetime())
# To Unix epoch
stampInt = int(time.mktime(stamp.timetuple())) # pylint: disable=maybe-no-member
stampInt = int(
time.mktime(stamp.timetuple())
) # pylint: disable=maybe-no-member
try:
StatsCounters.objects.create(owner_type=owner_type, owner_id=owner_id, counter_type=counterType, value=counterValue, stamp=stampInt)
StatsCounters.objects.create(
owner_type=owner_type,
owner_id=owner_id,
counter_type=counterType,
value=counterValue,
stamp=stampInt,
)
return True
except Exception:
logger.error('Exception handling counter stats saving (maybe database is full?)')
logger.error(
'Exception handling counter stats saving (maybe database is full?)'
)
return False
def getCounters(
@ -116,7 +140,7 @@ class StatsManager:
interval: typing.Optional[int],
max_intervals: typing.Optional[int],
limit: typing.Optional[int],
use_max: bool = False
use_max: bool = False,
) -> typing.Iterable:
"""
Retrieves counters from item
@ -146,7 +170,7 @@ class StatsManager:
interval=interval,
max_intervals=max_intervals,
limit=limit,
use_max=use_max
use_max=use_max,
)
def cleanupCounters(self):
@ -183,9 +207,12 @@ class StatsManager:
stamp = getSqlDatetimeAsUnix()
else:
# To Unix epoch
stamp = int(time.mktime(stamp.timetuple())) # pylint: disable=maybe-no-member
stamp = int(
time.mktime(stamp.timetuple())
) # pylint: disable=maybe-no-member
try:
def getKwarg(fld: str) -> str:
val = None
for i in FLDS_EQUIV[fld]:
@ -199,13 +226,29 @@ class StatsManager:
fld3 = getKwarg('fld3')
fld4 = getKwarg('fld4')
StatsEvents.objects.create(owner_type=owner_type, owner_id=owner_id, event_type=eventType, stamp=stamp, fld1=fld1, fld2=fld2, fld3=fld3, fld4=fld4)
StatsEvents.objects.create(
owner_type=owner_type,
owner_id=owner_id,
event_type=eventType,
stamp=stamp,
fld1=fld1,
fld2=fld2,
fld3=fld3,
fld4=fld4,
)
return True
except Exception:
logger.exception('Exception handling event stats saving (maybe database is full?)')
logger.exception(
'Exception handling event stats saving (maybe database is full?)'
)
return False
def getEvents(self, ownerType: typing.Union[int, typing.Iterable[int]], eventType: typing.Union[int, typing.Iterable[int]], **kwargs):
def getEvents(
self,
ownerType: typing.Union[int, typing.Iterable[int]],
eventType: typing.Union[int, typing.Iterable[int]],
**kwargs
):
"""
Retrieves counters from item

View File

@ -44,10 +44,12 @@ from uds.core.util.config import GlobalConfig
logger = logging.getLogger(__name__)
class BaseThread(threading.Thread):
def notifyTermination(self):
raise NotImplementedError
class SchedulerThread(BaseThread):
def run(self):
Scheduler.scheduler().run()
@ -109,7 +111,9 @@ class TaskManager:
noSchedulers: int = GlobalConfig.SCHEDULER_THREADS.getInt()
noDelayedTasks: int = GlobalConfig.DELAYED_TASKS_THREADS.getInt()
logger.info('Starting %s schedulers and %s task executors', noSchedulers, noDelayedTasks)
logger.info(
'Starting %s schedulers and %s task executors', noSchedulers, noDelayedTasks
)
threads: typing.List[BaseThread] = []
thread: BaseThread

View File

@ -61,7 +61,9 @@ class UserPrefsManager:
def __nameFor(self, module, name):
return module + "_" + name
def registerPrefs(self, modName: str, friendlyModName: str, prefs: typing.Any) -> None:
def registerPrefs(
self, modName: str, friendlyModName: str, prefs: typing.Any
) -> None:
"""
Register an array of preferences for a module
"""
@ -81,7 +83,9 @@ class UserPrefsManager:
logger.debug('Preferences: %s', prefs)
return prefs
def setPreferenceForUser(self, user: 'User', modName: str, prefName: str, value: str):
def setPreferenceForUser(
self, user: 'User', modName: str, prefName: str, value: str
):
try:
user.preferences.create(module=modName, name=prefName, value=value) # type: ignore
except Exception: # Already exits, update it
@ -99,7 +103,13 @@ class UserPrefsManager:
name = self.__nameFor(mod, p.getName())
val = data[name] if name in data else p.getDefValue()
form.fields[name] = p.formField(val)
res += '<fieldset class="prefset"><legend>' + v['friendlyName'] + '</legend>' + form.as_p() + '</fieldset>'
res += (
'<fieldset class="prefset"><legend>'
+ v['friendlyName']
+ '</legend>'
+ form.as_p()
+ '</fieldset>'
)
return res
def getGuiForUserPreferences(self, user=None):
@ -113,7 +123,13 @@ class UserPrefsManager:
for p in v['prefs']:
name = self.__nameFor(mod, p.getName())
val = data[name] if name in data else p.getDefValue()
grp.append({'name': name, 'gui': p.guiField(val).guiDescription(), 'value': val})
grp.append(
{
'name': name,
'gui': p.guiField(val).guiDescription(),
'value': val,
}
)
res.append({'moduleLabel': v['friendlyName'], 'prefs': grp})
return res
@ -136,11 +152,19 @@ class UserPrefsManager:
for p in v['prefs']:
name = self.__nameFor(mod, p.getName())
logger.debug(name)
prefs.append({'module': mod, 'name': p.getName(), 'value': form.cleaned_data[name]})
prefs.append(
{
'module': mod,
'name': p.getName(),
'value': form.cleaned_data[name],
}
)
user.preferences.all().delete()
try:
for p in prefs:
user.preferences.create(module=p['module'], name=p['name'], value=p['value'])
user.preferences.create(
module=p['module'], name=p['name'], value=p['value']
)
except Exception: # User does not exists
logger.info('Trying to dave user preferences failed (probably root user?)')
return None
@ -156,10 +180,14 @@ class UserPrefsManager:
for p in v['prefs']:
name = self.__nameFor(mod, p.getName())
if name in data:
prefs.append({'module': mod, 'name': p.getName(), 'value': data[name]})
prefs.append(
{'module': mod, 'name': p.getName(), 'value': data[name]}
)
user.preferences.all().delete()
for p in prefs:
user.preferences.create(module=p['module'], name=p['name'], value=p['value'])
user.preferences.create(
module=p['module'], name=p['name'], value=p['value']
)
class UserPreference(object):
@ -190,7 +218,6 @@ class UserPreference(object):
return None
class CommonPrefs(object):
SZ_PREF = 'screenSize'
SZ_640x480 = '1'
@ -219,7 +246,7 @@ class CommonPrefs(object):
CommonPrefs.SZ_1024x768: (1024, 768),
CommonPrefs.SZ_1366x768: (1366, 768),
CommonPrefs.SZ_1920x1080: (1920, 1080),
CommonPrefs.SZ_FULLSCREEN: (-1, -1)
CommonPrefs.SZ_FULLSCREEN: (-1, -1),
}.get(size, (1024, 768))
@staticmethod
@ -231,5 +258,5 @@ class CommonPrefs(object):
CommonPrefs.DEPTH_8: 8,
CommonPrefs.DEPTH_16: 16,
CommonPrefs.DEPTH_24: 24,
CommonPrefs.DEPTH_32: 32
CommonPrefs.DEPTH_32: 32,
}.get(depth, 24)

View File

@ -928,14 +928,18 @@ class UserServiceManager:
raise ServiceAccessDeniedByCalendar()
# Get pool members. Just pools "visible" and "usable"
poolMembers = [p for p in meta.members.all() if p.pool.isVisible() and p.pool.isUsable()]
poolMembers = [
p for p in meta.members.all() if p.pool.isVisible() and p.pool.isUsable()
]
# Sort pools based on meta selection
if meta.policy == MetaPool.PRIORITY_POOL:
sortPools = [(p.priority, p.pool) for p in poolMembers]
elif meta.policy == MetaPool.MOST_AVAILABLE_BY_NUMBER:
sortPools = [(p.pool.usage(), p.pool) for p in poolMembers]
else:
sortPools = [(random.randint(0, 10000), p.pool) for p in poolMembers] # Just shuffle them
sortPools = [
(random.randint(0, 10000), p.pool) for p in poolMembers
] # Just shuffle them
# Sort pools related to policy now, and xtract only pools, not sort keys
# Remove "full" pools (100%) from result and pools in maintenance mode, not ready pools, etc...

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019-2020 Virtual Cable S.L.
# Copyright (c) 2019-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -11,7 +11,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -38,7 +38,7 @@ import typing
import requests
if typing.TYPE_CHECKING:
from uds.models import UserService, Proxy
from uds.models import UserService
logger = logging.getLogger(__name__)

View File

@ -51,9 +51,17 @@ class StateUpdater:
userService: UserService
userServiceInstalce: UserDeployment
def __init__(self, userService: UserService, userServiceInstance: typing.Optional[UserDeployment] = None):
def __init__(
self,
userService: UserService,
userServiceInstance: typing.Optional[UserDeployment] = None,
):
self.userService = userService
self.userServiceInstance = userServiceInstance if userServiceInstance is not None else userService.getInstance()
self.userServiceInstance = (
userServiceInstance
if userServiceInstance is not None
else userService.getInstance()
)
def setError(self, msg: typing.Optional[str] = None):
logger.error('Got error on processor: %s', msg)
@ -80,10 +88,15 @@ class StateUpdater:
executor = {
State.RUNNING: self.running,
State.ERROR: self.error,
State.FINISHED: self.finish
State.FINISHED: self.finish,
}.get(state, self.error)
logger.debug('Running Executor for %s with state %s and executor %s', self.userService.friendly_name, State.toString(state), executor)
logger.debug(
'Running Executor for %s with state %s and executor %s',
self.userService.friendly_name,
State.toString(state),
executor,
)
try:
executor()
@ -104,7 +117,6 @@ class StateUpdater:
class UpdateFromPreparing(StateUpdater):
def checkOsManagerRelated(self) -> str:
osManager = self.userServiceInstance.osmanager()
@ -120,7 +132,12 @@ class UpdateFromPreparing(StateUpdater):
else:
stateOs = State.FINISHED
logger.debug('State %s, StateOS %s for %s', State.toString(state), State.toString(stateOs), self.userService.friendly_name)
logger.debug(
'State %s, StateOS %s for %s',
State.toString(state),
State.toString(stateOs),
self.userService.friendly_name,
)
if stateOs == State.RUNNING:
self.userService.setOsState(State.PREPARING)
else:
@ -130,7 +147,9 @@ class UpdateFromPreparing(StateUpdater):
rs = self.userServiceInstance.notifyReadyFromOsManager('')
if rs != State.FINISHED:
self.checkLater()
state = self.userService.state # No not alter current state if after notifying os manager the user service keeps working
state = (
self.userService.state
) # No not alter current state if after notifying os manager the user service keeps working
else:
self.logIp()
@ -142,7 +161,9 @@ class UpdateFromPreparing(StateUpdater):
self.save(State.REMOVABLE) # And start removing it
return
state = State.REMOVABLE # By default, if not valid publication, service will be marked for removal on preparation finished
state = (
State.REMOVABLE
) # By default, if not valid publication, service will be marked for removal on preparation finished
if self.userService.isValidPublication():
logger.debug('Publication is valid for %s', self.userService.friendly_name)
state = self.checkOsManagerRelated()
@ -153,6 +174,7 @@ class UpdateFromPreparing(StateUpdater):
self.save(state)
class UpdateFromRemoving(StateUpdater):
def finish(self):
osManager = self.userServiceInstance.osmanager()
@ -161,6 +183,7 @@ class UpdateFromRemoving(StateUpdater):
self.save(State.REMOVED)
class UpdateFromCanceling(StateUpdater):
def finish(self):
osManager = self.userServiceInstance.osmanager()
@ -169,50 +192,76 @@ class UpdateFromCanceling(StateUpdater):
self.save(State.CANCELED)
class UpdateFromOther(StateUpdater):
def finish(self):
self.setError('Unknown running transition from {}'.format(State.toString(self.userService.state)))
self.setError(
'Unknown running transition from {}'.format(
State.toString(self.userService.state)
)
)
def running(self):
self.setError('Unknown running transition from {}'.format(State.toString(self.userService.state)))
self.setError(
'Unknown running transition from {}'.format(
State.toString(self.userService.state)
)
)
class UserServiceOpChecker(DelayedTask):
"""
This is the delayed task responsible of executing the service tasks and the service state transitions
"""
def __init__(self, service):
super().__init__()
self._svrId = service.id
self._state = service.state
@staticmethod
def makeUnique(userService: UserService, userServiceInstance: UserDeployment, state: str):
def makeUnique(
userService: UserService, userServiceInstance: UserDeployment, state: str
):
"""
This method ensures that there will be only one delayedtask related to the userService indicated
"""
DelayedTaskRunner.runner().remove(USERSERVICE_TAG + userService.uuid)
UserServiceOpChecker.checkAndUpdateState(userService, userServiceInstance, state)
UserServiceOpChecker.checkAndUpdateState(
userService, userServiceInstance, state
)
@staticmethod
def checkAndUpdateState(userService: UserService, userServiceInstance: UserDeployment, state: str):
def checkAndUpdateState(
userService: UserService, userServiceInstance: UserDeployment, state: str
):
"""
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
Return True if it has to continue checking, False if finished
"""
try:
# Fills up basic data
userService.unique_id = userServiceInstance.getUniqueId() # Updates uniqueId
userService.friendly_name = userServiceInstance.getName() # And name, both methods can modify serviceInstance, so we save it later
userService.unique_id = (
userServiceInstance.getUniqueId()
) # Updates uniqueId
userService.friendly_name = (
userServiceInstance.getName()
) # And name, both methods can modify serviceInstance, so we save it later
userService.save(update_fields=['unique_id', 'friendly_name'])
updater = {
State.PREPARING: UpdateFromPreparing,
State.REMOVING: UpdateFromRemoving,
State.CANCELING: UpdateFromCanceling
State.CANCELING: UpdateFromCanceling,
}.get(userService.state, UpdateFromOther)
logger.debug('Updating %s from %s with updater %s and state %s', userService.friendly_name, State.toString(userService.state), updater, state)
logger.debug(
'Updating %s from %s with updater %s and state %s',
userService.friendly_name,
State.toString(userService.state),
updater,
state,
)
updater(userService, userServiceInstance).run(state)
@ -232,7 +281,11 @@ class UserServiceOpChecker(DelayedTask):
# Do not add task if already exists one that updates this service
if DelayedTaskRunner.runner().checkExists(USERSERVICE_TAG + userService.uuid):
return
DelayedTaskRunner.runner().insert(UserServiceOpChecker(userService), ci.suggestedTime, USERSERVICE_TAG + userService.uuid)
DelayedTaskRunner.runner().insert(
UserServiceOpChecker(userService),
ci.suggestedTime,
USERSERVICE_TAG + userService.uuid,
)
def run(self):
logger.debug('Checking user service finished %s', self._svrId)
@ -248,14 +301,16 @@ class UserServiceOpChecker(DelayedTask):
state = ci.checkState()
UserServiceOpChecker.checkAndUpdateState(uService, ci, state)
except UserService.DoesNotExist as e:
logger.error('User service not found (erased from database?) %s : %s', e.__class__, e)
logger.error(
'User service not found (erased from database?) %s : %s', e.__class__, e
)
except Exception as e:
# Exception caught, mark service as errored
logger.exception("Error %s, %s :", e.__class__, e)
if uService is not None:
if uService:
log.doLog(uService, log.ERROR, 'Exception: {}'.format(e), log.INTERNAL)
try:
uService.setState(State.ERROR)
uService.save(update_fields=['data', 'state', 'state_date'])
except Exception:
logger.error('Can\'t update state of uService object')
try:
uService.setState(State.ERROR)
uService.save(update_fields=['data', 'state', 'state_date'])
except Exception:
logger.error('Can\'t update state of uService object')

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -40,4 +40,5 @@ def factory():
Returns factory for register/access to authenticators
"""
from .osmfactory import OSManagersFactory
return OSManagersFactory.factory()

View File

@ -34,7 +34,6 @@ body {
background-attachment: fixed;
background-size: 80%;
background-position: center center;
background-blend-mode:
}
h1 { bookmark-level: none; }

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -101,9 +101,11 @@ class ServiceNotReadyError(ServiceException):
The service is not ready
Can include an optional code error
"""
code: int
userService: typing.Optional['UserService']
transport: typing.Optional['Transport']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.code = kwargs.get('code', 0x0000)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -147,7 +147,12 @@ class ServiceProvider(Module):
return _type
return None
def __init__(self, environment: Environment, values: 'Module.ValuesType' = None, uuid: typing.Optional[str] = None):
def __init__(
self,
environment: Environment,
values: 'Module.ValuesType' = None,
uuid: typing.Optional[str] = None,
):
"""
Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, values)"
if you override this method. Better is to provide an "__initialize__" method, that will be invoked
@ -177,7 +182,11 @@ class ServiceProvider(Module):
def getMaxPreparingServices(self) -> int:
val = self.maxPreparingServices
if val is None:
val = self.maxPreparingServices = GlobalConfig.MAX_PREPARING_SERVICES.getInt(force=True) # Recover global an cache till restart
val = (
self.maxPreparingServices
) = GlobalConfig.MAX_PREPARING_SERVICES.getInt(
force=True
) # Recover global an cache till restart
if isinstance(val, gui.InputField):
retVal = int(val.value)
@ -188,7 +197,9 @@ class ServiceProvider(Module):
def getMaxRemovingServices(self) -> int:
val = self.maxRemovingServices
if val is None:
val = self.maxRemovingServices = GlobalConfig.MAX_REMOVING_SERVICES.getInt(force=True) # Recover global an cache till restart
val = self.maxRemovingServices = GlobalConfig.MAX_REMOVING_SERVICES.getInt(
force=True
) # Recover global an cache till restart
if isinstance(val, gui.InputField):
retVal = int(val.value)
@ -199,7 +210,9 @@ class ServiceProvider(Module):
def getIgnoreLimits(self) -> bool:
val = self.ignoreLimits
if val is None:
val = self.ignoreLimits = GlobalConfig.IGNORE_LIMITS.getBool(force=True) # Recover global an cache till restart
val = self.ignoreLimits = GlobalConfig.IGNORE_LIMITS.getBool(
force=True
) # Recover global an cache till restart
val = getattr(val, 'value', val)
return val is True or val == gui.TRUE
@ -209,8 +222,11 @@ class ServiceProvider(Module):
Logs a message with requested level associated with this service
"""
from uds.models import Provider as DBProvider
if self.getUuid():
log.doLog(DBProvider.objects.get(uuid=self.getUuid()), level, message, log.SERVICE)
log.doLog(
DBProvider.objects.get(uuid=self.getUuid()), level, message, log.SERVICE
)
def __str__(self):
"""

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -46,6 +46,7 @@ class ServiceProviderFactory:
It provides a way to register and recover providers providers.
"""
_factory: typing.Optional['ServiceProviderFactory'] = None
def __init__(self):
@ -89,7 +90,12 @@ class ServiceProviderFactory:
if s.usesCache_L2:
s.usesCache = True
if s.publicationType is None:
logger.error('Provider %s offers %s, but %s needs cache and do not have publicationType defined', type_, s, s)
logger.error(
'Provider %s offers %s, but %s needs cache and do not have publicationType defined',
type_,
s,
s,
)
continue
offers.append(s)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -98,7 +98,9 @@ class Publication(Environmentable, Serializable):
Environmentable.__init__(self, environment)
Serializable.__init__(self)
self._osManager = kwargs.get('osManager', None)
self._service = kwargs['service'] # Raises an exception if service is not included
self._service = kwargs[
'service'
] # Raises an exception if service is not included
self._revision = kwargs.get('revision', -1)
self._dbPublication = kwargs.get('dbPublication')
self._dsName = kwargs.get('dsName', 'Unknown')
@ -189,7 +191,11 @@ class Publication(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('publish method for class {0} not provided! '.format(self.__class__.__name__))
raise NotImplementedError(
'publish method for class {0} not provided! '.format(
self.__class__.__name__
)
)
def checkState(self) -> str:
"""
@ -215,7 +221,11 @@ class Publication(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('checkState method for class {0} not provided!!!'.format(self.__class__.__name__))
raise NotImplementedError(
'checkState method for class {0} not provided!!!'.format(
self.__class__.__name__
)
)
def finish(self) -> None:
"""
@ -259,7 +269,9 @@ class Publication(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('destroy method for class {0} not provided!'.format(self.__class__.__name__))
raise NotImplementedError(
'destroy method for class {0} not provided!'.format(self.__class__.__name__)
)
def cancel(self) -> str:
"""
@ -279,7 +291,9 @@ class Publication(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('cancel method for class {0} not provided!'.format(self.__class__.__name__))
raise NotImplementedError(
'cancel method for class {0} not provided!'.format(self.__class__.__name__)
)
def __str__(self):
"""

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.U.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -55,6 +55,7 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
class Service(Module):
"""
This class is in fact an interface, and represents a service, that is the
@ -138,7 +139,9 @@ class Service(Module):
usesCache = False
# : Tooltip to be used if services uses cache at administration interface, indicated by :py:attr:.usesCache
cacheTooltip = _('None') # : Tooltip shown to user when this item is pointed at admin interface
cacheTooltip = _(
'None'
) # : Tooltip shown to user when this item is pointed at admin interface
# : If user deployments can be cached (see :py:attr:.usesCache), may he also can provide a secondary cache,
# : that is no more that user deployments that are "almost ready" to be used, but preperably consumes less
@ -147,7 +150,9 @@ class Service(Module):
usesCache_L2 = False # : If we need to generate a "Level 2" cache for this service (i.e., L1 could be running machines and L2 suspended machines)
# : Tooltip to be used if services uses L2 cache at administration interface, indicated by :py:attr:.usesCache_L2
cacheTooltip_L2 = _('None') # : Tooltip shown to user when this item is pointed at admin interface
cacheTooltip_L2 = _(
'None'
) # : Tooltip shown to user when this item is pointed at admin interface
# : If the service needs a o.s. manager (see os managers section)
needsManager: bool = False
@ -196,7 +201,13 @@ class Service(Module):
_provider: 'services.ServiceProvider'
def __init__(self, environment, parent: 'services.ServiceProvider', values: Module.ValuesType = None, uuid: typing.Optional[str] = None):
def __init__(
self,
environment,
parent: 'services.ServiceProvider',
values: Module.ValuesType = None,
uuid: typing.Optional[str] = None,
):
"""
Do not forget to invoke this in your derived class using "super().__init__(environment, parent, values)".
We want to use the env, parent methods outside class. If not called, you must implement your own methods
@ -232,7 +243,9 @@ class Service(Module):
"""
return self._provider
def requestServicesForAssignation(self, **kwargs) -> typing.Iterable[UserDeployment]:
def requestServicesForAssignation(
self, **kwargs
) -> typing.Iterable[UserDeployment]:
"""
override this if mustAssignManualy is True
@params kwargs: Named arguments
@ -240,7 +253,11 @@ class Service(Module):
We will access the returned array in "name" basis. This means that the service will be assigned by "name", so be care that every single service
returned are not repeated... :-)
"""
raise Exception('The class {0} has been marked as manually asignable but no requestServicesForAssignetion provided!!!'.format(self.__class__.__name__))
raise Exception(
'The class {0} has been marked as manually asignable but no requestServicesForAssignetion provided!!!'.format(
self.__class__.__name__
)
)
def macGenerator(self) -> typing.Optional['UniqueMacGenerator']:
"""
@ -268,7 +285,9 @@ class Service(Module):
"""
return []
def assignFromAssignables(self, assignableId: str, user: 'models.User', userDeployment: UserDeployment) -> str:
def assignFromAssignables(
self, assignableId: str, user: 'models.User', userDeployment: UserDeployment
) -> str:
"""
Assigns from it internal assignable list to an user
@ -296,7 +315,7 @@ class Service(Module):
Looks for an "owned" id in the provided list. If found, returns it, else return None
Args:
idsList (typing.Iterable[str]): List of IPs and MACs that acts as
idsList (typing.Iterable[str]): List of IPs and MACs that acts as
Returns:
typing.Optional[str]: [description]
@ -340,7 +359,7 @@ class Service(Module):
def notifyInitialization(self, id: str) -> None:
"""
In the case that the startup of a "tokenized" method is invoked (unmanaged method of actor_v3 rest api),
this method is forwarded, so the tokenized method could take proper actions on a "known-to-be-free service"
this method is forwarded, so the tokenized method could take proper actions on a "known-to-be-free service"
Args:
id (str): Id validated through "getValidId"
@ -356,21 +375,26 @@ class Service(Module):
self.storage.delete('__nfo_' + id)
return value
def doLog(self, level: int, message: str) -> None:
"""
Logs a message with requested level associated with this service
"""
from uds.models import Service as DBService
if self.getUuid():
log.doLog(DBService.objects.get(uuid=self.getUuid()), level, message, log.SERVICE)
log.doLog(
DBService.objects.get(uuid=self.getUuid()), level, message, log.SERVICE
)
@classmethod
def canAssign(cls) -> bool:
"""
Helper to query if a class is custom (implements getJavascript method)
"""
return cls.listAssignables is not Service.listAssignables and cls.assignFromAssignables is not Service.assignFromAssignables
return (
cls.listAssignables is not Service.listAssignables
and cls.assignFromAssignables is not Service.assignFromAssignables
)
def __str__(self):
"""

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-20019 Virtual Cable S.L.U.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2019 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -46,7 +46,10 @@ if typing.TYPE_CHECKING:
from uds.core.util.unique_mac_generator import UniqueMacGenerator
from uds.core.util.unique_gid_generator import UniqueGIDGenerator
class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many-public-methods
class UserDeployment(
Environmentable, Serializable
): # pylint: disable=too-many-public-methods
"""
Interface for deployed services.
@ -109,6 +112,7 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
method reasonOfError can be called multiple times, including
serializations in middle, so remember to include reason of error in serializations
"""
L1_CACHE = 1 # : Constant for Cache of level 1
L2_CACHE = 2 # : Constant for Cache of level 2
@ -147,7 +151,9 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
"""
Environmentable.__init__(self, environment)
Serializable.__init__(self)
self._service = kwargs['service'] # Raises an exception if service is not included. Parent
self._service = kwargs[
'service'
] # Raises an exception if service is not included. Parent
self._publication = kwargs.get('publication', None)
self._osmanager = kwargs.get('osmanager', None)
self._dbService = kwargs.get('dbservice', None)
@ -383,7 +389,11 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
to the core. Take that into account and handle exceptions inside
this method.
"""
raise Exception('Base deploy for cache invoked! for class {0}'.format(self.__class__.__name__))
raise Exception(
'Base deploy for cache invoked! for class {0}'.format(
self.__class__.__name__
)
)
def deployForUser(self, user: 'models.User') -> str:
"""
@ -418,7 +428,11 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('Base deploy for user invoked! for class {0}'.format(self.__class__.__name__))
raise NotImplementedError(
'Base deploy for user invoked! for class {0}'.format(
self.__class__.__name__
)
)
def checkState(self) -> str:
"""
@ -443,7 +457,9 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('Base check state invoked! for class {0}'.format(self.__class__.__name__))
raise NotImplementedError(
'Base check state invoked! for class {0}'.format(self.__class__.__name__)
)
def finish(self) -> None:
"""
@ -549,7 +565,9 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('destroy method for class {0} not provided!'.format(self.__class__.__name__))
raise NotImplementedError(
'destroy method for class {0} not provided!'.format(self.__class__.__name__)
)
def cancel(self) -> str:
"""

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2019 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -44,6 +44,7 @@ ICA = 'ica'
NX = 'nx'
X11 = 'x11'
X2GO = 'x2go' # Based on NX
NICE = 'nice'
OTHER = 'other'
GENERIC = (RDP, RGS, VNC, NX, X11, X2GO, PCOIP, OTHER)
GENERIC = (RDP, RGS, VNC, NX, X11, X2GO, PCOIP, NICE, OTHER)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -61,6 +61,7 @@ class Transport(Module):
Server will iterate thought them and look for an identifier associated with the service. This list is a comma separated values (i.e. AA:BB:CC:DD:EE:FF,00:11:22:...)
Remember also that we inherit the test and check methods from BaseModule
"""
# Transport informational related data, inherited from BaseModule
typeName = 'Base Transport Manager'
typeType = 'Base Transport'
@ -110,8 +111,16 @@ class Transport(Module):
Invoked when Transport is deleted
"""
def testServer(self, userService: 'models.UserService', ip: str, port: typing.Union[str, int], timeout: int = 4) -> bool:
proxy: typing.Optional['models.Proxy'] = userService.deployed_service.service.proxy
def testServer(
self,
userService: 'models.UserService',
ip: str,
port: typing.Union[str, int],
timeout: int = 4,
) -> bool:
proxy: typing.Optional[
'models.Proxy'
] = userService.deployed_service.service.proxy
if proxy:
return proxy.doTestServer(ip, port, timeout)
return connection.testServer(ip, str(port), timeout)
@ -123,7 +132,9 @@ class Transport(Module):
"""
return False
def getCustomAvailableErrorMsg(self, userService: 'models.UserService', ip: str) -> str:
def getCustomAvailableErrorMsg(
self, userService: 'models.UserService', ip: str
) -> str:
"""
Returns a customized error message, that will be used when a service fails to check "isAvailableFor"
Override this in yours transports if needed
@ -156,11 +167,11 @@ class Transport(Module):
return cls.getConnectionInfo is not Transport.getConnectionInfo
def getConnectionInfo(
self,
userService: typing.Union['models.UserService', 'models.ServicePool'],
user: 'models.User',
password: str
) -> typing.Mapping[str, str]:
self,
userService: typing.Union['models.UserService', 'models.ServicePool'],
user: 'models.User',
password: str,
) -> typing.Mapping[str, str]:
"""
This method must provide information about connection.
We don't have to implement it, but if we wont to allow some types of connections
@ -186,7 +197,9 @@ class Transport(Module):
"""
return {'protocol': self.protocol, 'username': '', 'password': '', 'domain': ''}
def processedUser(self, userService: 'models.UserService', user: 'models.User') -> str:
def processedUser(
self, userService: 'models.UserService', user: 'models.User'
) -> str:
"""
Used to "transform" username that will be sent to service
This is used to make the "user" that will receive the service match with that sent in notification
@ -195,64 +208,75 @@ class Transport(Module):
return user.name
def getUDSTransportScript(
self,
userService: 'models.UserService',
transport: 'models.Transport',
ip: str,
os: typing.Dict[str, str],
user: 'models.User',
password: str,
request: 'HttpRequest'
) -> typing.Tuple[str, str, typing.Mapping[str, typing.Any]]:
self,
userService: 'models.UserService',
transport: 'models.Transport',
ip: str,
os: typing.Dict[str, str],
user: 'models.User',
password: str,
request: 'HttpRequest',
) -> typing.Tuple[str, str, typing.Mapping[str, typing.Any]]:
"""
If this is an uds transport, this will return the tranport script needed for executing
this on client
"""
return "raise Exception('The transport {transport} is not supported on your platform.'.format(transport=params['transport']))", \
'EH/91J7u9+/sHtB5+EUVRDW1+jqF0LuZzfRi8qxyIuSdJuWt'\
'8V8Yngu24p0NNr13TaxPQ1rpGN8x0NsU/Ma8k4GGohc+zxdf'\
'4xlkwMjAIytp8jaMHKkzvcihiIAMtaicP786FZCwGMmFTH4Z'\
'A9i7YWaSzT95h84kirAG67J0GWKiyATxs6mtxBNaLgqU4juA'\
'Qn98hYp5ffWa5FQDSAmheiDyQbCXMRwtWcxVHVQCAoZbsvCe'\
'njKc+FaeKNmXsYOgmcj+pz8IViNOyTbueP9u7lTzuBlIyV+7'\
'OlBPTqb5yA5wOBicKIpplPd8V71Oh3pdpRvdlvVbbwNfsCl5'\
'v6s1X20MxaQOSwM5z02eY1lJSbLIp8d9WRkfVty0HP/4Z8JZ'\
'kavkWNaGiKXEZXqojx/ZdzvTfvBkYrREQ8lMCIvtawBTysus'\
'IV4vHnDRdSmRxpYdj+1SNfzB0s1VuY6F7bSdBvgzja4P3Zbo'\
'Z63yNGuBhIsqUDA2ARmiMHRx9jr6eilFBKhoyWgNi9izTkar'\
'3iMYtXfvcFnmz4jvuJHUccbpUo4O31K2G7OaqlLylQ5dCu62'\
'JuVuquKKSfiwOIdYcdPJ6gvpgkQQDPqt7wN+duyZA0FI5F4h'\
'O6acQZmbjBCqZoo9Qsg7k9cTcalNkc5flEYAk1mULnddgDM6'\
'YGmoJgVnDr0=', {'transport': transport.name}
return (
"raise Exception('The transport {transport} is not supported on your platform.'.format(transport=params['transport']))",
'EH/91J7u9+/sHtB5+EUVRDW1+jqF0LuZzfRi8qxyIuSdJuWt'
'8V8Yngu24p0NNr13TaxPQ1rpGN8x0NsU/Ma8k4GGohc+zxdf'
'4xlkwMjAIytp8jaMHKkzvcihiIAMtaicP786FZCwGMmFTH4Z'
'A9i7YWaSzT95h84kirAG67J0GWKiyATxs6mtxBNaLgqU4juA'
'Qn98hYp5ffWa5FQDSAmheiDyQbCXMRwtWcxVHVQCAoZbsvCe'
'njKc+FaeKNmXsYOgmcj+pz8IViNOyTbueP9u7lTzuBlIyV+7'
'OlBPTqb5yA5wOBicKIpplPd8V71Oh3pdpRvdlvVbbwNfsCl5'
'v6s1X20MxaQOSwM5z02eY1lJSbLIp8d9WRkfVty0HP/4Z8JZ'
'kavkWNaGiKXEZXqojx/ZdzvTfvBkYrREQ8lMCIvtawBTysus'
'IV4vHnDRdSmRxpYdj+1SNfzB0s1VuY6F7bSdBvgzja4P3Zbo'
'Z63yNGuBhIsqUDA2ARmiMHRx9jr6eilFBKhoyWgNi9izTkar'
'3iMYtXfvcFnmz4jvuJHUccbpUo4O31K2G7OaqlLylQ5dCu62'
'JuVuquKKSfiwOIdYcdPJ6gvpgkQQDPqt7wN+duyZA0FI5F4h'
'O6acQZmbjBCqZoo9Qsg7k9cTcalNkc5flEYAk1mULnddgDM6'
'YGmoJgVnDr0=',
{'transport': transport.name},
)
def getEncodedTransportScript(
self,
userService: 'models.UserService',
transport: 'models.Transport',
ip: str,
os: typing.Dict[str, str],
user: 'models.User',
password: str,
request: 'HttpRequest'
) -> typing.Tuple[str, str, typing.Mapping[str, str]]:
self,
userService: 'models.UserService',
transport: 'models.Transport',
ip: str,
os: typing.Dict[str, str],
user: 'models.User',
password: str,
request: 'HttpRequest',
) -> typing.Tuple[str, str, typing.Mapping[str, str]]:
"""
Encodes the script so the client can understand it
"""
script, signature, params = self.getUDSTransportScript(userService, transport, ip, os, user, password, request)
script, signature, params = self.getUDSTransportScript(
userService, transport, ip, os, user, password, request
)
logger.debug('Transport script: %s', script)
return codecs.encode(codecs.encode(script.encode(), 'bz2'), 'base64').decode().replace('\n', ''), signature, params
return (
codecs.encode(codecs.encode(script.encode(), 'bz2'), 'base64')
.decode()
.replace('\n', ''),
signature,
params,
)
def getLink(
self,
userService: 'models.UserService',
transport: 'models.Transport',
ip: str,
os: typing.Dict[str, str],
user: 'models.User',
password: str,
request: 'HttpRequest'
) -> str:
self,
userService: 'models.UserService',
transport: 'models.Transport',
ip: str,
os: typing.Dict[str, str],
user: 'models.User',
password: str,
request: 'HttpRequest',
) -> str:
"""
Must override if transport does provides its own link
If transport provides own link, this method provides the link itself

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2020 Virtual Cable S.L.U.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -11,7 +11,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -404,7 +404,6 @@ class gui:
def cleanStr(self):
return str(self.value).strip()
class NumericField(InputField):
"""
This represents a numeric field. It apears with an spin up/down button.
@ -428,8 +427,12 @@ class gui:
def __init__(self, **options):
super().__init__(**options)
self._data['minValue'] = int(options.get('minValue', options.get('minvalue', '987654321')))
self._data['maxValue'] = int(options.get('maxValue', options.get('maxvalue', '987654321')))
self._data['minValue'] = int(
options.get('minValue', options.get('minvalue', '987654321'))
)
self._data['maxValue'] = int(
options.get('maxValue', options.get('maxvalue', '987654321'))
)
self._type(gui.InputField.NUMERIC_TYPE)
@ -540,7 +543,7 @@ class gui:
self._type(gui.InputField.PASSWORD_TYPE)
def cleanStr(self):
return str(self.value).strip()
return str(self.value).strip()
class HiddenField(InputField):
"""
@ -863,7 +866,12 @@ class UserInterfaceType(type):
better place
"""
def __new__(cls: typing.Type['UserInterfaceType'], classname: str, bases: typing.Tuple[type, ...], namespace: typing.Dict[str, typing.Any]) -> 'UserInterfaceType':
def __new__(
cls: typing.Type['UserInterfaceType'],
classname: str,
bases: typing.Tuple[type, ...],
namespace: typing.Dict[str, typing.Any],
) -> 'UserInterfaceType':
newClassDict = {}
_gui: typing.MutableMapping[str, gui.InputField] = {}
# We will keep a reference to gui elements also at _gui so we can access them easily
@ -872,7 +880,9 @@ class UserInterfaceType(type):
_gui[attrName] = attr
newClassDict[attrName] = attr
newClassDict['_gui'] = _gui
return typing.cast('UserInterfaceType', type.__new__(cls, classname, bases, newClassDict))
return typing.cast(
'UserInterfaceType', type.__new__(cls, classname, bases, newClassDict)
)
class UserInterface(metaclass=UserInterfaceType):

View File

@ -179,7 +179,9 @@ class CalendarChecker:
return bool(data[dtime.hour * 60 + dtime.minute])
def nextEvent(self, checkFrom=None, startEvent=True, offset=None) -> typing.Optional[datetime.datetime]:
def nextEvent(
self, checkFrom=None, startEvent=True, offset=None
) -> typing.Optional[datetime.datetime]:
"""
Returns next event for this interval
"""

View File

@ -1,187 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016-2021 Virtual Cable S.L.U.
# 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.U. 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.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
# pylint: disable=no-name-in-module,import-error, maybe-no-member
import os
import io
import logging
from urllib.parse import urlparse
import typing
from django.core.cache import caches
from django.core.files import File
from django.core.files.storage import Storage
from django.conf import settings
from uds.models import DBFile
from uds.models import getSqlDatetime
from .tools import DictAsObj
logger = logging.getLogger(__name__)
class FileStorage(Storage):
_base_url: str
cache: typing.Any
def __init__(self, *args, **kwargs):
self._base_url = getattr(settings, 'FILE_STORAGE', '/uds/utility/files')
if self._base_url[-1] != '/':
self._base_url += '/'
cacheName: str = getattr(settings, 'FILE_CACHE', 'memory')
try:
cache = caches[cacheName]
except Exception:
logger.info('No cache for FileStorage configured.')
self.cache = None
return
self.cache = cache
if 'owner' in kwargs:
self.owner = kwargs.get('owner')
del kwargs['owner']
else:
self.owner = 'fstor'
# On start, ensures that cache is empty to avoid surprises
self.cache._cache.flush_all() # pylint: disable=protected-access
Storage.__init__(self, *args, **kwargs)
def get_valid_name(self, name):
if name is None:
return name
return name.replace('\\', os.path.sep)
def _getKey(self, name):
"""
We have only a few files on db, an we are running on a 64 bits system
memcached does not allow keys bigger than 250 chars, so we are going to use hash() to
get a key for this
"""
return 'fstor' + str(hash(self.get_valid_name(name)))
def _dbFileForReadOnly(self, name):
# If we have a cache, & the cache contains the object
if self.cache is not None:
dbf = self.cache.get(self._getKey(name))
if dbf is not None:
return dbf
return self._dbFileForReadWrite(name)
def _dbFileForReadWrite(self, name):
f = DBFile.objects.get(name=self.get_valid_name(name))
self._storeInCache(f)
return f
def _storeInCache(self, f):
if self.cache is None:
return
dbf = DictAsObj(
{
'name': f.name,
'uuid': f.uuid,
'size': f.size,
'data': f.data,
'created': f.created,
'modified': f.modified,
}
)
self.cache.set(self._getKey(f.name), dbf, 3600) # Cache defaults to one hour
def _removeFromCache(self, name):
if self.cache is None:
return
self.cache.delete(self._getKey(name))
def _open(self, name, mode='rb'):
f = io.BytesIO(self._dbFileForReadOnly(name).data)
f.name = name
return File(f)
def _save(self, name, content):
name = self.get_valid_name(name)
try:
f = self._dbFileForReadWrite(name)
except DBFile.DoesNotExist:
now = getSqlDatetime()
f = DBFile.objects.create(
owner=self.owner, name=name, created=now, modified=now
)
f.data = content.read()
f.modified = getSqlDatetime()
f.save()
# Store on cache also
self._storeInCache(f)
return name
def accessed_time(self, name):
raise NotImplementedError
def created_time(self, name):
return self._dbFileForReadOnly(name).created
def modified_time(self, name):
return self._dbFileForReadOnly(name).modified
def size(self, name):
return self._dbFileForReadOnly(name).size
def delete(self, name):
logger.debug('Delete callef for %s', name)
self._dbFileForReadWrite(name).delete()
self._removeFromCache(name)
def exists(self, name):
logger.debug('Called exists for %s', name)
try:
_ = self._dbFileForReadOnly(name).uuid # Tries to access uuid
return True
except DBFile.DoesNotExist:
return False
def url(self, name):
try:
uuid = self._dbFileForReadWrite(name).uuid
return urlparse.urljoin(self._base_url, uuid)
except DBFile.DoesNotExist:
return None

View File

@ -11,7 +11,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#

View File

@ -40,6 +40,7 @@ class RedirectMiddleware:
Some paths will not be redirected, to avoid problems, but they are advised to use SSL (this is for backwards compat)
"""
NO_REDIRECT = [
'rest',
'pam',
@ -51,7 +52,7 @@ class RedirectMiddleware:
# Test client can be http
'uds/rest/client/test',
# And also the tunnel
'uds/rest/tunnel'
'uds/rest/tunnel',
]
def __init__(self, get_response):
@ -65,7 +66,11 @@ class RedirectMiddleware:
redirect = False
break
if redirect and not request.is_secure() and GlobalConfig.REDIRECT_TO_HTTPS.getBool():
if (
redirect
and not request.is_secure()
and GlobalConfig.REDIRECT_TO_HTTPS.getBool()
):
if request.method == 'POST':
# url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get())
url = reverse('page.login')

View File

@ -44,6 +44,7 @@ class XUACompatibleMiddleware:
This header tells to Internet Explorer to render page with latest
possible version or to use chrome frame if it is installed.
"""
def __init__(self, get_response):
self.get_response = get_response

View File

@ -73,7 +73,7 @@ string = {
FOR_EXECUTE: _('Waiting execution'),
BALANCING: _('Balancing'),
MAINTENANCE: _('In maintenance'),
WAITING_OS: _('Waiting OS')
WAITING_OS: _('Waiting OS'),
}
# States that are merely for "information" to the user. They don't contain any usable instance

View File

@ -32,4 +32,12 @@
"""
# pylint: disable=unused-import
from .common import ERROR, USABLE, REMOVABLE, REMOVED, CANCELED, LAUNCHING, PREPARING # @UnusedImport
from .common import (
ERROR,
USABLE,
REMOVABLE,
REMOVED,
CANCELED,
LAUNCHING,
PREPARING,
) # @UnusedImport

View File

@ -33,4 +33,3 @@
# pylint: disable=unused-import
from .common import ACTIVE, REMOVABLE, REMOVING, REMOVED # @UnusedImport

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015-2019 Virtual Cable S.L.
# Copyright (c) 2015-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015-2019 Virtual Cable S.L.
# Copyright (c) 2015-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -32,4 +32,13 @@
"""
# pylint: disable=unused-import
from .common import ERROR, USABLE, PREPARING, REMOVABLE, REMOVING, REMOVED, INFO_STATES, VALID_STATES # @UnusedImport
from .common import (
ERROR,
USABLE,
PREPARING,
REMOVABLE,
REMOVING,
REMOVED,
INFO_STATES,
VALID_STATES,
) # @UnusedImport

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2013-2019 Virtual Cable S.L.
# Copyright (c) 2013-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,

View File

@ -41,91 +41,166 @@ from uds.core.util.modfinder import loadModulesUrls
urlpatterns = [
# Root url placeholder
path(r'', RedirectView.as_view(pattern_name='page.index', permanent=False), name='page.index.placeholder'),
path(
r'',
RedirectView.as_view(pattern_name='page.index', permanent=False),
name='page.index.placeholder',
),
# path(r'', RedirectView.as_view(url='https://www.udsenterprise.com', permanent=False), name='page.index.placeholder')
# START COMPAT redirections & urls
path(r'login/', RedirectView.as_view(pattern_name='page.login', permanent=False), name='page.login.compat'),
path(r'logout/', RedirectView.as_view(pattern_name='page.logut'), name='page.logout.compat'),
path(
r'login/',
RedirectView.as_view(pattern_name='page.login', permanent=False),
name='page.login.compat',
),
path(
r'logout/',
RedirectView.as_view(pattern_name='page.logut'),
name='page.logout.compat',
),
# Backwards compatibility with REST API path
re_path(r'^rest/(?P<arguments>.*)$', REST.Dispatcher.as_view(), name="REST.compat"),
# Old urls for federated authentications
re_path(r'^auth/(?P<authName>.+)', uds.web.views.authCallback, name='page.auth.callback.compat'),
re_path(r'^authinfo/(?P<authName>.+)', uds.web.views.authInfo, name='page.auth.info.compat'),
re_path(
r'^auth/(?P<authName>.+)',
uds.web.views.authCallback,
name='page.auth.callback.compat',
),
re_path(
r'^authinfo/(?P<authName>.+)',
uds.web.views.authInfo,
name='page.auth.info.compat',
),
# Ticket authentication
re_path(r'^tkauth/(?P<ticketId>[a-zA-Z0-9-]+)$', uds.web.views.ticketAuth, name='page.auth.ticket.compat'),
re_path(
r'^tkauth/(?P<ticketId>[a-zA-Z0-9-]+)$',
uds.web.views.ticketAuth,
name='page.auth.ticket.compat',
),
# END COMPAT
# Index
path(r'uds/page/services', uds.web.views.modern.index, name='page.index'),
# Login/logout
path(r'uds/page/login', uds.web.views.modern.login, name='page.login'),
re_path(r'^uds/page/login/(?P<tag>[a-zA-Z0-9-]+)$', uds.web.views.modern.login, name='page.login.tag'),
re_path(
r'^uds/page/login/(?P<tag>[a-zA-Z0-9-]+)$',
uds.web.views.modern.login,
name='page.login.tag',
),
path(r'uds/page/logout', uds.web.views.modern.logout, name='page.logout'),
# Error URL (just a placeholder, calls index with data on url for angular)
re_path(r'^uds/page/error/(?P<err>[a-zA-Z0-9=-]+)$', uds.web.views.error, name='page.error'),
re_path(
r'^uds/page/error/(?P<err>[a-zA-Z0-9=-]+)$',
uds.web.views.error,
name='page.error',
),
# Download plugins URL (just a placeholder, calls index with data on url for angular)
path(r'uds/page/client-download', uds.web.views.modern.index, name='page.client-download'),
path(
r'uds/page/client-download',
uds.web.views.modern.index,
name='page.client-download',
),
# Federated authentication
re_path(r'^uds/page/auth/(?P<authName>[^/]+)$', uds.web.views.authCallback, name='page.auth.callback'),
re_path(r'^uds/page/auth/stage2/(?P<ticketId>[^/]+)$', uds.web.views.authCallback_stage2, name='page.auth.callback_stage2'),
re_path(r'^uds/page/auth/info/(?P<authName>[a-zA-Z0-9.-]+)$', uds.web.views.authInfo, name='page.auth.info'),
re_path(
r'^uds/page/auth/(?P<authName>[^/]+)$',
uds.web.views.authCallback,
name='page.auth.callback',
),
re_path(
r'^uds/page/auth/stage2/(?P<ticketId>[^/]+)$',
uds.web.views.authCallback_stage2,
name='page.auth.callback_stage2',
),
re_path(
r'^uds/page/auth/info/(?P<authName>[a-zA-Z0-9.-]+)$',
uds.web.views.authInfo,
name='page.auth.info',
),
# Ticket authentication related
re_path(r'^uds/page/ticket/auth/(?P<ticketId>[a-zA-Z0-9.-]+)$', uds.web.views.ticketAuth, name='page.ticket.auth'),
path(r'uds/page/ticket/launcher', uds.web.views.modern.ticketLauncher, name='page.ticket.launcher'),
re_path(
r'^uds/page/ticket/auth/(?P<ticketId>[a-zA-Z0-9.-]+)$',
uds.web.views.ticketAuth,
name='page.ticket.auth',
),
path(
r'uds/page/ticket/launcher',
uds.web.views.modern.ticketLauncher,
name='page.ticket.launcher',
),
# This must be the last, so any patition will be managed by client in fact
re_path(r'uds/page/.*', uds.web.views.modern.index, name='page.placeholder'),
# Utility
path(r'uds/utility/closer', uds.web.views.service.closer, name='utility.closer'),
# Javascript
path(r'uds/utility/uds.js', uds.web.views.modern.js, name="utility.js"),
path(r'uds/adm/utility/uds.js', uds.web.views.modern.js, name="utility-adm.js"),
# i18n
re_path(r'^uds/utility/i18n/(?P<lang>[a-z_-]*).js$', JavaScriptCatalog.as_view(), name='utility.jsCatalog'),
re_path(
r'^uds/utility/i18n/(?P<lang>[a-z_-]*).js$',
JavaScriptCatalog.as_view(),
name='utility.jsCatalog',
),
path(r'uds/utility/i18n', include('django.conf.urls.i18n')),
# Downloader
re_path(r'^uds/utility/download/(?P<idDownload>[a-zA-Z0-9-]*)$', uds.web.views.download, name='utility.downloader'),
re_path(
r'^uds/utility/download/(?P<idDownload>[a-zA-Z0-9-]*)$',
uds.web.views.download,
name='utility.downloader',
),
# WEB API path (not REST api, frontend)
re_path(r'^uds/webapi/img/transport/(?P<idTrans>[a-zA-Z0-9:-]+)$', uds.web.views.transportIcon, name='webapi.transportIcon'),
re_path(r'^uds/webapi/img/gallery/(?P<idImage>[a-zA-Z0-9-]+)$', uds.web.views.image, name='webapi.galleryImage'),
re_path(
r'^uds/webapi/img/transport/(?P<idTrans>[a-zA-Z0-9:-]+)$',
uds.web.views.transportIcon,
name='webapi.transportIcon',
),
re_path(
r'^uds/webapi/img/gallery/(?P<idImage>[a-zA-Z0-9-]+)$',
uds.web.views.image,
name='webapi.galleryImage',
),
# Enabler and Status action are first processed, and if not match, execute the generic "action" handler
re_path(r'^uds/webapi/action/(?P<idService>.+)/enable/(?P<idTransport>[a-zA-Z0-9:-]+)$', uds.web.views.userServiceEnabler, name='webapi.enabler'),
re_path(r'^uds/webapi/action/(?P<idService>.+)/status/(?P<idTransport>[a-zA-Z0-9:-]+)$', uds.web.views.userServiceStatus, name='webapi.status'),
re_path(r'^uds/webapi/action/(?P<idService>.+)/(?P<actionString>[a-zA-Z0-9:-]+)$', uds.web.views.action, name='webapi.action'),
re_path(
r'^uds/webapi/action/(?P<idService>.+)/enable/(?P<idTransport>[a-zA-Z0-9:-]+)$',
uds.web.views.userServiceEnabler,
name='webapi.enabler',
),
re_path(
r'^uds/webapi/action/(?P<idService>.+)/status/(?P<idTransport>[a-zA-Z0-9:-]+)$',
uds.web.views.userServiceStatus,
name='webapi.status',
),
re_path(
r'^uds/webapi/action/(?P<idService>.+)/(?P<actionString>[a-zA-Z0-9:-]+)$',
uds.web.views.action,
name='webapi.action',
),
# Services list, ...
path(r'uds/webapi/services', uds.web.views.modern.servicesData, name='webapi.services'),
path(
r'uds/webapi/services',
uds.web.views.modern.servicesData,
name='webapi.services',
),
# Transport own link processor
re_path(r'^uds/webapi/trans/(?P<idService>.+)/(?P<idTransport>.+)$', uds.web.views.transportOwnLink, name='TransportOwnLink'),
re_path(
r'^uds/webapi/trans/(?P<idService>.+)/(?P<idTransport>.+)$',
uds.web.views.transportOwnLink,
name='TransportOwnLink',
),
# Authenticators custom html
re_path(r'^uds/webapi/customAuth/(?P<idAuth>.*)$', uds.web.views.customAuth, name='uds.web.views.customAuth'),
re_path(
r'^uds/webapi/customAuth/(?P<idAuth>.*)$',
uds.web.views.customAuth,
name='uds.web.views.customAuth',
),
# END WEB API
# Costumization of GUI
re_path(r'^uds/custom/(?P<component>[a-zA-Z.-]+)$', uds.web.views.custom.custom, name='custom'),
re_path(
r'^uds/custom/(?P<component>[a-zA-Z.-]+)$',
uds.web.views.custom.custom,
name='custom',
),
# REST Api
re_path(r'^uds/rest/(?P<arguments>.*)$', REST.Dispatcher.as_view(), name="REST"),
# Web admin GUI
re_path(r'^uds/adm', include('uds.admin.urls')),
]

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * 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
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#

View File

@ -38,10 +38,17 @@ from uds.models import Authenticator
logger = logging.getLogger(__name__)
class LoginForm(forms.Form):
user = forms.CharField(label=_('Username'), max_length=64, widget=forms.TextInput())
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput(attrs={'title': _('Password')}), required=False)
authenticator = forms.ChoiceField(label=_('Authenticator'), choices=(), required=False)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput(attrs={'title': _('Password')}),
required=False,
)
authenticator = forms.ChoiceField(
label=_('Authenticator'), choices=(), required=False
)
logouturl = forms.CharField(widget=forms.HiddenInput(), required=False)
def __init__(self, *args, **kwargs):

View File

@ -55,11 +55,11 @@ logger = logging.getLogger(__name__)
# (None, NumericError) if errorview redirection
# (User, password_string) if all is ok
def checkLogin( # pylint: disable=too-many-branches, too-many-statements
request: 'ExtendedHttpRequest',
form: 'LoginForm',
tag: typing.Optional[str] = None
) -> typing.Tuple[typing.Optional['User'], typing.Any]:
host = request.META.get('HTTP_HOST') or request.META.get('SERVER_NAME') or 'auth_host' # Last one is a placeholder in case we can't locate host name
request: 'ExtendedHttpRequest', form: 'LoginForm', tag: typing.Optional[str] = None
) -> typing.Tuple[typing.Optional['User'], typing.Any]:
host = (
request.META.get('HTTP_HOST') or request.META.get('SERVER_NAME') or 'auth_host'
) # Last one is a placeholder in case we can't locate host name
# Get Authenticators limitation
if GlobalConfig.DISALLOW_GLOBAL_LOGIN.getBool(False) is True:
@ -81,7 +81,9 @@ def checkLogin( # pylint: disable=too-many-branches, too-many-statements
if form.is_valid():
os = request.os
try:
authenticator = Authenticator.objects.get(uuid=processUuid(form.cleaned_data['authenticator']))
authenticator = Authenticator.objects.get(
uuid=processUuid(form.cleaned_data['authenticator'])
)
except Exception:
authenticator = Authenticator()
userName = form.cleaned_data['user']
@ -91,11 +93,20 @@ def checkLogin( # pylint: disable=too-many-branches, too-many-statements
cache = Cache('auth')
cacheKey = str(authenticator.id) + userName
tries = cache.get(cacheKey) or 0
triesByIp = (cache.get(request.ip) or 0) if GlobalConfig.LOGIN_BLOCK_IP.getBool() else 0
triesByIp = (
(cache.get(request.ip) or 0) if GlobalConfig.LOGIN_BLOCK_IP.getBool() else 0
)
maxTries = GlobalConfig.MAX_LOGIN_TRIES.getInt()
if (authenticator.getInstance().blockUserOnLoginFailures is True and (tries >= maxTries) or triesByIp >= maxTries):
if (
authenticator.getInstance().blockUserOnLoginFailures is True
and (tries >= maxTries)
or triesByIp >= maxTries
):
authLogLogin(request, authenticator, userName, 'Temporarily blocked')
return (None, _('Too many authentication errrors. User temporarily blocked'))
return (
None,
_('Too many authentication errrors. User temporarily blocked'),
)
password = form.cleaned_data['password']
user = None
@ -106,9 +117,14 @@ def checkLogin( # pylint: disable=too-many-branches, too-many-statements
if user is None:
logger.debug("Invalid user %s (access denied)", userName)
cache.put(cacheKey, tries+1, GlobalConfig.LOGIN_BLOCK.getInt())
cache.put(request.ip, triesByIp+1, GlobalConfig.LOGIN_BLOCK.getInt())
authLogLogin(request, authenticator, userName, 'Access denied (user not allowed by UDS)')
cache.put(cacheKey, tries + 1, GlobalConfig.LOGIN_BLOCK.getInt())
cache.put(request.ip, triesByIp + 1, GlobalConfig.LOGIN_BLOCK.getInt())
authLogLogin(
request,
authenticator,
userName,
'Access denied (user not allowed by UDS)',
)
return (None, _('Access denied'))
request.session.cycle_key()

View File

@ -49,7 +49,7 @@ from uds.models import Authenticator, Image, Network, Transport
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core.util.request import ExtendedHttpRequest
from uds.core.util.request import ExtendedHttpRequest
from uds.models import User
logger = logging.getLogger(__name__)
@ -61,13 +61,21 @@ CSRF_FIELD = 'csrfmiddlewaretoken'
@register.simple_tag(takes_context=True)
def udsJs(request: 'ExtendedHttpRequest') -> str:
auth_host = request.META.get('HTTP_HOST') or request.META.get('SERVER_NAME') or 'auth_host' # Last one is a placeholder in case we can't locate host name
auth_host = (
request.META.get('HTTP_HOST') or request.META.get('SERVER_NAME') or 'auth_host'
) # Last one is a placeholder in case we can't locate host name
role: str = 'user'
user: typing.Optional['User'] = request.user
if user:
role = 'staff' if user.isStaff() and not user.is_admin else 'admin' if user.is_admin else 'user'
role = (
'staff'
if user.isStaff() and not user.is_admin
else 'admin'
if user.is_admin
else 'user'
)
if request.session.get('restricted', False):
role = 'restricted'
@ -87,7 +95,9 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
try:
# Get authenticators with auth_host or tag. If tag is None, auth_host, if exists
# tag, later will remove "auth_host"
authenticators = Authenticator.objects.filter(small_name__in=[auth_host, tag])[:]
authenticators = Authenticator.objects.filter(
small_name__in=[auth_host, tag]
)[:]
except Exception as e:
authenticators = []
else:
@ -95,8 +105,15 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
# logger.debug('Authenticators PRE: %s', authenticators)
if tag and authenticators: # Refilter authenticators, visible and with this tag if required
authenticators = [x for x in authenticators if x.small_name == tag or (tag == 'disabled' and x.getType().isCustom() is False)]
if (
tag and authenticators
): # Refilter authenticators, visible and with this tag if required
authenticators = [
x
for x in authenticators
if x.small_name == tag
or (tag == 'disabled' and x.getType().isCustom() is False)
]
if not authenticators:
try:
@ -117,15 +134,19 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
'name': auth.name,
'label': auth.small_name,
'priority': auth.priority,
'is_custom': theType.isCustom()
'is_custom': theType.isCustom(),
}
config = {
'version': VERSION,
'version_stamp': VERSION_STAMP,
'language': get_language(),
'available_languages': [{'id': k, 'name': gettext(v)} for k, v in settings.LANGUAGES],
'authenticators': [getAuthInfo(auth) for auth in authenticators if auth.getType()],
'available_languages': [
{'id': k, 'name': gettext(v)} for k, v in settings.LANGUAGES
],
'authenticators': [
getAuthInfo(auth) for auth in authenticators if auth.getType()
],
'tag': tag,
'os': request.os['OS'],
'csrf_field': CSRF_FIELD,
@ -142,7 +163,8 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
'launcher_wait_time': 5000,
'messages': {
# Calendar denied message
'calendarDenied': GlobalConfig.LIMITED_BY_CALENDAR_TEXT.get().strip() or gettext('Access limited by calendar')
'calendarDenied': GlobalConfig.LIMITED_BY_CALENDAR_TEXT.get().strip()
or gettext('Access limited by calendar')
},
'urls': {
'changeLang': reverse('set_language'),
@ -151,11 +173,23 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
'user': reverse('page.index'),
'customAuth': reverse('uds.web.views.customAuth', kwargs={'idAuth': ''}),
'services': reverse('webapi.services'),
'enabler': reverse('webapi.enabler', kwargs={'idService': 'param1', 'idTransport': 'param2'}),
'status': reverse('webapi.status', kwargs={'idService': 'param1', 'idTransport': 'param2'}),
'action': reverse('webapi.action', kwargs={'idService': 'param1', 'actionString': 'param2'}),
'galleryImage': reverse('webapi.galleryImage', kwargs={'idImage': 'param1'}),
'transportIcon': reverse('webapi.transportIcon', kwargs={'idTrans': 'param1'}),
'enabler': reverse(
'webapi.enabler',
kwargs={'idService': 'param1', 'idTransport': 'param2'},
),
'status': reverse(
'webapi.status', kwargs={'idService': 'param1', 'idTransport': 'param2'}
),
'action': reverse(
'webapi.action',
kwargs={'idService': 'param1', 'actionString': 'param2'},
),
'galleryImage': reverse(
'webapi.galleryImage', kwargs={'idImage': 'param1'}
),
'transportIcon': reverse(
'webapi.transportIcon', kwargs={'idTrans': 'param1'}
),
'static': static(''),
'clientDownload': reverse('page.client-download'),
# Launcher URL if exists
@ -168,9 +202,11 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
if user and user.isStaff():
info = {
'networks': [n.name for n in Network.networksFor(request.ip)],
'transports': [t.name for t in Transport.objects.all() if t.validForIp(request.ip)],
'transports': [
t.name for t in Transport.objects.all() if t.validForIp(request.ip)
],
'ip': request.ip,
'ip_proxy': request.ip_proxy
'ip_proxy': request.ip_proxy,
}
# all plugins are under url clients...
@ -180,17 +216,75 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
'description': description,
'name': name,
'legacy': legacy,
} for url, description, name, legacy in (
('UDSClientSetup-{version}.exe', gettext('Windows client'), 'Windows', False),
}
for url, description, name, legacy in (
(
'UDSClientSetup-{version}.exe',
gettext('Windows client'),
'Windows',
False,
),
('UDSClient-{version}.pkg', gettext('Mac OS X client'), 'MacOS', False),
('udsclient3_{version}_all.deb', gettext('Debian based Linux client') + ' ' + gettext('(requires Python-3.6 or newer)'), 'Linux', False),
('udsclient3-{version}-1.noarch.rpm', gettext('RPM based Linux client (Fedora, Suse, ...)') + ' ' + gettext('(requires Python-3.6 or newer)'), 'Linux', False),
('udsclient3-x86_64-{version}.tar.gz', gettext('Binary appimage X86_64 Linux client'), 'Linux', False),
('udsclient3-armhf-{version}.tar.gz', gettext('Binary appimage Raspberry Linux client'), 'Linux', False),
('udsclient3-{version}.tar.gz', gettext('Generic .tar.gz Linux client') + ' ' + gettext('(requires Python-3.6 or newer)'), 'Linux', False),
('udsclient_{version}_all.deb', gettext('Legacy Debian based Python 2.7 Linux client') + ' ' + gettext('(requires outdated Python-2.7)'), 'Linux', True),
('udsclient-{version}-1.noarch.rpm', gettext('Legacy RH based Linux client (Fedora, Centos, Suse, ...)') + ' ' + gettext('(requires outdated Python-2.7)'), 'Linux', True),
('udsclient-opensuse-{version}-1.noarch.rpm', gettext('Legacy OpenSuse based Linux client)') + ' ' + gettext('(requires outdated Python-2.7)'), 'Linux', True),
(
'udsclient3_{version}_all.deb',
gettext('Debian based Linux client')
+ ' '
+ gettext('(requires Python-3.6 or newer)'),
'Linux',
False,
),
(
'udsclient3-{version}-1.noarch.rpm',
gettext('RPM based Linux client (Fedora, Suse, ...)')
+ ' '
+ gettext('(requires Python-3.6 or newer)'),
'Linux',
False,
),
(
'udsclient3-x86_64-{version}.tar.gz',
gettext('Binary appimage X86_64 Linux client'),
'Linux',
False,
),
(
'udsclient3-armhf-{version}.tar.gz',
gettext('Binary appimage Raspberry Linux client'),
'Linux',
False,
),
(
'udsclient3-{version}.tar.gz',
gettext('Generic .tar.gz Linux client')
+ ' '
+ gettext('(requires Python-3.6 or newer)'),
'Linux',
False,
),
(
'udsclient_{version}_all.deb',
gettext('Legacy Debian based Python 2.7 Linux client')
+ ' '
+ gettext('(requires outdated Python-2.7)'),
'Linux',
True,
),
(
'udsclient-{version}-1.noarch.rpm',
gettext('Legacy RH based Linux client (Fedora, Centos, Suse, ...)')
+ ' '
+ gettext('(requires outdated Python-2.7)'),
'Linux',
True,
),
(
'udsclient-opensuse-{version}-1.noarch.rpm',
gettext('Legacy OpenSuse based Linux client)')
+ ' '
+ gettext('(requires outdated Python-2.7)'),
'Linux',
True,
),
)
]
@ -200,8 +294,7 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
# 'description': 'Cliente SPICE for download', # Text that appears on download
# 'name': 'Linux', # Can be 'Linux', 'Windows', o 'MacOS'. Sets the icon.
# 'legacy': False # True = Gray, False = White
#})
# })
actors: typing.List[typing.Dict[str, str]] = []
@ -212,7 +305,14 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
config['auth_token'] = request.session.session_key
config['auth_header'] = AUTH_TOKEN_HEADER
# Actors
actors = [{'url': reverse('utility.downloader', kwargs={'idDownload': key}), 'name': val['name'], 'description': gettext(val['comment'])} for key, val in downloadsManager().getDownloadables().items()]
actors = [
{
'url': reverse('utility.downloader', kwargs={'idDownload': key}),
'name': val['name'],
'description': gettext(val['comment']),
}
for key, val in downloadsManager().getDownloadables().items()
]
# URLS
config['urls']['admin'] = reverse('uds.admin.views.index')
config['urls']['rest'] = reverse('REST', kwargs={'arguments': ''})
@ -239,7 +339,7 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
'plugins': plugins,
'actors': actors,
'errors': errors,
'data': request.session.get('data')
'data': request.session.get('data'),
}
# Reset some 1 time values...

View File

@ -125,7 +125,9 @@ def getServicesData(
):
yield t
except Exception as e:
logger.warning('Transport %s of %s not found. Ignoring. (%s)', t, member.pool, e)
logger.warning(
'Transport %s of %s not found. Ignoring. (%s)', t, member.pool, e
)
def buildMetaTransports(
transports: typing.Iterable[Transport],
@ -167,8 +169,7 @@ def getServicesData(
inAll = tmpSet
# tmpSet has ALL common transports
metaTransports = buildMetaTransports(
Transport.objects.filter(uuid__in=inAll or []),
isLabel=False
Transport.objects.filter(uuid__in=inAll or []), isLabel=False
)
elif meta.transport_grouping == MetaPool.LABEL_TRANSPORT_SELECT:
ltrans: typing.MutableMapping[str, Transport] = {}
@ -188,8 +189,7 @@ def getServicesData(
inAll = tmpSet
# tmpSet has ALL common transports
metaTransports = buildMetaTransports(
(v for k, v in ltrans.items() if k in (inAll or set())),
isLabel=True
(v for k, v in ltrans.items() if k in (inAll or set())), isLabel=True
)
else:
for member in meta.members.all():
@ -382,10 +382,13 @@ def getServicesData(
'ip': request.ip,
'nets': nets,
'transports': validTrans,
'autorun': autorun
'autorun': autorun,
}
def enableService(request: 'ExtendedHttpRequestWithUser', idService: str, idTransport: str) -> typing.Mapping[str, typing.Any]:
def enableService(
request: 'ExtendedHttpRequestWithUser', 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 = ''
@ -394,13 +397,15 @@ def enableService(request: 'ExtendedHttpRequestWithUser', idService: str, idTran
# 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)
userService, trans = res[1], res[3]
userService.setProperty('accessedByClient', '0') # Reset accesed property to
userService.setProperty('accessedByClient', '0') # Reset accesed property to
typeTrans = trans.getType()
@ -413,7 +418,7 @@ def enableService(request: 'ExtendedHttpRequestWithUser', idService: str, idTran
'service': 'A' + userService.uuid,
'transport': trans.uuid,
'user': request.user.uuid,
'password': password
'password': password,
}
ticket = TicketStore.create(data)
@ -422,7 +427,9 @@ def enableService(request: 'ExtendedHttpRequestWithUser', idService: str, idTran
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))
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)
@ -433,7 +440,4 @@ def enableService(request: 'ExtendedHttpRequestWithUser', idService: str, idTran
logger.exception('Error')
error = str(e)
return {
'url': str(url),
'error': str(error)
}
return {'url': str(url), 'error': str(error)}

View File

@ -33,7 +33,14 @@ import logging
# from .login import login, logout
from uds.web.util.errors import error
from .service import transportOwnLink, transportIcon, userServiceEnabler, userServiceStatus, serviceImage, action
from .service import (
transportOwnLink,
transportIcon,
userServiceEnabler,
userServiceStatus,
serviceImage,
action,
)
from .auth import authCallback, authCallback_stage2, authInfo, ticketAuth, customAuth
from .download import download
from .images import image

View File

@ -39,7 +39,13 @@ from django.views.decorators.csrf import csrf_exempt
import uds.web.util.errors as errors
from uds.core import auths
from uds.core.auths.auth import webLogin, webLogout, authenticateViaCallback, authLogLogin, getUDSCookie
from uds.core.auths.auth import (
webLogin,
webLogout,
authenticateViaCallback,
authLogLogin,
getUDSCookie,
)
from uds.core.managers import userServiceManager, cryptoManager
from uds.core.services.exceptions import ServiceNotReadyError
from uds.core.util import os_detector as OsDetector
@ -62,6 +68,7 @@ logger = logging.getLogger(__name__)
# * Recovers parameters from first stage
# * Process real callback
@csrf_exempt
def authCallback(request: HttpRequest, authName: str) -> HttpResponse:
"""
@ -77,7 +84,9 @@ def authCallback(request: HttpRequest, authName: str) -> HttpResponse:
params.update(request.POST)
params['_query'] = request.META.get('QUERY_STRING', '')
logger.debug('Auth callback for %s with params %s', authenticator, params.keys())
logger.debug(
'Auth callback for %s with params %s', authenticator, params.keys()
)
ticket = TicketStore.create({'params': params, 'auth': authenticator.uuid})
return HttpResponseRedirect(reverse('page.auth.callback_stage2', args=[ticket]))
@ -86,7 +95,9 @@ def authCallback(request: HttpRequest, authName: str) -> HttpResponse:
return errors.exceptionView(request, e)
def authCallback_stage2(request: 'ExtendedHttpRequestWithUser', ticketId: str) -> HttpResponse:
def authCallback_stage2(
request: 'ExtendedHttpRequestWithUser', ticketId: str
) -> HttpResponse:
try:
ticket = TicketStore.get(ticketId)
params: typing.Dict[str, typing.Any] = ticket['params']
@ -95,14 +106,21 @@ def authCallback_stage2(request: 'ExtendedHttpRequestWithUser', ticketId: str) -
params['_request'] = request
# params['_session'] = request.session
# params['_user'] = request.user
logger.debug('Request session:%s -> %s, %s', request.ip, request.session.keys(), request.session.session_key)
logger.debug(
'Request session:%s -> %s, %s',
request.ip,
request.session.keys(),
request.session.session_key,
)
user = authenticateViaCallback(authenticator, params)
os = OsDetector.getOsFromUA(request.META['HTTP_USER_AGENT'])
if user is None:
authLogLogin(request, authenticator, '{0}'.format(params), 'Invalid at auth callback')
authLogLogin(
request, authenticator, '{0}'.format(params), 'Invalid at auth callback'
)
raise auths.exceptions.InvalidUserException()
response = HttpResponseRedirect(reverse('page.index'))
@ -114,9 +132,14 @@ def authCallback_stage2(request: 'ExtendedHttpRequestWithUser', ticketId: str) -
return response
except auths.exceptions.Redirect as e:
return HttpResponseRedirect(request.build_absolute_uri(str(e)) if e.args and e.args[0] else '/' )
return HttpResponseRedirect(
request.build_absolute_uri(str(e)) if e.args and e.args[0] else '/'
)
except auths.exceptions.Logout as e:
return webLogout(request, request.build_absolute_uri(str(e)) if e.args and e.args[0] else None)
return webLogout(
request,
request.build_absolute_uri(str(e)) if e.args and e.args[0] else None,
)
except Exception as e:
logger.exception('authCallback')
return errors.exceptionView(request, e)
@ -173,7 +196,9 @@ def customAuth(request: 'HttpRequest', idAuth: str) -> HttpResponse:
@never_cache
def ticketAuth(request: 'ExtendedHttpRequestWithUser', ticketId: str) -> HttpResponse: # pylint: disable=too-many-locals,too-many-branches,too-many-statements
def ticketAuth(
request: 'ExtendedHttpRequestWithUser', ticketId: str
) -> HttpResponse: # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""
Used to authenticate an user via a ticket
"""
@ -208,7 +233,9 @@ def ticketAuth(request: 'ExtendedHttpRequestWithUser', ticketId: str) -> HttpRes
raise Exception('Invalid ticket authentication')
usr = auth.getOrCreateUser(username, realname)
if usr is None or State.isActive(usr.state) is False: # If user is inactive, raise an exception
if (
usr is None or State.isActive(usr.state) is False
): # If user is inactive, raise an exception
raise auths.exceptions.InvalidUserException()
# Add groups to user (replace existing groups)
@ -228,14 +255,20 @@ def ticketAuth(request: 'ExtendedHttpRequestWithUser', ticketId: str) -> HttpRes
# Check if servicePool is part of the ticket
if poolUuid:
# If service pool is in there, also is transport
res = userServiceManager().getService(request.user, request.os, request.ip, poolUuid, transport, False)
res = userServiceManager().getService(
request.user, request.os, request.ip, poolUuid, transport, False
)
_, userService, _, transport, _ = res
transportInstance = transport.getInstance()
if transportInstance.ownLink is True:
link = reverse('TransportOwnLink', args=('A' + userService.uuid, transport.uuid))
link = reverse(
'TransportOwnLink', args=('A' + userService.uuid, transport.uuid)
)
else:
link = html.udsAccessLink(request, 'A' + userService.uuid, transport.uuid)
link = html.udsAccessLink(
request, 'A' + userService.uuid, transport.uuid
)
request.session['launch'] = link
response = HttpResponseRedirect(reverse('page.ticket.launcher'))

View File

@ -30,6 +30,7 @@
"""
from django.http import HttpResponse
# from django.views.decorators.cache import cache_page
from uds.core.util.config import GlobalConfig

View File

@ -37,7 +37,10 @@ from .modern import index
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse # pylint: disable=ungrouped-imports
from django.http import (
HttpRequest,
HttpResponse,
) # pylint: disable=ungrouped-imports
logger = logging.getLogger(__name__)

View File

@ -33,7 +33,7 @@ import logging
import typing
from django.shortcuts import render
from django.http import HttpRequest, HttpResponse, JsonResponse, HttpResponseRedirect
from django.http import HttpRequest, HttpResponse, JsonResponse, HttpResponseRedirect
from django.urls import reverse
from uds.core.util.request import ExtendedHttpRequest, ExtendedHttpRequestWithUser
from uds.core.auths import auth
@ -58,14 +58,16 @@ def index(request: HttpRequest) -> HttpResponse:
return response
# Includes a request.session ticket, indicating that
# Includes a request.session ticket, indicating that
def ticketLauncher(request: HttpRequest) -> HttpResponse:
request.session['restricted'] = True # Access is from ticket
return index(request)
# Basically, the original /login method, but fixed for modern interface
def login(request: ExtendedHttpRequest, tag: typing.Optional[str] = None) -> HttpResponse:
def login(
request: ExtendedHttpRequest, tag: typing.Optional[str] = None
) -> HttpResponse:
# Default empty form
logger.debug('Tag: %s', tag)
if request.method == 'POST':
@ -106,7 +108,9 @@ def logout(request: ExtendedHttpRequestWithUser) -> HttpResponse:
def js(request: ExtendedHttpRequest) -> HttpResponse:
return HttpResponse(content=configjs.udsJs(request), content_type='application/javascript')
return HttpResponse(
content=configjs.udsJs(request), content_type='application/javascript'
)
@auth.denyNonAuthenticated

View File

@ -158,7 +158,9 @@ def userServiceStatus(
userService = None
status = 'running'
# If service exists
userService = userServiceManager().locateUserService(user=request.user, idService=idService, create=False)
userService = userServiceManager().locateUserService(
user=request.user, idService=idService, create=False
)
if userService:
# Service exists...
try: