Adding more tests and fixes

This commit is contained in:
Adolfo Gómez García 2022-08-24 22:16:47 +02:00
parent 5f12c2e7b3
commit 69b778c922
22 changed files with 342 additions and 51 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2020 Virtual Cable S.L. # Copyright (c) 2020-2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -35,7 +35,7 @@ import os
import logging import logging
import typing import typing
import PyQt5 # pylint: disable=unused-import import PyQt5 # Ensures PyQt is included in the package
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox
import udsactor import udsactor

View File

@ -326,6 +326,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
if self.isManaged() if self.isManaged()
else (initResult.alias_token or self._cfg.master_token) else (initResult.alias_token or self._cfg.master_token)
) )
# Replace master token with alias token if present
self._cfg = self._cfg._replace( self._cfg = self._cfg._replace(
master_token=master_token, master_token=master_token,
own_token=initResult.own_token, own_token=initResult.own_token,

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2022 Virtual Cable S.L.U. # Copyright (c) 2022 Virtual Cable S.L.U.
@ -29,6 +28,5 @@
""" """
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
from . import test_login_logout
from . import test_register from . import actor
from . import test_initialize

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 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 . import test_login_logout
from . import test_register
from . import test_initialize

View File

@ -37,9 +37,8 @@ from django.conf import settings
from uds import models from uds import models
from uds.core import VERSION from uds.core import VERSION
from uds.REST.handlers import AUTH_TOKEN_HEADER from uds.REST.handlers import AUTH_TOKEN_HEADER
from uds.models import user_service
from ..utils import rest, constants from ...utils import rest
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -153,35 +152,42 @@ class ActorInitializeV3(rest.test.RESTTestCase):
success = functools.partial(self.invoke_success, 'unmanaged') success = functools.partial(self.invoke_success, 'unmanaged')
failure = functools.partial(self.invoke_failure, 'unmanaged') failure = functools.partial(self.invoke_failure, 'unmanaged')
# This will succeed, but only alias token is provided # This will succeed, but only alias token is returned because MAC is not registered by UDS
result = success( result = success(
actor_token, actor_token,
mac='00:00:00:00:00:00', mac='00:00:00:00:00:00',
) )
alias_token = result['alias_token']
# Unmanaged host is the response for initialization of unmanaged actor ALWAYS # Unmanaged host is the response for initialization of unmanaged actor ALWAYS
self.assertIsNone(result['alias_token'])
self.assertIsNone(result['own_token']) self.assertIsNone(result['own_token'])
self.assertIsInstance(alias_token, str)
self.assertIsNone(result['unique_id']) self.assertIsNone(result['unique_id'])
self.assertIsNone(result['os']) self.assertIsNone(result['os'])
# Now, invoke a "nice" initialize
result = success(
actor_token,
mac=unique_id,
)
alias_token = result['alias_token']
self.assertIsInstance(alias_token, str)
self.assertEqual(result['own_token'], user_service.uuid)
self.assertEqual(result['alias_token'], alias_token)
self.assertEqual(result['unique_id'], unique_id)
# Ensure that the alias returned is on alias db, and it points to the same service as the one we belong to # Ensure that the alias returned is on alias db, and it points to the same service as the one we belong to
alias = models.ServiceTokenAlias.objects.get(alias=result['alias_token']) alias = models.ServiceTokenAlias.objects.get(alias=result['alias_token'])
self.assertEqual(alias.service, user_service.deployed_service.service) self.assertEqual(alias.service, user_service.deployed_service.service)
# Now, we should be able to "initialize" with valid mac and with original and alias tokens # Now, we should be able to "initialize" with valid mac and with original and alias tokens
# If we call initialize and we get "own-token" means that we have already logged in with this data # If we call initialize and we get "own-token" means that we have already logged in with this data
result = success( result = success(alias_token, mac=unique_id)
alias_token,
mac=unique_id
)
self.assertEqual(result['own_token'], user_service.uuid) self.assertEqual(result['own_token'], user_service.uuid)
self.assertEqual(result['alias_token'], alias_token) self.assertEqual(result['alias_token'], alias_token)
self.assertEqual(result['unique_id'], unique_id) self.assertEqual(result['unique_id'], unique_id)
#
failure('invalid token', unique_id, True)
logger.info('Result: %s', result)

View File

@ -32,8 +32,8 @@ import random
import typing import typing
from .. import fixtures from ... import fixtures
from ..utils import rest, test from ...utils import rest, test
class RESTLoginLogoutCase(test.UDSTestCase): class RESTLoginLogoutCase(test.UDSTestCase):
@ -41,7 +41,7 @@ class RESTLoginLogoutCase(test.UDSTestCase):
Test login and logout Test login and logout
""" """
def test_login_logout(self): def test_login_logout(self) -> None:
""" """
Test login and logout Test login and logout
""" """

View File

@ -31,12 +31,10 @@
import typing import typing
import logging import logging
from django.conf import settings
from uds import models from uds import models
from uds.REST.handlers import AUTH_TOKEN_HEADER from uds.REST.handlers import AUTH_TOKEN_HEADER
from ..utils import rest, constants from ...utils import rest, constants
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -34,7 +34,7 @@ import logging
from uds.REST.handlers import AUTH_TOKEN_HEADER from uds.REST.handlers import AUTH_TOKEN_HEADER
from uds.REST.methods.actor_v3 import MANAGED, UNMANAGED, ALLOWED_FAILS from uds.REST.methods.actor_v3 import MANAGED, UNMANAGED, ALLOWED_FAILS
from ..utils import rest from ...utils import rest
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2022 Virtual Cable S.L. # Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -11,7 +11,7 @@
# * Redistributions in binary form must reproduce the above copyright notice, # * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation # this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution. # and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors # * 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 # may be used to endorse or promote products derived from this software
# without specific prior written permission. # without specific prior written permission.
# #
@ -28,5 +28,5 @@
""" """
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
from . import test_cache from . import util
from . import test_stats_counters from . import managers

View File

@ -0,0 +1 @@
This file is the downloadable for download manager tests

View File

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 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. 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
import os.path
import sys
# We use commit/rollback
from ...utils.test import UDSTransactionTestCase
from django.urls import reverse
from uds.core.managers.downloads import DownloadsManager
from uds.core.util.config import GlobalConfig
if typing.TYPE_CHECKING:
from django.http import HttpResponse
class DownloadsManagerTests(UDSTransactionTestCase):
filePath: str = ''
manager: DownloadsManager
@classmethod
def setUpClass(cls):
from uds.core.managers import downloadsManager
super(DownloadsManagerTests, cls).setUpClass()
cls.filePath = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'downloadable.txt'
)
cls.manager = downloadsManager()
def test_downloads(self):
for v in (
('test.txt', 'text/plain', '1f47ec0a-1ad4-5d63-b41c-5d2befadab8d'),
(
'test.bin',
'application/octet-stream',
'6454d619-cc62-5bd4-aa9a-c9e2458d44da',
),
):
fileName, mimeType, knownUuid = v
self.manager.registerDownloadable(
fileName,
'This is the test file {}'.format(fileName),
self.filePath,
mimeType,
)
downloadables = self.manager.getDownloadables()
self.assertIn(
knownUuid,
downloadables,
'The File {} was not found in downloadables!'.format(fileName),
)
# This will fail, no user has logged in
self.client.get(
reverse('utility.downloader', kwargs={'idDownload': knownUuid})
)
# Remove last '/' for redirect check. URL redirection will not contain it
# Commented because i don't know why when executed in batch returns the last '/', and alone don't
# self.assertRedirects(response, reverse('uds.web.views.login'), fetch_redirect_response=False)
# And try to download again
response = self.client.get(
reverse('utility.downloader', kwargs={'idDownload': knownUuid})
)
self.assertEqual(
response.get('Content-Type'),
mimeType,
'Mime type of {} is not {} as expected (it is {})'.format(
fileName, mimeType, response.get('Content-Type')
),
)
self.assertContains(
response,
'This file is the downloadable for download manager tests',
msg_prefix='File does not seems to be fine',
)
# Now do logout
# response = client.get(reverse('uds.web.views.logout'))
# self.assertRedirects(response, reverse('uds.web.views.login')[:-1], fetch_redirect_response=False)

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.
# 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. 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 . import test_cache
from . import test_stats_counters

View File

@ -32,7 +32,7 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
# We use commit/rollback # We use commit/rollback
from django.test import TransactionTestCase as TestCase from ...utils.test import UDSTransactionTestCase
from uds.core.util.cache import Cache from uds.core.util.cache import Cache
import time import time
@ -41,7 +41,7 @@ UNICODE_CHARS_2 = 'ñöçóá^(€íöè)'
VALUE_1 = [u'únîcödè€', b'string', {'a': 1, 'b': 2.0}] VALUE_1 = [u'únîcödè€', b'string', {'a': 1, 'b': 2.0}]
class CacheTests(TestCase): class CacheTests(UDSTransactionTestCase):
def test_cache(self): def test_cache(self):
cache = Cache(UNICODE_CHARS) cache = Cache(UNICODE_CHARS)
@ -60,7 +60,7 @@ class CacheTests(TestCase):
# Add new "str" key, with 1 second life, wait 2 seconds and recover # Add new "str" key, with 1 second life, wait 2 seconds and recover
cache.put(b'key', VALUE_1, 1) cache.put(b'key', VALUE_1, 1)
time.sleep(2) time.sleep(1.1)
self.assertEqual(cache.get(b'key'), None, 'Put an "str" key and recover it once it has expired') self.assertEqual(cache.get(b'key'), None, 'Put an "str" key and recover it once it has expired')
# Refresh previous key and will be again available # Refresh previous key and will be again available
@ -79,7 +79,7 @@ class CacheTests(TestCase):
# Checks cache cleanup (remove expired keys) # Checks cache cleanup (remove expired keys)
cache.put('key', 'test', 0) cache.put('key', 'test', 0)
time.sleep(1) time.sleep(0.1)
Cache.cleanUp() Cache.cleanUp()
cache.refresh('key') cache.refresh('key')
self.assertEqual(cache.get('key'), None, 'Put a key and recover it once it has expired and has been cleaned') self.assertEqual(cache.get('key'), None, 'Put a key and recover it once it has expired and has been cleaned')

View File

@ -34,10 +34,10 @@
import datetime import datetime
import typing import typing
from ..fixtures.stats_counters import create_stats_counters from ...fixtures.stats_counters import create_stats_counters
# We use commit/rollback # We use commit/rollback
from django.test import TestCase from ...utils.test import UDSTransactionTestCase
from uds.core.util.stats import counters from uds.core.util.stats import counters
from uds import models from uds import models
@ -47,7 +47,7 @@ END_DATE_DAY = datetime.datetime(2020, 1, 2, 0, 0, 0)
END_DATE_MONTH = datetime.datetime(2020, 2, 1, 0, 0, 0) END_DATE_MONTH = datetime.datetime(2020, 2, 1, 0, 0, 0)
END_DATE_YEAR = datetime.datetime(2021, 1, 1, 0, 0, 0) END_DATE_YEAR = datetime.datetime(2021, 1, 1, 0, 0, 0)
class StatsCountersTests(TestCase): class StatsCountersTests(UDSTransactionTestCase):
def setUp(self) -> None: def setUp(self) -> None:
return super().setUp() return super().setUp()

93
server/src/tests/fixtures/calendars.py vendored Normal file
View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 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
import datetime
import random
from uds import models
from uds.models.calendar_rule import freqs, dunits
# Counters so we can reinvoke the same method and generate new data
glob = {
'calendar_id': 1,
'calendar_rule_id': 1,
}
def createCalendars(number: int) -> typing.List[models.Calendar]:
"""
Creates some testing calendars
"""
calendars: typing.List[models.Calendar] = []
for i in range(number):
calendar = models.Calendar.objects.create(
name='Calendar {}'.format(glob['calendar_id']),
comments='Calendar {} comments'.format(glob['calendar_id']),
)
calendars.append(calendar)
glob['calendar_id'] += 1
return calendars
def createRules(number: int, calendar: models.Calendar) -> typing.List[models.CalendarRule]:
"""
Creates some testing rules associated to a calendar
"""
rules: typing.List[models.CalendarRule] = []
rnd = random.Random() # nosec: testing purposes
for i in range(number):
# All rules will start now
# Rules duration will be a random between 1 and 10 days
# freqs a random value from freqs
# interval is a random value between 1 and 10
# duration is a random value between 0 and 24
# duration_unit is a random from dunits
start = datetime.datetime.now()
end = start + datetime.timedelta(days=rnd.randint(1, 10))
freq = rnd.choice(freqs)
interval = rnd.randint(1, 10)
duration = rnd.randint(0, 24)
duration_unit = rnd.choice(dunits)
rule = models.CalendarRule.objects.create(
calendar=calendar,
name='Rule {}'.format(glob['calendar_rule_id']),
comments='Rule {} comments'.format(glob['calendar_rule_id']),
start=start,
end=end,
freq=freq,
interval=interval,
duration=duration,
duration_unit=duration_unit,
)
rules.append(rule)
glob['calendar_rule_id'] += 1
return rules

View File

@ -42,7 +42,7 @@ from uds.REST.handlers import AUTH_TOKEN_HEADER
NUMBER_OF_ITEMS_TO_CREATE = 4 NUMBER_OF_ITEMS_TO_CREATE = 4
class RESTTestCase(test.UDSTransactionTestCasse): class RESTTestCase(test.UDSTransactionTestCase):
# Authenticators related # Authenticators related
auth: models.Authenticator auth: models.Authenticator
groups: typing.List[models.Group] groups: typing.List[models.Group]

View File

@ -33,6 +33,7 @@ import logging
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
from django.test.client import Client from django.test.client import Client
from django.http import HttpResponse
from django.conf import settings from django.conf import settings
from uds import models from uds import models
@ -76,12 +77,19 @@ class UDSClient(Client):
return super().request(**request) return super().request(**request)
def get(self, *args, **kwargs) -> 'HttpResponse':
return typing.cast('HttpResponse', super().get(*args, **kwargs))
def post(self, *args, **kwargs) -> 'HttpResponse':
return typing.cast('HttpResponse', super().post(*args, **kwargs))
class UDSTestCase(TestCase): class UDSTestCase(TestCase):
client_class: typing.Type = UDSClient client_class: typing.Type = UDSClient
client: UDSClient client: UDSClient
class UDSTransactionTestCasse(TransactionTestCase): class UDSTransactionTestCase(TransactionTestCase):
client_class: typing.Type = UDSClient client_class: typing.Type = UDSClient
client: UDSClient client: UDSClient

View File

@ -31,6 +31,8 @@
import random import random
import typing import typing
from django.urls import reverse
from uds.core.util.config import GlobalConfig from uds.core.util.config import GlobalConfig
from ...utils import test from ...utils import test
@ -43,7 +45,7 @@ from uds import models
class WebLoginLogout(test.UDSTransactionTestCasse): class WebLoginLogout(test.UDSTransactionTestCase):
""" """
Test WEB login and logout Test WEB login and logout
""" """
@ -105,7 +107,7 @@ class WebLoginLogout(test.UDSTransactionTestCasse):
# Now invoke logout # Now invoke logout
response = typing.cast('HttpResponse', self.client.get('/uds/page/logout')) response = typing.cast('HttpResponse', self.client.get('/uds/page/logout'))
self.assertRedirects( self.assertRedirects(
response, 'http://testserver/uds/page/login', status_code=302 response, reverse('page.login'), status_code=302
) )
# Ensures a couple of logs are created for every operation # Ensures a couple of logs are created for every operation
# Except for root, that has no user associated on db # Except for root, that has no user associated on db

View File

@ -302,20 +302,16 @@ class Initialize(ActorV3Action):
token = self._params['token'] token = self._params['token']
# First, try to locate an user service providing this token. # First, try to locate an user service providing this token.
if self._params['type'] == UNMANAGED: if self._params['type'] == UNMANAGED:
alias_token = token # Store token as possible alias
# First, try to locate on alias table # First, try to locate on alias table
if ServiceTokenAlias.objects.filter(alias=token).exists(): if ServiceTokenAlias.objects.filter(alias=token).exists():
# Retrieve real service from token alias # Retrieve real service from token alias
service = ServiceTokenAlias.objects.get(alias=token).service service = ServiceTokenAlias.objects.get(alias=token).service
alias_token = token # Store token as possible alias
# If not found an alias, try to locate on service table # If not found an alias, try to locate on service table
# Not on alias token, try to locate on Service table # Not on alias token, try to locate on Service table
if not service: if not service:
service = typing.cast('Service', Service.objects.get(token=token)) service = typing.cast('Service', Service.objects.get(token=token))
# And create a new alias for it, and save
alias_token = (
cryptoManager().randomString(40)
) # fix alias with new token
service.aliases.create(alias=alias_token)
# Locate an userService that belongs to this service and which # Locate an userService that belongs to this service and which
# Build the possible ids and make initial filter to match service # Build the possible ids and make initial filter to match service
@ -355,6 +351,14 @@ class Initialize(ActorV3Action):
if osManager: if osManager:
osData = osManager.actorData(userService) osData = osManager.actorData(userService)
if service and not alias_token: # Is a service managed by UDS
# Create a new alias for it, and save
alias_token = (
cryptoManager().randomString(40)
) # fix alias with new token
service.aliases.create(alias=alias_token)
return initialization_result( return initialization_result(
userService.uuid, userService.unique_id, osData, alias_token userService.uuid, userService.unique_id, osData, alias_token
) )

View File

@ -454,7 +454,7 @@ def webLogout(
username = request.user.name username = request.user.name
logout = authenticator.logout(request, username) logout = authenticator.logout(request, username)
if logout and logout.success == auths.AuthenticationSuccess.REDIRECT: if logout and logout.success == auths.AuthenticationSuccess.REDIRECT:
exit_url = logout.url exit_url = logout.url or exit_url
if request.user.id != ROOT_ID: if request.user.id != ROOT_ID:
# Log the event if not root user # Log the event if not root user
events.addEvent( events.addEvent(
@ -472,7 +472,7 @@ def webLogout(
request.session.flush() request.session.flush()
request.authorized = False request.authorized = False
response = HttpResponseRedirect(exit_url) # type: ignore response = HttpResponseRedirect(exit_url)
if authenticator: if authenticator:
authenticator.webLogoutHook(username, request, response) authenticator.webLogoutHook(username, request, response)
return response return response

View File

@ -109,12 +109,12 @@ class CalendarRule(UUIDModel):
duration = models.IntegerField(default=0) # Duration in minutes duration = models.IntegerField(default=0) # Duration in minutes
duration_unit = models.CharField(choices=dunits, default='MINUTES', max_length=32) duration_unit = models.CharField(choices=dunits, default='MINUTES', max_length=32)
calendar: 'models.ForeignKey[CalendarRule, Calendar]' = models.ForeignKey( calendar: 'models.ForeignKey["CalendarRule", Calendar]' = models.ForeignKey(
Calendar, related_name='rules', on_delete=models.CASCADE Calendar, related_name='rules', on_delete=models.CASCADE
) )
# "fake" declarations for type checking # "fake" declarations for type checking
objects: 'models.manager.Manager[CalendarRule]' objects: 'models.manager.Manager["CalendarRule"]'
class Meta: class Meta:
""" """