changeset 3833:712e5938ec59

sendcached action: new function cache_key to calculate hard-to-guess cache keys from either content (parser sections) or wiki/page/file name and some metadata from filesystem (attachments)
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sat, 12 Jul 2008 15:53:01 +0200
parents 682fd34b9422
children 27ddf6dfa7bd
files MoinMoin/action/_tests/test_sendcached.py MoinMoin/action/sendcached.py
diffstat 2 files changed, 78 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/action/_tests/test_sendcached.py	Fri Jul 11 16:26:28 2008 +0200
+++ b/MoinMoin/action/_tests/test_sendcached.py	Sat Jul 12 15:53:01 2008 +0200
@@ -9,7 +9,7 @@
 import os, StringIO
 
 from MoinMoin import caching
-from MoinMoin.action import sendcached
+from MoinMoin.action import AttachFile, sendcached
 
 from MoinMoin._tests import become_trusted, create_page, nuke_page
 
@@ -17,6 +17,44 @@
     """ testing action sendcached """
     pagename = u"AutoCreatedSillyPageToTestAttachments"
 
+    def test_cache_key_content(self):
+        request = self.request
+        result1 = sendcached.cache_key(request, content='foo', secret='bar')
+        result2 = sendcached.cache_key(request, content='foo', secret='baz')
+        assert result1  # not empty
+        assert result1 != result2  # different for different secret
+        result3 = sendcached.cache_key(request, content='foofoo', secret='baz')
+        assert result3 != result2  # different for different content
+        result4 = sendcached.cache_key(request, content='foo'*1000, secret='baz')
+        assert len(result4) == len(result3)  # same length of key for different input lengths
+
+    def test_cache_key_attachment(self):
+        request = self.request
+        pagename = self.pagename
+        attachname = 'foo.txt'
+
+        become_trusted(request)
+        create_page(request, pagename, u"Foo!")
+
+        AttachFile.add_attachment(request, pagename, attachname, "Test content1", True)
+
+        result1 = sendcached.cache_key(request, itemname=pagename, attachname=attachname, secret='bar')
+        result2 = sendcached.cache_key(request, itemname=pagename, attachname=attachname, secret='baz')
+        assert result1  # not empty
+        assert result1 != result2  # different for different secret
+
+        # test below does not work, because mtime is often same, inode can be same due to how add_attachment
+        # works, file size is same, attachment name is same, wikiname/pagename is same.
+        # In practice, this should rather rarely cause problems:
+        #AttachFile.add_attachment(request, pagename, attachname, "Test content2", True)
+        #result3 = sendcached.cache_key(request, itemname=pagename, attachname=attachname, secret='baz')
+        #assert result3 != result2  # different for different content
+
+        AttachFile.add_attachment(request, pagename, attachname, "Test content33333", True)
+        result4 = sendcached.cache_key(request, itemname=pagename, attachname=attachname, secret='baz')
+        assert len(result4) == len(result2)  # same length of key for different input lengths
+        nuke_page(request, pagename)
+
     def test_put_cache_minimal(self):
         """Test if put_cache() works"""
         request = self.request
--- a/MoinMoin/action/sendcached.py	Fri Jul 11 16:26:28 2008 +0200
+++ b/MoinMoin/action/sendcached.py	Sat Jul 12 15:53:01 2008 +0200
@@ -10,6 +10,7 @@
     IMPORTANT: use some non-guessable key derived from your source content.
 
     TODO:
+    * add secret to wikiconfig
     * add error handling
     * maybe use page local caching, not global:
       + smaller directories
@@ -22,15 +23,19 @@
     @license: GNU GPL, see COPYING for details.
 """
 
+import hmac, sha
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import config, caching
-
 # keep both imports below as they are, order is important:
 from MoinMoin import wikiutil
 import mimetypes
 
+from MoinMoin import config, caching
+from MoinMoin.util import filesys
+from MoinMoin.action import AttachFile
+
 action_name = 'sendcached'
 
 # Do NOT get this directly from request.form or user would be able to read any cache!
@@ -38,6 +43,38 @@
 sendcached_scope = 'wiki'
 do_locking = False
 
+def cache_key(request, wikiname=None, itemname=None, attachname=None, content=None, secret=None):
+    """
+    Calculate a (hard-to-guess) cache key.
+
+    If content is supplied, we will calculate and return a hMAC of the content.
+
+    If wikiname, itemname, attachname is given, we don't touch the content (nor do
+    we read it ourselves from the attachment file), but we just calculate a key
+    from the given metadata values and some metadata we get from the filesystem.
+
+    @param request: the request object
+    @param wikiname: the name of the wiki (if not given, will be read from cfg)
+    @param itemname: the name of the page
+    @param attachname: the filename of the attachment
+    @param content: content data as unicode object (e.g. for page content or
+                    parser section content)
+    """
+    secret = secret or 'nobodyexpectedsuchasecret'
+    if content:
+        hmac_data = content
+    elif itemname is not None and attachname is not None:
+        wikiname = wikiname or request.cfg.interwikiname or request.cfg.siteid
+        fuid = filesys.fuid(AttachFile.getFilename(request, itemname, attachname))
+        hmac_data = u''.join([wikiname, itemname, attachname, repr(fuid)])
+    else:
+        raise AssertionError('cache_key called with unsupported parameters')
+
+    hmac_data = hmac_data.encode('utf-8')
+    key = hmac.new(secret, hmac_data, sha).hexdigest()
+    return key
+
+
 def put_cache(request, key, data,
               filename=None,
               content_type=None,