mirror of
https://github.com/dkmstr/openuds.git
synced 2025-03-11 00:58:39 +03:00
Replacing several diferent "registration" tables to a single, versatile one.
This commit is contained in:
parent
81db3278bd
commit
012f8de6c5
@ -71,4 +71,4 @@ class ActorRegisterTest(rest.test.RESTActorTestCase):
|
||||
token = response.json()['result']
|
||||
|
||||
# Ensure database contains the registered token
|
||||
self.assertEqual(models.ActorToken.objects.filter(token=token).count(), 1)
|
||||
self.assertEqual(models.RegisteredServers.objects.filter(token=token).count(), 1)
|
||||
|
@ -35,7 +35,7 @@ import typing
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from uds.models import ActorToken
|
||||
from uds.models import RegisteredServers
|
||||
from uds.REST.exceptions import RequestError, NotFound
|
||||
from uds.REST.model import ModelHandler, OK
|
||||
from uds.core.util import permissions
|
||||
@ -47,7 +47,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ActorTokens(ModelHandler):
|
||||
model = ActorToken
|
||||
model = RegisteredServers
|
||||
model_filter = {'kind': RegisteredServers.ServerType.ACTOR}
|
||||
|
||||
table_title = _('Actor tokens')
|
||||
table_fields = [
|
||||
@ -62,21 +63,22 @@ class ActorTokens(ModelHandler):
|
||||
{'log_level': {'title': _('Log level')}},
|
||||
]
|
||||
|
||||
def item_as_dict(self, item: ActorToken) -> typing.Dict[str, typing.Any]:
|
||||
def item_as_dict(self, item: RegisteredServers) -> typing.Dict[str, typing.Any]:
|
||||
data = item.data or {}
|
||||
return {
|
||||
'id': item.token,
|
||||
'name': str(_('Token isued by {} from {}')).format(
|
||||
item.username, item.hostname or item.ip
|
||||
),
|
||||
'name': str(_('Token isued by {} from {}')).format(item.username, item.hostname or item.ip),
|
||||
'stamp': item.stamp,
|
||||
'username': item.username,
|
||||
'ip': item.ip,
|
||||
'host': f'{item.ip} - {item.mac}',
|
||||
'host': f'{item.ip} - {data.get("mac")}',
|
||||
'hostname': item.hostname,
|
||||
'pre_command': item.pre_command,
|
||||
'post_command': item.post_command,
|
||||
'runonce_command': item.runonce_command,
|
||||
'log_level': LogLevel.fromActorLevel(item.log_level).name # ['DEBUG', 'INFO', 'ERROR', 'FATAL'][item.log_level % 4],
|
||||
'pre_command': data.get('pre_command', ''),
|
||||
'post_command': data.get('post_command', ''),
|
||||
'runonce_command': data.get('runonce_command', ''),
|
||||
'log_level': LogLevel.fromActorLevel(
|
||||
data.get('log_level', 2)
|
||||
).name, # ['DEBUG', 'INFO', 'ERROR', 'FATAL'][item.log_level % 4],
|
||||
}
|
||||
|
||||
def delete(self) -> str:
|
||||
|
@ -36,10 +36,10 @@ import functools
|
||||
import enum
|
||||
|
||||
from uds.models import (
|
||||
ActorToken,
|
||||
UserService,
|
||||
Service,
|
||||
TicketStore,
|
||||
RegisteredServers,
|
||||
)
|
||||
|
||||
from uds.core.util.model import getSqlDatetimeAsUnix, getSqlDatetime
|
||||
@ -240,7 +240,9 @@ class Test(ActorV3Action):
|
||||
if self._params.get('type') == UNMANAGED:
|
||||
Service.objects.get(token=self._params['token'])
|
||||
else:
|
||||
ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check
|
||||
RegisteredServers.objects.get(
|
||||
token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR
|
||||
) # Not assigned, because only needs check
|
||||
clearFailedIp(self._request)
|
||||
except Exception:
|
||||
# Increase failed attempts
|
||||
@ -273,44 +275,55 @@ class Register(ActorV3Action):
|
||||
name = 'register'
|
||||
|
||||
def post(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
actorToken: ActorToken
|
||||
try:
|
||||
# If already exists a token for this MAC, return it instead of creating a new one, and update the information...
|
||||
actorToken = ActorToken.objects.get(mac=self._params['mac'])
|
||||
# Update parameters
|
||||
actorToken.username = self._user.pretty_name
|
||||
actorToken.ip_from = self._request.ip
|
||||
actorToken.ip = self._params['ip']
|
||||
actorToken.hostname = self._params['hostname']
|
||||
actorToken.pre_command = self._params['pre_command']
|
||||
actorToken.post_command = self._params['post_command']
|
||||
actorToken.runonce_command = self._params['run_once_command']
|
||||
actorToken.log_level = self._params['log_level']
|
||||
if 'custom' in self._params:
|
||||
actorToken.custom = self._params['certificate']
|
||||
actorToken.stamp = getSqlDatetime()
|
||||
actorToken.save()
|
||||
logger.info('Registered actor %s', self._params)
|
||||
except Exception: # Not found, create a new token
|
||||
actorToken: RegisteredServers
|
||||
# If already exists a token for this MAC, return it instead of creating a new one, and update the information...
|
||||
# Look for a token for this mac. mac is "inside" data, so we must filter first by type and then ensure mac is inside data
|
||||
# and mac is the requested one
|
||||
found = False
|
||||
for actorToken in RegisteredServers.objects.filter(kind=RegisteredServers.ServerType.ACTOR):
|
||||
if actorToken.data and actorToken.data.get('mac', '') == self._params['mac']:
|
||||
# Update parameters
|
||||
actorToken.username = self._user.pretty_name
|
||||
actorToken.ip_from = self._request.ip
|
||||
actorToken.ip = self._params['ip']
|
||||
actorToken.hostname = self._params['hostname']
|
||||
actorToken.data = { # type: ignore
|
||||
'mac': self._params['mac'],
|
||||
'pre_command': self._params['pre_command'],
|
||||
'post_command': self._params['post_command'],
|
||||
'run_once_command': self._params['run_once_command'],
|
||||
'log_level': self._params['log_level'],
|
||||
'custom': self._params.get('custom', ''),
|
||||
}
|
||||
actorToken.stamp = getSqlDatetime()
|
||||
actorToken.save()
|
||||
logger.info('Registered actor %s', self._params)
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
kwargs = {
|
||||
'username': self._user.pretty_name,
|
||||
'ip_from': self._request.ip,
|
||||
'ip': self._params['ip'],
|
||||
'ip_version': self._request.ip_version,
|
||||
'hostname': self._params['hostname'],
|
||||
'mac': self._params['mac'],
|
||||
'pre_command': self._params['pre_command'],
|
||||
'post_command': self._params['post_command'],
|
||||
'runonce_command': self._params['run_once_command'],
|
||||
'log_level': self._params['log_level'],
|
||||
'data': { # type: ignore
|
||||
'mac': self._params['mac'],
|
||||
'pre_command': self._params['pre_command'],
|
||||
'post_command': self._params['post_command'],
|
||||
'run_once_command': self._params['run_once_command'],
|
||||
'log_level': self._params['log_level'],
|
||||
'custom': self._params.get('custom', ''),
|
||||
},
|
||||
'token': secrets.token_urlsafe(36),
|
||||
'kind': RegisteredServers.ServerType.ACTOR,
|
||||
'stamp': getSqlDatetime(),
|
||||
}
|
||||
if 'custom' in self._params:
|
||||
kwargs['custom'] = self._params['custom']
|
||||
|
||||
actorToken = ActorToken.objects.create(**kwargs)
|
||||
return ActorV3Action.actorResult(actorToken.token)
|
||||
actorToken = RegisteredServers.objects.create(**kwargs)
|
||||
|
||||
return ActorV3Action.actorResult(actorToken.token) # type: ignore # actorToken is always assigned
|
||||
|
||||
|
||||
class Initialize(ActorV3Action):
|
||||
@ -396,7 +409,9 @@ class Initialize(ActorV3Action):
|
||||
dbFilter = UserService.objects.filter(deployed_service__service=service)
|
||||
else:
|
||||
# If not service provided token, use actor tokens
|
||||
ActorToken.objects.get(token=token) # Not assigned, because only needs check
|
||||
RegisteredServers.objects.get(
|
||||
token=token, kind=RegisteredServers.ServerType.ACTOR
|
||||
) # 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()
|
||||
@ -430,7 +445,7 @@ class Initialize(ActorV3Action):
|
||||
service.aliases.create(alias=alias_token)
|
||||
|
||||
return initialization_result(userService.uuid, userService.unique_id, osData, alias_token)
|
||||
except (ActorToken.DoesNotExist, Service.DoesNotExist):
|
||||
except (RegisteredServers.DoesNotExist, Service.DoesNotExist):
|
||||
raise BlockAccess() from None
|
||||
|
||||
|
||||
@ -706,8 +721,8 @@ class Ticket(ActorV3Action):
|
||||
|
||||
try:
|
||||
# Simple check that token exists
|
||||
ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check
|
||||
except ActorToken.DoesNotExist:
|
||||
RegisteredServers.objects.get(token=self._params['token'], kind=RegisteredServers.ServerType.ACTOR) # Not assigned, because only needs check
|
||||
except RegisteredServers.DoesNotExist:
|
||||
raise BlockAccess() from None # If too many blocks...
|
||||
|
||||
try:
|
||||
|
@ -74,6 +74,7 @@ class ServerRegister(Handler):
|
||||
token=secrets.token_urlsafe(36),
|
||||
stamp=getSqlDatetime(),
|
||||
kind=self._params['type'],
|
||||
data=self._params.get('data', None),
|
||||
)
|
||||
except Exception as e:
|
||||
return {'result': '', 'stamp': now, 'error': str(e)}
|
||||
@ -82,6 +83,7 @@ class ServerRegister(Handler):
|
||||
|
||||
class ServersTokens(ModelHandler):
|
||||
model = models.RegisteredServers
|
||||
model_filter = {'kind__in': [models.RegisteredServers.ServerType.TUNNEL, models.RegisteredServers.ServerType.OTHER]}
|
||||
path = 'servers'
|
||||
name = 'tokens'
|
||||
|
||||
|
@ -709,6 +709,8 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
# Which model does this manage, must be a django model ofc
|
||||
model: 'typing.ClassVar[typing.Type[models.Model]]'
|
||||
# If the model is filtered (for overviews)
|
||||
model_filter: 'typing.ClassVar[typing.Optional[typing.Mapping[str, typing.Any]]]' = None
|
||||
|
||||
# By default, filter is empty
|
||||
fltr: typing.Optional[str] = None
|
||||
@ -935,6 +937,9 @@ class ModelHandler(BaseModelHandler):
|
||||
*prefetch
|
||||
)
|
||||
|
||||
if self.model_filter is not None:
|
||||
query = query.filter(**self.model_filter)
|
||||
|
||||
for item in query:
|
||||
try:
|
||||
if (
|
||||
|
@ -61,8 +61,7 @@ class ObjectType(enum.Enum):
|
||||
# PROXY_TYPE = (15, models.Proxy) has been removed
|
||||
METAPOOL = (16, models.MetaPool)
|
||||
ACCOUNT = (17, models.Account)
|
||||
ACTOR_TOKEN = (18, models.ActorToken)
|
||||
TUNNEL_TOKEN = (19, models.RegisteredServers)
|
||||
REGISTERED_SERVER = (19, models.RegisteredServers)
|
||||
ACCOUNT_USAGE = (20, models.AccountUsage)
|
||||
IMAGE = (21, models.Image)
|
||||
LOG = (22, models.Log)
|
||||
|
@ -356,24 +356,14 @@ class Command(BaseCommand):
|
||||
|
||||
tree[counter('GALLERY')] = gallery
|
||||
|
||||
# Actor tokens
|
||||
actorTokens: typing.Dict[str, typing.Any] = {}
|
||||
for actorToken in models.ActorToken.objects.all():
|
||||
actorTokens[actorToken.hostname] = getSerializedFromModel(
|
||||
actorToken, passwordFields=['token']
|
||||
|
||||
# Rest of registerd servers
|
||||
registeredServers: typing.Dict[str, typing.Any] = {}
|
||||
for i, registeredServer in enumerate(models.RegisteredServers.objects.all()):
|
||||
registeredServers[f'{i}'] = getSerializedFromModel(
|
||||
registeredServer
|
||||
)
|
||||
|
||||
tree[counter('ACTORTOKENS')] = actorTokens
|
||||
|
||||
# Tunnel tokens
|
||||
tunnelTokens: typing.Dict[str, typing.Any] = {}
|
||||
for tunnelToken in models.RegisteredServers.objects.all():
|
||||
tunnelTokens[tunnelToken.hostname] = getSerializedFromModel(
|
||||
tunnelToken, passwordFields=['token']
|
||||
)
|
||||
|
||||
tree[counter('TUNNELTOKENS')] = tunnelTokens
|
||||
|
||||
self.stdout.write(yaml.safe_dump(tree, default_flow_style=False))
|
||||
|
||||
except Exception as e:
|
||||
|
@ -1,7 +1,58 @@
|
||||
# Generated by Django 4.2.1 on 2023-07-12 04:04
|
||||
import typing
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
ACTOR_TYPE = 2 # Hardcoded value from uds/models/registered_servers.py
|
||||
|
||||
def migrate_old_actor_tokens(apps, schema_editor):
|
||||
try:
|
||||
RegisteredServers = apps.get_model('uds', 'RegisteredServers')
|
||||
ActorToken = apps.get_model('uds', 'ActorToken')
|
||||
for token in ActorToken.objects.all():
|
||||
RegisteredServers.objects.create(
|
||||
username=token.username,
|
||||
ip_from=token.ip_from,
|
||||
ip=token.ip,
|
||||
ip_version=token.ip_version,
|
||||
hostname=token.hostname,
|
||||
token=token.token,
|
||||
stamp=token.stamp,
|
||||
kind=ACTOR_TYPE,
|
||||
data={
|
||||
'mac': token.mac,
|
||||
'pre_command': token.pre_command,
|
||||
'post_command': token.post_command,
|
||||
'runonce_command': token.runonce_command,
|
||||
'log_level': token.log_level,
|
||||
'custom': token.custom,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
if 'no such table' not in str(e):
|
||||
# Pytest is running this method twice??
|
||||
raise e
|
||||
|
||||
def revert_migration_to_old_actor_tokens(apps, schema_editor):
|
||||
RegisteredServers = apps.get_model('uds', 'RegisteredServers')
|
||||
ActorToken = apps.get_model('uds', 'ActorToken')
|
||||
for server in RegisteredServers.objects.filter(kind=ACTOR_TYPE):
|
||||
ActorToken.objects.create(
|
||||
username=server.username,
|
||||
ip_from=server.ip_from,
|
||||
ip=server.ip,
|
||||
ip_version=server.ip_version,
|
||||
hostname=server.hostname,
|
||||
token=server.token,
|
||||
stamp=server.stamp,
|
||||
mac=server.data['mac'],
|
||||
pre_command=server.data['pre_command'],
|
||||
post_command=server.data['post_command'],
|
||||
runonce_command=server.data['runonce_command'],
|
||||
log_level=server.data['log_level'],
|
||||
custom=server.data['custom'],
|
||||
)
|
||||
# Delete the server
|
||||
server.delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
@ -13,9 +64,31 @@ class Migration(migrations.Migration):
|
||||
'TunnelToken',
|
||||
'RegisteredServers',
|
||||
),
|
||||
migrations.RemoveConstraint(
|
||||
model_name="registeredservers",
|
||||
name="tt_ip_hostname",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="registeredservers",
|
||||
name="kind",
|
||||
field=models.IntegerField(default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="registeredservers",
|
||||
name="ip_version",
|
||||
field=models.IntegerField(default=4),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="registeredservers",
|
||||
name="data",
|
||||
field=models.JSONField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.RunPython(
|
||||
migrate_old_actor_tokens,
|
||||
revert_migration_to_old_actor_tokens,
|
||||
atomic=True,
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="ActorToken",
|
||||
),
|
||||
]
|
||||
|
@ -103,8 +103,7 @@ from .account_usage import AccountUsage
|
||||
from .tag import Tag, TaggingMixin
|
||||
|
||||
# Tokens
|
||||
from .actor_token import ActorToken
|
||||
from .tunnel_token import RegisteredServers
|
||||
from .registered_servers import RegisteredServers
|
||||
|
||||
# Notifications & Alerts
|
||||
from .notifications import Notification, Notifier, LogLevel
|
||||
|
@ -1,63 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-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
|
||||
'''
|
||||
from django.db import models
|
||||
|
||||
from .consts import MAX_IPV6_LENGTH, MAX_DNS_NAME_LENGTH
|
||||
|
||||
|
||||
class ActorToken(models.Model):
|
||||
"""
|
||||
UDS Actors tokens on DB
|
||||
"""
|
||||
|
||||
username = models.CharField(max_length=128)
|
||||
ip_from = models.CharField(max_length=MAX_IPV6_LENGTH)
|
||||
ip = models.CharField(max_length=MAX_IPV6_LENGTH)
|
||||
ip_version = models.IntegerField(default=4) # Version of ip fields
|
||||
|
||||
hostname = models.CharField(max_length=MAX_DNS_NAME_LENGTH)
|
||||
mac = models.CharField(max_length=128, db_index=True, unique=True)
|
||||
pre_command = models.CharField(max_length=255, blank=True, default='')
|
||||
post_command = models.CharField(max_length=255, blank=True, default='')
|
||||
runonce_command = models.CharField(max_length=255, blank=True, default='')
|
||||
log_level = models.IntegerField()
|
||||
|
||||
token = models.CharField(max_length=48, db_index=True, unique=True)
|
||||
stamp = models.DateTimeField() # Date creation or validation of this entry
|
||||
|
||||
# New fields for 4.0, optional "custom" data, to be interpreted by specific code
|
||||
custom = models.TextField(blank=True, default='')
|
||||
|
||||
class Meta: # pylint: disable=too-few-public-methods
|
||||
app_label = 'uds'
|
||||
|
||||
def __str__(self):
|
||||
return f'<ActorToken {self.token} created on {self.stamp} by {self.username} from {self.hostname}/{self.ip_from}>'
|
@ -55,7 +55,8 @@ class RegisteredServers(models.Model):
|
||||
"""
|
||||
class ServerType(enum.IntFlag):
|
||||
TUNNEL = 1
|
||||
OTHER = 2
|
||||
ACTOR = 2
|
||||
OTHER = 99
|
||||
|
||||
def as_str(self) -> str:
|
||||
return self.name.lower() # type: ignore
|
||||
@ -63,6 +64,8 @@ class RegisteredServers(models.Model):
|
||||
username = models.CharField(max_length=128)
|
||||
ip_from = models.CharField(max_length=MAX_IPV6_LENGTH)
|
||||
ip = models.CharField(max_length=MAX_IPV6_LENGTH)
|
||||
ip_version = models.IntegerField(default=4) # 4 or 6, version of ip fields
|
||||
|
||||
hostname = models.CharField(max_length=MAX_DNS_NAME_LENGTH)
|
||||
|
||||
token = models.CharField(max_length=48, db_index=True, unique=True)
|
||||
@ -70,14 +73,10 @@ class RegisteredServers(models.Model):
|
||||
|
||||
kind = models.IntegerField(default=ServerType.TUNNEL.value) # Defaults to tunnel server, so we can migrate from previous versions
|
||||
|
||||
# "fake" declarations for type checking
|
||||
# objects: 'models.manager.Manager[TunnelToken]'
|
||||
data = models.JSONField(null=True, blank=True, default=None)
|
||||
|
||||
class Meta: # pylint: disable=too-few-public-methods
|
||||
app_label = 'uds'
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['ip', 'hostname'], name='tt_ip_hostname')
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def validateToken(
|
Loading…
x
Reference in New Issue
Block a user