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

Fixed type checkings and detection of client launched when machine not ready

This commit is contained in:
Adolfo Gómez García 2021-07-20 13:32:28 +02:00
parent f14f36b0d0
commit f4e953c9c9
12 changed files with 76 additions and 28 deletions

View File

@ -46,7 +46,7 @@ from uds.core.managers import cryptoManager
# 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 ExtendedHttpRequestWithUser
logger = logging.getLogger(__name__)
@ -100,7 +100,7 @@ class Handler:
needs_admin: typing.ClassVar[bool] = False # By default, the methods will be accessible by anyone if nothing else indicated
needs_staff: typing.ClassVar[bool] = False # By default, staff
_request: 'ExtendedHttpRequest' # It's a modified HttpRequest
_request: 'ExtendedHttpRequestWithUser' # It's a modified HttpRequest
_path: str
_operation: str
_params: typing.Any # This is a deserliazied object from request. Can be anything as 'a' or {'a': 1} or ....
@ -113,7 +113,7 @@ class Handler:
# method names: 'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'
def __init__(self, request: 'ExtendedHttpRequest', path: str, operation: str, params: typing.Any, *args: str, **kwargs):
def __init__(self, request: 'ExtendedHttpRequestWithUser', path: str, operation: str, params: typing.Any, *args: str, **kwargs):
logger.debug('Data: %s %s %s', self.__class__, self.needs_admin, self.authenticated)
if (self.needs_admin or self.needs_staff) and not self.authenticated: # If needs_admin, must also be authenticated

View File

@ -240,11 +240,12 @@ class Initialize(ActorV3Action):
"""
# First, validate token...
logger.debug('Args: %s, Params: %s', self._args, self._params)
service: typing.Optional[Service] = None
try:
# First, try to locate an user service providing this token.
if self._params['type'] == UNMANAGED:
# If unmanaged, use Service locator
service: Service = Service.objects.get(token=self._params['token'])
service = Service.objects.get(token=self._params['token'])
# Locate an userService that belongs to this service and which
# Build the possible ids and make initial filter to match service
idsList = [x['ip'] for x in self._params['id']] + [
@ -262,6 +263,30 @@ class Initialize(ActorV3Action):
# Valid actor token, now validate access allowed. That is, look for a valid mac from the ones provided.
try:
# Set full filter
dbFilter = dbFilter.filter(
unique_id__in=idsList,
state__in=[State.USABLE, State.PREPARING],
)
# If no UserService exists,
# ist managed (service exists), then it's a "local login"
if dbFilter.exists() is False and service:
# The userService does not exists, try to lock the id on the service
serviceInstance = service.getInstance()
lockedId = serviceInstance.lockId(idsList)
if lockedId:
# Return an "generic" result allowing login/logout processing
return ActorV3Action.actorResult(
{
'own_token': self._params['token'],
'unique_id': lockedId,
'os': None,
}
)
else: # if no lock, return empty result
raise Exception() # Unmanaged host
userService: UserService = next(
iter(
dbFilter.filter(
@ -463,6 +488,16 @@ class Login(LoginLogout):
name = 'login'
# payload received
# {
# 'type': actor_type or types.MANAGED,
# 'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
# 'token': token,
# 'username': username,
# 'session_type': sessionType,
# 'secret': secret or '',
# }
@staticmethod
def process_login(
userService: UserService, username: str

View File

@ -38,7 +38,7 @@ from django.utils.translation import ugettext as _
from django.urls import reverse
from uds.REST import Handler
from uds.REST import RequestError
from uds.models import TicketStore, user
from uds.models import TicketStore
from uds.models import User
from uds.web.util import errors
from uds.core.managers import cryptoManager, userServiceManager
@ -166,9 +166,14 @@ class Client(Handler):
'params': codecs.encode(codecs.encode(json.dumps(params).encode(), 'bz2'), 'base64').decode(),
})
except ServiceNotReadyError as e:
# Set that client has accesed userService
if e.userService:
e.userService.setProperty('accessedByClient', '1')
# Refresh ticket and make this retrayable
TicketStore.revalidate(ticket, 20) # Retry will be in at most 5 seconds, so 20 is fine :)
return Client.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True)
except Exception as e:
logger.exception("Exception")
return Client.result(error=str(e))

View File

@ -88,7 +88,7 @@ class Connection(Handler):
# Ensure user is present on request, used by web views methods
self._request.user = self._user
return Connection.result(result=getServicesData(typing.cast(ExtendedHttpRequestWithUser, self._request)))
return Connection.result(result=services.getServicesData(typing.cast(ExtendedHttpRequestWithUser, self._request)))
def connection(self, doNotCheck: bool = False):
idService = self._args[0]
@ -152,7 +152,7 @@ class Connection(Handler):
self._request.ip, hostname
) # Store where we are accessing from so we can notify Service
if not ip:
if not ip or not transportInstance:
raise ServiceNotReadyError()
transportScript = transportInstance.getEncodedTransportScript(

View File

@ -345,7 +345,7 @@ def authInfoUrl(authenticator: typing.Union[str, bytes, Authenticator]) -> str:
def webLogin(
request: 'ExtendedHttpRequest', response: 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.

View File

@ -491,7 +491,7 @@ class Authenticator(Module): # pylint: disable=too-many-public-methods
"""
return None
def getInfo(self, parameters: typing.Dict[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

@ -736,7 +736,7 @@ class UserServiceManager:
os: typing.MutableMapping,
srcIp: str,
idService: str,
idTransport: str,
idTransport: typing.Optional[str],
doTest: bool = True,
clientHostname: typing.Optional[str] = None,
) -> typing.Tuple[
@ -750,7 +750,7 @@ class UserServiceManager:
Get service info from user service
"""
if idService[0] == 'M': # Meta pool
return self.getMeta(user, srcIp, os, idService[1:], idTransport)
return self.getMeta(user, srcIp, os, idService[1:], idTransport or '')
userService = self.locateUserService(user, idService, create=True)
@ -900,7 +900,7 @@ class UserServiceManager:
ip,
)
raise ServiceNotReadyError(
code=serviceNotReadyCode, service=userService, transport=transport
code=serviceNotReadyCode, userService=userService, transport=transport
)
def getMeta(

View File

@ -102,10 +102,10 @@ class ServiceNotReadyError(ServiceException):
Can include an optional code error
"""
code: int
service: 'UserService'
transport: 'Transport'
userService: typing.Optional['UserService']
transport: typing.Optional['Transport']
def __init__(self, *args, **kwargs):
super(ServiceNotReadyError, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.code = kwargs.get('code', 0x0000)
self.service = kwargs.get('service', None)
self.userService = kwargs.get('service', None)
self.transport = kwargs.get('transport', None)

View File

@ -303,17 +303,17 @@ class Service(Module):
"""
return None
def lockId(self, id: str) -> bool:
def lockId(self, id: typing.List[str]) -> typing.Optional[str]:
"""
Locks the id, so it cannot be used by a service pool.
Args:
id (str): Id to lock
id (typing.List[str]): Id to lock (list of possible ids)
Returns:
bool: True if the id has been locked, False if not
str: Valid id of locked element, or None if no id found
"""
return False
return None
def processLogin(self, id: str, remote_login: bool) -> None:
"""

View File

@ -88,9 +88,11 @@ class User(UUIDModel):
ordering = ('name',)
app_label = 'uds'
# unique_together = (("manager", "name"),)
# unique_together = (("manager", "name"),)
constraints = [
models.UniqueConstraint(fields=['manager', 'name'], name='u_usr_manager_name')
models.UniqueConstraint(
fields=['manager', 'name'], name='u_usr_manager_name'
)
]
def getUsernameForAuth(self) -> str:

View File

@ -44,6 +44,7 @@ class UserPreference(models.Model):
"""
This class represents a single user preference for an user and a module
"""
module = models.CharField(max_length=32, db_index=True)
name = models.CharField(max_length=32, db_index=True)
value = models.CharField(max_length=128, db_index=True)
@ -56,4 +57,6 @@ class UserPreference(models.Model):
app_label = 'uds'
def __str__(self) -> str:
return '{}.{} = "{}" for user {}'.format(self.module, self.name, self.value, self.user)
return '{}.{} = "{}" for user {}'.format(
self.module, self.name, self.value, self.user
)

View File

@ -32,7 +32,7 @@ import logging
import typing
from django.urls import reverse
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
@ -49,6 +49,9 @@ from uds.core.util.model import processUuid
from uds.models import Authenticator, ServicePool
from uds.models import TicketStore
if typing.TYPE_CHECKING:
from uds.core.util.request import ExtendedHttpRequestWithUser
logger = logging.getLogger(__name__)
# The callback is now a two stage, so we can use cookies samesite policy to "Lax"
@ -83,7 +86,7 @@ def authCallback(request: HttpRequest, authName: str) -> HttpResponse:
return errors.exceptionView(request, e)
def authCallback_stage2(request: HttpRequest, ticketId: str) -> HttpResponse:
def authCallback_stage2(request: ExtendedHttpRequestWithUser, ticketId: str) -> HttpResponse:
try:
ticket = TicketStore.get(ticketId)
params: typing.Dict[str, typing.Any] = ticket['params']
@ -134,7 +137,7 @@ def authInfo(request: 'HttpRequest', authName: str) -> HttpResponse:
logger.debug('Getting info for %s', authName)
authenticator = Authenticator.objects.get(name=authName)
authInstance = authenticator.getInstance()
if authInstance.getInfo == auths.Authenticator.getInfo:
if typing.cast(typing.Any, authInstance.getInfo) == auths.Authenticator.getInfo:
raise Exception() # This authenticator do not provides info
info = authInstance.getInfo(request.GET)
@ -170,7 +173,7 @@ def customAuth(request: 'HttpRequest', idAuth: str) -> HttpResponse:
@never_cache
def ticketAuth(request: 'HttpRequest', 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
"""
@ -209,7 +212,7 @@ def ticketAuth(request: 'HttpRequest', ticketId: str) -> HttpResponse: # pylint
raise auths.exceptions.InvalidUserException()
# Add groups to user (replace existing groups)
usr.groups.set(grps)
usr.groups.set(grps) # type: ignore
# Force cookie generation
webLogin(request, None, usr, password)