diff --git a/server/src/uds/auths/Sample/SampleAuth.py b/server/src/uds/auths/Sample/SampleAuth.py index b2f4d8f59..c09217599 100644 --- a/server/src/uds/auths/Sample/SampleAuth.py +++ b/server/src/uds/auths/Sample/SampleAuth.py @@ -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. # @@ -38,7 +38,10 @@ from uds.core.ui import gui from uds.core import auths 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__) @@ -125,7 +128,9 @@ class SampleAuth(auths.Authenticator): # unserialization, and at this point all will be default values # so self.groups.value will be [] if values and len(self.groups.value) < 2: - raise auths.Authenticator.ValidationException(_('We need more than two groups!')) + raise auths.Authenticator.ValidationException( + _('We need more than two groups!') + ) def searchUsers(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]: """ @@ -137,7 +142,13 @@ class SampleAuth(auths.Authenticator): facility for users. In our case, we will simply return a list of users (array of dictionaries with ids and names) with the pattern plus 1..10 """ - return [{'id': '{0}-{1}'.format(pattern, a), 'name': '{0} number {1}'.format(pattern, a)} for a in range(1, 10)] + return [ + { + 'id': '{0}-{1}'.format(pattern, a), + 'name': '{0} number {1}'.format(pattern, a), + } + for a in range(1, 10) + ] def searchGroups(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]: """ @@ -154,7 +165,9 @@ class SampleAuth(auths.Authenticator): res.append({'id': g, 'name': ''}) return res - def authenticate(self, username: str, credentials: str, groupsManager: 'auths.GroupsManager') -> bool: + def authenticate( + self, username: str, credentials: str, groupsManager: 'auths.GroupsManager' + ) -> bool: """ This method is invoked by UDS whenever it needs an user to be authenticated. It is used from web interface, but also from administration interface to @@ -196,7 +209,9 @@ class SampleAuth(auths.Authenticator): :note: groupsManager is an in/out parameter """ - if username != credentials: # All users with same username and password are allowed + if ( + username != credentials + ): # All users with same username and password are allowed return False # Now the tricky part. We will make this user belong to groups that contains at leat @@ -247,11 +262,17 @@ class SampleAuth(auths.Authenticator): # I know, this is a bit ugly, but this is just a sample :-) res = '

Login name:

' - res += '

Login

' return res - def authCallback(self, parameters: typing.Dict[str, typing.Any], gm: 'auths.GroupsManager') -> typing.Optional[str]: + def authCallback( + self, parameters: typing.Dict[str, typing.Any], gm: 'auths.GroupsManager' + ) -> typing.Optional[str]: """ We provide this as a sample of callback for an user. We will accept all petitions that has "user" parameter @@ -286,6 +307,7 @@ class SampleAuth(auths.Authenticator): Here, we will set the state to "Inactive" and realName to the same as username, but twice :-) """ from uds.core.util.state import State + usrData['real_name'] = usrData['name'] + ' ' + usrData['name'] usrData['state'] = State.INACTIVE diff --git a/server/src/uds/services/PhysicalMachines/service_multi.py b/server/src/uds/services/PhysicalMachines/service_multi.py index 421630cac..4474f567c 100644 --- a/server/src/uds/services/PhysicalMachines/service_multi.py +++ b/server/src/uds/services/PhysicalMachines/service_multi.py @@ -97,6 +97,19 @@ class IPMachinesService(IPServiceBase): tab=gui.ADVANCED_TAB, ) + maxSessionForMachine = gui.NumericField( + length=3, + label=_('Max session per machine'), + defvalue='0', + order=3, + tooltip=_('Maximum session duration before UDS thinks this machine got locked and releases it (hours). 0 means "never".'), + minValue=0, + required=True, + tab=gui.ADVANCED_TAB, + ) + + + # Description of service typeName = _('Static Multiple IP') typeType = 'IPMachinesService' @@ -120,6 +133,7 @@ class IPMachinesService(IPServiceBase): _token: str = '' _port: int = 0 _skipTimeOnFailure: int = 0 + _maxSessionForMachine: int = 0 def initialize(self, values: 'Module.ValuesType') -> None: if values is None: @@ -141,7 +155,7 @@ class IPMachinesService(IPServiceBase): d = self.storage.readData('ips') old_ips = pickle.loads(d) if d and isinstance(d, bytes) else [] # dissapeared ones - dissapeared = set(i.split('~')[0] for i in old_ips) - set(i.split('~')[0] for i in self._ips) + dissapeared = set(IPServiceBase.getIp(i.split('~')[0]) for i in old_ips) - set(i.split('~')[0] for i in self._ips) with transaction.atomic(): for removable in dissapeared: self.storage.remove(removable) @@ -149,6 +163,7 @@ class IPMachinesService(IPServiceBase): self._token = self.token.value.strip() self._port = self.port.value self._skipTimeOnFailure = self.skipTimeOnFailure.num() + self._maxSessionForMachine = self.maxSessionForMachine.num() def getToken(self): return self._token or None @@ -161,16 +176,18 @@ class IPMachinesService(IPServiceBase): 'token': self._token, 'port': str(self._port), 'skipTimeOnFailure': str(self._skipTimeOnFailure), + 'maxSessionForMachine': str(self._maxSessionForMachine), } def marshal(self) -> bytes: self.storage.saveData('ips', pickle.dumps(self._ips)) return b'\0'.join( [ - b'v4', + b'v5', self._token.encode(), str(self._port).encode(), str(self._skipTimeOnFailure).encode(), + str(self._maxSessionForMachine).encode(), ] ) @@ -186,24 +203,41 @@ class IPMachinesService(IPServiceBase): self._ips = [] if values[0] != b'v1': self._token = values[1].decode() - if values[0] in (b'v3', b'v4'): + if values[0] in (b'v3', b'v4', b'v5'): self._port = int(values[2].decode()) - if values[0] == b'v4': + if values[0] in (b'v4', b'v5'): self._skipTimeOnFailure = int(values[3].decode()) + if values[0] == b'v5': + self._maxSessionForMachine = int(values[4].decode()) # Sets maximum services for this self.maxDeployed = len(self._ips) + def canBeUsed(self, locked: typing.Optional[int], now: int) -> int: + # If _maxSessionForMachine is 0, it can be used only if not locked + # (that is locked is None) + if self._maxSessionForMachine <= 0: + return bool(locked) + + if not isinstance(locked, int): # May have "old" data, that was the IP repeated + return False + + if not locked or locked < now - self._maxSessionForMachine * 3600: + return True + + return False + + def getUnassignedMachine(self) -> typing.Optional[str]: # Search first unassigned machine try: now = getSqlDatetimeAsUnix() - consideredFreeTime = now - config.GlobalConfig.SESSION_EXPIRE_TIME.getInt(force=False) * 3600 + for ip in self._ips: theIP = IPServiceBase.getIp(ip) theMAC = IPServiceBase.getMac(ip) locked = self.storage.getPickle(theIP) - if not locked or locked < consideredFreeTime: + if self.canBeUsed(locked, now): if self._port > 0 and self._skipTimeOnFailure > 0 and self.cache.get('port{}'.format(theIP)): continue # The check failed not so long ago, skip it... self.storage.putPickle(theIP, now) @@ -246,9 +280,7 @@ class IPMachinesService(IPServiceBase): def unassignMachine(self, ip: str) -> None: try: - if ';' in ip: - ip = ip.split(';')[0] # ; means that HAS an attached MAC - self.storage.remove(ip) + self.storage.remove(IPServiceBase.getIp(ip)) except Exception: logger.exception("Exception at getUnassignedMachine") @@ -271,8 +303,10 @@ class IPMachinesService(IPServiceBase): theIP = IPServiceBase.getIp(assignableId) theMAC = IPServiceBase.getMac(assignableId) - if self.storage.readData(theIP) is None: - self.storage.saveData(theIP, theIP) + now = getSqlDatetimeAsUnix() + locked = self.storage.getPickle(theIP) + if self.canBeUsed(locked, now): + self.storage.saveData(theIP, now) if theMAC: theIP += ';' + theMAC return userServiceInstance.assign(theIP) diff --git a/server/src/uds/web/views/auth.py b/server/src/uds/web/views/auth.py index 13ce85a5a..63ef4865d 100644 --- a/server/src/uds/web/views/auth.py +++ b/server/src/uds/web/views/auth.py @@ -86,7 +86,7 @@ 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']