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:
parent
f14f36b0d0
commit
f4e953c9c9
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user