Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions django/core/cache/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ def get_key_func(key_func):
class BaseCache:
_missing_key = object()

def check(self, **kwargs):
"""
Check the cache backend for potential issues.
Returns a list of Warnings and Errors.
"""
return []

def __init__(self, params):
timeout = params.get("timeout", params.get("TIMEOUT", 300))
if timeout is not None:
Expand Down
53 changes: 53 additions & 0 deletions django/core/cache/backends/filebased.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"File-based cache backend"

import glob
Expand All @@ -18,6 +18,59 @@
cache_suffix = ".djcache"
pickle_protocol = pickle.HIGHEST_PROTOCOL

def check(self, **kwargs):
from django.conf import settings
from django.core.checks import Warning
import pathlib

errors = super().check(**kwargs)
alias = kwargs.get('alias', '')
alias_prefix = f"'{alias}' " if alias else ""

# Check if the cache directory path is absolute
if not pathlib.Path(self._dir).is_absolute():
errors.append(
Warning(
f"Your {alias_prefix}cache LOCATION path is relative. Use an "
f"absolute path instead.",
id="caches.W003",
)
)

# Check if the cache directory is exposed or might lead to data corruption
for name in ("MEDIA_ROOT", "STATIC_ROOT", "STATICFILES_DIRS"):
setting = getattr(settings, name, None)
if not setting:
continue
if name == "STATICFILES_DIRS":
paths = set()
for staticfiles_dir in setting:
if isinstance(staticfiles_dir, (list, tuple)):
_, staticfiles_dir = staticfiles_dir
paths.add(pathlib.Path(staticfiles_dir).resolve())
else:
paths = {pathlib.Path(setting).resolve()}

cache_path = pathlib.Path(self._dir).resolve()
if any(path == cache_path for path in paths):
relation = "matches"
elif any(path in cache_path.parents for path in paths):
relation = "is inside"
elif any(cache_path in path.parents for path in paths):
relation = "contains"
else:
continue
errors.append(
Warning(
f"Your {alias_prefix}cache configuration might expose your cache "
f"or lead to corruption of your data because its LOCATION "
f"{relation} {name}.",
id="caches.W002",
)
)

return errors

def __init__(self, dir, params):
super().__init__(params)
self._dir = os.path.abspath(dir)
Expand Down
64 changes: 9 additions & 55 deletions django/core/checks/caches.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import pathlib

from django.conf import settings
from django.core.cache import DEFAULT_CACHE_ALIAS, caches
from django.core.cache.backends.filebased import FileBasedCache

from . import Error, Tags, Warning, register
from . import Error, Tags, register

E001 = Error(
"You must define a '%s' cache in your CACHES setting." % DEFAULT_CACHE_ALIAS,
Expand All @@ -19,58 +16,15 @@ def check_default_cache_is_configured(app_configs, **kwargs):
return []


@register(Tags.caches, deploy=True)
def check_cache_location_not_exposed(app_configs, **kwargs):
errors = []
for name in ("MEDIA_ROOT", "STATIC_ROOT", "STATICFILES_DIRS"):
setting = getattr(settings, name, None)
if not setting:
continue
if name == "STATICFILES_DIRS":
paths = set()
for staticfiles_dir in setting:
if isinstance(staticfiles_dir, (list, tuple)):
_, staticfiles_dir = staticfiles_dir
paths.add(pathlib.Path(staticfiles_dir).resolve())
else:
paths = {pathlib.Path(setting).resolve()}
for alias in settings.CACHES:
cache = caches[alias]
if not isinstance(cache, FileBasedCache):
continue
cache_path = pathlib.Path(cache._dir).resolve()
if any(path == cache_path for path in paths):
relation = "matches"
elif any(path in cache_path.parents for path in paths):
relation = "is inside"
elif any(cache_path in path.parents for path in paths):
relation = "contains"
else:
continue
errors.append(
Warning(
f"Your '{alias}' cache configuration might expose your cache "
f"or lead to corruption of your data because its LOCATION "
f"{relation} {name}.",
id="caches.W002",
)
)
return errors


@register(Tags.caches)
def check_file_based_cache_is_absolute(app_configs, **kwargs):
def check_cache_backends(app_configs, **kwargs):
"""
Call the check() method on each cache backend to collect errors and warnings.
"""
errors = []
for alias, config in settings.CACHES.items():
for alias in settings.CACHES:
cache = caches[alias]
if not isinstance(cache, FileBasedCache):
continue
if not pathlib.Path(config["LOCATION"]).is_absolute():
errors.append(
Warning(
f"Your '{alias}' cache LOCATION path is relative. Use an "
f"absolute path instead.",
id="caches.W003",
)
)
# Add the alias to the kwargs so the cache backend can include it in warnings
backend_kwargs = {**kwargs, "alias": alias}
errors.extend(cache.check(**backend_kwargs))
return errors
19 changes: 9 additions & 10 deletions tests/check_framework/test_caches.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
from django.core.checks import Warning
from django.core.checks.caches import (
E001,
check_cache_location_not_exposed,
check_cache_backends,
check_default_cache_is_configured,
check_file_based_cache_is_absolute,
)
from django.test import SimpleTestCase
from django.test.utils import override_settings
Expand Down Expand Up @@ -63,7 +62,7 @@ def test_cache_path_matches_media_static_setting(self):
with self.subTest(setting=setting), self.settings(**settings):
msg = self.warning_message % ("matches", setting)
self.assertEqual(
check_cache_location_not_exposed(None),
check_cache_backends(None),
[
Warning(msg, id="caches.W002"),
],
Expand All @@ -76,7 +75,7 @@ def test_cache_path_inside_media_static_setting(self):
with self.subTest(setting=setting), self.settings(**settings):
msg = self.warning_message % ("is inside", setting)
self.assertEqual(
check_cache_location_not_exposed(None),
check_cache_backends(None),
[
Warning(msg, id="caches.W002"),
],
Expand All @@ -89,7 +88,7 @@ def test_cache_path_contains_media_static_setting(self):
with self.subTest(setting=setting), self.settings(**settings):
msg = self.warning_message % ("contains", setting)
self.assertEqual(
check_cache_location_not_exposed(None),
check_cache_backends(None),
[
Warning(msg, id="caches.W002"),
],
Expand All @@ -100,7 +99,7 @@ def test_cache_path_not_conflict(self):
for setting in ("MEDIA_ROOT", "STATIC_ROOT", "STATICFILES_DIRS"):
settings = self.get_settings(setting, root / "cache", root / "other")
with self.subTest(setting=setting), self.settings(**settings):
self.assertEqual(check_cache_location_not_exposed(None), [])
self.assertEqual(check_cache_backends(None), [])

def test_staticfiles_dirs_prefix(self):
root = pathlib.Path.cwd()
Expand All @@ -118,7 +117,7 @@ def test_staticfiles_dirs_prefix(self):
with self.subTest(path=setting_path), self.settings(**settings):
msg = self.warning_message % (msg, "STATICFILES_DIRS")
self.assertEqual(
check_cache_location_not_exposed(None),
check_cache_backends(None),
[
Warning(msg, id="caches.W002"),
],
Expand All @@ -132,7 +131,7 @@ def test_staticfiles_dirs_prefix_not_conflict(self):
("prefix", root / "other"),
)
with self.settings(**settings):
self.assertEqual(check_cache_location_not_exposed(None), [])
self.assertEqual(check_cache_backends(None), [])


class CheckCacheAbsolutePath(SimpleTestCase):
Expand All @@ -145,7 +144,7 @@ def test_absolute_path(self):
},
}
):
self.assertEqual(check_file_based_cache_is_absolute(None), [])
self.assertEqual(check_cache_backends(None), [])

def test_relative_path(self):
with self.settings(
Expand All @@ -157,7 +156,7 @@ def test_relative_path(self):
}
):
self.assertEqual(
check_file_based_cache_is_absolute(None),
check_cache_backends(None),
[
Warning(
"Your 'default' cache LOCATION path is relative. Use an "
Expand Down
Loading