From 4b0acad518ce0bb9789fa0861905d835be313bfd Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 19 Sep 2025 10:18:03 +0200 Subject: [PATCH] fix(backend): better siteurl testing in middleware (#10335) * fix(backend): simplify siteurl testing * add multi-site test * pass off site_url check if more than one trusted origin is set * split up testing * add temporary debug info * fix test enviorment --- src/backend/InvenTree/InvenTree/middleware.py | 17 ++- .../InvenTree/InvenTree/test_middleware.py | 103 ++++++++++++++---- 2 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/middleware.py b/src/backend/InvenTree/InvenTree/middleware.py index 91e8ab7d8d..4bfb0ace37 100644 --- a/src/backend/InvenTree/InvenTree/middleware.py +++ b/src/backend/InvenTree/InvenTree/middleware.py @@ -237,11 +237,18 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin): # Ensure that the settings are set correctly with the current request accessed_scheme = request._current_scheme_host if accessed_scheme and not accessed_scheme.startswith(settings.SITE_URL): - msg = f'INVE-E7: The used path `{accessed_scheme}` does not match the SITE_URL `{settings.SITE_URL}`' - logger.error(msg) - return render( - request, 'config_error.html', {'error_message': msg}, status=500 - ) + if ( + isinstance(settings.CSRF_TRUSTED_ORIGINS, list) + and len(settings.CSRF_TRUSTED_ORIGINS) > 1 + ): + # The used url might not be the primary url - next check determines if in a trusted origins + pass + else: + msg = f'INVE-E7: The used path `{accessed_scheme}` does not match the SITE_URL `{settings.SITE_URL}`' + logger.error(msg) + return render( + request, 'config_error.html', {'error_message': msg}, status=500 + ) # Check trusted origins referer = urlsplit(accessed_scheme) diff --git a/src/backend/InvenTree/InvenTree/test_middleware.py b/src/backend/InvenTree/InvenTree/test_middleware.py index 5c09058162..f308d0526d 100644 --- a/src/backend/InvenTree/InvenTree/test_middleware.py +++ b/src/backend/InvenTree/InvenTree/test_middleware.py @@ -1,5 +1,6 @@ """Tests for middleware functions.""" +from django.conf import settings from django.http import Http404 from django.urls import reverse @@ -87,36 +88,72 @@ class MiddlewareTests(InvenTreeTestCase): log_error('testpath') check(1) + def do_positive_test(self, response): + """Helper function to check for positive test results.""" + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'INVE-E7') + self.assertContains(response, 'window.INVENTREE_SETTINGS') + def test_site_url_checks(self): """Test that the site URL check is correctly working.""" - # correctly set + # simple setup with self.settings( SITE_URL='http://testserver', CSRF_TRUSTED_ORIGINS=['http://testserver'] ): response = self.client.get(reverse('web')) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, 'INVE-E7') - self.assertContains(response, 'window.INVENTREE_SETTINGS') + self.do_positive_test(response) - # wrongly set site URL - with self.settings(SITE_URL='https://example.com'): + # simple setup with wildcard + with self.settings( + SITE_URL='http://testserver', CSRF_TRUSTED_ORIGINS=['http://*.testserver'] + ): response = self.client.get(reverse('web')) - self.assertEqual(response.status_code, 500) - self.assertContains( - response, - 'INVE-E7: The used path `http://testserver` does not match', - status_code=500, - ) - self.assertNotContains( - response, 'window.INVENTREE_SETTINGS', status_code=500 - ) + self.do_positive_test(response) + def test_site_url_checks_multi(self): + """Test that the site URL check is correctly working in a multi-site setup.""" + # multi-site setup with trusted origins + with self.settings( + SITE_URL='https://testserver.example.com', + CSRF_TRUSTED_ORIGINS=[ + 'http://testserver', + 'https://testserver.example.com', + ], + ): + # this will run with testserver as host by default + response = self.client.get(reverse('web')) + self.do_positive_test(response) + + # Now test with the "outside" url name + response = self.client.get( + 'https://testserver.example.com/web/', + SERVER_NAME='testserver.example.com', + ) + self.do_positive_test(response) + + # A non-trsuted origin must still fail in multi - origin setup + response = self.client.get( + 'https://not-my-testserver.example.com/web/', + SERVER_NAME='not-my-testserver.example.com', + ) + self.assertEqual(response.status_code, 500) + + # Even if it is a subdomain + response = self.client.get( + 'https://not-my.testserver.example.com/web/', + SERVER_NAME='not-my.testserver.example.com', + ) + self.assertEqual(response.status_code, 500) + + def test_site_url_checks_fails(self): + """Test that the site URL check is correctly failing. + + Important for security. + """ # wrongly set but in debug -> is ignored with self.settings(SITE_URL='https://example.com', DEBUG=True): response = self.client.get(reverse('web')) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, 'INVE-E7') - self.assertContains(response, 'window.INVENTREE_SETTINGS') + self.do_positive_test(response) # wrongly set cors with self.settings( @@ -133,10 +170,32 @@ class MiddlewareTests(InvenTreeTestCase): response, 'window.INVENTREE_SETTINGS', status_code=500 ) + # wrongly set site URL with self.settings( - SITE_URL='http://testserver', CSRF_TRUSTED_ORIGINS=['http://*.testserver'] + SITE_URL='https://example.com', + CSRF_TRUSTED_ORIGINS=['http://localhost:8000'], ): response = self.client.get(reverse('web')) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, 'INVE-E7') - self.assertContains(response, 'window.INVENTREE_SETTINGS') + self.assertEqual(response.status_code, 500) + self.assertContains( + response, 'INVE-E7: The used path `http://testserver` ', status_code=500 + ) + self.assertNotContains( + response, 'window.INVENTREE_SETTINGS', status_code=500 + ) + + # Log stuff # TODO remove + print( + '###DBG-TST###', + 'site', + settings.SITE_URL, + 'trusted', + settings.CSRF_TRUSTED_ORIGINS, + ) + + # Check that the correct step triggers the error message + self.assertContains( + response, + 'INVE-E7: The used path `http://testserver` does not match', + status_code=500, + )