1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-22 13:34:04 +03:00

added communication between a token-actor and a token-service

This commit is contained in:
Adolfo Gómez García 2023-05-10 23:53:35 +02:00
parent d25f278230
commit fd85e3a202
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
2 changed files with 83 additions and 69 deletions

View File

@ -33,6 +33,7 @@ import time
import logging
import typing
import functools
import enum
from uds.models import (
ActorToken,
@ -76,6 +77,16 @@ class BlockAccess(Exception):
pass
class NotifyActionType(enum.StrEnum):
LOGIN = 'login'
LOGOUT = 'logout'
DATA = 'data'
@staticmethod
def valid_names() -> typing.List[str]:
return [e.value for e in NotifyActionType]
# Helpers
def fixIdsList(idsList: typing.List[str]) -> typing.List[str]:
"""
@ -174,6 +185,46 @@ class ActorV3Action(Handler):
raise AccessDenied('Access denied')
# Some helpers
def notifyService(self, action: NotifyActionType) -> None:
try:
# If unmanaged, use Service locator
service: 'services.Service' = Service.objects.get(token=self._params['token']).getInstance()
# We have a valid service, now we can make notifications
# Build the possible ids and make initial filter to match service
idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10]
# ensure idsLists has upper and lower versions for case sensitive databases
idsList = fixIdsList(idsList)
validId: typing.Optional[str] = service.getValidId(idsList)
is_remote = self._params.get('session_type', '')[:4] in ('xrdp', 'RDP-')
# Must be valid
if action in (NotifyActionType.LOGIN, NotifyActionType.LOGOUT):
if not validId: # For login/logout, we need a valid id
raise Exception()
# Notify Service that someone logged in/out
if action == NotifyActionType.LOGIN:
# Try to guess if this is a remote session
service.processLogin(validId, remote_login=is_remote)
elif action == NotifyActionType.LOGOUT:
service.processLogout(validId, remote_login=is_remote)
elif action == NotifyActionType.DATA:
service.notifyData(validId, self._params['data'])
else:
raise Exception('Invalid action')
# All right, service notified..
except Exception as e:
# Log error and continue
logger.error('Error notifying service: %s (%s)', e, self._params)
raise BlockAccess() from None
class Test(ActorV3Action):
"""
@ -501,47 +552,7 @@ class Version(ActorV3Action):
return ActorV3Action.actorResult()
class LoginLogout(ActorV3Action):
name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available
def notifyService(self, isLogin: bool) -> None:
try:
# If unmanaged, use Service locator
service: 'services.Service' = Service.objects.get(token=self._params['token']).getInstance()
# We have a valid service, now we can make notifications
# Build the possible ids and make initial filter to match service
idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10]
# ensure idsLists has upper and lower versions for case sensitive databases
idsList = fixIdsList(idsList)
validId: typing.Optional[str] = service.getValidId(idsList)
# Must be valid
if not validId:
raise Exception()
# Recover Id Info from service and validId
# idInfo = service.recoverIdInfo(validId)
# Notify Service that someone logged in/out
is_remote = self._params.get('session_type', '')[:4] in ('xrdp', 'RDP-')
if isLogin:
# Try to guess if this is a remote session
service.processLogin(validId, remote_login=is_remote)
else:
service.processLogout(validId, remote_login=is_remote)
# All right, service notified..
except Exception as e:
# Log error and continue
logger.error('Error notifying service: %s (%s)', e, self._params)
raise BlockAccess() from None
class Login(LoginLogout):
class Login(ActorV3Action):
"""
Notifies user logged id
"""
@ -597,7 +608,7 @@ class Login(LoginLogout):
): # If unamanaged host, lest do a bit more work looking for a service with the provided parameters...
if isManaged:
raise
self.notifyService(isLogin=True)
self.notifyService(action=NotifyActionType.LOGIN)
return ActorV3Action.actorResult(
{
@ -610,7 +621,7 @@ class Login(LoginLogout):
)
class Logout(LoginLogout):
class Logout(ActorV3Action):
"""
Notifies user logged out
"""
@ -653,7 +664,7 @@ class Logout(LoginLogout):
): # If unamanaged host, lest do a bit more work looking for a service with the provided parameters...
if isManaged:
raise
self.notifyService(isLogin=False) # Logout notification
self.notifyService(NotifyActionType.LOGOUT) # Logout notification
return ActorV3Action.actorResult(
'notified'
) # Result is that we have not processed the logout in fact, but notified the service
@ -761,7 +772,7 @@ class Unmanaged(ActorV3Action):
# Try to infer the ip from the valid id (that could be an IP or a MAC)
ip: str
try:
ip = next(x['ip'] for x in self._params['id'] if validId in (x['ip'], x['mac']))
ip = next(x['ip'] for x in self._params['id'] if validId in (x['ip'], x['mac']))
except StopIteration:
ip = self._params['id'][0]['ip'] # Get first IP if no valid ip found
@ -802,21 +813,22 @@ class Notify(ActorV3Action):
def get(self) -> typing.MutableMapping[str, typing.Any]:
logger.debug('Args: %s, Params: %s', self._args, self._params)
if (
'action' not in self._params
or 'token' not in self._params
or self._params['action'] not in ('login', 'logout')
):
# Requested login or logout
raise RequestError('Invalid parameters')
try:
action = NotifyActionType(self._params['action'])
token = self._params['token'] # pylint: disable=unused-variable # Just to check it exists
except Exception as e:
# Requested login, logout or whatever
raise RequestError('Invalid parameters') from e
try:
# Check block manually
checkBlockedIp(self._request) # pylint: disable=protected-access
if self._params['action'] == 'login':
if action == NotifyActionType.LOGIN:
Login.action(typing.cast(Login, self))
else:
elif action == NotifyActionType.LOGOUT:
Logout.action(typing.cast(Logout, self))
elif action == NotifyActionType.DATA:
self.notifyService(action)
return ActorV3Action.actorResult('ok')
except UserService.DoesNotExist:

View File

@ -139,9 +139,7 @@ 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
@ -150,9 +148,7 @@ 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
@ -270,9 +266,7 @@ class Service(Module):
# Keep untouched if maxServices is not present
def requestServicesForAssignation(
self, **kwargs
) -> typing.Iterable[UserDeployment]:
def requestServicesForAssignation(self, **kwargs) -> typing.Iterable[UserDeployment]:
"""
override this if mustAssignManualy is True
@params kwargs: Named arguments
@ -311,9 +305,7 @@ class Service(Module):
return []
def assignFromAssignables(
self, assignableId: str,
user: 'models.User',
userDeployment: UserDeployment
self, assignableId: str, user: 'models.User', userDeployment: UserDeployment
) -> str:
"""
Assigns from it internal assignable list to an user
@ -384,6 +376,18 @@ class Service(Module):
"""
return
def notifyData(self, id: typing.Optional[str], data: str) -> None:
"""
Processes a custom data notification, that must be interpreted by the service itself.
This allows "token actors" to communicate with service directly, what is needed for
some kind of services (like LinuxApps)
Args:
id (typing.Optional[str]): Id validated through "getValidId". May be None if not validated (or not provided)
data (str): Data to process
"""
return
def storeIdInfo(self, id: str, data: typing.Any) -> None:
self.storage.putPickle('__nfo_' + id, data)
@ -401,9 +405,7 @@ class Service(Module):
from uds.models import Service as DBService # pylint: disable=import-outside-toplevel
if self.getUuid():
log.doLog(
DBService.objects.get(uuid=self.getUuid()), level, message, log.LogSource.SERVICE
)
log.doLog(DBService.objects.get(uuid=self.getUuid()), level, message, log.LogSource.SERVICE)
@classmethod
def canAssign(cls) -> bool: