diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index bd358a9fe6..502ac9c3b0 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -1181,3 +1181,18 @@ def plugins_info(*args, **kwargs): return [ {'name': plg.name, 'slug': plg.slug, 'version': plg.version} for plg in plugins ] + + +def sanitize_token(token_value: str, front=8, back=12) -> str: + """Sanitize a token by replacing the middle characters with asterisks. + + Args: + token_value: The token string to sanitize + front: Number of characters to show at the start of the token (default = 8) + back: Number of characters to show at the end of the token (default = 12) + + Returns: + The sanitized token string + """ + middle = len(token_value) - (front + back) + return token_value[:front] + '*' * middle + token_value[-back:] diff --git a/src/backend/InvenTree/InvenTree/middleware.py b/src/backend/InvenTree/InvenTree/middleware.py index 369d9c29a2..98ffc08c22 100644 --- a/src/backend/InvenTree/InvenTree/middleware.py +++ b/src/backend/InvenTree/InvenTree/middleware.py @@ -10,12 +10,13 @@ from django.http import HttpRequest, HttpResponse, JsonResponse from django.shortcuts import redirect, render from django.urls import resolve, reverse, reverse_lazy from django.utils.deprecation import MiddlewareMixin -from django.utils.http import is_same_domain +from django.utils.http import is_same_domain, url_has_allowed_host_and_scheme from django.utils.translation import gettext_lazy as _ import structlog from error_report.middleware import ExceptionProcessor +import InvenTree.helpers from common.settings import get_global_setting from InvenTree.cache import create_session_cache, delete_session_cache from InvenTree.config import CONFIG_LOOKUPS, inventreeInstaller @@ -126,7 +127,8 @@ class AuthRequiredMiddleware: return True except ApiToken.DoesNotExist: # pragma: no cover logger.warning( - 'Access denied for unknown token %s', token + 'Access denied for unknown token %s', + InvenTree.helpers.sanitize_token(str(token)), ) # pragma: no cover return False @@ -163,9 +165,16 @@ class AuthRequiredMiddleware: if path not in urls and not any( path.startswith(p) for p in paths_ignore_handling ): + # Validate next url is safe to redirect to + next_url = request.path + if not url_has_allowed_host_and_scheme( + url=next_url, + allowed_hosts=settings.ALLOWED_HOSTS, + require_https=request.is_secure(), + ): + return redirect(str(reverse_lazy('account_login'))) # Save the 'next' parameter to pass through to the login view - - return redirect(f'{reverse_lazy("account_login")}?next={request.path}') + return redirect(f'{reverse_lazy("account_login")}?next={next_url}') # Return a 401 (Unauthorized) response code for this request return HttpResponse('Unauthorized', status=401) diff --git a/src/backend/InvenTree/company/test_migrations.py b/src/backend/InvenTree/company/test_migrations.py index cfea9f7cca..741c7f187f 100644 --- a/src/backend/InvenTree/company/test_migrations.py +++ b/src/backend/InvenTree/company/test_migrations.py @@ -299,7 +299,7 @@ class TestAddressMigration(MigratorTestCase): c1 = Company.objects.filter(name='Company 1').first() c2 = Company.objects.filter(name='Company 2').first() - self.assertEqual(len(Address.objects.all()), 2) + self.assertEqual(Address.objects.count(), 2) a1 = Address.objects.filter(company=c1.pk).first() a2 = Address.objects.filter(company=c2.pk).first() diff --git a/src/backend/InvenTree/machine/test_api.py b/src/backend/InvenTree/machine/test_api.py index 20e9330542..32a2809d2f 100644 --- a/src/backend/InvenTree/machine/test_api.py +++ b/src/backend/InvenTree/machine/test_api.py @@ -147,7 +147,7 @@ class MachineAPITest(TestMachineRegistryMixin, InvenTreeAPITestCase): def test_machine_detail(self): """Test machine detail API endpoint.""" - self.assertFalse(len(MachineConfig.objects.all()), 0) + self.assertFalse(MachineConfig.objects.count(), 0) self.get( reverse('api-machine-detail', kwargs={'pk': self.placeholder_uuid}), expected_code=404, @@ -185,7 +185,7 @@ class MachineAPITest(TestMachineRegistryMixin, InvenTreeAPITestCase): response = self.delete( reverse('api-machine-detail', kwargs={'pk': pk}), expected_code=204 ) - self.assertFalse(len(MachineConfig.objects.all()), 0) + self.assertFalse(MachineConfig.objects.count(), 0) # Create machine where the driver does not exist machine_data['driver'] = 'non-existent-driver' diff --git a/src/backend/InvenTree/part/test_category.py b/src/backend/InvenTree/part/test_category.py index dca64697da..6d3c7f7578 100644 --- a/src/backend/InvenTree/part/test_category.py +++ b/src/backend/InvenTree/part/test_category.py @@ -43,8 +43,8 @@ class CategoryTest(TestCase): self.assertTrue(self.electronics.has_children) self.assertTrue(self.mechanical.has_children) - self.assertEqual(len(self.electronics.children.all()), 3) - self.assertEqual(len(self.mechanical.children.all()), 1) + self.assertEqual(self.electronics.children.count(), 3) + self.assertEqual(self.mechanical.children.count(), 1) def test_unique_children(self): """Test the 'unique_children' functionality.""" diff --git a/src/backend/InvenTree/users/models.py b/src/backend/InvenTree/users/models.py index a89f3f921c..54fb8ae2da 100644 --- a/src/backend/InvenTree/users/models.py +++ b/src/backend/InvenTree/users/models.py @@ -179,9 +179,7 @@ class ApiToken(AuthToken, InvenTree.models.MetadataMixin): if self.pk is None: return self.key # pragma: no cover - M = len(self.key) - 20 - - return self.key[:8] + '*' * M + self.key[-12:] + return InvenTree.helpers.sanitize_token(self.key) @property @admin.display(boolean=True, description=_('Expired'))