diff --git a/actor/src/udsactor/client.py b/actor/src/udsactor/client.py index 700bb99f..57a33b2b 100644 --- a/actor/src/udsactor/client.py +++ b/actor/src/udsactor/client.py @@ -198,7 +198,7 @@ class UDSActorClient(threading.Thread): # pylint: disable=too-many-instance-att time.sleep(1.3) # Sleeps between loop iterations self._loginInfo = None - self.api.logout(platform.operations.getCurrentUser() + self._extraLogoff) + self.api.logout(platform.operations.getCurrentUser() + self._extraLogoff, platform.operations.getSessionType()) except Exception as e: logger.error('Error on client loop: %s', e) diff --git a/actor/src/udsactor/rest.py b/actor/src/udsactor/rest.py index 86dfe48a..446c974b 100644 --- a/actor/src/udsactor/rest.py +++ b/actor/src/udsactor/rest.py @@ -329,11 +329,7 @@ class UDSServerApi(UDSApi): ) -> types.LoginResultInfoType: if not token: return types.LoginResultInfoType( - ip='0.0.0.0', # nosec: this is not a binding - hostname=UNKNOWN, - dead_line=None, - max_idle=None, - session_id=None, + ip='0.0.0.0', hostname=UNKNOWN, dead_line=None, max_idle=None, session_id=None ) payload = { 'type': actor_type or types.MANAGED, @@ -344,7 +340,7 @@ class UDSServerApi(UDSApi): 'secret': secret or '', } result = self._doPost('login', payload) - return types.LoginResultInfoType( # nosec: this is not a binding + return types.LoginResultInfoType( ip=result['ip'], hostname=result['hostname'], dead_line=result['dead_line'], @@ -358,20 +354,22 @@ class UDSServerApi(UDSApi): token: str, username: str, session_id: typing.Optional[str], + session_type: str, interfaces: typing.Iterable[types.InterfaceInfoType], secret: typing.Optional[str], - ) -> None: + ) -> typing.Optional[str]: if not token: - return + return None payload = { 'type': actor_type or types.MANAGED, 'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces], 'token': token, 'username': username, + 'session_type': session_type, 'session_id': session_id or '', 'secret': secret or '', } - self._doPost('logout', payload) + return self._doPost('logout', payload) # Can be 'ok' or 'notified' def log(self, own_token: str, level: int, message: str) -> None: if not own_token: @@ -427,8 +425,11 @@ class UDSClientApi(UDSApi): session_id=result['session_id'], ) - def logout(self, username: str) -> None: - payLoad = {'username': username} + def logout(self, username: str, sessionType: typing.Optional[str]) -> None: + payLoad = { + 'username': username, + 'session_type': sessionType or UNKNOWN + } self.post('logout', payLoad) def ping(self) -> bool: diff --git a/actor/src/udsactor/service.py b/actor/src/udsactor/service.py index 11e93276..affa6f5f 100644 --- a/actor/src/udsactor/service.py +++ b/actor/src/udsactor/service.py @@ -379,6 +379,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes self._cfg.actorType, self._cfg.own_token, '', + '', self._interfaces, self._secret, ) @@ -496,9 +497,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes # If unmanaged, do initialization now, because we don't know before this # Also, even if not initialized, get a "login" notification token if not self.isManaged(): - self.initialize() - master_token = self._cfg.master_token - secret = self._secret + self._initialized = ( + self.initialize() + ) # Maybe it's a local login by an unmanaged host.... On real login, will execute initilize again + if self._initialized: + master_token = self._cfg.master_token + secret = self._secret # Own token will not be set if UDS did not assigned the initialized VM to an user # In that case, take master token (if machine is Unamanaged version) @@ -520,7 +524,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes return result - def logout(self, username: str) -> None: + def logout(self, username: str, sessionType: typing.Optional[str] = None) -> None: self._loggedIn = False master_token = self._cfg.master_token @@ -529,9 +533,20 @@ class CommonService: # pylint: disable=too-many-instance-attributes # In that case, take master token (if machine is Unamanaged version) token = self._cfg.own_token or master_token if token: - self._api.logout( - self._cfg.actorType, token, username, self._interfaces, self._secret - ) + # If logout is not processed (that is, not ok result), the logout has not been processed + if ( + self._api.logout( + self._cfg.actorType, + token, + username, + sessionType or '', + self._interfaces, + self._secret, + ) + != 'ok' + ): + logger.info('Logout from %s ignored as required by uds broker', username) + return self.onLogout(username) diff --git a/server/src/uds/REST/methods/actor_v3.py b/server/src/uds/REST/methods/actor_v3.py index 881ef35f..1a680f64 100644 --- a/server/src/uds/REST/methods/actor_v3.py +++ b/server/src/uds/REST/methods/actor_v3.py @@ -435,7 +435,7 @@ class Version(ActorV3Action): 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): + def notifyService(self, isLogin: bool) -> None: try: # If unmanaged, use Service locator service: 'services.Service' = Service.objects.get( @@ -462,12 +462,12 @@ class LoginLogout(ActorV3Action): # 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 - is_remote = self._params.get('session_type', '')[:4] in ('xrdp', 'RDP-') service.processLogin(validId, remote_login=is_remote) else: - service.processLogout(validId) + service.processLogout(validId, remote_login=is_remote) # All right, service notified... except Exception: @@ -578,6 +578,7 @@ class Logout(LoginLogout): if isManaged: raise self.notifyService(isLogin=False) # Logout notification + return ActorV3Action.actorResult('notified') # Result is that we have not processed the logout in fact, but notified the service return ActorV3Action.actorResult('ok') diff --git a/server/src/uds/core/services/service.py b/server/src/uds/core/services/service.py index 0cacc069..bb75ce66 100644 --- a/server/src/uds/core/services/service.py +++ b/server/src/uds/core/services/service.py @@ -344,7 +344,7 @@ class Service(Module): """ return - def processLogout(self, id: str) -> None: + def processLogout(self, id: str, remote_login: bool) -> None: """ In the case that a logout is invoked directly on an actor controlled machine with an service token, this method will be called with provided info by uds actor (parameters) diff --git a/server/src/uds/services/PhysicalMachines/service_multi.py b/server/src/uds/services/PhysicalMachines/service_multi.py index 18028407..438abe84 100644 --- a/server/src/uds/services/PhysicalMachines/service_multi.py +++ b/server/src/uds/services/PhysicalMachines/service_multi.py @@ -230,10 +230,13 @@ class IPMachinesService(IPServiceBase): # Sets maximum services for this self.maxDeployed = len(self._ips) - def canBeUsed(self, locked: typing.Optional[int], now: int) -> int: + def canBeUsed(self, locked: typing.Optional[typing.Union[str, int]], now: int) -> int: # If _maxSessionForMachine is 0, it can be used only if not locked # (that is locked is None) locked = locked or 0 + if isinstance(locked, str) and not '.' in locked: # Convert to int and treat it as a "locked" element + locked = int(locked) + if self._maxSessionForMachine <= 0: return not bool(locked) # If locked is None, it can be used @@ -341,19 +344,26 @@ class IPMachinesService(IPServiceBase): Process login for a machine not assigned to any user. ''' logger.debug('Processing login for %s: %s', self, id) + # Locate the IP on the storage theIP = IPServiceBase.getIp(id) now = getSqlDatetimeAsUnix() - locked = self.storage.getPickle(theIP) + locked: typing.Union[None, str, int] = self.storage.getPickle(theIP) if self.canBeUsed(locked, now): - self.storage.putPickle(theIP, now) # Lock it + self.storage.putPickle(theIP, str(now)) # Lock it - def processLogout(self, id: str) -> None: + def processLogout(self, id: str, remote_login: bool) -> None: ''' Process logout for a machine not assigned to any user. ''' logger.debug('Processing logout for %s: %s', self, id) - self.unassignMachine(id) + # Locate the IP on the storage + theIP = IPServiceBase.getIp(id) + locked: typing.Union[None, str, int] = self.storage.getPickle(theIP) + # If locked is str, has been locked by processLogin so we can unlock it + if isinstance(locked, str): + self.unassignMachine(id) + # If not proccesed by login, we cannot release it def notifyInitialization(self, id: str) -> None: '''