mirror of
https://github.com/dkmstr/openuds.git
synced 2025-01-11 05:17:55 +03:00
Advancing on servers management
This commit is contained in:
parent
9879eaf8d6
commit
5a420e0967
@ -32,11 +32,16 @@
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import gettext
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from uds import models
|
||||
from uds.core import types
|
||||
from uds.core.types import permissions
|
||||
from uds.core.types import permissions as permtypes
|
||||
from uds.core.types import rest, servers
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util import permissions
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.REST.exceptions import NotFound, RequestError
|
||||
from uds.REST.model import OK, ModelHandler
|
||||
|
||||
@ -85,7 +90,7 @@ class ServersTokens(ModelHandler):
|
||||
raise RequestError('Delete need one and only one argument')
|
||||
|
||||
self.ensureAccess(
|
||||
self.model(), permissions.PermissionType.ALL, root=True
|
||||
self.model(), permtypes.PermissionType.ALL, root=True
|
||||
) # Must have write permissions to delete
|
||||
|
||||
try:
|
||||
@ -102,30 +107,68 @@ class ServersGroups(ModelHandler):
|
||||
model_filter = {
|
||||
'type__in': [
|
||||
types.servers.ServerType.SERVER,
|
||||
types.servers.ServerType.LEGACY,
|
||||
types.servers.ServerType.UNMANAGED,
|
||||
]
|
||||
}
|
||||
path = 'servers'
|
||||
name = 'groups'
|
||||
|
||||
save_fields = ['name', 'comments', 'type', 'subtype']
|
||||
table_title = _('Servers Groups')
|
||||
table_fields = [
|
||||
{'stamp': {'title': _('Date'), 'type': 'datetime'}},
|
||||
{'name': {'title': _('Name')}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'type': {'title': _('Type')}},
|
||||
{'ip': {'title': _('IP')}},
|
||||
{'subtype': {'title': _('Subtype')}},
|
||||
]
|
||||
|
||||
def item_as_dict(self, item: models.RegisteredServer) -> typing.Dict[str, typing.Any]:
|
||||
def getTypes(self, *args, **kwargs) -> typing.Generator[typing.Dict[str, typing.Any], None, None]:
|
||||
for i in servers.ServerSubType.manager().enum():
|
||||
v = rest.TypeInfo(name=i.description, type=f'{i.type.name}@{i.subtype}', description='', icon='').asDict(
|
||||
group=gettext('Managed') if i.managed else gettext('Unmanaged')
|
||||
)
|
||||
yield v
|
||||
|
||||
def getGui(self, type_: str) -> typing.List[typing.Any]:
|
||||
strServerType, serverSubType = type_.split('@')
|
||||
serverType = types.servers.ServerType[strServerType]
|
||||
serverSubType = serverSubType.lower()
|
||||
|
||||
logger.info('Server type: %s', serverType)
|
||||
return self.addField(
|
||||
self.addDefaultFields(
|
||||
[],
|
||||
['name', 'comments', 'tags'],
|
||||
),
|
||||
[
|
||||
{
|
||||
'name': 'type',
|
||||
'value': serverType, # As int
|
||||
'type': gui.InputField.Types.HIDDEN,
|
||||
},
|
||||
{
|
||||
'name': 'subtype',
|
||||
'value': serverSubType, # As str
|
||||
'type': gui.InputField.Types.HIDDEN,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def beforeSave(self, fields: typing.Dict[str, typing.Any]) -> None:
|
||||
|
||||
return super().beforeSave(fields)
|
||||
|
||||
def item_as_dict(self, item: 'models.RegisteredServerGroup') -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'name': str(_('Token isued by {} from {}')).format(item.username, item.ip),
|
||||
'stamp': item.stamp,
|
||||
'username': item.username,
|
||||
'ip': item.ip,
|
||||
'hostname': item.hostname,
|
||||
'token': item.token,
|
||||
'type': types.servers.ServerType(item.type).as_str(),
|
||||
'os': item.os_type,
|
||||
'name': item.name,
|
||||
'comments': item.comments,
|
||||
'host': item.host,
|
||||
'port': item.port,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'transports_count': item.transports.count(),
|
||||
'servers_count': item.servers.count(),
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
}
|
||||
|
||||
def delete(self) -> str:
|
||||
@ -140,7 +183,7 @@ class ServersGroups(ModelHandler):
|
||||
) # Must have write permissions to delete
|
||||
|
||||
try:
|
||||
self.model.objects.get(token=self._args[0]).delete()
|
||||
self.model.objects.get(uuid=processUuid(self._args[0])).delete()
|
||||
except self.model.DoesNotExist:
|
||||
raise NotFound('Element do not exists') from None
|
||||
|
||||
|
@ -40,9 +40,10 @@ import typing
|
||||
from django.db import IntegrityError, models
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from uds.core.consts import OK
|
||||
import uds.core.types.permissions
|
||||
import uds.core.types.rest
|
||||
from uds.core import exceptions as g_exceptions
|
||||
from uds.core.consts import OK
|
||||
from uds.core.module import Module
|
||||
from uds.core.ui import gui as uiGui
|
||||
from uds.core.util import log, permissions
|
||||
@ -68,6 +69,7 @@ LOG: typing.Final[str] = 'log'
|
||||
|
||||
FieldType = typing.Mapping[str, typing.Any]
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class BaseModelHandler(Handler):
|
||||
"""
|
||||
@ -110,11 +112,8 @@ class BaseModelHandler(Handler):
|
||||
v['gui']['tab'] = _(str(field['tab']))
|
||||
gui.append(v)
|
||||
return gui
|
||||
|
||||
|
||||
def addDefaultFields(
|
||||
self, gui: typing.List[typing.Any], flds: typing.List[str]
|
||||
) -> typing.List[typing.Any]:
|
||||
def addDefaultFields(self, gui: typing.List[typing.Any], flds: typing.List[str]) -> typing.List[typing.Any]:
|
||||
"""
|
||||
Adds default fields (based in a list) to a "gui" description
|
||||
:param gui: Gui list where the "default" fielsds will be added
|
||||
@ -176,9 +175,7 @@ class BaseModelHandler(Handler):
|
||||
'name': 'priority',
|
||||
'type': 'numeric',
|
||||
'label': _('Priority'),
|
||||
'tooltip': _(
|
||||
'Selects the priority of this element (lower number means higher priority)'
|
||||
),
|
||||
'tooltip': _('Selects the priority of this element (lower number means higher priority)'),
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
@ -228,9 +225,7 @@ class BaseModelHandler(Handler):
|
||||
key=lambda x: x['text'].lower(),
|
||||
),
|
||||
'label': _('Networks'),
|
||||
'tooltip': _(
|
||||
'Networks associated. If No network selected, will mean "all networks"'
|
||||
),
|
||||
'tooltip': _('Networks associated. If No network selected, will mean "all networks"'),
|
||||
'type': 'multichoice',
|
||||
'order': 101,
|
||||
'tab': uiGui.Tab.ADVANCED,
|
||||
@ -264,15 +259,12 @@ class BaseModelHandler(Handler):
|
||||
"""
|
||||
Returns a dictionary describing the type (the name, the icon, description, etc...)
|
||||
"""
|
||||
res = self.typeInfo(type_)
|
||||
res.update(
|
||||
{
|
||||
'name': _(type_.name()),
|
||||
'type': type_.type(),
|
||||
'description': _(type_.description()),
|
||||
'icon': type_.icon64().replace('\n', ''),
|
||||
}
|
||||
)
|
||||
res = uds.core.types.rest.TypeInfo(
|
||||
name=_(type_.name()),
|
||||
type=type_.type(),
|
||||
description=_(type_.description()),
|
||||
icon=type_.icon64().replace('\n', ''),
|
||||
).asDict(**self.typeInfo(type_))
|
||||
if hasattr(type_, 'group'):
|
||||
res['group'] = _(type_.group) # Add group info is it is contained
|
||||
return res
|
||||
@ -294,9 +286,7 @@ class BaseModelHandler(Handler):
|
||||
'subtitle': subtitle or '',
|
||||
}
|
||||
|
||||
def readFieldsFromParams(
|
||||
self, fldList: typing.List[str]
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
def readFieldsFromParams(self, fldList: typing.List[str]) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
Reads the indicated fields from the parameters received, and if
|
||||
:param fldList: List of required fields
|
||||
@ -345,9 +335,7 @@ class BaseModelHandler(Handler):
|
||||
return res
|
||||
|
||||
# Exceptions
|
||||
def invalidRequestException(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> exceptions.HandlerError:
|
||||
def invalidRequestException(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
|
||||
"""
|
||||
Raises an invalid request error with a default translated string
|
||||
:param message: Custom message to add to exception. If it is None, "Invalid Request" is used
|
||||
@ -355,9 +343,7 @@ class BaseModelHandler(Handler):
|
||||
message = message or _('Invalid Request')
|
||||
return exceptions.RequestError(f'{message} {self.__class__}: {self._args}')
|
||||
|
||||
def invalidResponseException(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> exceptions.HandlerError:
|
||||
def invalidResponseException(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
|
||||
message = 'Invalid response' if message is None else message
|
||||
return exceptions.ResponseError(message)
|
||||
|
||||
@ -365,13 +351,9 @@ class BaseModelHandler(Handler):
|
||||
"""
|
||||
Raises a NotFound exception with translated "Method not found" string to current locale
|
||||
"""
|
||||
return exceptions.RequestError(
|
||||
_('Method not found in {}: {}').format(self.__class__, self._args)
|
||||
)
|
||||
return exceptions.RequestError(_('Method not found in {}: {}').format(self.__class__, self._args))
|
||||
|
||||
def invalidItemException(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> exceptions.HandlerError:
|
||||
def invalidItemException(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
|
||||
"""
|
||||
Raises a NotFound exception, with location info
|
||||
"""
|
||||
@ -379,14 +361,10 @@ class BaseModelHandler(Handler):
|
||||
return exceptions.NotFound(message)
|
||||
# raise NotFound('{} {}: {}'.format(message, self.__class__, self._args))
|
||||
|
||||
def accessDenied(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> exceptions.HandlerError:
|
||||
def accessDenied(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
|
||||
return exceptions.AccessDenied(message or _('Access denied'))
|
||||
|
||||
def notSupported(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> exceptions.HandlerError:
|
||||
def notSupported(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
|
||||
return exceptions.NotSupportedError(message or _('Operation not supported'))
|
||||
|
||||
# Success methods
|
||||
@ -401,9 +379,7 @@ class BaseModelHandler(Handler):
|
||||
"""
|
||||
Invokes a test for an item
|
||||
"""
|
||||
logger.debug(
|
||||
'Called base test for %s --> %s', self.__class__.__name__, self._params
|
||||
)
|
||||
logger.debug('Called base test for %s --> %s', self.__class__.__name__, self._params)
|
||||
raise self.invalidMethodException()
|
||||
|
||||
|
||||
@ -460,9 +436,7 @@ class DetailHandler(BaseModelHandler):
|
||||
self._kwargs = kwargs
|
||||
self._user = kwargs.get('user', None)
|
||||
|
||||
def __checkCustom(
|
||||
self, check: str, parent: models.Model, arg: typing.Any = None
|
||||
) -> typing.Any:
|
||||
def __checkCustom(self, check: str, parent: models.Model, arg: typing.Any = None) -> typing.Any:
|
||||
"""
|
||||
checks curron methods
|
||||
:param check: Method to check
|
||||
@ -590,7 +564,9 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
# Override this to provide functionality
|
||||
# Default (as sample) getItems
|
||||
def getItems(self, parent: models.Model, item: typing.Optional[str]) -> typing.Union[typing.List[typing.Dict], typing.Dict]:
|
||||
def getItems(
|
||||
self, parent: models.Model, item: typing.Optional[str]
|
||||
) -> typing.Union[typing.List[typing.Dict], typing.Dict]:
|
||||
"""
|
||||
This MUST be overridden by derived classes
|
||||
Excepts to return a list of dictionaries or a single dictionary, depending on "item" param
|
||||
@ -600,9 +576,7 @@ class DetailHandler(BaseModelHandler):
|
||||
# if item is None: # Returns ALL detail items
|
||||
# return []
|
||||
# return {} # Returns one item
|
||||
raise NotImplementedError(
|
||||
f'Must provide an getItems method for {self.__class__} class'
|
||||
)
|
||||
raise NotImplementedError(f'Must provide an getItems method for {self.__class__} class')
|
||||
|
||||
# Default save
|
||||
def saveItem(self, parent: models.Model, item: typing.Optional[str]) -> None:
|
||||
@ -768,7 +742,7 @@ class ModelHandler(BaseModelHandler):
|
||||
"""
|
||||
return []
|
||||
|
||||
def getTypes(self, *args, **kwargs):
|
||||
def getTypes(self, *args, **kwargs) -> typing.Generator[typing.Dict[str, typing.Any], None, None]:
|
||||
for type_ in self.enum_types():
|
||||
yield self.typeAsDict(type_)
|
||||
|
||||
@ -865,16 +839,12 @@ class ModelHandler(BaseModelHandler):
|
||||
except Exception as e:
|
||||
logger.exception('Exception:')
|
||||
logger.info('Filtering expression %s is invalid!', self.fltr)
|
||||
raise exceptions.RequestError(
|
||||
f'Filtering expression {self.fltr} is invalid'
|
||||
) from e
|
||||
raise exceptions.RequestError(f'Filtering expression {self.fltr} is invalid') from e
|
||||
|
||||
# Helper to process detail
|
||||
# Details can be managed (writen) by any user that has MANAGEMENT permission over parent
|
||||
def processDetail(self) -> typing.Any:
|
||||
logger.debug(
|
||||
'Processing detail %s for with params %s', self._path, self._params
|
||||
)
|
||||
logger.debug('Processing detail %s for with params %s', self._path, self._params)
|
||||
try:
|
||||
item: models.Model = self.model.objects.filter(uuid=self._args[0])[0] # type: ignore # Slicing is not supported by pylance right now
|
||||
# If we do not have access to parent to, at least, read...
|
||||
@ -899,9 +869,7 @@ class ModelHandler(BaseModelHandler):
|
||||
detailCls = self.detail[self._args[1]]
|
||||
args = list(self._args[2:])
|
||||
path = self._path + '/' + '/'.join(args[:2])
|
||||
detail = detailCls(
|
||||
self, path, self._params, *args, parent=item, user=self._user
|
||||
)
|
||||
detail = detailCls(self, path, self._params, *args, parent=item, user=self._user)
|
||||
method = getattr(detail, self._operation)
|
||||
|
||||
return method()
|
||||
@ -915,9 +883,7 @@ class ModelHandler(BaseModelHandler):
|
||||
logger.error('Exception processing detail: %s', e)
|
||||
raise self.invalidRequestException() from e
|
||||
|
||||
def getItems(
|
||||
self, *args, **kwargs
|
||||
) -> typing.Generator[typing.MutableMapping[str, typing.Any], None, None]:
|
||||
def getItems(self, *args, **kwargs) -> typing.Generator[typing.MutableMapping[str, typing.Any], None, None]:
|
||||
if 'overview' in kwargs:
|
||||
overview = kwargs['overview']
|
||||
del kwargs['overview']
|
||||
@ -932,14 +898,12 @@ class ModelHandler(BaseModelHandler):
|
||||
prefetch = []
|
||||
|
||||
if 'query' in kwargs:
|
||||
query = kwargs['query']
|
||||
query = kwargs['query'] # We are using a prebuilt query on args
|
||||
logger.debug('Got query: %s', query)
|
||||
del kwargs['query']
|
||||
else:
|
||||
logger.debug('Args: %s, kwargs: %s', args, kwargs)
|
||||
query = self.model.objects.filter(*args, **kwargs).prefetch_related(
|
||||
*prefetch
|
||||
)
|
||||
query = self.model.objects.filter(*args, **kwargs).prefetch_related(*prefetch)
|
||||
|
||||
if self.model_filter is not None:
|
||||
query = query.filter(**self.model_filter)
|
||||
@ -1133,16 +1097,8 @@ class ModelHandler(BaseModelHandler):
|
||||
if isinstance(item, TaggingMixin):
|
||||
if tags:
|
||||
logger.debug('Updating tags: %s', tags)
|
||||
item.tags.set(
|
||||
[
|
||||
Tag.objects.get_or_create(tag=val)[0]
|
||||
for val in tags
|
||||
if val != ''
|
||||
]
|
||||
)
|
||||
elif isinstance(
|
||||
tags, list
|
||||
): # Present, but list is empty (will be proccesed on "if" else)
|
||||
item.tags.set([Tag.objects.get_or_create(tag=val)[0] for val in tags if val != ''])
|
||||
elif isinstance(tags, list): # Present, but list is empty (will be proccesed on "if" else)
|
||||
item.tags.clear()
|
||||
|
||||
if not deleteOnError:
|
||||
@ -1153,9 +1109,7 @@ class ModelHandler(BaseModelHandler):
|
||||
# Store associated object if requested (data_type)
|
||||
try:
|
||||
if isinstance(item, ManagedObjectModel):
|
||||
data_type: typing.Optional[str] = self._params.get(
|
||||
'data_type', self._params.get('type')
|
||||
)
|
||||
data_type: typing.Optional[str] = self._params.get('data_type', self._params.get('type'))
|
||||
if data_type:
|
||||
item.data_type = data_type
|
||||
item.data = item.getInstance(self._params).serialize()
|
||||
@ -1177,9 +1131,7 @@ class ModelHandler(BaseModelHandler):
|
||||
except self.model.DoesNotExist:
|
||||
raise exceptions.NotFound('Item not found') from None
|
||||
except IntegrityError: # Duplicate key probably
|
||||
raise exceptions.RequestError(
|
||||
'Element already exists (duplicate key error)'
|
||||
) from None
|
||||
raise exceptions.RequestError('Element already exists (duplicate key error)') from None
|
||||
except (exceptions.SaveException, g_exceptions.ValidationError) as e:
|
||||
raise exceptions.RequestError(str(e)) from e
|
||||
except (exceptions.RequestError, exceptions.ResponseError):
|
||||
|
@ -77,6 +77,10 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
)
|
||||
self._namespace = uuid.UUID('627a37a5-e8db-431a-b783-73f7d20b4934')
|
||||
|
||||
@staticmethod
|
||||
def manager() -> 'CryptoManager':
|
||||
return CryptoManager() # Singleton pattern will return always the same instance
|
||||
|
||||
@staticmethod
|
||||
def AESKey(key: typing.Union[str, bytes], length: int) -> bytes:
|
||||
if isinstance(key, str):
|
||||
@ -94,10 +98,6 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
|
||||
return bytes(kl)
|
||||
|
||||
@staticmethod
|
||||
def manager() -> 'CryptoManager':
|
||||
return CryptoManager() # Singleton pattern will return always the same instance
|
||||
|
||||
def encrypt(self, value: str) -> str:
|
||||
return codecs.encode(
|
||||
self._rsa.public_key().encrypt(
|
||||
@ -139,9 +139,7 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
)
|
||||
rndStr = secrets.token_bytes(16) # Same as block size of CBC (that is 16 here)
|
||||
paddedLength = ((len(text) + 4 + 15) // 16) * 16
|
||||
toEncode = (
|
||||
struct.pack('>i', len(text)) + text + rndStr[: paddedLength - len(text) - 4]
|
||||
)
|
||||
toEncode = struct.pack('>i', len(text)) + text + rndStr[: paddedLength - len(text) - 4]
|
||||
encryptor = cipher.encryptor()
|
||||
encoded = encryptor.update(toEncode) + encryptor.finalize()
|
||||
|
||||
@ -172,9 +170,7 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
def fastDecrypt(self, data: bytes) -> bytes:
|
||||
return self.AESDecrypt(data, UDSK)
|
||||
|
||||
def xor(
|
||||
self, value: typing.Union[str, bytes], key: typing.Union[str, bytes]
|
||||
) -> bytes:
|
||||
def xor(self, value: typing.Union[str, bytes], key: typing.Union[str, bytes]) -> bytes:
|
||||
if not key:
|
||||
return b'' # Protect against division by cero
|
||||
|
||||
@ -184,17 +180,11 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
key = key.encode('utf-8')
|
||||
mult = len(value) // len(key) + 1
|
||||
value_array = array.array('B', value)
|
||||
key_array = array.array(
|
||||
'B', key * mult
|
||||
) # Ensure key array is at least as long as value_array
|
||||
key_array = array.array('B', key * mult) # Ensure key array is at least as long as value_array
|
||||
# We must return binary in xor, because result is in fact binary
|
||||
return array.array(
|
||||
'B', (value_array[i] ^ key_array[i] for i in range(len(value_array)))
|
||||
).tobytes()
|
||||
return array.array('B', (value_array[i] ^ key_array[i] for i in range(len(value_array)))).tobytes()
|
||||
|
||||
def symCrypt(
|
||||
self, text: typing.Union[str, bytes], key: typing.Union[str, bytes]
|
||||
) -> bytes:
|
||||
def symCrypt(self, text: typing.Union[str, bytes], key: typing.Union[str, bytes]) -> bytes:
|
||||
if isinstance(text, str):
|
||||
text = text.encode()
|
||||
if isinstance(key, str):
|
||||
@ -202,9 +192,7 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
|
||||
return self.AESCrypt(text, key)
|
||||
|
||||
def symDecrpyt(
|
||||
self, cryptText: typing.Union[str, bytes], key: typing.Union[str, bytes]
|
||||
) -> str:
|
||||
def symDecrpyt(self, cryptText: typing.Union[str, bytes], key: typing.Union[str, bytes]) -> str:
|
||||
if isinstance(cryptText, str):
|
||||
cryptText = cryptText.encode()
|
||||
|
||||
@ -221,19 +209,13 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
|
||||
def loadPrivateKey(
|
||||
self, rsaKey: str
|
||||
) -> typing.Union[
|
||||
'RSAPrivateKey', 'DSAPrivateKey', 'DHPrivateKey', 'EllipticCurvePrivateKey'
|
||||
]:
|
||||
) -> typing.Union['RSAPrivateKey', 'DSAPrivateKey', 'DHPrivateKey', 'EllipticCurvePrivateKey']:
|
||||
try:
|
||||
return serialization.load_pem_private_key(
|
||||
rsaKey.encode(), password=None, backend=default_backend()
|
||||
)
|
||||
return serialization.load_pem_private_key(rsaKey.encode(), password=None, backend=default_backend())
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def loadCertificate(
|
||||
self, certificate: typing.Union[str, bytes]
|
||||
) -> x509.Certificate:
|
||||
def loadCertificate(self, certificate: typing.Union[str, bytes]) -> x509.Certificate:
|
||||
if isinstance(certificate, str):
|
||||
certificate = certificate.encode()
|
||||
|
||||
@ -274,16 +256,12 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
return not hashValue
|
||||
|
||||
if hashValue[:8] == '{SHA256}':
|
||||
return secrets.compare_digest(
|
||||
hashlib.sha3_256(value).hexdigest(), hashValue[8:]
|
||||
)
|
||||
return secrets.compare_digest(hashlib.sha3_256(value).hexdigest(), hashValue[8:])
|
||||
if hashValue[:12] == '{SHA256SALT}':
|
||||
# Extract 16 chars salt and hash
|
||||
salt = hashValue[12:28].encode()
|
||||
value = salt + value
|
||||
return secrets.compare_digest(
|
||||
hashlib.sha3_256(value).hexdigest(), hashValue[28:]
|
||||
)
|
||||
return secrets.compare_digest(hashlib.sha3_256(value).hexdigest(), hashValue[28:])
|
||||
# Argon2
|
||||
if hashValue[:8] == '{ARGON2}':
|
||||
ph = PasswordHasher()
|
||||
@ -295,11 +273,16 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
|
||||
# Old sha1
|
||||
return secrets.compare_digest(
|
||||
hashValue, str(hashlib.sha1(value).hexdigest()) # nosec: Old compatibility SHA1, not used anymore but need to be supported
|
||||
) # nosec: Old compatibility SHA1, not used anymore but need to be supported
|
||||
hashValue,
|
||||
str(
|
||||
hashlib.sha1(
|
||||
value
|
||||
).hexdigest() # nosec: Old SHA1 password, not used anymore but need to be supported
|
||||
),
|
||||
)
|
||||
|
||||
def uuid(self, obj: typing.Any = None) -> str:
|
||||
""" Generates an uuid from obj. (lower case)
|
||||
"""Generates an uuid from obj. (lower case)
|
||||
If obj is None, returns an uuid based on a random string
|
||||
"""
|
||||
if obj is None:
|
||||
@ -319,8 +302,5 @@ class CryptoManager(metaclass=singleton.Singleton):
|
||||
|
||||
def unique(self) -> str:
|
||||
return hashlib.sha3_256(
|
||||
(
|
||||
self.randomString(24, True)
|
||||
+ datetime.datetime.now().strftime('%H%M%S%f')
|
||||
).encode()
|
||||
(self.randomString(24, True) + datetime.datetime.now().strftime('%H%M%S%f')).encode()
|
||||
).hexdigest()
|
||||
|
48
server/src/uds/core/types/rest.py
Normal file
48
server/src/uds/core/types/rest.py
Normal file
@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2023 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 typing
|
||||
|
||||
class TypeInfo(typing.NamedTuple):
|
||||
name: str
|
||||
type: str
|
||||
description: str
|
||||
icon: str
|
||||
|
||||
def asDict(self, **extra) -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'name': self.name,
|
||||
'type': self.type,
|
||||
'description': self.description,
|
||||
'icon': self.icon,
|
||||
**extra
|
||||
}
|
||||
|
@ -30,13 +30,17 @@
|
||||
Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import enum
|
||||
import typing
|
||||
|
||||
from uds.core.util import singleton
|
||||
|
||||
|
||||
class ServerType(enum.IntEnum):
|
||||
TUNNEL = 1
|
||||
ACTOR = 2
|
||||
SERVER = 3
|
||||
|
||||
LEGACY = 100
|
||||
UNMANAGED = 100
|
||||
|
||||
def as_str(self) -> str:
|
||||
return self.name.lower() # type: ignore
|
||||
@ -46,5 +50,38 @@ class ServerType(enum.IntEnum):
|
||||
ServerType.TUNNEL: 'tunnel',
|
||||
ServerType.ACTOR: 'actor',
|
||||
ServerType.SERVER: 'server',
|
||||
ServerType.LEGACY: '', # Legacy has no path, does not listen to anything
|
||||
ServerType.UNMANAGED: '', # Unmanaged has no path, does not listen to anything
|
||||
}[self]
|
||||
|
||||
|
||||
class ServerSubType(metaclass=singleton.Singleton):
|
||||
class Info(typing.NamedTuple):
|
||||
type: ServerType
|
||||
subtype: str
|
||||
description: str
|
||||
managed: bool
|
||||
|
||||
registered: typing.Dict[typing.Tuple[ServerType, str], Info]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.registered = {}
|
||||
|
||||
@staticmethod
|
||||
def manager() -> 'ServerSubType':
|
||||
return ServerSubType()
|
||||
|
||||
def register(self, type: ServerType, subtype: str, description: str, managed: bool) -> None:
|
||||
self.registered[(type, subtype)] = ServerSubType.Info(
|
||||
type=type, subtype=subtype, description=description, managed=managed
|
||||
)
|
||||
|
||||
def enum(self) -> typing.Iterable[Info]:
|
||||
return self.registered.values()
|
||||
|
||||
def get(self, type: ServerType, subtype: str) -> typing.Optional[Info]:
|
||||
return self.registered.get((type, subtype))
|
||||
|
||||
|
||||
# Registering default subtypes (basically, ip unmanaged is the "global" one), any other will be registered by the providers
|
||||
# I.e. "linuxapp" will be registered by the Linux Applications Provider
|
||||
ServerSubType.manager().register(ServerType.UNMANAGED, 'ip', 'Unmanaged IP Server', False)
|
||||
|
@ -80,7 +80,7 @@ def getTunnelFromField(fld: ui.gui.ChoiceField) -> models.RegisteredServerGroup:
|
||||
|
||||
# Server group field
|
||||
def serverGroupField(
|
||||
kind: types.servers.ServerType = types.servers.ServerType.LEGACY, subkind: typing.Optional[str] = None
|
||||
kind: types.servers.ServerType = types.servers.ServerType.UNMANAGED, subkind: typing.Optional[str] = None
|
||||
) -> ui.gui.ChoiceField:
|
||||
"""Returns a field to select a server group
|
||||
|
||||
@ -102,7 +102,7 @@ def serverGroupField(
|
||||
|
||||
|
||||
def getServerGroupFromField(
|
||||
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.LEGACY
|
||||
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.UNMANAGED
|
||||
) -> models.RegisteredServerGroup:
|
||||
"""Returns a server group from a field
|
||||
|
||||
@ -114,7 +114,7 @@ def getServerGroupFromField(
|
||||
|
||||
|
||||
def getServersFromServerGroupField(
|
||||
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.LEGACY
|
||||
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.UNMANAGED
|
||||
) -> typing.List[models.RegisteredServer]:
|
||||
"""Returns a list of servers from a server group field
|
||||
|
||||
|
@ -115,7 +115,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
("name", models.CharField(max_length=64, unique=True)),
|
||||
("comments", models.CharField(default="", max_length=255)),
|
||||
("type", models.IntegerField(default=uds.core.types.servers.ServerType["LEGACY"], db_index=True)),
|
||||
("type", models.IntegerField(default=uds.core.types.servers.ServerType["UNMANAGED"], db_index=True)),
|
||||
("subtype", models.CharField(db_index=True, default="", max_length=32)),
|
||||
("host", models.CharField(default="", max_length=255)),
|
||||
("port", models.IntegerField(default=0)),
|
||||
|
@ -68,7 +68,7 @@ class RegisteredServerGroup(UUIDModel, TaggingMixin):
|
||||
# These are not the servers ports itself, and it depends on the kind of server
|
||||
# For example, for tunnel server groups, has an internet address and port that will be used
|
||||
# But for APP Servers, host and port are ununsed
|
||||
type = models.IntegerField(default=types.servers.ServerType.LEGACY, db_index=True)
|
||||
type = models.IntegerField(default=types.servers.ServerType.UNMANAGED, db_index=True)
|
||||
subtype = models.CharField(
|
||||
max_length=32, default='', db_index=True
|
||||
) # Subkind of server, if any (I.E. LinuxDocker, RDS, etc..)
|
||||
|
Loading…
Reference in New Issue
Block a user