2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-23 09:27:39 +00:00

Fixes for SITE_URL validity checks (#10619)

* [docker] Allow HTTPS port to be specified for Caddy proxy

* Fix naming collision for INVENTREE_WEB_PORT

* Push InvenTree version first

* Adjust Caddyfile

- Change backup server

* Fix docstring

* Tweak for site URL check:

- Ignore port if SITE_LAX_PROTOCOL_CHECK is set
- Invert logic for readability

* Additional checks for port mismatch

* Adjust middleware checks

- Allow for less strict checking of CSRF_TRUSTED_ORIGINS

* Slight refactor
This commit is contained in:
Oliver
2025-10-20 16:05:37 +11:00
committed by GitHub
parent 5425ace1fa
commit f9ce9e20b2
5 changed files with 78 additions and 20 deletions

View File

@@ -30,9 +30,9 @@
}
# The default server address is configured in the .env file
# If not specified, the default address is used - http://inventree.localhost
# If not specified, the proxy listens for all http/https traffic
# If you need to listen on multiple addresses, or use a different port, you can modify this section directly
{$INVENTREE_SITE_URL:http://inventree.localhost} {
{$INVENTREE_SITE_URL:"http://, https://"} {
import log_common inventree
encode gzip

View File

@@ -101,6 +101,7 @@ services:
restart: unless-stopped
# caddy acts as reverse proxy and static file server
# You can adjust the ports that the proxy listens on via the .env file
# https://hub.docker.com/_/caddy
inventree-proxy:
container_name: inventree-proxy
@@ -109,8 +110,8 @@ services:
depends_on:
- inventree-server
ports:
- ${INVENTREE_WEB_PORT:-80}:80
- 443:443
- ${INVENTREE_HTTP_PORT:-80}:80
- ${INVENTREE_HTTPS_PORT:-443}:443
env_file:
- .env
volumes:

View File

@@ -239,13 +239,29 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin):
accessed_scheme = request._current_scheme_host
referer = urlsplit(accessed_scheme)
# Ensure that the settings are set correctly with the current request
matches = (
(accessed_scheme and not accessed_scheme.startswith(settings.SITE_URL))
if not settings.SITE_LAX_PROTOCOL_CHECK
else not is_same_domain(referer.netloc, urlsplit(settings.SITE_URL).netloc)
site_url = urlsplit(settings.SITE_URL)
# Check if the accessed URL matches the SITE_URL setting
site_url_match = (
(
# Exact match on domain
is_same_domain(referer.netloc, site_url.netloc)
and referer.scheme == site_url.scheme
)
or (
# Lax protocol match, accessed URL starts with SITE_URL
settings.SITE_LAX_PROTOCOL_CHECK
and accessed_scheme.startswith(settings.SITE_URL)
)
or (
# Lax protocol match, same domain
settings.SITE_LAX_PROTOCOL_CHECK
and referer.hostname == site_url.hostname
)
)
if matches:
if not site_url_match:
# The accessed URL does not match the SITE_URL setting
if (
isinstance(settings.CSRF_TRUSTED_ORIGINS, list)
and len(settings.CSRF_TRUSTED_ORIGINS) > 1
@@ -263,17 +279,31 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin):
request, 'config_error.html', {'error_message': msg}, status=500
)
# Check trusted origins
if not any(
is_same_domain(referer.netloc, host)
for host in [
urlsplit(origin).netloc.lstrip('*')
trusted_origins_match = (
# Matching domain found in allowed origins
any(
is_same_domain(referer.netloc, host)
for host in [
urlsplit(origin).netloc.lstrip('*')
for origin in settings.CSRF_TRUSTED_ORIGINS
]
)
) or (
# Lax protocol match allowed
settings.SITE_LAX_PROTOCOL_CHECK
and any(
referer.hostname == urlsplit(origin).hostname
for origin in settings.CSRF_TRUSTED_ORIGINS
]
):
)
)
# Check trusted origins
if not trusted_origins_match:
msg = f'INVE-E7: The used path `{accessed_scheme}` is not in the TRUSTED_ORIGINS'
logger.error(msg)
return render(
request, 'config_error.html', {'error_message': msg}, status=500
)
# All checks passed
return None

View File

@@ -112,6 +112,15 @@ class MiddlewareTests(InvenTreeTestCase):
def test_site_lax_protocol(self):
"""Test that the site URL check is correctly working with/without lax protocol check."""
# Test that a completely different host fails
with self.settings(
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
):
response = self.client.get(
reverse('web'), HTTP_HOST='otherhost.example.com'
)
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
# Simple setup with proxy
with self.settings(
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
@@ -128,6 +137,24 @@ class MiddlewareTests(InvenTreeTestCase):
response = self.client.get(reverse('web'))
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
def test_site_url_port(self):
"""URL checks with different ports."""
with self.settings(
SITE_URL='https://testserver:8000',
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
):
response = self.client.get(reverse('web'), HTTP_HOST='testserver:8008')
self.do_positive_test(response)
# Try again with strict protocol check
with self.settings(
SITE_URL='https://testserver:8000',
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
SITE_LAX_PROTOCOL_CHECK=False,
):
response = self.client.get(reverse('web'), HTTP_HOST='testserver:8008')
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
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
@@ -149,7 +176,7 @@ class MiddlewareTests(InvenTreeTestCase):
)
self.do_positive_test(response)
# A non-trsuted origin must still fail in multi - origin setup
# A non-trusted 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',

View File

@@ -1556,10 +1556,10 @@ Static {get_static_dir(error=False) or NOT_SPECIFIED}
Backup {get_backup_dir(error=False) or NOT_SPECIFIED}
Versions:
Python {python_version()}
Django {InvenTreeVersion.inventreeDjangoVersion()}
InvenTree {InvenTreeVersion.inventreeVersion()}
API {InvenTreeVersion.inventreeApiVersion()}
Python {python_version()}
Django {InvenTreeVersion.inventreeDjangoVersion()}
Node {node if node else NA}
Yarn {yarn if yarn else NA}