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:
parent
4ecb520496
commit
56376ecd56
@ -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')
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
"""
|
||||
|
@ -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', ''),
|
||||
|
18
server/src/uds/migrations/0037_service_token.py
Normal file
18
server/src/uds/migrations/0037_service_token.py
Normal 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),
|
||||
),
|
||||
]
|
@ -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
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user