1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-06 08:58:37 +03:00

Updating v3 actor so we can honor service tokens for some kind of actions

This commit is contained in:
Adolfo Gómez García 2020-02-08 20:35:43 +01:00
parent 4ecb520496
commit 56376ecd56
6 changed files with 93 additions and 32 deletions

View File

@ -38,7 +38,9 @@ from uds.models import (
getSqlDatetime,
ActorToken,
UserService,
TicketStore
Service,
TicketStore,
)
#from uds.core import VERSION
@ -182,35 +184,56 @@ class Initiialize(ActorV3Action):
Initialize method expect a json POST with this fields:
* version: str -> Actor version
* token: str -> Valid Actor Token (if invalid, will return an error)
* id: List[dict] -> List of dictionary containing id and mac:
Will return on field "result" a dictinary with:
* own_token: Optional[str] -> Personal uuid for the service (That, on service, will be used from now onwards). If None, there is no own_token
* unique_id: Optional[str] -> If not None, unique id for the service
* max_idle: Optional[int] -> If not None, max configured Idle for the vm
* os: Optional[dict] -> Data returned by os manager for setting up this service.
On error, will return Empty (None) result, and error field
* id: List[dict] -> List of dictionary containing ip and mac:
Example:
{
'version': '3.0',
'token': 'asbdasdf',
'maxIdle': 99999 or None,
'id': [
{
'mac': 'xxxxx',
'mac': 'aa:bb:cc:dd:ee:ff',
'ip': 'vvvvvvvv'
}, ...
]
}
Will return on field "result" a dictinary with:
* own_token: Optional[str] -> Personal uuid for the service (That, on service, will be used from now onwards). If None, there is no own_token
* unique_id: Optional[str] -> If not None, unique id for the service (normally, mac adress of recognized interface)
* max_idle: Optional[int] -> If not None, max configured Idle for the vm. Remember it can be a null value
* os: Optional[dict] -> Data returned by os manager for setting up this service.
Example:
{
'own_token' 'asdfasdfasdffsadfasfd'
'unique_ids': 'aa:bb:cc:dd:ee:ff'
'maxIdle': 34
}
On error, will return Empty (None) result, and error field
"""
# First, validate token...
logger.debug('Args: %s, Params: %s', self._args, self._params)
try:
ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check
# First, try to locate an user service providing this token.
# User service has precedence over ActorToken
try:
service: 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']] + [x['mac'] for x in self._params['id']][:10]
dbFilter = UserService.objects.filter(deployed_service__service=service)
except Service.DoesNotExist:
# If not service provided token, use actor tokens
ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check
# Build the possible ids and make initial filter to match ANY userservice with provided MAC
idsList = [i['mac'] for i in self._params['id'][:5]]
dbFilter = UserService.objects.all()
# Valid actor token, now validate access allowed. That is, look for a valid mac from the ones provided.
try:
userService: UserService = next(
iter(UserService.objects.filter(
unique_id__in=[i['mac'] for i in self._params.get('id')[:5]],
iter(dbFilter.filter(
unique_id__in=idsList,
state__in=[State.USABLE, State.PREPARING]
))
)
@ -371,7 +394,7 @@ class Logout(ActorV3Action):
if osManager:
if osManager.isRemovableOnLogout(userService):
logger.debug('Removable on logout: %s', osManager)
userService.remove()
userService.remove()
return ActorV3Action.actorResult('ok')

View File

@ -36,7 +36,7 @@ import typing
from django.db import IntegrityError
from django.utils.translation import ugettext as _
from uds.models import Service, UserService, Tag, Proxy
from uds import models
from uds.core import services
from uds.core.util import log
@ -65,7 +65,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
custom_methods = ['servicesPools']
@staticmethod
def serviceInfo(item: Service) -> typing.Dict[str, typing.Any]:
def serviceInfo(item: models.Service) -> typing.Dict[str, typing.Any]:
info = item.getType()
return {
@ -85,7 +85,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
}
@staticmethod
def serviceToDict(item: Service, perm: int, full: bool = False) -> typing.Dict[str, typing.Any]:
def serviceToDict(item: models.Service, perm: int, full: bool = False) -> typing.Dict[str, typing.Any]:
"""
Convert a service db item to a dict for a rest response
:param item: Service item (db)
@ -102,7 +102,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
'proxy_id': item.proxy.uuid if item.proxy is not None else '-1',
'proxy': item.proxy.name if item.proxy is not None else '',
'deployed_services_count': item.deployedServices.count(),
'user_services_count': UserService.objects.filter(deployed_service__service=item).exclude(state__in=State.INFO_STATES).count(),
'user_services_count': models.UserService.objects.filter(deployed_service__service=item).exclude(state__in=State.INFO_STATES).count(),
'maintenance_mode': item.provider.maintenance_mode,
'permission': perm
}
@ -127,7 +127,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
def getRowStyle(self, parent: 'Provider') -> typing.Dict[str, typing.Any]:
return {'field': 'maintenance_mode', 'prefix': 'row-maintenance-'}
def _deleteIncompleteService(self, service: Service): # pylint: disable=no-self-use
def _deleteIncompleteService(self, service: models.Service): # pylint: disable=no-self-use
"""
Deletes a service if it is needed to (that is, if it is not None) and silently catch any exception of this operation
:param service: Service to delete (may be None, in which case it does nothing)
@ -145,21 +145,21 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
fields = self.readFieldsFromParams(['name', 'comments', 'data_type', 'tags', 'proxy_id'])
tags = fields['tags']
del fields['tags']
service: typing.Optional[Service] = None
service: typing.Optional[models.Service] = None
proxyId = fields['proxy_id']
fields['proxy_id'] = None
logger.debug('Proxy id: %s', proxyId)
proxy: typing.Optional[Proxy] = None
proxy: typing.Optional[models.Proxy] = None
if proxyId != '-1':
try:
proxy = Proxy.objects.get(uuid=processUuid(proxyId))
proxy = models.Proxy.objects.get(uuid=processUuid(proxyId))
except Exception:
logger.exception('Getting proxy ID')
try:
if item is None: # Create new
if not item: # Create new
service = parent.services.create(**fields)
else:
service = parent.services.get(uuid=processUuid(item))
@ -168,14 +168,22 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
if service is None:
raise Exception('Cannot create service!')
service.tags.set([Tag.objects.get_or_create(tag=val)[0] for val in tags])
service.tags.set([models.Tag.objects.get_or_create(tag=val)[0] for val in tags])
service.proxy = proxy
service.data = service.getInstance(self._params).serialize() # This may launch an validation exception (the getInstance(...) part)
serviceInstance = service.getInstance(self._params)
# Store token if this service provides one
service.token = serviceInstance.getToken()
service.data = serviceInstance.serialize() # This may launch an validation exception (the getInstance(...) part)
service.save()
except Service.DoesNotExist:
except models.Service.DoesNotExist:
raise self.invalidItemException()
except IntegrityError: # Duplicate key probably
if service and service.token:
raise RequestError(_('Service token seems to be in use by other service. Please, select a new one.'))
raise RequestError(_('Element already exists (duplicate key error)'))
except services.Service.ValidationException as e:
if not item and service: # Only remove partially saved element if creating new (if editing, ignore this)
@ -254,7 +262,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
localGui = self.addDefaultFields(service.guiDescription(service), ['name', 'comments', 'tags'])
for field in [{
'name': 'proxy_id',
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in Proxy.objects.all()]),
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in models.Proxy.objects.all()]),
'label': _('Proxy'),
'tooltip': _('Proxy for services behind a firewall'),
'type': gui.InputField.CHOICE_TYPE,

View File

@ -49,7 +49,7 @@ if typing.TYPE_CHECKING:
from uds.core.util.unique_name_generator import UniqueNameGenerator
from uds.core.util.unique_mac_generator import UniqueMacGenerator
from uds.core.util.unique_gid_generator import UniqueGIDGenerator
from uds.models import ServicePoolPublication, User
from uds import models
class Service(Module):
@ -264,7 +264,7 @@ class Service(Module):
"""
return []
def assignFromAssignables(self, assignableId: str, user: 'User', userDeployment: UserDeployment) -> str:
def assignFromAssignables(self, assignableId: str, user: 'models.User', userDeployment: UserDeployment) -> str:
"""
Assigns from it internal assignable list to an user
@ -278,6 +278,15 @@ class Service(Module):
"""
return State.FINISHED
def getToken(self) -> typing.Optional[str]:
"""
This method is to allow some kind of services to register a "token", so special actors
(for example, those for static pool of machines) can communicate with UDS services for
several actor.
By default, services does not have a token
"""
return None
@classmethod
def canAssign(cls) -> bool:
"""

View File

@ -236,11 +236,14 @@ class gui:
_data: typing.Dict[str, typing.Any]
def __init__(self, **options):
defvalue = options.get('defvalue', '')
if callable(defvalue):
defvalue = defvalue()
self._data = {
'length': options.get('length', gui.InputField.DEFAULT_LENTGH), # Length is not used on some kinds of fields, but present in all anyway
'required': options.get('required', False),
'label': options.get('label', ''),
'defvalue': str(options.get('defvalue', '')),
'defvalue': str(defvalue),
'rdonly': options.get('rdonly', False), # This property only affects in "modify" operations
'order': options.get('order', 0),
'tooltip': options.get('tooltip', ''),

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2020-02-08 18:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uds', '0036_auto_20200131_1224'),
]
operations = [
migrations.AddField(
model_name='service',
name='token',
field=models.CharField(blank=True, default=None, max_length=32, null=True, unique=True),
),
]

View File

@ -59,16 +59,16 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore
A Service represents an specidied type of service offered to final users, with it configuration (i.e. a KVM Base Machine for cloning
or a Terminal Server configuration).
"""
# pylint: disable=model-missing-unicode
provider: 'Provider' = models.ForeignKey(Provider, related_name='services', on_delete=models.CASCADE)
# Proxy for this service
proxy: typing.Optional['Proxy'] = models.ForeignKey(Proxy, null=True, blank=True, related_name='services', on_delete=models.CASCADE)
token = models.CharField(max_length=32, default=None, null=True, blank=True, unique=True)
_cachedInstance: typing.Optional['services.Service'] = None
class Meta(ManagedObjectModel.Meta):
class Meta(ManagedObjectModel.Meta): # pylint: disable=too-few-public-methods
"""
Meta class to declare default order and unique multiple field index
"""