1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-10-06 11:33:43 +03:00
Files
openuds/server/src/uds/REST/methods/tickets.py

267 lines
10 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * 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.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import datetime
import logging
import typing
from uds.REST import Handler
from uds import models
from uds.core.managers.crypto import CryptoManager
from uds.core.util.model import process_uuid
from uds.core.util import ensure
from uds.core import exceptions
logger = logging.getLogger(__name__)
# Valid parameters accepted by ticket creation method
VALID_PARAMS = (
'authId',
'auth_id',
'authTag',
'auth_tag',
'authSmallName',
'auth',
'auth_name',
'username',
'realname',
'password',
'groups',
'servicePool',
'service_pool',
'transport', # Admited to be backwards compatible, but not used. Will be removed on a future release.
'force',
'userIp',
'user_ip',
)
# Enclosed methods under /tickets path
class Tickets(Handler):
"""
Processes tickets access requests.
Tickets are element used to "register" & "allow access" to users to a service.
Designed to be used by external systems (like web services) to allow access to users to services.
The rest API accepts the following parameters:
authId: uuid of the authenticator for the user | Mutually excluyents
authSmallName: tag of the authenticator (alias for "authTag") | But must include one of theese
authTag: tag of the authenticator |
auth: Name of authenticator |
userIp: Direccion IP del cliente. Si no se pasa, no se puede filtar
username:
password:
groups:
servicePool:
transport: Ignored. Transport must be auto-detected on ticket auth
force: If "1" or "true" will ensure that:
- Groups exists on authenticator
- servicePool has these groups in it's allowed list
"""
needs_admin = True # By default, staff is lower level needed
@staticmethod
def result(result: str = '', error: typing.Optional[str] = None) -> dict[str, typing.Any]:
"""
Returns a result for a Ticket request
"""
res = {'result': result, 'date': datetime.datetime.now()}
if error is not None:
res['error'] = error
return res
def get(self) -> typing.Any:
"""
Processes get requests, currently none
"""
logger.debug('Ticket args for GET: %s', self._args)
raise exceptions.rest.RequestError('Invalid request')
def _check_parameters(self) -> None:
# Parameters can only be theese
for p in self._params:
if p not in VALID_PARAMS:
logger.debug('Parameter %s not in valid ticket parameters list', p)
raise exceptions.rest.RequestError('Invalid parameters')
if len(self._args) != 1 or self._args[0] not in ('create',):
raise exceptions.rest.RequestError('Invalid method')
try:
for i in (
'authId',
'auth_id',
'authTag',
'auth_tag',
'auth_label',
'auth',
'auth_name',
'authSmallName',
):
if i in self._params:
raise StopIteration
if 'username' in self._params and 'groups' in self._params:
raise StopIteration()
raise exceptions.rest.RequestError('Invalid parameters (no auth or username/groups)')
except StopIteration:
pass # All ok
# Must be invoked as '/rest/ticket/create, with "username", ("authId" or "auth_id") or ("auth_tag" or "authSmallName" or "authTag"), "groups" (array) and optionally "time" (in seconds) as paramteres
def put(
self,
) -> dict[str, typing.Any]:
"""
Processes put requests, currently only under "create"
"""
logger.debug(self._args)
# Check that call is correct (pamateters, args, ...)
self._check_parameters()
force: bool = self.get_param('force') in ('1', 'true', 'True', True)
try:
service_pool_id: typing.Optional[str] = None
# First param is recommended, last ones are compatible with old versions
auth_id = self.get_param('auth_id', 'authId')
auth_name = self.get_param('auth_name', 'auth')
auth_label = self.get_param('auth_label', 'auth_tag', 'authTag', 'authSmallName')
# Will raise an exception if no auth found
if auth_id:
auth = models.Authenticator.objects.get(uuid=process_uuid(auth_id.lower()))
elif auth_name:
auth = models.Authenticator.objects.get(name=auth_name)
else:
auth = models.Authenticator.objects.get(small_name=auth_label)
username: str = self.get_param('username')
password: str = self.get_param('password')
# Some machines needs password, depending on configuration
groups_ids: list[str] = []
for group_name in ensure.as_list(self.get_param('groups')):
try:
groups_ids.append(auth.groups.get(name=group_name).uuid or '')
except Exception:
logger.info(
'Group %s from ticket does not exists on auth %s, forced creation: %s',
group_name,
auth,
force,
)
if force: # Force creation by call
groups_ids.append(
auth.groups.create(
name=group_name,
comments='Autocreated form ticket by using force paratemeter',
).uuid
or ''
)
if not groups_ids: # No valid group in groups names
raise exceptions.rest.RequestError(
'Authenticator does not contain ANY of the requested groups and force is not used'
)
try:
time = int(self.get_param('time') or 60)
time = 60 if time < 1 else time
except Exception:
time = 60
realname: str = self.get_param('realname', 'username') or ''
pool_uuid = self.get_param('servicepool', 'servicePool')
if pool_uuid:
# Check if is pool or metapool
pool_uuid = process_uuid(pool_uuid)
pool: typing.Union[models.ServicePool, models.MetaPool]
try:
pool = models.MetaPool.objects.get(
uuid=pool_uuid
) # If not an metapool uuid, will process it as a servicePool
if force:
# First, add groups to metapool
for group_to_add in set(groups_ids) - set(pool.assignedGroups.values_list('uuid', flat=True)):
pool.assignedGroups.add(auth.groups.get(uuid=group_to_add))
# And now, to ALL metapool members, even those disabled
for meta_member in pool.members.all():
# Now add groups to pools
for group_to_add in set(groups_ids) - set(
meta_member.pool.assignedGroups.values_list('uuid', flat=True)
):
meta_member.pool.assignedGroups.add(auth.groups.get(uuid=group_to_add))
# For metapool, transport is ignored..
service_pool_id = 'M' + pool.uuid
except models.MetaPool.DoesNotExist:
pool = models.ServicePool.objects.get(uuid=pool_uuid)
# If forced that servicePool must honor groups
if force:
for group_to_add in set(groups_ids) - set(pool.assignedGroups.values_list('uuid', flat=True)):
pool.assignedGroups.add(auth.groups.get(uuid=group_to_add))
service_pool_id = 'F' + pool.uuid
except models.Authenticator.DoesNotExist:
return Tickets.result(error='Authenticator does not exists')
except models.ServicePool.DoesNotExist:
pass
return Tickets.result(error='Service pool (or metapool) does not exists')
except models.Transport.DoesNotExist:
pass
return Tickets.result(error='Transport does not exists')
except Exception as e:
return Tickets.result(error=str(e))
data = {
'username': username,
'password': CryptoManager.manager().encrypt(password),
'realname': realname,
'groups': groups_ids,
'auth': auth.uuid,
'servicePool': service_pool_id,
}
ticket = models.TicketStore.create(data)
return Tickets.result(ticket)