changeset 3974:0347889df050

merge with 1.8 main branch
author Byeongweon [tasyblue@gmail.com]
date Mon, 21 Jul 2008 15:57:56 +0900
parents d2200a05c888 (current diff) 6b9d964c70dc (diff)
children d8771735a6db
files
diffstat 18 files changed, 129 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/action/_tests/test_cache.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/action/_tests/test_cache.py	Mon Jul 21 15:57:56 2008 +0900
@@ -60,7 +60,8 @@
         request = self.request
         key = 'nooneknowsit'
         data = "dontcare"
-        url = cache.put(request, key, data)
+        cache.put(request, key, data)
+        url = cache.url(request, key)
 
         assert key in url
         meta_cache = caching.CacheEntry(request,
@@ -78,7 +79,8 @@
         key = 'nooneknowsit'
         filename = "test.png"
         data = "dontcare"
-        url = cache.put(request, key, data, filename=filename, last_modified=1)
+        cache.put(request, key, data, filename=filename, last_modified=1)
+        url = cache.url(request, key)
         assert key in url
 
         meta_cache = caching.CacheEntry(request,
@@ -97,7 +99,8 @@
         filename = "test.png"
         data = "dontcareatall"
         data_file = StringIO.StringIO(data)
-        url = cache.put(request, key, data_file)
+        cache.put(request, key, data_file)
+        url = cache.url(request, key)
 
         assert key in url
         meta_cache = caching.CacheEntry(request,
@@ -143,7 +146,8 @@
         rendered1 = render(source)
         key1 = keycalc(source)
 
-        url1 = cache.put(request, key1, rendered1)
+        cache.put(request, key1, rendered1)
+        url1 = cache.url(request, key1)
         assert 'key=%s' % key1 in url1
 
         data_cache = caching.CacheEntry(request,
@@ -160,7 +164,8 @@
         rendered2 = render(source)
         key2 = keycalc(source)
 
-        url2 = cache.put(request, key2, rendered2)
+        cache.put(request, key2, rendered2)
+        url2 = cache.url(request, key2)
         assert 'key=%s' % key2 in url2
 
         data_cache = caching.CacheEntry(request,
--- a/MoinMoin/action/cache.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/action/cache.py	Mon Jul 21 15:57:56 2008 +0900
@@ -3,23 +3,25 @@
     MoinMoin - Send a raw object from the caching system (and offer utility
     functions to put data into cache, calculate cache key, etc.).
 
-    This can be used e.g. for all image generating extensions:
-    E.g. a thumbnail generating extension just uses cache.put() to
-    write the thumbnails into the cache and emits <img src="cache_url">
-    to display them. cache_url is returned by put() or url().
-
-    IMPORTANT: use some non-guessable key derived from your source content,
-               use cache.key() if you don't have something better.
+    Sample usage
+    ------------
+    Assume we have a big picture (bigpic) and we want to efficiently show some
+    thumbnail (thumbpic) for it:
 
-    TODO:
-    * add secret to wikiconfig
-    * add error handling
-    * maybe use page local caching, not global:
-      + smaller directories
-      - but harder to clean
-      - harder to backup data_dir
-    * move file-like code to caching module
-    * add auto-key generation?
+    # first calculate a (hard to guess) cache key (this key will change if the
+    # original data (bigpic) changes):
+    key = cache.key(..., attachname=bigpic, ...)
+
+    # check if we don't have it in cache yet
+    if not cache.exists(..., key):
+        # if we don't have it in cache, we need to render it - this is an
+        # expensive operation that we want to avoid by caching:
+        thumbpic = render_thumb(bigpic)
+        # put expensive operation's results into cache:
+        cache.put(..., key, thumbpic, ...)
+
+    url = cache.url(..., key)
+    html = '<img src="%s">' % url
 
     @copyright: 2008 MoinMoin:ThomasWaldmann
     @license: GNU GPL, see COPYING for details.
@@ -42,13 +44,29 @@
 
 # Do NOT get this directly from request.form or user would be able to read any cache!
 cache_arena = 'sendcache'  # just using action_name is maybe rather confusing
+
+# We maybe could use page local caching (not 'wiki' global) to have less directory entries.
+# Local is easier to automatically cleanup if an item changes. Global is easier to manually cleanup.
+# Local makes data_dir much larger, harder to backup.
 cache_scope = 'wiki'
+
 do_locking = False
 
 def key(request, wikiname=None, itemname=None, attachname=None, content=None, secret=None):
     """
     Calculate a (hard-to-guess) cache key.
 
+    Important key properties:
+    * The key must be hard to guess (this is because do=get does no ACL checks,
+      so whoever got the key [e.g. from html rendering of an ACL protected wiki
+      page], will be able to see the cached content.
+    * The key must change if the (original) content changes. This is because
+      ACLs on some item may change and even if somebody was allowed to see some
+      revision of some item, it does not implicate that he is allowed to see
+      any other revision also. There will be no harm if he can see exactly the
+      same content again, but there could be harm if he could access a revision
+      with different content.
+
     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
@@ -66,8 +84,10 @@
     @param attachname: the filename of the attachment
     @param content: content data as unicode object (e.g. for page content or
                     parser section content)
+    @param secret: secret for hMAC calculation (default: use secret from cfg)
     """
-    secret = secret or 'nobodyexpectedsuchasecret'
+    if secret is None:
+        secret = request.cfg.secrets['action/cache']
     if content:
         hmac_data = content
     elif itemname is not None and attachname is not None:
@@ -101,7 +121,6 @@
     @param content_type: content-type header value (str, default: autodetect from filename)
     @param last_modified: last modified timestamp (int, default: autodetect)
     @param content_length: data length for content-length header (int, default: autodetect)
-    @return: URL of cached object
     """
     import os.path
     from MoinMoin.util import timefuncs
@@ -138,8 +157,6 @@
     meta_cache = caching.CacheEntry(request, cache_arena, key+'.meta', cache_scope, do_locking=do_locking, use_pickle=True)
     meta_cache.update((last_modified, headers))
 
-    return url(request, key, do='get')
-
 
 def exists(request, key, strict=False):
     """
@@ -193,12 +210,16 @@
 
 def _do_get(request, key):
     """ send a complete http response with headers/data cached for key """
-    last_modified, headers = _get_headers(request, key)
-    if request.if_modified_since == last_modified:
-        request.emit_http_headers(["Status: 304 Not modified"])
-    else:
-        request.emit_http_headers(headers)
-        request.send_file(_get_datafile(request, key))
+    try:
+        last_modified, headers = _get_headers(request, key)
+        if request.if_modified_since == last_modified:
+            request.emit_http_headers(["Status: 304 Not modified"])
+        else:
+            data_file = _get_datafile(request, key)
+            request.emit_http_headers(headers)
+            request.send_file(data_file)
+    except caching.CacheError:
+        request.emit_http_headers(["Status: 404 Not found"])
 
 
 def _do_remove(request, key):
--- a/MoinMoin/action/revert.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/action/revert.py	Mon Jul 21 15:57:56 2008 +0900
@@ -3,7 +3,7 @@
     MoinMoin - revert a page to a previous revision
 
     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
-                2006 MoinMoin:ThomasWaldmann,
+                2006-2008 MoinMoin:ThomasWaldmann,
                 2007 MoinMoin:ReimarBauer,
                 2008 MoinMoin:JohannesBerg
     @license: GNU GPL, see COPYING for details.
@@ -30,7 +30,7 @@
         # as well
         _ = self._
         may = self.request.user.may
-        allowed = may.write(self.pagename) and may.delete(self.pagename)
+        allowed = may.write(self.pagename) and may.revert(self.pagename)
         return allowed, _('You are not allowed to revert this page!')
 
     def check_condition(self):
@@ -42,9 +42,6 @@
                      'If you want to revert to an older revision, first view that older revision and '
                      'then call revert to this (older) revision again.')
             return note
-
-        if not self.page.exists():
-            return _('This page is already deleted or was never created!')
         else:
             return None
 
--- a/MoinMoin/config/_tests/test_configs.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/config/_tests/test_configs.py	Mon Jul 21 15:57:56 2008 +0900
@@ -9,5 +9,6 @@
     def testConfigs(self):
         for cls in _tests:
             cls.data_dir = self.request.cfg.data_dir
+            cls.secrets = self.request.cfg.secrets
             # quite a bad hack to make _importPlugin succeed
             cls('MoinMoin')
--- a/MoinMoin/config/multiconfig.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/config/multiconfig.py	Mon Jul 21 15:57:56 2008 +0900
@@ -345,14 +345,6 @@
 
         # if we are to use the jabber bot, instantiate a server object for future use
         if self.jabber_enabled:
-
-            errmsg = "You must set a (long) secret string to send notifications!"
-            try:
-                if not self.secret:
-                    raise error.ConfigurationError(errmsg)
-            except AttributeError, err:
-                raise error.ConfigurationError(errmsg)
-
             from xmlrpclib import Server
             self.notification_server = Server(self.notification_bot_uri, )
 
@@ -370,6 +362,31 @@
         if self.url_prefix_local is None:
             self.url_prefix_local = self.url_prefix_static
 
+        secret_key_names = ['action/cache', 'wikiutil/tickets', 'xmlrpc/ProcessMail', 'xmlrpc/RemoteScript', ]
+        if self.jabber_enabled:
+            secret_key_names.append('jabberbot')
+
+        secret_min_length = 10
+        if isinstance(self.secrets, str):
+            if len(self.secrets) < secret_min_length:
+                raise error.ConfigurationError("The secrets = '...' wiki config setting is a way too short string (minimum length is %d chars)!" % (
+                    secret_min_length))
+            # for lazy people: set all required secrets to same value
+            secrets = {}
+            for key in secret_key_names:
+                secrets[key] = self.secrets
+            self.secrets = secrets
+
+        # we check if we have all secrets we need and that they have minimum length
+        for secret_key_name in secret_key_names:
+            try:
+                secret = self.secrets[secret_key_name]
+                if len(secret) < secret_min_length:
+                    raise ValueError
+            except (KeyError, ValueError):
+                raise error.ConfigurationError("You must set a (at least %d chars long) secret string for secrets['%s']!" % (
+                    secret_min_length, secret_key_name))
+
     _meta_dict = None
     def load_meta_dict(self):
         """ The meta_dict contains meta data about the wiki instance. """
@@ -677,6 +694,7 @@
      "list of auth objects, to be called in this order (see HelpOnAuthentication)"),
     ('auth_methods_trusted', ['http', 'xmlrpc_applytoken'],
      'authentication methods for which users should be included in the special "Trusted" ACL group.'),
+    ('secrets', '', 'Either a long shared secret string used for multiple purposes or a dict {"purpose": "longsecretstring", ...} for setting up different shared secrets for different purposes. REQUIRED!'),
     ('DesktopEdition',
      False,
      "if True, give all local users special powers - ''only use this for a local desktop wiki!''"),
@@ -1145,7 +1163,6 @@
       ('smarthost', None, "Address of SMTP server to use for sending mail (None = don't use SMTP server)."),
       ('sendmail', None, "sendmail command to use for sending mail (None = don't use sendmail)"),
 
-      ('import_secret', "", "Shared secret for mail importing"),
       ('import_subpage_template', u"$from-$date-$subject", "Create subpages using this template when importing mail."),
       ('import_pagename_search', ['subject', 'to', ], "Where to look for target pagename specification."),
       ('import_pagename_envelope', u"%s", "Use this to add some fixed prefix/postfix to the generated target pagename."),
--- a/MoinMoin/events/_tests/test_events.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/events/_tests/test_events.py	Mon Jul 21 15:57:56 2008 +0900
@@ -59,7 +59,7 @@
 
     event = events.UserCreatedEvent(request, User(request))
     request.cfg.notification_server = server_dummy()
-    request.cfg.secret = "dummy"
+    request.cfg.secrets = "thisisnotsecret"
 
     jabbernotify.handle_user_created(event)
     assert request.cfg.notification_server.sent is True
--- a/MoinMoin/events/jabbernotify.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/events/jabbernotify.py	Mon Jul 21 15:57:56 2008 +0900
@@ -50,11 +50,12 @@
 
     request = event.request
     server = request.cfg.notification_server
+    secret = request.cfg.secrets['jabberbot']
     try:
         if isinstance(event, ev.JabberIDSetEvent):
-            server.addJIDToRoster(request.cfg.secret, event.jid)
+            server.addJIDToRoster(secret, event.jid)
         else:
-            server.removeJIDFromRoster(request.cfg.secret, event.jid)
+            server.removeJIDFromRoster(secret, event.jid)
     except xmlrpclib.Error, err:
         logging.error("XML RPC error: %s" % str(err))
     except Exception, err:
@@ -197,7 +198,7 @@
         raise ValueError("url_list must be of type list!")
 
     try:
-        server.send_notification(request.cfg.secret, jids, notification)
+        server.send_notification(request.cfg.secrets['jabberbot'], jids, notification)
         return True
     except xmlrpclib.Error, err:
         logging.error("XML RPC error: %s" % str(err))
--- a/MoinMoin/macro/MonthCalendar.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/macro/MonthCalendar.py	Mon Jul 21 15:57:56 2008 +0900
@@ -365,6 +365,7 @@
                             title = match.group(1)
                             title = wikiutil.escape(title).replace("'", "\\'")
                             titletext.append(title)
+                    link = wikiutil.escape(link).replace("'", "\\'")
                     tipname = link
                     tiptitle = link
                     tiptext = '<br>'.join(titletext)
--- a/MoinMoin/request/__init__.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/request/__init__.py	Mon Jul 21 15:57:56 2008 +0900
@@ -263,10 +263,11 @@
         if not limits:
             return False
 
+        if self.remote_addr.startswith('127.'): # localnet
+            return False
+
         validuser = self.user.valid
         current_id = validuser and self.user.name or self.remote_addr
-        if not validuser and current_id.startswith('127.'): # localnet
-            return False
         current_action = self.action
 
         default_limit = self.cfg.surge_action_limits.get('default', (30, 60))
--- a/MoinMoin/wikiutil.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/wikiutil.py	Mon Jul 21 15:57:56 2008 +0900
@@ -2432,7 +2432,7 @@
 ########################################################################
 
 def createTicket(request, tm=None, action=None):
-    """ Create a ticket using a site-specific secret (the config)
+    """ Create a ticket using a configured secret
 
         @param tm: unix timestamp (optional, uses current time if not given)
         @param action: action name (optional, uses current action if not given)
@@ -2457,20 +2457,12 @@
         except:
             action = 'None'
 
+    secret = request.cfg.secrets['wikiutil/tickets']
+    digest = sha.new(secret)
 
     ticket = "%s.%s.%s" % (tm, pagename, action)
-    digest = sha.new()
     digest.update(ticket)
 
-    varnames = ['data_dir', 'data_underlay_dir', 'language_default',
-                'mail_smarthost', 'mail_from', 'page_front_page',
-                'theme_default', 'sitename', 'logo_string',
-                'interwikiname', 'user_homewiki', 'acl_rights_before', ]
-    for varname in varnames:
-        var = getattr(request.cfg, varname, None)
-        if isinstance(var, (str, unicode)):
-            digest.update(repr(var))
-
     return "%s.%s" % (ticket, digest.hexdigest())
 
 
--- a/MoinMoin/xmlrpc/ProcessMail.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/xmlrpc/ProcessMail.py	Mon Jul 21 15:57:56 2008 +0900
@@ -16,10 +16,7 @@
     secret = xmlrpcobj._instr(secret)
     mail = str(mail)
 
-    if not request.cfg.mail_import_secret:
-        return u"No password set"
-
-    if request.cfg.mail_import_secret != secret:
+    if request.cfg.secrets['xmlrpc/ProcessMail'] != secret:
         return u"Invalid password"
 
     try:
--- a/MoinMoin/xmlrpc/RemoteScript.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/xmlrpc/RemoteScript.py	Mon Jul 21 15:57:56 2008 +0900
@@ -15,9 +15,7 @@
     request = xmlrpcobj.request
     their_secret = xmlrpcobj._instr(their_secret)
 
-    our_secret = request.cfg.remote_script_secret
-    if not our_secret:
-        return u"No password set"
+    our_secret = request.cfg.secrets['xmlrpc/RemoteScript']
 
     if our_secret != their_secret:
         return u"Invalid password"
--- a/MoinMoin/xmlrpc/__init__.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/MoinMoin/xmlrpc/__init__.py	Mon Jul 21 15:57:56 2008 +0900
@@ -702,7 +702,7 @@
 
         @param jid: a bare Jabber ID
         """
-        if self.cfg.secret != secret:
+        if self.cfg.secrets['jabberbot'] != secret:
             return ""
 
         u = self.request.handle_jid_auth(jid)
--- a/jabberbot/config.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/jabberbot/config.py	Mon Jul 21 15:57:56 2008 +0900
@@ -41,9 +41,8 @@
 
     # A secret shared with Wiki , must be the same in both
     # configs for communication to work.
-    #
-    # CHANGE IT TO A LONG RANDOM STRING, OR YOU WILL HAVE A SECURITY ISSUE!
-    secret = u""
+    secret = "use same string as in secrets setting in wiki config"
+
 
     # Maximum number of items in service discovery cache (XEP-0115)
     disco_cache_size = 100
--- a/tests/wikiconfig.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/tests/wikiconfig.py	Mon Jul 21 15:57:56 2008 +0900
@@ -20,6 +20,9 @@
     data_underlay_dir = os.path.join(_base_dir, "underlay")
 
     show_hosts = 1
+
+    secrets = 'some not secret string just to make tests happy'
+
     # used to check if it is really a wiki we may modify
     is_test_wiki = True
 
--- a/wiki/config/more_samples/mailimportconf.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/wiki/config/more_samples/mailimportconf.py	Mon Jul 21 15:57:56 2008 +0900
@@ -1,7 +1,7 @@
 # This is the configuration file for the mail import client
 
 # This secret has to be known by the wiki server
-mail_import_secret = u"foo"
+mail_import_secret = "use same string as in secrets setting in wiki config"
 
 # The target wiki URL
 mail_import_url = u"http://localhost/?action=xmlrpc2"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/config/more_samples/secrets_wikiconfig_snippet	Mon Jul 21 15:57:56 2008 +0900
@@ -0,0 +1,21 @@
+    # MoinMoin uses (shared) secrets in some subsystems. These secrets are NOT passwords, you
+    # will never have to enter them as a moin wiki user. Don't re-use some of your passwords
+    # as a secret, just use some long random string.
+    # For example, the 'action/cache' secret is used to calculate unpredictable cache access keys.
+    # The 'mailimport' secret is a shared secret for importing e-mail via xmlrpc (you will have
+    # to copy it to the mailimporter script).
+    #
+    # IMPORTANT: Don't use the strings below, they are NOT secret as they are part of the moin
+    #            distribution archive's config samples.
+
+    # For low security requirements (all subsystems will use this same secret then):
+    secrets = 'I&TNny^UVBUasdK^NN&T^^RyyujB^UN^B^UNBasrgrttdNFU^BFNasad'
+
+    # For higher security requirements, you can use different secrets for different subsystems:
+    secrets = {
+            'action/cache':'35gnb35h8g0835hgnbe035g85b8',
+            'wikiutil/tickets': 'asdasdvarebtbertbaetbtrbetgrergfqe3r',
+            'jabberbot': 'asasegs5hg5h64he56h5e6j5e6uhgsewhye56h5jne56hj56',
+            'xmlrpc/ProcessMail': '324tgw2g3q3gw3g3wg3353ehb',
+            'xmlrpc/RemoteScript': 'kuIUYBO85jtf932l:-0aGf',
+    }
--- a/wikiconfig.py	Thu Jul 17 16:29:23 2008 +0900
+++ b/wikiconfig.py	Mon Jul 21 15:57:56 2008 +0900
@@ -23,9 +23,8 @@
     page_front_page = u'FrontPage' # change to some better value
     # ^^^ DON'T TOUCH THIS EXCEPT IF YOU KNOW WHAT YOU DO ^^^
 
-
     # Add your configuration items here.
-
+    secrets = 'This string is NOT a secret, please make up your own, long, random secret string!'
 
 
 # DEVELOPERS! Do not add your configuration items there,