Caching issues in django

Yesterday I stumbled upon an interesting and broken behaviour of Django: Running two instances with the same memcached as cache backend might result in displaying the wrong template from the cache in case we have template files with the same name but a different content.

This behaviour is also visible in all django applications which use the cache backend without taking care of settings.CACHE_MIDDLEWARE_KEY_PREFIX. As mentioned in the bug report a design decision is necessary here, as the setting was only supposed to be used in middlewares. On the other side not honouring the setting everywhere makes it impossible to use different (unrelated) Django instances with one memcached. While this is not such a big issue for template data, it should be - for example - possible to insert faked django CMS user permissions into memcached as they are cached there, too.

One possible solution is to ensure that settings.CACHE_MIDDLEWARE_KEY_PREFIX (or a similar setting with a better name) is used for every access. Using the following snippet as custom cache backend ensures this. Additionally it is much harder to insert faked cache content by using a md5 hexdigest as key. Although you should never store sensitive data in memcached - if you need to cache such data, use locmem, file or db as cache backend, as they allow to limit access to the data properly.

from django.conf import settings
from django.core.cache.backends.memcached import CacheClass as DjangoMemcachedCacheClass
from django.utils.hashcompat import md5_constructor

class CacheClass(DjangoMemcachedCacheClass):
    def __init__(self, server, params):
        self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
        super(CacheClass, self).__init__(server, params)

    def _genkey(self, origkey):
        return md5_constructor("%s:%s" %(self.key_prefix, origkey)).hexdigest()

    def add(self, key, value, timeout=0):
        return super(CacheClass, self).add(self._genkey(key), value, timeout)

    def get(self, key, default=None):
        return super(CacheClass, self).get(self._genkey(key), default)

    def set(self, key, value, timeout=0):
        return super(CacheClass, self).set(self._genkey(key), value, timeout)

    def delete(self, key):
        return super(CacheClass, self).delete(self._genkey(key))

    def get_many(self, keys):
        return super(CacheClass, self).get_many(self._genkey(key))

    def incr(self, key, delta=1):
        return super(CacheClass, self).incr(self._genkey(key), delta)

    def decr(self, key, delta=1):
        return super(CacheClass, self).decr(self._genkey(key), delta)