2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-05-17 23:08:28 +00:00

refactor(backend): various SAST fixes (#11952)

* optimize asserts

* ensure redirect is safe

* sanatize token in log
This commit is contained in:
Matthias Mair
2026-05-15 23:53:37 +02:00
committed by GitHub
parent 2abaaff543
commit e3a22762e8
6 changed files with 34 additions and 12 deletions
@@ -1181,3 +1181,18 @@ def plugins_info(*args, **kwargs):
return [ return [
{'name': plg.name, 'slug': plg.slug, 'version': plg.version} for plg in plugins {'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:]
+13 -4
View File
@@ -10,12 +10,13 @@ from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import resolve, reverse, reverse_lazy from django.urls import resolve, reverse, reverse_lazy
from django.utils.deprecation import MiddlewareMixin 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 _ from django.utils.translation import gettext_lazy as _
import structlog import structlog
from error_report.middleware import ExceptionProcessor from error_report.middleware import ExceptionProcessor
import InvenTree.helpers
from common.settings import get_global_setting from common.settings import get_global_setting
from InvenTree.cache import create_session_cache, delete_session_cache from InvenTree.cache import create_session_cache, delete_session_cache
from InvenTree.config import CONFIG_LOOKUPS, inventreeInstaller from InvenTree.config import CONFIG_LOOKUPS, inventreeInstaller
@@ -126,7 +127,8 @@ class AuthRequiredMiddleware:
return True return True
except ApiToken.DoesNotExist: # pragma: no cover except ApiToken.DoesNotExist: # pragma: no cover
logger.warning( logger.warning(
'Access denied for unknown token %s', token 'Access denied for unknown token %s',
InvenTree.helpers.sanitize_token(str(token)),
) # pragma: no cover ) # pragma: no cover
return False return False
@@ -163,9 +165,16 @@ class AuthRequiredMiddleware:
if path not in urls and not any( if path not in urls and not any(
path.startswith(p) for p in paths_ignore_handling 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 # Save the 'next' parameter to pass through to the login view
return redirect(f'{reverse_lazy("account_login")}?next={next_url}')
return redirect(f'{reverse_lazy("account_login")}?next={request.path}')
# Return a 401 (Unauthorized) response code for this request # Return a 401 (Unauthorized) response code for this request
return HttpResponse('Unauthorized', status=401) return HttpResponse('Unauthorized', status=401)
@@ -299,7 +299,7 @@ class TestAddressMigration(MigratorTestCase):
c1 = Company.objects.filter(name='Company 1').first() c1 = Company.objects.filter(name='Company 1').first()
c2 = Company.objects.filter(name='Company 2').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() a1 = Address.objects.filter(company=c1.pk).first()
a2 = Address.objects.filter(company=c2.pk).first() a2 = Address.objects.filter(company=c2.pk).first()
+2 -2
View File
@@ -147,7 +147,7 @@ class MachineAPITest(TestMachineRegistryMixin, InvenTreeAPITestCase):
def test_machine_detail(self): def test_machine_detail(self):
"""Test machine detail API endpoint.""" """Test machine detail API endpoint."""
self.assertFalse(len(MachineConfig.objects.all()), 0) self.assertFalse(MachineConfig.objects.count(), 0)
self.get( self.get(
reverse('api-machine-detail', kwargs={'pk': self.placeholder_uuid}), reverse('api-machine-detail', kwargs={'pk': self.placeholder_uuid}),
expected_code=404, expected_code=404,
@@ -185,7 +185,7 @@ class MachineAPITest(TestMachineRegistryMixin, InvenTreeAPITestCase):
response = self.delete( response = self.delete(
reverse('api-machine-detail', kwargs={'pk': pk}), expected_code=204 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 # Create machine where the driver does not exist
machine_data['driver'] = 'non-existent-driver' machine_data['driver'] = 'non-existent-driver'
+2 -2
View File
@@ -43,8 +43,8 @@ class CategoryTest(TestCase):
self.assertTrue(self.electronics.has_children) self.assertTrue(self.electronics.has_children)
self.assertTrue(self.mechanical.has_children) self.assertTrue(self.mechanical.has_children)
self.assertEqual(len(self.electronics.children.all()), 3) self.assertEqual(self.electronics.children.count(), 3)
self.assertEqual(len(self.mechanical.children.all()), 1) self.assertEqual(self.mechanical.children.count(), 1)
def test_unique_children(self): def test_unique_children(self):
"""Test the 'unique_children' functionality.""" """Test the 'unique_children' functionality."""
+1 -3
View File
@@ -179,9 +179,7 @@ class ApiToken(AuthToken, InvenTree.models.MetadataMixin):
if self.pk is None: if self.pk is None:
return self.key # pragma: no cover return self.key # pragma: no cover
M = len(self.key) - 20 return InvenTree.helpers.sanitize_token(self.key)
return self.key[:8] + '*' * M + self.key[-12:]
@property @property
@admin.display(boolean=True, description=_('Expired')) @admin.display(boolean=True, description=_('Expired'))