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:
@@ -30,9 +30,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
# The default server address is configured in the .env file
|
# 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
|
# 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
|
import log_common inventree
|
||||||
|
|
||||||
encode gzip
|
encode gzip
|
||||||
|
@@ -101,6 +101,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# caddy acts as reverse proxy and static file server
|
# 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
|
# https://hub.docker.com/_/caddy
|
||||||
inventree-proxy:
|
inventree-proxy:
|
||||||
container_name: inventree-proxy
|
container_name: inventree-proxy
|
||||||
@@ -109,8 +110,8 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- inventree-server
|
- inventree-server
|
||||||
ports:
|
ports:
|
||||||
- ${INVENTREE_WEB_PORT:-80}:80
|
- ${INVENTREE_HTTP_PORT:-80}:80
|
||||||
- 443:443
|
- ${INVENTREE_HTTPS_PORT:-443}:443
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
|
@@ -239,13 +239,29 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin):
|
|||||||
accessed_scheme = request._current_scheme_host
|
accessed_scheme = request._current_scheme_host
|
||||||
referer = urlsplit(accessed_scheme)
|
referer = urlsplit(accessed_scheme)
|
||||||
|
|
||||||
# Ensure that the settings are set correctly with the current request
|
site_url = urlsplit(settings.SITE_URL)
|
||||||
matches = (
|
|
||||||
(accessed_scheme and not accessed_scheme.startswith(settings.SITE_URL))
|
# Check if the accessed URL matches the SITE_URL setting
|
||||||
if not settings.SITE_LAX_PROTOCOL_CHECK
|
site_url_match = (
|
||||||
else not is_same_domain(referer.netloc, urlsplit(settings.SITE_URL).netloc)
|
(
|
||||||
|
# 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 (
|
if (
|
||||||
isinstance(settings.CSRF_TRUSTED_ORIGINS, list)
|
isinstance(settings.CSRF_TRUSTED_ORIGINS, list)
|
||||||
and len(settings.CSRF_TRUSTED_ORIGINS) > 1
|
and len(settings.CSRF_TRUSTED_ORIGINS) > 1
|
||||||
@@ -263,17 +279,31 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin):
|
|||||||
request, 'config_error.html', {'error_message': msg}, status=500
|
request, 'config_error.html', {'error_message': msg}, status=500
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check trusted origins
|
trusted_origins_match = (
|
||||||
if not any(
|
# Matching domain found in allowed origins
|
||||||
is_same_domain(referer.netloc, host)
|
any(
|
||||||
for host in [
|
is_same_domain(referer.netloc, host)
|
||||||
urlsplit(origin).netloc.lstrip('*')
|
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
|
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'
|
msg = f'INVE-E7: The used path `{accessed_scheme}` is not in the TRUSTED_ORIGINS'
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
return render(
|
return render(
|
||||||
request, 'config_error.html', {'error_message': msg}, status=500
|
request, 'config_error.html', {'error_message': msg}, status=500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# All checks passed
|
||||||
return None
|
return None
|
||||||
|
@@ -112,6 +112,15 @@ class MiddlewareTests(InvenTreeTestCase):
|
|||||||
|
|
||||||
def test_site_lax_protocol(self):
|
def test_site_lax_protocol(self):
|
||||||
"""Test that the site URL check is correctly working with/without lax protocol check."""
|
"""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
|
# Simple setup with proxy
|
||||||
with self.settings(
|
with self.settings(
|
||||||
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
|
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
|
||||||
@@ -128,6 +137,24 @@ class MiddlewareTests(InvenTreeTestCase):
|
|||||||
response = self.client.get(reverse('web'))
|
response = self.client.get(reverse('web'))
|
||||||
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
|
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):
|
def test_site_url_checks_multi(self):
|
||||||
"""Test that the site URL check is correctly working in a multi-site setup."""
|
"""Test that the site URL check is correctly working in a multi-site setup."""
|
||||||
# multi-site setup with trusted origins
|
# multi-site setup with trusted origins
|
||||||
@@ -149,7 +176,7 @@ class MiddlewareTests(InvenTreeTestCase):
|
|||||||
)
|
)
|
||||||
self.do_positive_test(response)
|
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(
|
response = self.client.get(
|
||||||
'https://not-my-testserver.example.com/web/',
|
'https://not-my-testserver.example.com/web/',
|
||||||
SERVER_NAME='not-my-testserver.example.com',
|
SERVER_NAME='not-my-testserver.example.com',
|
||||||
|
4
tasks.py
4
tasks.py
@@ -1556,10 +1556,10 @@ Static {get_static_dir(error=False) or NOT_SPECIFIED}
|
|||||||
Backup {get_backup_dir(error=False) or NOT_SPECIFIED}
|
Backup {get_backup_dir(error=False) or NOT_SPECIFIED}
|
||||||
|
|
||||||
Versions:
|
Versions:
|
||||||
Python {python_version()}
|
|
||||||
Django {InvenTreeVersion.inventreeDjangoVersion()}
|
|
||||||
InvenTree {InvenTreeVersion.inventreeVersion()}
|
InvenTree {InvenTreeVersion.inventreeVersion()}
|
||||||
API {InvenTreeVersion.inventreeApiVersion()}
|
API {InvenTreeVersion.inventreeApiVersion()}
|
||||||
|
Python {python_version()}
|
||||||
|
Django {InvenTreeVersion.inventreeDjangoVersion()}
|
||||||
Node {node if node else NA}
|
Node {node if node else NA}
|
||||||
Yarn {yarn if yarn else NA}
|
Yarn {yarn if yarn else NA}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user